Browse Source

work-in-progress: asynchronous texture loads

David Rose 17 years ago
parent
commit
d7e6603118
32 changed files with 1129 additions and 308 deletions
  1. 1 1
      panda/src/cull/drawCullHandler.cxx
  2. 9 0
      panda/src/display/config_display.cxx
  3. 1 0
      panda/src/display/config_display.h
  4. 24 0
      panda/src/display/graphicsEngine.I
  5. 3 0
      panda/src/display/graphicsEngine.cxx
  6. 5 0
      panda/src/display/graphicsEngine.h
  7. 73 11
      panda/src/display/graphicsStateGuardian.I
  8. 212 191
      panda/src/display/graphicsStateGuardian.cxx
  9. 13 3
      panda/src/display/graphicsStateGuardian.h
  10. 2 2
      panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
  11. 91 7
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  12. 3 2
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  13. 34 8
      panda/src/gobj/config_gobj.cxx
  14. 3 0
      panda/src/gobj/config_gobj.h
  15. 11 0
      panda/src/gobj/preparedGraphicsObjects.cxx
  16. 1 0
      panda/src/gobj/preparedGraphicsObjects.h
  17. 128 4
      panda/src/gobj/texture.I
  18. 259 56
      panda/src/gobj/texture.cxx
  19. 32 4
      panda/src/gobj/texture.h
  20. 31 3
      panda/src/gobj/textureContext.I
  21. 3 0
      panda/src/gobj/textureContext.h
  22. 22 4
      panda/src/gobj/texturePool.cxx
  23. 2 0
      panda/src/gsgbase/graphicsStateGuardianBase.h
  24. 3 0
      panda/src/pgraph/Sources.pp
  25. 2 9
      panda/src/pgraph/config_pgraph.cxx
  26. 0 1
      panda/src/pgraph/config_pgraph.h
  27. 2 2
      panda/src/pgraph/cullResult.cxx
  28. 1 0
      panda/src/pgraph/pgraph_composite4.cxx
  29. 51 0
      panda/src/pgraph/textureReloadRequest.I
  30. 35 0
      panda/src/pgraph/textureReloadRequest.cxx
  31. 71 0
      panda/src/pgraph/textureReloadRequest.h
  32. 1 0
      panda/src/pnmimagetypes/pnmFileTypeJPGReader.cxx

+ 1 - 1
panda/src/cull/drawCullHandler.cxx

@@ -32,7 +32,7 @@ void DrawCullHandler::
 record_object(CullableObject *object, const CullTraverser *traverser) {
   // Munge vertices as needed for the GSG's requirements, and the
   // object's current state.
-  bool force = !allow_incomplete_render;
+  bool force = !_gsg->get_incomplete_render();
   Thread *current_thread = traverser->get_current_thread();
 
   if (object->munge_geom(_gsg, _gsg->get_geom_munger(object->_state, current_thread), traverser, force)) {

+ 9 - 0
panda/src/display/config_display.cxx

@@ -205,6 +205,15 @@ ConfigVariableBool alpha_scale_via_texture
           "application specifically enables it.  See also "
           "color-scale-via-lighting."));
 
