浏览代码

support real-time rendered cube maps

David Rose 21 年之前
父节点
当前提交
c045c5894d

+ 28 - 0
panda/src/display/displayRegion.I

@@ -50,6 +50,34 @@ get_sort() const {
   return _sort;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegion::set_cube_map_index
+//       Access: Published
+//  Description: This is a special parameter that is only used when
+//               rendering the faces of a cube map.  Normally you
+//               should not need to set it directly.  This sets up the
+//               DisplayRegion to render to the nth cube map face; the
+//               value must be between 0 and 5, inclusive.  A normal
+//               DisplayRegion that is not associated with any
+//               particular cube map should be set to -1.
+////////////////////////////////////////////////////////////////////
+INLINE void DisplayRegion::
+set_cube_map_index(int cube_map_index) {
+  _cube_map_index = cube_map_index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegion::get_cube_map_index
+//       Access: Published
+//  Description: Returns the cube map face index associated with this
+//               particular DisplayRegion, or -1 if it is not
+//               associated with a cube map.  See
+//               set_cube_map_index().
+////////////////////////////////////////////////////////////////////
+INLINE int DisplayRegion::
+get_cube_map_index() const {
+  return _cube_map_index;
+}
 
 INLINE ostream &operator << (ostream &out, const DisplayRegion &dr) {
   dr.output(out);

+ 5 - 3
panda/src/display/displayRegion.cxx

@@ -37,7 +37,8 @@ DisplayRegion(GraphicsOutput *window) :
   _window(window),
   _camera_node((Camera *)NULL),
   _active(true),
-  _sort(0)
+  _sort(0),
+  _cube_map_index(-1)
 {
   _draw_buffer_type = window->get_draw_buffer_type();
   compute_pixels();
@@ -55,7 +56,8 @@ DisplayRegion(GraphicsOutput *window, const float l,
   _window(window),
   _camera_node((Camera *)NULL),
   _active(true),
-  _sort(0)
+  _sort(0),
+  _cube_map_index(-1)
 {
   _draw_buffer_type = window->get_draw_buffer_type();
   compute_pixels();
@@ -535,7 +537,7 @@ get_screenshot(PNMImage &image) {
   PT(Texture) tex = new Texture;
 
   RenderBuffer buffer = gsg->get_render_buffer(get_screenshot_buffer_type());
-  if (!gsg->framebuffer_copy_to_ram(tex, this, buffer)) {
+  if (!gsg->framebuffer_copy_to_ram(tex, -1, this, buffer)) {
     return false;
   }
 

+ 4 - 0
panda/src/display/displayRegion.h

@@ -82,6 +82,9 @@ PUBLISHED:
   void set_sort(int sort);
   INLINE int get_sort() const;
 
+  INLINE void set_cube_map_index(int cube_map_index);
+  INLINE int get_cube_map_index() const;
+
   void compute_pixels();
   void compute_pixels(int x_size, int y_size);
   void get_pixels(int &pl, int &pr, int &pb, int &pt) const;
@@ -123,6 +126,7 @@ private:
 
   bool _active;
   int _sort;
+  int _cube_map_index;
 
   // This is used to cache the culling result from last frame's
   // drawing into this display region.  It should only be accessed or

+ 15 - 20
panda/src/display/graphicsEngine.cxx

@@ -240,7 +240,7 @@ make_window(GraphicsStateGuardian *gsg, const string &name, int sort) {
 ////////////////////////////////////////////////////////////////////
 GraphicsOutput *GraphicsEngine::
 make_buffer(GraphicsStateGuardian *gsg, const string &name, 
-            int sort, int x_size, int y_size, bool want_texture) {
+            int sort, int x_size, int y_size) {
   if (show_buffers) {
     GraphicsWindow *window = make_window(gsg, name, sort);
     if (window != (GraphicsWindow *)NULL) {
@@ -249,11 +249,6 @@ make_buffer(GraphicsStateGuardian *gsg, const string &name,
       props.set_fixed_size(true);
       props.set_title(name);
       window->request_properties(props);
-
-      if (want_texture) {
-        window->setup_render_texture();
-      }
-
       return window;
     }
   }
@@ -269,9 +264,6 @@ make_buffer(GraphicsStateGuardian *gsg, const string &name,
     gsg->get_pipe()->make_buffer(gsg, name, x_size, y_size);
   if (buffer != (GraphicsBuffer *)NULL) {
     buffer->_sort = sort;
-    if (want_texture) {
-      buffer->setup_render_texture();
-    }
     do_add_window(buffer, gsg, threading_model);
   }
   return buffer;
@@ -303,7 +295,6 @@ make_parasite(GraphicsOutput *host, const string &name,
       props.set_fixed_size(true);
       props.set_title(name);
       window->request_properties(props);
-      window->setup_render_texture();
 
       return window;
     }
@@ -317,7 +308,6 @@ make_parasite(GraphicsOutput *host, const string &name,
 
   ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size);
   buffer->_sort = sort;
-  buffer->setup_render_texture();
   do_add_window(buffer, gsg, threading_model);
 
   return buffer;
@@ -643,12 +633,12 @@ flip_frame() {
 //               whichever thread that may be.
 ////////////////////////////////////////////////////////////////////
 void GraphicsEngine::
-render_subframe(GraphicsStateGuardian *gsg, DisplayRegion *dr,
+render_subframe(GraphicsOutput *win, DisplayRegion *dr,
                 bool cull_sorting) {
   if (cull_sorting) {
-    cull_bin_draw(gsg, dr);
+    cull_bin_draw(win, dr);
   } else {
-    cull_and_draw_together(gsg, dr);
+    cull_and_draw_together(win, dr);
   }
 }
 
@@ -736,7 +726,7 @@ cull_and_draw_together(const GraphicsEngine::Windows &wlist) {
         for (int i = 0; i < num_display_regions; i++) {
           DisplayRegion *dr = win->get_active_display_region(i);
           if (dr != (DisplayRegion *)NULL) {
-            cull_and_draw_together(win->get_gsg(), dr);
+            cull_and_draw_together(win, dr);
           }
         }
         win->end_frame();
@@ -765,11 +755,13 @@ cull_and_draw_together(const GraphicsEngine::Windows &wlist) {
 //               only by render_subframe().
 ////////////////////////////////////////////////////////////////////
 void GraphicsEngine::
-cull_and_draw_together(GraphicsStateGuardian *gsg, DisplayRegion *dr) {
+cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr) {
+  GraphicsStateGuardian *gsg = win->get_gsg();
   nassertv(gsg != (GraphicsStateGuardian *)NULL);
 
   PT(SceneSetup) scene_setup = setup_scene(gsg, dr);
   if (setup_gsg(gsg, scene_setup)) {
+    win->change_scenes(dr);
     DisplayRegionStack old_dr = gsg->push_display_region(dr);
     gsg->prepare_display_region();
     if (dr->is_any_clear_active()) {
@@ -808,7 +800,7 @@ cull_bin_draw(const GraphicsEngine::Windows &wlist) {
         for (int i = 0; i < num_display_regions; i++) {
           DisplayRegion *dr = win->get_active_display_region(i);
           if (dr != (DisplayRegion *)NULL) {
-            cull_bin_draw(win->get_gsg(), dr);
+            cull_bin_draw(win, dr);
           }
         }
         win->end_frame();
@@ -838,7 +830,8 @@ cull_bin_draw(const GraphicsEngine::Windows &wlist) {
 //               implementation of cull_bin_draw(), above.
 ////////////////////////////////////////////////////////////////////
 void GraphicsEngine::
-cull_bin_draw(GraphicsStateGuardian *gsg, DisplayRegion *dr) {
+cull_bin_draw(GraphicsOutput *win, DisplayRegion *dr) {
+  GraphicsStateGuardian *gsg = win->get_gsg();
   nassertv(gsg != (GraphicsStateGuardian *)NULL);
 
   PT(CullResult) cull_result = dr->_cull_result;
@@ -860,7 +853,7 @@ cull_bin_draw(GraphicsStateGuardian *gsg, DisplayRegion *dr) {
     
     // Now draw.
     // This should get deferred into the next pipeline stage.
-    do_draw(cull_result, scene_setup, gsg, dr);
+    do_draw(cull_result, scene_setup, win, dr);
   }
 }
 
@@ -1121,11 +1114,13 @@ do_cull(CullHandler *cull_handler, SceneSetup *scene_setup,
 ////////////////////////////////////////////////////////////////////
 void GraphicsEngine::
 do_draw(CullResult *cull_result, SceneSetup *scene_setup,
-        GraphicsStateGuardian *gsg, DisplayRegion *dr) {
+        GraphicsOutput *win, DisplayRegion *dr) {
   // Statistics
   PStatTimer timer(_draw_pcollector);
 
+  GraphicsStateGuardian *gsg = win->get_gsg();
   if (setup_gsg(gsg, scene_setup)) {
+    win->change_scenes(dr);
     DisplayRegionStack old_dr = gsg->push_display_region(dr);
     gsg->prepare_display_region();
     if (dr->is_any_clear_active()) {

+ 5 - 6
panda/src/display/graphicsEngine.h

@@ -80,8 +80,7 @@ PUBLISHED:
   GraphicsWindow *make_window(GraphicsStateGuardian *gsg, const string &name,
                               int sort);
   GraphicsOutput *make_buffer(GraphicsStateGuardian *gsg, const string &name,
-                              int sort,
-                              int x_size, int y_size, bool want_texture);
+                              int sort, int x_size, int y_size);
   GraphicsOutput *make_parasite(GraphicsOutput *host, const string &name,
                                 int sort, int x_size, int y_size);
 
@@ -98,7 +97,7 @@ PUBLISHED:
   void sync_frame();
   void flip_frame();
   
-  void render_subframe(GraphicsStateGuardian *gsg, DisplayRegion *dr,
+  void render_subframe(GraphicsOutput *win, DisplayRegion *dr,
                        bool cull_sorting);
 
 public:
@@ -143,10 +142,10 @@ private:
   void set_window_sort(GraphicsOutput *window, int sort);
 
   void cull_and_draw_together(const Windows &wlist);
-  void cull_and_draw_together(GraphicsStateGuardian *gsg, DisplayRegion *dr);
+  void cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr);
 
   void cull_bin_draw(const Windows &wlist);
-  void cull_bin_draw(GraphicsStateGuardian *gsg, DisplayRegion *dr);
+  void cull_bin_draw(GraphicsOutput *win, DisplayRegion *dr);
   void make_contexts(const Windows &wlist);
 
   void process_events(const Windows &wlist);
@@ -159,7 +158,7 @@ private:
   void do_cull(CullHandler *cull_handler, SceneSetup *scene_setup,
                GraphicsStateGuardian *gsg);
   void do_draw(CullResult *cull_result, SceneSetup *scene_setup,
-               GraphicsStateGuardian *gsg, DisplayRegion *dr);
+               GraphicsOutput *win, DisplayRegion *dr);
 
   bool setup_gsg(GraphicsStateGuardian *gsg, SceneSetup *scene_setup);
 

+ 255 - 43
panda/src/display/graphicsOutput.cxx

@@ -26,12 +26,32 @@
 #include "indirectLess.h"
 #include "pStatTimer.h"
 #include "configVariableBool.h"
+#include "camera.h"
+#include "displayRegion.h"
+#include "lens.h"
+#include "perspectiveLens.h"
+#include "pointerTo.h"
 
 TypeHandle GraphicsOutput::_type_handle;
 
 PStatCollector GraphicsOutput::_make_current_pcollector("Draw:Make current");
 PStatCollector GraphicsOutput::_copy_texture_pcollector("Draw:Copy texture");
 
+struct CubeFaceDef {
+  const char *_name;
+  LPoint3f _look_at;
+  LVector3f _up;
+};
+
+static CubeFaceDef cube_faces[6] = {
+  { "positive_x", LPoint3f(1, 0, 0), LVector3f(0, -1, 0) },
+  { "negative_x", LPoint3f(-1, 0, 0), LVector3f(0, -1, 0) },
+  { "positive_y", LPoint3f(0, 1, 0), LVector3f(0, 0, 1) },
+  { "negative_y", LPoint3f(0, -1, 0), LVector3f(0, 0, -1) },
+  { "positive_z", LPoint3f(0, 0, 1), LVector3f(0, -1, 0) },
+  { "negative_z", LPoint3f(0, 0, -1), LVector3f(0, -1, 0) }
+};
+  
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsOutput::Constructor
 //       Access: Protected
@@ -52,10 +72,10 @@ GraphicsOutput(GraphicsPipe *pipe, GraphicsStateGuardian *gsg,
   _y_size = 0;
   _has_size = false;
   _is_valid = false;
-  _copy_texture = false;
-  _render_texture = false;
+  _rtm_mode = RTM_none;
   _flip_ready = false;
   _needs_context = true;
+  _cube_map_index = -1;
   _sort = 0;
   _internal_sort_index = 0;
   _active = true;
@@ -168,42 +188,67 @@ void GraphicsOutput::
 detach_texture() {
   MutexHolder holder(_lock);
 
-  if (_render_texture && _gsg != (GraphicsStateGuardian *)NULL) {
+  if (_rtm_mode == RTM_bind_texture && _gsg != (GraphicsStateGuardian *)NULL) {
     _gsg->framebuffer_release_texture(this, get_texture());
   }
 
   _texture = NULL;
-  _copy_texture = false;
-  _render_texture = false;
+  _rtm_mode = RTM_none;
 
   set_inverted(window_inverted);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsOutput::setup_render_texture
-//       Access: Published, Virtual
+//       Access: Published
 //  Description: Creates a new Texture object, suitable for rendering
 //               the contents of this buffer into, and stores it in
 //               _texture.  This also disassociates the previous
 //               texture (if any).
 //
-//               If the backend supports it, this will actually set up
-//               the framebuffer to render directly into texture
-//               memory; otherwise, the framebuffer will be copied
-//               into the texture after each frame.
+//               If tex is not NULL, it is the texture that will be
+//               set up for rendering into; otherwise, a new Texture
+//               object will be created (in which case you may call
+//               get_texture() to retrieve the new texture pointer
+//               later).
+//
+//               If allow_bind is true, and this GraphicsOutput is an
+//               offscreen graphics buffer that has not yet been
+//               rendered into, it will attempt to set up the buffer
+//               for rendering directly into the texture, avoiding the
+//               cost of the copy-to-texture-memory each frame.  This
+//               is not supported by all graphics hardware, but if it
+//               is not supported, this option is quietly ignored.
+//
+//               If to_ram is true, the texture image will be
+//               downloaded from the framebuffer memory into system
+//               RAM each frame, which is more expensive but allows
+//               the texture to subsequently be applied to any GSG.
+//               Otherwise, the texture image remains in texture
+//               memory only.
+//
+//               Also see make_texture_buffer(), which is a
+//               higher-level interface for preparing
+//               render-to-a-texture mode.
 ////////////////////////////////////////////////////////////////////
 void GraphicsOutput::
-setup_render_texture() {
+setup_render_texture(Texture *tex, bool allow_bind, bool to_ram) {
   MutexHolder holder(_lock);
 
-  if (_render_texture && _gsg != (GraphicsStateGuardian *)NULL) {
+  if (_rtm_mode == RTM_bind_texture && _gsg != (GraphicsStateGuardian *)NULL) {
     _gsg->framebuffer_release_texture(this, get_texture());
   }
 
-  _texture = new Texture(get_name());
+  if (tex == (Texture *)NULL) {
+    _texture = new Texture(get_name());
+    _texture->set_wrap_u(Texture::WM_clamp);
+    _texture->set_wrap_v(Texture::WM_clamp);
+
+  } else {
+    _texture = tex;
+    _texture->clear_ram_image();
+  }
   _texture->set_match_framebuffer_format(true);
-  _texture->set_wrap_u(Texture::WM_clamp);
-  _texture->set_wrap_v(Texture::WM_clamp);
 
   // Go ahead and tell the texture our anticipated size, even if it
   // might be inaccurate (particularly if this is a GraphicsWindow,
@@ -211,8 +256,13 @@ setup_render_texture() {
   _texture->set_x_size(get_x_size());
   _texture->set_y_size(get_y_size());
 
-  _copy_texture = true;
-  _render_texture = false;
+  if (to_ram) {
+    _rtm_mode = RTM_copy_ram;
+  } else if (allow_bind) {
+    _rtm_mode = RTM_bind_if_possible;
+  } else {
+    _rtm_mode = RTM_copy_texture;
+  }
 
   nassertv(_gsg != (GraphicsStateGuardian *)NULL);
   set_inverted(_gsg->get_copy_texture_inverted());
@@ -412,6 +462,20 @@ get_active_display_region(int n) const {
 //               for applying to geometry within the scene rendered
 //               into this window.
 //
+//               If tex is not NULL, it is the texture that will be
+//               set up for rendering into; otherwise, a new Texture
+//               object will be created.  In either case, the target
+//               texture can be retrieved from the return value with
+//               buffer->get_texture() (assuming the return value is
+//               not NULL).
+//
+//               If to_ram is true, the buffer will be set up to
+//               download its contents to the system RAM memory
+//               associated with the Texture object, instead of
+//               keeping it strictly within texture memory; this is
+//               much slower, but it allows using the texture with any
+//               GSG.
+//
 //               This will attempt to be smart about maximizing render
 //               performance while minimizing framebuffer waste.  It
 //               might return a GraphicsBuffer set to render directly
@@ -420,14 +484,14 @@ get_active_display_region(int n) const {
 //               return value is NULL if the buffer could not be
 //               created for some reason.
 //
-//               Assuming the return value is not NULL, the texture
-//               that is represents the scene rendered to the new
-//               buffer can be accessed by buffer->get_texture().
 //               When you are done using the buffer, you should remove
-//               it with a call to GraphicsEngine::remove_window().
+//               it with a call to GraphicsEngine::remove_window() (or
+//               set the one_shot flag so it removes itself after one
+//               frame).
 ////////////////////////////////////////////////////////////////////
 GraphicsOutput *GraphicsOutput::
-make_texture_buffer(const string &name, int x_size, int y_size) {
+make_texture_buffer(const string &name, int x_size, int y_size,
+                    Texture *tex, bool to_ram) {
   GraphicsStateGuardian *gsg = get_gsg();
   GraphicsEngine *engine = gsg->get_engine();
   GraphicsOutput *host = get_host();
@@ -437,26 +501,30 @@ make_texture_buffer(const string &name, int x_size, int y_size) {
   // value himself.
   int sort = get_sort() - 1;
 
+  GraphicsOutput *buffer = NULL;
+
   if (show_buffers) {
     // If show_buffers is true, just go ahead and call make_buffer(),
     // since it all amounts to the same thing anyway--this will
     // actually create a new GraphicsWindow.
-    return engine->make_buffer(gsg, name, sort, x_size, y_size, true);
+    buffer = engine->make_buffer(gsg, name, sort, x_size, y_size);
+    buffer->setup_render_texture(tex, false, to_ram);
+    return buffer;
   }
-
-  GraphicsOutput *buffer = NULL;
+  
+  bool allow_bind = 
+    (prefer_texture_buffer && gsg->get_supports_render_texture() && !to_ram);
 
   // If the user so indicated in the Config.prc file, try to create a
   // parasite buffer first.  We can only do this if the requested size
   // fits within the available framebuffer size.  Also, don't do this
-  // if the GSG supports render-to-a-texture and prefer_texture_buffer
-  // is true, since using a ParasiteButter precludes
-  // render-to-a-texture.
-  if (prefer_parasite_buffer && 
-      !(prefer_texture_buffer && gsg->get_supports_render_texture()) && 
+  // if we want to try using render-to-a-texture mode, since using a
+  // ParasiteButter will preclude that.
+  if (prefer_parasite_buffer && !allow_bind &&
       (x_size <= host->get_x_size() && y_size <= host->get_y_size())) {
     buffer = engine->make_parasite(host, name, sort, x_size, y_size);
     if (buffer != (GraphicsOutput *)NULL) {
+      buffer->setup_render_texture(tex, false, to_ram);
       return buffer;
     }
   }
@@ -472,9 +540,10 @@ make_texture_buffer(const string &name, int x_size, int y_size) {
       PT(GraphicsStateGuardian) sb_gsg = 
         engine->make_gsg(gsg->get_pipe(), sb_props, gsg);
       if (sb_gsg != (GraphicsStateGuardian *)NULL) {
-        buffer = engine->make_buffer(sb_gsg, name, sort, x_size, y_size, true);
+        buffer = engine->make_buffer(sb_gsg, name, sort, x_size, y_size);
         if (buffer != (GraphicsOutput *)NULL) {
           // Check the buffer for goodness.
+          buffer->setup_render_texture(tex, allow_bind, to_ram);
           engine->open_windows();
           if (buffer->is_valid()) {
             return buffer;
@@ -492,8 +561,9 @@ make_texture_buffer(const string &name, int x_size, int y_size) {
   // All right, attempt to create an offscreen buffer, using the same
   // GSG.  This will be a double-buffered offscreen buffer, if the
   // source window is double-buffered.
-  buffer = engine->make_buffer(gsg, name, sort, x_size, y_size, true);
+  buffer = engine->make_buffer(gsg, name, sort, x_size, y_size);
   if (buffer != (GraphicsOutput *)NULL) {
+    buffer->setup_render_texture(tex, allow_bind, to_ram);
     engine->open_windows();
     if (buffer->is_valid()) {
       return buffer;
@@ -506,12 +576,82 @@ make_texture_buffer(const string &name, int x_size, int y_size) {
 
   // Looks like we have to settle for a parasite buffer.
   if (x_size <= host->get_x_size() && y_size <= host->get_y_size()) {
-    return engine->make_parasite(host, name, sort, x_size, y_size);
+    buffer = engine->make_parasite(host, name, sort, x_size, y_size);
+    buffer->setup_render_texture(tex, false, to_ram);
+    return buffer;
   }
 
   return NULL;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsOutput::make_cube_map
+//       Access: Published
+//  Description: This is similar to make_texture_buffer() in that it
+//               allocates a separate buffer suitable for rendering to
+//               a texture that can be assigned to geometry in this
+//               window, but in this case, the buffer is set up to
+//               render the six faces of a cube map.
+//
+//               The buffer is automatically set up with six display
+//               regions and six cameras, each of which are assigned
+//               the indicated draw_mask and parented to the given
+//               camera_rig node (which you should then put in your
+//               scene to render the cube map from the appropriate
+//               point of view).
+//
+//               You may take the texture associated with the buffer
+//               and apply it to geometry, particularly with
+//               TexGenAttrib::M_world_cube_map also in effect, to
+//               apply a reflection of everything seen by the camera
+//               rig.
+////////////////////////////////////////////////////////////////////
+GraphicsOutput *GraphicsOutput::
+make_cube_map(const string &name, int size, bool to_ram,
+              NodePath &camera_rig, DrawMask camera_mask) {
+  if (!to_ram) {
+    // Check the limits imposed by the GSG.  (However, if we're
+    // rendering the texture to RAM only, these limits may be
+    // irrelevant.)
+    GraphicsStateGuardian *gsg = get_gsg();
+    int max_dimension = gsg->get_max_cube_map_dimension();
+    if (max_dimension == 0) {
+      // The GSG doesn't support cube mapping; too bad for you.
+      return NULL;
+    }
+    if (max_dimension > 0) {
+      size = min(max_dimension, size);
+    }
+  }
+
+  PT(Texture) tex = new Texture(name);
+  tex->setup_cube_map();
+  GraphicsOutput *buffer = make_texture_buffer(name, size, size, tex, to_ram);
+
+  // We don't need to clear the overall buffer; instead, we'll clear
+  // each display region.
+  buffer->set_clear_color_active(false);
+  buffer->set_clear_depth_active(false);
+
+  PT(Lens) lens = new PerspectiveLens;
+  lens->set_fov(90.0f);
+
+  for (int i = 0; i < 6; i++) {
+    PT(Camera) camera = new Camera(cube_faces[i]._name);
+    camera->set_lens(lens);
+    camera->set_camera_mask(camera_mask);
+    NodePath camera_np = camera_rig.attach_new_node(camera);
+    camera_np.look_at(cube_faces[i]._look_at, cube_faces[i]._up);
+    
+    DisplayRegion *dr = buffer->make_display_region();
+    dr->set_cube_map_index(i);
+    dr->copy_clear_settings(*this);
+    dr->set_camera(camera_np);
+  }
+
+  return buffer;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsOutput::get_host
 //       Access: Public, Virtual
@@ -604,11 +744,13 @@ begin_frame() {
   // Okay, we already have a GSG, so activate it.
   make_current();
 
-  if (_render_texture) {
+  if (_rtm_mode == RTM_bind_texture) {
     // Release the texture so we can render into the frame buffer.
     _gsg->framebuffer_release_texture(this, get_texture());
   }
 
+  _cube_map_index = -1;
+
   return _gsg->begin_frame();
 }
 
@@ -657,17 +799,17 @@ end_frame() {
   nassertv(_gsg != (GraphicsStateGuardian *)NULL);
   _gsg->end_frame();
 
-  // If _copy_texture is true, it means we should copy or lock the
+  // If _rtm_mode isn't RTM_none, it means we should copy or lock the
   // framebuffer to the GraphicsOutput's associated texture after the
   // frame has rendered.
-  if (_copy_texture) {
+  if (_rtm_mode != RTM_none) {
     PStatTimer timer(_copy_texture_pcollector);
     nassertv(has_texture());
 
-    // If _render_texture is true, it means we should attempt to lock
-    // the framebuffer directly to the texture memory, avoiding the
-    // copy.
-    if (_render_texture) {
+    // If _rtm_mode is RTM_bind_texture, it means we should attempt to
+    // lock the framebuffer directly to the texture memory, avoiding
+    // the copy.
+    if (_rtm_mode == RTM_bind_texture) {
       if (display_cat.is_debug()) {
         display_cat.debug()
           << "Locking texture for " << get_name() << " at frame end.\n";
@@ -675,17 +817,25 @@ end_frame() {
       if (!_gsg->framebuffer_bind_to_texture(this, get_texture())) {
         display_cat.warning()
           << "Lock-to-texture failed, resorting to copy.\n";
-        _render_texture = false;
+        _rtm_mode = RTM_copy_texture;
       }
     }
 
-    if (!_render_texture) {
+    if (_rtm_mode != RTM_bind_texture) {
       if (display_cat.is_debug()) {
         display_cat.debug()
           << "Copying texture for " << get_name() << " at frame end.\n";
+        display_cat.debug()
+          << "cube_map_index = " << _cube_map_index << "\n";
       }
       RenderBuffer buffer = _gsg->get_render_buffer(get_draw_buffer_type());
-      _gsg->framebuffer_copy_to_texture(get_texture(), _default_display_region, buffer);
+      if (_rtm_mode == RTM_copy_ram) {
+        _gsg->framebuffer_copy_to_ram(get_texture(), _cube_map_index,
+                                      _default_display_region, buffer);
+      } else {
+        _gsg->framebuffer_copy_to_texture(get_texture(), _cube_map_index,
+                                          _default_display_region, buffer);
+      }
     }
   }
 
@@ -702,6 +852,68 @@ end_frame() {
     _active = false;
     _delete_flag = true;
   }
+
+  _cube_map_index = -1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsOutput::change_scenes
+//       Access: Public
+//  Description: Called by the GraphicsEngine when the window is about
+//               to change to another DisplayRegion.  This exists
+//               mainly to provide a callback for switching the cube
+//               map face, if we are rendering to the different faces
+//               of a cube map.
+////////////////////////////////////////////////////////////////////
+void GraphicsOutput::
+change_scenes(DisplayRegion *new_dr) {
+  int new_cube_map_index = new_dr->get_cube_map_index();
+  if (new_cube_map_index != -1 &&
+      new_cube_map_index != _cube_map_index) {
+    int old_cube_map_index = _cube_map_index;
+    _cube_map_index = new_cube_map_index;
+
+    if (_rtm_mode != RTM_none) {
+      if (_rtm_mode == RTM_bind_texture) {
+        // In render-to-texture mode, switch the rendering backend to
+        // the new cube map face, so that the subsequent frame will be
+        // rendered to the new face.
+
+        select_cube_map(new_cube_map_index);
+        
+      } else if (old_cube_map_index != -1) {
+        // In copy-to-texture mode, copy the just-rendered framebuffer
+        // to the old cube map face.
+        if (display_cat.is_debug()) {
+          display_cat.debug()
+            << "Copying texture for " << get_name() << " at scene change.\n";
+          display_cat.debug()
+            << "cube_map_index = " << old_cube_map_index << "\n";
+        }
+        RenderBuffer buffer = _gsg->get_render_buffer(get_draw_buffer_type());
+        if (_rtm_mode == RTM_copy_ram) {
+          _gsg->framebuffer_copy_to_ram(get_texture(), old_cube_map_index, 
+                                        _default_display_region, buffer);
+        } else {
+          _gsg->framebuffer_copy_to_texture(get_texture(), old_cube_map_index, 
+                                            _default_display_region, buffer);
+        }
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsOutput::select_cube_map
+//       Access: Public, Virtual
+//  Description: Called internally when the window is in
+//               render-to-a-texture mode and we are in the process of
+//               rendering the six faces of a cube map.  This should
+//               do whatever needs to be done to switch the buffer to
+//               the indicated face.
+////////////////////////////////////////////////////////////////////
+void GraphicsOutput::
+select_cube_map(int) {
 }
 
 ////////////////////////////////////////////////////////////////////

+ 19 - 4
panda/src/display/graphicsOutput.h

@@ -32,6 +32,7 @@
 #include "notify.h"
 #include "pmutex.h"
 #include "filename.h"
+#include "drawMask.h"
 #include "pvector.h"
 
 class PNMImage;
@@ -75,7 +76,7 @@ PUBLISHED:
   INLINE bool has_texture() const;  
   INLINE Texture *get_texture() const;  
   void detach_texture();
-  virtual void setup_render_texture();
+  void setup_render_texture(Texture *tex, bool allow_bind, bool to_ram);
 
   INLINE int get_x_size() const;
   INLINE int get_y_size() const;
@@ -108,7 +109,10 @@ PUBLISHED:
   int get_num_active_display_regions() const;
   PT(DisplayRegion) get_active_display_region(int n) const;
 
-  GraphicsOutput *make_texture_buffer(const string &name, int x_size, int y_size);
+  GraphicsOutput *make_texture_buffer(const string &name, int x_size, int y_size,
+                                      Texture *tex = NULL, bool to_ram = false);
+  GraphicsOutput *make_cube_map(const string &name, int size, bool to_ram,
+                                NodePath &camera_rig, DrawMask camera_mask);
 
   INLINE Filename save_screenshot_default(const string &prefix = "screenshot");
   INLINE bool save_screenshot(const Filename &filename);
@@ -136,6 +140,9 @@ public:
   void clear();
   virtual void end_frame();
 
+  void change_scenes(DisplayRegion *new_dr);
+  virtual void select_cube_map(int cube_map_index);
+
   // This method is called in the draw thread prior to issuing any
   // drawing commands for the window.
   virtual bool make_context();
@@ -152,14 +159,22 @@ public:
   virtual void process_events();
   
 protected:
+  enum RenderTextureMode {
+    RTM_none,
+    RTM_bind_texture,
+    RTM_bind_if_possible,
+    RTM_copy_texture,
+    RTM_copy_ram,
+  };
+
   PT(GraphicsStateGuardian) _gsg;
   PT(GraphicsPipe) _pipe;
   string _name;
   PT(Texture) _texture;
-  bool _copy_texture;
-  bool _render_texture;
+  RenderTextureMode _rtm_mode;
   bool _flip_ready;
   bool _needs_context;
+  int _cube_map_index;
 
 private:
   DisplayRegion *add_display_region(DisplayRegion *display_region);

+ 50 - 0
panda/src/display/graphicsStateGuardian.I

@@ -151,6 +151,56 @@ get_max_texture_stages() const {
   return _max_texture_stages;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_max_texture_dimension
+//       Access: Published
+//  Description: Returns the largest possible texture size in any one
+//               dimension supported by the GSG, or -1 if there is no
+//               particular limit.
+//
+//               The value returned may not be meaningful until after
+//               the graphics context has been fully created (e.g. the
+//               window has been opened).
+////////////////////////////////////////////////////////////////////
+INLINE int GraphicsStateGuardian::
+get_max_texture_dimension() const {
+  return _max_texture_dimension;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_max_3d_texture_dimension
+//       Access: Published
+//  Description: Returns the largest possible texture size in any one
+//               dimension for a 3-d texture, or -1 if there is no
+//               particular limit.  Returns 0 if 3-d textures are not
+//               supported.
+//
+//               The value returned may not be meaningful until after
+//               the graphics context has been fully created (e.g. the
+//               window has been opened).
+////////////////////////////////////////////////////////////////////
+INLINE int GraphicsStateGuardian::
+get_max_3d_texture_dimension() const {
+  return _max_3d_texture_dimension;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_max_cube_map_dimension
+//       Access: Published
+//  Description: Returns the largest possible texture size in any one
+//               dimension for a cube map texture, or -1 if there is
+//               no particular limit.  Returns 0 if cube map textures
+//               are not supported.
+//
+//               The value returned may not be meaningful until after
+//               the graphics context has been fully created (e.g. the
+//               window has been opened).
+////////////////////////////////////////////////////////////////////
+INLINE int GraphicsStateGuardian::
+get_max_cube_map_dimension() const {
+  return _max_cube_map_dimension;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::get_copy_texture_inverted
 //       Access: Published

+ 6 - 0
panda/src/display/graphicsStateGuardian.cxx

@@ -90,6 +90,12 @@ GraphicsStateGuardian(const FrameBufferProperties &properties,
   // supports multitexturing.
   _max_texture_stages = 1;
 
+  // Also initially, we assume there are no limits on texture sizes,
+  // and that 3-d and cube-map textures are not supported.
+  _max_texture_dimension = -1;
+  _max_3d_texture_dimension = 0;
+  _max_cube_map_dimension = 0;
+
   // Initially, we set this to false; a GSG that knows it has this
   // property should set it to true.
   _copy_texture_inverted = false;

+ 8 - 0
panda/src/display/graphicsStateGuardian.h

@@ -83,6 +83,10 @@ PUBLISHED:
   INLINE const GraphicsThreadingModel &get_threading_model() const;
 
   INLINE int get_max_texture_stages() const;
+  INLINE int get_max_texture_dimension() const;
+  INLINE int get_max_3d_texture_dimension() const;
+  INLINE int get_max_cube_map_dimension() const;
+
   INLINE bool get_copy_texture_inverted() const;
   virtual bool get_supports_multisample() const;
   INLINE bool get_supports_generate_mipmap() const;
@@ -278,6 +282,10 @@ protected:
 
   PT(PreparedGraphicsObjects) _prepared_objects;
   int _max_texture_stages;
+  int _max_texture_dimension;
+  int _max_3d_texture_dimension;
+  int _max_cube_map_dimension;
+
   bool _copy_texture_inverted;
   bool _supports_multisample;
   bool _supports_generate_mipmap;

+ 2 - 1
panda/src/distort/nonlinearImager.cxx

@@ -680,7 +680,8 @@ recompute_screen(NonlinearImager::Screen &screen, size_t vi) {
 
   if (screen._buffer == (GraphicsOutput *)NULL) {
     GraphicsOutput *win = viewer._dr->get_window();
-    GraphicsOutput *buffer = win->make_texture_buffer(screen._name, screen._tex_width, screen._tex_height);
+    GraphicsOutput *buffer = win->make_texture_buffer
+      (screen._name, screen._tex_width, screen._tex_height, NULL, false);
 
     if (buffer != (GraphicsOutput *)NULL) {
       screen._buffer = buffer;

+ 5 - 3
panda/src/doc/eggSyntax.txt

@@ -279,12 +279,14 @@ appear before they are referenced.
     for instance to apply a reflection map or some other effect.  The
     valid values for mode are:
 
-      SPHERE_MAP
-      CUBE_MAP
+      EYE_SPHERE_MAP (or SPHERE_MAP)
+      WORLD_CUBE_MAP
+      EYE_CUBE_MAP (or CUBE_MAP)
+      WORLD_NORMAL
+      EYE_NORMAL
       WORLD_POSITION
       OBJECT_POSITION
       EYE_POSITION
-      OBJECT_NORMAL
 
   <Scalar> stage-name { name }
 

+ 2 - 2
panda/src/dxgsg7/dxGraphicsStateGuardian7.cxx

@@ -3488,7 +3488,7 @@ release_texture(TextureContext *tc) {
 //  Description:
 ////////////////////////////////////////////////////////////////////
 void DXGraphicsStateGuardian7::
-framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr, const RenderBuffer &rb) {
+framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb) {
     dxgsg7_cat.error() << "DX copy_texture unimplemented!!!";
 }
 
@@ -3498,7 +3498,7 @@ framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr, const RenderB
 //  Description:
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian7::
-framebuffer_copy_to_ram(Texture *tex, const DisplayRegion *dr,
+framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
                         const RenderBuffer &rb) {
   set_read_buffer(rb);
   

+ 2 - 2
panda/src/dxgsg7/dxGraphicsStateGuardian7.h

@@ -91,10 +91,10 @@ public:
   virtual void apply_texture(TextureContext *tc);
   virtual void release_texture(TextureContext *tc);
 
-  virtual void framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr,
+  virtual void framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
                                            const RenderBuffer &rb);
 
-  virtual bool framebuffer_copy_to_ram(Texture *pb, const DisplayRegion *dr,
+  virtual bool framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
                                        const RenderBuffer &rb);
 
   virtual void apply_material(const Material *material);

+ 3 - 3
panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx

@@ -2783,7 +2783,7 @@ release_texture(TextureContext *tc) {
 // copies current display region in framebuffer to the texture
 // usually its more efficient to do SetRenderTgt
 void DXGraphicsStateGuardian8::
-framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr, const RenderBuffer &rb) {
+framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb) {
   set_read_buffer(rb);
 
   HRESULT hr;
@@ -2838,7 +2838,7 @@ framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr, const RenderB
 //  Description:
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian8::
-framebuffer_copy_to_ram(Texture *tex, const DisplayRegion *dr, const RenderBuffer &rb) {
+framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb) {
   set_read_buffer(rb);
 
   RECT SrcCopyRect;
@@ -3145,7 +3145,7 @@ issue_tex_gen(const TexGenAttrib *attrib) {
     _pD3DDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, 0);
     _pD3DDevice->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0);
 
-  } else if (attrib->get_mode(TextureStage::get_default()) == TexGenAttrib::M_sphere_map) {
+  } else if (attrib->get_mode(TextureStage::get_default()) == TexGenAttrib::M_eye_sphere_map) {
 
 #if 0
     // best reflection on a sphere is achieved by camera space normals in directx

+ 2 - 2
panda/src/dxgsg8/dxGraphicsStateGuardian8.h

@@ -93,9 +93,9 @@ public:
   virtual void apply_texture(TextureContext *tc);
   virtual void release_texture(TextureContext *tc);
 
-  virtual void framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr,
+  virtual void framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
                                            const RenderBuffer &rb);
-  virtual bool framebuffer_copy_to_ram(Texture *tex, const DisplayRegion *dr,
+  virtual bool framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
                                        const RenderBuffer &rb);
 
   virtual void apply_material(const Material *material);

+ 3 - 3
panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx

@@ -2766,7 +2766,7 @@ release_texture(TextureContext *tc) {
 // copies current display region in framebuffer to the texture
 // usually its more efficient to do SetRenderTgt
 void DXGraphicsStateGuardian9::
-framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr, const RenderBuffer &rb) {
+framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb) {
   set_read_buffer(rb);
 
   HRESULT hr;
@@ -2825,7 +2825,7 @@ framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr, const RenderB
 //  Description:
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian9::
-framebuffer_copy_to_ram(Texture *tex, const DisplayRegion *dr, const RenderBuffer &rb) {
+framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb) {
   set_read_buffer(rb);
 
   RECT SrcCopyRect;
@@ -3132,7 +3132,7 @@ issue_tex_gen(const TexGenAttrib *attrib) {
     _pD3DDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, 0);
     _pD3DDevice->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0);
 
-  } else if (attrib->get_mode(TextureStage::get_default()) == TexGenAttrib::M_sphere_map) {
+  } else if (attrib->get_mode(TextureStage::get_default()) == TexGenAttrib::M_eye_sphere_map) {
 
 #if 0
     // best reflection on a sphere is achieved by camera space normals in directx

+ 2 - 2
panda/src/dxgsg9/dxGraphicsStateGuardian9.h

@@ -94,9 +94,9 @@ public:
   virtual void apply_texture(TextureContext *tc);
   virtual void release_texture(TextureContext *tc);
 
-  virtual void framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr,
+  virtual void framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
                                            const RenderBuffer &rb);
-  virtual bool framebuffer_copy_to_ram(Texture *tex, const DisplayRegion *dr,
+  virtual bool framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
                                        const RenderBuffer &rb);
 
   virtual void apply_material(const Material *material);

+ 28 - 14
panda/src/egg/eggTexture.cxx

@@ -801,11 +801,22 @@ string_tex_gen(const string &string) {
   if (cmp_nocase_uh(string, "unspecified") == 0) {
     return TG_unspecified;
 
-  } else if (cmp_nocase_uh(string, "sphere_map") == 0) {
-    return TG_sphere_map;
+  } else if (cmp_nocase_uh(string, "sphere_map") == 0 ||
+             cmp_nocase_uh(string, "eye_sphere_map") == 0) {
+    return TG_eye_sphere_map;
 
-  } else if (cmp_nocase_uh(string, "cube_map") == 0) {
-    return TG_cube_map;
+  } else if (cmp_nocase_uh(string, "world_cube_map") == 0) {
+    return TG_world_cube_map;
+
+  } else if (cmp_nocase_uh(string, "cube_map") == 0 ||
+             cmp_nocase_uh(string, "eye_cube_map") == 0) {
+    return TG_eye_cube_map;
+
+  } else if (cmp_nocase_uh(string, "world_normal") == 0) {
+    return TG_world_normal;
+
+  } else if (cmp_nocase_uh(string, "eye_normal") == 0) {
+    return TG_eye_normal;
 
   } else if (cmp_nocase_uh(string, "world_position") == 0) {
     return TG_world_position;
@@ -816,9 +827,6 @@ string_tex_gen(const string &string) {
   } else if (cmp_nocase_uh(string, "eye_position") == 0) {
     return TG_eye_position;
 
-  } else if (cmp_nocase_uh(string, "object_normal") == 0) {
-    return TG_object_normal;
-
   } else {
     return TG_unspecified;
   }
@@ -1114,11 +1122,20 @@ operator << (ostream &out, EggTexture::TexGen tex_gen) {
   case EggTexture::TG_unspecified:
     return out << "unspecified";
 
-  case EggTexture::TG_sphere_map:
-    return out << "sphere_map";
+  case EggTexture::TG_eye_sphere_map:
+    return out << "eye_sphere_map";
 
-  case EggTexture::TG_cube_map:
-    return out << "cube_map";
+  case EggTexture::TG_world_cube_map:
+    return out << "world_cube_map";
+
+  case EggTexture::TG_eye_cube_map:
+    return out << "eye_cube_map";
+
+  case EggTexture::TG_world_normal:
+    return out << "world_normal";
+
+  case EggTexture::TG_eye_normal:
+    return out << "eye_normal";
 
   case EggTexture::TG_world_position:
     return out << "world_position";
@@ -1128,9 +1145,6 @@ operator << (ostream &out, EggTexture::TexGen tex_gen) {
 
   case EggTexture::TG_eye_position:
     return out << "eye_position";
-
-  case EggTexture::TG_object_normal:
-    return out << "object_normal";
   }
 
   return out << "**invalid TexGen(" << (int)tex_gen << ")**";

+ 9 - 3
panda/src/egg/eggTexture.h

@@ -129,12 +129,18 @@ PUBLISHED:
   };
   enum TexGen {
     TG_unspecified,
-    TG_sphere_map,
-    TG_cube_map,
+
+    TG_eye_sphere_map,
+
+    TG_world_cube_map,
+    TG_eye_cube_map,
+
+    TG_world_normal,
+    TG_eye_normal,
+
     TG_world_position,
     TG_object_position,
     TG_eye_position,
-    TG_object_normal,
   };
 
   INLINE void set_format(Format format);

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

@@ -3115,11 +3115,20 @@ get_tex_gen(const EggTexture *egg_tex) {
   case EggTexture::TG_unspecified:
     return TexGenAttrib::M_off;
 
-  case EggTexture::TG_sphere_map:
-    return TexGenAttrib::M_sphere_map;
+  case EggTexture::TG_eye_sphere_map:
+    return TexGenAttrib::M_eye_sphere_map;
 
-  case EggTexture::TG_cube_map:
-    return TexGenAttrib::M_cube_map;
+  case EggTexture::TG_world_cube_map:
+    return TexGenAttrib::M_world_cube_map;
+
+  case EggTexture::TG_eye_cube_map:
+    return TexGenAttrib::M_eye_cube_map;
+
+  case EggTexture::TG_world_normal:
+    return TexGenAttrib::M_world_normal;
+
+  case EggTexture::TG_eye_normal:
+    return TexGenAttrib::M_eye_normal;
 
   case EggTexture::TG_world_position:
     return TexGenAttrib::M_world_position;
@@ -3129,9 +3138,6 @@ get_tex_gen(const EggTexture *egg_tex) {
 
   case EggTexture::TG_eye_position:
     return TexGenAttrib::M_eye_position;
-
-  case EggTexture::TG_object_normal:
-    return TexGenAttrib::M_object_normal;
   };
 
   return TexGenAttrib::M_off;

+ 195 - 94
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -487,25 +487,33 @@ reset() {
       _supports_multisample = false;
     }
   }
+
+  GLint max_texture_size;
+  GLint max_3d_texture_size;
+  GLint max_cube_map_size;
   
-  GLP(GetIntegerv)(GL_MAX_TEXTURE_SIZE, &_max_texture_size);
+  GLP(GetIntegerv)(GL_MAX_TEXTURE_SIZE, &max_texture_size);
+  _max_texture_dimension = max_texture_size;
+
   if (_supports_3d_texture) {
-    GLP(GetIntegerv)(GL_MAX_3D_TEXTURE_SIZE, &_max_3d_texture_size);
+    GLP(GetIntegerv)(GL_MAX_3D_TEXTURE_SIZE, &max_3d_texture_size);
+    _max_3d_texture_dimension = max_3d_texture_size;
   } else {
-    _max_3d_texture_size = 0;
+    _max_3d_texture_dimension = 0;
   }
 
   if (_supports_cube_map) {
-    GLP(GetIntegerv)(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &_max_cube_map_size);
+    GLP(GetIntegerv)(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &max_cube_map_size);
+    _max_cube_map_dimension = max_cube_map_size;
   } else {
-    _max_cube_map_size = 0;
+    _max_cube_map_dimension = 0;
   }
 
   if (GLCAT.is_debug()) {
     GLCAT.debug()
-      << "max texture size = " << _max_texture_size
-      << ", max 3d texture = " << _max_3d_texture_size
-      << ", max cube map = " << _max_cube_map_size << "\n";
+      << "max texture dimension = " << _max_texture_dimension
+      << ", max 3d texture = " << _max_3d_texture_dimension
+      << ", max cube map = " << _max_cube_map_dimension << "\n";
   }
 
   report_my_gl_errors();
@@ -723,7 +731,7 @@ do_clear(const RenderBuffer &buffer) {
 //     Function: CLP(GraphicsStateGuardian)::prepare_display_region
 //       Access: Public, Virtual
 //  Description: Prepare a display region for rendering (set up
-//       scissor region and viewport)
+//               scissor region and viewport)
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 prepare_display_region() {
@@ -2162,13 +2170,15 @@ static int binary_log_cap(const int x) {
 //     Function: CLP(GraphicsStateGuardian)::framebuffer_copy_to_texture
 //       Access: Public, Virtual
 //  Description: Copy the pixels within the indicated display
-//               region from the framebuffer into texture memory
+//               region from the framebuffer into texture memory.
+//
+//               If z > -1, it is the cube map index into which to
+//               copy.
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
-framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr,
+framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
                             const RenderBuffer &rb) {
-  nassertv(tex != NULL && dr != NULL && 
-           tex->get_texture_type() == Texture::TT_2d_texture);
+  nassertv(tex != NULL && dr != NULL);
   set_read_buffer(rb);
 
   int xo, yo, w, h;
@@ -2181,9 +2191,27 @@ framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr,
   nassertv(tc != (TextureContext *)NULL);
   bind_texture(tc);
 
-  GLP(CopyTexImage2D)(GL_TEXTURE_2D, 0,
-                      get_internal_image_format(tex->get_format()),
-                      xo, yo, w, h, 0);
+  if (z >= 0) {
+    // Copy to a cube map face.
+    nassertv(z < 6);
+    nassertv(tex->get_texture_type() == Texture::TT_cube_map);
+
+    if (_supports_cube_map) {
+      // We cleverly defined the cube map faces to fall in the same
+      // order as the GL constants are defined, so we can just make this
+      // simple addition to get to the right GL constant.
+      GLP(CopyTexImage2D)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + z, 0,
+                          get_internal_image_format(tex->get_format()),
+                          xo, yo, w, h, 0);
+    }
+
+  } else {
+    // Copy to a regular texture.
+    nassertv(tex->get_texture_type() == Texture::TT_2d_texture);
+    GLP(CopyTexImage2D)(GL_TEXTURE_2D, 0,
+                        get_internal_image_format(tex->get_format()),
+                        xo, yo, w, h, 0);
+  }
 
   // Clear the internal texture state, since we've just monkeyed with it.
   modify_state(get_untextured_state());
@@ -2195,9 +2223,12 @@ framebuffer_copy_to_texture(Texture *tex, const DisplayRegion *dr,
 //  Description: Copy the pixels within the indicated display region
 //               from the framebuffer into system memory, not texture
 //               memory.  Returns true on success, false on failure.
+//
+//               This completely redefines the ram image of the
+//               indicated texture.
 ////////////////////////////////////////////////////////////////////
 bool CLP(GraphicsStateGuardian)::
-framebuffer_copy_to_ram(Texture *tex, const DisplayRegion *dr,
+framebuffer_copy_to_ram(Texture *tex, int z, const DisplayRegion *dr,
                         const RenderBuffer &rb) {
   nassertr(tex != NULL && dr != NULL, false);
   set_read_buffer(rb);
@@ -2228,7 +2259,21 @@ framebuffer_copy_to_ram(Texture *tex, const DisplayRegion *dr,
     format = Texture::F_rgb;
   }
 
-  tex->setup_2d_texture(w, h, component_type, format);
+  Texture::TextureType texture_type;
+  if (z >= 0) {
+    texture_type = Texture::TT_cube_map;
+  } else {
+    texture_type = Texture::TT_2d_texture;
+  }
+
+  if (tex->get_x_size() != w || tex->get_y_size() != h ||
+      tex->get_component_type() != component_type ||
+      tex->get_format() != format ||
+      tex->get_texture_type() != texture_type) {
+    // Re-setup the texture; its properties have changed.
+    tex->setup_texture(texture_type, w, h, 1, component_type, format);
+  }
+
   GLenum external_format = get_external_image_format(format);
 
 #ifdef GSG_VERBOSE
@@ -2272,13 +2317,21 @@ framebuffer_copy_to_ram(Texture *tex, const DisplayRegion *dr,
     << ")" << endl;
 #endif
 
+  unsigned char *image = tex->modify_ram_image();
+  if (z >= 0) {
+    nassertr(z < tex->get_z_size(), false);
+    image += z * tex->get_expected_ram_page_size();
+  }
+
   GLP(ReadPixels)(xo, yo, w, h,
                   external_format, get_component_type(component_type),
-                  tex->make_ram_image());
+                  image);
 
   // We may have to reverse the byte ordering of the image if GL
-  // didn't do it for us.
-  if (!_supports_bgr) {
+  // didn't do it for us.  This assumes we render out the six faces of
+  // a cube map in ascending order, since we can't do this until we
+  // have rendered the last face.
+  if (!_supports_bgr && (z == -1 || z == 5)) {
     tex->set_ram_image(fix_component_ordering(tex->get_ram_image(), 
                                               external_format, tex));
   }
@@ -3399,15 +3452,15 @@ apply_texture_immediate(CLP(TextureContext) *gtc, Texture *tex) {
   int max_dimension;
   switch (tex->get_texture_type()) {
   case Texture::TT_3d_texture:
-    max_dimension = _max_3d_texture_size;
+    max_dimension = _max_3d_texture_dimension;
     break;
 
   case Texture::TT_cube_map:
-    max_dimension = _max_cube_map_size;
+    max_dimension = _max_cube_map_dimension;
     break;
 
   default:
-    max_dimension = _max_texture_size;
+    max_dimension = _max_texture_dimension;
   }
 
   if (max_dimension == 0) {
@@ -3421,48 +3474,51 @@ apply_texture_immediate(CLP(TextureContext) *gtc, Texture *tex) {
   // If it doesn't fit, we have to reduce it on-the-fly.  This is kind
   // of expensive and it doesn't look great; it would have been better
   // if the user had specified max-texture-dimension to reduce the
-  // texture at load time instead.
-  if (width > max_dimension) {
-    int byte_chunk = texel_size;
-    int stride = 1;
-    int new_width = width;
-    while (new_width > max_dimension) {
-      stride <<= 1;
-      new_width >>= 1;
+  // 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 (width > max_dimension) {
+      int byte_chunk = texel_size;
+      int stride = 1;
+      int new_width = width;
+      while (new_width > max_dimension) {
+        stride <<= 1;
+        new_width >>= 1;
+      }
+      GLCAT.info()
+        << "Reducing width of " << tex->get_name()
+        << " from " << width << " to " << new_width << "\n";
+      image = reduce_image(image, byte_chunk, stride);
+      width = new_width;
     }
-    GLCAT.info()
-      << "Reducing width of " << tex->get_name()
-      << " from " << width << " to " << new_width << "\n";
-    image = reduce_image(image, byte_chunk, stride);
-    width = new_width;
-  }
-  if (height > max_dimension) {
-    int byte_chunk = width * texel_size;
-    int stride = 1;
-    int new_height = height;
-    while (new_height > max_dimension) {
-      stride <<= 1;
-      new_height >>= 1;
+    if (height > max_dimension) {
+      int byte_chunk = width * texel_size;
+      int stride = 1;
+      int new_height = height;
+      while (new_height > max_dimension) {
+        stride <<= 1;
+        new_height >>= 1;
+      }
+      GLCAT.info()
+        << "Reducing height of " << tex->get_name()
+        << " from " << height << " to " << new_height << "\n";
+      image = reduce_image(image, byte_chunk, stride);
+      height = new_height;
     }
-    GLCAT.info()
-      << "Reducing height of " << tex->get_name()
-      << " from " << height << " to " << new_height << "\n";
-    image = reduce_image(image, byte_chunk, stride);
-    height = new_height;
-  }
-  if (depth > max_dimension) {
-    int byte_chunk = height * width * texel_size;
-    int stride = 1;
-    int new_depth = depth;
-    while (new_depth > max_dimension) {
-      stride <<= 1;
-      new_depth >>= 1;
+    if (depth > max_dimension) {
+      int byte_chunk = height * width * texel_size;
+      int stride = 1;
+      int new_depth = depth;
+      while (new_depth > max_dimension) {
+        stride <<= 1;
+        new_depth >>= 1;
+      }
+      GLCAT.info()
+        << "Reducing depth of " << tex->get_name()
+        << " from " << depth << " to " << new_depth << "\n";
+      image = reduce_image(image, byte_chunk, stride);
+      depth = new_depth;
     }
-    GLCAT.info()
-      << "Reducing depth of " << tex->get_name()
-      << " from " << depth << " to " << new_depth << "\n";
-    image = reduce_image(image, byte_chunk, stride);
-    depth = new_depth;
   }
 
   if (!_supports_bgr) {
@@ -4542,7 +4598,7 @@ finish_modify_state() {
   }
 
   // Apply the texture matrix, if needed.
-  if (_needs_tex_mat) {
+  if (_needs_tex_mat || _needs_tex_gen) {
     _needs_tex_mat = false;
 
     int num_stages = _current_texture->get_num_on_stages();
@@ -4590,8 +4646,9 @@ finish_modify_state() {
     for (int i = 0; i < num_stages; i++) {
       TextureStage *stage = _current_texture->get_on_stage(i);
       _glActiveTexture(GL_TEXTURE0 + i);
-      
-      switch (_current_tex_gen->get_mode(stage)) {
+
+      TexGenAttrib::Mode mode = _current_tex_gen->get_mode(stage);
+      switch (mode) {
       case TexGenAttrib::M_off:
         GLP(Disable)(GL_TEXTURE_GEN_S);
         GLP(Disable)(GL_TEXTURE_GEN_T);
@@ -4599,7 +4656,7 @@ finish_modify_state() {
         GLP(Disable)(GL_TEXTURE_GEN_Q);
         break;
         
-      case TexGenAttrib::M_sphere_map:
+      case TexGenAttrib::M_eye_sphere_map:
         GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
         GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
         GLP(Enable)(GL_TEXTURE_GEN_S);
@@ -4609,8 +4666,28 @@ finish_modify_state() {
         force_normal = true;
         break;
         
-      case TexGenAttrib::M_cube_map:
+      case TexGenAttrib::M_eye_cube_map:
+      case TexGenAttrib::M_world_cube_map:
         if (_supports_cube_map) {
+          if (mode != TexGenAttrib::M_eye_cube_map) {
+            // We dynamically transform normals from eye space to
+            // world space by applying the appropriate rotation
+            // transform to the current texture matrix.  Although it's
+            // tempting to try, we can't safely convert to object
+            // space, since this method doesn't get called with each
+            // different object.
+            CPT(TransformState) transform = _scene_setup->get_render_transform();
+            transform = transform->invert_compose(TransformState::make_identity());
+            LMatrix4f mat = transform->get_mat();
+            mat.set_row(3, LVecBase3f(0.0f, 0.0f, 0.0f));
+            GLP(MatrixMode)(GL_TEXTURE);
+            GLP(MultMatrixf)(mat.get_data());
+
+            // Now we need to reset the texture matrix next time
+            // around to undo this.
+            _needs_tex_mat = true;
+          }
+
           GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
           GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
           GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
@@ -4626,6 +4703,44 @@ finish_modify_state() {
           GLP(Disable)(GL_TEXTURE_GEN_Q);
         }
         break;
+        
+      case TexGenAttrib::M_eye_normal:
+      case TexGenAttrib::M_world_normal:
+        if (_supports_cube_map) {
+          if (mode != TexGenAttrib::M_eye_normal) {
+            // We dynamically transform normals from eye space to
+            // world space by applying the appropriate rotation
+            // transform to the current texture matrix.  Although it's
+            // tempting to try, we can't safely convert to object
+            // space, since this method doesn't get called with each
+            // different object.
+            CPT(TransformState) transform = _scene_setup->get_render_transform();
+            transform = transform->invert_compose(TransformState::make_identity());
+            LMatrix4f mat = transform->get_mat();
+            mat.set_row(3, LVecBase3f(0.0f, 0.0f, 0.0f));
+            GLP(MatrixMode)(GL_TEXTURE);
+            GLP(MultMatrixf)(mat.get_data());
+
+            // Now we need to reset the texture matrix next time
+            // around to undo this.
+            _needs_tex_mat = true;
+          }
+
+          GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP);
+          GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP);
+          GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP);
+          GLP(Enable)(GL_TEXTURE_GEN_S);
+          GLP(Enable)(GL_TEXTURE_GEN_T);
+          GLP(Enable)(GL_TEXTURE_GEN_R);
+          GLP(Disable)(GL_TEXTURE_GEN_Q);
+          force_normal = true;
+        } else {
+          GLP(Disable)(GL_TEXTURE_GEN_S);
+          GLP(Disable)(GL_TEXTURE_GEN_T);
+          GLP(Disable)(GL_TEXTURE_GEN_R);
+          GLP(Disable)(GL_TEXTURE_GEN_Q);
+        }
+        break;
 
       case TexGenAttrib::M_object_position:
         GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
@@ -4698,24 +4813,6 @@ finish_modify_state() {
           GLP(PopMatrix)();
         }
         break;
-        
-      case TexGenAttrib::M_object_normal:
-        if (_supports_cube_map) {
-          GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP);
-          GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP);
-          GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP);
-          GLP(Enable)(GL_TEXTURE_GEN_S);
-          GLP(Enable)(GL_TEXTURE_GEN_T);
-          GLP(Enable)(GL_TEXTURE_GEN_R);
-          GLP(Disable)(GL_TEXTURE_GEN_Q);
-          force_normal = true;
-        } else {
-          GLP(Disable)(GL_TEXTURE_GEN_S);
-          GLP(Disable)(GL_TEXTURE_GEN_T);
-          GLP(Disable)(GL_TEXTURE_GEN_R);
-          GLP(Disable)(GL_TEXTURE_GEN_Q);
-        }
-        break;
       }
     }
 
@@ -4829,16 +4926,20 @@ do_issue_texture() {
       _glActiveTexture(GL_TEXTURE0 + i);
 
       GLenum target = get_texture_target(texture->get_texture_type());
+
+      // First, turn off the previous texture mode.
+      GLP(Disable)(GL_TEXTURE_1D);
+      GLP(Disable)(GL_TEXTURE_2D);
+      if (_supports_3d_texture) {
+        GLP(Disable)(GL_TEXTURE_3D);
+      }
+      if (_supports_cube_map) {
+        GLP(Disable)(GL_TEXTURE_CUBE_MAP);
+      }
+
+      // Then, turn on the current texture mode.
       if (target == GL_NONE) {
         // Unsupported texture mode.
-        GLP(Disable)(GL_TEXTURE_1D);
-        GLP(Disable)(GL_TEXTURE_2D);
-        if (_supports_3d_texture) {
-          GLP(Disable)(GL_TEXTURE_3D);
-        }
-        if (_supports_cube_map) {
-          GLP(Disable)(GL_TEXTURE_CUBE_MAP);
-        }
         break;
       }
       GLP(Enable)(target);

+ 2 - 6
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -97,9 +97,9 @@ public:
   virtual void release_geom(GeomContext *gc);
 
   virtual void framebuffer_copy_to_texture
-    (Texture *tex, const DisplayRegion *dr, const RenderBuffer &rb);
+    (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb);
   virtual bool framebuffer_copy_to_ram
-    (Texture *tex, const DisplayRegion *dr, const RenderBuffer &rb);
+    (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb);
 
   virtual void apply_material(const Material *material);
   void apply_fog(Fog *fog);
@@ -301,10 +301,6 @@ public:
 
   bool _supports_cube_map;
 
-  GLint _max_texture_size;
-  GLint _max_3d_texture_size;
-  GLint _max_cube_map_size;
-
   bool _supports_bgr;
   bool _supports_rescale_normal;
 

+ 115 - 2
panda/src/gobj/texture.cxx

@@ -28,6 +28,7 @@
 #include "string_utils.h"
 #include "preparedGraphicsObjects.h"
 #include "pnmImage.h"
+#include "virtualFileSystem.h"
 
 #include <stddef.h>
 
@@ -263,6 +264,118 @@ write(const Filename &name, int z) const {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_pages
+//       Access: Published
+//  Description: Automatically reads in a sequence of pages, for the
+//               purpose of reading in a 3-d texture or a cube map
+//               texture.  The filename should contain a sequence of
+//               one or more hash marks ("#") which will be filled in
+//               with the z value of each page, zero-based.  If z_size
+//               is specified, the reading will stop there; otherwise,
+//               all found textures will be loaded, until a gap in the
+//               sequence is encountered.
+//
+//               If more than one hash mark is used, the numbers will
+//               be padded with zeroes if necessary to the
+//               corresponding number of digits.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_pages(const Filename &fullpath_template, int z_size) {
+  string fp = fullpath_template.get_fullpath();
+  size_t hash = fp.rfind('#');
+  if (hash == string::npos) {
+    gobj_cat.error()
+      << "Template " << fullpath_template << " contains no hash marks.\n";
+    return false;
+  }
+
+  // Count the number of hash marks.
+  size_t num_hash = 1;
+  while (hash >= num_hash && fp[hash - num_hash] == '#') {
+    num_hash++;
+  }
+
+  string prefix = fp.substr(0, hash - num_hash + 1);
+  string suffix = fp.substr(hash + 1);
+
+  clear_ram_image();
+
+  if (z_size != 0) {
+    set_z_size(z_size);
+    for (int z = 0; z < z_size; z++) {
+      ostringstream strm;
+      strm << prefix << setw(num_hash) << setfill('0') << z << suffix;
+      if (!read(strm.str(), z)) {
+        return false;
+      }
+    }
+  } else {
+    set_z_size(0);
+    int z = 0;
+    ostringstream strm;
+    strm << prefix << setw(num_hash) << setfill('0') << z << suffix;
+    Filename file(strm.str());
+    VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+
+    while (vfs->exists(file)) {
+      if (!read(file, z)) {
+        return false;
+      }
+      ++z;
+
+      ostringstream strm;
+      strm << prefix << setw(num_hash) << setfill('0') << z << suffix;
+      file = Filename(strm.str());
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::write_pages
+//       Access: Published
+//  Description: Automatically writes out a sequence of pages, for the
+//               purpose of writing out a 3-d texture or a cube map
+//               texture.  The filename should contain a sequence of
+//               one or more hash marks ("#") which will be filled in
+//               with the z value of each page, zero-based.
+//
+//               If more than one hash mark is used, the numbers will
+//               be padded with zeroes if necessary to the
+//               corresponding number of digits.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+write_pages(const Filename &fullpath_template) {
+  string fp = fullpath_template.get_fullpath();
+  size_t hash = fp.rfind('#');
+  if (hash == string::npos) {
+    gobj_cat.error()
+      << "Template " << fullpath_template << " contains no hash marks.\n";
+    return false;
+  }
+
+  // Count the number of hash marks.
+  size_t num_hash = 1;
+  while (hash >= num_hash && fp[hash - num_hash] == '#') {
+    num_hash++;
+  }
+
+  string prefix = fp.substr(0, hash - num_hash + 1);
+  string suffix = fp.substr(hash + 1);
+
+  for (int z = 0; z < _z_size; z++) {
+    ostringstream strm;
+    strm << prefix << setw(num_hash) << setfill('0') << z << suffix;
+    if (!write(strm.str(), z)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::load
 //       Access: Published
@@ -500,7 +613,7 @@ store(PNMImage &pnmimage, int z) const {
       }
     }
 
-    nassertr(idx == (int)get_expected_ram_image_size(), false);
+    nassertr((size_t)idx == get_expected_ram_page_size() * (z + 1), false);
 
     return true;
 
@@ -526,7 +639,7 @@ store(PNMImage &pnmimage, int z) const {
       }
     }
 
-    nassertr(idx == (int)get_expected_ram_image_size(), false);
+    nassertr((size_t)idx == get_expected_ram_page_size() * (z + 1), false);
 
     return true;
   }

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

@@ -157,6 +157,9 @@ PUBLISHED:
             int primary_file_num_channels = 0, int alpha_file_channel = 0);
   bool write(const Filename &fullpath, int z = 0) const;
 
+  bool read_pages(const Filename &fullpath_template, int z_size = 0);
+  bool write_pages(const Filename &fullpath_template);
+
   bool load(const PNMImage &pnmimage, int z = 0);
   bool store(PNMImage &pnmimage, int z = 0) const;
 

+ 2 - 2
panda/src/grutil/multitexReducer.cxx

@@ -263,8 +263,8 @@ flatten(GraphicsOutput *window) {
     multitex_name_strm << "multitex" << multitex_id;
     multitex_id++;
 
-    GraphicsOutput *buffer = 
-      window->make_texture_buffer(multitex_name_strm.str(), x_size, y_size);
+    GraphicsOutput *buffer = window->make_texture_buffer
+      (multitex_name_strm.str(), x_size, y_size, NULL, false);
     buffer->set_one_shot(true);
     Texture *tex = buffer->get_texture();
     tex->set_anisotropic_degree(aniso_degree);

+ 2 - 2
panda/src/gsgbase/graphicsStateGuardianBase.h

@@ -166,9 +166,9 @@ public:
   virtual void draw_sphere(GeomSphere *geom, GeomContext *gc)=0;
 
   virtual void framebuffer_copy_to_texture
-  (Texture *tex, const DisplayRegion *dr, const RenderBuffer &rb)=0;
+  (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb)=0;
   virtual bool framebuffer_copy_to_ram
-  (Texture *tex, const DisplayRegion *dr, const RenderBuffer &rb)=0;
+  (Texture *tex, int z, const DisplayRegion *dr, const RenderBuffer &rb)=0;
 
   virtual bool framebuffer_bind_to_texture(GraphicsOutput *win, Texture *tex)=0;
   virtual void framebuffer_release_texture(GraphicsOutput *win, Texture *tex)=0;

+ 17 - 7
panda/src/pgraph/texGenAttrib.cxx

@@ -157,12 +157,25 @@ output(ostream &out) const {
     case M_off:
       out << "off";
       break;
-    case M_sphere_map:
-      out << "sphere_map";
+
+    case M_eye_sphere_map:
+      out << "eye_sphere_map";
+      break;
+
+    case M_world_cube_map:
+      out << "world_cube_map";
+      break;
+    case M_eye_cube_map:
+      out << "eye_cube_map";
       break;
-    case M_cube_map:
-      out << "cube_map";
+
+    case M_world_normal:
+      out << "world_normal";
+      break;
+    case M_eye_normal:
+      out << "eye_normal";
       break;
+
     case M_world_position:
       out << "world_position";
       break;
@@ -172,9 +185,6 @@ output(ostream &out) const {
     case M_eye_position:
       out << "eye_position";
       break;
-    case M_object_normal:
-      out << "object_normal";
-      break;
     }
     out << ")";
   }

+ 33 - 3
panda/src/pgraph/texGenAttrib.h

@@ -40,12 +40,42 @@ class EXPCL_PANDA TexGenAttrib : public RenderAttrib {
 PUBLISHED:
   enum Mode {
     M_off,
-    M_sphere_map,
-    M_cube_map,
+
+    // In the types below, "eye" means the coordinate space of the
+    // observing camera, "object" means the local coordinate space of
+    // the object, and "world" means world coordinates, e.g. the
+    // coordinate space of the root of the graph.
+
+    // Sphere maps are classic static reflection maps.  They are
+    // supported on just about any hardware, and require a precomputed
+    // 180-degree fisheye image.  Sphere maps only make sense in eye
+    // coordinate space.
+    M_eye_sphere_map,
+
+    // Cube maps are a modern improvement on the sphere map; they
+    // don't suffer from any polar singularities, but they require six
+    // texture images.  They can also be generated dynamically for
+    // real-time reflections (see GraphicsOutput::make_cube_map()).
+    // Typically, a statically-generated cube map will be in eye
+    // space, while a dynamically-generated map will be in world space
+    // or object space (depending on where the camera rig that
+    // generates the map is parented).
+
+    // Cube mapping is not supported on all hardware.
+    M_world_cube_map,
+    M_eye_cube_map,
+
+    // Normal maps are most useful for applying diffuse lighting
+    // effects via a pregenerated cube map.
+    M_world_normal,
+    M_eye_normal,
+
+    // Position maps convert XYZ coordinates directly to texture
+    // coordinates.  This is particularly useful for implementing
+    // projective texturing (see NodePath::project_texture()).
     M_world_position,
     M_object_position,
     M_eye_position,
-    M_object_normal,
   };
 
 protected:

+ 19 - 6
panda/src/wgldisplay/wglGraphicsBuffer.cxx

@@ -43,7 +43,6 @@ wglGraphicsBuffer(GraphicsPipe *pipe, GraphicsStateGuardian *gsg,
   // Since the pbuffer never gets flipped, we get screenshots from the
   // same buffer we draw into.
   _screenshot_buffer_type = _draw_buffer_type;
-  _render_texture = false;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -204,12 +203,25 @@ open_buffer() {
   if (wgldisplay_cat.is_debug()) {
     wgldisplay_cat.debug()
       << "Created PBuffer " << _pbuffer << ", DC " << _pbuffer_dc << "\n";
-    if (_render_texture) {
+    switch (_rtm_mode) {
+    case RTM_bind_texture:
       wgldisplay_cat.debug()
         << "pbuffer renders directly to texture.\n";
-    } else if (_copy_texture) {
+      break;
+
+    case RTM_copy_texture:
+    case RTM_bind_if_possible:
       wgldisplay_cat.debug()
         << "pbuffer copies indirectly into texture.\n";
+      break;
+
+    case RTM_copy_ram:
+      wgldisplay_cat.debug()
+        << "pbuffer copies indirectly into system RAM.\n";
+      break;
+
+    default:
+      break;
     }
   }
   
@@ -245,14 +257,15 @@ make_pbuffer(HDC twindow_dc) {
   if (wglgsg->_supports_pixel_format) {
     bool got_pbuffer_format = false;
 
-    if (_copy_texture && wglgsg->_supports_render_texture) {
+    if (_rtm_mode == RTM_bind_if_possible && 
+        wglgsg->_supports_render_texture) {
       // First, try to get a pbuffer format that supports
       // render-to-texture.
       int new_pbformat = choose_pbuffer_format(twindow_dc, true);
       if (new_pbformat != 0) {
         pbformat = new_pbformat;
         got_pbuffer_format = true;
-        _render_texture = true;
+        _rtm_mode = RTM_bind_texture;
       }
     }
 
@@ -279,7 +292,7 @@ make_pbuffer(HDC twindow_dc) {
   int iattrib_list[max_attrib_list];
   int ni = 0;
 
-  if (_render_texture) {
+  if (_rtm_mode == RTM_bind_texture) {
     if (_gsg->get_properties().get_frame_buffer_mode() & FrameBufferProperties::FM_alpha) {
       iattrib_list[ni++] = WGL_TEXTURE_FORMAT_ARB;
       iattrib_list[ni++] = WGL_TEXTURE_RGBA_ARB;