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) {
 record_object(CullableObject *object, const CullTraverser *traverser) {
   // Munge vertices as needed for the GSG's requirements, and the
   // Munge vertices as needed for the GSG's requirements, and the
   // object's current state.
   // object's current state.
-  bool force = !allow_incomplete_render;
+  bool force = !_gsg->get_incomplete_render();
   Thread *current_thread = traverser->get_current_thread();
   Thread *current_thread = traverser->get_current_thread();
 
 
   if (object->munge_geom(_gsg, _gsg->get_geom_munger(object->_state, current_thread), traverser, force)) {
   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 "
           "application specifically enables it.  See also "
           "color-scale-via-lighting."));
           "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
 ConfigVariableInt win_size
 ("win-size", "640 480",
 ("win-size", "640 480",
  PRC_DESC("This is the default size at which to open a new window.  This "
  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 auto_generate_mipmaps;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool color_scale_via_lighting;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool color_scale_via_lighting;
 extern EXPCL_PANDA_DISPLAY ConfigVariableBool alpha_scale_via_texture;
 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_size;
 extern EXPCL_PANDA_DISPLAY ConfigVariableInt win_origin;
 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;
   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
 //     Function: GraphicsEngine::close_gsg
 //       Access: Published
 //       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->_threading_model = threading_model;
   gsg->_pipe = pipe;
   gsg->_pipe = pipe;
   gsg->_engine = this;
   gsg->_engine = this;
+  if (!_default_loader.is_null()) {
+    gsg->set_loader(_default_loader);
+  }
 
 
   auto_adjust_capabilities(gsg);
   auto_adjust_capabilities(gsg);
   
   

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

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

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

@@ -105,6 +105,79 @@ is_valid() const {
   return _is_valid;
   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
 //     Function: GraphicsStateGuardian::get_pipe
 //       Access: Published
 //       Access: Published
@@ -716,17 +789,6 @@ mark_new() {
   _needs_reset = true;
   _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
 //     Function: GraphicsStateGuardian::get_external_transform
 //       Access: Public
 //       Access: Public

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

@@ -40,6 +40,7 @@
 #include "directionalLight.h"
 #include "directionalLight.h"
 #include "pointLight.h"
 #include "pointLight.h"
 #include "spotlight.h"
 #include "spotlight.h"
+#include "textureReloadRequest.h"
 
 
 #include <algorithm>
 #include <algorithm>
 #include <limits.h>
 #include <limits.h>
@@ -131,6 +132,7 @@ GraphicsStateGuardian(CoordinateSystem internal_coordinate_system,
   _active = true;
   _active = true;
   _prepared_objects = new PreparedGraphicsObjects;
   _prepared_objects = new PreparedGraphicsObjects;
   _stereo_buffer_mask = ~0;
   _stereo_buffer_mask = ~0;
+  _incomplete_render = allow_incomplete_render;
 
 
   _is_hardware = false;
   _is_hardware = false;
   _prefers_triangle_strips = 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
 //       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;
   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
 //     Function: GraphicsStateGuardian::begin_frame
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -1462,6 +1415,106 @@ end_draw_primitives() {
   _data_reader = NULL;
   _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
 //     Function: GraphicsStateGuardian::get_cs_transform
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -1798,6 +1851,58 @@ init_frame_pstats() {
 }
 }
 #endif  // DO_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
 //     Function: GraphicsStateGuardian::enable_lighting
 //       Access: Protected, Virtual
 //       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::
 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 "texture.h"
 #include "occlusionQueryContext.h"
 #include "occlusionQueryContext.h"
 #include "stencilRenderStates.h"
 #include "stencilRenderStates.h"
+#include "loader.h"
 
 
 class DrawableRegion;
 class DrawableRegion;
 class GraphicsEngine;
 class GraphicsEngine;
@@ -90,6 +91,12 @@ PUBLISHED:
   INLINE bool is_valid() const;
   INLINE bool is_valid() const;
   INLINE bool needs_reset() 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 GraphicsPipe *get_pipe() const;
   INLINE GraphicsEngine *get_engine() const;
   INLINE GraphicsEngine *get_engine() const;
   INLINE const GraphicsThreadingModel &get_threading_model() const;
   INLINE const GraphicsThreadingModel &get_threading_model() const;
@@ -268,6 +275,8 @@ public:
   static void init_frame_pstats();
   static void init_frame_pstats();
 #endif
 #endif
 
 
+  void traverse_prepared_textures(bool (*pertex_callbackfn)(TextureContext *,void *),void *callback_arg);
+
 protected:
 protected:
   virtual void enable_lighting(bool enable);
   virtual void enable_lighting(bool enable);
   virtual void set_ambient_light(const Colorf &color);
   virtual void set_ambient_light(const Colorf &color);
@@ -293,6 +302,8 @@ protected:
   static CPT(RenderState) get_unclipped_state();
   static CPT(RenderState) get_unclipped_state();
   static CPT(RenderState) get_untextured_state();
   static CPT(RenderState) get_untextured_state();
 
 
+  void async_reload_texture(TextureContext *tc);
+
 protected:
 protected:
   PT(SceneSetup) _scene_null;
   PT(SceneSetup) _scene_null;
   PT(SceneSetup) _scene_setup;
   PT(SceneSetup) _scene_setup;
@@ -353,6 +364,8 @@ protected:
   bool _is_valid;
   bool _is_valid;
   bool _closing_gsg;
   bool _closing_gsg;
   bool _active;
   bool _active;
+  bool _incomplete_render;
+  PT(Loader) _loader;
 
 
   PT(PreparedGraphicsObjects) _prepared_objects;
   PT(PreparedGraphicsObjects) _prepared_objects;
 
 
@@ -482,9 +495,6 @@ private:
   GraphicsEngine *_engine;
   GraphicsEngine *_engine;
   GraphicsThreadingModel _threading_model;
   GraphicsThreadingModel _threading_model;
 
 
-public:
-  void traverse_prepared_textures(bool (*pertex_callbackfn)(TextureContext *,void *),void *callback_arg);
-
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     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
     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
     from the page_in function?  Will we get called again next frame if
     we do?
     we do?
-  if (allow_incomplete_render) {
+  if (_incomplete_render) {
     // Check if the data is resident before continuing.
     // Check if the data is resident before continuing.
     const unsigned char *data_pointer = reader->get_read_pointer(false);
     const unsigned char *data_pointer = reader->get_read_pointer(false);
     if (data_pointer == NULL) {
     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
     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
     from the page_in function?  Will we get called again next frame if
     we do?
     we do?
-  if (allow_incomplete_render) {
+  if (_incomplete_render) {
     // Check if the data is resident before continuing.
     // Check if the data is resident before continuing.
     const unsigned char *data_pointer = reader.get_read_pointer(false);
     const unsigned char *data_pointer = reader.get_read_pointer(false);
     if (data_pointer == NULL) {
     if (data_pointer == NULL) {

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

@@ -290,7 +290,6 @@ CLP(GraphicsStateGuardian)::
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 void CLP(GraphicsStateGuardian)::
 reset() {
 reset() {
-  cerr << "begin reset\n";
   free_pointers();
   free_pointers();
   GraphicsStateGuardian::reset();
   GraphicsStateGuardian::reset();
 
 
@@ -1332,7 +1331,6 @@ reset() {
   // Now that the GSG has been initialized, make it available for
   // Now that the GSG has been initialized, make it available for
   // optimizations.
   // optimizations.
   add_gsg(this);
   add_gsg(this);
-  cerr << "end reset()\n";
 }
 }
 
 
 
 
@@ -6393,7 +6391,10 @@ update_standard_texture_bindings() {
     }
     }
     GLP(Enable)(target);
     GLP(Enable)(target);
     
     
-    apply_texture(tc);
+    if (!apply_texture(tc)) {
+      GLP(Disable)(target);
+      break;
+    }
     
     
     if (stage->involves_color_scale() && _color_scale_enabled) {
     if (stage->involves_color_scale() && _color_scale_enabled) {
       Colorf color = stage->get_color();
       Colorf color = stage->get_color();
@@ -6939,14 +6940,14 @@ specify_texture(Texture *tex) {
 //               texture, and makes it the current texture available
 //               texture, and makes it the current texture available
 //               for rendering.
 //               for rendering.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-void CLP(GraphicsStateGuardian)::
+bool CLP(GraphicsStateGuardian)::
 apply_texture(TextureContext *tc) {
 apply_texture(TextureContext *tc) {
   CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
   CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
 
 
   gtc->set_active(true);
   gtc->set_active(true);
   GLenum target = get_texture_target(gtc->get_texture()->get_texture_type());
   GLenum target = get_texture_target(gtc->get_texture()->get_texture_type());
   if (target == GL_NONE) {
   if (target == GL_NONE) {
-    return;
+    return false;
   }
   }
   GLP(BindTexture)(target, gtc->_index);
   GLP(BindTexture)(target, gtc->_index);
 
 
@@ -6954,8 +6955,12 @@ apply_texture(TextureContext *tc) {
     // If the texture image was modified, reload the texture.  This
     // If the texture image was modified, reload the texture.  This
     // means we also re-specify the properties for good measure.
     // means we also re-specify the properties for good measure.
     specify_texture(gtc->get_texture());
     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()) {
   } else if (gtc->was_properties_modified()) {
     // If only the properties have been modified, we don't necessarily
     // If only the properties have been modified, we don't necessarily
@@ -6965,6 +6970,7 @@ apply_texture(TextureContext *tc) {
   }
   }
 
 
   report_my_gl_errors();
   report_my_gl_errors();
+  return true;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -6980,6 +6986,21 @@ bool CLP(GraphicsStateGuardian)::
 upload_texture(CLP(TextureContext) *gtc) {
 upload_texture(CLP(TextureContext) *gtc) {
   Texture *tex = gtc->get_texture();
   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();
   CPTA_uchar image = tex->get_ram_image();
 
 
   Texture::CompressionMode image_compression;
   Texture::CompressionMode image_compression;
@@ -7139,6 +7160,7 @@ upload_texture(CLP(TextureContext) *gtc) {
 #endif
 #endif
 
 
     tex->texture_uploaded();
     tex->texture_uploaded();
+    gtc->mark_loaded();
 
 
     report_my_gl_errors();
     report_my_gl_errors();
     return true;
     return true;
@@ -7462,6 +7484,68 @@ upload_texture_image(CLP(TextureContext) *gtc,
   return true;
   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
 //     Function: GLGraphicsStateGuardian::get_texture_memory_size
 //       Access: Protected
 //       Access: Protected

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

@@ -323,7 +323,7 @@ protected:
 
 
   void do_auto_rescale_normal();
   void do_auto_rescale_normal();
   void specify_texture(Texture *tex);
   void specify_texture(Texture *tex);
-  void apply_texture(TextureContext *tc);
+  bool apply_texture(TextureContext *tc);
   bool upload_texture(CLP(TextureContext) *gtc);
   bool upload_texture(CLP(TextureContext) *gtc);
   bool upload_texture_image(CLP(TextureContext) *gtc,
   bool upload_texture_image(CLP(TextureContext) *gtc,
                             bool uses_mipmaps, int mipmap_bias,
                             bool uses_mipmaps, int mipmap_bias,
@@ -331,7 +331,8 @@ protected:
                             GLint internal_format, GLint external_format, 
                             GLint internal_format, GLint external_format, 
                             GLenum component_type,
                             GLenum component_type,
                             bool one_page_only, int z,
                             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);
   size_t get_texture_memory_size(Texture *tex);
   void check_nonresident_texture(BufferContextChain &chain);
   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 "
           "wasted memory from Textures that are created but never used "
           "to render."));
           "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
 ConfigVariableBool compressed_textures
 ("compressed-textures", false,
 ("compressed-textures", false,
  PRC_DESC("Set this to true to compress textures as they are loaded into "
  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 "
           "you then open a second window that doesn't support the same "
           "capabilities, it will have no choice but to print an error message."));
           "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
 ConfigVariableEnum<ShaderUtilization> shader_utilization
 ("shader-utilization", SUT_none,
 ("shader-utilization", SUT_none,
  PRC_DESC("At times, panda may generate shaders.  This variable controls what "
  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 "
           "you then open a second window that doesn't support the same "
           "capabilities, it will have no choice but to print an error message."));
           "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
 ConfigVariableInt geom_cache_size
 ("geom-cache-size", 5000,
 ("geom-cache-size", 5000,
  PRC_DESC("Specifies the maximum number of entries in the cache "
  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 keep_texture_ram;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool preload_textures;
 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 compressed_textures;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool vertex_buffers;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool vertex_buffers;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool vertex_arrays;
 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 ConfigVariableEnum<AutoTextureScale> textures_square;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_auto_power_2;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_auto_power_2;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_header_only;
 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 ConfigVariableEnum<ShaderUtilization> shader_utilization;
 extern EXPCL_PANDA_GOBJ ConfigVariableBool shader_auto_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);
   _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
 //     Function: PreparedGraphicsObjects::release_all_textures
 //       Access: Public
 //       Access: Public

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

@@ -71,6 +71,7 @@ PUBLISHED:
   bool dequeue_texture(Texture *tex);
   bool dequeue_texture(Texture *tex);
   bool is_texture_prepared(const Texture *tex) const;
   bool is_texture_prepared(const Texture *tex) const;
   void release_texture(TextureContext *tc);
   void release_texture(TextureContext *tc);
+  void release_texture(Texture *tex);
   int release_all_textures();
   int release_all_textures();
   int get_num_queued_textures() const;
   int get_num_queued_textures() const;
   int get_num_prepared_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::
 INLINE bool Texture::
 read(const Filename &fullpath) {
 read(const Filename &fullpath) {
+  ReMutexHolder holder(_lock);
   clear();
   clear();
   return do_read(fullpath, Filename(), 0, 0, 0, 0, false, false, 
   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::
 INLINE bool Texture::
 read(const Filename &fullpath, const Filename &alpha_fullpath,
 read(const Filename &fullpath, const Filename &alpha_fullpath,
      int primary_file_num_channels, int alpha_file_channel) {
      int primary_file_num_channels, int alpha_file_channel) {
+  ReMutexHolder holder(_lock);
   clear();
   clear();
   return do_read(fullpath, alpha_fullpath, primary_file_num_channels,
   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);
                  NULL);
 }
 }
 
 
@@ -167,10 +170,11 @@ read(const Filename &fullpath, const Filename &alpha_fullpath,
 INLINE bool Texture::
 INLINE bool Texture::
 read(const Filename &fullpath, int z, int n, 
 read(const Filename &fullpath, int z, int n, 
      bool read_pages, bool read_mipmaps) {
      bool read_pages, bool read_mipmaps) {
+  ReMutexHolder holder(_lock);
   ++_properties_modified;
   ++_properties_modified;
   ++_image_modified;
   ++_image_modified;
   return do_read(fullpath, Filename(), 0, 0, z, n, read_pages, read_mipmaps,
   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 primary_file_num_channels, int alpha_file_channel,
      int z, int n, bool read_pages, bool read_mipmaps,
      int z, int n, bool read_pages, bool read_mipmaps,
      BamCacheRecord *record) {
      BamCacheRecord *record) {
+  ReMutexHolder holder(_lock);
   ++_properties_modified;
   ++_properties_modified;
   ++_image_modified;
   ++_image_modified;
   return do_read(fullpath, alpha_fullpath, primary_file_num_channels,
   return do_read(fullpath, alpha_fullpath, primary_file_num_channels,
 		 alpha_file_channel, z, n, read_pages, read_mipmaps,
 		 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::
 INLINE bool Texture::
 write(const Filename &fullpath) {
 write(const Filename &fullpath) {
+  ReMutexHolder holder(_lock);
   return do_write(fullpath, 0, 0, false, false);
   return do_write(fullpath, 0, 0, false, false);
 }
 }
 
 
@@ -321,6 +327,7 @@ write(const Filename &fullpath) {
 INLINE bool Texture::
 INLINE bool Texture::
 write(const Filename &fullpath, int z, int n, 
 write(const Filename &fullpath, int z, int n, 
       bool write_pages, bool write_mipmaps) {
       bool write_pages, bool write_mipmaps) {
+  ReMutexHolder holder(_lock);
   return do_write(fullpath, z, n, write_pages, write_mipmaps);
   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::
 INLINE bool Texture::
 load(const PNMImage &pnmimage) {
 load(const PNMImage &pnmimage) {
+  ReMutexHolder holder(_lock);
   clear();
   clear();
   return do_load_one(pnmimage, get_name(), 0, 0);
   return do_load_one(pnmimage, get_name(), 0, 0);
 }
 }
@@ -343,6 +351,7 @@ load(const PNMImage &pnmimage) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 INLINE bool Texture::
 load(const PNMImage &pnmimage, int z, int n) {
 load(const PNMImage &pnmimage, int z, int n) {
+  ReMutexHolder holder(_lock);
   ++_properties_modified;
   ++_properties_modified;
   ++_image_modified;
   ++_image_modified;
   return do_load_one(pnmimage, get_name(), z, n);
   return do_load_one(pnmimage, get_name(), z, n);
@@ -356,6 +365,7 @@ load(const PNMImage &pnmimage, int z, int n) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 INLINE bool Texture::
 store(PNMImage &pnmimage) const {
 store(PNMImage &pnmimage) const {
+  ReMutexHolder holder(_lock);
   return do_store_one(pnmimage, 0, 0);
   return do_store_one(pnmimage, 0, 0);
 }
 }
 
 
@@ -367,6 +377,7 @@ store(PNMImage &pnmimage) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 INLINE bool Texture::
 store(PNMImage &pnmimage, int z, int n) const {
 store(PNMImage &pnmimage, int z, int n) const {
+  ReMutexHolder holder(_lock);
   return do_store_one(pnmimage, z, n);
   return do_store_one(pnmimage, z, n);
 }
 }
 
 
@@ -378,6 +389,7 @@ store(PNMImage &pnmimage, int z, int n) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 INLINE bool Texture::
 has_filename() const {
 has_filename() const {
+  ReMutexHolder holder(_lock);
   return !_filename.empty();
   return !_filename.empty();
 }
 }
 
 
@@ -390,6 +402,7 @@ has_filename() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE const Filename &Texture::
 INLINE const Filename &Texture::
 get_filename() const {
 get_filename() const {
+  ReMutexHolder holder(_lock);
   return _filename;
   return _filename;
 }
 }
 
 
@@ -401,6 +414,7 @@ get_filename() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 INLINE bool Texture::
 has_alpha_filename() const {
 has_alpha_filename() const {
+  ReMutexHolder holder(_lock);
   return !_alpha_filename.empty();
   return !_alpha_filename.empty();
 }
 }
 
 
@@ -414,6 +428,7 @@ has_alpha_filename() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE const Filename &Texture::
 INLINE const Filename &Texture::
 get_alpha_filename() const {
 get_alpha_filename() const {
+  ReMutexHolder holder(_lock);
   return _alpha_filename;
   return _alpha_filename;
 }
 }
 
 
@@ -425,6 +440,7 @@ get_alpha_filename() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 INLINE bool Texture::
 has_fullpath() const {
 has_fullpath() const {
+  ReMutexHolder holder(_lock);
   return !_fullpath.empty();
   return !_fullpath.empty();
 }
 }
 
 
@@ -437,6 +453,7 @@ has_fullpath() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE const Filename &Texture::
 INLINE const Filename &Texture::
 get_fullpath() const {
 get_fullpath() const {
+  ReMutexHolder holder(_lock);
   return _fullpath;
   return _fullpath;
 }
 }
 
 
@@ -448,6 +465,7 @@ get_fullpath() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 INLINE bool Texture::
 has_alpha_fullpath() const {
 has_alpha_fullpath() const {
+  ReMutexHolder holder(_lock);
   return !_alpha_fullpath.empty();
   return !_alpha_fullpath.empty();
 }
 }
 
 
@@ -461,6 +479,7 @@ has_alpha_fullpath() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE const Filename &Texture::
 INLINE const Filename &Texture::
 get_alpha_fullpath() const {
 get_alpha_fullpath() const {
+  ReMutexHolder holder(_lock);
   return _alpha_fullpath;
   return _alpha_fullpath;
 }
 }
 
 
@@ -546,6 +565,7 @@ get_pad_z_size() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 set_pad_size(int x, int y, int z) {
 set_pad_size(int x, int y, int z) {
+  ReMutexHolder holder(_lock);
   if (x > _x_size) x = _x_size;
   if (x > _x_size) x = _x_size;
   if (y > _y_size) y = _y_size;
   if (y > _y_size) y = _y_size;
   if (z > _z_size) z = _z_size;
   if (z > _z_size) z = _z_size;
@@ -770,6 +790,7 @@ get_quality_level() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 INLINE bool Texture::
 might_have_ram_image() const {
 might_have_ram_image() const {
+  ReMutexHolder holder(_lock);
   return (has_ram_image() || has_filename());
   return (has_ram_image() || has_filename());
 }
 }
 
 
@@ -781,6 +802,7 @@ might_have_ram_image() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 INLINE size_t Texture::
 get_ram_image_size() const {
 get_ram_image_size() const {
+  ReMutexHolder holder(_lock);
   if (_ram_images.empty()) {
   if (_ram_images.empty()) {
     return 0;
     return 0;
   }
   }
@@ -801,6 +823,7 @@ get_ram_image_size() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 INLINE size_t Texture::
 get_ram_page_size() const {
 get_ram_page_size() const {
+  ReMutexHolder holder(_lock);
   if (_ram_image_compression == CM_off || _ram_images.empty()) {
   if (_ram_image_compression == CM_off || _ram_images.empty()) {
     return get_expected_ram_page_size();
     return get_expected_ram_page_size();
   } else {
   } else {
@@ -817,6 +840,7 @@ get_ram_page_size() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 INLINE size_t Texture::
 get_expected_ram_image_size() const {
 get_expected_ram_image_size() const {
+  ReMutexHolder holder(_lock);
   return get_expected_ram_page_size() * (size_t)_z_size;
   return get_expected_ram_page_size() * (size_t)_z_size;
 }
 }
 
 
@@ -830,6 +854,7 @@ get_expected_ram_image_size() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 INLINE size_t Texture::
 get_expected_ram_page_size() const {
 get_expected_ram_page_size() const {
+  ReMutexHolder holder(_lock);
   return (size_t)(_x_size * _y_size * _num_components * _component_width);
   return (size_t)(_x_size * _y_size * _num_components * _component_width);
 }
 }
 
 
@@ -860,6 +885,7 @@ get_ram_image_compression() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE PTA_uchar Texture::
 INLINE PTA_uchar Texture::
 modify_ram_image() {
 modify_ram_image() {
+  ReMutexHolder holder(_lock);
   do_modify_ram_image();
   do_modify_ram_image();
   ++_image_modified;
   ++_image_modified;
   return _ram_images[0]._image;
   return _ram_images[0]._image;
@@ -876,6 +902,7 @@ modify_ram_image() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE PTA_uchar Texture::
 INLINE PTA_uchar Texture::
 make_ram_image() {
 make_ram_image() {
+  ReMutexHolder holder(_lock);
   ++_image_modified;
   ++_image_modified;
   do_make_ram_image();
   do_make_ram_image();
   return _ram_images[0]._image;
   return _ram_images[0]._image;
@@ -911,6 +938,7 @@ set_keep_ram_image(bool keep_ram_image) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE int Texture::
 INLINE int Texture::
 get_num_ram_mipmap_images() const {
 get_num_ram_mipmap_images() const {
+  ReMutexHolder holder(_lock);
   return _ram_images.size();
   return _ram_images.size();
 }
 }
 
 
@@ -926,6 +954,7 @@ get_num_ram_mipmap_images() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool Texture::
 INLINE bool Texture::
 has_ram_mipmap_image(int n) const {
 has_ram_mipmap_image(int n) const {
+  ReMutexHolder holder(_lock);
   return (n >= 0 && n < (int)_ram_images.size() && !_ram_images[n]._image.empty());
   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::
 INLINE size_t Texture::
 get_ram_mipmap_image_size(int n) const {
 get_ram_mipmap_image_size(int n) const {
+  ReMutexHolder holder(_lock);
   if (n >= 0 && n < (int)_ram_images.size()) {
   if (n >= 0 && n < (int)_ram_images.size()) {
     return _ram_images[n]._image.size();
     return _ram_images[n]._image.size();
   }
   }
@@ -959,6 +989,7 @@ get_ram_mipmap_image_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 INLINE size_t Texture::
 get_ram_mipmap_page_size(int n) const {
 get_ram_mipmap_page_size(int n) const {
+  ReMutexHolder holder(_lock);
   if (_ram_image_compression != CM_off) {
   if (_ram_image_compression != CM_off) {
     if (n >= 0 && n < (int)_ram_images.size()) {
     if (n >= 0 && n < (int)_ram_images.size()) {
       return _ram_images[n]._page_size;
       return _ram_images[n]._page_size;
@@ -978,6 +1009,7 @@ get_ram_mipmap_page_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE size_t Texture::
 INLINE size_t Texture::
 get_expected_ram_mipmap_image_size(int n) const {
 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);
   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::
 INLINE size_t Texture::
 get_expected_ram_mipmap_page_size(int n) const {
 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);
   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::
 INLINE PTA_uchar Texture::
 modify_ram_mipmap_image(int n) {
 modify_ram_mipmap_image(int n) {
+  ReMutexHolder holder(_lock);
   do_modify_ram_mipmap_image(n);
   do_modify_ram_mipmap_image(n);
   ++_image_modified;
   ++_image_modified;
   return _ram_images[n]._image;
   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
 //     Function: Texture::get_properties_modified
 //       Access: Published
 //       Access: Published
@@ -1035,6 +1136,18 @@ get_image_modified() const {
   return _image_modified;
   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
 //     Function: Texture::set_filename
 //       Access: Published
 //       Access: Published
@@ -1050,6 +1163,7 @@ get_image_modified() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 set_filename(const Filename &filename) {
 set_filename(const Filename &filename) {
+  ReMutexHolder holder(_lock);
   _filename = filename;
   _filename = filename;
 }
 }
 
 
@@ -1061,6 +1175,7 @@ set_filename(const Filename &filename) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 clear_filename() {
 clear_filename() {
+  ReMutexHolder holder(_lock);
   _filename = Filename();
   _filename = Filename();
 }
 }
 
 
@@ -1082,6 +1197,7 @@ clear_filename() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 set_alpha_filename(const Filename &alpha_filename) {
 set_alpha_filename(const Filename &alpha_filename) {
+  ReMutexHolder holder(_lock);
   _alpha_filename = alpha_filename;
   _alpha_filename = alpha_filename;
 }
 }
 
 
@@ -1093,6 +1209,7 @@ set_alpha_filename(const Filename &alpha_filename) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 clear_alpha_filename() {
 clear_alpha_filename() {
+  ReMutexHolder holder(_lock);
   _alpha_filename = Filename();
   _alpha_filename = Filename();
 }
 }
 
 
@@ -1106,6 +1223,7 @@ clear_alpha_filename() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 set_fullpath(const Filename &fullpath) {
 set_fullpath(const Filename &fullpath) {
+  ReMutexHolder holder(_lock);
   _fullpath = fullpath;
   _fullpath = fullpath;
 }
 }
 
 
@@ -1117,6 +1235,7 @@ set_fullpath(const Filename &fullpath) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 clear_fullpath() {
 clear_fullpath() {
+  ReMutexHolder holder(_lock);
   _fullpath = Filename();
   _fullpath = Filename();
 }
 }
 
 
@@ -1131,6 +1250,7 @@ clear_fullpath() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 set_alpha_fullpath(const Filename &alpha_fullpath) {
 set_alpha_fullpath(const Filename &alpha_fullpath) {
+  ReMutexHolder holder(_lock);
   _alpha_fullpath = alpha_fullpath;
   _alpha_fullpath = alpha_fullpath;
 }
 }
 
 
@@ -1142,6 +1262,7 @@ set_alpha_fullpath(const Filename &alpha_fullpath) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 clear_alpha_fullpath() {
 clear_alpha_fullpath() {
+  ReMutexHolder holder(_lock);
   _alpha_fullpath = Filename();
   _alpha_fullpath = Filename();
 }
 }
 
 
@@ -1154,6 +1275,7 @@ clear_alpha_fullpath() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 set_x_size(int x_size) {
 set_x_size(int x_size) {
+  ReMutexHolder holder(_lock);
   if (_x_size != x_size) {
   if (_x_size != x_size) {
     _x_size = x_size;
     _x_size = x_size;
     ++_image_modified;
     ++_image_modified;
@@ -1171,6 +1293,7 @@ set_x_size(int x_size) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 set_y_size(int y_size) {
 set_y_size(int y_size) {
+  ReMutexHolder holder(_lock);
   if (_y_size != y_size) {
   if (_y_size != y_size) {
     nassertv(_texture_type != Texture::TT_1d_texture || y_size == 1);
     nassertv(_texture_type != Texture::TT_1d_texture || y_size == 1);
     _y_size = y_size;
     _y_size = y_size;
@@ -1189,6 +1312,7 @@ set_y_size(int y_size) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void Texture::
 INLINE void Texture::
 set_z_size(int z_size) {
 set_z_size(int z_size) {
+  ReMutexHolder holder(_lock);
   if (_z_size != z_size) {
   if (_z_size != z_size) {
     nassertv(_texture_type == Texture::TT_3d_texture ||
     nassertv(_texture_type == Texture::TT_3d_texture ||
              (_texture_type == Texture::TT_cube_map && z_size == 6) ||
              (_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_pages = false;
   _has_read_mipmaps = false;
   _has_read_mipmaps = false;
   _num_mipmap_levels_read = 0;
   _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.
 //               an existing Texture.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 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::
 void Texture::
 operator = (const Texture &copy) {
 operator = (const Texture &copy) {
   Namable::operator = (copy);
   Namable::operator = (copy);
+
+  ReMutexHolder holder(_lock);
+  ReMutexHolder holder2(copy._lock);
+
   _filename = copy._filename;
   _filename = copy._filename;
   _alpha_filename = copy._alpha_filename;
   _alpha_filename = copy._alpha_filename;
   if (!copy._fullpath.empty()) {
   if (!copy._fullpath.empty()) {
@@ -190,8 +166,13 @@ operator = (const Texture &copy) {
   _quality_level = copy._quality_level;
   _quality_level = copy._quality_level;
   _ram_image_compression = copy._ram_image_compression;
   _ram_image_compression = copy._ram_image_compression;
   _ram_images = copy._ram_images;
   _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;
   ++_properties_modified;
   ++_image_modified;
   ++_image_modified;
+  ++_simple_image_modified;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -245,6 +226,7 @@ void Texture::
 setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
 setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
               int z_size, Texture::ComponentType component_type,
               int z_size, Texture::ComponentType component_type,
               Texture::Format format) {
               Texture::Format format) {
+  ReMutexHolder holder(_lock);
   if (texture_type == TT_cube_map) {
   if (texture_type == TT_cube_map) {
     // Cube maps must always consist of six square images.
     // Cube maps must always consist of six square images.
     nassertv(x_size == y_size && z_size == 6);
     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_v = WM_clamp;
     _wrap_w = WM_clamp;
     _wrap_w = WM_clamp;
   }
   }
+  if (texture_type != TT_2d_texture) {
+    clear_simple_ram_image();
+  }
 
 
   _texture_type = texture_type;
   _texture_type = texture_type;
   _x_size = x_size;
   _x_size = x_size;
@@ -284,6 +269,7 @@ setup_texture(Texture::TextureType texture_type, int x_size, int y_size,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 generate_normalization_cube_map(int size) {
 generate_normalization_cube_map(int size) {
+  ReMutexHolder holder(_lock);
   setup_cube_map(size, T_unsigned_byte, F_rgb);
   setup_cube_map(size, T_unsigned_byte, F_rgb);
   PTA_uchar image = make_ram_image();
   PTA_uchar image = make_ram_image();
   _keep_ram_image = true;
   _keep_ram_image = true;
@@ -385,6 +371,7 @@ generate_normalization_cube_map(int size) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 generate_alpha_scale_map() {
 generate_alpha_scale_map() {
+  ReMutexHolder holder(_lock);
   setup_1d_texture(256, T_unsigned_byte, F_alpha);
   setup_1d_texture(256, T_unsigned_byte, F_alpha);
   set_wrap_u(WM_clamp);
   set_wrap_u(WM_clamp);
   set_minfilter(FT_nearest);
   set_minfilter(FT_nearest);
@@ -416,6 +403,7 @@ generate_alpha_scale_map() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 size_t Texture::
 size_t Texture::
 estimate_texture_memory() const {
 estimate_texture_memory() const {
+  ReMutexHolder holder(_lock);
   size_t pixels = get_x_size() * get_y_size();
   size_t pixels = get_x_size() * get_y_size();
 
 
   size_t bpp = 4;
   size_t bpp = 4;
@@ -487,6 +475,7 @@ estimate_texture_memory() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_aux_data(const string &key, TypedReferenceCount *aux_data) {
 set_aux_data(const string &key, TypedReferenceCount *aux_data) {
+  ReMutexHolder holder(_lock);
   _aux_data[key] = aux_data;
   _aux_data[key] = aux_data;
 }
 }
 
 
@@ -498,6 +487,7 @@ set_aux_data(const string &key, TypedReferenceCount *aux_data) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 clear_aux_data(const string &key) {
 clear_aux_data(const string &key) {
+  ReMutexHolder holder(_lock);
   _aux_data.erase(key);
   _aux_data.erase(key);
 }
 }
 
 
@@ -510,6 +500,7 @@ clear_aux_data(const string &key) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 TypedReferenceCount *Texture::
 TypedReferenceCount *Texture::
 get_aux_data(const string &key) const {
 get_aux_data(const string &key) const {
+  ReMutexHolder holder(_lock);
   AuxData::const_iterator di;
   AuxData::const_iterator di;
   di = _aux_data.find(key);
   di = _aux_data.find(key);
   if (di != _aux_data.end()) {
   if (di != _aux_data.end()) {
@@ -529,6 +520,7 @@ get_aux_data(const string &key) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
 read_txo(istream &in, const string &filename) {
 read_txo(istream &in, const string &filename) {
+  ReMutexHolder holder(_lock);
   DatagramInputFile din;
   DatagramInputFile din;
 
 
   if (!din.open(in)) {
   if (!din.open(in)) {
@@ -605,6 +597,7 @@ read_txo(istream &in, const string &filename) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
 write_txo(ostream &out, const string &filename) const {
 write_txo(ostream &out, const string &filename) const {
+  ReMutexHolder holder(_lock);
   DatagramOutputFile dout;
   DatagramOutputFile dout;
 
 
   if (!dout.open(out)) {
   if (!dout.open(out)) {
@@ -651,6 +644,7 @@ write_txo(ostream &out, const string &filename) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
 reload() {
 reload() {
+  ReMutexHolder holder(_lock);
   if (_loaded_from_image && has_filename()) {
   if (_loaded_from_image && has_filename()) {
     reload_ram_image();
     reload_ram_image();
     ++_image_modified;
     ++_image_modified;
@@ -671,6 +665,7 @@ reload() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 Texture *Texture::
 Texture *Texture::
 load_related(const InternalName *suffix) const {
 load_related(const InternalName *suffix) const {
+  ReMutexHolder holder(_lock);
   RelatedTextures::const_iterator ti;
   RelatedTextures::const_iterator ti;
   ti = _related_textures.find(suffix);
   ti = _related_textures.find(suffix);
   if (ti != _related_textures.end()) {
   if (ti != _related_textures.end()) {
@@ -719,6 +714,7 @@ load_related(const InternalName *suffix) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_wrap_u(Texture::WrapMode wrap) {
 set_wrap_u(Texture::WrapMode wrap) {
+  ReMutexHolder holder(_lock);
   if (_wrap_u != wrap) {
   if (_wrap_u != wrap) {
     ++_properties_modified;
     ++_properties_modified;
     _wrap_u = wrap;
     _wrap_u = wrap;
@@ -732,6 +728,7 @@ set_wrap_u(Texture::WrapMode wrap) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_wrap_v(Texture::WrapMode wrap) {
 set_wrap_v(Texture::WrapMode wrap) {
+  ReMutexHolder holder(_lock);
   if (_wrap_v != wrap) {
   if (_wrap_v != wrap) {
     ++_properties_modified;
     ++_properties_modified;
     _wrap_v = wrap;
     _wrap_v = wrap;
@@ -745,6 +742,7 @@ set_wrap_v(Texture::WrapMode wrap) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_wrap_w(Texture::WrapMode wrap) {
 set_wrap_w(Texture::WrapMode wrap) {
+  ReMutexHolder holder(_lock);
   if (_wrap_w != wrap) {
   if (_wrap_w != wrap) {
     ++_properties_modified;
     ++_properties_modified;
     _wrap_w = wrap;
     _wrap_w = wrap;
@@ -758,6 +756,7 @@ set_wrap_w(Texture::WrapMode wrap) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_minfilter(Texture::FilterType filter) {
 set_minfilter(Texture::FilterType filter) {
+  ReMutexHolder holder(_lock);
   if (_minfilter != filter) {
   if (_minfilter != filter) {
     ++_properties_modified;
     ++_properties_modified;
     _minfilter = filter;
     _minfilter = filter;
@@ -771,6 +770,7 @@ set_minfilter(Texture::FilterType filter) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_magfilter(Texture::FilterType filter) {
 set_magfilter(Texture::FilterType filter) {
+  ReMutexHolder holder(_lock);
   if (_magfilter != filter) {
   if (_magfilter != filter) {
     ++_properties_modified;
     ++_properties_modified;
     _magfilter = filter;
     _magfilter = filter;
@@ -788,6 +788,7 @@ set_magfilter(Texture::FilterType filter) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_anisotropic_degree(int anisotropic_degree) {
 set_anisotropic_degree(int anisotropic_degree) {
+  ReMutexHolder holder(_lock);
   if (_anisotropic_degree != anisotropic_degree) {
   if (_anisotropic_degree != anisotropic_degree) {
     ++_properties_modified;
     ++_properties_modified;
     _anisotropic_degree = anisotropic_degree;
     _anisotropic_degree = anisotropic_degree;
@@ -804,6 +805,7 @@ set_anisotropic_degree(int anisotropic_degree) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_border_color(const Colorf &color) {
 set_border_color(const Colorf &color) {
+  ReMutexHolder holder(_lock);
   if (_border_color != color) {
   if (_border_color != color) {
     ++_properties_modified;
     ++_properties_modified;
     _border_color = color;
     _border_color = color;
@@ -831,6 +833,7 @@ set_border_color(const Colorf &color) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_compression(Texture::CompressionMode compression) {
 set_compression(Texture::CompressionMode compression) {
+  ReMutexHolder holder(_lock);
   if (_compression != compression) {
   if (_compression != compression) {
     ++_properties_modified;
     ++_properties_modified;
     _compression = compression;
     _compression = compression;
@@ -870,6 +873,7 @@ set_render_to_texture(bool render_to_texture) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_quality_level(Texture::QualityLevel quality_level) {
 set_quality_level(Texture::QualityLevel quality_level) {
+  ReMutexHolder holder(_lock);
   if (_quality_level != quality_level) {
   if (_quality_level != quality_level) {
     ++_properties_modified;
     ++_properties_modified;
     _quality_level = quality_level;
     _quality_level = quality_level;
@@ -888,6 +892,7 @@ set_quality_level(Texture::QualityLevel quality_level) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int Texture::
 int Texture::
 get_expected_num_mipmap_levels() const {
 get_expected_num_mipmap_levels() const {
+  ReMutexHolder holder(_lock);
   int size = max(_x_size, max(_y_size, _z_size));
   int size = max(_x_size, max(_y_size, _z_size));
   int count = 1;
   int count = 1;
   while (size > 1) {
   while (size > 1) {
@@ -905,6 +910,7 @@ get_expected_num_mipmap_levels() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int Texture::
 int Texture::
 get_expected_mipmap_x_size(int n) const {
 get_expected_mipmap_x_size(int n) const {
+  ReMutexHolder holder(_lock);
   int size = max(_x_size, 1);
   int size = max(_x_size, 1);
   while (n > 0 && size > 1) {
   while (n > 0 && size > 1) {
     size >>= 1;
     size >>= 1;
@@ -921,6 +927,7 @@ get_expected_mipmap_x_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int Texture::
 int Texture::
 get_expected_mipmap_y_size(int n) const {
 get_expected_mipmap_y_size(int n) const {
+  ReMutexHolder holder(_lock);
   int size = max(_y_size, 1);
   int size = max(_y_size, 1);
   while (n > 0 && size > 1) {
   while (n > 0 && size > 1) {
     size >>= 1;
     size >>= 1;
@@ -937,6 +944,7 @@ get_expected_mipmap_y_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int Texture::
 int Texture::
 get_expected_mipmap_z_size(int n) const {
 get_expected_mipmap_z_size(int n) const {
+  ReMutexHolder holder(_lock);
   // 3-D textures have a different number of pages per each mipmap
   // 3-D textures have a different number of pages per each mipmap
   // level.  Other kinds of textures--especially, cube map
   // level.  Other kinds of textures--especially, cube map
   // textures--always have the same.
   // textures--always have the same.
@@ -983,6 +991,7 @@ get_expected_mipmap_z_size(int n) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
 has_ram_image() const {
 has_ram_image() const {
+  ReMutexHolder holder(_lock);
   return !_ram_images.empty() && !_ram_images[0]._image.empty();
   return !_ram_images.empty() && !_ram_images[0]._image.empty();
 }
 }
 
 
@@ -1016,6 +1025,7 @@ has_ram_image() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 CPTA_uchar Texture::
 CPTA_uchar Texture::
 get_ram_image() {
 get_ram_image() {
+  ReMutexHolder holder(_lock);
   if (_loaded_from_image && !has_ram_image() && has_filename()) {
   if (_loaded_from_image && !has_ram_image() && has_filename()) {
     reload_ram_image();
     reload_ram_image();
   }
   }
@@ -1040,6 +1050,7 @@ get_ram_image() {
 void Texture::
 void Texture::
 set_ram_image(PTA_uchar image, Texture::CompressionMode compression,
 set_ram_image(PTA_uchar image, Texture::CompressionMode compression,
               size_t page_size) {
               size_t page_size) {
+  ReMutexHolder holder(_lock);
   nassertv(compression != CM_default);
   nassertv(compression != CM_default);
   nassertv(compression != CM_off || image.size() == get_expected_ram_image_size());
   nassertv(compression != CM_off || image.size() == get_expected_ram_image_size());
   if (_ram_images.empty()) {
   if (_ram_images.empty()) {
@@ -1067,6 +1078,7 @@ set_ram_image(PTA_uchar image, Texture::CompressionMode compression,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 clear_ram_image() {
 clear_ram_image() {
+  ReMutexHolder holder(_lock);
   _ram_image_compression = CM_off;
   _ram_image_compression = CM_off;
   _ram_images.clear();
   _ram_images.clear();
 }
 }
@@ -1093,6 +1105,7 @@ get_keep_ram_image() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
 has_all_ram_mipmap_images() const {
 has_all_ram_mipmap_images() const {
+  ReMutexHolder holder(_lock);
   if (_ram_images.empty() || _ram_images[0]._image.empty()) {
   if (_ram_images.empty() || _ram_images[0]._image.empty()) {
     // If we don't even have a base image, the answer is no.
     // If we don't even have a base image, the answer is no.
     return false;
     return false;
@@ -1128,6 +1141,7 @@ has_all_ram_mipmap_images() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 CPTA_uchar Texture::
 CPTA_uchar Texture::
 get_ram_mipmap_image(int n) {
 get_ram_mipmap_image(int n) {
+  ReMutexHolder holder(_lock);
   if (n < (int)_ram_images.size()) {
   if (n < (int)_ram_images.size()) {
     return _ram_images[n]._image;
     return _ram_images[n]._image;
   }
   }
@@ -1145,6 +1159,7 @@ get_ram_mipmap_image(int n) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 PTA_uchar Texture::
 PTA_uchar Texture::
 make_ram_mipmap_image(int n) {
 make_ram_mipmap_image(int n) {
+  ReMutexHolder holder(_lock);
   nassertr(_ram_image_compression == CM_off, PTA_uchar(get_class_type()));
   nassertr(_ram_image_compression == CM_off, PTA_uchar(get_class_type()));
 
 
   while (n >= (int)_ram_images.size()) {
   while (n >= (int)_ram_images.size()) {
@@ -1171,6 +1186,7 @@ make_ram_mipmap_image(int n) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_ram_mipmap_image(int n, PTA_uchar image, size_t page_size) {
 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));
   nassertv(_ram_image_compression != CM_off || image.size() == get_expected_ram_mipmap_image_size(n));
 
 
   while (n >= (int)_ram_images.size()) {
   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::
 void Texture::
 clear_ram_mipmap_image(int n) {
 clear_ram_mipmap_image(int n) {
+  ReMutexHolder holder(_lock);
   if (n >= (int)_ram_images.size()) {
   if (n >= (int)_ram_images.size()) {
     return;
     return;
   }
   }
@@ -1212,6 +1229,7 @@ clear_ram_mipmap_image(int n) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 clear_ram_mipmap_images() {
 clear_ram_mipmap_images() {
+  ReMutexHolder holder(_lock);
   if (!_ram_images.empty()) {
   if (!_ram_images.empty()) {
     _ram_images.erase(_ram_images.begin() + 1, _ram_images.end());
     _ram_images.erase(_ram_images.begin() + 1, _ram_images.end());
   }
   }
@@ -1232,6 +1250,7 @@ clear_ram_mipmap_images() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 generate_ram_mipmap_images() {
 generate_ram_mipmap_images() {
+  ReMutexHolder holder(_lock);
   nassertv(has_ram_image());
   nassertv(has_ram_image());
   nassertv(get_ram_image_compression() == CM_off);
   nassertv(get_ram_image_compression() == CM_off);
   nassertv(get_component_type() != T_float);
   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
 //     Function: Texture::prepare
 //       Access: Published
 //       Access: Published
@@ -1300,6 +1455,7 @@ prepare(PreparedGraphicsObjects *prepared_objects) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
 is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 is_prepared(PreparedGraphicsObjects *prepared_objects) const {
+  ReMutexHolder holder(_lock);
   Contexts::const_iterator ci;
   Contexts::const_iterator ci;
   ci = _contexts.find(prepared_objects);
   ci = _contexts.find(prepared_objects);
   if (ci != _contexts.end()) {
   if (ci != _contexts.end()) {
@@ -1317,6 +1473,7 @@ is_prepared(PreparedGraphicsObjects *prepared_objects) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
 release(PreparedGraphicsObjects *prepared_objects) {
 release(PreparedGraphicsObjects *prepared_objects) {
+  ReMutexHolder holder(_lock);
   Contexts::iterator ci;
   Contexts::iterator ci;
   ci = _contexts.find(prepared_objects);
   ci = _contexts.find(prepared_objects);
   if (ci != _contexts.end()) {
   if (ci != _contexts.end()) {
@@ -1342,6 +1499,7 @@ release(PreparedGraphicsObjects *prepared_objects) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int Texture::
 int Texture::
 release_all() {
 release_all() {
+  ReMutexHolder holder(_lock);
   // We have to traverse a copy of the _contexts list, because the
   // We have to traverse a copy of the _contexts list, because the
   // PreparedGraphicsObjects object will call clear_prepared() in response
   // PreparedGraphicsObjects object will call clear_prepared() in response
   // to each release_texture(), and we don't want to be modifying the
   // to each release_texture(), and we don't want to be modifying the
@@ -1373,6 +1531,7 @@ release_all() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 write(ostream &out, int indent_level) const {
 write(ostream &out, int indent_level) const {
+  ReMutexHolder holder(_lock);
   indent(out, indent_level)
   indent(out, indent_level)
     << get_type() << " " << get_name();
     << get_type() << " " << get_name();
   if (!get_filename().empty()) {
   if (!get_filename().empty()) {
@@ -1549,6 +1708,13 @@ write(ostream &out, int indent_level) const {
     indent(out, indent_level + 2)
     indent(out, indent_level + 2)
       << "no ram image\n";
       << "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::
 void Texture::
 set_format(Texture::Format format) {
 set_format(Texture::Format format) {
+  ReMutexHolder holder(_lock);
   _format = format;
   _format = format;
 
 
   switch (_format) {
   switch (_format) {
@@ -1606,6 +1773,7 @@ set_format(Texture::Format format) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 set_component_type(Texture::ComponentType component_type) {
 set_component_type(Texture::ComponentType component_type) {
+  ReMutexHolder holder(_lock);
   _component_type = component_type;
   _component_type = component_type;
 
 
   switch (component_type) {
   switch (component_type) {
@@ -1662,6 +1830,7 @@ is_mipmap(FilterType filter_type) {
 TextureContext *Texture::
 TextureContext *Texture::
 prepare_now(PreparedGraphicsObjects *prepared_objects,
 prepare_now(PreparedGraphicsObjects *prepared_objects,
             GraphicsStateGuardianBase *gsg) {
             GraphicsStateGuardianBase *gsg) {
+  ReMutexHolder holder(_lock);
   Contexts::const_iterator ci;
   Contexts::const_iterator ci;
   ci = _contexts.find(prepared_objects);
   ci = _contexts.find(prepared_objects);
   if (ci != _contexts.end()) {
   if (ci != _contexts.end()) {
@@ -1689,6 +1858,7 @@ prepare_now(PreparedGraphicsObjects *prepared_objects,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 texture_uploaded() {
 texture_uploaded() {
+  ReMutexHolder holder(_lock);
   if (!keep_texture_ram && !_keep_ram_image) {
   if (!keep_texture_ram && !_keep_ram_image) {
     // Once we have prepared the texture, we can generally safely
     // Once we have prepared the texture, we can generally safely
     // remove the pixels from main RAM.  The GSG is now responsible
     // 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;
         << "Texture::read() - couldn't read: " << fullpath << endl;
       return false;
       return false;
     }
     }
+    Thread::consider_yield();
   }
   }
 
 
   PNMImage alpha_image;
   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;
           << "Texture::read() - couldn't read (alpha): " << alpha_fullpath << endl;
         return false;
         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_num_channels(),
                       alpha_image.get_maxval(), alpha_image.get_type());
                       alpha_image.get_maxval(), alpha_image.get_type());
       scaled.quick_filter_from(alpha_image);
       scaled.quick_filter_from(alpha_image);
+      Thread::consider_yield();
       alpha_image = scaled;
       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 scaled(x_size, y_size, pnmimage.get_num_channels(),
                     pnmimage.get_maxval(), pnmimage.get_type());
                     pnmimage.get_maxval(), pnmimage.get_type());
     scaled.quick_filter_from(pnmimage);
     scaled.quick_filter_from(pnmimage);
+    Thread::consider_yield();
 
 
     convert_from_pnmimage(_ram_images[n]._image, 
     convert_from_pnmimage(_ram_images[n]._image, 
                           get_expected_ram_mipmap_page_size(n), z,
                           get_expected_ram_mipmap_page_size(n), z,
-                          scaled);
+                          scaled, _num_components, _component_width);
   } else {
   } else {
     // Now copy the pixel data from the PNMImage into our internal
     // Now copy the pixel data from the PNMImage into our internal
     // _image component.
     // _image component.
     convert_from_pnmimage(_ram_images[n]._image, 
     convert_from_pnmimage(_ram_images[n]._image, 
                           get_expected_ram_mipmap_page_size(n), z,
                           get_expected_ram_mipmap_page_size(n), z,
-                          pnmimage);
+                          pnmimage, _num_components, _component_width);
   }
   }
+  Thread::consider_yield();
 
 
   return true;
   return true;
 }
 }
@@ -2408,6 +2583,7 @@ do_store_one(PNMImage &pnmimage, int z, int n) const {
   return convert_to_pnmimage(pnmimage, 
   return convert_to_pnmimage(pnmimage, 
                              get_expected_mipmap_x_size(n), 
                              get_expected_mipmap_x_size(n), 
                              get_expected_mipmap_y_size(n), 
                              get_expected_mipmap_y_size(n), 
+                             _num_components, _component_width,
                              _ram_images[n]._image, 
                              _ram_images[n]._image, 
                              get_ram_mipmap_page_size(n), z);
                              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
 //     Function: Texture::convert_from_pnmimage
-//       Access: Private
+//       Access: Private, Static
 //  Description: Internal method to convert pixel data from the
 //  Description: Internal method to convert pixel data from the
 //               indicated PNMImage into the given ram_image.
 //               indicated PNMImage into the given ram_image.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Texture::
 void Texture::
 convert_from_pnmimage(PTA_uchar &image, size_t page_size, int z,
 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 x_size = pnmimage.get_x_size();
   int y_size = pnmimage.get_y_size();
   int y_size = pnmimage.get_y_size();
   xelval maxval = pnmimage.get_maxval();
   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();
   bool img_has_alpha = pnmimage.has_alpha();
 
 
   int idx = page_size * z;
   int idx = page_size * z;
   nassertv(idx + page_size <= image.size());
   nassertv(idx + page_size <= image.size());
   unsigned char *p = &image[idx];
   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
     // Most common case: one byte per pixel, and the source image
     // shows a maxval of 255.  No scaling is necessary.
     // shows a maxval of 255.  No scaling is necessary.
     for (int j = y_size-1; j >= 0; j--) {
     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
     // Another possible case: two bytes per pixel, and the source
     // image shows a maxval of 65535.  Again, no scaling is necessary.
     // image shows a maxval of 65535.  Again, no scaling is necessary.
     for (int j = y_size-1; j >= 0; j--) {
     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
     // A less common case: one byte per pixel, but the maxval is
     // something other than 255.  In this case, we should scale the
     // something other than 255.  In this case, we should scale the
     // pixel values up to the appropriate amount.
     // 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
     // Another uncommon case: two bytes per pixel, and the maxval is
     // something other than 65535.  Again, we must scale the pixel
     // something other than 65535.  Again, we must scale the pixel
     // values.
     // values.
@@ -2837,14 +3014,15 @@ convert_from_pnmimage(PTA_uchar &image, size_t page_size, int z,
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::convert_to_pnmimage
 //     Function: Texture::convert_to_pnmimage
-//       Access: Private
+//       Access: Private, Static
 //  Description: Internal method to convert pixel data to the
 //  Description: Internal method to convert pixel data to the
 //               indicated PNMImage from the given ram_image.
 //               indicated PNMImage from the given ram_image.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
 convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
 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 has_alpha = pnmimage.has_alpha();
   bool is_grayscale = pnmimage.is_grayscale();
   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);
   nassertr(idx + page_size <= image.size(), false);
   const unsigned char *p = &image[idx];
   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 j = y_size-1; j >= 0; j--) {
       for (int i = 0; i < x_size; i++) {
       for (int i = 0; i < x_size; i++) {
         if (is_grayscale) {
         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 j = y_size-1; j >= 0; j--) {
       for (int i = 0; i < x_size; i++) {
       for (int i = 0; i < x_size; i++) {
         if (is_grayscale) {
         if (is_grayscale) {
@@ -2885,9 +3063,6 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
     }
     }
 
 
   } else {
   } else {
-    gobj_cat.error()
-      << "Couldn't write image for " << get_name()
-      << "; inappropriate data type " << (int)_component_type << ".\n";
     return false;
     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
 //     Function: Texture::read_txo_file
 //       Access: Private
 //       Access: Private

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

@@ -26,6 +26,8 @@
 #include "pmap.h"
 #include "pmap.h"
 #include "config_gobj.h"
 #include "config_gobj.h"
 #include "pStatCollector.h"
 #include "pStatCollector.h"
+#include "reMutex.h"
+#include "reMutexHolder.h"
 
 
 class PNMImage;
 class PNMImage;
 class TextureContext;
 class TextureContext;
@@ -313,8 +315,18 @@ PUBLISHED:
   void clear_ram_mipmap_images();
   void clear_ram_mipmap_images();
   void generate_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_properties_modified() const;
   INLINE UpdateSeq get_image_modified() const;
   INLINE UpdateSeq get_image_modified() const;
+  INLINE UpdateSeq get_simple_image_modified() const;
 
 
   void prepare(PreparedGraphicsObjects *prepared_objects);
   void prepare(PreparedGraphicsObjects *prepared_objects);
   bool is_prepared(PreparedGraphicsObjects *prepared_objects) const;
   bool is_prepared(PreparedGraphicsObjects *prepared_objects) const;
@@ -428,15 +440,20 @@ protected:
   };
   };
 
 
 private:
 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 clear_prepared(PreparedGraphicsObjects *prepared_objects);
 
 
   void consider_rescale(PNMImage &pnmimage, const string &name);
   void consider_rescale(PNMImage &pnmimage, const string &name);
   void consider_downgrade(PNMImage &pnmimage, int num_channels);
   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_byte(unsigned char *&p, int value);
   INLINE static void store_unscaled_short(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);
   INLINE static void store_scaled_byte(unsigned char *&p, int value, double scale);
@@ -480,6 +497,9 @@ private:
                                        size_t page_size);
                                        size_t page_size);
 
 
 protected:
 protected:
+  // Protects all of the members of this class.
+  ReMutex _lock;
+
   Filename _filename;
   Filename _filename;
   Filename _alpha_filename;
   Filename _alpha_filename;
   Filename _fullpath;
   Filename _fullpath;
@@ -550,9 +570,17 @@ protected:
   // additional mipmap levels.
   // additional mipmap levels.
   typedef pvector<RamImage> RamImages;
   typedef pvector<RamImage> RamImages;
   RamImages _ram_images;
   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 _properties_modified;
   UpdateSeq _image_modified;
   UpdateSeq _image_modified;
+  UpdateSeq _simple_image_modified;
   
   
 private:
 private:
   // The auxiliary data is not recorded to a bam file.
   // 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();
   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
 //     Function: TextureContext::mark_loaded
 //       Access: Public
 //       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::
 INLINE void TextureContext::
 mark_loaded() {
 mark_loaded() {
@@ -89,3 +101,19 @@ mark_loaded() {
   // Assume the texture is now resident.
   // Assume the texture is now resident.
   set_resident(true);
   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_modified() const;
   INLINE bool was_properties_modified() const;
   INLINE bool was_properties_modified() const;
   INLINE bool was_image_modified() const;
   INLINE bool was_image_modified() const;
+  INLINE bool was_simple_image_modified() const;
 
 
 public:
 public:
   INLINE void mark_loaded();
   INLINE void mark_loaded();
+  INLINE void mark_simple_loaded();
 
 
 private:
 private:
   // This cannot be a PT(Texture), because the texture and the GSG
   // This cannot be a PT(Texture), because the texture and the GSG
@@ -55,6 +57,7 @@ private:
   Texture *_texture;
   Texture *_texture;
   UpdateSeq _properties_modified;
   UpdateSeq _properties_modified;
   UpdateSeq _image_modified;
   UpdateSeq _image_modified;
+  UpdateSeq _simple_image_modified;
   
   
 public:
 public:
   static TypeHandle get_class_type() {
   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()
           gobj_cat.info()
             << "Texture " << filename << " found in disk cache.\n";
             << "Texture " << filename << " found in disk cache.\n";
           tex = DCAST(Texture, record->extract_data());
           tex = DCAST(Texture, record->extract_data());
+          if (preload_simple_textures && !tex->has_simple_ram_image()) {
+            tex->generate_simple_ram_image();
+          }
           if (!preload_textures) {
           if (!preload_textures) {
             // But drop the RAM until we need it.
             // But drop the RAM until we need it.
             tex->clear_ram_image();
             tex->clear_ram_image();
@@ -300,6 +303,11 @@ ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
       report_texture_unreadable(filename);
       report_texture_unreadable(filename);
       return NULL;
       return NULL;
     }
     }
+
+    if (preload_simple_textures) {
+      tex->generate_simple_ram_image();
+    }
+
     store_record = (record != (BamCacheRecord *)NULL);
     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);
     nassertr(tex->has_ram_image(), tex);
     record->set_data(tex, false);
     record->set_data(tex, false);
     cache->store(record);
     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);
   nassertr(!tex->get_fullpath().empty(), tex);
@@ -424,6 +432,11 @@ ns_load_texture(const Filename &orig_filename,
       report_texture_unreadable(filename);
       report_texture_unreadable(filename);
       return NULL;
       return NULL;
     }
     }
+
+    if (preload_simple_textures) {
+      tex->generate_simple_ram_image();
+    }
+
     store_record = (record != (BamCacheRecord *)NULL);
     store_record = (record != (BamCacheRecord *)NULL);
   }
   }
 
 
@@ -457,6 +470,11 @@ ns_load_texture(const Filename &orig_filename,
     cache->store(record);
     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);
   nassertr(!tex->get_fullpath().empty(), tex);
 
 
   // Finally, apply any post-loading texture filters.
   // 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 {
 class EXPCL_PANDA_GSGBASE GraphicsStateGuardianBase : public TypedWritableReferenceCount {
 PUBLISHED:
 PUBLISHED:
+  virtual bool get_incomplete_render() const=0;
+
   virtual bool prefers_triangle_strips() const=0;
   virtual bool prefers_triangle_strips() const=0;
   virtual int get_max_vertices_per_array() const=0;
   virtual int get_max_vertices_per_array() const=0;
   virtual int get_max_vertices_per_primitive() 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 \
     textureAttrib.I textureAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     textureCollection.I textureCollection.h \
     textureCollection.I textureCollection.h \
+    textureReloadRequest.I textureReloadRequest.h \
     textureStageCollection.I textureStageCollection.h \
     textureStageCollection.I textureStageCollection.h \
     transformState.I transformState.h \
     transformState.I transformState.h \
     transparencyAttrib.I transparencyAttrib.h \
     transparencyAttrib.I transparencyAttrib.h \
@@ -232,6 +233,7 @@
     textureAttrib.cxx \
     textureAttrib.cxx \
     texGenAttrib.cxx \
     texGenAttrib.cxx \
     textureCollection.cxx \
     textureCollection.cxx \
+    textureReloadRequest.cxx \
     textureStageCollection.cxx \
     textureStageCollection.cxx \
     transformState.cxx \
     transformState.cxx \
     transparencyAttrib.cxx \
     transparencyAttrib.cxx \
@@ -343,6 +345,7 @@
     textureAttrib.I textureAttrib.h \
     textureAttrib.I textureAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     textureCollection.I textureCollection.h \
     textureCollection.I textureCollection.h \
+    textureReloadRequest.I textureReloadRequest.h \
     textureStageCollection.I textureStageCollection.h \
     textureStageCollection.I textureStageCollection.h \
     transformState.I transformState.h \
     transformState.I transformState.h \
     transparencyAttrib.I transparencyAttrib.h \
     transparencyAttrib.I transparencyAttrib.h \

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

@@ -94,6 +94,7 @@
 #include "texMatrixAttrib.h"
 #include "texMatrixAttrib.h"
 #include "texProjectorEffect.h"
 #include "texProjectorEffect.h"
 #include "textureAttrib.h"
 #include "textureAttrib.h"
+#include "textureReloadRequest.h"
 #include "texGenAttrib.h"
 #include "texGenAttrib.h"
 #include "transformState.h"
 #include "transformState.h"
 #include "transparencyAttrib.h"
 #include "transparencyAttrib.h"
@@ -338,15 +339,6 @@ ConfigVariableString default_model_extension
           "Panda's loader; new code should probably give the correct name "
           "Panda's loader; new code should probably give the correct name "
           "for each model file they intend to load."));
           "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
 ConfigVariableEnum<LODNodeType> default_lod_type
 ("default-lod-type", LNT_pop,
 ("default-lod-type", LNT_pop,
  PRC_DESC("Set this to either 'pop' or 'fade' to determine the type of "
  PRC_DESC("Set this to either 'pop' or 'fade' to determine the type of "
@@ -448,6 +440,7 @@ init_libpgraph() {
   TexMatrixAttrib::init_type();
   TexMatrixAttrib::init_type();
   TexProjectorEffect::init_type();
   TexProjectorEffect::init_type();
   TextureAttrib::init_type();
   TextureAttrib::init_type();
+  TextureReloadRequest::init_type();
   TexGenAttrib::init_type();
   TexGenAttrib::init_type();
   TransformState::init_type();
   TransformState::init_type();
   TransparencyAttrib::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 ConfigVariableList load_file_type;
 extern ConfigVariableString default_model_extension;
 extern ConfigVariableString default_model_extension;
-extern EXPCL_PANDA_PGRAPH ConfigVariableBool allow_incomplete_render;
 
 
 extern ConfigVariableEnum<LODNodeType> default_lod_type;
 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_multisample_color(0.78f, 0.05f, 0.81f, 1.0f);
   static const Colorf flash_dual_color(0.92f, 0.01f, 0.01f, 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();
   Thread *current_thread = traverser->get_current_thread();
 
 
   // Check to see if there's a special transparency setting.
   // Check to see if there's a special transparency setting.
@@ -264,7 +264,7 @@ finish_cull(SceneSetup *scene_setup, Thread *current_thread) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void CullResult::
 void CullResult::
 draw(Thread *current_thread) {
 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.
   // Ask the bin manager for the correct order to draw all the bins.
   CullBinManager *bin_manager = CullBinManager::get_global_ptr();
   CullBinManager *bin_manager = CullBinManager::get_global_ptr();

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

@@ -25,6 +25,7 @@
 #include "textureAttrib.cxx"
 #include "textureAttrib.cxx"
 #include "texGenAttrib.cxx"
 #include "texGenAttrib.cxx"
 #include "textureCollection.cxx"
 #include "textureCollection.cxx"
+#include "textureReloadRequest.cxx"
 #include "textureStageCollection.cxx"
 #include "textureStageCollection.cxx"
 #include "transformState.cxx"
 #include "transformState.cxx"
 #include "transparencyAttrib.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++;
       x++;
     }
     }
+    Thread::consider_yield();
   }
   }
 
 
   /* Step 7: Finish decompression */
   /* Step 7: Finish decompression */