+ConfigVariableBool allow_incomplete_render
+("allow-incomplete-render", false,
+ PRC_DESC("When this is true, the frame may be rendered even if some of the "
+          "geometry in the scene has been paged out.  The nonresident "
+          "geometry will be rendered as soon as it can be paged back in, "
+          "which may be several frames in the future.  When this is false, "
+          "geometry is always paged in when needed, holding up the frame "
+          "render if necessary."));
+
 ConfigVariableInt win_size
 ("win-size", "640 480",
  PRC_DESC("This is the default size at which to open a new window.  This "

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

@@ -58,6 +58,7 @@ extern EXPCL_PANDA_DISPLAY ConfigVariableString red_blue_stereo_colors;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool auto_generate_mipmaps;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool color_scale_via_lighting;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool alpha_scale_via_texture;
+extern EXPCL_PANDA_DISPLAY ConfigVariableBool allow_incomplete_render;
 
 extern EXPCL_PANDA_DISPLAY ConfigVariableInt win_size;
 extern EXPCL_PANDA_DISPLAY ConfigVariableInt win_origin;

+ 24 - 0
panda/src/display/graphicsEngine.I

@@ -74,6 +74,30 @@ get_portal_cull() const {
   return _portal_enabled;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::set_default_loader
+//       Access: Public
+//  Description: Sets the Loader object that will be assigned to every
+//               GSG created with this GraphicsEngine.  See
+//               GraphicsStateGuardian::set_loader().
+////////////////////////////////////////////////////////////////////
+INLINE void GraphicsEngine::
+set_default_loader(Loader *loader) {
+  _default_loader = loader;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsEngine::get_default_loader
+//       Access: Public, Virtual
+//  Description: Returns the Loader object that will be assigned to
+//               every GSG created with this GraphicsEngine.  See
+//               GraphicsStateGuardian::set_loader().
+////////////////////////////////////////////////////////////////////
+INLINE Loader *GraphicsEngine::
+get_default_loader() const {
+  return _default_loader;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsEngine::close_gsg
 //       Access: Published

+ 3 - 0
panda/src/display/graphicsEngine.cxx

@@ -1760,6 +1760,9 @@ do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe,
   gsg->_threading_model = threading_model;
   gsg->_pipe = pipe;
   gsg->_engine = this;
+  if (!_default_loader.is_null()) {
+    gsg->set_loader(_default_loader);
+  }
 
   auto_adjust_capabilities(gsg);
   

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

@@ -30,6 +30,7 @@
 #include "pset.h"
 #include "ordered_vector.h"
 #include "indirectLess.h"
+#include "loader.h"
 
 class Pipeline;
 class DisplayRegion;
@@ -66,6 +67,9 @@ PUBLISHED:
   INLINE void set_portal_cull(bool value);
   INLINE bool get_portal_cull() const;
 
+  INLINE void set_default_loader(Loader *loader);
+  INLINE Loader *get_default_loader() const;
+
   GraphicsOutput *make_output(GraphicsPipe *pipe,
                               const string &name, int sort,
                               const FrameBufferProperties &fb_prop,
@@ -322,6 +326,7 @@ private:
   GraphicsThreadingModel _threading_model;
   bool _auto_flip;
   bool _portal_enabled; //toggle to portal culling on/off
+  PT(Loader) _default_loader;
 
   enum FlipState {
     FS_draw,  // Still drawing.

+ 73 - 11
panda/src/display/graphicsStateGuardian.I

@@ -105,6 +105,79 @@ is_valid() const {
   return _is_valid;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::needs_reset
+//       Access: Public
+//  Description: Returns true if the gsg is marked as needing a
+//               reset.
+////////////////////////////////////////////////////////////////////
+INLINE bool GraphicsStateGuardian::
+needs_reset() const {
+  return _needs_reset;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::set_incomplete_render
+//       Access: Public
+//  Description: Sets the incomplete_render flag.  When this is
+//               true, the frame will be rendered even if some of the
+//               geometry or textures in the scene are not available
+//               (e.g. they have been temporarily paged out).  When
+//               this is false, the frame will be held up while this
+//               data is reloaded.
+//
+//               Setting this true allows for a smoother frame rate,
+//               but occasionally parts of the frame will be invisible
+//               or missing (they will generally come in within a
+//               second or two).  Setting this false guarantees that
+//               every frame will be complete, but may cause more
+//               chugs as things are loaded up at runtime.
+//
+//               You may want to set this false during loading
+//               screens, to guarantee that all of your assets are
+//               available by the time you take the loading screen
+//               down.
+////////////////////////////////////////////////////////////////////
+INLINE void GraphicsStateGuardian::
+set_incomplete_render(bool incomplete_render) {
+  _incomplete_render = incomplete_render;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_incomplete_render
+//       Access: Public, Virtual
+//  Description: Returns the incomplete_render flag.  See
+//               set_incomplete_render().
+////////////////////////////////////////////////////////////////////
+INLINE bool GraphicsStateGuardian::
+get_incomplete_render() const {
+  return _incomplete_render;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::set_loader
+//       Access: Public
+//  Description: Sets the Loader object that will be used by this GSG
+//               to load textures when necessary, if
+//               get_incomplete_render() is true.
+////////////////////////////////////////////////////////////////////
+INLINE void GraphicsStateGuardian::
+set_loader(Loader *loader) {
+  _loader = loader;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_loader
+//       Access: Public, Virtual
+//  Description: Returns the Loader object that will be used by this
+//               GSG to load textures when necessary, if
+//               get_incomplete_render() is true.
+////////////////////////////////////////////////////////////////////
+INLINE Loader *GraphicsStateGuardian::
+get_loader() const {
+  return _loader;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::get_pipe
 //       Access: Published
@@ -716,17 +789,6 @@ mark_new() {
   _needs_reset = true;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::needs_reset
-//       Access: Public
-//  Description: Returns true if the gsg is marked as needing a
-//               reset.
-////////////////////////////////////////////////////////////////////
-INLINE bool GraphicsStateGuardian::
-needs_reset() const {
-  return _needs_reset;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::get_external_transform
 //       Access: Public

+ 212 - 191
panda/src/display/graphicsStateGuardian.cxx

@@ -40,6 +40,7 @@
 #include "directionalLight.h"
 #include "pointLight.h"
 #include "spotlight.h"
+#include "textureReloadRequest.h"
 
 #include <algorithm>
 #include <limits.h>
@@ -131,6 +132,7 @@ GraphicsStateGuardian(CoordinateSystem internal_coordinate_system,
   _active = true;
   _prepared_objects = new PreparedGraphicsObjects;
   _stereo_buffer_mask = ~0;
+  _incomplete_render = allow_incomplete_render;
 
   _is_hardware = false;
   _prefers_triangle_strips = false;
@@ -318,115 +320,46 @@ get_internal_coordinate_system() const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::reset
+//     Function: GraphicsStateGuardian::get_prepared_objects
 //       Access: Public, Virtual
-//  Description: Resets all internal state as if the gsg were newly
-//               created.
+//  Description: Returns the set of texture and geom objects that have
+//               been prepared with this GSG (and possibly other GSG's
+//               that share objects).
 ////////////////////////////////////////////////////////////////////
-void GraphicsStateGuardian::
-reset() {
-  _needs_reset = false;
-  _is_valid = false;
-
-  _state_rs = NULL;
-  _target_rs = NULL;
-  _state.clear_to_zero();
-  _target.clear_to_defaults();
-  _internal_transform = _cs_transform;
-  _scene_null = new SceneSetup;
-  _scene_setup = _scene_null;
-
-  _color_write_mask = ColorWriteAttrib::C_all;
-
-  _has_scene_graph_color = false;
-  _scene_graph_color.set(1.0f, 1.0f, 1.0f, 1.0f);
-  _transform_stale = true;
-  _color_blend_involves_color_scale = false;
-  _texture_involves_color_scale = false;
-  _vertex_colors_enabled = true;
-  _lighting_enabled = false;
-  _num_lights_enabled = 0;
-  _num_clip_planes_enabled = 0;
-
-  _color_scale_enabled = false;
-  _current_color_scale.set(1.0f, 1.0f, 1.0f, 1.0f);
-  _has_texture_alpha_scale = false;
-
-  _has_material_force_color = false;
-  _material_force_color.set(1.0f, 1.0f, 1.0f, 1.0f);
-  _light_color_scale.set(1.0f, 1.0f, 1.0f, 1.0f);
-
-  _tex_gen_modifies_mat = false;
-  _last_max_stage_index = 0;
-
-  _is_valid = true;
-
-  if (_stencil_render_states) {
-    delete _stencil_render_states;
-    _stencil_render_states = 0;
-  }
-  _stencil_render_states = new StencilRenderStates (this);
+PreparedGraphicsObjects *GraphicsStateGuardian::
+get_prepared_objects() {
+  return _prepared_objects;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::set_state_and_transform
-//       Access: Public
-//  Description: Simultaneously resets the render state and the
-//               transform state.
-//
-//               This transform specified is the "internal" net
-//               transform, already converted into the GSG's internal
-//               coordinate space by composing it to
-//               get_cs_transform().  (Previously, this used to be the
-//               "external" net transform, with the assumption that
-//               that GSG would convert it internally, but that is no
-//               longer the case.)
-//
-//               Special case: if (state==NULL), then the target
-//               state is already stored in _target.
+//     Function: GraphicsStateGuardian::set_gamma
+//       Access: Published, Virtual
+//  Description: Set gamma.  Returns true on success.
 ////////////////////////////////////////////////////////////////////
-void GraphicsStateGuardian::
-set_state_and_transform(const RenderState *state,
-                        const TransformState *trans) {
-}
+bool GraphicsStateGuardian::
+set_gamma(float gamma) {
+  _gamma = gamma;  
 
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::clear
-//       Access: Public
-//  Description: Clears the framebuffer within the current
-//               DisplayRegion, according to the flags indicated by
-//               the given DrawableRegion object.
-//
-//               This does not set the DisplayRegion first.  You
-//               should call prepare_display_region() to specify the
-//               region you wish the clear operation to apply to.
-////////////////////////////////////////////////////////////////////
-void GraphicsStateGuardian::
-clear(DrawableRegion *clearable) {
+  return false;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::get_render_buffer
-//       Access: Public
-//  Description: Returns a RenderBuffer object suitable for operating
-//               on the requested set of buffers.  buffer_type is the
-//               union of all the desired RenderBuffer::Type values.
+//     Function: GraphicsStateGuardian::get_gamma
+//       Access: Published
+//  Description: Get the current gamma setting.
 ////////////////////////////////////////////////////////////////////
-RenderBuffer GraphicsStateGuardian::
-get_render_buffer(int buffer_type, const FrameBufferProperties &prop) {
-  return RenderBuffer(this, buffer_type & prop.get_buffer_mask() & _stereo_buffer_mask);
+float GraphicsStateGuardian::
+get_gamma(float gamma) {
+  return _gamma;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::get_prepared_objects
-//       Access: Public, Virtual
-//  Description: Returns the set of texture and geom objects that have
-//               been prepared with this GSG (and possibly other GSG's
-//               that share objects).
+//     Function: GraphicsStateGuardian::restore_gamma
+//       Access: Published, Virtual
+//  Description: Restore original gamma setting.
 ////////////////////////////////////////////////////////////////////
-PreparedGraphicsObjects *GraphicsStateGuardian::
-get_prepared_objects() {
-  return _prepared_objects;
+void GraphicsStateGuardian::
+restore_gamma() {
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1152,6 +1085,26 @@ prepare_lens() {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::calc_projection_mat
+//       Access: Public, Virtual
+//  Description: Given a lens, this function calculates the appropriate
+//               projection matrix for this gsg.  The result depends
+//               on the peculiarities of the rendering API.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) GraphicsStateGuardian::
+calc_projection_mat(const Lens *lens) {
+  if (lens == (Lens *)NULL) {
+    return NULL;
+  }
+
+  if (!lens->is_linear()) {
+    return NULL;
+  }
+
+  return TransformState::make_identity();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::begin_frame
 //       Access: Public, Virtual
@@ -1462,6 +1415,106 @@ end_draw_primitives() {
   _data_reader = NULL;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::reset
+//       Access: Public, Virtual
+//  Description: Resets all internal state as if the gsg were newly
+//               created.
+////////////////////////////////////////////////////////////////////
+void GraphicsStateGuardian::
+reset() {
+  _needs_reset = false;
+  _is_valid = false;
+
+  _state_rs = NULL;
+  _target_rs = NULL;
+  _state.clear_to_zero();
+  _target.clear_to_defaults();
+  _internal_transform = _cs_transform;
+  _scene_null = new SceneSetup;
+  _scene_setup = _scene_null;
+
+  _color_write_mask = ColorWriteAttrib::C_all;
+
+  _has_scene_graph_color = false;
+  _scene_graph_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _transform_stale = true;
+  _color_blend_involves_color_scale = false;
+  _texture_involves_color_scale = false;
+  _vertex_colors_enabled = true;
+  _lighting_enabled = false;
+  _num_lights_enabled = 0;
+  _num_clip_planes_enabled = 0;
+
+  _color_scale_enabled = false;
+  _current_color_scale.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _has_texture_alpha_scale = false;
+
+  _has_material_force_color = false;
+  _material_force_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _light_color_scale.set(1.0f, 1.0f, 1.0f, 1.0f);
+
+  _tex_gen_modifies_mat = false;
+  _last_max_stage_index = 0;
+
+  _is_valid = true;
+
+  if (_stencil_render_states) {
+    delete _stencil_render_states;
+    _stencil_render_states = 0;
+  }
+  _stencil_render_states = new StencilRenderStates (this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::set_state_and_transform
+//       Access: Public
+//  Description: Simultaneously resets the render state and the
+//               transform state.
+//
+//               This transform specified is the "internal" net
+//               transform, already converted into the GSG's internal
+//               coordinate space by composing it to
+//               get_cs_transform().  (Previously, this used to be the
+//               "external" net transform, with the assumption that
+//               that GSG would convert it internally, but that is no
+//               longer the case.)
+//
+//               Special case: if (state==NULL), then the target
+//               state is already stored in _target.
+////////////////////////////////////////////////////////////////////
+void GraphicsStateGuardian::
+set_state_and_transform(const RenderState *state,
+                        const TransformState *trans) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::clear
+//       Access: Public
+//  Description: Clears the framebuffer within the current
+//               DisplayRegion, according to the flags indicated by
+//               the given DrawableRegion object.
+//
+//               This does not set the DisplayRegion first.  You
+//               should call prepare_display_region() to specify the
+//               region you wish the clear operation to apply to.
+////////////////////////////////////////////////////////////////////
+void GraphicsStateGuardian::
+clear(DrawableRegion *clearable) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_render_buffer
+//       Access: Public
+//  Description: Returns a RenderBuffer object suitable for operating
+//               on the requested set of buffers.  buffer_type is the
+//               union of all the desired RenderBuffer::Type values.
+////////////////////////////////////////////////////////////////////
+RenderBuffer GraphicsStateGuardian::
+get_render_buffer(int buffer_type, const FrameBufferProperties &prop) {
+  return RenderBuffer(this, buffer_type & prop.get_buffer_mask() & _stereo_buffer_mask);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::get_cs_transform
 //       Access: Public, Virtual
@@ -1798,6 +1851,58 @@ init_frame_pstats() {
 }
 #endif  // DO_PSTATS
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::create_gamma_table
+//       Access: Public, Static
+//  Description: Create a gamma table.
+////////////////////////////////////////////////////////////////////
+void GraphicsStateGuardian::
+create_gamma_table (float gamma, unsigned short *red_table, unsigned short *green_table, unsigned short *blue_table) {
+  int i;
+
+  if (gamma <= 0.0) {
+    // avoid divide by zero and negative exponents
+    gamma = 1.0;
+  }
+  
+  for (i = 0; i < 256; i++) {
+    double g;
+    double x;
+    float gamma_correction;
+    
+    x = ((double) i / 255.0);
+    gamma_correction = 1.0 / gamma;    
+    x = pow (x, (double) gamma_correction);
+    if (x > 1.00) {
+      x = 1.0;
+    }
+
+    g = x * 65535.0;    
+    red_table [i] = (int)g;
+    green_table [i] = (int)g;
+    blue_table [i] = (int)g;
+  }    
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::traverse_prepared_textures
+//       Access: Public
+//  Description: Calls the indicated function on all
+//               currently-prepared textures, or until the callback
+//               function returns false.
+////////////////////////////////////////////////////////////////////
+void GraphicsStateGuardian::
+traverse_prepared_textures(bool (*pertex_callbackfn)(TextureContext *,void *),void *callback_arg) {
+  PreparedGraphicsObjects::Textures::const_iterator ti;
+  for (ti = _prepared_objects->_prepared_textures.begin();
+       ti != _prepared_objects->_prepared_textures.end();
+       ++ti) {
+    bool bResult=(*pertex_callbackfn)(*ti,callback_arg);
+    if(!bResult)
+      return;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::enable_lighting
 //       Access: Protected, Virtual
@@ -2095,104 +2200,20 @@ get_untextured_state() {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::traverse_prepared_textures
-//       Access: Public
-//  Description: Calls the indicated function on all
-//               currently-prepared textures, or until the callback
-//               function returns false.
-////////////////////////////////////////////////////////////////////
-void GraphicsStateGuardian::
-traverse_prepared_textures(bool (*pertex_callbackfn)(TextureContext *,void *),void *callback_arg) {
-  PreparedGraphicsObjects::Textures::const_iterator ti;
-  for (ti = _prepared_objects->_prepared_textures.begin();
-       ti != _prepared_objects->_prepared_textures.end();
-       ++ti) {
-    bool bResult=(*pertex_callbackfn)(*ti,callback_arg);
-    if(!bResult)
-      return;
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::calc_projection_mat
-//       Access: Public, Virtual
-//  Description: Given a lens, this function calculates the appropriate
-//               projection matrix for this gsg.  The result depends
-//               on the peculiarities of the rendering API.
-////////////////////////////////////////////////////////////////////
-CPT(TransformState) GraphicsStateGuardian::
-calc_projection_mat(const Lens *lens) {
-  if (lens == (Lens *)NULL) {
-    return NULL;
-  }
-
-  if (!lens->is_linear()) {
-    return NULL;
-  }
-
-  return TransformState::make_identity();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::set_gamma
-//       Access: Published, Virtual
-//  Description: Set gamma.  Returns true on success.
-////////////////////////////////////////////////////////////////////
-bool GraphicsStateGuardian::
-set_gamma(float gamma) {
-  _gamma = gamma;  
-
-  return false;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::create_gamma_table
-//       Access: Published
-//  Description: Get the current gamma setting.
-////////////////////////////////////////////////////////////////////
-float GraphicsStateGuardian::
-get_gamma(float gamma) {
-  return _gamma;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::restore_gamma
-//       Access: Published, Virtual
-//  Description: Restore original gamma setting.
-////////////////////////////////////////////////////////////////////
-void GraphicsStateGuardian::
-restore_gamma() {
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: GraphicsStateGuardian::create_gamma_table
-//       Access: Public, Static
-//  Description: Create a gamma table.
+//     Function: GraphicsStateGuardian::async_reload_texture
+//       Access: Protected
+//  Description: Should be called when a texture is encountered that
+//               needs to have its RAM image reloaded, and
+//               get_incomplete_render() is true.  This will fire off
+//               a thread on the current Loader object that will
+//               request the texture to load its image.  The image
+//               will be available at some point in the future (no
+//               event will be generated).
 ////////////////////////////////////////////////////////////////////
 void GraphicsStateGuardian::
-create_gamma_table (float gamma, unsigned short *red_table, unsigned short *green_table, unsigned short *blue_table) {
-  int i;
-
-  if (gamma <= 0.0) {
-    // avoid divide by zero and negative exponents
-    gamma = 1.0;
-  }
-  
-  for (i = 0; i < 256; i++) {
-    double g;
-    double x;
-    float gamma_correction;
-    
-    x = ((double) i / 255.0);
-    gamma_correction = 1.0 / gamma;    
-    x = pow (x, (double) gamma_correction);
-    if (x > 1.00) {
-      x = 1.0;
-    }
+async_reload_texture(TextureContext *tc) {
+  nassertv(_loader != (Loader *)NULL);
 
-    g = x * 65535.0;    
-    red_table [i] = (int)g;
-    green_table [i] = (int)g;
-    blue_table [i] = (int)g;
-  }    
+  PT(AsyncTask) request = new TextureReloadRequest(tc);
+  _loader->load_async(request);
 }

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

@@ -44,6 +44,7 @@
 #include "texture.h"
 #include "occlusionQueryContext.h"
 #include "stencilRenderStates.h"
+#include "loader.h"
 
 class DrawableRegion;
 class GraphicsEngine;
@@ -90,6 +91,12 @@ PUBLISHED:
   INLINE bool is_valid() const;
   INLINE bool needs_reset() const;
 
+  INLINE void set_incomplete_render(bool incomplete_render);
+  virtual INLINE bool get_incomplete_render() const;
+
+  INLINE void set_loader(Loader *loader);
+  INLINE Loader *get_loader() const;
+
   INLINE GraphicsPipe *get_pipe() const;
   INLINE GraphicsEngine *get_engine() const;
   INLINE const GraphicsThreadingModel &get_threading_model() const;
@@ -268,6 +275,8 @@ public:
   static void init_frame_pstats();
 #endif
 
+  void traverse_prepared_textures(bool (*pertex_callbackfn)(TextureContext *,void *),void *callback_arg);
+
 protected:
   virtual void enable_lighting(bool enable);
   virtual void set_ambient_light(const Colorf &color);
@@ -293,6 +302,8 @@ protected:
   static CPT(RenderState) get_unclipped_state();
   static CPT(RenderState) get_untextured_state();
 
+  void async_reload_texture(TextureContext *tc);
+
 protected:
   PT(SceneSetup) _scene_null;
   PT(SceneSetup) _scene_setup;
@@ -353,6 +364,8 @@ protected:
   bool _is_valid;
   bool _closing_gsg;
   bool _active;
+  bool _incomplete_render;
+  PT(Loader) _loader;
 
   PT(PreparedGraphicsObjects) _prepared_objects;
 
@@ -482,9 +495,6 @@ private:
   GraphicsEngine *_engine;
   GraphicsThreadingModel _threading_model;
 
-public:
-  void traverse_prepared_textures(bool (*pertex_callbackfn)(TextureContext *,void *),void *callback_arg);
-
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

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

@@ -2377,7 +2377,7 @@ bool vertex_buffer_page_in_function (LruPage *lru_page)
     Not sure if this is the correct thing to do.  Can we return false
     from the page_in function?  Will we get called again next frame if
     we do?
-  if (allow_incomplete_render) {
+  if (_incomplete_render) {
     // Check if the data is resident before continuing.
     const unsigned char *data_pointer = reader->get_read_pointer(false);
     if (data_pointer == NULL) {
@@ -2431,7 +2431,7 @@ bool index_buffer_page_in_function (LruPage *lru_page)
     Not sure if this is the correct thing to do.  Can we return false
     from the page_in function?  Will we get called again next frame if
     we do?
-  if (allow_incomplete_render) {
+  if (_incomplete_render) {
     // Check if the data is resident before continuing.
     const unsigned char *data_pointer = reader.get_read_pointer(false);
     if (data_pointer == NULL) {

+ 91 - 7
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -290,7 +290,6 @@ CLP(GraphicsStateGuardian)::
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 reset() {
-  cerr << "begin reset\n";
   free_pointers();
   GraphicsStateGuardian::reset();
 
@@ -1332,7 +1331,6 @@ reset() {
   // Now that the GSG has been initialized, make it available for
   // optimizations.
   add_gsg(this);
-  cerr << "end reset()\n";
 }
 
 
@@ -6393,7 +6391,10 @@ update_standard_texture_bindings() {
     }
     GLP(Enable)(target);
     
-    apply_texture(tc);
+    if (!apply_texture(tc)) {
+      GLP(Disable)(target);
+      break;
+    }
     
     if (stage->involves_color_scale() && _color_scale_enabled) {
       Colorf color = stage->get_color();
@@ -6939,14 +6940,14 @@ specify_texture(Texture *tex) {
 //               texture, and makes it the current texture available
 //               for rendering.
 ////////////////////////////////////////////////////////////////////
-void CLP(GraphicsStateGuardian)::
+bool CLP(GraphicsStateGuardian)::
 apply_texture(TextureContext *tc) {
   CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
 
   gtc->set_active(true);
   GLenum target = get_texture_target(gtc->get_texture()->get_texture_type());
   if (target == GL_NONE) {
-    return;
+    return false;
   }
   GLP(BindTexture)(target, gtc->_index);
 
@@ -6954,8 +6955,12 @@ apply_texture(TextureContext *tc) {
     // If the texture image was modified, reload the texture.  This
     // means we also re-specify the properties for good measure.
     specify_texture(gtc->get_texture());
-    upload_texture(gtc);
-    gtc->mark_loaded();
+    bool okflag = upload_texture(gtc);
+    if (!okflag) {
+      GLCAT.error()
+        << "Could not load " << *gtc->get_texture() << "\n";
+      return false;
+    }
 
   } else if (gtc->was_properties_modified()) {
     // If only the properties have been modified, we don't necessarily
@@ -6965,6 +6970,7 @@ apply_texture(TextureContext *tc) {
   }
 
   report_my_gl_errors();
+  return true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -6980,6 +6986,21 @@ bool CLP(GraphicsStateGuardian)::
 upload_texture(CLP(TextureContext) *gtc) {
   Texture *tex = gtc->get_texture();
 
+  if (_incomplete_render && 
+      !tex->has_ram_image() && tex->might_have_ram_image() &&
+      tex->has_simple_ram_image() &&
+      !_loader.is_null()) {
+    // If we don't have the texture data right now, go get it, but in
+    // the meantime load a temporary simple image in its place.
+    async_reload_texture(gtc);
+    if (!tex->has_ram_image()) {
+      if (gtc->was_simple_image_modified()) {
+        return upload_simple_texture(gtc);
+      }
+      return true;
+    }
+  }
+
   CPTA_uchar image = tex->get_ram_image();
 
   Texture::CompressionMode image_compression;
@@ -7139,6 +7160,7 @@ upload_texture(CLP(TextureContext) *gtc) {
 #endif
 
     tex->texture_uploaded();
+    gtc->mark_loaded();
 
     report_my_gl_errors();
     return true;
@@ -7462,6 +7484,68 @@ upload_texture_image(CLP(TextureContext) *gtc,
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GLGraphicsStateGuardian::upload_simple_texture
+//       Access: Protected
+//  Description: This is used as a standin for upload_texture
+//               when the texture in question is unavailable (e.g. it
+//               hasn't yet been loaded from disk).  Until the texture
+//               image itself becomes available, we will render the
+//               texture's "simple" image--a sharply reduced version
+//               of the same texture.
+////////////////////////////////////////////////////////////////////
+bool CLP(GraphicsStateGuardian)::
+upload_simple_texture(CLP(TextureContext) *gtc) {
+  report_my_gl_errors();
+
+  PStatTimer timer(_load_texture_pcollector);
+  Texture *tex = gtc->get_texture();
+  nassertr(tex != (Texture *)NULL, false);
+
+  int internal_format = GL_RGBA;
+  int external_format = GL_BGRA;
+
+  const unsigned char *image_ptr = tex->get_simple_ram_image();
+  if (image_ptr == (const unsigned char *)NULL) {
+    return false;
+  }
+
+  size_t image_size = tex->get_simple_ram_image_size();
+  PTA_uchar bgr_image;
+  if (!_supports_bgr) {
+    // If the GL doesn't claim to support BGR, we may have to reverse
+    // the component ordering of the image.
+    external_format = GL_RGBA;
+    image_ptr = fix_component_ordering(bgr_image, image_ptr, image_size,
+                                       external_format, tex);
+  }
+
+  int width = tex->get_simple_x_size();
+  int height = tex->get_simple_y_size();
+  int component_type = GL_UNSIGNED_BYTE;
+
+  if (GLCAT.is_debug()) {
+    GLCAT.debug()
+      << "loading simple image for " << tex->get_name() << "\n";
+  }
+
+  // Turn off mipmaps for the simple texture.
+  if (tex->uses_mipmaps()) {
+    if (is_at_least_version(1, 2)) {
+      GLP(TexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+    }
+  }
+
+  GLP(TexImage2D)(GL_TEXTURE_2D, 0, internal_format,
+                  width, height, 0,
+                  external_format, component_type, image_ptr);
+
+  gtc->mark_simple_loaded();
+
+  report_my_gl_errors();
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GLGraphicsStateGuardian::get_texture_memory_size
 //       Access: Protected

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

@@ -323,7 +323,7 @@ protected:
 
   void do_auto_rescale_normal();
   void specify_texture(Texture *tex);
-  void apply_texture(TextureContext *tc);
+  bool apply_texture(TextureContext *tc);
   bool upload_texture(CLP(TextureContext) *gtc);
   bool upload_texture_image(CLP(TextureContext) *gtc,
                             bool uses_mipmaps, int mipmap_bias,
@@ -331,7 +331,8 @@ protected:
                             GLint internal_format, GLint external_format, 
                             GLenum component_type,
                             bool one_page_only, int z,
-          Texture::CompressionMode image_compression);
+                            Texture::CompressionMode image_compression);
+  bool upload_simple_texture(CLP(TextureContext) *gtc);
 
   size_t get_texture_memory_size(Texture *tex);
   void check_nonresident_texture(BufferContextChain &chain);

+ 34 - 8
panda/src/gobj/config_gobj.cxx

@@ -108,6 +108,15 @@ ConfigVariableBool preload_textures
           "wasted memory from Textures that are created but never used "
           "to render."));
 
+ConfigVariableBool preload_simple_textures
+("preload-simple-textures", false,
+ PRC_DESC("When this is true, every texture image will have a simple "
+          "image generated for it at load time.  (Normally, textures "
+          "get a simple image at egg2bam time.)  This slows the initial "
+          "loading time of textures, but allows you to take advantage "
+          "of gsg::set_incomplete_render() to load textures on-the-fly "
+          "in a sub-thread."));
+
 ConfigVariableBool compressed_textures
 ("compressed-textures", false,
  PRC_DESC("Set this to true to compress textures as they are loaded into "
@@ -253,6 +262,31 @@ ConfigVariableBool textures_auto_power_2
           "you then open a second window that doesn't support the same "
           "capabilities, it will have no choice but to print an error message."));
 
+ConfigVariableBool textures_header_only
+("textures-header-only", false,
+ PRC_DESC("If this is true, texture images will not actually be loaded from "
+          "disk, but the image header information will be consulted to verify "
+          "number of channels and so forth.  The texture images themselves "
+          "will be generated in a default blue color."));
+
+ConfigVariableInt simple_image_size
+("simple-image-size", "16 16",
+ PRC_DESC("This is an x y pair that specifies the maximum size of an "
+          "automatically-generated "
+          "texture simple image.  The simple image can displayed before "
+          "the texture has been loaded from disk."));
+
+ConfigVariableDouble simple_image_threshold
+("simple-image-threshold", 0.1,
+ PRC_DESC("This is a value that indicates how closely a texture's " 
+          "generated simple "
+          "image should approximate the original image.  The smaller the "
+          "number, the closer the match; small numbers will result in "
+          "simple images close to the maximum size specified by "
+          "simple-image-size.  Larger numbers will result in smaller "
+          "simple images.  Generally the value should be considerably "
+          "less than 1."));
+
 ConfigVariableEnum<ShaderUtilization> shader_utilization
 ("shader-utilization", SUT_none,
  PRC_DESC("At times, panda may generate shaders.  This variable controls what "
@@ -270,14 +304,6 @@ ConfigVariableBool shader_auto_utilization
           "you then open a second window that doesn't support the same "
           "capabilities, it will have no choice but to print an error message."));
 
-extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_header_only;
-ConfigVariableBool textures_header_only
-("textures-header-only", false,
- PRC_DESC("If this is true, texture images will not actually be loaded from "
-          "disk, but the image header information will be consulted to verify "
-          "number of channels and so forth.  The texture images themselves "
-          "will be generated in a default blue color."));
-
 ConfigVariableInt geom_cache_size
 ("geom-cache-size", 5000,
  PRC_DESC("Specifies the maximum number of entries in the cache "

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

@@ -52,6 +52,7 @@ extern EXPCL_PANDA_GOBJ ConfigVariableList exclude_texture_scale;
 
 extern EXPCL_PANDA_GOBJ ConfigVariableBool keep_texture_ram;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool preload_textures;
+extern EXPCL_PANDA_GOBJ ConfigVariableBool preload_simple_textures;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool compressed_textures;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool vertex_buffers;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool vertex_arrays;
@@ -69,6 +70,8 @@ extern EXPCL_PANDA_GOBJ ConfigVariableEnum<AutoTextureScale> textures_power_2;
 extern EXPCL_PANDA_GOBJ ConfigVariableEnum<AutoTextureScale> textures_square;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_auto_power_2;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_header_only;
+extern EXPCL_PANDA_GOBJ ConfigVariableInt simple_image_size;
+extern EXPCL_PANDA_GOBJ ConfigVariableDouble simple_image_threshold;
 
 extern EXPCL_PANDA_GOBJ ConfigVariableEnum<ShaderUtilization> shader_utilization;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool shader_auto_utilization;

+ 11 - 0
panda/src/gobj/preparedGraphicsObjects.cxx

@@ -194,6 +194,17 @@ release_texture(TextureContext *tc) {
   _released_textures.insert(tc);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PreparedGraphicsObjects::release_texture
+//       Access: Public
+//  Description: Releases a texture if it has already been prepared,
+//               or removes it from the preparation queue.
+////////////////////////////////////////////////////////////////////
+void PreparedGraphicsObjects::
+release_texture(Texture *tex) {
+  tex->release(this);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PreparedGraphicsObjects::release_all_textures
 //       Access: Public

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

@@ -71,6 +71,7 @@ PUBLISHED:
   bool dequeue_texture(Texture *tex);
   bool is_texture_prepared(const Texture *tex) const;
   void release_texture(TextureContext *tc);
+  void release_texture(Texture *tex);
   int release_all_textures();
   int get_num_queued_textures() const;
   int get_num_prepared_textures() const;

+ 128 - 4
panda/src/gobj/texture.I

@@ -129,9 +129,10 @@ setup_cube_map(int size, ComponentType component_type,
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 read(const Filename &fullpath) {
+  ReMutexHolder holder(_lock);
   clear();
   return do_read(fullpath, Filename(), 0, 0, 0, 0, false, false, 
-                 !preload_textures, NULL);
+                 !preload_textures && !preload_simple_textures, NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -148,9 +149,11 @@ read(const Filename &fullpath) {
 INLINE bool Texture::
 read(const Filename &fullpath, const Filename &alpha_fullpath,
      int primary_file_num_channels, int alpha_file_channel) {
+  ReMutexHolder holder(_lock);
   clear();
   return do_read(fullpath, alpha_fullpath, primary_file_num_channels,
-		 alpha_file_channel, 0, 0, false, false, !preload_textures, 
+		 alpha_file_channel, 0, 0, false, false, 
+                 !preload_textures && !preload_simple_textures, 
                  NULL);
 }
 
@@ -167,10 +170,11 @@ read(const Filename &fullpath, const Filename &alpha_fullpath,
 INLINE bool Texture::
 read(const Filename &fullpath, int z, int n, 
      bool read_pages, bool read_mipmaps) {
+  ReMutexHolder holder(_lock);
   ++_properties_modified;
   ++_image_modified;
   return do_read(fullpath, Filename(), 0, 0, z, n, read_pages, read_mipmaps,
-                 !preload_textures, NULL);
+                 !preload_textures && !preload_simple_textures, NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -247,11 +251,12 @@ read(const Filename &fullpath, const Filename &alpha_fullpath,
      int primary_file_num_channels, int alpha_file_channel,
      int z, int n, bool read_pages, bool read_mipmaps,
      BamCacheRecord *record) {
+  ReMutexHolder holder(_lock);
   ++_properties_modified;
   ++_image_modified;
   return do_read(fullpath, alpha_fullpath, primary_file_num_channels,
 		 alpha_file_channel, z, n, read_pages, read_mipmaps,
-                 !preload_textures, record);
+                 !preload_textures && !preload_simple_textures, record);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -261,6 +266,7 @@ read(const Filename &fullpath, const Filename &alpha_fullpath,
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 write(const Filename &fullpath) {
+  ReMutexHolder holder(_lock);
   return do_write(fullpath, 0, 0, false, false);
 }
 
@@ -321,6 +327,7 @@ write(const Filename &fullpath) {
 INLINE bool Texture::
 write(const Filename &fullpath, int z, int n, 
       bool write_pages, bool write_mipmaps) {
+  ReMutexHolder holder(_lock);
   return do_write(fullpath, z, n, write_pages, write_mipmaps);
 }
 
@@ -331,6 +338,7 @@ write(const Filename &fullpath, int z, int n,
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 load(const PNMImage &pnmimage) {
+  ReMutexHolder holder(_lock);
   clear();
   return do_load_one(pnmimage, get_name(), 0, 0);
 }
@@ -343,6 +351,7 @@ load(const PNMImage &pnmimage) {
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 load(const PNMImage &pnmimage, int z, int n) {
+  ReMutexHolder holder(_lock);
   ++_properties_modified;
   ++_image_modified;
   return do_load_one(pnmimage, get_name(), z, n);
@@ -356,6 +365,7 @@ load(const PNMImage &pnmimage, int z, int n) {
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 store(PNMImage &pnmimage) const {
+  ReMutexHolder holder(_lock);
   return do_store_one(pnmimage, 0, 0);
 }
 
@@ -367,6 +377,7 @@ store(PNMImage &pnmimage) const {
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 store(PNMImage &pnmimage, int z, int n) const {
+  ReMutexHolder holder(_lock);
   return do_store_one(pnmimage, z, n);
 }
 
@@ -378,6 +389,7 @@ store(PNMImage &pnmimage, int z, int n) const {
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 has_filename() const {
+  ReMutexHolder holder(_lock);
   return !_filename.empty();
 }
 
@@ -390,6 +402,7 @@ has_filename() const {
 ////////////////////////////////////////////////////////////////////
 INLINE const Filename &Texture::
 get_filename() const {
+  ReMutexHolder holder(_lock);
   return _filename;
 }
 
@@ -401,6 +414,7 @@ get_filename() const {
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 has_alpha_filename() const {
+  ReMutexHolder holder(_lock);
   return !_alpha_filename.empty();
 }
 
@@ -414,6 +428,7 @@ has_alpha_filename() const {
 ////////////////////////////////////////////////////////////////////
 INLINE const Filename &Texture::
 get_alpha_filename() const {
+  ReMutexHolder holder(_lock);
   return _alpha_filename;
 }
 
@@ -425,6 +440,7 @@ get_alpha_filename() const {
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 has_fullpath() const {
+  ReMutexHolder holder(_lock);
   return !_fullpath.empty();
 }
 
@@ -437,6 +453,7 @@ has_fullpath() const {
 ////////////////////////////////////////////////////////////////////
 INLINE const Filename &Texture::
 get_fullpath() const {
+  ReMutexHolder holder(_lock);
   return _fullpath;
 }
 
@@ -448,6 +465,7 @@ get_fullpath() const {
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 has_alpha_fullpath() const {
+  ReMutexHolder holder(_lock);
   return !_alpha_fullpath.empty();
 }
 
@@ -461,6 +479,7 @@ has_alpha_fullpath() const {
 ////////////////////////////////////////////////////////////////////
 INLINE const Filename &Texture::
 get_alpha_fullpath() const {
+  ReMutexHolder holder(_lock);
   return _alpha_fullpath;
 }
 
@@ -546,6 +565,7 @@ get_pad_z_size() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 set_pad_size(int x, int y, int z) {
+  ReMutexHolder holder(_lock);
   if (x > _x_size) x = _x_size;
   if (y > _y_size) y = _y_size;
   if (z > _z_size) z = _z_size;
@@ -770,6 +790,7 @@ get_quality_level() const {
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 might_have_ram_image() const {
+  ReMutexHolder holder(_lock);
   return (has_ram_image() || has_filename());
 }
 
@@ -781,6 +802,7 @@ might_have_ram_image() const {
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 get_ram_image_size() const {
+  ReMutexHolder holder(_lock);
   if (_ram_images.empty()) {
     return 0;
   }
@@ -801,6 +823,7 @@ get_ram_image_size() const {
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 get_ram_page_size() const {
+  ReMutexHolder holder(_lock);
   if (_ram_image_compression == CM_off || _ram_images.empty()) {
     return get_expected_ram_page_size();
   } else {
@@ -817,6 +840,7 @@ get_ram_page_size() const {
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 get_expected_ram_image_size() const {
+  ReMutexHolder holder(_lock);
   return get_expected_ram_page_size() * (size_t)_z_size;
 }
 
@@ -830,6 +854,7 @@ get_expected_ram_image_size() const {
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 get_expected_ram_page_size() const {
+  ReMutexHolder holder(_lock);
   return (size_t)(_x_size * _y_size * _num_components * _component_width);
 }
 
@@ -860,6 +885,7 @@ get_ram_image_compression() const {
 ////////////////////////////////////////////////////////////////////
 INLINE PTA_uchar Texture::
 modify_ram_image() {
+  ReMutexHolder holder(_lock);
   do_modify_ram_image();
   ++_image_modified;
   return _ram_images[0]._image;
@@ -876,6 +902,7 @@ modify_ram_image() {
 ////////////////////////////////////////////////////////////////////
 INLINE PTA_uchar Texture::
 make_ram_image() {
+  ReMutexHolder holder(_lock);
   ++_image_modified;
   do_make_ram_image();
   return _ram_images[0]._image;
@@ -911,6 +938,7 @@ set_keep_ram_image(bool keep_ram_image) {
 ////////////////////////////////////////////////////////////////////
 INLINE int Texture::
 get_num_ram_mipmap_images() const {
+  ReMutexHolder holder(_lock);
   return _ram_images.size();
 }
 
@@ -926,6 +954,7 @@ get_num_ram_mipmap_images() const {
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 has_ram_mipmap_image(int n) const {
+  ReMutexHolder holder(_lock);
   return (n >= 0 && n < (int)_ram_images.size() && !_ram_images[n]._image.empty());
 }
 
@@ -938,6 +967,7 @@ has_ram_mipmap_image(int n) const {
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 get_ram_mipmap_image_size(int n) const {
+  ReMutexHolder holder(_lock);
   if (n >= 0 && n < (int)_ram_images.size()) {
     return _ram_images[n]._image.size();
   }
@@ -959,6 +989,7 @@ get_ram_mipmap_image_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 get_ram_mipmap_page_size(int n) const {
+  ReMutexHolder holder(_lock);
   if (_ram_image_compression != CM_off) {
     if (n >= 0 && n < (int)_ram_images.size()) {
       return _ram_images[n]._page_size;
@@ -978,6 +1009,7 @@ get_ram_mipmap_page_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 get_expected_ram_mipmap_image_size(int n) const {
+  ReMutexHolder holder(_lock);
   return get_expected_ram_mipmap_page_size(n) * (size_t)get_expected_mipmap_z_size(n);
 }
 
@@ -991,6 +1023,7 @@ get_expected_ram_mipmap_image_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 get_expected_ram_mipmap_page_size(int n) const {
+  ReMutexHolder holder(_lock);
   return (size_t)(get_expected_mipmap_x_size(n) * get_expected_mipmap_y_size(n) * _num_components * _component_width);
 }
 
@@ -1006,11 +1039,79 @@ get_expected_ram_mipmap_page_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 INLINE PTA_uchar Texture::
 modify_ram_mipmap_image(int n) {
+  ReMutexHolder holder(_lock);
   do_modify_ram_mipmap_image(n);
   ++_image_modified;
   return _ram_images[n]._image;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_simple_x_size
+//       Access: Published
+//  Description: Returns the width of the "simple" image in texels.
+////////////////////////////////////////////////////////////////////
+INLINE int Texture::
+get_simple_x_size() const {
+  return _simple_x_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_simple_y_size
+//       Access: Published
+//  Description: Returns the height of the "simple" image in texels.
+////////////////////////////////////////////////////////////////////
+INLINE int Texture::
+get_simple_y_size() const {
+  return _simple_y_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::has_simple_ram_image
+//       Access: Published, Virtual
+//  Description: Returns true if the Texture has a "simple" image
+//               available in main RAM.
+////////////////////////////////////////////////////////////////////
+INLINE bool Texture::
+has_simple_ram_image() const {
+  ReMutexHolder holder(_lock);
+  return !_simple_ram_image._image.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_simple_ram_image_size
+//       Access: Published
+//  Description: Returns the number of bytes used by the "simple"
+//               image, or 0 if there is no simple image.
+////////////////////////////////////////////////////////////////////
+INLINE size_t Texture::
+get_simple_ram_image_size() const {
+  ReMutexHolder holder(_lock);
+  return _simple_ram_image._image.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_simple_ram_image
+//       Access: Published
+//  Description: Returns the image data associated with the "simple"
+//               texture image.  This is provided for some textures as
+//               an option to display while the main texture image is
+//               being loaded from disk.
+//
+//               Unlike get_ram_image(), this function will always
+//               return immediately.  Either the simple image is
+//               available, or it is not.
+//
+//               The "simple" image is always 4 components, 1 byte
+//               each, regardless of the parameters of the full
+//               texture.  The simple image is only supported for
+//               ordinary 2-d textures.
+////////////////////////////////////////////////////////////////////
+INLINE CPTA_uchar Texture::
+get_simple_ram_image() const {
+  ReMutexHolder holder(_lock);
+  return _simple_ram_image._image;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::get_properties_modified
 //       Access: Published
@@ -1035,6 +1136,18 @@ get_image_modified() const {
   return _image_modified;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::get_simple_image_modified
+//       Access: Published
+//  Description: Returns a sequence number which is guaranteed to
+//               change at least every time the texture's "simple"
+//               image data is modified.
+////////////////////////////////////////////////////////////////////
+INLINE UpdateSeq Texture::
+get_simple_image_modified() const {
+  return _simple_image_modified;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::set_filename
 //       Access: Published
@@ -1050,6 +1163,7 @@ get_image_modified() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 set_filename(const Filename &filename) {
+  ReMutexHolder holder(_lock);
   _filename = filename;
 }
 
@@ -1061,6 +1175,7 @@ set_filename(const Filename &filename) {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 clear_filename() {
+  ReMutexHolder holder(_lock);
   _filename = Filename();
 }
 
@@ -1082,6 +1197,7 @@ clear_filename() {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 set_alpha_filename(const Filename &alpha_filename) {
+  ReMutexHolder holder(_lock);
   _alpha_filename = alpha_filename;
 }
 
@@ -1093,6 +1209,7 @@ set_alpha_filename(const Filename &alpha_filename) {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 clear_alpha_filename() {
+  ReMutexHolder holder(_lock);
   _alpha_filename = Filename();
 }
 
@@ -1106,6 +1223,7 @@ clear_alpha_filename() {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 set_fullpath(const Filename &fullpath) {
+  ReMutexHolder holder(_lock);
   _fullpath = fullpath;
 }
 
@@ -1117,6 +1235,7 @@ set_fullpath(const Filename &fullpath) {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 clear_fullpath() {
+  ReMutexHolder holder(_lock);
   _fullpath = Filename();
 }
 
@@ -1131,6 +1250,7 @@ clear_fullpath() {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 set_alpha_fullpath(const Filename &alpha_fullpath) {
+  ReMutexHolder holder(_lock);
   _alpha_fullpath = alpha_fullpath;
 }
 
@@ -1142,6 +1262,7 @@ set_alpha_fullpath(const Filename &alpha_fullpath) {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 clear_alpha_fullpath() {
+  ReMutexHolder holder(_lock);
   _alpha_fullpath = Filename();
 }
 
@@ -1154,6 +1275,7 @@ clear_alpha_fullpath() {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 set_x_size(int x_size) {
+  ReMutexHolder holder(_lock);
   if (_x_size != x_size) {
     _x_size = x_size;
     ++_image_modified;
@@ -1171,6 +1293,7 @@ set_x_size(int x_size) {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 set_y_size(int y_size) {
+  ReMutexHolder holder(_lock);
   if (_y_size != y_size) {
     nassertv(_texture_type != Texture::TT_1d_texture || y_size == 1);
     _y_size = y_size;
@@ -1189,6 +1312,7 @@ set_y_size(int y_size) {
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 set_z_size(int z_size) {
+  ReMutexHolder holder(_lock);
   if (_z_size != z_size) {
     nassertv(_texture_type == Texture::TT_3d_texture ||
              (_texture_type == Texture::TT_cube_map && z_size == 6) ||

+ 259 - 56
panda/src/gobj/texture.cxx

@@ -96,6 +96,10 @@ Texture(const string &name) :
   _has_read_pages = false;
   _has_read_mipmaps = false;
   _num_mipmap_levels_read = 0;
+
+  _simple_x_size = 0;
+  _simple_y_size = 0;
+  _simple_ram_image._page_size = 0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -105,44 +109,12 @@ Texture(const string &name) :
 //               an existing Texture.
 ////////////////////////////////////////////////////////////////////
 Texture::
-Texture(const Texture &copy) :
-  Namable(copy),
-  _filename(copy._filename),
-  _alpha_filename(copy._alpha_filename),
-  _fullpath(copy._fullpath),
-  _alpha_fullpath(copy._alpha_fullpath),
-  _primary_file_num_channels(copy._primary_file_num_channels),
-  _alpha_file_channel(copy._alpha_file_channel),
-  _x_size(copy._x_size),
-  _y_size(copy._y_size),
-  _z_size(copy._z_size),
-  _num_components(copy._num_components),
-  _component_width(copy._component_width),
-  _texture_type(copy._texture_type),
-  _format(copy._format),
-  _component_type(copy._component_type),
-  _loaded_from_image(copy._loaded_from_image),
-  _loaded_from_txo(copy._loaded_from_txo),
-  _has_read_pages(copy._has_read_pages),
-  _has_read_mipmaps(copy._has_read_mipmaps),
-  _num_mipmap_levels_read(copy._num_mipmap_levels_read),
-  _wrap_u(copy._wrap_u),
-  _wrap_v(copy._wrap_v),
-  _wrap_w(copy._wrap_w),
-  _minfilter(copy._minfilter),
-  _magfilter(copy._magfilter),
-  _anisotropic_degree(copy._anisotropic_degree),
-  _keep_ram_image(copy._keep_ram_image),
-  _border_color(copy._border_color),
-  _compression(copy._compression),
-  _match_framebuffer_format(copy._match_framebuffer_format),
-  _quality_level(copy._quality_level),
-  _pad_x_size(copy._pad_x_size),
-  _pad_y_size(copy._pad_y_size),
-  _pad_z_size(copy._pad_z_size),
-  _ram_image_compression(copy._ram_image_compression),
-  _ram_images(copy._ram_images)
-{
+Texture(const Texture &copy) {
+  _has_read_pages = false;
+  _has_read_mipmaps = false;
+  _num_mipmap_levels_read = 0;
+
+  operator = (copy);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -154,6 +126,10 @@ Texture(const Texture &copy) :
 void Texture::
 operator = (const Texture &copy) {
   Namable::operator = (copy);
+
+  ReMutexHolder holder(_lock);
+  ReMutexHolder holder2(copy._lock);
+
   _filename = copy._filename;
   _alpha_filename = copy._alpha_filename;
   if (!copy._fullpath.empty()) {
@@ -190,8 +166,13 @@ operator = (const Texture &copy) {
   _quality_level = copy._quality_level;
   _ram_image_compression = copy._ram_image_compression;
   _ram_images = copy._ram_images;
+  _simple_x_size = copy._simple_x_size;
+  _simple_y_size = copy._simple_y_size;
+  _simple_ram_image = copy._simple_ram_image;
+
   ++_properties_modified;
   ++_image_modified;
+  ++_simple_image_modified;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -245,6 +226,7 @@ void Texture::
 setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
               int z_size, Texture::ComponentType component_type,
               Texture::Format format) {
+  ReMutexHolder holder(_lock);
   if (texture_type == TT_cube_map) {
     // Cube maps must always consist of six square images.
     nassertv(x_size == y_size && z_size == 6);
@@ -256,6 +238,9 @@ setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
     _wrap_v = WM_clamp;
     _wrap_w = WM_clamp;
   }
+  if (texture_type != TT_2d_texture) {
+    clear_simple_ram_image();
+  }
 
   _texture_type = texture_type;
   _x_size = x_size;
@@ -284,6 +269,7 @@ setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
 ////////////////////////////////////////////////////////////////////
 void Texture::
 generate_normalization_cube_map(int size) {
+  ReMutexHolder holder(_lock);
   setup_cube_map(size, T_unsigned_byte, F_rgb);
   PTA_uchar image = make_ram_image();
   _keep_ram_image = true;
@@ -385,6 +371,7 @@ generate_normalization_cube_map(int size) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 generate_alpha_scale_map() {
+  ReMutexHolder holder(_lock);
   setup_1d_texture(256, T_unsigned_byte, F_alpha);
   set_wrap_u(WM_clamp);
   set_minfilter(FT_nearest);
@@ -416,6 +403,7 @@ generate_alpha_scale_map() {
 ////////////////////////////////////////////////////////////////////
 size_t Texture::
 estimate_texture_memory() const {
+  ReMutexHolder holder(_lock);
   size_t pixels = get_x_size() * get_y_size();
 
   size_t bpp = 4;
@@ -487,6 +475,7 @@ estimate_texture_memory() const {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_aux_data(const string &key, TypedReferenceCount *aux_data) {
+  ReMutexHolder holder(_lock);
   _aux_data[key] = aux_data;
 }
 
@@ -498,6 +487,7 @@ set_aux_data(const string &key, TypedReferenceCount *aux_data) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 clear_aux_data(const string &key) {
+  ReMutexHolder holder(_lock);
   _aux_data.erase(key);
 }
 
@@ -510,6 +500,7 @@ clear_aux_data(const string &key) {
 ////////////////////////////////////////////////////////////////////
 TypedReferenceCount *Texture::
 get_aux_data(const string &key) const {
+  ReMutexHolder holder(_lock);
   AuxData::const_iterator di;
   di = _aux_data.find(key);
   if (di != _aux_data.end()) {
@@ -529,6 +520,7 @@ get_aux_data(const string &key) const {
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 read_txo(istream &in, const string &filename) {
+  ReMutexHolder holder(_lock);
   DatagramInputFile din;
 
   if (!din.open(in)) {
@@ -605,6 +597,7 @@ read_txo(istream &in, const string &filename) {
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 write_txo(ostream &out, const string &filename) const {
+  ReMutexHolder holder(_lock);
   DatagramOutputFile dout;
 
   if (!dout.open(out)) {
@@ -651,6 +644,7 @@ write_txo(ostream &out, const string &filename) const {
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 reload() {
+  ReMutexHolder holder(_lock);
   if (_loaded_from_image && has_filename()) {
     reload_ram_image();
     ++_image_modified;
@@ -671,6 +665,7 @@ reload() {
 ////////////////////////////////////////////////////////////////////
 Texture *Texture::
 load_related(const InternalName *suffix) const {
+  ReMutexHolder holder(_lock);
   RelatedTextures::const_iterator ti;
   ti = _related_textures.find(suffix);
   if (ti != _related_textures.end()) {
@@ -719,6 +714,7 @@ load_related(const InternalName *suffix) const {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_wrap_u(Texture::WrapMode wrap) {
+  ReMutexHolder holder(_lock);
   if (_wrap_u != wrap) {
     ++_properties_modified;
     _wrap_u = wrap;
@@ -732,6 +728,7 @@ set_wrap_u(Texture::WrapMode wrap) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_wrap_v(Texture::WrapMode wrap) {
+  ReMutexHolder holder(_lock);
   if (_wrap_v != wrap) {
     ++_properties_modified;
     _wrap_v = wrap;
@@ -745,6 +742,7 @@ set_wrap_v(Texture::WrapMode wrap) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_wrap_w(Texture::WrapMode wrap) {
+  ReMutexHolder holder(_lock);
   if (_wrap_w != wrap) {
     ++_properties_modified;
     _wrap_w = wrap;
@@ -758,6 +756,7 @@ set_wrap_w(Texture::WrapMode wrap) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_minfilter(Texture::FilterType filter) {
+  ReMutexHolder holder(_lock);
   if (_minfilter != filter) {
     ++_properties_modified;
     _minfilter = filter;
@@ -771,6 +770,7 @@ set_minfilter(Texture::FilterType filter) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_magfilter(Texture::FilterType filter) {
+  ReMutexHolder holder(_lock);
   if (_magfilter != filter) {
     ++_properties_modified;
     _magfilter = filter;
@@ -788,6 +788,7 @@ set_magfilter(Texture::FilterType filter) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_anisotropic_degree(int anisotropic_degree) {
+  ReMutexHolder holder(_lock);
   if (_anisotropic_degree != anisotropic_degree) {
     ++_properties_modified;
     _anisotropic_degree = anisotropic_degree;
@@ -804,6 +805,7 @@ set_anisotropic_degree(int anisotropic_degree) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_border_color(const Colorf &color) {
+  ReMutexHolder holder(_lock);
   if (_border_color != color) {
     ++_properties_modified;
     _border_color = color;
@@ -831,6 +833,7 @@ set_border_color(const Colorf &color) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_compression(Texture::CompressionMode compression) {
+  ReMutexHolder holder(_lock);
   if (_compression != compression) {
     ++_properties_modified;
     _compression = compression;
@@ -870,6 +873,7 @@ set_render_to_texture(bool render_to_texture) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_quality_level(Texture::QualityLevel quality_level) {
+  ReMutexHolder holder(_lock);
   if (_quality_level != quality_level) {
     ++_properties_modified;
     _quality_level = quality_level;
@@ -888,6 +892,7 @@ set_quality_level(Texture::QualityLevel quality_level) {
 ////////////////////////////////////////////////////////////////////
 int Texture::
 get_expected_num_mipmap_levels() const {
+  ReMutexHolder holder(_lock);
   int size = max(_x_size, max(_y_size, _z_size));
   int count = 1;
   while (size > 1) {
@@ -905,6 +910,7 @@ get_expected_num_mipmap_levels() const {
 ////////////////////////////////////////////////////////////////////
 int Texture::
 get_expected_mipmap_x_size(int n) const {
+  ReMutexHolder holder(_lock);
   int size = max(_x_size, 1);
   while (n > 0 && size > 1) {
     size >>= 1;
@@ -921,6 +927,7 @@ get_expected_mipmap_x_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 int Texture::
 get_expected_mipmap_y_size(int n) const {
+  ReMutexHolder holder(_lock);
   int size = max(_y_size, 1);
   while (n > 0 && size > 1) {
     size >>= 1;
@@ -937,6 +944,7 @@ get_expected_mipmap_y_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 int Texture::
 get_expected_mipmap_z_size(int n) const {
+  ReMutexHolder holder(_lock);
   // 3-D textures have a different number of pages per each mipmap
   // level.  Other kinds of textures--especially, cube map
   // textures--always have the same.
@@ -983,6 +991,7 @@ get_expected_mipmap_z_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 has_ram_image() const {
+  ReMutexHolder holder(_lock);
   return !_ram_images.empty() && !_ram_images[0]._image.empty();
 }
 
@@ -1016,6 +1025,7 @@ has_ram_image() const {
 ////////////////////////////////////////////////////////////////////
 CPTA_uchar Texture::
 get_ram_image() {
+  ReMutexHolder holder(_lock);
   if (_loaded_from_image && !has_ram_image() && has_filename()) {
     reload_ram_image();
   }
@@ -1040,6 +1050,7 @@ get_ram_image() {
 void Texture::
 set_ram_image(PTA_uchar image, Texture::CompressionMode compression,
               size_t page_size) {
+  ReMutexHolder holder(_lock);
   nassertv(compression != CM_default);
   nassertv(compression != CM_off || image.size() == get_expected_ram_image_size());
   if (_ram_images.empty()) {
@@ -1067,6 +1078,7 @@ set_ram_image(PTA_uchar image, Texture::CompressionMode compression,
 ////////////////////////////////////////////////////////////////////
 void Texture::
 clear_ram_image() {
+  ReMutexHolder holder(_lock);
   _ram_image_compression = CM_off;
   _ram_images.clear();
 }
@@ -1093,6 +1105,7 @@ get_keep_ram_image() const {
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 has_all_ram_mipmap_images() const {
+  ReMutexHolder holder(_lock);
   if (_ram_images.empty() || _ram_images[0]._image.empty()) {
     // If we don't even have a base image, the answer is no.
     return false;
@@ -1128,6 +1141,7 @@ has_all_ram_mipmap_images() const {
 ////////////////////////////////////////////////////////////////////
 CPTA_uchar Texture::
 get_ram_mipmap_image(int n) {
+  ReMutexHolder holder(_lock);
   if (n < (int)_ram_images.size()) {
     return _ram_images[n]._image;
   }
@@ -1145,6 +1159,7 @@ get_ram_mipmap_image(int n) {
 ////////////////////////////////////////////////////////////////////
 PTA_uchar Texture::
 make_ram_mipmap_image(int n) {
+  ReMutexHolder holder(_lock);
   nassertr(_ram_image_compression == CM_off, PTA_uchar(get_class_type()));
 
   while (n >= (int)_ram_images.size()) {
@@ -1171,6 +1186,7 @@ make_ram_mipmap_image(int n) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_ram_mipmap_image(int n, PTA_uchar image, size_t page_size) {
+  ReMutexHolder holder(_lock);
   nassertv(_ram_image_compression != CM_off || image.size() == get_expected_ram_mipmap_image_size(n));
 
   while (n >= (int)_ram_images.size()) {
@@ -1197,6 +1213,7 @@ set_ram_mipmap_image(int n, PTA_uchar image, size_t page_size) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 clear_ram_mipmap_image(int n) {
+  ReMutexHolder holder(_lock);
   if (n >= (int)_ram_images.size()) {
     return;
   }
@@ -1212,6 +1229,7 @@ clear_ram_mipmap_image(int n) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 clear_ram_mipmap_images() {
+  ReMutexHolder holder(_lock);
   if (!_ram_images.empty()) {
     _ram_images.erase(_ram_images.begin() + 1, _ram_images.end());
   }
@@ -1232,6 +1250,7 @@ clear_ram_mipmap_images() {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 generate_ram_mipmap_images() {
+  ReMutexHolder holder(_lock);
   nassertv(has_ram_image());
   nassertv(get_ram_image_compression() == CM_off);
   nassertv(get_component_type() != T_float);
@@ -1274,6 +1293,142 @@ generate_ram_mipmap_images() {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::set_simple_ram_image
+//       Access: Published
+//  Description: Replaces the internal "simple" texture image.  This
+//               can be used as an option to display while the main
+//               texture image is being loaded from disk.  It is
+//               normally a very small image, 16x16 or smaller (and
+//               maybe even 1x1), that is designed to give just enough
+//               sense of color to serve as a placeholder until the
+//               full texture is available.
+//
+//               The "simple" image is always 4 components, 1 byte
+//               each, regardless of the parameters of the full
+//               texture.  The simple image is only supported for
+//               ordinary 2-d textures.
+//
+//               Also see generate_simple_ram_image().
+////////////////////////////////////////////////////////////////////
+void Texture::
+set_simple_ram_image(PTA_uchar image, int x_size, int y_size) {
+  ReMutexHolder holder(_lock);
+  nassertv(get_texture_type() == TT_2d_texture);
+  size_t expected_page_size = (size_t)(x_size * y_size * 4);
+  nassertv(image.size() == expected_page_size);
+
+  _simple_x_size = x_size;
+  _simple_y_size = y_size;
+  _simple_ram_image._image = image;
+  _simple_ram_image._page_size = image.size();
+  _simple_image_date_generated = 0;
+  ++_simple_image_modified;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::generate_simple_ram_image
+//       Access: Published
+//  Description: Computes the "simple" ram image by loading the main
+//               RAM image, if it is not already available, and
+//               reducing it to 16x16 or smaller.  This may be an
+//               expensive operation.
+////////////////////////////////////////////////////////////////////
+void Texture::
+generate_simple_ram_image() {
+  ReMutexHolder holder(_lock);
+
+  if (get_texture_type() != TT_2d_texture) {
+    clear_simple_ram_image();
+    return;
+  }
+
+  PNMImage pnmimage;
+  if (!store(pnmimage)) {
+    clear_simple_ram_image();
+    return;
+  }
+
+  // Start at the suggested size from the config file.
+  int x_size = simple_image_size.get_word(0);
+  int y_size = simple_image_size.get_word(1);
+
+  // Limit it to no larger than the source image, and also make it a
+  // power of two.
+  x_size = down_to_power_2(min(x_size, get_x_size()));
+  y_size = down_to_power_2(min(y_size, get_y_size()));
+
+  // Generate a reduced image of that size.
+  PNMImage scaled(x_size, y_size, pnmimage.get_num_channels());
+  scaled.quick_filter_from(pnmimage);
+
+  // Make sure the reduced image has 4 components, by convention.
+  if (!scaled.has_alpha()) {
+    scaled.add_alpha();
+    scaled.alpha_fill(1.0);
+  }
+  scaled.set_num_channels(4);
+
+  // Now see if we can go even smaller.
+  bool did_anything;
+  do {
+    did_anything = false;
+
+    // Try to reduce X.
+    if (x_size > 1) {
+      int new_x_size = (x_size >> 1);
+      PNMImage smaller(new_x_size, y_size, 4);
+      smaller.quick_filter_from(scaled);
+      PNMImage bigger(x_size, y_size, 4);
+      bigger.quick_filter_from(smaller);
+      
+      if (compare_images(scaled, bigger)) {
+        scaled.take_from(smaller);
+        x_size = new_x_size;
+        did_anything = true;
+      }
+    }
+
+    // Try to reduce Y.
+    if (y_size > 1) {
+      int new_y_size = (y_size >> 1);
+      PNMImage smaller(x_size, new_y_size, 4);
+      smaller.quick_filter_from(scaled);
+      PNMImage bigger(x_size, y_size, 4);
+      bigger.quick_filter_from(smaller);
+      
+      if (compare_images(scaled, bigger)) {
+        scaled.take_from(smaller);
+        y_size = new_y_size;
+        did_anything = true;
+      }
+    }
+  } while (did_anything);
+
+  size_t expected_page_size = (size_t)(x_size * y_size * 4);
+  PTA_uchar image = PTA_uchar::empty_array(expected_page_size, get_class_type());
+  convert_from_pnmimage(image, expected_page_size, 0, scaled, 4, 1);
+
+  set_simple_ram_image(image, x_size, y_size);
+  _simple_image_date_generated = (PN_int32)time(NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::clear_simple_ram_image
+//       Access: Published
+//  Description: Discards the current "simple" image.
+////////////////////////////////////////////////////////////////////
+void Texture::
+clear_simple_ram_image() {
+  ReMutexHolder holder(_lock);
+  _simple_x_size = 0;
+  _simple_y_size = 0;
+  _simple_ram_image._image.clear();
+  _simple_ram_image._page_size = 0;
+  _simple_image_date_generated = 0;
+  ++_simple_image_modified;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::prepare
 //       Access: Published
@@ -1300,6 +1455,7 @@ prepare(PreparedGraphicsObjects *prepared_objects) {
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 is_prepared(PreparedGraphicsObjects *prepared_objects) const {
+  ReMutexHolder holder(_lock);
   Contexts::const_iterator ci;
   ci = _contexts.find(prepared_objects);
   if (ci != _contexts.end()) {
@@ -1317,6 +1473,7 @@ is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 release(PreparedGraphicsObjects *prepared_objects) {
+  ReMutexHolder holder(_lock);
   Contexts::iterator ci;
   ci = _contexts.find(prepared_objects);
   if (ci != _contexts.end()) {
@@ -1342,6 +1499,7 @@ release(PreparedGraphicsObjects *prepared_objects) {
 ////////////////////////////////////////////////////////////////////
 int Texture::
 release_all() {
+  ReMutexHolder holder(_lock);
   // We have to traverse a copy of the _contexts list, because the
   // PreparedGraphicsObjects object will call clear_prepared() in response
   // to each release_texture(), and we don't want to be modifying the
@@ -1373,6 +1531,7 @@ release_all() {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 write(ostream &out, int indent_level) const {
+  ReMutexHolder holder(_lock);
   indent(out, indent_level)
     << get_type() << " " << get_name();
   if (!get_filename().empty()) {
@@ -1549,6 +1708,13 @@ write(ostream &out, int indent_level) const {
     indent(out, indent_level + 2)
       << "no ram image\n";
   }
+
+  if (has_simple_ram_image()) {
+    indent(out, indent_level + 2)
+      << "simple image: " << get_simple_x_size() << " x "
+      << get_simple_y_size() << ", "
+      << get_simple_ram_image_size() << " bytes\n";
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1559,6 +1725,7 @@ write(ostream &out, int indent_level) const {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_format(Texture::Format format) {
+  ReMutexHolder holder(_lock);
   _format = format;
 
   switch (_format) {
@@ -1606,6 +1773,7 @@ set_format(Texture::Format format) {
 ////////////////////////////////////////////////////////////////////
 void Texture::
 set_component_type(Texture::ComponentType component_type) {
+  ReMutexHolder holder(_lock);
   _component_type = component_type;
 
   switch (component_type) {
@@ -1662,6 +1830,7 @@ is_mipmap(FilterType filter_type) {
 TextureContext *Texture::
 prepare_now(PreparedGraphicsObjects *prepared_objects,
             GraphicsStateGuardianBase *gsg) {
+  ReMutexHolder holder(_lock);
   Contexts::const_iterator ci;
   ci = _contexts.find(prepared_objects);
   if (ci != _contexts.end()) {
@@ -1689,6 +1858,7 @@ prepare_now(PreparedGraphicsObjects *prepared_objects,
 ////////////////////////////////////////////////////////////////////
 void Texture::
 texture_uploaded() {
+  ReMutexHolder holder(_lock);
   if (!keep_texture_ram && !_keep_ram_image) {
     // Once we have prepared the texture, we can generally safely
     // remove the pixels from main RAM.  The GSG is now responsible
@@ -2074,6 +2244,7 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
         << "Texture::read() - couldn't read: " << fullpath << endl;
       return false;
     }
+    Thread::consider_yield();
   }
 
   PNMImage alpha_image;
@@ -2119,6 +2290,7 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
           << "Texture::read() - couldn't read (alpha): " << alpha_fullpath << endl;
         return false;
       }
+      Thread::consider_yield();
     }
   }
 
@@ -2156,6 +2328,7 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
                       alpha_image.get_num_channels(),
                       alpha_image.get_maxval(), alpha_image.get_type());
       scaled.quick_filter_from(alpha_image);
+      Thread::consider_yield();
       alpha_image = scaled;
     }
   }
@@ -2375,17 +2548,19 @@ do_load_one(const PNMImage &pnmimage, const string &name, int z, int n) {
     PNMImage scaled(x_size, y_size, pnmimage.get_num_channels(),
                     pnmimage.get_maxval(), pnmimage.get_type());
     scaled.quick_filter_from(pnmimage);
+    Thread::consider_yield();
 
     convert_from_pnmimage(_ram_images[n]._image, 
                           get_expected_ram_mipmap_page_size(n), z,
-                          scaled);
+                          scaled, _num_components, _component_width);
   } else {
     // Now copy the pixel data from the PNMImage into our internal
     // _image component.
     convert_from_pnmimage(_ram_images[n]._image, 
                           get_expected_ram_mipmap_page_size(n), z,
-                          pnmimage);
+                          pnmimage, _num_components, _component_width);
   }
+  Thread::consider_yield();
 
   return true;
 }
@@ -2408,6 +2583,7 @@ do_store_one(PNMImage &pnmimage, int z, int n) const {
   return convert_to_pnmimage(pnmimage, 
                              get_expected_mipmap_x_size(n), 
                              get_expected_mipmap_y_size(n), 
+                             _num_components, _component_width,
                              _ram_images[n]._image, 
                              get_ram_mipmap_page_size(n), z);
 }
@@ -2718,26 +2894,27 @@ reconsider_image_properties(int x_size, int y_size, int num_components,
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::convert_from_pnmimage
-//       Access: Private
+//       Access: Private, Static
 //  Description: Internal method to convert pixel data from the
 //               indicated PNMImage into the given ram_image.
 ////////////////////////////////////////////////////////////////////
 void Texture::
 convert_from_pnmimage(PTA_uchar &image, size_t page_size, int z,
-                      const PNMImage &pnmimage) {
+                      const PNMImage &pnmimage,
+                      int num_components, int component_width) {
   int x_size = pnmimage.get_x_size();
   int y_size = pnmimage.get_y_size();
   xelval maxval = pnmimage.get_maxval();
 
-  bool is_grayscale = (_num_components == 1 || _num_components == 2);
-  bool has_alpha = (_num_components == 2 || _num_components == 4);
+  bool is_grayscale = (num_components == 1 || num_components == 2);
+  bool has_alpha = (num_components == 2 || num_components == 4);
   bool img_has_alpha = pnmimage.has_alpha();
 
   int idx = page_size * z;
   nassertv(idx + page_size <= image.size());
   unsigned char *p = &image[idx];
 
-  if (maxval == 255) {
+  if (maxval == 255 && component_width == 1) {
     // Most common case: one byte per pixel, and the source image
     // shows a maxval of 255.  No scaling is necessary.
     for (int j = y_size-1; j >= 0; j--) {
@@ -2759,7 +2936,7 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size, int z,
       }
     }
 
-  } else if (maxval == 65535) {
+  } else if (maxval == 65535 && component_width == 2) {
     // Another possible case: two bytes per pixel, and the source
     // image shows a maxval of 65535.  Again, no scaling is necessary.
     for (int j = y_size-1; j >= 0; j--) {
@@ -2781,7 +2958,7 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size, int z,
       }
     }
 
-  } else if (maxval <= 255) {
+  } else if (component_width == 1) {
     // A less common case: one byte per pixel, but the maxval is
     // something other than 255.  In this case, we should scale the
     // pixel values up to the appropriate amount.
@@ -2806,7 +2983,7 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size, int z,
       }
     }
 
-  } else {
+  } else {  // component_width == 2
     // Another uncommon case: two bytes per pixel, and the maxval is
     // something other than 65535.  Again, we must scale the pixel
     // values.
@@ -2837,14 +3014,15 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size, int z,
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::convert_to_pnmimage
-//       Access: Private
+//       Access: Private, Static
 //  Description: Internal method to convert pixel data to the
 //               indicated PNMImage from the given ram_image.
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
-                    CPTA_uchar image, size_t page_size, int z) const {
-  pnmimage.clear(x_size, y_size, _num_components);
+                    int num_components, int component_width,
+                    CPTA_uchar image, size_t page_size, int z) {
+  pnmimage.clear(x_size, y_size, num_components);
   bool has_alpha = pnmimage.has_alpha();
   bool is_grayscale = pnmimage.is_grayscale();
   
@@ -2852,7 +3030,7 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
   nassertr(idx + page_size <= image.size(), false);
   const unsigned char *p = &image[idx];
 
-  if (_component_type == T_unsigned_byte) {
+  if (component_width == 1) {
     for (int j = y_size-1; j >= 0; j--) {
       for (int i = 0; i < x_size; i++) {
         if (is_grayscale) {
@@ -2868,7 +3046,7 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
       }
     }
 
-  } else if (_component_type == T_unsigned_short) {
+  } else if (component_width == 2) {
     for (int j = y_size-1; j >= 0; j--) {
       for (int i = 0; i < x_size; i++) {
         if (is_grayscale) {
@@ -2885,9 +3063,6 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
     }
 
   } else {
-    gobj_cat.error()
-      << "Couldn't write image for " << get_name()
-      << "; inappropriate data type " << (int)_component_type << ".\n";
     return false;
   }
 
@@ -3043,6 +3218,34 @@ consider_downgrade(PNMImage &pnmimage, int num_channels) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::compare_images
+//       Access: Private, Static
+//  Description: Called by generate_simple_ram_image(), this compares
+//               the two PNMImages pixel-by-pixel.  If they're similar
+//               enough (within a given threshold), returns true.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+compare_images(const PNMImage &a, const PNMImage &b) {
+  nassertr(a.get_maxval() == 255 && b.get_maxval() == 255, false);
+  nassertr(a.get_num_channels() == 4 && b.get_num_channels() == 4, false);
+  nassertr(a.get_x_size() == b.get_x_size() && 
+           a.get_y_size() == b.get_y_size(), false);
+
+  int delta = 0;
+  for (int yi = 0; yi < a.get_y_size(); ++yi) {
+    for (int xi = 0; xi < a.get_x_size(); ++xi) {
+      delta += abs(a.get_red_val(xi, yi) - b.get_red_val(xi, yi));
+      delta += abs(a.get_green_val(xi, yi) - b.get_green_val(xi, yi));
+      delta += abs(a.get_blue_val(xi, yi) - b.get_blue_val(xi, yi));
+      delta += abs(a.get_alpha_val(xi, yi) - b.get_alpha_val(xi, yi));
+    }
+  }
+
+  double average_delta = (double)delta / ((double)a.get_x_size() * (double)b.get_y_size() * (double)a.get_maxval());
+  return (average_delta <= simple_image_threshold);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::read_txo_file
 //       Access: Private

+ 32 - 4
panda/src/gobj/texture.h

@@ -26,6 +26,8 @@
 #include "pmap.h"
 #include "config_gobj.h"
 #include "pStatCollector.h"
+#include "reMutex.h"
+#include "reMutexHolder.h"
 
 class PNMImage;
 class TextureContext;
@@ -313,8 +315,18 @@ PUBLISHED:
   void clear_ram_mipmap_images();
   void generate_ram_mipmap_images();
 
+  INLINE int get_simple_x_size() const;
+  INLINE int get_simple_y_size() const;
+  INLINE bool has_simple_ram_image() const;
+  INLINE size_t get_simple_ram_image_size() const;
+  INLINE CPTA_uchar get_simple_ram_image() const;
+  void set_simple_ram_image(PTA_uchar image, int x_size, int y_size);
+  void generate_simple_ram_image();
+  void clear_simple_ram_image();
+
   INLINE UpdateSeq get_properties_modified() const;
   INLINE UpdateSeq get_image_modified() const;
+  INLINE UpdateSeq get_simple_image_modified() const;
 
   void prepare(PreparedGraphicsObjects *prepared_objects);
   bool is_prepared(PreparedGraphicsObjects *prepared_objects) const;
@@ -428,15 +440,20 @@ protected:
   };
 
 private:
-  void convert_from_pnmimage(PTA_uchar &image, size_t page_size, int z,
-                             const PNMImage &pnmimage);
-  bool convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
-                           CPTA_uchar image, size_t page_size, int z) const;
+  static void convert_from_pnmimage(PTA_uchar &image, size_t page_size, 
+                                    int z, const PNMImage &pnmimage,
+                                    int num_components, int component_width);
+  static bool convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
+                                  int num_components, int component_width,
+                                  CPTA_uchar image, size_t page_size, 
+                                  int z);
   void clear_prepared(PreparedGraphicsObjects *prepared_objects);
 
   void consider_rescale(PNMImage &pnmimage, const string &name);
   void consider_downgrade(PNMImage &pnmimage, int num_channels);
 
+  static bool compare_images(const PNMImage &a, const PNMImage &b);
+
   INLINE static void store_unscaled_byte(unsigned char *&p, int value);
   INLINE static void store_unscaled_short(unsigned char *&p, int value);
   INLINE static void store_scaled_byte(unsigned char *&p, int value, double scale);
@@ -480,6 +497,9 @@ private:
                                        size_t page_size);
 
 protected:
+  // Protects all of the members of this class.
+  ReMutex _lock;
+
   Filename _filename;
   Filename _alpha_filename;
   Filename _fullpath;
@@ -550,9 +570,17 @@ protected:
   // additional mipmap levels.
   typedef pvector<RamImage> RamImages;
   RamImages _ram_images;
+
+  // This is the simple image, which may be loaded before the texture
+  // is loaded from disk.  It exists only for 2-d textures.
+  RamImage _simple_ram_image;
+  int _simple_x_size;
+  int _simple_y_size;
+  PN_int32 _simple_image_date_generated;
   
   UpdateSeq _properties_modified;
   UpdateSeq _image_modified;
+  UpdateSeq _simple_image_modified;
   
 private:
   // The auxiliary data is not recorded to a bam file.

+ 31 - 3
panda/src/gobj/textureContext.I

@@ -72,12 +72,24 @@ was_image_modified() const {
   return _image_modified != _texture->get_image_modified();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TextureContext::was_simple_image_modified
+//       Access: Public
+//  Description: Returns true if the texture's "simple" image has been
+//               modified since the last time mark_simple_loaded() was
+//               called.
+////////////////////////////////////////////////////////////////////
+INLINE bool TextureContext::
+was_simple_image_modified() const {
+  return _simple_image_modified != _texture->get_simple_image_modified();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TextureContext::mark_loaded
 //       Access: Public
-//  Description: Should be called after the TextureContext has been
-//               loaded into graphics memory, this updates the
-//               internal flags for changed_size() and modified().
+//  Description: Should be called after the texture has been loaded
+//               into graphics memory, this updates the internal flags
+//               for changed_size() and modified().
 ////////////////////////////////////////////////////////////////////
 INLINE void TextureContext::
 mark_loaded() {
@@ -89,3 +101,19 @@ mark_loaded() {
   // Assume the texture is now resident.
   set_resident(true);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureContext::mark_simple_loaded
+//       Access: Public
+//  Description: Should be called after the texture's "simple" image
+//               has been loaded into graphics memory.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureContext::
+mark_simple_loaded() {
+  _properties_modified = _texture->get_properties_modified();
+  _simple_image_modified = _texture->get_simple_image_modified();
+  update_modified(max(_properties_modified, _simple_image_modified));
+
+  // The texture's not exactly resident now, but some part of it is.
+  set_resident(true);
+}

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

@@ -44,9 +44,11 @@ PUBLISHED:
   INLINE bool was_modified() const;
   INLINE bool was_properties_modified() const;
   INLINE bool was_image_modified() const;
+  INLINE bool was_simple_image_modified() const;
 
 public:
   INLINE void mark_loaded();
+  INLINE void mark_simple_loaded();
 
 private:
   // This cannot be a PT(Texture), because the texture and the GSG
@@ -55,6 +57,7 @@ private:
   Texture *_texture;
   UpdateSeq _properties_modified;
   UpdateSeq _image_modified;
+  UpdateSeq _simple_image_modified;
   
 public:
   static TypeHandle get_class_type() {

+ 22 - 4
panda/src/gobj/texturePool.cxx

@@ -278,6 +278,9 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
           gobj_cat.info()
             << "Texture " << filename << " found in disk cache.\n";
           tex = DCAST(Texture, record->extract_data());
+          if (preload_simple_textures && !tex->has_simple_ram_image()) {
+            tex->generate_simple_ram_image();
+          }
           if (!preload_textures) {
             // But drop the RAM until we need it.
             tex->clear_ram_image();
@@ -300,6 +303,11 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
       report_texture_unreadable(filename);
       return NULL;
     }
+
+    if (preload_simple_textures) {
+      tex->generate_simple_ram_image();
+    }
+
     store_record = (record != (BamCacheRecord *)NULL);
   }
 
@@ -331,11 +339,11 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
     nassertr(tex->has_ram_image(), tex);
     record->set_data(tex, false);
     cache->store(record);
+  }
 
-    if (!preload_textures) {
-      // And now drop the RAM until we need it.
-      tex->clear_ram_image();
-    }
+  if (!preload_textures) {
+    // And now drop the RAM until we need it.
+    tex->clear_ram_image();
   }
 
   nassertr(!tex->get_fullpath().empty(), tex);
@@ -424,6 +432,11 @@ ns_load_texture(const Filename &orig_filename,
       report_texture_unreadable(filename);
       return NULL;
     }
+
+    if (preload_simple_textures) {
+      tex->generate_simple_ram_image();
+    }
+
     store_record = (record != (BamCacheRecord *)NULL);
   }
 
@@ -457,6 +470,11 @@ ns_load_texture(const Filename &orig_filename,
     cache->store(record);
   }
 
+  if (!preload_textures) {
+    // And now drop the RAM until we need it.
+    tex->clear_ram_image();
+  }
+
   nassertr(!tex->get_fullpath().empty(), tex);
 
   // Finally, apply any post-loading texture filters.

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

@@ -108,6 +108,8 @@ class Lens;
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA_GSGBASE GraphicsStateGuardianBase : public TypedWritableReferenceCount {
 PUBLISHED:
+  virtual bool get_incomplete_render() const=0;
+
   virtual bool prefers_triangle_strips() const=0;
   virtual int get_max_vertices_per_array() const=0;
   virtual int get_max_vertices_per_primitive() const=0;

+ 3 - 0
panda/src/pgraph/Sources.pp

@@ -119,6 +119,7 @@
     textureAttrib.I textureAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     textureCollection.I textureCollection.h \
+    textureReloadRequest.I textureReloadRequest.h \
     textureStageCollection.I textureStageCollection.h \
     transformState.I transformState.h \
     transparencyAttrib.I transparencyAttrib.h \
@@ -232,6 +233,7 @@
     textureAttrib.cxx \
     texGenAttrib.cxx \
     textureCollection.cxx \
+    textureReloadRequest.cxx \
     textureStageCollection.cxx \
     transformState.cxx \
     transparencyAttrib.cxx \
@@ -343,6 +345,7 @@
     textureAttrib.I textureAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     textureCollection.I textureCollection.h \
+    textureReloadRequest.I textureReloadRequest.h \
     textureStageCollection.I textureStageCollection.h \
     transformState.I transformState.h \
     transparencyAttrib.I transparencyAttrib.h \

+ 2 - 9
panda/src/pgraph/config_pgraph.cxx

@@ -94,6 +94,7 @@
 #include "texMatrixAttrib.h"
 #include "texProjectorEffect.h"
 #include "textureAttrib.h"
+#include "textureReloadRequest.h"
 #include "texGenAttrib.h"
 #include "transformState.h"
 #include "transparencyAttrib.h"
@@ -338,15 +339,6 @@ ConfigVariableString default_model_extension
           "Panda's loader; new code should probably give the correct name "
           "for each model file they intend to load."));
 
-ConfigVariableBool allow_incomplete_render
-("allow-incomplete-render", false,
- PRC_DESC("When this is true, the frame may be rendered even if some of the "
-          "geometry in the scene has been paged out.  The nonresident "
-          "geometry will be rendered as soon as it can be paged back in, "
-          "which may be several frames in the future.  When this is false, "
-          "geometry is always paged in when needed, holding up the frame "
-          "render if necessary."));
-
 ConfigVariableEnum<LODNodeType> default_lod_type
 ("default-lod-type", LNT_pop,
  PRC_DESC("Set this to either 'pop' or 'fade' to determine the type of "
@@ -448,6 +440,7 @@ init_libpgraph() {
   TexMatrixAttrib::init_type();
   TexProjectorEffect::init_type();
   TextureAttrib::init_type();
+  TextureReloadRequest::init_type();
   TexGenAttrib::init_type();
   TransformState::init_type();
   TransparencyAttrib::init_type();

+ 0 - 1
panda/src/pgraph/config_pgraph.h

@@ -68,7 +68,6 @@ extern ConfigVariableBool m_dual_flash;
 
 extern ConfigVariableList load_file_type;
 extern ConfigVariableString default_model_extension;
-extern EXPCL_PANDA_PGRAPH ConfigVariableBool allow_incomplete_render;
 
 extern ConfigVariableEnum<LODNodeType> default_lod_type;
 

+ 2 - 2
panda/src/pgraph/cullResult.cxx

@@ -107,7 +107,7 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
   static const Colorf flash_multisample_color(0.78f, 0.05f, 0.81f, 1.0f);
   static const Colorf flash_dual_color(0.92f, 0.01f, 0.01f, 1.0f);
 
-  bool force = !allow_incomplete_render;
+  bool force = !_gsg->get_incomplete_render();
   Thread *current_thread = traverser->get_current_thread();
 
   // Check to see if there's a special transparency setting.
@@ -264,7 +264,7 @@ finish_cull(SceneSetup *scene_setup, Thread *current_thread) {
 ////////////////////////////////////////////////////////////////////
 void CullResult::
 draw(Thread *current_thread) {
-  bool force = !allow_incomplete_render;
+  bool force = !_gsg->get_incomplete_render();
 
   // Ask the bin manager for the correct order to draw all the bins.
   CullBinManager *bin_manager = CullBinManager::get_global_ptr();

+ 1 - 0
panda/src/pgraph/pgraph_composite4.cxx

@@ -25,6 +25,7 @@
 #include "textureAttrib.cxx"
 #include "texGenAttrib.cxx"
 #include "textureCollection.cxx"
+#include "textureReloadRequest.cxx"
 #include "textureStageCollection.cxx"
 #include "transformState.cxx"
 #include "transparencyAttrib.cxx"

+ 51 - 0
panda/src/pgraph/textureReloadRequest.I

@@ -0,0 +1,51 @@
+// Filename: textureReloadRequest.I
+// Created by:  drose (12Aug08)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReloadRequest::Constructor
+//       Access: Published
+//  Description: Create a new TextureReloadRequest, and add it to the loader
+//               via load_async(), to begin an asynchronous load.
+////////////////////////////////////////////////////////////////////
+INLINE TextureReloadRequest::
+TextureReloadRequest(TextureContext *tc) :
+  _texture_context(tc),
+  _is_ready(false)
+{
+  nassertv(_texture_context != (TextureContext *)NULL);
+  _texture = _texture_context->get_texture();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReloadRequest::get_texture_context
+//       Access: Published
+//  Description: Returns the TextureContext object associated with
+//               this asynchronous TextureReloadRequest.
+////////////////////////////////////////////////////////////////////
+INLINE TextureContext *TextureReloadRequest::
+get_texture_context() const {
+  return _texture_context;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReloadRequest::is_ready
+//       Access: Published
+//  Description: Returns true if this request has completed, false if
+//               it is still pending.
+////////////////////////////////////////////////////////////////////
+INLINE bool TextureReloadRequest::
+is_ready() const {
+  return _is_ready;
+}

+ 35 - 0
panda/src/pgraph/textureReloadRequest.cxx

@@ -0,0 +1,35 @@
+// Filename: textureReloadRequest.cxx
+// Created by:  drose (12Aug08)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "textureReloadRequest.h"
+#include "loader.h"
+
+TypeHandle TextureReloadRequest::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReloadRequest::do_task
+//       Access: Protected, Virtual
+//  Description: Performs the task: that is, loads the one model.
+////////////////////////////////////////////////////////////////////
+bool TextureReloadRequest::
+do_task() {
+  // Don't reload the texture if it doesn't need it.
+  if (_texture_context->was_image_modified()) {
+    _texture->get_ram_image();
+  }
+  _is_ready = true;
+
+  // Don't continue the task; we're done.
+  return false;
+}

+ 71 - 0
panda/src/pgraph/textureReloadRequest.h

@@ -0,0 +1,71 @@
+// Filename: textureReloadRequest.h
+// Created by:  drose (12Aug08)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTURERELOADREQUEST
+#define TEXTURERELOADREQUEST
+
+#include "pandabase.h"
+
+#include "asyncTask.h"
+#include "texture.h"
+#include "textureContext.h"
+#include "pointerTo.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : TextureReloadRequest
+// Description : This loader request will call
+//               Texture::get_ram_image() in a sub-thread, to force
+//               the texture's image to be re-read from disk.  It is
+//               used by GraphicsStateGuardian::async_reload_texture(),
+//               when get_incomplete_render() is true.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_PGRAPH TextureReloadRequest : public AsyncTask {
+public:
+  ALLOC_DELETED_CHAIN(TextureReloadRequest);
+
+PUBLISHED:
+  INLINE TextureReloadRequest(TextureContext *tc);
+  
+  INLINE TextureContext *get_texture_context() const;
+  INLINE bool is_ready() const;
+  
+protected:
+  virtual bool do_task();
+  
+private:
+  TextureContext *_texture_context;
+  PT(Texture) _texture;
+  bool _is_ready;
+  
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    AsyncTask::init_type();
+    register_type(_type_handle, "TextureReloadRequest",
+                  AsyncTask::get_class_type());
+    }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+  
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "textureReloadRequest.I"
+
+#endif

+ 1 - 0
panda/src/pnmimagetypes/pnmFileTypeJPGReader.cxx

@@ -415,6 +415,7 @@ read_data(xel *array, xelval *) {
       }
       x++;
     }
+    Thread::consider_yield();
   }
 
   /* Step 7: Finish decompression */