Bläddra i källkod

stereo/multiview textures

David Rose 14 år sedan
förälder
incheckning
6872488839
63 ändrade filer med 1288 tillägg och 406 borttagningar
  1. 71 7
      direct/src/showbase/Loader.py
  2. 38 1
      panda/src/display/displayRegion.I
  3. 30 0
      panda/src/display/displayRegion.cxx
  4. 5 1
      panda/src/display/displayRegion.h
  5. 2 2
      panda/src/display/graphicsEngine.cxx
  6. 1 1
      panda/src/display/graphicsOutput.cxx
  7. 12 0
      panda/src/display/graphicsStateGuardian.I
  8. 5 9
      panda/src/display/graphicsStateGuardian.cxx
  9. 3 3
      panda/src/display/graphicsStateGuardian.h
  10. 25 0
      panda/src/display/stereoDisplayRegion.cxx
  11. 1 0
      panda/src/display/stereoDisplayRegion.h
  12. 27 2
      panda/src/doc/eggSyntax.txt
  13. 7 8
      panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx
  14. 2 3
      panda/src/dxgsg8/dxGraphicsStateGuardian8.h
  15. 2 2
      panda/src/dxgsg8/dxTextureContext8.cxx
  16. 1 1
      panda/src/dxgsg8/dxTextureContext8.h
  17. 22 12
      panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
  18. 2 3
      panda/src/dxgsg9/dxGraphicsStateGuardian9.h
  19. 3 2
      panda/src/dxgsg9/dxShaderContext9.cxx
  20. 10 4
      panda/src/dxgsg9/dxTextureContext9.cxx
  21. 1 1
      panda/src/dxgsg9/dxTextureContext9.h
  22. 5 5
      panda/src/dxgsg9/wdxGraphicsBuffer9.cxx
  23. 79 0
      panda/src/egg/eggTexture.I
  24. 14 0
      panda/src/egg/eggTexture.cxx
  25. 11 0
      panda/src/egg/eggTexture.h
  26. 11 0
      panda/src/egg/parser.yxx
  27. 7 0
      panda/src/egg2pg/eggLoader.cxx
  28. 4 4
      panda/src/glstuff/glGraphicsBuffer_src.cxx
  29. 48 31
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  30. 2 3
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  31. 3 1
      panda/src/glstuff/glShaderContext_src.cxx
  32. 2 2
      panda/src/glstuff/glTextureContext_src.I
  33. 1 1
      panda/src/glstuff/glTextureContext_src.h
  34. 9 7
      panda/src/gobj/preparedGraphicsObjects.cxx
  35. 2 1
      panda/src/gobj/preparedGraphicsObjects.h
  36. 168 8
      panda/src/gobj/texture.I
  37. 344 188
      panda/src/gobj/texture.cxx
  38. 20 6
      panda/src/gobj/texture.h
  39. 15 2
      panda/src/gobj/textureContext.I
  40. 3 1
      panda/src/gobj/textureContext.h
  41. 14 6
      panda/src/gobj/texturePool.cxx
  42. 28 0
      panda/src/gobj/textureStage.I
  43. 12 2
      panda/src/gobj/textureStage.cxx
  44. 4 0
      panda/src/gobj/textureStage.h
  45. 64 17
      panda/src/grutil/ffmpegTexture.cxx
  46. 3 1
      panda/src/grutil/ffmpegTexture.h
  47. 30 9
      panda/src/grutil/movieTexture.cxx
  48. 1 0
      panda/src/grutil/movieTexture.h
  49. 1 1
      panda/src/grutil/pipeOcclusionCullTraverser.cxx
  50. 1 1
      panda/src/gsgbase/graphicsStateGuardianBase.h
  51. 15 6
      panda/src/movies/ffmpegVideoCursor.cxx
  52. 1 1
      panda/src/movies/ffmpegVideoCursor.h
  53. 1 1
      panda/src/osxdisplay/osxGraphicsWindow.mm
  54. 2 1
      panda/src/putil/bam.h
  55. 33 2
      panda/src/putil/loaderOptions.I
  56. 3 1
      panda/src/putil/loaderOptions.cxx
  57. 4 0
      panda/src/putil/loaderOptions.h
  58. 40 21
      panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx
  59. 8 9
      panda/src/tinydisplay/tinyGraphicsStateGuardian.h
  60. 2 2
      panda/src/tinydisplay/tinyTextureContext.I
  61. 1 1
      panda/src/tinydisplay/tinyTextureContext.h
  62. 1 1
      panda/src/vision/openCVTexture.cxx
  63. 1 1
      panda/src/wgldisplay/wglGraphicsBuffer.cxx

+ 71 - 7
direct/src/showbase/Loader.py

@@ -486,7 +486,9 @@ class Loader(DirectObject):
     # texture loading funcs
     # texture loading funcs
     def loadTexture(self, texturePath, alphaPath = None,
     def loadTexture(self, texturePath, alphaPath = None,
                     readMipmaps = False, okMissing = False,
                     readMipmaps = False, okMissing = False,
-                    minfilter = None, magfilter = None, anisotropicDegree = None):
+                    minfilter = None, magfilter = None,
+                    anisotropicDegree = None, loaderOptions = None,
+                    multiview = None):
         """
         """
         texturePath is a string.
         texturePath is a string.
 
 
@@ -521,13 +523,31 @@ class Loader(DirectObject):
         to apply to the texture when it is loaded.  Like minfilter and
         to apply to the texture when it is loaded.  Like minfilter and
         magfilter, egg-texture-cards may be a more robust way to apply
         magfilter, egg-texture-cards may be a more robust way to apply
         this setting.
         this setting.
+
+        If multiview is true, it indicates to load a multiview or
+        stereo texture.  In this case, the filename should contain a
+        hash character ('#') that will be replaced with '0' for the
+        left image and '1' for the right image.  Larger numbers are
+        also allowed if you need more than two views.
         """
         """
+        if loaderOptions == None:
+            loaderOptions = LoaderOptions()
+        else:
+            loaderOptions = LoaderOptions(loaderOptions)
+        if multiview is not None:
+            flags = loaderOptions.getTextureFlags()
+            if multiview:
+                flags |= LoaderOptions.TFMultiview
+            else:
+                flags &= ~LoaderOptions.TFMultiview
+            loaderOptions.setTextureFlags(flags)
+
         if alphaPath is None:
         if alphaPath is None:
             assert Loader.notify.debug("Loading texture: %s" % (texturePath))
             assert Loader.notify.debug("Loading texture: %s" % (texturePath))
-            texture = TexturePool.loadTexture(texturePath, 0, readMipmaps)
+            texture = TexturePool.loadTexture(texturePath, 0, readMipmaps, loaderOptions)
         else:
         else:
             assert Loader.notify.debug("Loading texture: %s %s" % (texturePath, alphaPath))
             assert Loader.notify.debug("Loading texture: %s %s" % (texturePath, alphaPath))
-            texture = TexturePool.loadTexture(texturePath, alphaPath, 0, 0, readMipmaps)
+            texture = TexturePool.loadTexture(texturePath, alphaPath, 0, 0, readMipmaps, loaderOptions)
         if not texture and not okMissing:
         if not texture and not okMissing:
             message = 'Could not load texture: %s' % (texturePath)
             message = 'Could not load texture: %s' % (texturePath)
             raise IOError, message
             raise IOError, message
@@ -542,7 +562,8 @@ class Loader(DirectObject):
         return texture
         return texture
 
 
     def load3DTexture(self, texturePattern, readMipmaps = False, okMissing = False,
     def load3DTexture(self, texturePattern, readMipmaps = False, okMissing = False,
-                      minfilter = None, magfilter = None, anisotropicDegree = None):
+                      minfilter = None, magfilter = None, anisotropicDegree = None,
+                      loaderOptions = None, multiview = None, numViews = 2):
         """
         """
         texturePattern is a string that contains a sequence of one or
         texturePattern is a string that contains a sequence of one or
         more hash characters ('#'), which will be filled in with the
         more hash characters ('#'), which will be filled in with the
@@ -558,9 +579,32 @@ class Loader(DirectObject):
         two sequences of hash characters; the first group is filled in
         two sequences of hash characters; the first group is filled in
         with the z-height number, and the second group with the mipmap
         with the z-height number, and the second group with the mipmap
         index number.
         index number.
+
+        If multiview is true, it indicates to load a multiview or
+        stereo texture.  In this case, numViews should also be
+        specified (the default is 2), and the sequence of texture
+        images will be divided into numViews views.  The total
+        z-height will be (numImages / numViews).  For instance, if you
+        read 16 images with numViews = 2, then you have created a
+        stereo multiview image, with z = 8.  In this example, images
+        numbered 0 - 7 will be part of the left eye view, and images
+        numbered 8 - 15 will be part of the right eye view.
         """
         """
         assert Loader.notify.debug("Loading 3-D texture: %s" % (texturePattern))
         assert Loader.notify.debug("Loading 3-D texture: %s" % (texturePattern))
-        texture = TexturePool.load3dTexture(texturePattern, readMipmaps)
+        if loaderOptions == None:
+            loaderOptions = LoaderOptions()
+        else:
+            loaderOptions = LoaderOptions(loaderOptions)
+        if multiview is not None:
+            flags = loaderOptions.getTextureFlags()
+            if multiview:
+                flags |= LoaderOptions.TFMultiview
+            else:
+                flags &= ~LoaderOptions.TFMultiview
+            loaderOptions.setTextureFlags(flags)
+            loaderOptions.setTextureNumViews(numViews)
+
+        texture = TexturePool.load3dTexture(texturePattern, readMipmaps, loaderOptions)
         if not texture and not okMissing:
         if not texture and not okMissing:
             message = 'Could not load 3-D texture: %s' % (texturePattern)
             message = 'Could not load 3-D texture: %s' % (texturePattern)
             raise IOError, message
             raise IOError, message
@@ -575,7 +619,8 @@ class Loader(DirectObject):
         return texture
         return texture
 
 
     def loadCubeMap(self, texturePattern, readMipmaps = False, okMissing = False,
     def loadCubeMap(self, texturePattern, readMipmaps = False, okMissing = False,
-                    minfilter = None, magfilter = None, anisotropicDegree = None):
+                    minfilter = None, magfilter = None, anisotropicDegree = None,
+                    loaderOptions = None, multiview = None):
         """
         """
         texturePattern is a string that contains a sequence of one or
         texturePattern is a string that contains a sequence of one or
         more hash characters ('#'), which will be filled in with the
         more hash characters ('#'), which will be filled in with the
@@ -591,9 +636,28 @@ class Loader(DirectObject):
         two sequences of hash characters; the first group is filled in
         two sequences of hash characters; the first group is filled in
         with the face index number, and the second group with the
         with the face index number, and the second group with the
         mipmap index number.
         mipmap index number.
+
+        If multiview is true, it indicates to load a multiview or
+        stereo cube map.  For a stereo cube map, 12 images will be
+        loaded--images numbered 0 - 5 will become the left eye view,
+        and images 6 - 11 will become the right eye view.  In general,
+        the number of images found on disk must be a multiple of six,
+        and each six images will define a new view.
         """
         """
         assert Loader.notify.debug("Loading cube map: %s" % (texturePattern))
         assert Loader.notify.debug("Loading cube map: %s" % (texturePattern))
-        texture = TexturePool.loadCubeMap(texturePattern, readMipmaps)
+        if loaderOptions == None:
+            loaderOptions = LoaderOptions()
+        else:
+            loaderOptions = LoaderOptions(loaderOptions)
+        if multiview is not None:
+            flags = loaderOptions.getTextureFlags()
+            if multiview:
+                flags |= LoaderOptions.TFMultiview
+            else:
+                flags &= ~LoaderOptions.TFMultiview
+            loaderOptions.setTextureFlags(flags)
+
+        texture = TexturePool.loadCubeMap(texturePattern, readMipmaps, loaderOptions)
         if not texture and not okMissing:
         if not texture and not okMissing:
             message = 'Could not load cube map: %s' % (texturePattern)
             message = 'Could not load cube map: %s' % (texturePattern)
             raise IOError, message
             raise IOError, message

+ 38 - 1
panda/src/display/displayRegion.I

