Forráskód Böngészése

stereo/multiview textures

David Rose 14 éve
szülő
commit
6872488839
63 módosított fájl, 1288 hozzáadás és 406 törlés
  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
     def loadTexture(self, texturePath, alphaPath = None,
                     readMipmaps = False, okMissing = False,
-                    minfilter = None, magfilter = None, anisotropicDegree = None):
+                    minfilter = None, magfilter = None,
+                    anisotropicDegree = None, loaderOptions = None,
+                    multiview = None):
         """
         texturePath is a string.
 
@@ -521,13 +523,31 @@ class Loader(DirectObject):
         to apply to the texture when it is loaded.  Like minfilter and
         magfilter, egg-texture-cards may be a more robust way to apply
         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:
             assert Loader.notify.debug("Loading texture: %s" % (texturePath))
-            texture = TexturePool.loadTexture(texturePath, 0, readMipmaps)
+            texture = TexturePool.loadTexture(texturePath, 0, readMipmaps, loaderOptions)
         else:
             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:
             message = 'Could not load texture: %s' % (texturePath)
             raise IOError, message
@@ -542,7 +562,8 @@ class Loader(DirectObject):
         return texture
 
     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
         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
         with the z-height number, and the second group with the mipmap
         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))
-        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:
             message = 'Could not load 3-D texture: %s' % (texturePattern)
             raise IOError, message
@@ -575,7 +619,8 @@ class Loader(DirectObject):
         return texture
 
     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
         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
         with the face index number, and the second group with the
         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))
-        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:
             message = 'Could not load cube map: %s' % (texturePattern)
             raise IOError, message

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

@@ -191,11 +191,30 @@ get_sort() const {
 //               set_stereo_channel().
 ////////////////////////////////////////////////////////////////////
 INLINE Lens::StereoChannel DisplayRegion::
-get_stereo_channel() {
+get_stereo_channel() const {
   CDReader cdata(_cycler);
   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
 //       Access: Published
@@ -738,6 +757,24 @@ get_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
 //       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,
 //               SC_left, or SC_right.  You may set SC_stereo only on
 //               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::
 set_stereo_channel(Lens::StereoChannel stereo_channel) {
@@ -278,6 +283,29 @@ set_stereo_channel(Lens::StereoChannel stereo_channel) {
 
   CDWriter cdata(_cycler);
   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),
   _sort(0),
   _stereo_channel(Lens::SC_mono),
+  _tex_view_offset(0),
   _cube_map_index(-1)
 {
 }
@@ -794,6 +823,7 @@ CData(const DisplayRegion::CData &copy) :
   _active(copy._active),
   _sort(copy._sort),
   _stereo_channel(copy._stereo_channel),
+  _tex_view_offset(copy._tex_view_offset),
   _cube_map_index(copy._cube_map_index)
 {
 }

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

@@ -95,7 +95,9 @@ PUBLISHED:
   INLINE int get_sort() const;
 
   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);
   INLINE bool get_incomplete_render() const;
@@ -203,6 +205,7 @@ private:
     bool _active;
     int _sort;
     Lens::StereoChannel _stereo_channel;
+    int _tex_view_offset;
     int _cube_map_index;
 
     PT(CallbackObject) _cull_callback;
@@ -297,6 +300,7 @@ public:
   INLINE bool is_active() const;
   INLINE int get_sort() const;
   INLINE Lens::StereoChannel get_stereo_channel();
+  INLINE int get_tex_view_offset();
   INLINE bool get_clear_depth_between_eyes() const;
   INLINE int get_cube_map_index() 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);
 
   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()) {
     gsg->clear(dr);
@@ -1848,7 +1848,7 @@ do_draw(CullResult *cull_result, SceneSetup *scene_setup,
   {
     DisplayRegionPipelineReader dr_reader(dr, current_thread);
     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()) {
       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);
 
     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);
   }
 }

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

@@ -922,6 +922,18 @@ get_current_stereo_channel() const {
   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
 //       Access: Public

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

@@ -144,6 +144,7 @@ GraphicsStateGuardian(CoordinateSystem internal_coordinate_system,
   _data_reader = (GeomVertexDataPipelineReader *)NULL;
   _current_display_region = (DisplayRegion*)NULL;
   _current_stereo_channel = Lens::SC_mono;
+  _current_tex_view_offset = 0;
   _current_lens = (Lens *)NULL;
   _projection_mat = 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
 //               future drawing and clear operations will be
 //               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::
-prepare_display_region(DisplayRegionPipelineReader *dr,
-                       Lens::StereoChannel stereo_channel) {
+prepare_display_region(DisplayRegionPipelineReader *dr) {
   _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();
 
   _stereo_buffer_mask = ~0;
 
-  switch (stereo_channel) {
+  switch (dr->get_stereo_channel()) {
   case Lens::SC_left:
     _color_write_mask = dr->get_window()->get_left_eye_color_mask();
     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 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_state_and_transform();
 
@@ -287,6 +285,7 @@ public:
 
   INLINE const DisplayRegion *get_current_display_region() const;
   INLINE Lens::StereoChannel get_current_stereo_channel() const;
+  INLINE int get_current_tex_view_offset() const;
   INLINE const Lens *get_current_lens() const;
 
   virtual const TransformState *get_cs_transform() const;
@@ -390,6 +389,7 @@ protected:
 
   CPT(DisplayRegion) _current_display_region;
   Lens::StereoChannel _current_stereo_channel;
+  int _current_tex_view_offset;
   CPT(Lens) _current_lens;
   CPT(TransformState) _projection_mat;
   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
 //               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::
 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
 //       Access: Published, Virtual

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

@@ -57,6 +57,7 @@ PUBLISHED:
   virtual void set_active(bool active);
   virtual void set_sort(int sort);
   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_texture_reload_priority(int texture_reload_priority);
   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
     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
     contain a sequence of one or more hash mark ("#") characters,
     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().
 ////////////////////////////////////////////////////////////////////
 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())) {
     dxgsg8_cat.error()
@@ -350,7 +350,7 @@ release_texture(TextureContext *tc) {
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian8::
 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);
   DXTextureContext8 *dtc = DCAST(DXTextureContext8, tc);
 
@@ -648,10 +648,9 @@ clear(DrawableRegion *clearable) {
 //       scissor region and viewport)
 ////////////////////////////////////////////////////////////////////
 void DXGraphicsStateGuardian8::
-prepare_display_region(DisplayRegionPipelineReader *dr,
-                       Lens::StereoChannel stereo_channel) {
+prepare_display_region(DisplayRegionPipelineReader *dr) {
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
-  GraphicsStateGuardian::prepare_display_region(dr, stereo_channel);
+  GraphicsStateGuardian::prepare_display_region(dr);
 
   int 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);
   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) {
     return false;
   }
@@ -2849,7 +2848,7 @@ do_issue_texture() {
 
     // We always reissue every stage in DX, just in case the texcoord
     // 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);
     set_texture_blend_mode(si, stage);
 

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

@@ -49,7 +49,7 @@ public:
   FrameBufferProperties
     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);
   virtual bool update_texture(TextureContext *tc, bool force);
   bool upload_texture(DXTextureContext8 *dtc, bool force);
@@ -71,8 +71,7 @@ public:
 
   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 bool prepare_lens();
 

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

@@ -33,8 +33,8 @@ static const DWORD g_LowByteMask = 0x000000FF;
 //  Description:
 ////////////////////////////////////////////////////////////////////
 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()) {
     dxgsg8_cat.spam()

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

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

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

@@ -196,8 +196,8 @@ DXGraphicsStateGuardian9::
 //               prepare a texture.  Instead, call Texture::prepare().
 ////////////////////////////////////////////////////////////////////
 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())) {
     dxgsg9_cat.error()
@@ -410,11 +410,20 @@ release_texture(TextureContext *tc) {
 ////////////////////////////////////////////////////////////////////
 bool DXGraphicsStateGuardian9::
 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)
 ////////////////////////////////////////////////////////////////////
 void DXGraphicsStateGuardian9::
-prepare_display_region(DisplayRegionPipelineReader *dr,
-                       Lens::StereoChannel stereo_channel) {
+prepare_display_region(DisplayRegionPipelineReader *dr) {
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
-  GraphicsStateGuardian::prepare_display_region(dr, stereo_channel);
+  GraphicsStateGuardian::prepare_display_region(dr);
 
   int 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
   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) {
     return false;
   }
@@ -3776,7 +3785,8 @@ update_standard_texture_bindings() {
 
     // We always reissue every stage in DX, just in case the texcoord
     // 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);
     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";
     return false;
   }
-  return cgD3D9IsProfileSupported(cgGetProfile(name.c_str()));
+  return cgD3D9IsProfileSupported(cgGetProfile(name.c_str())) != 0;
 #endif  // HAVE_CG
 }
 

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

@@ -72,7 +72,7 @@ public:
     calc_fb_properties(DWORD cformat, DWORD dformat,
                        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);
   virtual bool update_texture(TextureContext *tc, bool force);
   bool upload_texture(DXTextureContext9 *dtc, bool force);
@@ -100,8 +100,7 @@ public:
 
   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 bool prepare_lens();
 

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

@@ -767,6 +767,7 @@ update_shader_texture_bindings(CLP(ShaderContext) *prev, GSG *gsg)
         continue;
       }        
       Texture *tex = 0;
+      int view = gsg->get_current_tex_view_offset();
       InternalName *id = _shader->_tex_spec[i]._name;
       if (id != 0) {
         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);
         tex = texattrib->get_on_texture(stage);
+        view += stage->get_tex_view_offset();
       }
       if (_shader->_tex_spec[i]._suffix != 0) {
         // 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)) {
         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) {
         continue;
       }

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

@@ -35,8 +35,8 @@ static const DWORD g_LowByteMask = 0x000000FF;
 //  Description:
 ////////////////////////////////////////////////////////////////////
 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()) {
     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);
   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) {
     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 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()) {
         dxgsg9_cat.debug()
           << "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);
 
+  size_t view_size = tex->get_ram_mipmap_view_size(0);
+  image_pixels += view_size * get_view();
+
   IDirect3DVolume9 *mip_level_0 = NULL;
   bool using_temp_buffer = false;
   BYTE *pixels = image_pixels;

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

@@ -27,7 +27,7 @@
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDADX DXTextureContext9 : public TextureContext {
 public:
-  DXTextureContext9(PreparedGraphicsObjects *pgo, Texture *tex);
+  DXTextureContext9(PreparedGraphicsObjects *pgo, Texture *tex, int view);
   virtual ~DXTextureContext9();
 
   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_ctx =
       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->create_texture(*_dxgsg->_screen)) {
@@ -441,7 +441,7 @@ rebuild_bitplanes() {
     depth_tex->set_format(Texture::F_depth_stencil);
     depth_ctx =
       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->create_texture(*_dxgsg->_screen)) {
         dxgsg9_cat.error()
@@ -516,7 +516,7 @@ rebuild_bitplanes() {
           IDirect3DSurface9 *color_surf = 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->create_texture(*_dxgsg->_screen)) {
               dxgsg9_cat.error()
@@ -607,7 +607,7 @@ select_cube_map(int cube_map_index) {
   if (color_tex) {
     color_ctx =
       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;
     if (color_cube && _cube_map_index >= 0 && _cube_map_index < 6) {
       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;
           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 (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;
 }
 
+////////////////////////////////////////////////////////////////////
+//     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
 //       Access: Published

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

@@ -43,6 +43,8 @@ EggTexture(const string &tref_name, const Filename &filename)
   _anisotropic_degree = 0;
   _env_type = ET_unspecified;
   _saved_result = false;
+  _multiview = false;
+  _num_views = 0;
   _tex_gen = TG_unspecified;
   _quality_level = QL_unspecified;
   _priority = 0;
@@ -89,6 +91,8 @@ operator = (const EggTexture &copy) {
   _anisotropic_degree = copy._anisotropic_degree;
   _env_type = copy._env_type;
   _saved_result = copy._saved_result;
+  _multiview = copy._multiview;
+  _num_views = copy._num_views;
   _tex_gen = copy._tex_gen;
   _quality_level = copy._quality_level;
   _stage_name = copy._stage_name;
@@ -287,6 +291,16 @@ write(ostream &out, int indent_level) const {
       << "<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);
 
   if (has_transform()) {

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

@@ -269,6 +269,14 @@ PUBLISHED:
   INLINE bool has_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 bool get_read_mipmaps() const;
 
@@ -310,6 +318,7 @@ private:
     F_has_rgb_scale          = 0x0100,
     F_has_alpha_scale        = 0x0200,
     F_has_border_color       = 0x0400,
+    F_has_num_views          = 0x0800,
   };
 
   TextureType _texture_type;
@@ -320,6 +329,8 @@ private:
   int _anisotropic_degree;
   EnvType _env_type;
   bool _saved_result;
+  bool _multiview;
+  int _num_views;
   TexGen _tex_gen;
   QualityLevel _quality_level;
   string _stage_name;

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

@@ -572,6 +572,17 @@ texture_body:
   } else if (cmp_nocase_uh(name, "priority") == 0) {
     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) {
     Colorf color = texture->get_color();
     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;
   switch (egg_tex->get_texture_type()) {
   case EggTexture::TT_unspecified:

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

@@ -391,7 +391,7 @@ rebuild_bitplanes() {
         //}
 
         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);
         CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
         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);
         _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);
       CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
       glgsg->update_texture(tc, true);
@@ -551,7 +551,7 @@ bind_slot(bool rb_resize, Texture **attach, RenderTexturePlane slot, GLenum atta
       }
 #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);
       CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
 #ifndef OPENGLES
@@ -804,7 +804,7 @@ generate_mipmaps() {
     Texture *tex = _tex[slot];
     if ((tex != 0) && (tex->uses_mipmaps())) {
       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);
       CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
       glgsg->update_texture(tc, true);

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

@@ -53,6 +53,7 @@
 #include "depthWriteAttrib.h"
 #include "shadeModelAttrib.h"
 #include "rescaleNormalAttrib.h"
+#include "clipPlaneAttrib.h"
 #include "alphaTestAttrib.h"
 #include "cullFaceAttrib.h"
 #include "fogAttrib.h"
@@ -1913,10 +1914,9 @@ clear(DrawableRegion *clearable) {
 //               scissor region and viewport)
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
-prepare_display_region(DisplayRegionPipelineReader *dr,
-                       Lens::StereoChannel stereo_channel) {
+prepare_display_region(DisplayRegionPipelineReader *dr) {
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
-  GraphicsStateGuardian::prepare_display_region(dr, stereo_channel);
+  GraphicsStateGuardian::prepare_display_region(dr);
 
   int 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().
 ////////////////////////////////////////////////////////////////////
 TextureContext *CLP(GraphicsStateGuardian)::
-prepare_texture(Texture *tex) {
+prepare_texture(Texture *tex, int view) {
   report_my_gl_errors();
   // Make sure we'll support this texture when it's rendered.  Don't
   // bother to prepare it if we won't.
@@ -3322,7 +3322,7 @@ prepare_texture(Texture *tex) {
   }
 
   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();
   GLP(GenTextures)(1, &gtc->_index);
   report_my_gl_errors();
@@ -3423,14 +3423,22 @@ release_texture(TextureContext *tc) {
 ////////////////////////////////////////////////////////////////////
 bool CLP(GraphicsStateGuardian)::
 extract_texture_data(Texture *tex) {
+  bool success = true;
   // Make sure the error stack is cleared out before we begin.
   report_my_gl_errors();
 
-  TextureContext *tc = tex->prepare_now(get_prepared_objects(), this);
-  nassertr(tc != (TextureContext *)NULL, false);
-  CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
+  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);
   CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
 
@@ -7510,8 +7519,9 @@ update_standard_texture_bindings() {
       GLP(Disable)(GL_TEXTURE_CUBE_MAP);
     }
 #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) {
       // Something wrong with this texture; skip it.
       break;
@@ -7715,7 +7725,8 @@ update_show_usage_texture_bindings(int show_stage_index) {
     Texture *texture = _target_texture->get_on_texture(stage);
     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) {
       // Something wrong with this texture; skip it.
       break;
@@ -8783,17 +8794,20 @@ upload_texture_image(CLP(TextureContext) *gtc,
         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) {
-        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;
       if (!_supports_bgr && image_compression == Texture::CM_off) {
         // If the GL doesn't claim to support BGR, we may have to reverse
         // 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);
       }
 
@@ -8802,7 +8816,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
       int depth = tex->get_expected_mipmap_z_size(n);
 
 #ifdef DO_PSTATS
-      _data_transferred_pcollector.add_level(image_size);
+      _data_transferred_pcollector.add_level(view_size);
 #endif
       switch (texture_target) {
       case GL_TEXTURE_1D:
@@ -8811,7 +8825,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                              external_format, component_type, image_ptr);
         } else {
           _glCompressedTexSubImage1D(page_target, n - mipmap_bias, 0, width,
-                                     external_format, image_size, image_ptr);
+                                     external_format, view_size, image_ptr);
         }
         break;
 
@@ -8828,7 +8842,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                              external_format, component_type, image_ptr);
           } else {
             _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 {
           report_my_gl_errors();
@@ -8844,7 +8858,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                              external_format, component_type, image_ptr);
           } else {
             _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 {
           report_my_gl_errors();
@@ -8863,7 +8877,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                              external_format, component_type, image_ptr);
         } else {
           _glCompressedTexSubImage2D(page_target, n - mipmap_bias, 0, 0, width, height,
-                                     external_format, image_size, image_ptr);
+                                     external_format, view_size, image_ptr);
         }
         break;
       }
@@ -8931,17 +8945,20 @@ upload_texture_image(CLP(TextureContext) *gtc,
         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) {
-        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;
       if (!_supports_bgr && image_compression == Texture::CM_off) {
         // If the GL doesn't claim to support BGR, we may have to reverse
         // 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);
       }
 
@@ -8950,7 +8967,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
       int depth = tex->get_expected_mipmap_z_size(n);
 
 #ifdef DO_PSTATS
-      _data_transferred_pcollector.add_level(image_size);
+      _data_transferred_pcollector.add_level(view_size);
 #endif
       switch (texture_target) {
 #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);
         } else {
           _glCompressedTexImage1D(page_target, n - mipmap_bias, external_format, width,
-                                  0, image_size, image_ptr);
+                                  0, view_size, image_ptr);
         }
         break;
 #endif  // OPENGLES  // OpenGL ES will fall through.
@@ -8981,7 +8998,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
           } else {
             _glCompressedTexImage3D(page_target, n - mipmap_bias, external_format, width,
                                     height, depth,
-                                    0, image_size, image_ptr);
+                                    0, view_size, image_ptr);
           }
         } else {
           report_my_gl_errors();
@@ -8999,7 +9016,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
           } else {
             _glCompressedTexImage3D(page_target, n - mipmap_bias, external_format, width,
                                     height, depth,
-                                    0, image_size, image_ptr);
+                                    0, view_size, image_ptr);
           }
         } else {
           report_my_gl_errors();
@@ -9015,7 +9032,7 @@ upload_texture_image(CLP(TextureContext) *gtc,
                           external_format, component_type, image_ptr);
         } else {
           _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 prepare_display_region(DisplayRegionPipelineReader *dr,
-                                      Lens::StereoChannel stereo_channel);
+  virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
   virtual void clear_before_callback();
   virtual CPT(TransformState) calc_projection_mat(const Lens *lens);
   virtual bool prepare_lens();
@@ -197,7 +196,7 @@ public:
                            bool force);
   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 void release_texture(TextureContext *tc);
   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
 
     Texture *tex = 0;
+    int view = gsg->get_current_tex_view_offset();
     if (id != 0) {
       const ShaderInput *input = gsg->_target_shader->get_shader_input(id);
       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);
       tex = texattrib->get_on_texture(stage);
+      view += stage->get_tex_view_offset();
     }
     if (_shader->_tex_spec[i]._suffix != 0) {
       // 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)) {
       continue;
     }
-    TextureContext *tc = tex->prepare_now(gsg->_prepared_objects, gsg);
+    TextureContext *tc = tex->prepare_now(view, gsg->_prepared_objects, gsg);
     if (tc == (TextureContext*)NULL) {
       continue;
     }

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

@@ -19,8 +19,8 @@
 //  Description:
 ////////////////////////////////////////////////////////////////////
 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;
   _already_applied = false;

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

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

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

@@ -241,7 +241,7 @@ void PreparedGraphicsObjects::
 release_texture(TextureContext *tc) {
   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
   // the Texture itself might destruct at any time after it has been
@@ -284,7 +284,7 @@ release_all_textures() {
        tci != _prepared_textures.end();
        ++tci) {
     TextureContext *tc = (*tci);
-    tc->_texture->clear_prepared(this);
+    tc->_texture->clear_prepared(tc->get_view(), this);
     tc->_texture = (Texture *)NULL;
 
     _released_textures.insert(tc);
@@ -341,14 +341,14 @@ get_num_prepared_textures() const {
 //               TextureContext will be deleted.
 ////////////////////////////////////////////////////////////////////
 TextureContext *PreparedGraphicsObjects::
-prepare_texture_now(Texture *tex, GraphicsStateGuardianBase *gsg) {
+prepare_texture_now(Texture *tex, int view, GraphicsStateGuardianBase *gsg) {
   ReMutexHolder holder(_lock);
 
   // Ask the GSG to create a brand new TextureContext.  There might
   // 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
   // all shared anyway).
-  TextureContext *tc = gsg->prepare_texture(tex);
+  TextureContext *tc = gsg->prepare_texture(tex, view);
 
   if (tc != (TextureContext *)NULL) {
     bool prepared = _prepared_textures.insert(tc).second;
@@ -1274,9 +1274,11 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
        qti != _enqueued_textures.end();
        ++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_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);
   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,
               Texture::Format format) {
   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);
 }
 
@@ -169,7 +169,7 @@ setup_2d_texture_array(int z_size) {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 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);
 }
 
@@ -198,8 +198,7 @@ setup_cube_map() {
 //               and z_size is always 6.
 ////////////////////////////////////////////////////////////////////
 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);
 }
 
@@ -232,7 +231,9 @@ write(const Filename &fullpath) {
 //               number to write.  3-D textures have one page number
 //               for each level of depth; cube maps have six pages
 //               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
 //               will be written.  In this case z is ignored, and the
@@ -487,6 +488,40 @@ get_z_size() const {
   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
 //       Access: Published
@@ -1064,6 +1099,21 @@ get_expected_mipmap_z_size(int n) const {
   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
 //       Access: Published
@@ -1130,8 +1180,9 @@ might_have_ram_image() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::get_ram_image_size
 //       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::
 get_ram_image_size() const {
@@ -1139,6 +1190,24 @@ get_ram_image_size() const {
   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
 //       Access: Published
@@ -1446,6 +1515,29 @@ get_ram_mipmap_image_size(int n) const {
   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
 //       Access: Published
@@ -1478,13 +1570,28 @@ get_expected_ram_mipmap_image_size(int n) const {
   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
 //       Access: Published
 //  Description: Returns the number of bytes that should be used per
 //               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
-//               get_expected_ram_mipmap_image_size(n).
+//               get_expected_ram_mipmap_view_size(n).
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 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);
 }
 
+////////////////////////////////////////////////////////////////////
+//     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
 //       Access: Published
@@ -2072,6 +2202,16 @@ do_has_ram_mipmap_image(int n) const {
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 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;
 }
 
@@ -2092,6 +2232,16 @@ do_get_expected_ram_page_size() const {
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 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);
 }
 
@@ -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);
 }
 
+////////////////////////////////////////////////////////////////////
+//     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
 //       Access: Protected

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

@@ -194,6 +194,7 @@ Texture(const string &name) :
   _x_size = 0;
   _y_size = 1;
   _z_size = 1;
+  _num_views = 1;
   // Set it to something else first to
   // avoid the check in do_set_format
   // depending on an uninitialised value
@@ -1401,9 +1402,9 @@ prepare(PreparedGraphicsObjects *prepared_objects) {
 bool Texture::
 is_prepared(PreparedGraphicsObjects *prepared_objects) const {
   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 prepared_objects->is_texture_queued(this);
@@ -1420,11 +1421,22 @@ is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 bool Texture::
 was_image_modified(PreparedGraphicsObjects *prepared_objects) const {
   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;
 }
@@ -1443,13 +1455,22 @@ was_image_modified(PreparedGraphicsObjects *prepared_objects) const {
 size_t Texture::
 get_data_size_bytes(PreparedGraphicsObjects *prepared_objects) const {
   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::
 get_active(PreparedGraphicsObjects *prepared_objects) const {
   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;
 }
@@ -1480,11 +1510,20 @@ get_active(PreparedGraphicsObjects *prepared_objects) const {
 bool Texture::
 get_resident(PreparedGraphicsObjects *prepared_objects) const {
   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;
 }
@@ -1499,16 +1538,19 @@ get_resident(PreparedGraphicsObjects *prepared_objects) const {
 bool Texture::
 release(PreparedGraphicsObjects *prepared_objects) {
   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.
@@ -1525,26 +1567,28 @@ release(PreparedGraphicsObjects *prepared_objects) {
 int Texture::
 release_all() {
   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
   // 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;
 }
 
@@ -1588,6 +1632,10 @@ write(ostream &out, int indent_level) const {
     break;
   }
 
+  if (_num_views > 1) {
+    out << " (x " << _num_views << " views)";
+  }
+
   out << " pixels, each " << _num_components;
 
   switch (_component_type) {
@@ -1839,17 +1887,24 @@ is_mipmap(FilterType filter_type) {
 //               rendered.
 ////////////////////////////////////////////////////////////////////
 TextureContext *Texture::
-prepare_now(PreparedGraphicsObjects *prepared_objects,
+prepare_now(int view,
+            PreparedGraphicsObjects *prepared_objects,
             GraphicsStateGuardianBase *gsg) {
   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;
 }
@@ -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();
 
   if (read_pages && read_mipmaps) {
@@ -2753,8 +2818,9 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath,
         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,
                          alpha_file_channel, options, header_only, record)) {
           return false;
@@ -2774,6 +2840,8 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath,
       }
       ++n;
     }
+    _fullpath = fullpath_pattern;
+    _alpha_fullpath = alpha_fullpath_pattern;
 
   } else if (read_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;
     Filename file = 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,
                        alpha_file_channel, options, header_only, record)) {
         return false;
@@ -2801,6 +2871,8 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath,
       file = 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) {
     // 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);
       alpha_file = alpha_fullpath_pattern.get_filename_index(n);
     }
+    _fullpath = fullpath_pattern;
+    _alpha_fullpath = alpha_fullpath_pattern;
 
   } else {
     // 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
     // 0, unless we already have mipmap levels, it determines the
     // image properties like size and number of components.
-    if (!do_reconsider_z_size(z)) {
+    if (!do_reconsider_z_size(z, options)) {
       return false;
     }
-    nassertr(z >= 0 && z < _z_size, false);
+    nassertr(z >= 0 && z < _z_size * _num_views, false);
 
     if (z == 0) {
       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();
 
     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));
 
         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;
     }
 
-    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)) {
         return false;
       }
@@ -3681,8 +3756,11 @@ do_store_one(PNMImage &pnmimage, int z, int n) const {
   // First, reload the ram image if necessary.
   ((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);
 
   return convert_to_pnmimage(pnmimage,
@@ -3822,6 +3900,7 @@ do_unlock_and_reload_ram_image(bool allow_compression) {
     if (tex->_x_size != _x_size ||
         tex->_y_size != _y_size ||
         tex->_z_size != _z_size ||
+        tex->_num_views != _num_views ||
         tex->_num_components != _num_components ||
         tex->_component_width != _component_width ||
         tex->_texture_type != _texture_type ||
@@ -3830,6 +3909,7 @@ do_unlock_and_reload_ram_image(bool allow_compression) {
       _x_size = tex->_x_size;
       _y_size = tex->_y_size;
       _z_size = tex->_z_size;
+      _num_views = tex->_num_views;
 
       _num_components = tex->_num_components;
       _component_width = tex->_component_width;
@@ -4315,37 +4395,74 @@ do_has_all_ram_mipmap_images() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::do_reconsider_z_size
 //       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.
 ////////////////////////////////////////////////////////////////////
 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
     // 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;
 }
 
+////////////////////////////////////////////////////////////////////
+//     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
 //       Access: Protected
@@ -4434,8 +4551,8 @@ bool Texture::
 do_rescale_texture() {
   int new_x_size = _x_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;
   }
 
@@ -4533,6 +4650,7 @@ do_assign(const Texture &copy) {
   _x_size = copy._x_size;
   _y_size = copy._y_size;
   _z_size = copy._z_size;
+  _num_views = copy._num_views;
   _pad_x_size = copy._pad_x_size;
   _pad_y_size = copy._pad_y_size;
   _pad_z_size = copy._pad_z_size;
@@ -4622,6 +4740,7 @@ do_setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
   _x_size = x_size;
   _y_size = y_size;
   _z_size = z_size;
+  _num_views = 1;
   do_set_component_type(component_type);
   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
 //       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.
 ////////////////////////////////////////////////////////////////////
 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;
   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);
 
-  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.
     unsigned char *p = to._image.p() + z * to._page_size;
+    nassertv(p <= to._image.p() + to._image.size() + to._page_size);
     const unsigned char *q = from._image.p() + z * from._page_size;
+    nassertv(q <= from._image.p() + from._image.size() + from._page_size);
     if (y_size != 1) {
       int y;
       for (y = 0; y < y_size - 1; y += 2) {
@@ -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 row_size = (size_t)x_size * pixel_size;
   size_t page_size = (size_t)y_size * row_size;
+  size_t view_size = (size_t)z_size * page_size;
 
   int to_x_size = max(x_size >> 1, 1);
   int to_y_size = max(y_size >> 1, 1);
@@ -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_page_size = (size_t)to_y_size * to_row_size;
+  size_t to_view_size = (size_t)to_z_size * to_page_size;
   to._page_size = to_page_size;
-  to._image = PTA_uchar::empty_array(to_page_size * to_z_size, 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);
 
-  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) {
         int y;
         for (y = 0; y < y_size - 1; y += 2) {
           // 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) {
             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);
+                filter_component(p, q, pixel_size, row_size, 0);
               }
               q += pixel_size;
             }
@@ -6427,7 +6643,7 @@ filter_3d_mipmap_level(Texture::RamImage &to, const Texture::RamImage &from,
             // Just one pixel.
             for (int c = 0; c < _num_components; ++c) {
               // For each component.
-              filter_component(p, q, 0, row_size, page_size);
+              filter_component(p, q, 0, row_size, 0);
             }
           }
           q += row_size;
@@ -6445,43 +6661,7 @@ filter_3d_mipmap_level(Texture::RamImage &to, const Texture::RamImage &from,
             // 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) {
-      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;
           }
@@ -6493,44 +6673,15 @@ filter_3d_mipmap_level(Texture::RamImage &to, const Texture::RamImage &from,
           // Just one pixel.
           for (int c = 0; c < _num_components; ++c) {
             // 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;
     int x_size = do_get_expected_mipmap_x_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 cell_size = squish::GetStorageRequirements(4, 4, squish_flags);
 
     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 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;
@@ -6732,13 +6883,13 @@ do_unsquish(int squish_flags) {
     RamImage uncompressed_image;
     int x_size = do_get_expected_mipmap_x_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 cell_size = squish::GetStorageRequirements(4, 4, squish_flags);
 
     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_end = dest_page + uncompressed_image._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(_y_size);
   me.add_uint32(_z_size);
+  me.add_uint32(_num_views);
   me.add_uint8(_component_type);
   me.add_uint8(_component_width);
   me.add_uint8(_ram_image_compression);
@@ -7222,6 +7374,10 @@ do_fillin_rawdata(DatagramIterator &scan, BamReader *manager) {
   _x_size = scan.get_uint32();
   _y_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_width = scan.get_uint8();
   _ram_image_compression = CM_off;

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

@@ -210,7 +210,6 @@ PUBLISHED:
   INLINE void setup_texture(TextureType texture_type,
                             int x_size, int y_size, int z_size,
                             ComponentType component_type, Format format);
-
   INLINE void setup_1d_texture();
   INLINE void setup_1d_texture(int x_size,
                                ComponentType component_type, Format format);
@@ -272,6 +271,8 @@ PUBLISHED:
   INLINE int get_x_size() const;
   INLINE int get_y_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_component_width() 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_y_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_uncompressed_ram_image() const;
   INLINE bool might_have_ram_image() 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_expected_ram_image_size() const;
   INLINE size_t get_expected_ram_page_size() const;
@@ -343,8 +346,10 @@ PUBLISHED:
   int get_num_loadable_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_view_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_view_size(int n) const;
   INLINE size_t get_expected_ram_mipmap_page_size(int n) const;
   CPTA_uchar get_ram_mipmap_image(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_y_size(int y_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_y_size() const;
@@ -444,7 +450,8 @@ PUBLISHED:
   INLINE bool get_post_load_store_cache() const;
   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);
 
   static int up_to_power_2(int value);
@@ -544,7 +551,8 @@ protected:
   bool do_uncompress_ram_image();
   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,
                                       ComponentType component_type, int z,
                                       const LoaderOptions &options);
@@ -556,6 +564,7 @@ protected:
   void do_setup_texture(TextureType texture_type, int x_size, int y_size,
                         int z_size, ComponentType component_type,
                         Format format);
+  void do_set_num_views(int num_views);
   void do_set_format(Format format);
   void do_set_component_type(ComponentType component_type);
   void do_set_x_size(int x_size);
@@ -582,13 +591,16 @@ protected:
   INLINE bool do_has_ram_mipmap_image(int n) 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_view_size() const;
   INLINE size_t do_get_expected_ram_page_size() 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_view_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_y_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();
   void do_clear_simple_ram_image();
   void do_clear_ram_mipmap_images();
@@ -645,7 +657,7 @@ private:
                                         const DDSHeader &header, 
                                         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);
 
@@ -720,6 +732,7 @@ protected:
   int _x_size;
   int _y_size;
   int _z_size;
+  int _num_views;
   int _num_components;
   int _component_width;
   TextureType _texture_type;
@@ -758,8 +771,9 @@ protected:
   // Each PGO conversely keeps a list (a set) of all the Textures that
   // have been prepared there.  When either destructs, it removes
   // 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,
   // and such, to use a file naming convention where the filenames

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

@@ -19,10 +19,11 @@
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE TextureContext::
-TextureContext(PreparedGraphicsObjects *pgo, Texture *tex) :
+TextureContext(PreparedGraphicsObjects *pgo, Texture *tex, int view) :
   BufferContext(&pgo->_texture_residency),
   AdaptiveLruPage(0),
-  _texture(tex)
+  _texture(tex),
+  _view(view)
 {
 }
 
@@ -37,6 +38,18 @@ get_texture() const {
   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

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

@@ -37,10 +37,11 @@
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA_GOBJ TextureContext : public BufferContext, public AdaptiveLruPage {
 public:
-  INLINE TextureContext(PreparedGraphicsObjects *pgo, Texture *tex);
+  INLINE TextureContext(PreparedGraphicsObjects *pgo, Texture *tex, int view);
 
 PUBLISHED:
   INLINE Texture *get_texture() const;
+  INLINE int get_view() const;
 
   INLINE bool was_modified() const;
   INLINE bool was_properties_modified() const;
@@ -62,6 +63,7 @@ private:
   // both own their TextureContexts!  That would create a circular
   // reference count.
   Texture *_texture;
+  int _view;
   UpdateSeq _properties_modified;
   UpdateSeq _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);
   TypeRegistry::const_iterator ti;
-  ti = _type_registry.find(extension);
+  ti = _type_registry.find(c);
   if (ti != _type_registry.end()) {
     return (*ti).second;
   }
 
   // Check the PNM type registry.
   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) {
     // 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;
   }
 
@@ -1183,7 +1183,8 @@ try_load_cache(PT(Texture) &tex, BamCache *cache, const Filename &filename,
 void TexturePool::
 report_texture_unreadable(const Filename &filename) const {
   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()) {
       // The file doesn't exist, and it wasn't
       // fully-qualified--therefore, it wasn't found along either
@@ -1200,8 +1201,15 @@ report_texture_unreadable(const Filename &filename) const {
 
   } else {
     // 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.
     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;
 }
 
+////////////////////////////////////////////////////////////////////
+//     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
 //       Access: Published

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

@@ -38,6 +38,7 @@ TextureStage(const string &name) {
   _rgb_scale = 1;
   _alpha_scale = 1;
   _saved_result = false;
+  _tex_view_offset = 0;
   _combine_rgb_mode = CM_undefined;
   _num_combine_rgb_operands = 0;
   _combine_rgb_source0 = CS_undefined;
@@ -75,6 +76,7 @@ operator = (const TextureStage &other) {
   _rgb_scale = other._rgb_scale;
   _alpha_scale = other._alpha_scale;
   _saved_result = other._saved_result;
+  _tex_view_offset = other._tex_view_offset;
 
   _combine_rgb_mode = other._combine_rgb_mode;
   _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()) {
     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()) {
     return get_mode() < other.get_mode() ? -1 : 1;
   }
@@ -231,7 +236,8 @@ write(ostream &out) const {
       << "  texcoords = " << get_texcoord_name()->get_name()
       << ", mode = " << get_mode() << ", color = " << get_color()
       << ", 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) {
     out << "  RGB combine mode =  " << get_combine_rgb_mode() << "\n";
@@ -400,8 +406,11 @@ fillin(DatagramIterator &scan, BamReader *manager) {
 
   _rgb_scale = scan.get_uint8();
   _alpha_scale = scan.get_uint8();
-  _saved_result = false;
   _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();
   _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(_alpha_scale);
     me.add_bool(_saved_result);
+    me.add_int32(_tex_view_offset);
     
     me.add_uint8(_combine_rgb_mode);
     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 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, 
                               CombineSource source0, CombineOperand operand0);
   INLINE void set_combine_rgb(CombineMode mode, 
@@ -201,6 +204,7 @@ private:
   int _rgb_scale;
   int _alpha_scale;
   bool _saved_result;
+  int _tex_view_offset;
   bool _involves_color_scale;
   bool _uses_color;
   bool _uses_primary_color;

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

@@ -57,7 +57,8 @@ FFMpegTexture(const FFMpegTexture &copy) :
 ////////////////////////////////////////////////////////////////////
 //     Function: FFMpegTexture::Destructor
 //       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() {
@@ -93,7 +94,13 @@ do_make_copy() {
 void FFMpegTexture::
 do_assign(const FFMpegTexture &copy) {
   VideoTexture::do_assign(copy);
+
   _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
 //  Description: Returns a reference to the zth VideoPage (level) of
 //               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::
 modify_page(int z) {
-  nassertr(z < _z_size, _pages[0]);
+  nassertr(z < get_num_pages(), *_pages[0]);
   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::
 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) {
-    VideoPage &page = _pages.at(z);
+    VideoPage &page = *(_pages.at(z));
     if (page._color.is_valid() || page._alpha.is_valid()) {
       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()) {
       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!)  
       if (page._color.get_frame_data(frame)) {
         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);
         
         // 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
         // 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 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(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);
   if (!page._color.read(fullpath)) {
@@ -407,10 +423,9 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
         page._alpha.clear();
         return false;
       }
-      
     }
-    
   }
+
   set_loaded_from_image();
   clear_current_frame();
   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);
 }
 
+////////////////////////////////////////////////////////////////////
+//     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
 //       Access: Protected, Virtual
@@ -493,7 +540,7 @@ void FFMpegTexture::
 do_write_datagram_rawdata(BamWriter *manager, Datagram &me) {
   me.add_uint16(_pages.size());
   for (size_t n = 0; n < _pages.size(); ++n) {
-    VideoPage &page = _pages[n];
+    VideoPage &page = (*_pages[n]);
     page.write_datagram_rawdata(manager, me);
   }
 }
@@ -509,8 +556,8 @@ do_fillin_rawdata(DatagramIterator &scan, BamReader *manager) {
   int num_pages = scan.get_uint16();
   _pages.clear();
   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);
 
     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);
   virtual bool do_load_one(const PNMImage &pnmimage, const string &name,
                            int z, int n, const LoaderOptions &options);
+  virtual void do_allocate_pages();
+  virtual void do_clear();
 
   virtual bool do_has_bam_rawdata() const;
   virtual void do_get_bam_rawdata();
@@ -125,7 +127,7 @@ private:
     VideoStream _color, _alpha;
   };
 
-  typedef pvector<VideoPage> Pages;
+  typedef pvector<VideoPage *> Pages;
   Pages _pages;
 
 public:

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

@@ -151,8 +151,7 @@ do_recalculate_image_properties(CDWriter &cdata, const LoaderOptions &options) {
   bool alpha = false;
   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;
     if (t) {
       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,
             const LoaderOptions &options,
             bool header_only, BamCacheRecord *record) {
-
   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) {
     record->add_dependent_file(fullpath);
@@ -278,10 +279,9 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
 bool MovieTexture::
 do_load_one(PT(MovieVideoCursor) color, PT(MovieVideoCursor) alpha, int z,
             const LoaderOptions &options) {
-  
   {
     CDWriter cdata(_cycler);
-    cdata->_pages.resize(z+1);
+    cdata->_pages.resize(z + 1);
     cdata->_pages[z]._color = color;
     cdata->_pages[z]._alpha = alpha;
     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;
 }
 
+////////////////////////////////////////////////////////////////////
+//     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
 //       Access: Public, Virtual
@@ -737,7 +752,9 @@ void MovieTexture::
 do_write_datagram_rawdata(BamWriter *manager, Datagram &dg) {
   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) {
     const VideoPage &page = cdata->_pages[n];
     manager->write_pointer(dg, page._color);
@@ -755,9 +772,13 @@ void MovieTexture::
 do_fillin_rawdata(DatagramIterator &scan, BamReader *manager) {
   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);
   for (size_t n = 0; n < num_pages; ++n) {
     cdata->_pages.push_back(VideoPage());

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

@@ -85,6 +85,7 @@ protected:
                            int z, int n, const LoaderOptions &options);
   bool do_load_one(PT(MovieVideoCursor) color, PT(MovieVideoCursor) alpha, 
                    int z, const LoaderOptions &options);
+  virtual void do_allocate_pages();
 
   class VideoPage {
   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);
 
   _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->set_display_region(_display_region);

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

@@ -142,7 +142,7 @@ public:
   virtual PreparedGraphicsObjects *get_prepared_objects()=0;
 #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 void release_texture(TextureContext *tc)=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
 //  Description: Fetches a frame from the stream and stores it in
 //               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() {
   int finished = 0;
   _last_start = _packet_time;
@@ -301,6 +302,8 @@ fetch_frame() {
     fetch_packet(_last_start + 1.0);
   }
   _next_start = _packet_time;
+
+  return (finished != 0);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -354,14 +357,18 @@ fetch_time(double time) {
     // Time is in the past.
     seek(time);
     while (_packet_time <= time) {
-      fetch_frame();
+      if (fetch_frame()) {
+        break;
+      }
     }
   } else if (time < _next_start) {
     // Time is in the present: already have the frame.
   } else if (time < _next_start + _min_fseek) {
     // Time is in the near future.
     while ((_packet_time <= time) && (_packet->data)) {
-      fetch_frame();
+      if (fetch_frame()) {
+        break;
+      }
     }
   } else {
     // Time is in the far future.  Seek forward, then read.
@@ -378,7 +385,9 @@ fetch_time(double time) {
       _min_fseek += (base - _packet_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_num_components() == 3) || (t->get_num_components() == 4));
   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();
   

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

@@ -49,7 +49,7 @@ public:
 
 protected:
   void fetch_packet(double default_time);
-  void fetch_frame();
+  bool fetch_frame();
   void seek(double t);
   void fetch_time(double time);
   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
       // if he wants.
       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();
     }
     

+ 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.
 
 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 15 on 4/9/08 to add TextureAttrib::_implicit_sort.
 // 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 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 26 on 8/5/11 to add multiview (stereo) Textures.
 
 
 #endif

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

@@ -20,7 +20,9 @@
 ////////////////////////////////////////////////////////////////////
 INLINE LoaderOptions::
 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::
 LoaderOptions(const LoaderOptions &copy) :
   _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) {
   _flags = copy._flags;
   _texture_flags = copy._texture_flags;
+  _texture_num_views = copy._texture_num_views;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -86,3 +90,30 @@ INLINE int LoaderOptions::
 get_texture_flags() const {
   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(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
   // issues.

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

@@ -44,6 +44,7 @@ PUBLISHED:
     TF_preload_simple    = 0x0008,  // Texture will have simple RAM image
     TF_allow_1d          = 0x0010,  // If texture is Nx1, make a 1-d texture
     TF_generate_mipmaps  = 0x0020,  // Consider generating mipmaps
+    TF_multiview         = 0x0040,  // Load a multiview texture in pages
   };
 
   LoaderOptions(int flags = LF_search | LF_report_errors);
@@ -56,6 +57,8 @@ PUBLISHED:
 
   INLINE void set_texture_flags(int flags);
   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;
 
@@ -66,6 +69,7 @@ private:
                           const string &flag_name, int flag) const;
   int _flags;
   int _texture_flags;
+  int _texture_num_views;
 };
 
 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)
 ////////////////////////////////////////////////////////////////////
 void TinyGraphicsStateGuardian::
-prepare_display_region(DisplayRegionPipelineReader *dr,
-                       Lens::StereoChannel stereo_channel) {
+prepare_display_region(DisplayRegionPipelineReader *dr) {
   nassertv(dr != (DisplayRegionPipelineReader *)NULL);
-  GraphicsStateGuardian::prepare_display_region(dr, stereo_channel);
+  GraphicsStateGuardian::prepare_display_region(dr);
 
   int 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);
 
-  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);
   TinyTextureContext *gtc = DCAST(TinyTextureContext, tc);
 
@@ -1610,7 +1610,7 @@ set_state_and_transform(const RenderState *target,
 //               prepare a texture.  Instead, call Texture::prepare().
 ////////////////////////////////////////////////////////////////////
 TextureContext *TinyGraphicsStateGuardian::
-prepare_texture(Texture *tex) {
+prepare_texture(Texture *tex, int view) {
   switch (tex->get_texture_type()) {
   case Texture::TT_1d_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;
 }
@@ -2209,7 +2209,8 @@ do_issue_texture() {
     Texture *texture = _target_texture->get_on_texture(stage);
     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) {
       // Something wrong with this texture; skip it.
       return;
@@ -2504,7 +2505,7 @@ upload_texture(TinyTextureContext *gtc, bool force) {
     case Texture::F_rgb8:
     case Texture::F_rgb12:
     case Texture::F_rgb332:
-      copy_rgb_image(dest, xsize, ysize, tex, level);
+      copy_rgb_image(dest, xsize, ysize, gtc, level);
       break;
 
     case Texture::F_rgba:
@@ -2515,32 +2516,32 @@ upload_texture(TinyTextureContext *gtc, bool force) {
     case Texture::F_rgba12:
     case Texture::F_rgba16:
     case Texture::F_rgba32:
-      copy_rgba_image(dest, xsize, ysize, tex, level);
+      copy_rgba_image(dest, xsize, ysize, gtc, level);
       break;
 
     case Texture::F_luminance:
-      copy_lum_image(dest, xsize, ysize, tex, level);
+      copy_lum_image(dest, xsize, ysize, gtc, level);
       break;
 
     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;
 
     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;
 
     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;
 
     case Texture::F_alpha:
-      copy_alpha_image(dest, xsize, ysize, tex, level);
+      copy_alpha_image(dest, xsize, ysize, gtc, level);
       break;
 
     case Texture::F_luminance_alphamask:
     case Texture::F_luminance_alpha:
-      copy_la_image(dest, xsize, ysize, tex, level);
+      copy_la_image(dest, xsize, ysize, gtc, level);
       break;
     }
 
@@ -2727,7 +2728,8 @@ get_tex_shift(int orig_size) {
 //               from the texture into the indicated ZTexture pixmap.
 ////////////////////////////////////////////////////////////////////
 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_expected_mipmap_x_size(level) == xsize &&
            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);
   nassertv(!src_image.is_null());
   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.
   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.
 ////////////////////////////////////////////////////////////////////
 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);
 
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   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.
   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.
 ////////////////////////////////////////////////////////////////////
 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);
 
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   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.
   int cw = tex->get_component_width();
@@ -2865,12 +2875,15 @@ copy_one_channel_image(ZTextureLevel *dest, int xsize, int ysize, Texture *tex,
 //               pixmap.
 ////////////////////////////////////////////////////////////////////
 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);
 
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   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.
   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.
 ////////////////////////////////////////////////////////////////////
 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);
 
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   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.
   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.
 ////////////////////////////////////////////////////////////////////
 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);
 
   CPTA_uchar src_image = tex->get_ram_mipmap_image(level);
   nassertv(!src_image.is_null());
   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.
   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 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 bool prepare_lens();
 
@@ -90,7 +89,7 @@ public:
   virtual void set_state_and_transform(const RenderState *state,
                                        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);
   bool update_texture(TextureContext *tc, bool force, int stage_index);
   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);
   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 do_auto_rescale_normal();

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

@@ -19,8 +19,8 @@
 //  Description:
 ////////////////////////////////////////////////////////////////////
 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.allocated_buffer = NULL;

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

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

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

@@ -134,7 +134,7 @@ do_assign(const OpenCVTexture &copy) {
 bool OpenCVTexture::
 from_camera(int camera_index, int z, int alpha_file_channel,
             const LoaderOptions &options) {
-  if (!do_reconsider_z_size(z)) {
+  if (!do_reconsider_z_size(z, options)) {
     return 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);
       }
     }
-    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);
     CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
     GLenum target = wglgsg->get_texture_target(tex->get_texture_type());