@@ -191,11 +191,30 @@ get_sort() const {
 //               set_stereo_channel().
 //               set_stereo_channel().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE Lens::StereoChannel DisplayRegion::
 INLINE Lens::StereoChannel DisplayRegion::
-get_stereo_channel() {
+get_stereo_channel() const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
   return cdata->_stereo_channel;
   return cdata->_stereo_channel;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegion::get_tex_view_offset
+//       Access: Public
+//  Description: Returns the current texture view offset for this
+//               DisplayRegion.  This is normally set to zero.  If
+//               nonzero, it is used to select a particular view of
+//               any multiview textures that are rendered within this
+//               DisplayRegion.
+//
+//               For a StereoDisplayRegion, this is normally 0 for the
+//               left eye, and 1 for the right eye, to support stereo
+//               textures.
+////////////////////////////////////////////////////////////////////
+INLINE int DisplayRegion::
+get_tex_view_offset() const {
+  CDReader cdata(_cycler);
+  return cdata->_tex_view_offset;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: DisplayRegion::get_incomplete_render
 //     Function: DisplayRegion::get_incomplete_render
 //       Access: Published
 //       Access: Published
@@ -738,6 +757,24 @@ get_stereo_channel() {
   return _cdata->_stereo_channel;
   return _cdata->_stereo_channel;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegionPipelineReader::get_tex_view_offset
+//       Access: Public
+//  Description: Returns the current texture view offset for this
+//               DisplayRegion.  This is normally set to zero.  If
+//               nonzero, it is used to select a particular view of
+//               any multiview textures that are rendered within this
+//               DisplayRegion.
+//
+//               For a StereoDisplayRegion, this is normally 0 for the
+//               left eye, and 1 for the right eye, to support stereo
+//               textures.
+////////////////////////////////////////////////////////////////////
+INLINE int DisplayRegionPipelineReader::
+get_tex_view_offset() {
+  return _cdata->_tex_view_offset;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: DisplayRegionPipelineReader::get_cube_map_index
 //     Function: DisplayRegionPipelineReader::get_cube_map_index
 //       Access: Public
 //       Access: Public

+ 30 - 0
panda/src/display/displayRegion.cxx

@@ -269,6 +269,11 @@ set_sort(int sort) {
 //               An ordinary DisplayRegion may be set to SC_mono,
 //               An ordinary DisplayRegion may be set to SC_mono,
 //               SC_left, or SC_right.  You may set SC_stereo only on
 //               SC_left, or SC_right.  You may set SC_stereo only on
 //               a StereoDisplayRegion.
 //               a StereoDisplayRegion.
+//
+//               This call also resets tex_view_offset to its default
+//               value, which is 0 for the left eye or 1 for the right
+//               eye of a stereo display region, or 0 for a mono
+//               display region.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void DisplayRegion::
 void DisplayRegion::
 set_stereo_channel(Lens::StereoChannel stereo_channel) {
 set_stereo_channel(Lens::StereoChannel stereo_channel) {
@@ -278,6 +283,29 @@ set_stereo_channel(Lens::StereoChannel stereo_channel) {
 
 
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
   cdata->_stereo_channel = stereo_channel;
   cdata->_stereo_channel = stereo_channel;
+  cdata->_tex_view_offset = (stereo_channel == Lens::SC_right) ? 1 : 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegion::set_tex_view_offset
+//       Access: Published, Virtual
+//  Description: Sets the current texture view offset for this
+//               DisplayRegion.  This is normally set to zero.  If
+//               nonzero, it is used to select a particular view of
+//               any multiview textures that are rendered within this
+//               DisplayRegion.
+//
+//               For a StereoDisplayRegion, this is normally 0 for the
+//               left eye, and 1 for the right eye, to support stereo
+//               textures.  This is set automatically when you call
+//               set_stereo_channel().
+////////////////////////////////////////////////////////////////////
+void DisplayRegion::
+set_tex_view_offset(int tex_view_offset) {
+  nassertv(Thread::get_current_pipeline_stage() == 0);
+
+  CDWriter cdata(_cycler);
+  cdata->_tex_view_offset = tex_view_offset;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -770,6 +798,7 @@ CData() :
   _active(true),
   _active(true),
   _sort(0),
   _sort(0),
   _stereo_channel(Lens::SC_mono),
   _stereo_channel(Lens::SC_mono),
+  _tex_view_offset(0),
   _cube_map_index(-1)
   _cube_map_index(-1)
 {
 {
 }
 }
@@ -794,6 +823,7 @@ CData(const DisplayRegion::CData &copy) :
   _active(copy._active),
   _active(copy._active),
   _sort(copy._sort),
   _sort(copy._sort),
   _stereo_channel(copy._stereo_channel),
   _stereo_channel(copy._stereo_channel),
+  _tex_view_offset(copy._tex_view_offset),
   _cube_map_index(copy._cube_map_index)
   _cube_map_index(copy._cube_map_index)
 {
 {
 }
 }

+ 5 - 1
panda/src/display/displayRegion.h

@@ -95,7 +95,9 @@ PUBLISHED:
   INLINE int get_sort() const;
   INLINE int get_sort() const;
 
 
   virtual void set_stereo_channel(Lens::StereoChannel stereo_channel);
   virtual void set_stereo_channel(Lens::StereoChannel stereo_channel);
-  INLINE Lens::StereoChannel get_stereo_channel();
+  INLINE Lens::StereoChannel get_stereo_channel() const;
+  virtual void set_tex_view_offset(int tex_view_offset);
+  INLINE int get_tex_view_offset() const;
 
 
   virtual void set_incomplete_render(bool incomplete_render);
   virtual void set_incomplete_render(bool incomplete_render);
   INLINE bool get_incomplete_render() const;
   INLINE bool get_incomplete_render() const;
@@ -203,6 +205,7 @@ private:
     bool _active;
     bool _active;
     int _sort;
     int _sort;
     Lens::StereoChannel _stereo_channel;
     Lens::StereoChannel _stereo_channel;
+    int _tex_view_offset;
     int _cube_map_index;
     int _cube_map_index;
 
 
     PT(CallbackObject) _cull_callback;
     PT(CallbackObject) _cull_callback;
@@ -297,6 +300,7 @@ public:
   INLINE bool is_active() const;
   INLINE bool is_active() const;
   INLINE int get_sort() const;
   INLINE int get_sort() const;
   INLINE Lens::StereoChannel get_stereo_channel();
   INLINE Lens::StereoChannel get_stereo_channel();
+  INLINE int get_tex_view_offset();
   INLINE bool get_clear_depth_between_eyes() const;
   INLINE bool get_clear_depth_between_eyes() const;
   INLINE int get_cube_map_index() const;
   INLINE int get_cube_map_index() const;
   INLINE CallbackObject *get_draw_callback() const;
   INLINE CallbackObject *get_draw_callback() const;

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

@@ -1284,7 +1284,7 @@ cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr,
     new DisplayRegionPipelineReader(dr, current_thread);
     new DisplayRegionPipelineReader(dr, current_thread);
 
 
   win->change_scenes(dr_reader);
   win->change_scenes(dr_reader);
-  gsg->prepare_display_region(dr_reader, dr_reader->get_stereo_channel());
+  gsg->prepare_display_region(dr_reader);
 
 
   if (dr_reader->is_any_clear_active()) {
   if (dr_reader->is_any_clear_active()) {
     gsg->clear(dr);
     gsg->clear(dr);
@@ -1848,7 +1848,7 @@ do_draw(CullResult *cull_result, SceneSetup *scene_setup,
   {
   {
     DisplayRegionPipelineReader dr_reader(dr, current_thread);
     DisplayRegionPipelineReader dr_reader(dr, current_thread);
     win->change_scenes(&dr_reader);
     win->change_scenes(&dr_reader);
-    gsg->prepare_display_region(&dr_reader, dr_reader.get_stereo_channel());
+    gsg->prepare_display_region(&dr_reader);
     if (dr_reader.is_any_clear_active()) {
     if (dr_reader.is_any_clear_active()) {
       gsg->clear(dr_reader.get_object());
       gsg->clear(dr_reader.get_object());
     }
     }

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

@@ -1155,7 +1155,7 @@ clear(Thread *current_thread) {
     nassertv(_gsg != (GraphicsStateGuardian *)NULL);
     nassertv(_gsg != (GraphicsStateGuardian *)NULL);
 
 
     DisplayRegionPipelineReader dr_reader(_overlay_display_region, current_thread);
     DisplayRegionPipelineReader dr_reader(_overlay_display_region, current_thread);
-    _gsg->prepare_display_region(&dr_reader, Lens::SC_mono);
+    _gsg->prepare_display_region(&dr_reader);
     _gsg->clear(this);
     _gsg->clear(this);
   }
   }
 }
 }

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

@@ -922,6 +922,18 @@ get_current_stereo_channel() const {
   return _current_stereo_channel;
   return _current_stereo_channel;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_current_tex_view_offset
+//       Access: Public
+//  Description: Returns the current tex view offset, as set by the
+//               last call to prepare_display_region().  This is read
+//               from the current DisplayRegion.
+////////////////////////////////////////////////////////////////////
+INLINE int GraphicsStateGuardian::
+get_current_tex_view_offset() const {
+  return _current_tex_view_offset;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::get_current_lens
 //     Function: GraphicsStateGuardian::get_current_lens
 //       Access: Public
 //       Access: Public

+ 5 - 9
panda/src/display/graphicsStateGuardian.cxx

@@ -144,6 +144,7 @@ GraphicsStateGuardian(CoordinateSystem internal_coordinate_system,
   _data_reader = (GeomVertexDataPipelineReader *)NULL;
   _data_reader = (GeomVertexDataPipelineReader *)NULL;
   _current_display_region = (DisplayRegion*)NULL;
   _current_display_region = (DisplayRegion*)NULL;
   _current_stereo_channel = Lens::SC_mono;
   _current_stereo_channel = Lens::SC_mono;
+  _current_tex_view_offset = 0;
   _current_lens = (Lens *)NULL;
   _current_lens = (Lens *)NULL;
   _projection_mat = TransformState::make_identity();
   _projection_mat = TransformState::make_identity();
   _projection_mat_inv = TransformState::make_identity();
   _projection_mat_inv = TransformState::make_identity();
@@ -1284,22 +1285,17 @@ fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec) {
 //  Description: Makes the specified DisplayRegion current.  All
 //  Description: Makes the specified DisplayRegion current.  All
 //               future drawing and clear operations will be
 //               future drawing and clear operations will be
 //               constrained within the given DisplayRegion.
 //               constrained within the given DisplayRegion.
-//
-//               The stereo_channel parameter further qualifies the
-//               channel that is to be rendered into, in the case of a
-//               stereo display region.  Normally, in the monocular
-//               case, it is Lens::SC_mono.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void GraphicsStateGuardian::
 void GraphicsStateGuardian::
-prepare_display_region(DisplayRegionPipelineReader *dr,
-                       Lens::StereoChannel stereo_channel) {
+prepare_display_region(DisplayRegionPipelineReader *dr) {
   _current_display_region = dr->get_object();
   _current_display_region = dr->get_object();
-  _current_stereo_channel = stereo_channel;
+  _current_stereo_channel = dr->get_stereo_channel();
+  _current_tex_view_offset = dr->get_tex_view_offset();
   _effective_incomplete_render = _incomplete_render && _current_display_region->get_incomplete_render();
   _effective_incomplete_render = _incomplete_render && _current_display_region->get_incomplete_render();
 
 
   _stereo_buffer_mask = ~0;
   _stereo_buffer_mask = ~0;
 
 
-  switch (stereo_channel) {
+  switch (dr->get_stereo_channel()) {
   case Lens::SC_left:
   case Lens::SC_left:
     _color_write_mask = dr->get_window()->get_left_eye_color_mask();
     _color_write_mask = dr->get_window()->get_left_eye_color_mask();
     if (_current_properties->is_stereo()) {
     if (_current_properties->is_stereo()) {

+ 3 - 3
panda/src/display/graphicsStateGuardian.h

@@ -232,9 +232,7 @@ public:
   const LMatrix4f *fetch_specified_part(Shader::ShaderMatInput input, InternalName *name, LMatrix4f &t);
   const LMatrix4f *fetch_specified_part(Shader::ShaderMatInput input, InternalName *name, LMatrix4f &t);
   const Shader::ShaderPtrData *fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec);
   const Shader::ShaderPtrData *fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec);
 
 
-  virtual void prepare_display_region(DisplayRegionPipelineReader *dr,
-                                      Lens::StereoChannel stereo_channel);
-
+  virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
   virtual void clear_before_callback();
   virtual void clear_before_callback();
   virtual void clear_state_and_transform();
   virtual void clear_state_and_transform();
 
 
@@ -287,6 +285,7 @@ public:
 
 
   INLINE const DisplayRegion *get_current_display_region() const;
   INLINE const DisplayRegion *get_current_display_region() const;
   INLINE Lens::StereoChannel get_current_stereo_channel() const;
   INLINE Lens::StereoChannel get_current_stereo_channel() const;
+  INLINE int get_current_tex_view_offset() const;
   INLINE const Lens *get_current_lens() const;
   INLINE const Lens *get_current_lens() const;
 
 
   virtual const TransformState *get_cs_transform() const;
   virtual const TransformState *get_cs_transform() const;
@@ -390,6 +389,7 @@ protected:
 
 
   CPT(DisplayRegion) _current_display_region;
   CPT(DisplayRegion) _current_display_region;
   Lens::StereoChannel _current_stereo_channel;
   Lens::StereoChannel _current_stereo_channel;
+  int _current_tex_view_offset;
   CPT(Lens) _current_lens;
   CPT(Lens) _current_lens;
   CPT(TransformState) _projection_mat;
   CPT(TransformState) _projection_mat;
   CPT(TransformState) _projection_mat_inv;
   CPT(TransformState) _projection_mat_inv;

+ 25 - 0
panda/src/display/stereoDisplayRegion.cxx

@@ -197,6 +197,11 @@ set_sort(int sort) {
 //
 //
 //               SC_mono - the left eye is set to SC_mono and
 //               SC_mono - the left eye is set to SC_mono and
 //               activated; the right eye is deactivated.
 //               activated; the right eye is deactivated.
+//
+//               This call also resets tex_view_offset to its default
+//               value, which is 0 for the left eye or 1 for the right
+//               eye of a stereo display region, or 0 for a mono
+//               display region.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void StereoDisplayRegion::
 void StereoDisplayRegion::
 set_stereo_channel(Lens::StereoChannel stereo_channel) {
 set_stereo_channel(Lens::StereoChannel stereo_channel) {
@@ -233,6 +238,26 @@ set_stereo_channel(Lens::StereoChannel stereo_channel) {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: StereoDisplayRegion::set_tex_view_offset
+//       Access: Published, Virtual
+//  Description: Sets the current texture view offset for this
+//               DisplayRegion.  This is normally set to zero.  If
+//               nonzero, it is used to select a particular view of
+//               any multiview textures that are rendered within this
+//               DisplayRegion.
+//
+//               When you call this on a StereoDisplayRegion, it
+//               automatically sets the specified value on the left
+//               eye, and the specified value + 1 on the right eye.
+////////////////////////////////////////////////////////////////////
+void StereoDisplayRegion::
+set_tex_view_offset(int tex_view_offset) {
+  DisplayRegion::set_tex_view_offset(tex_view_offset);
+  _left_eye->set_tex_view_offset(tex_view_offset);
+  _right_eye->set_tex_view_offset(tex_view_offset + 1);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: StereoDisplayRegion::set_incomplete_render
 //     Function: StereoDisplayRegion::set_incomplete_render
 //       Access: Published, Virtual
 //       Access: Published, Virtual

+ 1 - 0
panda/src/display/stereoDisplayRegion.h

@@ -57,6 +57,7 @@ PUBLISHED:
   virtual void set_active(bool active);
   virtual void set_active(bool active);
   virtual void set_sort(int sort);
   virtual void set_sort(int sort);
   virtual void set_stereo_channel(Lens::StereoChannel stereo_channel);
   virtual void set_stereo_channel(Lens::StereoChannel stereo_channel);
+  virtual void set_tex_view_offset(int tex_view_offset);
   virtual void set_incomplete_render(bool incomplete_render);
   virtual void set_incomplete_render(bool incomplete_render);
   virtual void set_texture_reload_priority(int texture_reload_priority);
   virtual void set_texture_reload_priority(int texture_reload_priority);
   virtual void set_cull_traverser(CullTraverser *trav);
   virtual void set_cull_traverser(CullTraverser *trav);

+ 27 - 2
panda/src/doc/eggSyntax.txt

@@ -252,9 +252,34 @@ appear before they are referenced.
     present, must be included in the same image with the color
     present, must be included in the same image with the color
     channel(s).
     channel(s).
 
 
-  <Scalar> read-mipmaps { flag }
+  <Scalar> multiview { flag }
+
+    If this flag is nonzero, the texture is loaded as a multiview
+    texture.  In this case, the filename must contain a hash mark
+    ("#") as in the 3D or CUBE_MAP case, above, and the different
+    images are loaded into the different views of the multiview
+    textures.  If the texture is already a cube map texture, the
+    same hash sequence is used for both purposes: the first six images
+    define the first view, the next six images define the second view,
+    and so on.  If the texture is a 3-D texture, you must also specify
+    num-views, below, to tell the loader how many images are loaded
+    for views, and how many are loaded for levels.
+
+    A multiview texture is most often used to load stereo textures,
+    where a different image is presented to each eye viewing the
+    texture, but other uses are possible, such as for texture
+    animation.
+
+  <Scalar> num-views { count }
+
+    This is used only when loading a 3-D multiview texture.  It
+    specifies how many different views the texture holds; the z height
+    of the texture is then implicitly determined as (number of images)
+    / (number of views).
 
 
-    If this flag is nonzero, then pre-generated mipmap levels will be
+  <Scalar> read-mipmaps { flag }
+ 
+   If this flag is nonzero, then pre-generated mipmap levels will be
     loaded along with the texture.  In this case, the filename should
     loaded along with the texture.  In this case, the filename should
     contain a sequence of one or more hash mark ("#") characters,
     contain a sequence of one or more hash mark ("#") characters,
     which will be filled in with the mipmap level number; the texture
     which will be filled in with the mipmap level number; the texture

+ 7 - 8
panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx

@@ -155,8 +155,8 @@ DXGraphicsStateGuardian8::
 //               prepare a texture.  Instead, call Texture::prepare().
 //               prepare a texture.  Instead, call Texture::prepare().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 TextureContext *DXGraphicsStateGuardian8::
 TextureContext *DXGraphicsStateGuardian8::
-prepare_texture(Texture *tex) {
-  DXTextureContext8 *dtc = new DXTextureContext8(_prepared_objects, tex);
+prepare_texture(Texture *tex, int view) {
+  DXTextureContext8 *dtc = new DXTextureContext8(_prepared_objects, tex, view);
 
 
   if (!get_supports_compressed_texture_format(tex->get_ram_image_compression())) {
   if (!get_supports_compressed_texture_format(tex->get_ram_image_compression())) {
     dxgsg8_cat.error()
     dxgsg8_cat.error()
@@ -350,7 +350,7 @@ release_texture(TextureContext *tc) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian8::
 bool DXGraphicsStateGuardian8::
 extract_texture_data(Texture *tex) {
 extract_texture_data(Texture *tex) {
-  TextureContext *tc = tex->prepare_now(get_prepared_objects(), this);
+  TextureContext *tc = tex->prepare_now(0, get_prepared_objects(), this);
   nassertr(tc != (TextureContext *)NULL, false);
   nassertr(tc != (TextureContext *)NULL, false);
   DXTextureContext8 *dtc = DCAST(DXTextureContext8, tc);
   DXTextureContext8 *dtc = DCAST(DXTextureContext8, tc);
 
 
@@ -648,10 +648,9 @@ clear(DrawableRegion *clearable) {
 //       scissor region and viewport)
 //       scissor region and viewport)
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void DXGraphicsStateGuardian8::
 void DXGraphicsStateGuardian8::
-prepare_display_region(DisplayRegionPipelineReader *dr,
-                       Lens::StereoChannel stereo_channel) {
+prepare_display_region(DisplayRegionPipelineReader *dr) {
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
-  GraphicsStateGuardian::prepare_display_region(dr, stereo_channel);
+  GraphicsStateGuardian::prepare_display_region(dr);
 
 
   int l, u, w, h;
   int l, u, w, h;
   dr->get_region_pixels_i(l, u, w, h);
   dr->get_region_pixels_i(l, u, w, h);
@@ -1501,7 +1500,7 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
   dr->get_region_pixels_i(xo, yo, w, h);
   dr->get_region_pixels_i(xo, yo, w, h);
   tex->set_size_padded(w, h);
   tex->set_size_padded(w, h);
   
   
-  TextureContext *tc = tex->prepare_now(get_prepared_objects(), this);
+  TextureContext *tc = tex->prepare_now(0, get_prepared_objects(), this);
   if (tc == (TextureContext *)NULL) {
   if (tc == (TextureContext *)NULL) {
     return false;
     return false;
   }
   }
@@ -2849,7 +2848,7 @@ do_issue_texture() {
 
 
     // We always reissue every stage in DX, just in case the texcoord
     // We always reissue every stage in DX, just in case the texcoord
     // index or texgen mode or some other property has changed.
     // index or texgen mode or some other property has changed.
-    TextureContext *tc = texture->prepare_now(_prepared_objects, this);
+    TextureContext *tc = texture->prepare_now(0, _prepared_objects, this);
     apply_texture(si, tc);
     apply_texture(si, tc);
     set_texture_blend_mode(si, stage);
     set_texture_blend_mode(si, stage);
 
 

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

@@ -49,7 +49,7 @@ public:
   FrameBufferProperties
   FrameBufferProperties
     calc_fb_properties(DWORD cformat, DWORD dformat, DWORD multisampletype);
     calc_fb_properties(DWORD cformat, DWORD dformat, DWORD multisampletype);
 
 
-  virtual TextureContext *prepare_texture(Texture *tex);
+  virtual TextureContext *prepare_texture(Texture *tex, int view);
   void apply_texture(int i, TextureContext *tc);
   void apply_texture(int i, TextureContext *tc);
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual bool update_texture(TextureContext *tc, bool force);
   bool upload_texture(DXTextureContext8 *dtc, bool force);
   bool upload_texture(DXTextureContext8 *dtc, bool force);
@@ -71,8 +71,7 @@ public:
 
 
   virtual void clear(DrawableRegion *region);
   virtual void clear(DrawableRegion *region);
 
 
-  virtual void prepare_display_region(DisplayRegionPipelineReader *dr,
-                                      Lens::StereoChannel stereo_channel);
+  virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
   virtual CPT(TransformState) calc_projection_mat(const Lens *lens);
   virtual CPT(TransformState) calc_projection_mat(const Lens *lens);
   virtual bool prepare_lens();
   virtual bool prepare_lens();
 
 

+ 2 - 2
panda/src/dxgsg8/dxTextureContext8.cxx

@@ -33,8 +33,8 @@ static const DWORD g_LowByteMask = 0x000000FF;
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 DXTextureContext8::
 DXTextureContext8::
-DXTextureContext8(PreparedGraphicsObjects *pgo, Texture *tex) :
-  TextureContext(pgo, tex) {
+DXTextureContext8(PreparedGraphicsObjects *pgo, Texture *tex, int view) :
+  TextureContext(pgo, tex, view) {
 
 
   if (dxgsg8_cat.is_spam()) {
   if (dxgsg8_cat.is_spam()) {
     dxgsg8_cat.spam()
     dxgsg8_cat.spam()

+ 1 - 1
panda/src/dxgsg8/dxTextureContext8.h

@@ -25,7 +25,7 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDADX DXTextureContext8 : public TextureContext {
 class EXPCL_PANDADX DXTextureContext8 : public TextureContext {
 public:
 public:
-  DXTextureContext8(PreparedGraphicsObjects *pgo, Texture *tex);
+  DXTextureContext8(PreparedGraphicsObjects *pgo, Texture *tex, int view);
   virtual ~DXTextureContext8();
   virtual ~DXTextureContext8();
 
 
   virtual void evict_lru();
   virtual void evict_lru();

+ 22 - 12
panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx

@@ -196,8 +196,8 @@ DXGraphicsStateGuardian9::
 //               prepare a texture.  Instead, call Texture::prepare().
 //               prepare a texture.  Instead, call Texture::prepare().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 TextureContext *DXGraphicsStateGuardian9::
 TextureContext *DXGraphicsStateGuardian9::
-prepare_texture(Texture *tex) {
-  DXTextureContext9 *dtc = new DXTextureContext9(_prepared_objects, tex);
+prepare_texture(Texture *tex, int view) {
+  DXTextureContext9 *dtc = new DXTextureContext9(_prepared_objects, tex, view);
 
 
   if (!get_supports_compressed_texture_format(tex->get_ram_image_compression())) {
   if (!get_supports_compressed_texture_format(tex->get_ram_image_compression())) {
     dxgsg9_cat.error()
     dxgsg9_cat.error()
@@ -410,11 +410,20 @@ release_texture(TextureContext *tc) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian9::
 bool DXGraphicsStateGuardian9::
 extract_texture_data(Texture *tex) {
 extract_texture_data(Texture *tex) {
-  TextureContext *tc = tex->prepare_now(get_prepared_objects(), this);
-  nassertr(tc != (TextureContext *)NULL, false);
-  DXTextureContext9 *dtc = DCAST(DXTextureContext9, tc);
+  bool success = true;
+
+  int num_views = tex->get_num_views();
+  for (int view = 0; view < num_views; ++view) {
+    TextureContext *tc = tex->prepare_now(view, get_prepared_objects(), this);
+    nassertr(tc != (TextureContext *)NULL, false);
+    DXTextureContext9 *dtc = DCAST(DXTextureContext9, tc);
+
+    if (!dtc->extract_texture_data(*_screen)) {
+      success = false;
+    }
+  }
 
 
-  return dtc->extract_texture_data(*_screen);
+  return success;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -929,10 +938,9 @@ clear(DrawableRegion *clearable) {
 //       scissor region and viewport)
 //       scissor region and viewport)
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void DXGraphicsStateGuardian9::
 void DXGraphicsStateGuardian9::
-prepare_display_region(DisplayRegionPipelineReader *dr,
-                       Lens::StereoChannel stereo_channel) {
+prepare_display_region(DisplayRegionPipelineReader *dr) {
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
-  GraphicsStateGuardian::prepare_display_region(dr, stereo_channel);
+  GraphicsStateGuardian::prepare_display_region(dr);
 
 
   int l, u, w, h;
   int l, u, w, h;
   dr->get_region_pixels_i(l, u, w, h);
   dr->get_region_pixels_i(l, u, w, h);
@@ -2012,7 +2020,8 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
   // must use a render target type texture for StretchRect
   // must use a render target type texture for StretchRect
   tex->set_render_to_texture(true);
   tex->set_render_to_texture(true);
 
 
-  TextureContext *tc = tex->prepare_now(get_prepared_objects(), this);
+  int view = dr->get_tex_view_offset();
+  TextureContext *tc = tex->prepare_now(view, get_prepared_objects(), this);
   if (tc == (TextureContext *)NULL) {
   if (tc == (TextureContext *)NULL) {
     return false;
     return false;
   }
   }
@@ -3776,7 +3785,8 @@ update_standard_texture_bindings() {
 
 
     // We always reissue every stage in DX, just in case the texcoord
     // We always reissue every stage in DX, just in case the texcoord
     // index or texgen mode or some other property has changed.
     // index or texgen mode or some other property has changed.
-    TextureContext *tc = texture->prepare_now(_prepared_objects, this);
+    int view = get_current_tex_view_offset() + stage->get_tex_view_offset();
+    TextureContext *tc = texture->prepare_now(view, _prepared_objects, this);
     apply_texture(si, tc);
     apply_texture(si, tc);
     set_texture_blend_mode(si, stage);
     set_texture_blend_mode(si, stage);
 
 
@@ -5732,7 +5742,7 @@ get_supports_cg_profile(const string &name) const {
     dxgsg9_cat.error() << name <<", unknown Cg-profile\n";
     dxgsg9_cat.error() << name <<", unknown Cg-profile\n";
     return false;
     return false;
   }
   }
-  return cgD3D9IsProfileSupported(cgGetProfile(name.c_str()));
+  return cgD3D9IsProfileSupported(cgGetProfile(name.c_str())) != 0;
 #endif  // HAVE_CG
 #endif  // HAVE_CG
 }
 }
 
 

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

@@ -72,7 +72,7 @@ public:
     calc_fb_properties(DWORD cformat, DWORD dformat,
     calc_fb_properties(DWORD cformat, DWORD dformat,
                        DWORD multisampletype, DWORD multisamplequality);
                        DWORD multisampletype, DWORD multisamplequality);
 
 
-  virtual TextureContext *prepare_texture(Texture *tex);
+  virtual TextureContext *prepare_texture(Texture *tex, int view);
   void apply_texture(int i, TextureContext *tc);
   void apply_texture(int i, TextureContext *tc);
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual bool update_texture(TextureContext *tc, bool force);
   bool upload_texture(DXTextureContext9 *dtc, bool force);
   bool upload_texture(DXTextureContext9 *dtc, bool force);
@@ -100,8 +100,7 @@ public:
 
 
   virtual void clear(DrawableRegion *clearable);
   virtual void clear(DrawableRegion *clearable);
 
 
-  virtual void prepare_display_region(DisplayRegionPipelineReader *dr,
-                                      Lens::StereoChannel stereo_channel);
+  virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
   virtual CPT(TransformState) calc_projection_mat(const Lens *lens);
   virtual CPT(TransformState) calc_projection_mat(const Lens *lens);
   virtual bool prepare_lens();
   virtual bool prepare_lens();
 
 

+ 3 - 2
panda/src/dxgsg9/dxShaderContext9.cxx

@@ -767,6 +767,7 @@ update_shader_texture_bindings(CLP(ShaderContext) *prev, GSG *gsg)
         continue;
         continue;
       }        
       }        
       Texture *tex = 0;
       Texture *tex = 0;
+      int view = gsg->get_current_tex_view_offset();
       InternalName *id = _shader->_tex_spec[i]._name;
       InternalName *id = _shader->_tex_spec[i]._name;
       if (id != 0) {
       if (id != 0) {
         const ShaderInput *input = gsg->_target_shader->get_shader_input(id);
         const ShaderInput *input = gsg->_target_shader->get_shader_input(id);
@@ -782,6 +783,7 @@ update_shader_texture_bindings(CLP(ShaderContext) *prev, GSG *gsg)
         }
         }
         TextureStage *stage = texattrib->get_on_stage(_shader->_tex_spec[i]._stage);
         TextureStage *stage = texattrib->get_on_stage(_shader->_tex_spec[i]._stage);
         tex = texattrib->get_on_texture(stage);
         tex = texattrib->get_on_texture(stage);
+        view += stage->get_tex_view_offset();
       }
       }
       if (_shader->_tex_spec[i]._suffix != 0) {
       if (_shader->_tex_spec[i]._suffix != 0) {
         // The suffix feature is inefficient. It is a temporary hack.
         // The suffix feature is inefficient. It is a temporary hack.
@@ -793,8 +795,7 @@ update_shader_texture_bindings(CLP(ShaderContext) *prev, GSG *gsg)
       if ((tex == 0) || (tex->get_texture_type() != _shader->_tex_spec[i]._desired_type)) {
       if ((tex == 0) || (tex->get_texture_type() != _shader->_tex_spec[i]._desired_type)) {
         continue;
         continue;
       }
       }
-      TextureContext *tc = tex->prepare_now(gsg->_prepared_objects, gsg);
-      //      TextureContext *tc = tex->prepare_now(gsg->get_prepared_objects(), gsg);
+      TextureContext *tc = tex->prepare_now(view, gsg->_prepared_objects, gsg);
       if (tc == (TextureContext*)NULL) {
       if (tc == (TextureContext*)NULL) {
         continue;
         continue;
       }
       }

+ 10 - 4
panda/src/dxgsg9/dxTextureContext9.cxx

@@ -35,8 +35,8 @@ static const DWORD g_LowByteMask = 0x000000FF;
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 DXTextureContext9::
 DXTextureContext9::
-DXTextureContext9(PreparedGraphicsObjects *pgo, Texture *tex) :
-  TextureContext(pgo, tex) {
+DXTextureContext9(PreparedGraphicsObjects *pgo, Texture *tex, int view) :
+  TextureContext(pgo, tex, view) {
 
 
   if (dxgsg9_cat.is_spam()) {
   if (dxgsg9_cat.is_spam()) {
     dxgsg9_cat.spam()
     dxgsg9_cat.spam()
@@ -1607,7 +1607,10 @@ HRESULT DXTextureContext9::fill_d3d_texture_mipmap_pixels(int mip_level, int dep
   DWORD height = (DWORD) get_texture()->get_expected_mipmap_y_size(mip_level);
   DWORD height = (DWORD) get_texture()->get_expected_mipmap_y_size(mip_level);
   int component_width = get_texture()->get_component_width();
   int component_width = get_texture()->get_component_width();
 
 
-  pixels += depth_index * get_texture()->get_expected_ram_mipmap_page_size(mip_level);
+  size_t view_size = get_texture()->get_ram_mipmap_view_size(mip_level);
+  pixels += view_size * get_view();
+  size_t page_size = get_texture()->get_expected_ram_mipmap_page_size(mip_level);
+  pixels += page_size * depth_index;
   
   
   if (get_texture()->get_texture_type() == Texture::TT_cube_map) {
   if (get_texture()->get_texture_type() == Texture::TT_cube_map) {
     nassertr(IS_VALID_PTR(_d3d_cube_texture), E_FAIL);
     nassertr(IS_VALID_PTR(_d3d_cube_texture), E_FAIL);
@@ -1842,7 +1845,7 @@ fill_d3d_texture_pixels(DXScreenData &scrn, bool compress_texture) {
 
 
     if (_has_mipmaps) {
     if (_has_mipmaps) {
       // if we have pre-calculated mipmap levels, use them, otherwise generate on the fly
       // if we have pre-calculated mipmap levels, use them, otherwise generate on the fly
-      int miplevel_count = _d3d_2d_texture->GetLevelCount(); // what if it's not a 2d texture?
+      int miplevel_count = _d3d_texture->GetLevelCount();
       if (miplevel_count <= tex->get_num_loadable_ram_mipmap_images()) {
       if (miplevel_count <= tex->get_num_loadable_ram_mipmap_images()) {
         dxgsg9_cat.debug()
         dxgsg9_cat.debug()
           << "Using pre-calculated mipmap levels for texture  " << tex->get_name() << "\n";
           << "Using pre-calculated mipmap levels for texture  " << tex->get_name() << "\n";
@@ -1949,6 +1952,9 @@ fill_d3d_volume_texture_pixels(DXScreenData &scrn) {
 
 
   nassertr(IS_VALID_PTR(image_pixels), E_FAIL);
   nassertr(IS_VALID_PTR(image_pixels), E_FAIL);
 
 
+  size_t view_size = tex->get_ram_mipmap_view_size(0);
+  image_pixels += view_size * get_view();
+
   IDirect3DVolume9 *mip_level_0 = NULL;
   IDirect3DVolume9 *mip_level_0 = NULL;
   bool using_temp_buffer = false;
   bool using_temp_buffer = false;
   BYTE *pixels = image_pixels;
   BYTE *pixels = image_pixels;

+ 1 - 1
panda/src/dxgsg9/dxTextureContext9.h

@@ -27,7 +27,7 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDADX DXTextureContext9 : public TextureContext {
 class EXPCL_PANDADX DXTextureContext9 : public TextureContext {
 public:
 public:
-  DXTextureContext9(PreparedGraphicsObjects *pgo, Texture *tex);
+  DXTextureContext9(PreparedGraphicsObjects *pgo, Texture *tex, int view);
   virtual ~DXTextureContext9();
   virtual ~DXTextureContext9();
 
 
   virtual void evict_lru();
   virtual void evict_lru();

+ 5 - 5
panda/src/dxgsg9/wdxGraphicsBuffer9.cxx

@@ -362,7 +362,7 @@ rebuild_bitplanes() {
 //    color_tex->set_format(Texture::F_rgba);
 //    color_tex->set_format(Texture::F_rgba);
     color_ctx =
     color_ctx =
       DCAST(DXTextureContext9,
       DCAST(DXTextureContext9,
-            color_tex->prepare_now(_gsg->get_prepared_objects(), _gsg));
+            color_tex->prepare_now(0, _gsg->get_prepared_objects(), _gsg));
 
 
     if (color_ctx) {
     if (color_ctx) {
       if (!color_ctx->create_texture(*_dxgsg->_screen)) {
       if (!color_ctx->create_texture(*_dxgsg->_screen)) {
@@ -441,7 +441,7 @@ rebuild_bitplanes() {
     depth_tex->set_format(Texture::F_depth_stencil);
     depth_tex->set_format(Texture::F_depth_stencil);
     depth_ctx =
     depth_ctx =
       DCAST(DXTextureContext9,
       DCAST(DXTextureContext9,
-            depth_tex->prepare_now(_gsg->get_prepared_objects(), _gsg));
+            depth_tex->prepare_now(0, _gsg->get_prepared_objects(), _gsg));
     if (depth_ctx) {
     if (depth_ctx) {
       if (!depth_ctx->create_texture(*_dxgsg->_screen)) {
       if (!depth_ctx->create_texture(*_dxgsg->_screen)) {
         dxgsg9_cat.error()
         dxgsg9_cat.error()
@@ -516,7 +516,7 @@ rebuild_bitplanes() {
           IDirect3DSurface9 *color_surf = 0;
           IDirect3DSurface9 *color_surf = 0;
           IDirect3DCubeTexture9 *color_cube = 0;
           IDirect3DCubeTexture9 *color_cube = 0;
 
 
-          color_ctx = DCAST(DXTextureContext9, tex->prepare_now(_gsg->get_prepared_objects(), _gsg));
+          color_ctx = DCAST(DXTextureContext9, tex->prepare_now(0, _gsg->get_prepared_objects(), _gsg));
           if (color_ctx) {
           if (color_ctx) {
             if (!color_ctx->create_texture(*_dxgsg->_screen)) {
             if (!color_ctx->create_texture(*_dxgsg->_screen)) {
               dxgsg9_cat.error()
               dxgsg9_cat.error()
@@ -607,7 +607,7 @@ select_cube_map(int cube_map_index) {
   if (color_tex) {
   if (color_tex) {
     color_ctx =
     color_ctx =
       DCAST(DXTextureContext9,
       DCAST(DXTextureContext9,
-            color_tex->prepare_now(_gsg->get_prepared_objects(), _gsg));
+            color_tex->prepare_now(0, _gsg->get_prepared_objects(), _gsg));
     color_cube = color_ctx->_d3d_cube_texture;
     color_cube = color_ctx->_d3d_cube_texture;
     if (color_cube && _cube_map_index >= 0 && _cube_map_index < 6) {
     if (color_cube && _cube_map_index >= 0 && _cube_map_index < 6) {
       hr = color_cube -> GetCubeMapSurface ((D3DCUBEMAP_FACES) _cube_map_index, 0, &color_surf);
       hr = color_cube -> GetCubeMapSurface ((D3DCUBEMAP_FACES) _cube_map_index, 0, &color_surf);
@@ -651,7 +651,7 @@ select_cube_map(int cube_map_index) {
           IDirect3DSurface9 *color_surf = 0;
           IDirect3DSurface9 *color_surf = 0;
           IDirect3DCubeTexture9 *color_cube = 0;
           IDirect3DCubeTexture9 *color_cube = 0;
 
 
-          color_ctx = DCAST(DXTextureContext9, tex->prepare_now(_gsg->get_prepared_objects(), _gsg));
+          color_ctx = DCAST(DXTextureContext9, tex->prepare_now(0, _gsg->get_prepared_objects(), _gsg));
           if (color_ctx) {
           if (color_ctx) {
             if (tex->get_texture_type() == Texture::TT_cube_map) {
             if (tex->get_texture_type() == Texture::TT_cube_map) {
 
 

+ 79 - 0
panda/src/egg/eggTexture.I

@@ -945,6 +945,85 @@ get_alpha_file_channel() const {
   return _alpha_file_channel;
   return _alpha_file_channel;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::set_multiview
+//       Access: Published
+//  Description: Sets the multiview flag.
+//
+//               If multiview is true, the filename should contain a
+//               hash mark ('#'), which will be filled in with the
+//               view number; and a multiview texture will be defined
+//               with a series of images, one for each view.
+//
+//               A multiview texture is most often used for stereo
+//               textures, but other uses are also possible, such as
+//               for texture animation.
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+set_multiview(bool multiview) {
+  _multiview = multiview;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::get_multiview
+//       Access: Published
+//  Description: Returns the current setting of the multiview flag.
+//               See set_multiview().
+////////////////////////////////////////////////////////////////////
+INLINE bool EggTexture::
+get_multiview() const {
+  return _multiview;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::set_num_views
+//       Access: Published
+//  Description: When loading a 3-D multiview texture, this parameter
+//               is necessary to specify how many views will be
+//               expected.  The z size is determined implicitly from
+//               the number of images loaded.
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+set_num_views(int num_views) {
+  _num_views = num_views;
+  _flags |= F_has_num_views;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::clear_num_views
+//       Access: Published
+//  Description: Removes the specification of the number of views
+//               for a 3-D multiview texture.
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+clear_num_views() {
+  _num_views = 0;
+  _flags &= ~F_has_num_views;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::has_num_views
+//       Access: Published
+//  Description: Returns true if the number of views has been
+//               specified for the 3-D multiview texture, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool EggTexture::
+has_num_views() const {
+  return (_flags & F_has_num_views) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::get_num_views
+//       Access: Published
+//  Description: Returns the specified number of views specified for
+//               the 3-D multiview texture.  See set_num_views().
+////////////////////////////////////////////////////////////////////
+INLINE int EggTexture::
+get_num_views() const {
+  return _num_views;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_read_mipmaps
 //     Function: EggTexture::set_read_mipmaps
 //       Access: Published
 //       Access: Published

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

@@ -43,6 +43,8 @@ EggTexture(const string &tref_name, const Filename &filename)
   _anisotropic_degree = 0;
   _anisotropic_degree = 0;
   _env_type = ET_unspecified;
   _env_type = ET_unspecified;
   _saved_result = false;
   _saved_result = false;
+  _multiview = false;
+  _num_views = 0;
   _tex_gen = TG_unspecified;
   _tex_gen = TG_unspecified;
   _quality_level = QL_unspecified;
   _quality_level = QL_unspecified;
   _priority = 0;
   _priority = 0;
@@ -89,6 +91,8 @@ operator = (const EggTexture &copy) {
   _anisotropic_degree = copy._anisotropic_degree;
   _anisotropic_degree = copy._anisotropic_degree;
   _env_type = copy._env_type;
   _env_type = copy._env_type;
   _saved_result = copy._saved_result;
   _saved_result = copy._saved_result;
+  _multiview = copy._multiview;
+  _num_views = copy._num_views;
   _tex_gen = copy._tex_gen;
   _tex_gen = copy._tex_gen;
   _quality_level = copy._quality_level;
   _quality_level = copy._quality_level;
   _stage_name = copy._stage_name;
   _stage_name = copy._stage_name;
@@ -287,6 +291,16 @@ write(ostream &out, int indent_level) const {
       << "<Scalar> alpha-scale { " << get_alpha_scale() << " }\n";
       << "<Scalar> alpha-scale { " << get_alpha_scale() << " }\n";
   }
   }
 
 
+  if (get_multiview()) {
+    indent(out, indent_level + 2)
+      << "<Scalar> multiview { 1 }\n";
+  }
+
+  if (has_num_views()) {
+    indent(out, indent_level + 2)
+      << "<Scalar> num-views { " << get_num_views() << " }\n";
+  }
+
   EggRenderMode::write(out, indent_level + 2);
   EggRenderMode::write(out, indent_level + 2);
 
 
   if (has_transform()) {
   if (has_transform()) {

+ 11 - 0
panda/src/egg/eggTexture.h

@@ -269,6 +269,14 @@ PUBLISHED:
   INLINE bool has_alpha_file_channel() const;
   INLINE bool has_alpha_file_channel() const;
   INLINE int get_alpha_file_channel() const;
   INLINE int get_alpha_file_channel() const;
 
 
+  INLINE void set_multiview(bool multiview);
+  INLINE bool get_multiview() const;
+
+  INLINE void set_num_views(int num_views);
+  INLINE void clear_num_views();
+  INLINE bool has_num_views() const;
+  INLINE int get_num_views() const;
+
   INLINE void set_read_mipmaps(bool read_mipmaps);
   INLINE void set_read_mipmaps(bool read_mipmaps);
   INLINE bool get_read_mipmaps() const;
   INLINE bool get_read_mipmaps() const;
 
 
@@ -310,6 +318,7 @@ private:
     F_has_rgb_scale          = 0x0100,
     F_has_rgb_scale          = 0x0100,
     F_has_alpha_scale        = 0x0200,
     F_has_alpha_scale        = 0x0200,
     F_has_border_color       = 0x0400,
     F_has_border_color       = 0x0400,
+    F_has_num_views          = 0x0800,
   };
   };
 
 
   TextureType _texture_type;
   TextureType _texture_type;
@@ -320,6 +329,8 @@ private:
   int _anisotropic_degree;
   int _anisotropic_degree;
   EnvType _env_type;
   EnvType _env_type;
   bool _saved_result;
   bool _saved_result;
+  bool _multiview;
+  int _num_views;
   TexGen _tex_gen;
   TexGen _tex_gen;
   QualityLevel _quality_level;
   QualityLevel _quality_level;
   string _stage_name;
   string _stage_name;

+ 11 - 0
panda/src/egg/parser.yxx

@@ -572,6 +572,17 @@ texture_body:
   } else if (cmp_nocase_uh(name, "priority") == 0) {
   } else if (cmp_nocase_uh(name, "priority") == 0) {
     texture->set_priority((int)value);
     texture->set_priority((int)value);
 
 
+  } else if (cmp_nocase_uh(name, "multiview") == 0) {
+    texture->set_multiview(((int)value) != 0);
+
+  } else if (cmp_nocase_uh(name, "num_views") == 0) {
+    int int_value = (int)value;
+    if (int_value < 1) {
+      eggyyerror("Invalid num-views value " + strval);
+    } else {
+      texture->set_num_views(int_value);
+    }
+
   } else if (cmp_nocase_uh(name, "blendr") == 0) {
   } else if (cmp_nocase_uh(name, "blendr") == 0) {
     Colorf color = texture->get_color();
     Colorf color = texture->get_color();
     color[0] = value;
     color[0] = value;

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

@@ -968,6 +968,13 @@ load_texture(TextureDef &def, EggTexture *egg_tex) {
     }
     }
   }
   }
 
 
+  if (egg_tex->get_multiview()) {
+    options.set_texture_flags(options.get_texture_flags() | LoaderOptions::TF_multiview);
+    if (egg_tex->has_num_views()) {
+      options.set_texture_num_views(egg_tex->get_num_views());
+    }
+  }
+
   PT(Texture) tex;
   PT(Texture) tex;
   switch (egg_tex->get_texture_type()) {
   switch (egg_tex->get_texture_type()) {
   case EggTexture::TT_unspecified:
   case EggTexture::TT_unspecified:

+ 4 - 4
panda/src/glstuff/glGraphicsBuffer_src.cxx

@@ -391,7 +391,7 @@ rebuild_bitplanes() {
         //}
         //}
 
 
         Texture *tex = get_texture(i);
         Texture *tex = get_texture(i);
-        TextureContext *tc = tex->prepare_now(glgsg->get_prepared_objects(), glgsg);
+        TextureContext *tc = tex->prepare_now(0, glgsg->get_prepared_objects(), glgsg);
         nassertv(tc != (TextureContext *)NULL);
         nassertv(tc != (TextureContext *)NULL);
         CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
         CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
         glgsg->update_texture(tc, true);
         glgsg->update_texture(tc, true);
@@ -514,7 +514,7 @@ bind_slot(bool rb_resize, Texture **attach, RenderTexturePlane slot, GLenum atta
         tex->set_component_type(Texture::T_unsigned_int_24_8);
         tex->set_component_type(Texture::T_unsigned_int_24_8);
         _use_depth_stencil = true;
         _use_depth_stencil = true;
       }
       }
-      TextureContext *tc = tex->prepare_now(glgsg->get_prepared_objects(), glgsg);
+      TextureContext *tc = tex->prepare_now(0, glgsg->get_prepared_objects(), glgsg);
       nassertv(tc != (TextureContext *)NULL);
       nassertv(tc != (TextureContext *)NULL);
       CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
       CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
       glgsg->update_texture(tc, true);
       glgsg->update_texture(tc, true);
@@ -551,7 +551,7 @@ bind_slot(bool rb_resize, Texture **attach, RenderTexturePlane slot, GLenum atta
       }
       }
 #endif
 #endif
 
 
-      TextureContext *tc = tex->prepare_now(glgsg->get_prepared_objects(), glgsg);
+      TextureContext *tc = tex->prepare_now(0, glgsg->get_prepared_objects(), glgsg);
       nassertv(tc != (TextureContext *)NULL);
       nassertv(tc != (TextureContext *)NULL);
       CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
       CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
 #ifndef OPENGLES
 #ifndef OPENGLES
@@ -804,7 +804,7 @@ generate_mipmaps() {
     Texture *tex = _tex[slot];
     Texture *tex = _tex[slot];
     if ((tex != 0) && (tex->uses_mipmaps())) {
     if ((tex != 0) && (tex->uses_mipmaps())) {
       glgsg->_state_texture = 0;
       glgsg->_state_texture = 0;
-      TextureContext *tc = tex->prepare_now(glgsg->get_prepared_objects(), glgsg);
+      TextureContext *tc = tex->prepare_now(0, glgsg->get_prepared_objects(), glgsg);
       nassertv(tc != (TextureContext *)NULL);
       nassertv(tc != (TextureContext *)NULL);
       CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
       CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
       glgsg->update_texture(tc, true);
       glgsg->update_texture(tc, true);

+ 48 - 31
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -53,6 +53,7 @@
 #include "depthWriteAttrib.h"
 #include "depthWriteAttrib.h"
 #include "shadeModelAttrib.h"
 #include "shadeModelAttrib.h"
 #include "rescaleNormalAttrib.h"
 #include "rescaleNormalAttrib.h"
+#include "clipPlaneAttrib.h"
 #include "alphaTestAttrib.h"
 #include "alphaTestAttrib.h"
 #include "cullFaceAttrib.h"
 #include "cullFaceAttrib.h"
 #include "fogAttrib.h"
 #include "fogAttrib.h"
@@ -1913,10 +1914,9 @@ clear(DrawableRegion *clearable) {
 //               scissor region and viewport)
 //               scissor region and viewport)
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 void CLP(GraphicsStateGuardian)::
-prepare_display_region(DisplayRegionPipelineReader *dr,
-                       Lens::StereoChannel stereo_channel) {
+prepare_display_region(DisplayRegionPipelineReader *dr) {
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
-  GraphicsStateGuardian::prepare_display_region(dr, stereo_channel);
+  GraphicsStateGuardian::prepare_display_region(dr);
 
 
   int l, b, w, h;
   int l, b, w, h;
   dr->get_region_pixels(l, b, w, h);
   dr->get_region_pixels(l, b, w, h);
@@ -3289,7 +3289,7 @@ end_draw_primitives() {
 //               prepare a texture.  Instead, call Texture::prepare().
 //               prepare a texture.  Instead, call Texture::prepare().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 TextureContext *CLP(GraphicsStateGuardian)::
 TextureContext *CLP(GraphicsStateGuardian)::
-prepare_texture(Texture *tex) {
+prepare_texture(Texture *tex, int view) {
   report_my_gl_errors();
   report_my_gl_errors();
   // Make sure we'll support this texture when it's rendered.  Don't
   // Make sure we'll support this texture when it's rendered.  Don't
   // bother to prepare it if we won't.
   // bother to prepare it if we won't.
@@ -3322,7 +3322,7 @@ prepare_texture(Texture *tex) {
   }
   }
 
 
   report_my_gl_errors();
   report_my_gl_errors();
-  CLP(TextureContext) *gtc = new CLP(TextureContext)(_prepared_objects, tex);
+  CLP(TextureContext) *gtc = new CLP(TextureContext)(_prepared_objects, tex, view);
   report_my_gl_errors();
   report_my_gl_errors();
   GLP(GenTextures)(1, &gtc->_index);
   GLP(GenTextures)(1, &gtc->_index);
   report_my_gl_errors();
   report_my_gl_errors();
@@ -3423,14 +3423,22 @@ release_texture(TextureContext *tc) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool CLP(GraphicsStateGuardian)::
 bool CLP(GraphicsStateGuardian)::
 extract_texture_data(Texture *tex) {
 extract_texture_data(Texture *tex) {
+  bool success = true;
   // Make sure the error stack is cleared out before we begin.
   // Make sure the error stack is cleared out before we begin.
   report_my_gl_errors();
   report_my_gl_errors();
 
 
-  TextureContext *tc = tex->prepare_now(get_prepared_objects(), this);
-  nassertr(tc != (TextureContext *)NULL, false);
-  CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
+  int num_views = tex->get_num_views();
+  for (int view = 0; view < num_views; ++view) {
+    TextureContext *tc = tex->prepare_now(view, get_prepared_objects(), this);
+    nassertr(tc != (TextureContext *)NULL, false);
+    CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
 
 
-  return do_extract_texture_data(gtc);
+    if (!do_extract_texture_data(gtc)) {
+      success = false;
+    }
+  }
+
+  return success;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -4046,7 +4054,8 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
     }
     }
   }
   }
 
 
-  TextureContext *tc = tex->prepare_now(get_prepared_objects(), this);
+  int view = dr->get_tex_view_offset();
+  TextureContext *tc = tex->prepare_now(view, get_prepared_objects(), this);
   nassertr(tc != (TextureContext *)NULL, false);
   nassertr(tc != (TextureContext *)NULL, false);
   CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
   CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
 
 
@@ -7510,8 +7519,9 @@ update_standard_texture_bindings() {
       GLP(Disable)(GL_TEXTURE_CUBE_MAP);
       GLP(Disable)(GL_TEXTURE_CUBE_MAP);
     }
     }
 #endif // OPENGLES_2
 #endif // OPENGLES_2
-    
-    TextureContext *tc = texture->prepare_now(_prepared_objects, this);
+
+    int view = get_current_tex_view_offset() + stage->get_tex_view_offset();
+    TextureContext *tc = texture->prepare_now(view, _prepared_objects, this);
     if (tc == (TextureContext *)NULL) {
     if (tc == (TextureContext *)NULL) {
       // Something wrong with this texture; skip it.
       // Something wrong with this texture; skip it.
       break;
       break;
@@ -7715,7 +7725,8 @@ update_show_usage_texture_bindings(int show_stage_index) {
     Texture *texture = _target_texture->get_on_texture(stage);
     Texture *texture = _target_texture->get_on_texture(stage);
     nassertv(texture != (Texture *)NULL);
     nassertv(texture != (Texture *)NULL);
 
 
-    TextureContext *tc = texture->prepare_now(_prepared_objects, this);
+    int view = get_current_tex_view_offset() + stage->get_tex_view_offset();
+    TextureContext *tc = texture->prepare_now(view, _prepared_objects, this);
     if (tc == (TextureContext *)NULL) {
     if (tc == (TextureContext *)NULL) {
       // Something wrong with this texture; skip it.
       // Something wrong with this texture; skip it.
       break;
       break;
@@ -8783,17 +8794,20 @@ upload_texture_image(CLP(TextureContext) *gtc,
         image_ptr = ptimage;
         image_ptr = ptimage;
       }
       }
 
 
-      size_t image_size = tex->get_ram_mipmap_image_size(n);
+      const unsigned char *orig_image_ptr = image_ptr;
+      size_t view_size = tex->get_ram_mipmap_view_size(n);
+      image_ptr += view_size * gtc->get_view();
       if (one_page_only) {
       if (one_page_only) {
-        image_size = tex->get_ram_mipmap_page_size(n);
-        image_ptr += image_size * z;
+        view_size = tex->get_ram_mipmap_page_size(n);
+        image_ptr += view_size * z;
       }
       }
+      nassertr(image_ptr >= orig_image_ptr && image_ptr + view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n), false);
 
 
       PTA_uchar bgr_image;
       PTA_uchar bgr_image;
       if (!_supports_bgr && image_compression == Texture::CM_off) {
       if (!_supports_bgr && image_compression == Texture::CM_off) {
         // If the GL doesn't claim to support BGR, we may have to reverse
         // If the GL doesn't claim to support BGR, we may have to reverse
         // the component ordering of the image.
         // the component ordering of the image.
-        image_ptr = fix_component_ordering(bgr_image, image_ptr, image_size,
+        image_ptr = fix_component_ordering(bgr_image, image_ptr, view_size,
                                            external_format, tex);
                                            external_format, tex);
       }
       }
 
 
@@ -8802,7 +8816,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
       int depth = tex->get_expected_mipmap_z_size(n);
       int depth = tex->get_expected_mipmap_z_size(n);
 
 
 #ifdef DO_PSTATS
 #ifdef DO_PSTATS
-      _data_transferred_pcollector.add_level(image_size);
+      _data_transferred_pcollector.add_level(view_size);
 #endif
 #endif
       switch (texture_target) {
       switch (texture_target) {
       case GL_TEXTURE_1D:
       case GL_TEXTURE_1D:
@@ -8811,7 +8825,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                              external_format, component_type, image_ptr);
                              external_format, component_type, image_ptr);
         } else {
         } else {
           _glCompressedTexSubImage1D(page_target, n - mipmap_bias, 0, width,
           _glCompressedTexSubImage1D(page_target, n - mipmap_bias, 0, width,
-                                     external_format, image_size, image_ptr);
+                                     external_format, view_size, image_ptr);
         }
         }
         break;
         break;
 
 
@@ -8828,7 +8842,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                              external_format, component_type, image_ptr);
                              external_format, component_type, image_ptr);
           } else {
           } else {
             _glCompressedTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth,
             _glCompressedTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth,
-                                       external_format, image_size, image_ptr);
+                                       external_format, view_size, image_ptr);
           }
           }
         } else {
         } else {
           report_my_gl_errors();
           report_my_gl_errors();
@@ -8844,7 +8858,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                              external_format, component_type, image_ptr);
                              external_format, component_type, image_ptr);
           } else {
           } else {
             _glCompressedTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth,
             _glCompressedTexSubImage3D(page_target, n - mipmap_bias, 0, 0, 0, width, height, depth,
-                                       external_format, image_size, image_ptr);
+                                       external_format, view_size, image_ptr);
           }
           }
         } else {
         } else {
           report_my_gl_errors();
           report_my_gl_errors();
@@ -8863,7 +8877,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                              external_format, component_type, image_ptr);
                              external_format, component_type, image_ptr);
         } else {
         } else {
           _glCompressedTexSubImage2D(page_target, n - mipmap_bias, 0, 0, width, height,
           _glCompressedTexSubImage2D(page_target, n - mipmap_bias, 0, 0, width, height,
-                                     external_format, image_size, image_ptr);
+                                     external_format, view_size, image_ptr);
         }
         }
         break;
         break;
       }
       }
@@ -8931,17 +8945,20 @@ upload_texture_image(CLP(TextureContext) *gtc,
         image_ptr = ptimage;
         image_ptr = ptimage;
       }
       }
 
 
-      size_t image_size = tex->get_ram_mipmap_image_size(n);
+      const unsigned char *orig_image_ptr = image_ptr;
+      size_t view_size = tex->get_ram_mipmap_view_size(n);
+      image_ptr += view_size * gtc->get_view();
       if (one_page_only) {
       if (one_page_only) {
-        image_size = tex->get_ram_mipmap_page_size(n);
-        image_ptr += image_size * z;
+        view_size = tex->get_ram_mipmap_page_size(n);
+        image_ptr += view_size * z;
       }
       }
+      nassertr(image_ptr >= orig_image_ptr && image_ptr + view_size <= orig_image_ptr + tex->get_ram_mipmap_image_size(n), false);
 
 
       PTA_uchar bgr_image;
       PTA_uchar bgr_image;
       if (!_supports_bgr && image_compression == Texture::CM_off) {
       if (!_supports_bgr && image_compression == Texture::CM_off) {
         // If the GL doesn't claim to support BGR, we may have to reverse
         // If the GL doesn't claim to support BGR, we may have to reverse
         // the component ordering of the image.
         // the component ordering of the image.
-        image_ptr = fix_component_ordering(bgr_image, image_ptr, image_size,
+        image_ptr = fix_component_ordering(bgr_image, image_ptr, view_size,
                                            external_format, tex);
                                            external_format, tex);
       }
       }
 
 
@@ -8950,7 +8967,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
       int depth = tex->get_expected_mipmap_z_size(n);
       int depth = tex->get_expected_mipmap_z_size(n);
 
 
 #ifdef DO_PSTATS
 #ifdef DO_PSTATS
-      _data_transferred_pcollector.add_level(image_size);
+      _data_transferred_pcollector.add_level(view_size);
 #endif
 #endif
       switch (texture_target) {
       switch (texture_target) {
 #ifndef OPENGLES  // 1-d textures not supported by OpenGL ES.  Fall through.
 #ifndef OPENGLES  // 1-d textures not supported by OpenGL ES.  Fall through.
@@ -8961,7 +8978,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                           external_format, component_type, image_ptr);
                           external_format, component_type, image_ptr);
         } else {
         } else {
           _glCompressedTexImage1D(page_target, n - mipmap_bias, external_format, width,
           _glCompressedTexImage1D(page_target, n - mipmap_bias, external_format, width,
-                                  0, image_size, image_ptr);
+                                  0, view_size, image_ptr);
         }
         }
         break;
         break;
 #endif  // OPENGLES  // OpenGL ES will fall through.
 #endif  // OPENGLES  // OpenGL ES will fall through.
@@ -8981,7 +8998,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
           } else {
           } else {
             _glCompressedTexImage3D(page_target, n - mipmap_bias, external_format, width,
             _glCompressedTexImage3D(page_target, n - mipmap_bias, external_format, width,
                                     height, depth,
                                     height, depth,
-                                    0, image_size, image_ptr);
+                                    0, view_size, image_ptr);
           }
           }
         } else {
         } else {
           report_my_gl_errors();
           report_my_gl_errors();
@@ -8999,7 +9016,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
           } else {
           } else {
             _glCompressedTexImage3D(page_target, n - mipmap_bias, external_format, width,
             _glCompressedTexImage3D(page_target, n - mipmap_bias, external_format, width,
                                     height, depth,
                                     height, depth,
-                                    0, image_size, image_ptr);
+                                    0, view_size, image_ptr);
           }
           }
         } else {
         } else {
           report_my_gl_errors();
           report_my_gl_errors();
@@ -9015,7 +9032,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                           external_format, component_type, image_ptr);
                           external_format, component_type, image_ptr);
         } else {
         } else {
           _glCompressedTexImage2D(page_target, n - mipmap_bias, external_format, width, height,
           _glCompressedTexImage2D(page_target, n - mipmap_bias, external_format, width, height,
-                                  0, image_size, image_ptr);
+                                  0, view_size, image_ptr);
         }
         }
       }
       }
 
 

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

@@ -168,8 +168,7 @@ public:
 
 
   virtual void reset();
   virtual void reset();
 
 
-  virtual void prepare_display_region(DisplayRegionPipelineReader *dr,
-                                      Lens::StereoChannel stereo_channel);
+  virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
   virtual void clear_before_callback();
   virtual void clear_before_callback();
   virtual CPT(TransformState) calc_projection_mat(const Lens *lens);
   virtual CPT(TransformState) calc_projection_mat(const Lens *lens);
   virtual bool prepare_lens();
   virtual bool prepare_lens();
@@ -197,7 +196,7 @@ public:
                            bool force);
                            bool force);
   virtual void end_draw_primitives();
   virtual void end_draw_primitives();
 
 
-  virtual TextureContext *prepare_texture(Texture *tex);
+  virtual TextureContext *prepare_texture(Texture *tex, int view);
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual void release_texture(TextureContext *tc);
   virtual void release_texture(TextureContext *tc);
   virtual bool extract_texture_data(Texture *tex);
   virtual bool extract_texture_data(Texture *tex);

+ 3 - 1
panda/src/glstuff/glShaderContext_src.cxx

@@ -911,6 +911,7 @@ update_shader_texture_bindings(CLP(ShaderContext) *prev, GSG *gsg) {
 #endif
 #endif
 
 
     Texture *tex = 0;
     Texture *tex = 0;
+    int view = gsg->get_current_tex_view_offset();
     if (id != 0) {
     if (id != 0) {
       const ShaderInput *input = gsg->_target_shader->get_shader_input(id);
       const ShaderInput *input = gsg->_target_shader->get_shader_input(id);
       tex = input->get_texture();
       tex = input->get_texture();
@@ -920,6 +921,7 @@ update_shader_texture_bindings(CLP(ShaderContext) *prev, GSG *gsg) {
       }
       }
       TextureStage *stage = texattrib->get_on_stage(_shader->_tex_spec[i]._stage);
       TextureStage *stage = texattrib->get_on_stage(_shader->_tex_spec[i]._stage);
       tex = texattrib->get_on_texture(stage);
       tex = texattrib->get_on_texture(stage);
+      view += stage->get_tex_view_offset();
     }
     }
     if (_shader->_tex_spec[i]._suffix != 0) {
     if (_shader->_tex_spec[i]._suffix != 0) {
       // The suffix feature is inefficient. It is a temporary hack.
       // The suffix feature is inefficient. It is a temporary hack.
@@ -931,7 +933,7 @@ update_shader_texture_bindings(CLP(ShaderContext) *prev, GSG *gsg) {
     if ((tex == 0) || (tex->get_texture_type() != _shader->_tex_spec[i]._desired_type)) {
     if ((tex == 0) || (tex->get_texture_type() != _shader->_tex_spec[i]._desired_type)) {
       continue;
       continue;
     }
     }
-    TextureContext *tc = tex->prepare_now(gsg->_prepared_objects, gsg);
+    TextureContext *tc = tex->prepare_now(view, gsg->_prepared_objects, gsg);
     if (tc == (TextureContext*)NULL) {
     if (tc == (TextureContext*)NULL) {
       continue;
       continue;
     }
     }

+ 2 - 2
panda/src/glstuff/glTextureContext_src.I

@@ -19,8 +19,8 @@
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE CLP(TextureContext)::
 INLINE CLP(TextureContext)::
-CLP(TextureContext)(PreparedGraphicsObjects *pgo, Texture *tex) :
-  TextureContext(pgo, tex)
+CLP(TextureContext)(PreparedGraphicsObjects *pgo, Texture *tex, int view) :
+  TextureContext(pgo, tex, view)
 {
 {
   _index = 0;
   _index = 0;
   _already_applied = false;
   _already_applied = false;

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

@@ -22,7 +22,7 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class EXPCL_GL CLP(TextureContext) : public TextureContext {
 class EXPCL_GL CLP(TextureContext) : public TextureContext {
 public:
 public:
-  INLINE CLP(TextureContext)(PreparedGraphicsObjects *pgo, Texture *tex);
+  INLINE CLP(TextureContext)(PreparedGraphicsObjects *pgo, Texture *tex, int view);
   ALLOC_DELETED_CHAIN(CLP(TextureContext));
   ALLOC_DELETED_CHAIN(CLP(TextureContext));
 
 
   virtual void evict_lru();
   virtual void evict_lru();

+ 9 - 7
panda/src/gobj/preparedGraphicsObjects.cxx

@@ -241,7 +241,7 @@ void PreparedGraphicsObjects::
 release_texture(TextureContext *tc) {
 release_texture(TextureContext *tc) {
   ReMutexHolder holder(_lock);
   ReMutexHolder holder(_lock);
 
 
-  tc->_texture->clear_prepared(this);
+  tc->_texture->clear_prepared(tc->get_view(), this);
 
 
   // We have to set the Texture pointer to NULL at this point, since
   // We have to set the Texture pointer to NULL at this point, since
   // the Texture itself might destruct at any time after it has been
   // the Texture itself might destruct at any time after it has been
@@ -284,7 +284,7 @@ release_all_textures() {
        tci != _prepared_textures.end();
        tci != _prepared_textures.end();
        ++tci) {
        ++tci) {
     TextureContext *tc = (*tci);
     TextureContext *tc = (*tci);
-    tc->_texture->clear_prepared(this);
+    tc->_texture->clear_prepared(tc->get_view(), this);
     tc->_texture = (Texture *)NULL;
     tc->_texture = (Texture *)NULL;
 
 
     _released_textures.insert(tc);
     _released_textures.insert(tc);
@@ -341,14 +341,14 @@ get_num_prepared_textures() const {
 //               TextureContext will be deleted.
 //               TextureContext will be deleted.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 TextureContext *PreparedGraphicsObjects::
 TextureContext *PreparedGraphicsObjects::
-prepare_texture_now(Texture *tex, GraphicsStateGuardianBase *gsg) {
+prepare_texture_now(Texture *tex, int view, GraphicsStateGuardianBase *gsg) {
   ReMutexHolder holder(_lock);
   ReMutexHolder holder(_lock);
 
 
   // Ask the GSG to create a brand new TextureContext.  There might
   // Ask the GSG to create a brand new TextureContext.  There might
   // be several GSG's sharing the same set of textures; if so, it
   // be several GSG's sharing the same set of textures; if so, it
   // doesn't matter which of them creates the context (since they're
   // doesn't matter which of them creates the context (since they're
   // all shared anyway).
   // all shared anyway).
-  TextureContext *tc = gsg->prepare_texture(tex);
+  TextureContext *tc = gsg->prepare_texture(tex, view);
 
 
   if (tc != (TextureContext *)NULL) {
   if (tc != (TextureContext *)NULL) {
     bool prepared = _prepared_textures.insert(tc).second;
     bool prepared = _prepared_textures.insert(tc).second;
@@ -1274,9 +1274,11 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
        qti != _enqueued_textures.end();
        qti != _enqueued_textures.end();
        ++qti) {
        ++qti) {
     Texture *tex = (*qti);
     Texture *tex = (*qti);
-    TextureContext *tc = tex->prepare_now(this, gsg);
-    if (tc != (TextureContext *)NULL) {
-      gsg->update_texture(tc, true);
+    for (int view = 0; view < tex->get_num_views(); ++view) {
+      TextureContext *tc = tex->prepare_now(view, this, gsg);
+      if (tc != (TextureContext *)NULL) {
+        gsg->update_texture(tc, true);
+      }
     }
     }
   }
   }
 
 

+ 2 - 1
panda/src/gobj/preparedGraphicsObjects.h

@@ -82,7 +82,8 @@ PUBLISHED:
   int get_num_queued_textures() const;
   int get_num_queued_textures() const;
   int get_num_prepared_textures() const;
   int get_num_prepared_textures() const;
 
 
-  TextureContext *prepare_texture_now(Texture *tex, GraphicsStateGuardianBase *gsg);
+  TextureContext *prepare_texture_now(Texture *tex, int view, 
+                                      GraphicsStateGuardianBase *gsg);
 
 
   void enqueue_geom(Geom *geom);
   void enqueue_geom(Geom *geom);
   bool is_geom_queued(const Geom *geom) const;
   bool is_geom_queued(const Geom *geom) const;

+ 168 - 8
panda/src/gobj/texture.I

@@ -62,7 +62,7 @@ setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
               int z_size, Texture::ComponentType component_type,
               int z_size, Texture::ComponentType component_type,
               Texture::Format format) {
               Texture::Format format) {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
-  do_setup_texture(texture_type, x_size, y_size, z_size,
+  do_setup_texture(texture_type, x_size, y_size, z_size, 
                    component_type, format);
                    component_type, format);
 }
 }
 
 
@@ -169,7 +169,7 @@ setup_2d_texture_array(int z_size) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 setup_2d_texture_array(int x_size, int y_size, int z_size,
 setup_2d_texture_array(int x_size, int y_size, int z_size,
-                 ComponentType component_type, Format format) {
+                       ComponentType component_type, Format format) {
   setup_texture(TT_2d_texture_array, x_size, y_size, z_size, component_type, format);
   setup_texture(TT_2d_texture_array, x_size, y_size, z_size, component_type, format);
 }
 }
 
 
@@ -198,8 +198,7 @@ setup_cube_map() {
 //               and z_size is always 6.
 //               and z_size is always 6.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
-setup_cube_map(int size, ComponentType component_type, 
-               Format format) {
+setup_cube_map(int size, ComponentType component_type, Format format) {
   setup_texture(TT_cube_map, size, size, 6, component_type, format);
   setup_texture(TT_cube_map, size, size, 6, component_type, format);
 }
 }
 
 
@@ -232,7 +231,9 @@ write(const Filename &fullpath) {
 //               number to write.  3-D textures have one page number
 //               number to write.  3-D textures have one page number
 //               for each level of depth; cube maps have six pages
 //               for each level of depth; cube maps have six pages
 //               number 0 through 5.  Other kinds of textures have
 //               number 0 through 5.  Other kinds of textures have
-//               only one page, numbered 0.
+//               only one page, numbered 0.  If there are multiple
+//               views, the range of z is increased; the total range
+//               is [0, get_num_pages()).
 //
 //
 //               If write_pages is true, then all pages of the texture
 //               If write_pages is true, then all pages of the texture
 //               will be written.  In this case z is ignored, and the
 //               will be written.  In this case z is ignored, and the
@@ -487,6 +488,40 @@ get_z_size() const {
   return _z_size;
   return _z_size;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_num_views
+//       Access: Published
+//  Description: Returns the number of "views" in the texture.  A view
+//               is a completely separate image stored within the
+//               Texture object.  Most textures have only one view,
+//               but a stereo texture, for instance, may have two
+//               views, a left and a right image.  Other uses for
+//               multiple views are not yet defined.
+//
+//               If this value is greater than one, the additional
+//               views are accessed as additional pages beyond
+//               get_z_size().
+////////////////////////////////////////////////////////////////////
+INLINE int Texture::
+get_num_views() const {
+  return _num_views;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_num_pages
+//       Access: Published
+//  Description: Returns the total number of pages in the texture.  
+//               Each "page" is a 2-d texture image within the larger
+//               image--a face of a cube map, or a level of a 3-d
+//               texture.  Normally, get_num_pages() is the same as
+//               get_z_size().  However, in a multiview texture, this
+//               returns get_z_size() * get_num_views().
+////////////////////////////////////////////////////////////////////
+INLINE int Texture::
+get_num_pages() const {
+  return get_z_size() * get_num_views();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::get_pad_x_size
 //     Function: Texture::get_pad_x_size
 //       Access: Published
 //       Access: Published
@@ -1064,6 +1099,21 @@ get_expected_mipmap_z_size(int n) const {
   return do_get_expected_mipmap_z_size(n);
   return do_get_expected_mipmap_z_size(n);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_expected_mipmap_num_pages
+//       Access: Published
+//  Description: Returns the total number of pages that the nth mipmap
+//               level should have, based on the texture's size.  This
+//               is usually the same as get_expected_mipmap_z_size(),
+//               except for a multiview texture, in which case it is
+//               get_expected_mipmap_z_size() * get_num_views().
+////////////////////////////////////////////////////////////////////
+INLINE int Texture::
+get_expected_mipmap_num_pages(int n) const {
+  MutexHolder holder(_lock);
+  return do_get_expected_mipmap_num_pages(n);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::has_ram_image
 //     Function: Texture::has_ram_image
 //       Access: Published
 //       Access: Published
@@ -1130,8 +1180,9 @@ might_have_ram_image() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::get_ram_image_size
 //     Function: Texture::get_ram_image_size
 //       Access: Published
 //       Access: Published
-//  Description: Returns the number of bytes used by the in-memory
-//               image, or 0 if there is no in-memory image.
+//  Description: Returns the total number of bytes used by the
+//               in-memory image, across all pages and views, or 0 if
+//               there is no in-memory image.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 INLINE size_t Texture::
 get_ram_image_size() const {
 get_ram_image_size() const {
@@ -1139,6 +1190,24 @@ get_ram_image_size() const {
   return do_get_ram_image_size();
   return do_get_ram_image_size();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_ram_view_size
+//       Access: Published
+//  Description: Returns the number of bytes used by the in-memory
+//               image per view, or 0 if there is no in-memory image.
+//               Since each view is a stack of z_size pages, this is
+//               get_z_size() * get_ram_page_size().
+////////////////////////////////////////////////////////////////////
+INLINE size_t Texture::
+get_ram_view_size() const {
+  MutexHolder holder(_lock);
+  if (_ram_image_compression == CM_off || _ram_images.empty()) {
+    return do_get_expected_ram_view_size();
+  } else {
+    return _z_size * _ram_images[0]._page_size;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::get_ram_page_size
 //     Function: Texture::get_ram_page_size
 //       Access: Published
 //       Access: Published
@@ -1446,6 +1515,29 @@ get_ram_mipmap_image_size(int n) const {
   return 0;
   return 0;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_ram_mipmap_view_size
+//       Access: Published
+//  Description: Returns the number of bytes used by the in-memory
+//               image per view for mipmap level n, or 0 if there is
+//               no in-memory image for this mipmap level.
+//
+//               A "view" is a collection of z_size pages for each
+//               mipmap level.  Most textures have only one view,
+//               except for multiview or stereo textures.
+//
+//               For a non-compressed texture, this is the same as
+//               get_expected_ram_mipmap_view_size().  For a compressed
+//               texture, this may be a smaller value.  (We do assume
+//               that all pages will be the same size on a compressed
+//               texture).
+////////////////////////////////////////////////////////////////////
+INLINE size_t Texture::
+get_ram_mipmap_view_size(int n) const {
+  MutexHolder holder(_lock);
+  return do_get_ram_mipmap_page_size(n) * do_get_expected_mipmap_z_size(n);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::get_ram_mipmap_page_size
 //     Function: Texture::get_ram_mipmap_page_size
 //       Access: Published
 //       Access: Published
@@ -1478,13 +1570,28 @@ get_expected_ram_mipmap_image_size(int n) const {
   return do_get_expected_ram_mipmap_image_size(n);
   return do_get_expected_ram_mipmap_image_size(n);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_expected_ram_mipmap_view_size
+//       Access: Published
+//  Description: Returns the number of bytes that *ought* to be used
+//               by each view of the in-memory image for mipmap level
+//               n, based on the texture parameters.  For a normal,
+//               non-multiview texture, this is the same as
+//               get_expected_ram_mipmap_image_size(n).
+////////////////////////////////////////////////////////////////////
+INLINE size_t Texture::
+get_expected_ram_mipmap_view_size(int n) const {
+  MutexHolder holder(_lock);
+  return do_get_expected_ram_mipmap_view_size(n);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::get_expected_ram_mipmap_page_size
 //     Function: Texture::get_expected_ram_mipmap_page_size
 //       Access: Published
 //       Access: Published
 //  Description: Returns the number of bytes that should be used per
 //  Description: Returns the number of bytes that should be used per
 //               each Z page of the 3-d texture, for mipmap level n.
 //               each Z page of the 3-d texture, for mipmap level n.
 //               For a 2-d or 1-d texture, this is the same as
 //               For a 2-d or 1-d texture, this is the same as
-//               get_expected_ram_mipmap_image_size(n).
+//               get_expected_ram_mipmap_view_size(n).
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 INLINE size_t Texture::
 get_expected_ram_mipmap_page_size(int n) const {
 get_expected_ram_mipmap_page_size(int n) const {
@@ -1873,6 +1980,29 @@ set_z_size(int z_size) {
   do_set_z_size(z_size);
   do_set_z_size(z_size);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::set_num_views
+//       Access: Published
+//  Description: Sets the number of "views" within a texture.  A view
+//               is a completely separate image stored within the
+//               Texture object.  Most textures have only one view,
+//               but a stereo texture, for instance, may have two
+//               views, a left and a right image.  Other uses for
+//               multiple views are not yet defined.
+//
+//               If this value is greater than one, the additional
+//               views are accessed as additional pages beyond
+//               get_z_size().
+//
+//               This also implicitly unloads the texture if it has
+//               already been loaded.
+////////////////////////////////////////////////////////////////////
+INLINE void Texture::
+set_num_views(int num_views) {
+  MutexHolder holder(_lock);
+  do_set_num_views(num_views);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::set_format
 //     Function: Texture::set_format
 //       Access: Published
 //       Access: Published
@@ -2072,6 +2202,16 @@ do_has_ram_mipmap_image(int n) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 INLINE size_t Texture::
 do_get_expected_ram_image_size() const {
 do_get_expected_ram_image_size() const {
+  return do_get_expected_ram_view_size() * (size_t)_num_views;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::do_get_expected_ram_view_size
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE size_t Texture::
+do_get_expected_ram_view_size() const {
   return do_get_expected_ram_page_size() * (size_t)_z_size;
   return do_get_expected_ram_page_size() * (size_t)_z_size;
 }
 }
 
 
@@ -2092,6 +2232,16 @@ do_get_expected_ram_page_size() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 INLINE size_t Texture::
 do_get_expected_ram_mipmap_image_size(int n) const {
 do_get_expected_ram_mipmap_image_size(int n) const {
+  return do_get_expected_ram_mipmap_view_size(n) * (size_t)_num_views;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::do_get_expected_ram_mipmap_view_size
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE size_t Texture::
+do_get_expected_ram_mipmap_view_size(int n) const {
   return do_get_expected_ram_mipmap_page_size(n) * (size_t)do_get_expected_mipmap_z_size(n);
   return do_get_expected_ram_mipmap_page_size(n) * (size_t)do_get_expected_mipmap_z_size(n);
 }
 }
 
 
@@ -2105,6 +2255,16 @@ do_get_expected_ram_mipmap_page_size(int n) const {
   return (size_t)(do_get_expected_mipmap_x_size(n) * do_get_expected_mipmap_y_size(n) * _num_components * _component_width);
   return (size_t)(do_get_expected_mipmap_x_size(n) * do_get_expected_mipmap_y_size(n) * _num_components * _component_width);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::do_get_expected_mipmap_num_pages
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE int Texture::
+do_get_expected_mipmap_num_pages(int n) const {
+  return do_get_expected_mipmap_z_size(n) * _num_views;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::do_clear_ram_image
 //     Function: Texture::do_clear_ram_image
 //       Access: Protected
 //       Access: Protected

+ 344 - 188
panda/src/gobj/texture.cxx

@@ -194,6 +194,7 @@ Texture(const string &name) :
   _x_size = 0;
   _x_size = 0;
   _y_size = 1;
   _y_size = 1;
   _z_size = 1;
   _z_size = 1;
+  _num_views = 1;
   // Set it to something else first to
   // Set it to something else first to
   // avoid the check in do_set_format
   // avoid the check in do_set_format
   // depending on an uninitialised value
   // depending on an uninitialised value
@@ -1401,9 +1402,9 @@ prepare(PreparedGraphicsObjects *prepared_objects) {
 bool Texture::
 bool Texture::
 is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 is_prepared(PreparedGraphicsObjects *prepared_objects) const {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
-  Contexts::const_iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
+  PreparedViews::const_iterator pvi;
+  pvi = _prepared_views.find(prepared_objects);
+  if (pvi != _prepared_views.end()) {
     return true;
     return true;
   }
   }
   return prepared_objects->is_texture_queued(this);
   return prepared_objects->is_texture_queued(this);
@@ -1420,11 +1421,22 @@ is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 bool Texture::
 bool Texture::
 was_image_modified(PreparedGraphicsObjects *prepared_objects) const {
 was_image_modified(PreparedGraphicsObjects *prepared_objects) const {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
-  Contexts::const_iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
-    TextureContext *tc = (*ci).second;
-    return tc->was_image_modified();
+  PreparedViews::const_iterator pvi;
+  pvi = _prepared_views.find(prepared_objects);
+  if (pvi != _prepared_views.end()) {
+    const Contexts &contexts = (*pvi).second;
+    for (int view = 0; view < _num_views; ++view) {
+      Contexts::const_iterator ci;
+      ci = contexts.find(view);
+      if (ci == contexts.end()) {
+        return true;
+      }
+      TextureContext *tc = (*ci).second;
+      if (tc->was_image_modified()) {
+        return true;
+      }
+    }
+    return false;
   }
   }
   return true;
   return true;
 }
 }
@@ -1443,13 +1455,22 @@ was_image_modified(PreparedGraphicsObjects *prepared_objects) const {
 size_t Texture::
 size_t Texture::
 get_data_size_bytes(PreparedGraphicsObjects *prepared_objects) const {
 get_data_size_bytes(PreparedGraphicsObjects *prepared_objects) const {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
-  Contexts::const_iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
-    TextureContext *tc = (*ci).second;
-    return tc->get_data_size_bytes();
+  PreparedViews::const_iterator pvi;
+  size_t total_size = 0;
+  pvi = _prepared_views.find(prepared_objects);
+  if (pvi != _prepared_views.end()) {
+    const Contexts &contexts = (*pvi).second;
+    for (int view = 0; view < _num_views; ++view) {
+      Contexts::const_iterator ci;
+      ci = contexts.find(view);
+      if (ci != contexts.end()) {
+        TextureContext *tc = (*ci).second;
+        total_size += tc->get_data_size_bytes();
+      }
+    }
   }
   }
-  return 0;
+
+  return total_size;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -1461,11 +1482,20 @@ get_data_size_bytes(PreparedGraphicsObjects *prepared_objects) const {
 bool Texture::
 bool Texture::
 get_active(PreparedGraphicsObjects *prepared_objects) const {
 get_active(PreparedGraphicsObjects *prepared_objects) const {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
-  Contexts::const_iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
-    TextureContext *tc = (*ci).second;
-    return tc->get_active();
+  PreparedViews::const_iterator pvi;
+  pvi = _prepared_views.find(prepared_objects);
+  if (pvi != _prepared_views.end()) {
+    const Contexts &contexts = (*pvi).second;
+    for (int view = 0; view < _num_views; ++view) {
+      Contexts::const_iterator ci;
+      ci = contexts.find(view);
+      if (ci != contexts.end()) {
+        TextureContext *tc = (*ci).second;
+        if (tc->get_active()) {
+          return true;
+        }
+      }
+    }
   }
   }
   return false;
   return false;
 }
 }
@@ -1480,11 +1510,20 @@ get_active(PreparedGraphicsObjects *prepared_objects) const {
 bool Texture::
 bool Texture::
 get_resident(PreparedGraphicsObjects *prepared_objects) const {
 get_resident(PreparedGraphicsObjects *prepared_objects) const {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
-  Contexts::const_iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
-    TextureContext *tc = (*ci).second;
-    return tc->get_resident();
+  PreparedViews::const_iterator pvi;
+  pvi = _prepared_views.find(prepared_objects);
+  if (pvi != _prepared_views.end()) {
+    const Contexts &contexts = (*pvi).second;
+    for (int view = 0; view < _num_views; ++view) {
+      Contexts::const_iterator ci;
+      ci = contexts.find(view);
+      if (ci != contexts.end()) {
+        TextureContext *tc = (*ci).second;
+        if (tc->get_resident()) {
+          return true;
+        }
+      }
+    }
   }
   }
   return false;
   return false;
 }
 }
@@ -1499,16 +1538,19 @@ get_resident(PreparedGraphicsObjects *prepared_objects) const {
 bool Texture::
 bool Texture::
 release(PreparedGraphicsObjects *prepared_objects) {
 release(PreparedGraphicsObjects *prepared_objects) {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
-  Contexts::iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
-    TextureContext *tc = (*ci).second;
-    if (tc != (TextureContext *)NULL) {
-      prepared_objects->release_texture(tc);
-    } else {
-      _contexts.erase(ci);
+  PreparedViews::iterator pvi;
+  pvi = _prepared_views.find(prepared_objects);
+  if (pvi != _prepared_views.end()) {
+    Contexts temp;
+    temp.swap((*pvi).second);
+    Contexts::iterator ci;
+    for (ci = temp.begin(); ci != temp.end(); ++ci) {
+      TextureContext *tc = (*ci).second;
+      if (tc != (TextureContext *)NULL) {
+        prepared_objects->release_texture(tc);
+      }
     }
     }
-    return true;
+    _prepared_views.erase(pvi);
   }
   }
 
 
   // Maybe it wasn't prepared yet, but it's about to be.
   // Maybe it wasn't prepared yet, but it's about to be.
@@ -1525,26 +1567,28 @@ release(PreparedGraphicsObjects *prepared_objects) {
 int Texture::
 int Texture::
 release_all() {
 release_all() {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
-  // We have to traverse a copy of the _contexts list, because the
+  // We have to traverse a copy of the _prepared_views list, because the
   // PreparedGraphicsObjects object will call clear_prepared() in response
   // PreparedGraphicsObjects object will call clear_prepared() in response
   // to each release_texture(), and we don't want to be modifying the
   // to each release_texture(), and we don't want to be modifying the
-  // _contexts list while we're traversing it.
-  Contexts temp = _contexts;
-  int num_freed = (int)_contexts.size();
-
-  Contexts::const_iterator ci;
-  for (ci = temp.begin(); ci != temp.end(); ++ci) {
-    PreparedGraphicsObjects *prepared_objects = (*ci).first;
-    TextureContext *tc = (*ci).second;
-    if (tc != (TextureContext *)NULL) {
-      prepared_objects->release_texture(tc);
+  // _prepared_views list while we're traversing it.
+  PreparedViews temp;
+  temp.swap(_prepared_views);
+  int num_freed = (int)temp.size();
+
+  PreparedViews::iterator pvi;
+  for (pvi = temp.begin(); pvi != temp.end(); ++pvi) {
+    PreparedGraphicsObjects *prepared_objects = (*pvi).first;
+    Contexts temp;
+    temp.swap((*pvi).second);
+    Contexts::iterator ci;
+    for (ci = temp.begin(); ci != temp.end(); ++ci) {
+      TextureContext *tc = (*ci).second;
+      if (tc != (TextureContext *)NULL) {
+        prepared_objects->release_texture(tc);
+      }
     }
     }
   }
   }
 
 
-  // There might still be some outstanding contexts in the map, if
-  // there were any NULL pointers there.  Eliminate them.
-  _contexts.clear();
-
   return num_freed;
   return num_freed;
 }
 }
 
 
@@ -1588,6 +1632,10 @@ write(ostream &out, int indent_level) const {
     break;
     break;
   }
   }
 
 
+  if (_num_views > 1) {
+    out << " (x " << _num_views << " views)";
+  }
+
   out << " pixels, each " << _num_components;
   out << " pixels, each " << _num_components;
 
 
   switch (_component_type) {
   switch (_component_type) {
@@ -1839,17 +1887,24 @@ is_mipmap(FilterType filter_type) {
 //               rendered.
 //               rendered.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 TextureContext *Texture::
 TextureContext *Texture::
-prepare_now(PreparedGraphicsObjects *prepared_objects,
+prepare_now(int view,
+            PreparedGraphicsObjects *prepared_objects,
             GraphicsStateGuardianBase *gsg) {
             GraphicsStateGuardianBase *gsg) {
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
-  Contexts::const_iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
-    return (*ci).second;
+
+  // Don't exceed the actual number of views.
+  view = max(min(view, _num_views - 1), 0);
+
+  // Get the list of PreparedGraphicsObjects for this view.
+  Contexts &contexts = _prepared_views[prepared_objects];
+  Contexts::const_iterator pvi;
+  pvi = contexts.find(view);
+  if (pvi != contexts.end()) {
+    return (*pvi).second;
   }
   }
 
 
-  TextureContext *tc = prepared_objects->prepare_texture_now(this, gsg);
-  _contexts[prepared_objects] = tc;
+  TextureContext *tc = prepared_objects->prepare_texture_now(this, view, gsg);
+  contexts[view] = tc;
 
 
   return tc;
   return tc;
 }
 }
@@ -2713,6 +2768,16 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath,
     }
     }
   }
   }
 
 
+  int num_views = 0;
+  if (options.get_texture_flags() & LoaderOptions::TF_multiview) {
+    // We'll be loading a multiview texture.
+    read_pages = true;
+    if (options.get_texture_num_views() != 0) {
+      num_views = options.get_texture_num_views();
+      do_set_num_views(num_views);
+    }
+  }
+
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
 
 
   if (read_pages && read_mipmaps) {
   if (read_pages && read_mipmaps) {
@@ -2753,8 +2818,9 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath,
         break;
         break;
       }
       }
 
 
-      while ((z_size == 0 && (vfs->exists(file) || z == 0)) ||
-             (z_size != 0 && z < z_size)) {
+      int num_pages = z_size * num_views;
+      while ((num_pages == 0 && (vfs->exists(file) || z == 0)) ||
+             (num_pages != 0 && z < num_pages)) {
         if (!do_read_one(file, alpha_file, z, n, primary_file_num_channels,
         if (!do_read_one(file, alpha_file, z, n, primary_file_num_channels,
                          alpha_file_channel, options, header_only, record)) {
                          alpha_file_channel, options, header_only, record)) {
           return false;
           return false;
@@ -2774,6 +2840,8 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath,
       }
       }
       ++n;
       ++n;
     }
     }
+    _fullpath = fullpath_pattern;
+    _alpha_fullpath = alpha_fullpath_pattern;
 
 
   } else if (read_pages) {
   } else if (read_pages) {
     // Read a sequence of cube map or 3-D texture pages.
     // Read a sequence of cube map or 3-D texture pages.
@@ -2790,8 +2858,10 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath,
     z = 0;
     z = 0;
     Filename file = fullpath_pattern.get_filename_index(z);
     Filename file = fullpath_pattern.get_filename_index(z);
     Filename alpha_file = alpha_fullpath_pattern.get_filename_index(z);
     Filename alpha_file = alpha_fullpath_pattern.get_filename_index(z);
-    while ((z_size == 0 && (vfs->exists(file) || z == 0)) ||
-           (z_size != 0 && z < z_size)) {
+
+    int num_pages = z_size * num_views;
+    while ((num_pages == 0 && (vfs->exists(file) || z == 0)) ||
+           (num_pages != 0 && z < num_pages)) {
       if (!do_read_one(file, alpha_file, z, 0, primary_file_num_channels,
       if (!do_read_one(file, alpha_file, z, 0, primary_file_num_channels,
                        alpha_file_channel, options, header_only, record)) {
                        alpha_file_channel, options, header_only, record)) {
         return false;
         return false;
@@ -2801,6 +2871,8 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath,
       file = fullpath_pattern.get_filename_index(z);
       file = fullpath_pattern.get_filename_index(z);
       alpha_file = alpha_fullpath_pattern.get_filename_index(z);
       alpha_file = alpha_fullpath_pattern.get_filename_index(z);
     }
     }
+    _fullpath = fullpath_pattern;
+    _alpha_fullpath = alpha_fullpath_pattern;
 
 
   } else if (read_mipmaps) {
   } else if (read_mipmaps) {
     // Read a sequence of mipmap levels.
     // Read a sequence of mipmap levels.
@@ -2835,6 +2907,8 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath,
       file = fullpath_pattern.get_filename_index(n);
       file = fullpath_pattern.get_filename_index(n);
       alpha_file = alpha_fullpath_pattern.get_filename_index(n);
       alpha_file = alpha_fullpath_pattern.get_filename_index(n);
     }
     }
+    _fullpath = fullpath_pattern;
+    _alpha_fullpath = alpha_fullpath_pattern;
 
 
   } else {
   } else {
     // Just an ordinary read of one file.
     // Just an ordinary read of one file.
@@ -3114,10 +3188,10 @@ do_load_one(const PNMImage &pnmimage, const string &name, int z, int n,
     // A special case for mipmap level 0.  When we load mipmap level
     // A special case for mipmap level 0.  When we load mipmap level
     // 0, unless we already have mipmap levels, it determines the
     // 0, unless we already have mipmap levels, it determines the
     // image properties like size and number of components.
     // image properties like size and number of components.
-    if (!do_reconsider_z_size(z)) {
+    if (!do_reconsider_z_size(z, options)) {
       return false;
       return false;
     }
     }
-    nassertr(z >= 0 && z < _z_size, false);
+    nassertr(z >= 0 && z < _z_size * _num_views, false);
 
 
     if (z == 0) {
     if (z == 0) {
       ComponentType component_type = T_unsigned_byte;
       ComponentType component_type = T_unsigned_byte;
@@ -3582,9 +3656,9 @@ do_write(const Filename &fullpath, int z, int n, bool write_pages, bool write_mi
     int num_levels = _ram_images.size();
     int num_levels = _ram_images.size();
 
 
     for (int n = 0; n < num_levels; ++n) {
     for (int n = 0; n < num_levels; ++n) {
-      int z_size = do_get_expected_mipmap_z_size(n);
+      int num_pages = do_get_expected_mipmap_num_pages(n);
 
 
-      for (z = 0; z < z_size; ++z) {
+      for (z = 0; z < num_pages; ++z) {
         Filename n_pattern = Filename::pattern_filename(fullpath_pattern.get_filename_index(z));
         Filename n_pattern = Filename::pattern_filename(fullpath_pattern.get_filename_index(z));
 
 
         if (!n_pattern.has_hash()) {
         if (!n_pattern.has_hash()) {
@@ -3610,7 +3684,8 @@ do_write(const Filename &fullpath, int z, int n, bool write_pages, bool write_mi
       return false;
       return false;
     }
     }
 
 
-    for (z = 0; z < _z_size; ++z) {
+    int num_pages = _z_size * _num_views;
+    for (z = 0; z < num_pages; ++z) {
       if (!do_write_one(fullpath_pattern.get_filename_index(z), z, n)) {
       if (!do_write_one(fullpath_pattern.get_filename_index(z), z, n)) {
         return false;
         return false;
       }
       }
@@ -3681,8 +3756,11 @@ do_store_one(PNMImage &pnmimage, int z, int n) const {
   // First, reload the ram image if necessary.
   // First, reload the ram image if necessary.
   ((Texture *)this)->do_get_uncompressed_ram_image();
   ((Texture *)this)->do_get_uncompressed_ram_image();
 
 
-  nassertr(do_has_ram_mipmap_image(n), false);
-  nassertr(z >= 0 && z < do_get_expected_mipmap_z_size(n), false);
+  if (!do_has_ram_mipmap_image(n)) {
+    return false;
+  }
+
+  nassertr(z >= 0 && z < do_get_expected_mipmap_num_pages(n), false);
   nassertr(_ram_image_compression == CM_off, false);
   nassertr(_ram_image_compression == CM_off, false);
 
 
   return convert_to_pnmimage(pnmimage,
   return convert_to_pnmimage(pnmimage,
@@ -3822,6 +3900,7 @@ do_unlock_and_reload_ram_image(bool allow_compression) {
     if (tex->_x_size != _x_size ||
     if (tex->_x_size != _x_size ||
         tex->_y_size != _y_size ||
         tex->_y_size != _y_size ||
         tex->_z_size != _z_size ||
         tex->_z_size != _z_size ||
+        tex->_num_views != _num_views ||
         tex->_num_components != _num_components ||
         tex->_num_components != _num_components ||
         tex->_component_width != _component_width ||
         tex->_component_width != _component_width ||
         tex->_texture_type != _texture_type ||
         tex->_texture_type != _texture_type ||
@@ -3830,6 +3909,7 @@ do_unlock_and_reload_ram_image(bool allow_compression) {
       _x_size = tex->_x_size;
       _x_size = tex->_x_size;
       _y_size = tex->_y_size;
       _y_size = tex->_y_size;
       _z_size = tex->_z_size;
       _z_size = tex->_z_size;
+      _num_views = tex->_num_views;
 
 
       _num_components = tex->_num_components;
       _num_components = tex->_num_components;
       _component_width = tex->_component_width;
       _component_width = tex->_component_width;
@@ -4315,37 +4395,74 @@ do_has_all_ram_mipmap_images() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::do_reconsider_z_size
 //     Function: Texture::do_reconsider_z_size
 //       Access: Protected
 //       Access: Protected
-//  Description: Considers whether the z_size should automatically be
-//               adjusted when the user loads a new page.  Returns
-//               true if the z size is valid, false otherwise.
+//  Description: Considers whether the z_size (or num_views) should
+//               automatically be adjusted when the user loads a new
+//               page.  Returns true if the z size is valid, false
+//               otherwise.
 //
 //
 //               Assumes the lock is already held.
 //               Assumes the lock is already held.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
-do_reconsider_z_size(int z) {
-  if (z >= _z_size) {
-    // If we're loading a page past _z_size, treat it as an implicit
-    // request to enlarge _z_size.  However, this is only legal if
-    // this is, in fact, a 3-d texture or a 2d texture array (cube maps
-    // always have z_size 6, and other types have z_size 1).
-    nassertr(_texture_type == Texture::TT_3d_texture ||
-             _texture_type == Texture::TT_2d_texture_array, false);
-
-    _z_size = z + 1;
+do_reconsider_z_size(int z, const LoaderOptions &options) {
+  if (z >= _z_size * _num_views) {
+    bool num_views_specified = true;
+    if (options.get_texture_flags() & LoaderOptions::TF_multiview) {
+      // This flag is false if is a multiview texture with a specified
+      // number of views.  It is true if it is not a multiview
+      // texture, or if it is but the number of views is explicitly
+      // specified.
+      num_views_specified = (options.get_texture_num_views() != 0);
+    }
+
+    if (num_views_specified &&
+        (_texture_type == Texture::TT_3d_texture ||
+         _texture_type == Texture::TT_2d_texture_array)) {
+      // If we're loading a page past _z_size, treat it as an implicit
+      // request to enlarge _z_size.  However, this is only legal if
+      // this is, in fact, a 3-d texture or a 2d texture array (cube maps
+      // always have z_size 6, and other types have z_size 1).
+      nassertr(_num_views != 0, false);
+      _z_size = (z / _num_views) + 1;
+
+    } else if (_z_size != 0) {
+      // In the case of a 2-d texture or cube map, or a 3-d texture
+      // with an unspecified _num_views, assume we're loading views of
+      // a multiview texture.
+      _num_views = (z / _z_size) + 1;
+
+    } else {
+      // The first image loaded sets an implicit z-size.
+      _z_size = 1;
+    }
+
     // Increase the size of the data buffer to make room for the new
     // Increase the size of the data buffer to make room for the new
     // texture level.
     // texture level.
-    size_t new_size = do_get_expected_ram_image_size();
-    if (!_ram_images.empty() &&
-        !_ram_images[0]._image.empty() &&
-        new_size > _ram_images[0]._image.size()) {
-      _ram_images[0]._image.insert(_ram_images[0]._image.end(), new_size - _ram_images[0]._image.size(), 0);
-      nassertr(_ram_images[0]._image.size() == new_size, false);
-    }
+    do_allocate_pages();
   }
   }
 
 
   return true;
   return true;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::do_allocate_pages
+//       Access: Protected, Virtual
+//  Description: Called internally by do_reconsider_z_size() to
+//               allocate new memory in _ram_images[0] for the new
+//               number of pages.
+//
+//               Assumes the lock is already held.
+////////////////////////////////////////////////////////////////////
+void Texture::
+do_allocate_pages() {
+  size_t new_size = do_get_expected_ram_image_size();
+  if (!_ram_images.empty() &&
+      !_ram_images[0]._image.empty() &&
+      new_size > _ram_images[0]._image.size()) {
+    _ram_images[0]._image.insert(_ram_images[0]._image.end(), new_size - _ram_images[0]._image.size(), 0);
+    nassertv(_ram_images[0]._image.size() == new_size);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::do_reconsider_image_properties
 //     Function: Texture::do_reconsider_image_properties
 //       Access: Protected
 //       Access: Protected
@@ -4434,8 +4551,8 @@ bool Texture::
 do_rescale_texture() {
 do_rescale_texture() {
   int new_x_size = _x_size;
   int new_x_size = _x_size;
   int new_y_size = _y_size;
   int new_y_size = _y_size;
-  if (_z_size != 1) {
-    nassert_raise("rescale_texture() doesn't support 3-d textures.");
+  if (_z_size * _num_views != 1) {
+    nassert_raise("rescale_texture() doesn't support 3-d or multiview textures.");
     return false;
     return false;
   }
   }
 
 
@@ -4533,6 +4650,7 @@ do_assign(const Texture &copy) {
   _x_size = copy._x_size;
   _x_size = copy._x_size;
   _y_size = copy._y_size;
   _y_size = copy._y_size;
   _z_size = copy._z_size;
   _z_size = copy._z_size;
+  _num_views = copy._num_views;
   _pad_x_size = copy._pad_x_size;
   _pad_x_size = copy._pad_x_size;
   _pad_y_size = copy._pad_y_size;
   _pad_y_size = copy._pad_y_size;
   _pad_z_size = copy._pad_z_size;
   _pad_z_size = copy._pad_z_size;
@@ -4622,6 +4740,7 @@ do_setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
   _x_size = x_size;
   _x_size = x_size;
   _y_size = y_size;
   _y_size = y_size;
   _z_size = z_size;
   _z_size = z_size;
+  _num_views = 1;
   do_set_component_type(component_type);
   do_set_component_type(component_type);
   do_set_format(format);
   do_set_format(format);
 
 
@@ -4768,6 +4887,22 @@ do_set_z_size(int z_size) {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::do_set_num_views
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+void Texture::
+do_set_num_views(int num_views) {
+  nassertv(num_views >= 1);
+  if (_num_views != num_views) {
+    _num_views = num_views;
+    ++_image_modified;
+    do_clear_ram_image();
+    do_set_pad_size(0, 0, 0);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::do_set_wrap_u
 //     Function: Texture::do_set_wrap_u
 //       Access: Protected
 //       Access: Protected
@@ -6205,15 +6340,20 @@ read_dds_level_dxt45(Texture *tex, const DDSHeader &header, int n, istream &in)
 //               never be called by user code.
 //               never be called by user code.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
-clear_prepared(PreparedGraphicsObjects *prepared_objects) {
-  Contexts::iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
-    _contexts.erase(ci);
-  } else {
-    // If this assertion fails, clear_prepared() was given a
-    // prepared_objects which the texture didn't know about.
-    nassertv(false);
+clear_prepared(int view, PreparedGraphicsObjects *prepared_objects) {
+  PreparedViews::iterator pvi;
+  pvi = _prepared_views.find(prepared_objects);
+  if (pvi != _prepared_views.end()) {
+    Contexts &contexts = (*pvi).second;
+    Contexts::iterator ci;
+    ci = contexts.find(view);
+    if (ci != contexts.end()) {
+      contexts.erase(ci);
+    }
+
+    if (contexts.empty()) {
+      _prepared_views.erase(pvi);
+    }
   }
   }
 }
 }
 
 
@@ -6292,14 +6432,17 @@ filter_2d_mipmap_pages(Texture::RamImage &to, const Texture::RamImage &from,
 
 
   size_t to_row_size = (size_t)to_x_size * pixel_size;
   size_t to_row_size = (size_t)to_x_size * pixel_size;
   to._page_size = (size_t)to_y_size * to_row_size;
   to._page_size = (size_t)to_y_size * to_row_size;
-  to._image = PTA_uchar::empty_array(to._page_size * _z_size, get_class_type());
+  to._image = PTA_uchar::empty_array(to._page_size * _z_size * _num_views, get_class_type());
 
 
   Filter2DComponent *filter_component = (_component_type == T_unsigned_byte ? &filter_2d_unsigned_byte : filter_2d_unsigned_short);
   Filter2DComponent *filter_component = (_component_type == T_unsigned_byte ? &filter_2d_unsigned_byte : filter_2d_unsigned_short);
 
 
-  for (int z = 0; z < _z_size; ++z) {
+  int num_pages = _z_size * _num_views;
+  for (int z = 0; z < num_pages; ++z) {
     // For each level.
     // For each level.
     unsigned char *p = to._image.p() + z * to._page_size;
     unsigned char *p = to._image.p() + z * to._page_size;
+    nassertv(p <= to._image.p() + to._image.size() + to._page_size);
     const unsigned char *q = from._image.p() + z * from._page_size;
     const unsigned char *q = from._image.p() + z * from._page_size;
+    nassertv(q <= from._image.p() + from._image.size() + from._page_size);
     if (y_size != 1) {
     if (y_size != 1) {
       int y;
       int y;
       for (y = 0; y < y_size - 1; y += 2) {
       for (y = 0; y < y_size - 1; y += 2) {
@@ -6383,6 +6526,7 @@ filter_3d_mipmap_level(Texture::RamImage &to, const Texture::RamImage &from,
   size_t pixel_size = _num_components * _component_width;
   size_t pixel_size = _num_components * _component_width;
   size_t row_size = (size_t)x_size * pixel_size;
   size_t row_size = (size_t)x_size * pixel_size;
   size_t page_size = (size_t)y_size * row_size;
   size_t page_size = (size_t)y_size * row_size;
+  size_t view_size = (size_t)z_size * page_size;
 
 
   int to_x_size = max(x_size >> 1, 1);
   int to_x_size = max(x_size >> 1, 1);
   int to_y_size = max(y_size >> 1, 1);
   int to_y_size = max(y_size >> 1, 1);
@@ -6390,32 +6534,104 @@ filter_3d_mipmap_level(Texture::RamImage &to, const Texture::RamImage &from,
 
 
   size_t to_row_size = (size_t)to_x_size * pixel_size;
   size_t to_row_size = (size_t)to_x_size * pixel_size;
   size_t to_page_size = (size_t)to_y_size * to_row_size;
   size_t to_page_size = (size_t)to_y_size * to_row_size;
+  size_t to_view_size = (size_t)to_z_size * to_page_size;
   to._page_size = to_page_size;
   to._page_size = to_page_size;
-  to._image = PTA_uchar::empty_array(to_page_size * to_z_size, get_class_type());
+  to._image = PTA_uchar::empty_array(to_page_size * to_z_size * _num_views, get_class_type());
 
 
   Filter3DComponent *filter_component = (_component_type == T_unsigned_byte ? &filter_3d_unsigned_byte : filter_3d_unsigned_short);
   Filter3DComponent *filter_component = (_component_type == T_unsigned_byte ? &filter_3d_unsigned_byte : filter_3d_unsigned_short);
 
 
-  unsigned char *p = to._image.p();
-  const unsigned char *q = from._image.p();
-  if (z_size != 1) {
-    int z;
-    for (z = 0; z < z_size - 1; z += 2) {
-      // For each level.
-      nassertv(p == to._image.p() + (z / 2) * to_page_size);
-      nassertv(q == from._image.p() + z * page_size);
+  for (int view = 0; view < _num_views; ++view) {
+    unsigned char *start_to = to._image.p() + view * to_view_size;
+    const unsigned char *start_from = from._image.p() + view * view_size;
+    nassertv(start_to + to_view_size <= to._image.p() + to._image.size());
+    nassertv(start_from + view_size <= from._image.p() + from._image.size());
+    unsigned char *p = start_to;
+    const unsigned char *q = start_from;
+    if (z_size != 1) {
+      int z;
+      for (z = 0; z < z_size - 1; z += 2) {
+        // For each level.
+        nassertv(p == start_to + (z / 2) * to_page_size);
+        nassertv(q == start_from + z * page_size);
+        if (y_size != 1) {
+          int y;
+          for (y = 0; y < y_size - 1; y += 2) {
+            // For each row.
+            nassertv(p == start_to + (z / 2) * to_page_size + (y / 2) * to_row_size);
+            nassertv(q == start_from + z * page_size + y * row_size);
+            if (x_size != 1) {
+              int x;
+              for (x = 0; x < x_size - 1; x += 2) {
+                // For each pixel.
+                for (int c = 0; c < _num_components; ++c) {
+                  // For each component.
+                  filter_component(p, q, pixel_size, row_size, page_size);
+                }
+                q += pixel_size;
+              }
+              if (x < x_size) {
+                // Skip the last odd pixel.
+                q += pixel_size;
+              }
+            } else {
+              // Just one pixel.
+              for (int c = 0; c < _num_components; ++c) {
+                // For each component.
+                filter_component(p, q, 0, row_size, page_size);
+              }
+            }
+            q += row_size;
+            Thread::consider_yield();
+          }
+          if (y < y_size) {
+            // Skip the last odd row.
+            q += row_size;
+          }
+        } else {
+          // Just one row.
+          if (x_size != 1) {
+            int x;
+            for (x = 0; x < x_size - 1; x += 2) {
+              // For each pixel.
+              for (int c = 0; c < _num_components; ++c) {
+                // For each component.
+                filter_component(p, q, pixel_size, 0, page_size);
+              }
+              q += pixel_size;
+            }
+            if (x < x_size) {
+              // Skip the last odd pixel.
+              q += pixel_size;
+            }
+          } else {
+            // Just one pixel.
+            for (int c = 0; c < _num_components; ++c) {
+              // For each component.
+              filter_component(p, q, 0, 0, page_size);
+            }
+          }
+        }
+        q += page_size;
+      }
+      if (z < z_size) {
+        // Skip the last odd page.
+        q += page_size;
+      }
+    } else {
+      // Just one page.
       if (y_size != 1) {
       if (y_size != 1) {
         int y;
         int y;
         for (y = 0; y < y_size - 1; y += 2) {
         for (y = 0; y < y_size - 1; y += 2) {
           // For each row.
           // For each row.
-          nassertv(p == to._image.p() + (z / 2) * to_page_size + (y / 2) * to_row_size);
-          nassertv(q == from._image.p() + z * page_size + y * row_size);
+          nassertv(p == start_to + (y / 2) * to_row_size);
+          nassertv(q == start_from + y * row_size);
           if (x_size != 1) {
           if (x_size != 1) {
             int x;
             int x;
             for (x = 0; x < x_size - 1; x += 2) {
             for (x = 0; x < x_size - 1; x += 2) {
               // For each pixel.
               // For each pixel.
               for (int c = 0; c < _num_components; ++c) {
               for (int c = 0; c < _num_components; ++c) {
                 // For each component.
                 // For each component.
-                filter_component(p, q, pixel_size, row_size, page_size);
+                filter_component(p, q, pixel_size, row_size, 0);
               }
               }
               q += pixel_size;
               q += pixel_size;
             }
             }
@@ -6427,7 +6643,7 @@ filter_3d_mipmap_level(Texture::RamImage &to, const Texture::RamImage &from,
             // Just one pixel.
             // Just one pixel.
             for (int c = 0; c < _num_components; ++c) {
             for (int c = 0; c < _num_components; ++c) {
               // For each component.
               // For each component.
-              filter_component(p, q, 0, row_size, page_size);
+              filter_component(p, q, 0, row_size, 0);
             }
             }
           }
           }
           q += row_size;
           q += row_size;
@@ -6445,43 +6661,7 @@ filter_3d_mipmap_level(Texture::RamImage &to, const Texture::RamImage &from,
             // For each pixel.
             // For each pixel.
             for (int c = 0; c < _num_components; ++c) {
             for (int c = 0; c < _num_components; ++c) {
               // For each component.
               // For each component.
-              filter_component(p, q, pixel_size, 0, page_size);
-            }
-            q += pixel_size;
-          }
-          if (x < x_size) {
-            // Skip the last odd pixel.
-            q += pixel_size;
-          }
-        } else {
-          // Just one pixel.
-          for (int c = 0; c < _num_components; ++c) {
-            // For each component.
-            filter_component(p, q, 0, 0, page_size);
-          }
-        }
-      }
-      q += page_size;
-    }
-    if (z < z_size) {
-      // Skip the last odd page.
-      q += page_size;
-    }
-  } else {
-    // Just one page.
-    if (y_size != 1) {
-      int y;
-      for (y = 0; y < y_size - 1; y += 2) {
-        // For each row.
-        nassertv(p == to._image.p() + (y / 2) * to_row_size);
-        nassertv(q == from._image.p() + y * row_size);
-        if (x_size != 1) {
-          int x;
-          for (x = 0; x < x_size - 1; x += 2) {
-            // For each pixel.
-            for (int c = 0; c < _num_components; ++c) {
-              // For each component.
-              filter_component(p, q, pixel_size, row_size, 0);
+              filter_component(p, q, pixel_size, 0, 0);
             }
             }
             q += pixel_size;
             q += pixel_size;
           }
           }
@@ -6493,44 +6673,15 @@ filter_3d_mipmap_level(Texture::RamImage &to, const Texture::RamImage &from,
           // Just one pixel.
           // Just one pixel.
           for (int c = 0; c < _num_components; ++c) {
           for (int c = 0; c < _num_components; ++c) {
             // For each component.
             // For each component.
-            filter_component(p, q, 0, row_size, 0);
-          }
-        }
-        q += row_size;
-        Thread::consider_yield();
-      }
-      if (y < y_size) {
-        // Skip the last odd row.
-        q += row_size;
-      }
-    } else {
-      // Just one row.
-      if (x_size != 1) {
-        int x;
-        for (x = 0; x < x_size - 1; x += 2) {
-          // For each pixel.
-          for (int c = 0; c < _num_components; ++c) {
-            // For each component.
-            filter_component(p, q, pixel_size, 0, 0);
+            filter_component(p, q, 0, 0, 0);
           }
           }
-          q += pixel_size;
-        }
-        if (x < x_size) {
-          // Skip the last odd pixel.
-          q += pixel_size;
-        }
-      } else {
-        // Just one pixel.
-        for (int c = 0; c < _num_components; ++c) {
-          // For each component.
-          filter_component(p, q, 0, 0, 0);
         }
         }
       }
       }
     }
     }
-  }
 
 
-  nassertv(p == to._image.p() + to_z_size * to_page_size);
-  nassertv(q == from._image.p() + z_size * page_size);
+    nassertv(p == start_to + to_z_size * to_page_size);
+    nassertv(q == start_from + z_size * page_size);
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -6641,13 +6792,13 @@ do_squish(Texture::CompressionMode compression, int squish_flags) {
     RamImage compressed_image;
     RamImage compressed_image;
     int x_size = do_get_expected_mipmap_x_size(n);
     int x_size = do_get_expected_mipmap_x_size(n);
     int y_size = do_get_expected_mipmap_y_size(n);
     int y_size = do_get_expected_mipmap_y_size(n);
-    int z_size = do_get_expected_mipmap_z_size(n);
+    int num_pages = do_get_expected_mipmap_num_pages(n);
     int page_size = squish::GetStorageRequirements(x_size, y_size, squish_flags);
     int page_size = squish::GetStorageRequirements(x_size, y_size, squish_flags);
     int cell_size = squish::GetStorageRequirements(4, 4, squish_flags);
     int cell_size = squish::GetStorageRequirements(4, 4, squish_flags);
 
 
     compressed_image._page_size = page_size;
     compressed_image._page_size = page_size;
-    compressed_image._image = PTA_uchar::empty_array(page_size * z_size);
-    for (int z = 0; z < z_size; ++z) {
+    compressed_image._image = PTA_uchar::empty_array(page_size * num_pages);
+    for (int z = 0; z < num_pages; ++z) {
       unsigned char *dest_page = compressed_image._image.p() + z * page_size;
       unsigned char *dest_page = compressed_image._image.p() + z * page_size;
       unsigned const char *source_page = _ram_images[n]._image.p() + z * _ram_images[n]._page_size;
       unsigned const char *source_page = _ram_images[n]._image.p() + z * _ram_images[n]._page_size;
       unsigned const char *source_page_end = source_page + _ram_images[n]._page_size;
       unsigned const char *source_page_end = source_page + _ram_images[n]._page_size;
@@ -6732,13 +6883,13 @@ do_unsquish(int squish_flags) {
     RamImage uncompressed_image;
     RamImage uncompressed_image;
     int x_size = do_get_expected_mipmap_x_size(n);
     int x_size = do_get_expected_mipmap_x_size(n);
     int y_size = do_get_expected_mipmap_y_size(n);
     int y_size = do_get_expected_mipmap_y_size(n);
-    int z_size = do_get_expected_mipmap_z_size(n);
+    int num_pages = do_get_expected_mipmap_num_pages(n);
     int page_size = squish::GetStorageRequirements(x_size, y_size, squish_flags);
     int page_size = squish::GetStorageRequirements(x_size, y_size, squish_flags);
     int cell_size = squish::GetStorageRequirements(4, 4, squish_flags);
     int cell_size = squish::GetStorageRequirements(4, 4, squish_flags);
 
 
     uncompressed_image._page_size = do_get_expected_ram_mipmap_page_size(n);
     uncompressed_image._page_size = do_get_expected_ram_mipmap_page_size(n);
-    uncompressed_image._image = PTA_uchar::empty_array(uncompressed_image._page_size * z_size);
-    for (int z = 0; z < z_size; ++z) {
+    uncompressed_image._image = PTA_uchar::empty_array(uncompressed_image._page_size * num_pages);
+    for (int z = 0; z < num_pages; ++z) {
       unsigned char *dest_page = uncompressed_image._image.p() + z * uncompressed_image._page_size;
       unsigned char *dest_page = uncompressed_image._image.p() + z * uncompressed_image._page_size;
       unsigned char *dest_page_end = dest_page + uncompressed_image._page_size;
       unsigned char *dest_page_end = dest_page + uncompressed_image._page_size;
       unsigned const char *source_page = _ram_images[n]._image.p() + z * page_size;
       unsigned const char *source_page = _ram_images[n]._image.p() + z * page_size;
@@ -7004,6 +7155,7 @@ do_write_datagram_rawdata(BamWriter *manager, Datagram &me) {
   me.add_uint32(_x_size);
   me.add_uint32(_x_size);
   me.add_uint32(_y_size);
   me.add_uint32(_y_size);
   me.add_uint32(_z_size);
   me.add_uint32(_z_size);
+  me.add_uint32(_num_views);
   me.add_uint8(_component_type);
   me.add_uint8(_component_type);
   me.add_uint8(_component_width);
   me.add_uint8(_component_width);
   me.add_uint8(_ram_image_compression);
   me.add_uint8(_ram_image_compression);
@@ -7222,6 +7374,10 @@ do_fillin_rawdata(DatagramIterator &scan, BamReader *manager) {
   _x_size = scan.get_uint32();
   _x_size = scan.get_uint32();
   _y_size = scan.get_uint32();
   _y_size = scan.get_uint32();
   _z_size = scan.get_uint32();
   _z_size = scan.get_uint32();
+  _num_views = 1;
+  if (manager->get_file_minor_ver() >= 26) {
+    _num_views = scan.get_uint32();
+  }
   _component_type = (ComponentType)scan.get_uint8();
   _component_type = (ComponentType)scan.get_uint8();
   _component_width = scan.get_uint8();
   _component_width = scan.get_uint8();
   _ram_image_compression = CM_off;
   _ram_image_compression = CM_off;

+ 20 - 6
panda/src/gobj/texture.h

@@ -210,7 +210,6 @@ PUBLISHED:
   INLINE void setup_texture(TextureType texture_type,
   INLINE void setup_texture(TextureType texture_type,
                             int x_size, int y_size, int z_size,
                             int x_size, int y_size, int z_size,
                             ComponentType component_type, Format format);
                             ComponentType component_type, Format format);
-
   INLINE void setup_1d_texture();
   INLINE void setup_1d_texture();
   INLINE void setup_1d_texture(int x_size,
   INLINE void setup_1d_texture(int x_size,
                                ComponentType component_type, Format format);
                                ComponentType component_type, Format format);
@@ -272,6 +271,8 @@ PUBLISHED:
   INLINE int get_x_size() const;
   INLINE int get_x_size() const;
   INLINE int get_y_size() const;
   INLINE int get_y_size() const;
   INLINE int get_z_size() const;
   INLINE int get_z_size() const;
+  INLINE int get_num_views() const;
+  INLINE int get_num_pages() const;
   INLINE int get_num_components() const;
   INLINE int get_num_components() const;
   INLINE int get_component_width() const;
   INLINE int get_component_width() const;
   INLINE TextureType get_texture_type() const;
   INLINE TextureType get_texture_type() const;
@@ -311,11 +312,13 @@ PUBLISHED:
   INLINE int get_expected_mipmap_x_size(int n) const;
   INLINE int get_expected_mipmap_x_size(int n) const;
   INLINE int get_expected_mipmap_y_size(int n) const;
   INLINE int get_expected_mipmap_y_size(int n) const;
   INLINE int get_expected_mipmap_z_size(int n) const;
   INLINE int get_expected_mipmap_z_size(int n) const;
+  INLINE int get_expected_mipmap_num_pages(int n) const;
 
 
   INLINE bool has_ram_image() const;
   INLINE bool has_ram_image() const;
   INLINE bool has_uncompressed_ram_image() const;
   INLINE bool has_uncompressed_ram_image() const;
   INLINE bool might_have_ram_image() const;
   INLINE bool might_have_ram_image() const;
   INLINE size_t get_ram_image_size() const;
   INLINE size_t get_ram_image_size() const;
+  INLINE size_t get_ram_view_size() const;
   INLINE size_t get_ram_page_size() const;
   INLINE size_t get_ram_page_size() const;
   INLINE size_t get_expected_ram_image_size() const;
   INLINE size_t get_expected_ram_image_size() const;
   INLINE size_t get_expected_ram_page_size() const;
   INLINE size_t get_expected_ram_page_size() const;
@@ -343,8 +346,10 @@ PUBLISHED:
   int get_num_loadable_ram_mipmap_images() const;
   int get_num_loadable_ram_mipmap_images() const;
   INLINE bool has_all_ram_mipmap_images() const;
   INLINE bool has_all_ram_mipmap_images() const;
   INLINE size_t get_ram_mipmap_image_size(int n) const;
   INLINE size_t get_ram_mipmap_image_size(int n) const;
+  INLINE size_t get_ram_mipmap_view_size(int n) const;
   INLINE size_t get_ram_mipmap_page_size(int n) const;
   INLINE size_t get_ram_mipmap_page_size(int n) const;
   INLINE size_t get_expected_ram_mipmap_image_size(int n) const;
   INLINE size_t get_expected_ram_mipmap_image_size(int n) const;
+  INLINE size_t get_expected_ram_mipmap_view_size(int n) const;
   INLINE size_t get_expected_ram_mipmap_page_size(int n) const;
   INLINE size_t get_expected_ram_mipmap_page_size(int n) const;
   CPTA_uchar get_ram_mipmap_image(int n);
   CPTA_uchar get_ram_mipmap_image(int n);
   void *get_ram_mipmap_pointer(int n);
   void *get_ram_mipmap_pointer(int n);
@@ -413,6 +418,7 @@ PUBLISHED:
   INLINE void set_x_size(int x_size);
   INLINE void set_x_size(int x_size);
   INLINE void set_y_size(int y_size);
   INLINE void set_y_size(int y_size);
   INLINE void set_z_size(int z_size);
   INLINE void set_z_size(int z_size);
+  INLINE void set_num_views(int num_views);
 
 
   INLINE int get_pad_x_size() const;
   INLINE int get_pad_x_size() const;
   INLINE int get_pad_y_size() const;
   INLINE int get_pad_y_size() const;
@@ -444,7 +450,8 @@ PUBLISHED:
   INLINE bool get_post_load_store_cache() const;
   INLINE bool get_post_load_store_cache() const;
   INLINE void set_post_load_store_cache(bool flag);
   INLINE void set_post_load_store_cache(bool flag);
 
 
-  TextureContext *prepare_now(PreparedGraphicsObjects *prepared_objects,
+  TextureContext *prepare_now(int view,
+                              PreparedGraphicsObjects *prepared_objects,
                               GraphicsStateGuardianBase *gsg);
                               GraphicsStateGuardianBase *gsg);
 
 
   static int up_to_power_2(int value);
   static int up_to_power_2(int value);
@@ -544,7 +551,8 @@ protected:
   bool do_uncompress_ram_image();
   bool do_uncompress_ram_image();
   bool do_has_all_ram_mipmap_images() const;
   bool do_has_all_ram_mipmap_images() const;
 
 
-  bool do_reconsider_z_size(int z);
+  bool do_reconsider_z_size(int z, const LoaderOptions &options);
+  virtual void do_allocate_pages();
   bool do_reconsider_image_properties(int x_size, int y_size, int num_components,
   bool do_reconsider_image_properties(int x_size, int y_size, int num_components,
                                       ComponentType component_type, int z,
                                       ComponentType component_type, int z,
                                       const LoaderOptions &options);
                                       const LoaderOptions &options);
@@ -556,6 +564,7 @@ protected:
   void do_setup_texture(TextureType texture_type, int x_size, int y_size,
   void do_setup_texture(TextureType texture_type, int x_size, int y_size,
                         int z_size, ComponentType component_type,
                         int z_size, ComponentType component_type,
                         Format format);
                         Format format);
+  void do_set_num_views(int num_views);
   void do_set_format(Format format);
   void do_set_format(Format format);
   void do_set_component_type(ComponentType component_type);
   void do_set_component_type(ComponentType component_type);
   void do_set_x_size(int x_size);
   void do_set_x_size(int x_size);
@@ -582,13 +591,16 @@ protected:
   INLINE bool do_has_ram_mipmap_image(int n) const;
   INLINE bool do_has_ram_mipmap_image(int n) const;
   int do_get_expected_num_mipmap_levels() const;
   int do_get_expected_num_mipmap_levels() const;
   INLINE size_t do_get_expected_ram_image_size() const;
   INLINE size_t do_get_expected_ram_image_size() const;
+  INLINE size_t do_get_expected_ram_view_size() const;
   INLINE size_t do_get_expected_ram_page_size() const;
   INLINE size_t do_get_expected_ram_page_size() const;
   size_t do_get_ram_mipmap_page_size(int n) const;
   size_t do_get_ram_mipmap_page_size(int n) const;
   INLINE size_t do_get_expected_ram_mipmap_image_size(int n) const;
   INLINE size_t do_get_expected_ram_mipmap_image_size(int n) const;
+  INLINE size_t do_get_expected_ram_mipmap_view_size(int n) const;
   INLINE size_t do_get_expected_ram_mipmap_page_size(int n) const;
   INLINE size_t do_get_expected_ram_mipmap_page_size(int n) const;
   int do_get_expected_mipmap_x_size(int n) const;
   int do_get_expected_mipmap_x_size(int n) const;
   int do_get_expected_mipmap_y_size(int n) const;
   int do_get_expected_mipmap_y_size(int n) const;
   int do_get_expected_mipmap_z_size(int n) const;
   int do_get_expected_mipmap_z_size(int n) const;
+  INLINE int do_get_expected_mipmap_num_pages(int n) const;
   INLINE void do_clear_ram_image();
   INLINE void do_clear_ram_image();
   void do_clear_simple_ram_image();
   void do_clear_simple_ram_image();
   void do_clear_ram_mipmap_images();
   void do_clear_ram_mipmap_images();
@@ -645,7 +657,7 @@ private:
                                         const DDSHeader &header, 
                                         const DDSHeader &header, 
                                         int n, istream &in);
                                         int n, istream &in);
 
 
-  void clear_prepared(PreparedGraphicsObjects *prepared_objects);
+  void clear_prepared(int view, PreparedGraphicsObjects *prepared_objects);
 
 
   static void consider_downgrade(PNMImage &pnmimage, int num_channels, const string &name);
   static void consider_downgrade(PNMImage &pnmimage, int num_channels, const string &name);
 
 
@@ -720,6 +732,7 @@ protected:
   int _x_size;
   int _x_size;
   int _y_size;
   int _y_size;
   int _z_size;
   int _z_size;
+  int _num_views;
   int _num_components;
   int _num_components;
   int _component_width;
   int _component_width;
   TextureType _texture_type;
   TextureType _texture_type;
@@ -758,8 +771,9 @@ protected:
   // Each PGO conversely keeps a list (a set) of all the Textures that
   // Each PGO conversely keeps a list (a set) of all the Textures that
   // have been prepared there.  When either destructs, it removes
   // have been prepared there.  When either destructs, it removes
   // itself from the other's list.
   // itself from the other's list.
-  typedef pmap<PreparedGraphicsObjects *, TextureContext *> Contexts;
-  Contexts _contexts;
+  typedef pmap<int, TextureContext *> Contexts;
+  typedef pmap<PreparedGraphicsObjects *, Contexts> PreparedViews;
+  PreparedViews _prepared_views;
   
   
   // It is common, when using normal maps, specular maps, gloss maps,
   // It is common, when using normal maps, specular maps, gloss maps,
   // and such, to use a file naming convention where the filenames
   // and such, to use a file naming convention where the filenames

+ 15 - 2
panda/src/gobj/textureContext.I

@@ -19,10 +19,11 @@
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE TextureContext::
 INLINE TextureContext::
-TextureContext(PreparedGraphicsObjects *pgo, Texture *tex) :
+TextureContext(PreparedGraphicsObjects *pgo, Texture *tex, int view) :
   BufferContext(&pgo->_texture_residency),
   BufferContext(&pgo->_texture_residency),
   AdaptiveLruPage(0),
   AdaptiveLruPage(0),
-  _texture(tex)
+  _texture(tex),
+  _view(view)
 {
 {
 }
 }
 
 
@@ -37,6 +38,18 @@ get_texture() const {
   return _texture;
   return _texture;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextureContext::get_view
+//       Access: Public
+//  Description: Returns the specific view of a multiview texture this
+//               context represents.  In the usual case, with a
+//               non-multiview texture, this will be 0.
+////////////////////////////////////////////////////////////////////
+INLINE int TextureContext::
+get_view() const {
+  return _view;
+}
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: TextureContext::was_modified
 //     Function: TextureContext::was_modified

+ 3 - 1
panda/src/gobj/textureContext.h

@@ -37,10 +37,11 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA_GOBJ TextureContext : public BufferContext, public AdaptiveLruPage {
 class EXPCL_PANDA_GOBJ TextureContext : public BufferContext, public AdaptiveLruPage {
 public:
 public:
-  INLINE TextureContext(PreparedGraphicsObjects *pgo, Texture *tex);
+  INLINE TextureContext(PreparedGraphicsObjects *pgo, Texture *tex, int view);
 
 
 PUBLISHED:
 PUBLISHED:
   INLINE Texture *get_texture() const;
   INLINE Texture *get_texture() const;
+  INLINE int get_view() const;
 
 
   INLINE bool was_modified() const;
   INLINE bool was_modified() const;
   INLINE bool was_properties_modified() const;
   INLINE bool was_properties_modified() const;
@@ -62,6 +63,7 @@ private:
   // both own their TextureContexts!  That would create a circular
   // both own their TextureContexts!  That would create a circular
   // reference count.
   // reference count.
   Texture *_texture;
   Texture *_texture;
+  int _view;
   UpdateSeq _properties_modified;
   UpdateSeq _properties_modified;
   UpdateSeq _image_modified;
   UpdateSeq _image_modified;
   UpdateSeq _simple_image_modified;
   UpdateSeq _simple_image_modified;

+ 14 - 6
panda/src/gobj/texturePool.cxx

@@ -92,17 +92,17 @@ get_texture_type(const string &extension) const {
 
 
   string c = downcase(extension);
   string c = downcase(extension);
   TypeRegistry::const_iterator ti;
   TypeRegistry::const_iterator ti;
-  ti = _type_registry.find(extension);
+  ti = _type_registry.find(c);
   if (ti != _type_registry.end()) {
   if (ti != _type_registry.end()) {
     return (*ti).second;
     return (*ti).second;
   }
   }
 
 
   // Check the PNM type registry.
   // Check the PNM type registry.
   PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr();
   PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr();
-  PNMFileType *type = pnm_reg->get_type_from_extension(extension);
+  PNMFileType *type = pnm_reg->get_type_from_extension(c);
   if (type != (PNMFileType *)NULL) {
   if (type != (PNMFileType *)NULL) {
     // This is a known image type; create an ordinary Texture.
     // This is a known image type; create an ordinary Texture.
-    ((TexturePool *)this)->_type_registry[extension] = Texture::make_texture;
+    ((TexturePool *)this)->_type_registry[c] = Texture::make_texture;
     return Texture::make_texture;
     return Texture::make_texture;
   }
   }
 
 
@@ -1183,7 +1183,8 @@ try_load_cache(PT(Texture) &tex, BamCache *cache, const Filename &filename,
 void TexturePool::
 void TexturePool::
 report_texture_unreadable(const Filename &filename) const {
 report_texture_unreadable(const Filename &filename) const {
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-  if (!vfs->exists(filename)) {
+  bool has_hash = (filename.get_fullpath().find('#') != string::npos);
+  if (!has_hash && !vfs->exists(filename)) {
     if (filename.is_local()) {
     if (filename.is_local()) {
       // The file doesn't exist, and it wasn't
       // The file doesn't exist, and it wasn't
       // fully-qualified--therefore, it wasn't found along either
       // fully-qualified--therefore, it wasn't found along either
@@ -1200,8 +1201,15 @@ report_texture_unreadable(const Filename &filename) const {
 
 
   } else {
   } else {
     // The file exists, but it couldn't be read for some reason.
     // The file exists, but it couldn't be read for some reason.
-    gobj_cat.error()
-      << "Texture \"" << filename << "\" exists but cannot be read.\n";
+    if (!has_hash) {
+      gobj_cat.error()
+        << "Texture \"" << filename << "\" exists but cannot be read.\n";
+    } else {
+      // If the filename contains a hash, we'll be noncommittal about
+      // whether it exists or not.
+      gobj_cat.error()
+        << "Texture \"" << filename << "\" cannot be read.\n";
+    }
 
 
     // Maybe the filename extension is unknown.
     // Maybe the filename extension is unknown.
     MakeTextureFunc *func = get_texture_type(filename.get_extension());
     MakeTextureFunc *func = get_texture_type(filename.get_extension());

+ 28 - 0
panda/src/gobj/textureStage.I

@@ -289,6 +289,34 @@ get_saved_result() const {
   return _saved_result;
   return _saved_result;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_tex_view_offset
+//       Access: Published
+//  Description: Sets the tex_view_offset value.  This is used only
+//               when a special multiview texture is bound to the
+//               TextureStage, and it selects the particular view of
+//               the texture that is to be used.
+//
+//               This value is added to the similar parameter on
+//               DisplayRegion to derive the final texture view index
+//               that is selected for rendering.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_tex_view_offset(int tex_view_offset) {
+  _tex_view_offset = tex_view_offset;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_tex_view_offset
+//       Access: Published
+//  Description: Returns the current setting of the tex_view_offset.
+//               See set_tex_view_offset().
+////////////////////////////////////////////////////////////////////
+INLINE int TextureStage::
+get_tex_view_offset() const {
+  return _tex_view_offset;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: TextureStage::set_combine_rgb
 //     Function: TextureStage::set_combine_rgb
 //       Access: Published
 //       Access: Published

+ 12 - 2
panda/src/gobj/textureStage.cxx

@@ -38,6 +38,7 @@ TextureStage(const string &name) {
   _rgb_scale = 1;
   _rgb_scale = 1;
   _alpha_scale = 1;
   _alpha_scale = 1;
   _saved_result = false;
   _saved_result = false;
+  _tex_view_offset = 0;
   _combine_rgb_mode = CM_undefined;
   _combine_rgb_mode = CM_undefined;
   _num_combine_rgb_operands = 0;
   _num_combine_rgb_operands = 0;
   _combine_rgb_source0 = CS_undefined;
   _combine_rgb_source0 = CS_undefined;
@@ -75,6 +76,7 @@ operator = (const TextureStage &other) {
   _rgb_scale = other._rgb_scale;
   _rgb_scale = other._rgb_scale;
   _alpha_scale = other._alpha_scale;
   _alpha_scale = other._alpha_scale;
   _saved_result = other._saved_result;
   _saved_result = other._saved_result;
+  _tex_view_offset = other._tex_view_offset;
 
 
   _combine_rgb_mode = other._combine_rgb_mode;
   _combine_rgb_mode = other._combine_rgb_mode;
   _combine_rgb_source0 = other._combine_rgb_source0;
   _combine_rgb_source0 = other._combine_rgb_source0;
@@ -149,6 +151,9 @@ compare_to(const TextureStage &other) const {
   if (get_saved_result() != other.get_saved_result()) {
   if (get_saved_result() != other.get_saved_result()) {
     return get_saved_result() < other.get_saved_result() ? -1 : 1;
     return get_saved_result() < other.get_saved_result() ? -1 : 1;
   }
   }
+  if (get_tex_view_offset() != other.get_tex_view_offset()) {
+    return get_tex_view_offset() < other.get_tex_view_offset() ? -1 : 1;
+  }
   if (get_mode() != other.get_mode()) {
   if (get_mode() != other.get_mode()) {
     return get_mode() < other.get_mode() ? -1 : 1;
     return get_mode() < other.get_mode() ? -1 : 1;
   }
   }
@@ -231,7 +236,8 @@ write(ostream &out) const {
       << "  texcoords = " << get_texcoord_name()->get_name()
       << "  texcoords = " << get_texcoord_name()->get_name()
       << ", mode = " << get_mode() << ", color = " << get_color()
       << ", mode = " << get_mode() << ", color = " << get_color()
       << ", scale = " << get_rgb_scale() << ", " << get_alpha_scale()
       << ", scale = " << get_rgb_scale() << ", " << get_alpha_scale()
-      << ", saved_result = " << get_saved_result() << "\n";
+      << ", saved_result = " << get_saved_result() 
+      << ", tex_view_offset = " << get_tex_view_offset() << "\n";
 
 
   if (get_mode() == M_combine) {
   if (get_mode() == M_combine) {
     out << "  RGB combine mode =  " << get_combine_rgb_mode() << "\n";
     out << "  RGB combine mode =  " << get_combine_rgb_mode() << "\n";
@@ -400,8 +406,11 @@ fillin(DatagramIterator &scan, BamReader *manager) {
 
 
   _rgb_scale = scan.get_uint8();
   _rgb_scale = scan.get_uint8();
   _alpha_scale = scan.get_uint8();
   _alpha_scale = scan.get_uint8();
-  _saved_result = false;
   _saved_result = scan.get_bool();
   _saved_result = scan.get_bool();
+  _tex_view_offset = 0;
+  if (manager->get_file_minor_ver() >= 26) {
+    _tex_view_offset = scan.get_int32();
+  }
 
 
   _combine_rgb_mode = (TextureStage::CombineMode) scan.get_uint8();
   _combine_rgb_mode = (TextureStage::CombineMode) scan.get_uint8();
   _num_combine_rgb_operands = scan.get_uint8();
   _num_combine_rgb_operands = scan.get_uint8();
@@ -464,6 +473,7 @@ write_datagram(BamWriter *manager, Datagram &me) {
     me.add_uint8(_rgb_scale);
     me.add_uint8(_rgb_scale);
     me.add_uint8(_alpha_scale);
     me.add_uint8(_alpha_scale);
     me.add_bool(_saved_result);
     me.add_bool(_saved_result);
+    me.add_int32(_tex_view_offset);
     
     
     me.add_uint8(_combine_rgb_mode);
     me.add_uint8(_combine_rgb_mode);
     me.add_uint8(_num_combine_rgb_operands);
     me.add_uint8(_num_combine_rgb_operands);

+ 4 - 0
panda/src/gobj/textureStage.h

@@ -130,6 +130,9 @@ PUBLISHED:
   INLINE void set_saved_result(bool saved_result);
   INLINE void set_saved_result(bool saved_result);
   INLINE bool get_saved_result() const;
   INLINE bool get_saved_result() const;
 
 
+  INLINE void set_tex_view_offset(int tex_view_offset);
+  INLINE int get_tex_view_offset() const;
+
   INLINE void set_combine_rgb(CombineMode mode, 
   INLINE void set_combine_rgb(CombineMode mode, 
                               CombineSource source0, CombineOperand operand0);
                               CombineSource source0, CombineOperand operand0);
   INLINE void set_combine_rgb(CombineMode mode, 
   INLINE void set_combine_rgb(CombineMode mode, 
@@ -201,6 +204,7 @@ private:
   int _rgb_scale;
   int _rgb_scale;
   int _alpha_scale;
   int _alpha_scale;
   bool _saved_result;
   bool _saved_result;
+  int _tex_view_offset;
   bool _involves_color_scale;
   bool _involves_color_scale;
   bool _uses_color;
   bool _uses_color;
   bool _uses_primary_color;
   bool _uses_primary_color;

+ 64 - 17
panda/src/grutil/ffmpegTexture.cxx

@@ -57,7 +57,8 @@ FFMpegTexture(const FFMpegTexture &copy) :
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: FFMpegTexture::Destructor
 //     Function: FFMpegTexture::Destructor
 //       Access: Published, Virtual
 //       Access: Published, Virtual
-//  Description: I'm betting that texture takes care of the, so we'll just do a clear.
+//  Description: I'm betting that texture takes care of the, so we'll
+//               just do a clear.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 FFMpegTexture::
 FFMpegTexture::
 ~FFMpegTexture() {
 ~FFMpegTexture() {
@@ -93,7 +94,13 @@ do_make_copy() {
 void FFMpegTexture::
 void FFMpegTexture::
 do_assign(const FFMpegTexture &copy) {
 do_assign(const FFMpegTexture &copy) {
   VideoTexture::do_assign(copy);
   VideoTexture::do_assign(copy);
+
   _pages = copy._pages;
   _pages = copy._pages;
+  Pages::iterator pi;
+  for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
+    VideoPage *page = (*pi);
+    (*pi) = new VideoPage(*page);
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -101,16 +108,16 @@ do_assign(const FFMpegTexture &copy) {
 //       Access: Private
 //       Access: Private
 //  Description: Returns a reference to the zth VideoPage (level) of
 //  Description: Returns a reference to the zth VideoPage (level) of
 //               the texture.  In the case of a 2-d texture, there is
 //               the texture.  In the case of a 2-d texture, there is
-//               only one page, level 0; but cube maps and 3-d
-//               textures have more.
+//               only one page, level 0; but cube maps, 3-d
+//               textures, and multiview textures have more.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 FFMpegTexture::VideoPage &FFMpegTexture::
 FFMpegTexture::VideoPage &FFMpegTexture::
 modify_page(int z) {
 modify_page(int z) {
-  nassertr(z < _z_size, _pages[0]);
+  nassertr(z < get_num_pages(), *_pages[0]);
   while (z >= (int)_pages.size()) {
   while (z >= (int)_pages.size()) {
-    _pages.push_back(VideoPage());
+    _pages.push_back(new VideoPage());
   }
   }
-  return _pages[z];
+  return *_pages[z];
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -207,11 +214,15 @@ make_texture() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void FFMpegTexture::
 void FFMpegTexture::
 update_frame(int frame) {
 update_frame(int frame) {
-  int max_z = min(_z_size, (int)_pages.size());
+  int max_z = min(get_num_pages(), (int)_pages.size());
+  size_t page_size = do_get_expected_ram_page_size();
   for (int z = 0; z < max_z; ++z) {
   for (int z = 0; z < max_z; ++z) {
-    VideoPage &page = _pages.at(z);
+    VideoPage &page = *(_pages.at(z));
     if (page._color.is_valid() || page._alpha.is_valid()) {
     if (page._color.is_valid() || page._alpha.is_valid()) {
       do_modify_ram_image();
       do_modify_ram_image();
+      if (_ram_images[0]._image.size() != do_get_expected_ram_image_size()) {
+        do_make_ram_image();
+      }
     }
     }
     if (page._color.is_valid()) {
     if (page._color.is_valid()) {
       nassertv(_num_components >= 3 && _component_width == 1);
       nassertv(_num_components >= 3 && _component_width == 1);
@@ -223,7 +234,8 @@ update_frame(int frame) {
       // that I don't convert, even if the IO formats are the same!)  
       // that I don't convert, even if the IO formats are the same!)  
       if (page._color.get_frame_data(frame)) {
       if (page._color.get_frame_data(frame)) {
         nassertv(get_video_width() <= _x_size && get_video_height() <= _y_size);
         nassertv(get_video_width() <= _x_size && get_video_height() <= _y_size);
-        unsigned char *dest = _ram_images[0]._image.p() + do_get_expected_ram_page_size() * z;
+        unsigned char *dest = _ram_images[0]._image.p() + page_size * z;
+        nassertv(dest + page_size <= _ram_images[0]._image.p() + _ram_images[0]._image.size());
         int dest_row_width = (_x_size * _num_components * _component_width);
         int dest_row_width = (_x_size * _num_components * _component_width);
         
         
         // Simplest case, where we deal with an rgb texture
         // Simplest case, where we deal with an rgb texture
@@ -285,7 +297,8 @@ update_frame(int frame) {
         
         
         // Currently, we assume the alpha has been converted to an rgb format
         // Currently, we assume the alpha has been converted to an rgb format
         // There is no reason it can't be a 256 color grayscale though.
         // There is no reason it can't be a 256 color grayscale though.
-        unsigned char *dest = _ram_images[0]._image.p() + do_get_expected_ram_page_size() * z;
+        unsigned char *dest = _ram_images[0]._image.p() + page_size * z;
+        nassertv(dest + page_size <= _ram_images[0]._image.p() + _ram_images[0]._image.size());
         int dest_row_width = (_x_size * _num_components * _component_width);
         int dest_row_width = (_x_size * _num_components * _component_width);
         
         
         int source_row_width= page._alpha._codec_context->width * 3;
         int source_row_width= page._alpha._codec_context->width * 3;
@@ -344,7 +357,10 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
   }
   }
 
 
   nassertr(n == 0, false);
   nassertr(n == 0, false);
-  nassertr(z >= 0 && z < get_z_size(), false);
+  if (!do_reconsider_z_size(z, options)) {
+    return false;
+  }
+  nassertr(z >= 0 && z < _z_size * _num_views, false);
 
 
   VideoPage &page = modify_page(z);
   VideoPage &page = modify_page(z);
   if (!page._color.read(fullpath)) {
   if (!page._color.read(fullpath)) {
@@ -407,10 +423,9 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
         page._alpha.clear();
         page._alpha.clear();
         return false;
         return false;
       }
       }
-      
     }
     }
-    
   }
   }
+
   set_loaded_from_image();
   set_loaded_from_image();
   clear_current_frame();
   clear_current_frame();
   update_frame(0);
   update_frame(0);
@@ -435,6 +450,38 @@ do_load_one(const PNMImage &pnmimage, const string &name, int z, int n,
   return Texture::do_load_one(pnmimage, name, z, n, options);
   return Texture::do_load_one(pnmimage, name, z, n, options);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: FFMpegTexture::do_allocate_pages
+//       Access: Protected, Virtual
+//  Description: Called internally by do_reconsider_z_size() to
+//               allocate new memory in _ram_images[0] for the new
+//               number of pages.
+//
+//               Assumes the lock is already held.
+////////////////////////////////////////////////////////////////////
+void FFMpegTexture::
+do_allocate_pages() {
+  // We don't actually do anything here; the allocation is made in
+  // do_load_one(), above.
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FFMpegTexture::do_clear
+//       Access: Protected, Virtual
+//  Description: The protected implementation of clear().  Assumes the
+//               lock is already held.
+////////////////////////////////////////////////////////////////////
+void FFMpegTexture::
+do_clear() {
+  Texture::do_clear();
+
+  Pages::iterator pi;
+  for (pi = _pages.begin(); pi != _pages.end(); ++pi) {
+    delete (*pi);
+  }
+  _pages.clear();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: FFMpegTexture::do_has_bam_rawdata
 //     Function: FFMpegTexture::do_has_bam_rawdata
 //       Access: Protected, Virtual
 //       Access: Protected, Virtual
@@ -493,7 +540,7 @@ void FFMpegTexture::
 do_write_datagram_rawdata(BamWriter *manager, Datagram &me) {
 do_write_datagram_rawdata(BamWriter *manager, Datagram &me) {
   me.add_uint16(_pages.size());
   me.add_uint16(_pages.size());
   for (size_t n = 0; n < _pages.size(); ++n) {
   for (size_t n = 0; n < _pages.size(); ++n) {
-    VideoPage &page = _pages[n];
+    VideoPage &page = (*_pages[n]);
     page.write_datagram_rawdata(manager, me);
     page.write_datagram_rawdata(manager, me);
   }
   }
 }
 }
@@ -509,8 +556,8 @@ do_fillin_rawdata(DatagramIterator &scan, BamReader *manager) {
   int num_pages = scan.get_uint16();
   int num_pages = scan.get_uint16();
   _pages.clear();
   _pages.clear();
   for (int n = 0; n < num_pages; ++n) {
   for (int n = 0; n < num_pages; ++n) {
-    _pages.push_back(VideoPage());
-    VideoPage &page = _pages.back();
+    _pages.push_back(new VideoPage());
+    VideoPage &page = *(_pages.back());
     page.fillin_rawdata(scan, manager);
     page.fillin_rawdata(scan, manager);
 
 
     LoaderOptions options;
     LoaderOptions options;
@@ -1041,5 +1088,5 @@ fillin_rawdata(DatagramIterator &scan, BamReader *manager) {
 }
 }
 
 
 
 
-#endif  // HAVE_FFMpeg
+#endif  // HAVE_FFMPEG
 
 

+ 3 - 1
panda/src/grutil/ffmpegTexture.h

@@ -57,6 +57,8 @@ protected:
                            bool header_only, BamCacheRecord *record);
                            bool header_only, BamCacheRecord *record);
   virtual bool do_load_one(const PNMImage &pnmimage, const string &name,
   virtual bool do_load_one(const PNMImage &pnmimage, const string &name,
                            int z, int n, const LoaderOptions &options);
                            int z, int n, const LoaderOptions &options);
+  virtual void do_allocate_pages();
+  virtual void do_clear();
 
 
   virtual bool do_has_bam_rawdata() const;
   virtual bool do_has_bam_rawdata() const;
   virtual void do_get_bam_rawdata();
   virtual void do_get_bam_rawdata();
@@ -125,7 +127,7 @@ private:
     VideoStream _color, _alpha;
     VideoStream _color, _alpha;
   };
   };
 
 
-  typedef pvector<VideoPage> Pages;
+  typedef pvector<VideoPage *> Pages;
   Pages _pages;
   Pages _pages;
 
 
 public:
 public:

+ 30 - 9
panda/src/grutil/movieTexture.cxx

@@ -151,8 +151,7 @@ do_recalculate_image_properties(CDWriter &cdata, const LoaderOptions &options) {
   bool alpha = false;
   bool alpha = false;
   double len = 0.0;
   double len = 0.0;
 
 
-  nassertv(cdata->_pages.size() == (size_t)_z_size);
-  for (int i = 0; i < _z_size; ++i) {
+  for (size_t i = 0; i < cdata->_pages.size(); ++i) {
     MovieVideoCursor *t = cdata->_pages[i]._color;
     MovieVideoCursor *t = cdata->_pages[i]._color;
     if (t) {
     if (t) {
       if (t->size_x() > x_max) x_max = t->size_x();
       if (t->size_x() > x_max) x_max = t->size_x();
@@ -221,9 +220,11 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
             int z, int n, int primary_file_num_channels, int alpha_file_channel,
             int z, int n, int primary_file_num_channels, int alpha_file_channel,
             const LoaderOptions &options,
             const LoaderOptions &options,
             bool header_only, BamCacheRecord *record) {
             bool header_only, BamCacheRecord *record) {
-
   nassertr(n == 0, false);
   nassertr(n == 0, false);
-  nassertr(z >= 0 && z < _z_size, false);
+  if (!do_reconsider_z_size(z, options)) {
+    return false;
+  }
+  nassertr(z >= 0 && z < _z_size * _num_views, false);
   
   
   if (record != (BamCacheRecord *)NULL) {
   if (record != (BamCacheRecord *)NULL) {
     record->add_dependent_file(fullpath);
     record->add_dependent_file(fullpath);
@@ -278,10 +279,9 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
 bool MovieTexture::
 bool MovieTexture::
 do_load_one(PT(MovieVideoCursor) color, PT(MovieVideoCursor) alpha, int z,
 do_load_one(PT(MovieVideoCursor) color, PT(MovieVideoCursor) alpha, int z,
             const LoaderOptions &options) {
             const LoaderOptions &options) {
-  
   {
   {
     CDWriter cdata(_cycler);
     CDWriter cdata(_cycler);
-    cdata->_pages.resize(z+1);
+    cdata->_pages.resize(z + 1);
     cdata->_pages[z]._color = color;
     cdata->_pages[z]._color = color;
     cdata->_pages[z]._alpha = alpha;
     cdata->_pages[z]._alpha = alpha;
     do_recalculate_image_properties(cdata, options);
     do_recalculate_image_properties(cdata, options);
@@ -303,6 +303,21 @@ do_load_one(const PNMImage &pnmimage, const string &name, int z, int n,
   return false;
   return false;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: MovieTexture::do_allocate_pages
+//       Access: Protected, Virtual
+//  Description: Called internally by do_reconsider_z_size() to
+//               allocate new memory in _ram_images[0] for the new
+//               number of pages.
+//
+//               Assumes the lock is already held.
+////////////////////////////////////////////////////////////////////
+void MovieTexture::
+do_allocate_pages() {
+  // We don't actually do anything here; the allocation is made in
+  // do_load_one(), above.
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: MovieTexture::has_cull_callback
 //     Function: MovieTexture::has_cull_callback
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -737,7 +752,9 @@ void MovieTexture::
 do_write_datagram_rawdata(BamWriter *manager, Datagram &dg) {
 do_write_datagram_rawdata(BamWriter *manager, Datagram &dg) {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
 
 
-  dg.add_uint16(cdata->_pages.size());
+  dg.add_uint16(_z_size);
+  dg.add_uint16(_num_views);
+  nassertv(cdata->_pages.size() == (size_t)(_z_size * _num_views));
   for (size_t n = 0; n < cdata->_pages.size(); ++n) {
   for (size_t n = 0; n < cdata->_pages.size(); ++n) {
     const VideoPage &page = cdata->_pages[n];
     const VideoPage &page = cdata->_pages[n];
     manager->write_pointer(dg, page._color);
     manager->write_pointer(dg, page._color);
@@ -755,9 +772,13 @@ void MovieTexture::
 do_fillin_rawdata(DatagramIterator &scan, BamReader *manager) {
 do_fillin_rawdata(DatagramIterator &scan, BamReader *manager) {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
 
 
-  size_t num_pages = scan.get_uint16();
-  _z_size = (int)num_pages;
+  _z_size = scan.get_uint16();
+  _num_views = 1;
+  if (manager->get_file_minor_ver() >= 26) {
+    _num_views = scan.get_uint16();
+  }
 
 
+  size_t num_pages = (size_t)(_z_size * _num_views);
   cdata->_pages.reserve(num_pages);
   cdata->_pages.reserve(num_pages);
   for (size_t n = 0; n < num_pages; ++n) {
   for (size_t n = 0; n < num_pages; ++n) {
     cdata->_pages.push_back(VideoPage());
     cdata->_pages.push_back(VideoPage());

+ 1 - 0
panda/src/grutil/movieTexture.h

@@ -85,6 +85,7 @@ protected:
                            int z, int n, const LoaderOptions &options);
                            int z, int n, const LoaderOptions &options);
   bool do_load_one(PT(MovieVideoCursor) color, PT(MovieVideoCursor) alpha, 
   bool do_load_one(PT(MovieVideoCursor) color, PT(MovieVideoCursor) alpha, 
                    int z, const LoaderOptions &options);
                    int z, const LoaderOptions &options);
+  virtual void do_allocate_pages();
 
 
   class VideoPage {
   class VideoPage {
   public:
   public:

+ 1 - 1
panda/src/grutil/pipeOcclusionCullTraverser.cxx

@@ -162,7 +162,7 @@ set_scene(SceneSetup *scene_setup, GraphicsStateGuardianBase *gsgbase,
   DisplayRegionPipelineReader dr_reader(_display_region, current_thread);
   DisplayRegionPipelineReader dr_reader(_display_region, current_thread);
 
 
   _buffer->change_scenes(&dr_reader);
   _buffer->change_scenes(&dr_reader);
-  gsg->prepare_display_region(&dr_reader, dr_reader.get_stereo_channel());
+  gsg->prepare_display_region(&dr_reader);
 
 
   _scene = new SceneSetup(*scene_setup);
   _scene = new SceneSetup(*scene_setup);
   _scene->set_display_region(_display_region);
   _scene->set_display_region(_display_region);

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

@@ -142,7 +142,7 @@ public:
   virtual PreparedGraphicsObjects *get_prepared_objects()=0;
   virtual PreparedGraphicsObjects *get_prepared_objects()=0;
 #endif
 #endif
 
 
-  virtual TextureContext *prepare_texture(Texture *tex)=0;
+  virtual TextureContext *prepare_texture(Texture *tex, int view)=0;
   virtual bool update_texture(TextureContext *tc, bool force)=0;
   virtual bool update_texture(TextureContext *tc, bool force)=0;
   virtual void release_texture(TextureContext *tc)=0;
   virtual void release_texture(TextureContext *tc)=0;
   virtual bool extract_texture_data(Texture *tex)=0;
   virtual bool extract_texture_data(Texture *tex)=0;

+ 15 - 6
panda/src/movies/ffmpegVideoCursor.cxx

@@ -285,9 +285,10 @@ fetch_packet(double default_time) {
 //       Access: Protected
 //       Access: Protected
 //  Description: Fetches a frame from the stream and stores it in
 //  Description: Fetches a frame from the stream and stores it in
 //               the frame buffer.  Sets last_start and next_start
 //               the frame buffer.  Sets last_start and next_start
-//               to indicate the extents of the frame.
+//               to indicate the extents of the frame.  Returns true
+//               if the end of the video is reached.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-void FfmpegVideoCursor::
+bool FfmpegVideoCursor::
 fetch_frame() {
 fetch_frame() {
   int finished = 0;
   int finished = 0;
   _last_start = _packet_time;
   _last_start = _packet_time;
@@ -301,6 +302,8 @@ fetch_frame() {
     fetch_packet(_last_start + 1.0);
     fetch_packet(_last_start + 1.0);
   }
   }
   _next_start = _packet_time;
   _next_start = _packet_time;
+
+  return (finished != 0);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -354,14 +357,18 @@ fetch_time(double time) {
     // Time is in the past.
     // Time is in the past.
     seek(time);
     seek(time);
     while (_packet_time <= time) {
     while (_packet_time <= time) {
-      fetch_frame();
+      if (fetch_frame()) {
+        break;
+      }
     }
     }
   } else if (time < _next_start) {
   } else if (time < _next_start) {
     // Time is in the present: already have the frame.
     // Time is in the present: already have the frame.
   } else if (time < _next_start + _min_fseek) {
   } else if (time < _next_start + _min_fseek) {
     // Time is in the near future.
     // Time is in the near future.
     while ((_packet_time <= time) && (_packet->data)) {
     while ((_packet_time <= time) && (_packet->data)) {
-      fetch_frame();
+      if (fetch_frame()) {
+        break;
+      }
     }
     }
   } else {
   } else {
     // Time is in the far future.  Seek forward, then read.
     // Time is in the far future.  Seek forward, then read.
@@ -378,7 +385,9 @@ fetch_time(double time) {
       _min_fseek += (base - _packet_time);
       _min_fseek += (base - _packet_time);
     }
     }
     while (_packet_time <= time) {
     while (_packet_time <= time) {
-      fetch_frame();
+      if (fetch_frame()) {
+        break;
+      }
     }
     }
   }
   }
 }
 }
@@ -397,7 +406,7 @@ fetch_into_texture(double time, Texture *t, int page) {
   nassertv(t->get_y_size() >= size_y());
   nassertv(t->get_y_size() >= size_y());
   nassertv((t->get_num_components() == 3) || (t->get_num_components() == 4));
   nassertv((t->get_num_components() == 3) || (t->get_num_components() == 4));
   nassertv(t->get_component_width() == 1);
   nassertv(t->get_component_width() == 1);
-  nassertv(page < t->get_z_size());
+  nassertv(page < t->get_num_pages());
   
   
   PTA_uchar img = t->modify_ram_image();
   PTA_uchar img = t->modify_ram_image();
   
   

+ 1 - 1
panda/src/movies/ffmpegVideoCursor.h

@@ -49,7 +49,7 @@ public:
 
 
 protected:
 protected:
   void fetch_packet(double default_time);
   void fetch_packet(double default_time);
-  void fetch_frame();
+  bool fetch_frame();
   void seek(double t);
   void seek(double t);
   void fetch_time(double time);
   void fetch_time(double time);
   void export_frame(unsigned char *data, bool bgra, int bufx);
   void export_frame(unsigned char *data, bool bgra, int bufx);

+ 1 - 1
panda/src/osxdisplay/osxGraphicsWindow.mm

@@ -922,7 +922,7 @@ end_frame(FrameMode mode, Thread *current_thread) {
       // so the user knows he's supposed to be able to drag the window
       // so the user knows he's supposed to be able to drag the window
       // if he wants.
       // if he wants.
       DisplayRegionPipelineReader dr_reader(_overlay_display_region, current_thread);
       DisplayRegionPipelineReader dr_reader(_overlay_display_region, current_thread);
-      _gsg->prepare_display_region(&dr_reader, Lens::SC_mono);
+      _gsg->prepare_display_region(&dr_reader);
       DCAST(osxGraphicsStateGuardian, _gsg)->draw_resize_box();
       DCAST(osxGraphicsStateGuardian, _gsg)->draw_resize_box();
     }
     }
     
     

+ 2 - 1
panda/src/putil/bam.h

@@ -33,7 +33,7 @@ static const unsigned short _bam_major_ver = 6;
 // Bumped to major version 6 on 2/11/06 to factor out PandaNode::CData.
 // Bumped to major version 6 on 2/11/06 to factor out PandaNode::CData.
 
 
 static const unsigned short _bam_first_minor_ver = 14;
 static const unsigned short _bam_first_minor_ver = 14;
-static const unsigned short _bam_minor_ver = 25;
+static const unsigned short _bam_minor_ver = 26;
 // Bumped to minor version 14 on 12/19/07 to change default ColorAttrib.
 // Bumped to minor version 14 on 12/19/07 to change default ColorAttrib.
 // Bumped to minor version 15 on 4/9/08 to add TextureAttrib::_implicit_sort.
 // Bumped to minor version 15 on 4/9/08 to add TextureAttrib::_implicit_sort.
 // Bumped to minor version 16 on 5/13/08 to add Texture::_quality_level.
 // Bumped to minor version 16 on 5/13/08 to add Texture::_quality_level.
@@ -46,6 +46,7 @@ static const unsigned short _bam_minor_ver = 25;
 // Bumped to minor version 23 on 5/4/10 to add internal TextureAttrib overrides.
 // Bumped to minor version 23 on 5/4/10 to add internal TextureAttrib overrides.
 // Bumped to minor version 24 on 5/4/10 to add internal TexMatrixAttrib overrides.
 // Bumped to minor version 24 on 5/4/10 to add internal TexMatrixAttrib overrides.
 // Bumped to minor version 25 on 6/22/11 to add support for caching movie files.
 // Bumped to minor version 25 on 6/22/11 to add support for caching movie files.
+// Bumped to minor version 26 on 8/5/11 to add multiview (stereo) Textures.
 
 
 
 
 #endif
 #endif

+ 33 - 2
panda/src/putil/loaderOptions.I

@@ -20,7 +20,9 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE LoaderOptions::
 INLINE LoaderOptions::
 LoaderOptions(int flags, int texture_flags) : 
 LoaderOptions(int flags, int texture_flags) : 
-  _flags(flags), _texture_flags(texture_flags)
+  _flags(flags), 
+  _texture_flags(texture_flags),
+  _texture_num_views(0)
 {
 {
 }
 }
 
 
@@ -32,7 +34,8 @@ LoaderOptions(int flags, int texture_flags) :
 INLINE LoaderOptions::
 INLINE LoaderOptions::
 LoaderOptions(const LoaderOptions &copy) :
 LoaderOptions(const LoaderOptions &copy) :
   _flags(copy._flags),
   _flags(copy._flags),
-  _texture_flags(copy._texture_flags)
+  _texture_flags(copy._texture_flags),
+  _texture_num_views(copy._texture_num_views)
 {
 {
 }
 }
 
 
@@ -45,6 +48,7 @@ INLINE void LoaderOptions::
 operator = (const LoaderOptions &copy) {
 operator = (const LoaderOptions &copy) {
   _flags = copy._flags;
   _flags = copy._flags;
   _texture_flags = copy._texture_flags;
   _texture_flags = copy._texture_flags;
+  _texture_num_views = copy._texture_num_views;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -86,3 +90,30 @@ INLINE int LoaderOptions::
 get_texture_flags() const {
 get_texture_flags() const {
   return _texture_flags;
   return _texture_flags;
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderOptions::set_texture_num_views
+//       Access: Published
+//  Description: Specifies the expected number of views to load for
+//               the texture.  This is ignored unless TF_multiview is
+//               included in texture_flags.  This must be specified
+//               when loading a 3-d multiview texture, in which case
+//               it is used to differentiate z levels from separate
+//               views; it may be zero in the case of 2-d textures or
+//               cube maps, in which case the number of views can be
+//               inferred from the number of images found on disk.
+////////////////////////////////////////////////////////////////////
+INLINE void LoaderOptions::
+set_texture_num_views(int texture_num_views) {
+  _texture_num_views = texture_num_views;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderOptions::get_texture_num_views
+//       Access: Published
+//  Description: See set_texture_num_views().
+////////////////////////////////////////////////////////////////////
+INLINE int LoaderOptions::
+get_texture_num_views() const {
+  return _texture_num_views;
+}

+ 3 - 1
panda/src/putil/loaderOptions.cxx

@@ -23,7 +23,9 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 LoaderOptions::
 LoaderOptions::
 LoaderOptions(int flags) : 
 LoaderOptions(int flags) : 
-  _flags(flags), _texture_flags(0)  
+  _flags(flags), 
+  _texture_flags(0),
+  _texture_num_views(0)
 {
 {
   // Shadowing the variables in config_util for static init ordering
   // Shadowing the variables in config_util for static init ordering
   // issues.
   // issues.

+ 4 - 0
panda/src/putil/loaderOptions.h

@@ -44,6 +44,7 @@ PUBLISHED:
     TF_preload_simple    = 0x0008,  // Texture will have simple RAM image
     TF_preload_simple    = 0x0008,  // Texture will have simple RAM image
     TF_allow_1d          = 0x0010,  // If texture is Nx1, make a 1-d texture
     TF_allow_1d          = 0x0010,  // If texture is Nx1, make a 1-d texture
     TF_generate_mipmaps  = 0x0020,  // Consider generating mipmaps
     TF_generate_mipmaps  = 0x0020,  // Consider generating mipmaps
+    TF_multiview         = 0x0040,  // Load a multiview texture in pages
   };
   };
 
 
   LoaderOptions(int flags = LF_search | LF_report_errors);
   LoaderOptions(int flags = LF_search | LF_report_errors);
@@ -56,6 +57,8 @@ PUBLISHED:
 
 
   INLINE void set_texture_flags(int flags);
   INLINE void set_texture_flags(int flags);
   INLINE int get_texture_flags() const;
   INLINE int get_texture_flags() const;
+  INLINE void set_texture_num_views(int num_views);
+  INLINE int get_texture_num_views() const;
 
 
   void output(ostream &out) const;
   void output(ostream &out) const;
 
 
@@ -66,6 +69,7 @@ private:
                           const string &flag_name, int flag) const;
                           const string &flag_name, int flag) const;
   int _flags;
   int _flags;
   int _texture_flags;
   int _texture_flags;
+  int _texture_num_views;
 };
 };
 
 
 INLINE ostream &operator << (ostream &out, const LoaderOptions &opts) {
 INLINE ostream &operator << (ostream &out, const LoaderOptions &opts) {

+ 40 - 21
panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx

@@ -263,10 +263,9 @@ clear(DrawableRegion *clearable) {
 //               scissor region and viewport)
 //               scissor region and viewport)
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void TinyGraphicsStateGuardian::
 void TinyGraphicsStateGuardian::
-prepare_display_region(DisplayRegionPipelineReader *dr,
-                       Lens::StereoChannel stereo_channel) {
+prepare_display_region(DisplayRegionPipelineReader *dr) {
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
-  GraphicsStateGuardian::prepare_display_region(dr, stereo_channel);
+  GraphicsStateGuardian::prepare_display_region(dr);
 
 
   int xmin, ymin, xsize, ysize;
   int xmin, ymin, xsize, ysize;
   dr->get_region_pixels_i(xmin, ymin, xsize, ysize);
   dr->get_region_pixels_i(xmin, ymin, xsize, ysize);
@@ -1361,7 +1360,8 @@ framebuffer_copy_to_texture(Texture *tex, int z, const DisplayRegion *dr,
 
 
   tex->setup_2d_texture(w, h, Texture::T_unsigned_byte, Texture::F_rgba);
   tex->setup_2d_texture(w, h, Texture::T_unsigned_byte, Texture::F_rgba);
 
 
-  TextureContext *tc = tex->prepare_now(get_prepared_objects(), this);
+  int view = dr->get_tex_view_offset();
+  TextureContext *tc = tex->prepare_now(view, get_prepared_objects(), this);
   nassertr(tc != (TextureContext *)NULL, false);
   nassertr(tc != (TextureContext *)NULL, false);
   TinyTextureContext *gtc = DCAST(TinyTextureContext, tc);
   TinyTextureContext *gtc = DCAST(TinyTextureContext, tc);
 
 
@@ -1610,7 +1610,7 @@ set_state_and_transform(const RenderState *target,
 //               prepare a texture.  Instead, call Texture::prepare().
 //               prepare a texture.  Instead, call Texture::prepare().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 TextureContext *TinyGraphicsStateGuardian::
 TextureContext *TinyGraphicsStateGuardian::
-prepare_texture(Texture *tex) {
+prepare_texture(Texture *tex, int view) {
   switch (tex->get_texture_type()) {
   switch (tex->get_texture_type()) {
   case Texture::TT_1d_texture:
   case Texture::TT_1d_texture:
   case Texture::TT_2d_texture:
   case Texture::TT_2d_texture:
@@ -1637,7 +1637,7 @@ prepare_texture(Texture *tex) {
   }
   }
   */
   */
 
 
-  TinyTextureContext *gtc = new TinyTextureContext(_prepared_objects, tex);
+  TinyTextureContext *gtc = new TinyTextureContext(_prepared_objects, tex, view);
 
 
   return gtc;
   return gtc;
 }
 }
@@ -2209,7 +2209,8 @@ do_issue_texture() {
     Texture *texture = _target_texture->get_on_texture(stage);
     Texture *texture = _target_texture->get_on_texture(stage);
     nassertv(texture != (Texture *)NULL);
     nassertv(texture != (Texture *)NULL);
     
     
-    TextureContext *tc = texture->prepare_now(_prepared_objects, this);
+    int view = get_current_tex_view_offset() + stage->get_tex_view_offset();
+    TextureContext *tc = texture->prepare_now(view, _prepared_objects, this);
     if (tc == (TextureContext *)NULL) {
     if (tc == (TextureContext *)NULL) {
       // Something wrong with this texture; skip it.
       // Something wrong with this texture; skip it.
       return;
       return;
@@ -2504,7 +2505,7 @@ upload_texture(TinyTextureContext *gtc, bool force) {
     case Texture::F_rgb8:
     case Texture::F_rgb8:
     case Texture::F_rgb12:
     case Texture::F_rgb12:
     case Texture::F_rgb332:
     case Texture::F_rgb332:
-      copy_rgb_image(dest, xsize, ysize, tex, level);
+      copy_rgb_image(dest, xsize, ysize, gtc, level);
       break;
       break;
 
 
     case Texture::F_rgba:
     case Texture::F_rgba:
@@ -2515,32 +2516,32 @@ upload_texture(TinyTextureContext *gtc, bool force) {
     case Texture::F_rgba12:
     case Texture::F_rgba12:
     case Texture::F_rgba16:
     case Texture::F_rgba16:
     case Texture::F_rgba32:
     case Texture::F_rgba32:
-      copy_rgba_image(dest, xsize, ysize, tex, level);
+      copy_rgba_image(dest, xsize, ysize, gtc, level);
       break;
       break;
 
 
     case Texture::F_luminance:
     case Texture::F_luminance:
-      copy_lum_image(dest, xsize, ysize, tex, level);
+      copy_lum_image(dest, xsize, ysize, gtc, level);
       break;
       break;
 
 
     case Texture::F_red:
     case Texture::F_red:
-      copy_one_channel_image(dest, xsize, ysize, tex, level, 0);
+      copy_one_channel_image(dest, xsize, ysize, gtc, level, 0);
       break;
       break;
 
 
     case Texture::F_green:
     case Texture::F_green:
-      copy_one_channel_image(dest, xsize, ysize, tex, level, 1);
+      copy_one_channel_image(dest, xsize, ysize, gtc, level, 1);
       break;
       break;
 
 
     case Texture::F_blue:
     case Texture::F_blue:
-      copy_one_channel_image(dest, xsize, ysize, tex, level, 2);
+      copy_one_channel_image(dest, xsize, ysize, gtc, level, 2);
       break;
       break;
 
 
     case Texture::F_alpha:
     case Texture::F_alpha:
-      copy_alpha_image(dest, xsize, ysize, tex, level);
+      copy_alpha_image(dest, xsize, ysize, gtc, level);
       break;
       break;
 
 
     case Texture::F_luminance_alphamask:
     case Texture::F_luminance_alphamask:
     case Texture::F_luminance_alpha:
     case Texture::F_luminance_alpha:
-      copy_la_image(dest, xsize, ysize, tex, level);
+      copy_la_image(dest, xsize, ysize, gtc, level);
       break;
       break;
     }
     }
 
 
@@ -2727,7 +2728,8 @@ get_tex_shift(int orig_size) {
 //               from the texture into the indicated ZTexture pixmap.
 //               from the texture into the indicated ZTexture pixmap.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void TinyGraphicsStateGuardian::
 void TinyGraphicsStateGuardian::
-copy_lum_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level) {
+copy_lum_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level) {
+  Texture *tex = gtc->get_texture();
   nassertv(tex->get_num_components() == 1);
   nassertv(tex->get_num_components() == 1);
   nassertv(tex->get_expected_mipmap_x_size(level) == xsize &&
   nassertv(tex->get_expected_mipmap_x_size(level) == xsize &&
            tex->get_expected_mipmap_y_size(level) == ysize);
            tex->get_expected_mipmap_y_size(level) == ysize);
@@ -2735,6 +2737,8 @@ copy_lum_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int leve
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   nassertv(!src_image.is_null());
   const unsigned char *src = src_image.p();
   const unsigned char *src = src_image.p();
+  size_t view_size = tex->get_ram_mipmap_view_size(level);
+  src += view_size * gtc->get_view();
 
 
   // Component width, and offset to the high-order byte.
   // Component width, and offset to the high-order byte.
   int cw = tex->get_component_width();
   int cw = tex->get_component_width();
@@ -2764,12 +2768,15 @@ copy_lum_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int leve
 //               from the texture into the indicated ZTexture pixmap.
 //               from the texture into the indicated ZTexture pixmap.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void TinyGraphicsStateGuardian::
 void TinyGraphicsStateGuardian::
-copy_alpha_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level) {
+copy_alpha_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level) {
+  Texture *tex = gtc->get_texture();
   nassertv(tex->get_num_components() == 1);
   nassertv(tex->get_num_components() == 1);
 
 
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   nassertv(!src_image.is_null());
   const unsigned char *src = src_image.p();
   const unsigned char *src = src_image.p();
+  size_t view_size = tex->get_ram_mipmap_view_size(level);
+  src += view_size * gtc->get_view();
 
 
   // Component width, and offset to the high-order byte.
   // Component width, and offset to the high-order byte.
   int cw = tex->get_component_width();
   int cw = tex->get_component_width();
@@ -2800,12 +2807,15 @@ copy_alpha_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int le
 //               the texture into the indicated ZTexture pixmap.
 //               the texture into the indicated ZTexture pixmap.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void TinyGraphicsStateGuardian::
 void TinyGraphicsStateGuardian::
-copy_one_channel_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level, int channel) {
+copy_one_channel_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level, int channel) {
+  Texture *tex = gtc->get_texture();
   nassertv(tex->get_num_components() == 1);
   nassertv(tex->get_num_components() == 1);
 
 
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   nassertv(!src_image.is_null());
   const unsigned char *src = src_image.p();
   const unsigned char *src = src_image.p();
+  size_t view_size = tex->get_ram_mipmap_view_size(level);
+  src += view_size * gtc->get_view();
 
 
   // Component width, and offset to the high-order byte.
   // Component width, and offset to the high-order byte.
   int cw = tex->get_component_width();
   int cw = tex->get_component_width();
@@ -2865,12 +2875,15 @@ copy_one_channel_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex,
 //               pixmap.
 //               pixmap.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void TinyGraphicsStateGuardian::
 void TinyGraphicsStateGuardian::
-copy_la_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level) {
+copy_la_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level) {
+  Texture *tex = gtc->get_texture();
   nassertv(tex->get_num_components() == 2);
   nassertv(tex->get_num_components() == 2);
 
 
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   nassertv(!src_image.is_null());
   const unsigned char *src = src_image.p();
   const unsigned char *src = src_image.p();
+  size_t view_size = tex->get_ram_mipmap_view_size(level);
+  src += view_size * gtc->get_view();
 
 
   // Component width, and offset to the high-order byte.
   // Component width, and offset to the high-order byte.
   int cw = tex->get_component_width();
   int cw = tex->get_component_width();
@@ -2901,12 +2914,15 @@ copy_la_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level
 //               the texture into the indicated ZTexture pixmap.
 //               the texture into the indicated ZTexture pixmap.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void TinyGraphicsStateGuardian::
 void TinyGraphicsStateGuardian::
-copy_rgb_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level) {
+copy_rgb_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level) {
+  Texture *tex = gtc->get_texture();
   nassertv(tex->get_num_components() == 3);
   nassertv(tex->get_num_components() == 3);
 
 
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   nassertv(!src_image.is_null());
   const unsigned char *src = src_image.p();
   const unsigned char *src = src_image.p();
+  size_t view_size = tex->get_ram_mipmap_view_size(level);
+  src += view_size * gtc->get_view();
 
 
   // Component width, and offset to the high-order byte.
   // Component width, and offset to the high-order byte.
   int cw = tex->get_component_width();
   int cw = tex->get_component_width();
@@ -2937,12 +2953,15 @@ copy_rgb_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int leve
 //               the texture into the indicated ZTexture pixmap.
 //               the texture into the indicated ZTexture pixmap.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void TinyGraphicsStateGuardian::
 void TinyGraphicsStateGuardian::
-copy_rgba_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level) {
+copy_rgba_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level) {
+  Texture *tex = gtc->get_texture();
   nassertv(tex->get_num_components() == 4);
   nassertv(tex->get_num_components() == 4);
 
 
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   nassertv(!src_image.is_null());
   const unsigned char *src = src_image.p();
   const unsigned char *src = src_image.p();
+  size_t view_size = tex->get_ram_mipmap_view_size(level);
+  src += view_size * gtc->get_view();
 
 
   // Component width, and offset to the high-order byte.
   // Component width, and offset to the high-order byte.
   int cw = tex->get_component_width();
   int cw = tex->get_component_width();

+ 8 - 9
panda/src/tinydisplay/tinyGraphicsStateGuardian.h

@@ -58,8 +58,7 @@ public:
 
 
   virtual void clear(DrawableRegion *clearable);
   virtual void clear(DrawableRegion *clearable);
 
 
-  virtual void prepare_display_region(DisplayRegionPipelineReader *dr,
-                                      Lens::StereoChannel stereo_channel);
+  virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
   virtual CPT(TransformState) calc_projection_mat(const Lens *lens);
   virtual CPT(TransformState) calc_projection_mat(const Lens *lens);
   virtual bool prepare_lens();
   virtual bool prepare_lens();
 
 
@@ -90,7 +89,7 @@ public:
   virtual void set_state_and_transform(const RenderState *state,
   virtual void set_state_and_transform(const RenderState *state,
                                        const TransformState *transform);
                                        const TransformState *transform);
 
 
-  virtual TextureContext *prepare_texture(Texture *tex);
+  virtual TextureContext *prepare_texture(Texture *tex, int view);
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual bool update_texture(TextureContext *tc, bool force);
   bool update_texture(TextureContext *tc, bool force, int stage_index);
   bool update_texture(TextureContext *tc, bool force, int stage_index);
   virtual void release_texture(TextureContext *tc);
   virtual void release_texture(TextureContext *tc);
@@ -121,12 +120,12 @@ private:
   bool setup_gltex(GLTexture *gltex, int x_size, int y_size, int num_levels);
   bool setup_gltex(GLTexture *gltex, int x_size, int y_size, int num_levels);
   int get_tex_shift(int orig_size);
   int get_tex_shift(int orig_size);
 
 
-  static void copy_lum_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level);
-  static void copy_alpha_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level);
-  static void copy_one_channel_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level, int channel);
-  static void copy_la_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level);
-  static void copy_rgb_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level);
-  static void copy_rgba_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex, int level);
+  static void copy_lum_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level);
+  static void copy_alpha_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level);
+  static void copy_one_channel_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level, int channel);
+  static void copy_la_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level);
+  static void copy_rgb_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level);
+  static void copy_rgba_image(ZTextureLevel *dest, int xsize, int ysize, TinyTextureContext *gtc, int level);
 
 
   void setup_material(GLMaterial *gl_material, const Material *material);
   void setup_material(GLMaterial *gl_material, const Material *material);
   void do_auto_rescale_normal();
   void do_auto_rescale_normal();

+ 2 - 2
panda/src/tinydisplay/tinyTextureContext.I

@@ -19,8 +19,8 @@
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE TinyTextureContext::
 INLINE TinyTextureContext::
-TinyTextureContext(PreparedGraphicsObjects *pgo, Texture *tex) :
-  TextureContext(pgo, tex)
+TinyTextureContext(PreparedGraphicsObjects *pgo, Texture *tex, int view) :
+  TextureContext(pgo, tex, view)
 {
 {
   _gltex.num_levels = 0;
   _gltex.num_levels = 0;
   _gltex.allocated_buffer = NULL;
   _gltex.allocated_buffer = NULL;

+ 1 - 1
panda/src/tinydisplay/tinyTextureContext.h

@@ -26,7 +26,7 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class EXPCL_TINYDISPLAY TinyTextureContext : public TextureContext {
 class EXPCL_TINYDISPLAY TinyTextureContext : public TextureContext {
 public:
 public:
-  INLINE TinyTextureContext(PreparedGraphicsObjects *pgo, Texture *tex);
+  INLINE TinyTextureContext(PreparedGraphicsObjects *pgo, Texture *tex, int view);
   ALLOC_DELETED_CHAIN(TinyTextureContext);
   ALLOC_DELETED_CHAIN(TinyTextureContext);
 
 
   INLINE ~TinyTextureContext();
   INLINE ~TinyTextureContext();

+ 1 - 1
panda/src/vision/openCVTexture.cxx

@@ -134,7 +134,7 @@ do_assign(const OpenCVTexture &copy) {
 bool OpenCVTexture::
 bool OpenCVTexture::
 from_camera(int camera_index, int z, int alpha_file_channel,
 from_camera(int camera_index, int z, int alpha_file_channel,
             const LoaderOptions &options) {
             const LoaderOptions &options) {
-  if (!do_reconsider_z_size(z)) {
+  if (!do_reconsider_z_size(z, options)) {
     return false;
     return false;
   }
   }
   nassertr(z >= 0 && z < get_z_size(), false);
   nassertr(z >= 0 && z < get_z_size(), false);

+ 1 - 1
panda/src/wgldisplay/wglGraphicsBuffer.cxx

@@ -169,7 +169,7 @@ bind_texture_to_pbuffer() {
         tex->set_format(Texture::F_rgb);
         tex->set_format(Texture::F_rgb);
       }
       }
     }
     }
-    TextureContext *tc = tex->prepare_now(_gsg->get_prepared_objects(), _gsg);
+    TextureContext *tc = tex->prepare_now(0, _gsg->get_prepared_objects(), _gsg);
     nassertv(tc != (TextureContext *)NULL);
     nassertv(tc != (TextureContext *)NULL);
     CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
     CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
     GLenum target = wglgsg->get_texture_target(tex->get_texture_type());
     GLenum target = wglgsg->get_texture_target(tex->get_texture_type());