split out PreparedGraphicsObjects from GraphicsStateGuardians
This commit is contained in:
parent
0e81017698
commit
232f9395bf
|
|
@ -551,7 +551,7 @@ get_screenshot(PNMImage &image) {
|
|||
PixelBuffer::F_rgb);
|
||||
|
||||
RenderBuffer buffer = gsg->get_render_buffer(get_screenshot_buffer_type());
|
||||
if (!p.copy(gsg, this, buffer)) {
|
||||
if (!gsg->copy_pixel_buffer(&p, this, buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Filename: graphicsBuffer.h
|
||||
// Created by: mike (09Jan97)
|
||||
// Created by: drose (06Feb04)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
|
|||
|
|
@ -437,7 +437,8 @@ end_frame() {
|
|||
nassertv(has_texture());
|
||||
DisplayRegion dr(this, _x_size, _y_size);
|
||||
RenderBuffer buffer = _gsg->get_render_buffer(get_draw_buffer_type());
|
||||
get_texture()->copy(_gsg, &dr, buffer);
|
||||
TextureContext *tc = get_texture()->prepare_now(_gsg->get_prepared_objects(), _gsg);
|
||||
_gsg->copy_texture(tc, &dr, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,17 @@ ClipPlaneInfo() {
|
|||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::release_all_textures
|
||||
// Access: Public
|
||||
// Description: Frees the resources for all textures associated with
|
||||
// this GSG.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE int GraphicsStateGuardian::
|
||||
release_all_textures() {
|
||||
return _prepared_objects->release_all_textures();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::set_active
|
||||
// Access: Published
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ GraphicsStateGuardian(const FrameBufferProperties &properties) {
|
|||
_needs_reset = true;
|
||||
_closing_gsg = false;
|
||||
_active = true;
|
||||
_prepared_objects = new PreparedGraphicsObjects;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -146,58 +147,15 @@ get_render_buffer(int buffer_type) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::release_all_textures
|
||||
// Access: Public
|
||||
// Description: Frees the resources for all textures associated with
|
||||
// this GSG.
|
||||
// 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).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void GraphicsStateGuardian::
|
||||
release_all_textures() {
|
||||
// We must get a copy of the _prepared_textures list first, because
|
||||
// each call to release_texture() will remove that texture from the
|
||||
// list, and we don't want to traverse a list while we're modifying
|
||||
// it!
|
||||
|
||||
Textures temp = _prepared_textures;
|
||||
for (Textures::const_iterator ti = temp.begin();
|
||||
ti != temp.end();
|
||||
++ti) {
|
||||
release_texture(*ti);
|
||||
}
|
||||
|
||||
// Now that we've released all of the textures, the
|
||||
// _prepared_textures list should have completely emptied itself.
|
||||
nassertv(_prepared_textures.empty());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::release_all_geoms
|
||||
// Access: Public
|
||||
// Description: Frees the resources for all Geoms and GeomNodes
|
||||
// associated with this GSG. Warning! This may make
|
||||
// the Geoms unrenderable, if the Panda-level
|
||||
// information has been deleted.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void GraphicsStateGuardian::
|
||||
release_all_geoms() {
|
||||
// As above, for both Geoms and GeomNodes.
|
||||
|
||||
Geoms temp_geoms = _prepared_geoms;
|
||||
for (Geoms::const_iterator gi = temp_geoms.begin();
|
||||
gi != temp_geoms.end();
|
||||
++gi) {
|
||||
release_geom(*gi);
|
||||
}
|
||||
|
||||
GeomNodes temp_geom_nodes = _prepared_geom_nodes;
|
||||
for (GeomNodes::const_iterator gni = temp_geom_nodes.begin();
|
||||
gni != temp_geom_nodes.end();
|
||||
++gni) {
|
||||
release_geom_node(*gni);
|
||||
}
|
||||
|
||||
nassertv(_prepared_geoms.empty());
|
||||
nassertv(_prepared_geom_nodes.empty());
|
||||
PreparedGraphicsObjects *GraphicsStateGuardian::
|
||||
get_prepared_objects() {
|
||||
return _prepared_objects;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -397,6 +355,12 @@ prepare_lens() {
|
|||
////////////////////////////////////////////////////////////////////
|
||||
bool GraphicsStateGuardian::
|
||||
begin_frame() {
|
||||
// Now we know the GSG is the currently active context, so this is a
|
||||
// good time to release any textures or geoms that had been queued
|
||||
// up to release in the past frame, and load up any newly requested
|
||||
// textures.
|
||||
_prepared_objects->update(this);
|
||||
|
||||
// Undo any lighting we had enabled last frame, to force the lights
|
||||
// to be reissued, in case their parameters or positions have
|
||||
// changed between frames.
|
||||
|
|
@ -1229,126 +1193,6 @@ set_blend_mode(ColorWriteAttrib::Mode, ColorBlendAttrib::Mode,
|
|||
TransparencyAttrib::Mode) {
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::mark_prepared_texture
|
||||
// Access: Protected
|
||||
// Description: This is intended to be called from within
|
||||
// prepare_texture(). It adds the indicated
|
||||
// TextureContext pointer to the _prepared_textures set,
|
||||
// and returns true if it was successfully added
|
||||
// (i.e. it was not already in the set).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool GraphicsStateGuardian::
|
||||
mark_prepared_texture(TextureContext *tc) {
|
||||
bool prepared = _prepared_textures.insert(tc).second;
|
||||
#ifdef DO_PSTATS
|
||||
if (prepared) {
|
||||
_total_texusage_pcollector.add_level(tc->estimate_texture_memory());
|
||||
}
|
||||
#endif
|
||||
return prepared;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::unmark_prepared_texture
|
||||
// Access: Protected
|
||||
// Description: This is intended to be called from within
|
||||
// release_texture(). It removes the indicated
|
||||
// TextureContext pointer from the _prepared_textures
|
||||
// set, and returns true if it was successfully removed
|
||||
// (i.e. it had been in the set).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool GraphicsStateGuardian::
|
||||
unmark_prepared_texture(TextureContext *tc) {
|
||||
bool removed = (_prepared_textures.erase(tc) != 0);
|
||||
#ifdef DO_PSTATS
|
||||
if (removed) {
|
||||
_total_texusage_pcollector.sub_level(tc->estimate_texture_memory());
|
||||
}
|
||||
#endif
|
||||
return removed;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::mark_prepared_geom
|
||||
// Access: Protected
|
||||
// Description: This is intended to be called from within
|
||||
// prepare_geom(). It adds the indicated GeomContext
|
||||
// pointer to the _prepared_geoms set, and returns true
|
||||
// if it was successfully added (i.e. it was not already
|
||||
// in the set).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool GraphicsStateGuardian::
|
||||
mark_prepared_geom(GeomContext *gc) {
|
||||
bool prepared = _prepared_geoms.insert(gc).second;
|
||||
#ifdef DO_PSTATS
|
||||
if (prepared) {
|
||||
_total_geom_pcollector.add_level(1);
|
||||
}
|
||||
#endif
|
||||
return prepared;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::unmark_prepared_geom
|
||||
// Access: Protected
|
||||
// Description: This is intended to be called from within
|
||||
// release_geom(). It removes the indicated GeomContext
|
||||
// pointer from the _prepared_geoms set, and returns
|
||||
// true if it was successfully removed (i.e. it had been
|
||||
// in the set).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool GraphicsStateGuardian::
|
||||
unmark_prepared_geom(GeomContext *gc) {
|
||||
bool removed = (_prepared_geoms.erase(gc) != 0);
|
||||
#ifdef DO_PSTATS
|
||||
if (removed) {
|
||||
_total_geom_pcollector.sub_level(1);
|
||||
}
|
||||
#endif
|
||||
return removed;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::mark_prepared_geom_node
|
||||
// Access: Protected
|
||||
// Description: This is intended to be called from within
|
||||
// prepare_geom_node(). It adds the indicated
|
||||
// GeomNodeContext pointer to the _prepared_geom_nodes
|
||||
// set, and returns true if it was successfully added
|
||||
// (i.e. it was not already in the set).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool GraphicsStateGuardian::
|
||||
mark_prepared_geom_node(GeomNodeContext *gnc) {
|
||||
bool prepared = _prepared_geom_nodes.insert(gnc).second;
|
||||
#ifdef DO_PSTATS
|
||||
if (prepared) {
|
||||
_total_geom_node_pcollector.add_level(1);
|
||||
}
|
||||
#endif
|
||||
return prepared;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::unmark_prepared_geom_node
|
||||
// Access: Protected
|
||||
// Description: This is intended to be called from within
|
||||
// release_geom_node(). It removes the indicated
|
||||
// GeomNodeContext pointer from the _prepared_geom_nodes
|
||||
// set, and returns true if it was successfully removed
|
||||
// (i.e. it had been in the set).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool GraphicsStateGuardian::
|
||||
unmark_prepared_geom_node(GeomNodeContext *gnc) {
|
||||
bool removed = (_prepared_geom_nodes.erase(gnc) != 0);
|
||||
#ifdef DO_PSTATS
|
||||
if (removed) {
|
||||
_total_geom_node_pcollector.sub_level(1);
|
||||
}
|
||||
#endif
|
||||
return removed;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GraphicsStateGuardian::free_pointers
|
||||
// Access: Protected, Virtual
|
||||
|
|
@ -1371,8 +1215,18 @@ void GraphicsStateGuardian::
|
|||
close_gsg() {
|
||||
_closing_gsg = true;
|
||||
free_pointers();
|
||||
release_all_textures();
|
||||
release_all_geoms();
|
||||
|
||||
// If we're not sharing the prepared objects list with any other
|
||||
// GSG, go ahread and release all the textures and geoms now. This
|
||||
// isn't really a reliable test of whether we are sharing this list,
|
||||
// but it's not too important if we get this wrong since this ought
|
||||
// to be an optional cleanup anyway. (Presumably, the underlying
|
||||
// graphics API will properly clean up outstanding textures and
|
||||
// geoms when the last context using them is released.)
|
||||
if (_prepared_objects->get_ref_count() == 1) {
|
||||
release_all_textures();
|
||||
//release_all_geoms();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -1522,7 +1376,9 @@ get_untextured_state() {
|
|||
|
||||
void GraphicsStateGuardian::
|
||||
traverse_prepared_textures(bool (*pertex_callbackfn)(TextureContext *,void *),void *callback_arg) {
|
||||
for (Textures::const_iterator ti = _prepared_textures.begin(); ti != _prepared_textures.end();
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include "frameBufferProperties.h"
|
||||
#include "displayRegionStack.h"
|
||||
#include "lensStack.h"
|
||||
#include "preparedGraphicsObjects.h"
|
||||
|
||||
#include "graphicsStateGuardianBase.h"
|
||||
#include "graphicsThreadingModel.h"
|
||||
|
|
@ -69,8 +70,8 @@ public:
|
|||
virtual ~GraphicsStateGuardian();
|
||||
|
||||
PUBLISHED:
|
||||
void release_all_textures();
|
||||
void release_all_geoms();
|
||||
INLINE int release_all_textures();
|
||||
// int release_all_geoms();
|
||||
|
||||
INLINE void set_active(bool active);
|
||||
INLINE bool is_active() const;
|
||||
|
|
@ -84,6 +85,8 @@ public:
|
|||
INLINE void set_scene(SceneSetup *scene_setup);
|
||||
INLINE SceneSetup *get_scene() const;
|
||||
|
||||
virtual PreparedGraphicsObjects *get_prepared_objects();
|
||||
|
||||
virtual TextureContext *prepare_texture(Texture *tex);
|
||||
virtual void apply_texture(TextureContext *tc);
|
||||
virtual void release_texture(TextureContext *tc);
|
||||
|
|
@ -193,13 +196,6 @@ protected:
|
|||
CPT(DisplayRegion) dr)=0;
|
||||
virtual void restore_frame_buffer(SavedFrameBuffer *frame_buffer)=0;
|
||||
|
||||
bool mark_prepared_texture(TextureContext *tc);
|
||||
bool unmark_prepared_texture(TextureContext *tc);
|
||||
bool mark_prepared_geom(GeomContext *gc);
|
||||
bool unmark_prepared_geom(GeomContext *gc);
|
||||
bool mark_prepared_geom_node(GeomNodeContext *gnc);
|
||||
bool unmark_prepared_geom_node(GeomNodeContext *gnc);
|
||||
|
||||
virtual void free_pointers();
|
||||
virtual void close_gsg();
|
||||
void panic_deactivate();
|
||||
|
|
@ -276,6 +272,8 @@ protected:
|
|||
bool _closing_gsg;
|
||||
bool _active;
|
||||
|
||||
PT(PreparedGraphicsObjects) _prepared_objects;
|
||||
|
||||
public:
|
||||
// Statistics
|
||||
static PStatCollector _total_texusage_pcollector;
|
||||
|
|
@ -321,15 +319,6 @@ private:
|
|||
pvector<ClipPlaneInfo> _clip_plane_info;
|
||||
bool _clip_planes_enabled_this_frame;
|
||||
|
||||
// NOTE: on win32 another DLL (e.g. libpandadx.dll) cannot access
|
||||
// these sets directly due to exported template issue
|
||||
typedef pset<TextureContext *> Textures;
|
||||
Textures _prepared_textures;
|
||||
typedef pset<GeomContext *> Geoms;
|
||||
Geoms _prepared_geoms;
|
||||
typedef pset<GeomNodeContext *> GeomNodes;
|
||||
GeomNodes _prepared_geom_nodes;
|
||||
|
||||
FrameBufferProperties _properties;
|
||||
PT(GraphicsPipe) _pipe;
|
||||
GraphicsEngine *_engine;
|
||||
|
|
|
|||
|
|
@ -608,8 +608,9 @@ render_screen(GraphicsEngine *engine, NonlinearImager::Screen &screen) {
|
|||
|
||||
// Copy the results of the render from the frame buffer into the
|
||||
// screen's texture.
|
||||
screen._texture->copy(_gsg, scratch_region,
|
||||
_gsg->get_render_buffer(RenderBuffer::T_back));
|
||||
TextureContext *tc = screen._texture->prepare_now(_gsg->get_prepared_objects(), _gsg);
|
||||
_gsg->copy_texture(tc, scratch_region,
|
||||
_gsg->get_render_buffer(RenderBuffer::T_back));
|
||||
|
||||
// It might be nice if we didn't throw away the scratch region every
|
||||
// time, which prevents us from preserving cull state from one frame
|
||||
|
|
|
|||
|
|
@ -1643,6 +1643,9 @@ draw_sphere(GeomSphere *geom, GeomContext *) {
|
|||
// responsibility of the calling function to later
|
||||
// call release_texture() with this same pointer (which
|
||||
// will also delete the pointer).
|
||||
//
|
||||
// This function should not be called directly to
|
||||
// prepare a texture. Instead, call Texture::prepare().
|
||||
////////////////////////////////////////////////////////////////////
|
||||
TextureContext *CLP(GraphicsStateGuardian)::
|
||||
prepare_texture(Texture *tex) {
|
||||
|
|
@ -1654,13 +1657,6 @@ prepare_texture(Texture *tex) {
|
|||
specify_texture(tex);
|
||||
apply_texture_immediate(tex);
|
||||
|
||||
bool inserted = mark_prepared_texture(gtc);
|
||||
|
||||
// If this assertion fails, the same texture was prepared twice,
|
||||
// which shouldn't be possible, since the texture itself should
|
||||
// detect this.
|
||||
nassertr(inserted, gtc);
|
||||
|
||||
report_my_gl_errors();
|
||||
return gtc;
|
||||
}
|
||||
|
|
@ -1695,32 +1691,19 @@ apply_texture(TextureContext *tc) {
|
|||
// Function: CLP(GraphicsStateGuardian)::release_texture
|
||||
// Access: Public, Virtual
|
||||
// Description: Frees the GL resources previously allocated for the
|
||||
// texture.
|
||||
// texture. This function should never be called
|
||||
// directly; instead, call Texture::release() (or simply
|
||||
// let the Texture destruct).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void CLP(GraphicsStateGuardian)::
|
||||
release_texture(TextureContext *tc) {
|
||||
CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
|
||||
Texture *tex = tc->_texture;
|
||||
|
||||
if (!_closing_gsg) {
|
||||
// Don't bother to delete the GL texture if we're about to destroy
|
||||
// the context anyway.
|
||||
GLP(DeleteTextures)(1, >c->_index);
|
||||
}
|
||||
GLP(DeleteTextures)(1, >c->_index);
|
||||
report_my_gl_errors();
|
||||
|
||||
gtc->_index = 0;
|
||||
|
||||
bool erased = unmark_prepared_texture(gtc);
|
||||
|
||||
// If this assertion fails, a texture was released that hadn't been
|
||||
// prepared (or a texture was released twice).
|
||||
nassertv(erased);
|
||||
|
||||
tex->clear_gsg(this);
|
||||
|
||||
delete gtc;
|
||||
if (!_closing_gsg) {
|
||||
report_my_gl_errors();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -1811,7 +1794,7 @@ prepare_geom_node(GeomNode *node) {
|
|||
_texturing_enabled = old_texturing_enabled;
|
||||
_vertex_colors_enabled = old_vertex_colors_enabled;
|
||||
|
||||
bool inserted = mark_prepared_geom_node(ggnc);
|
||||
bool inserted = _prepared_objects->mark_prepared_geom_node(ggnc);
|
||||
|
||||
// If this assertion fails, the same GeomNode was prepared twice,
|
||||
// which shouldn't be possible, since the GeomNode itself should
|
||||
|
|
@ -1875,7 +1858,7 @@ release_geom_node(GeomNodeContext *gnc) {
|
|||
CLP(GeomNodeContext) *ggnc = DCAST(CLP(GeomNodeContext), gnc);
|
||||
GLP(DeleteLists)(ggnc->_index, 1);
|
||||
|
||||
bool erased = unmark_prepared_geom_node(ggnc);
|
||||
bool erased = _prepared_objects->unmark_prepared_geom_node(ggnc);
|
||||
|
||||
// If this assertion fails, a GeomNode was released that hadn't
|
||||
// been prepared (or a GeomNode was released twice).
|
||||
|
|
@ -2220,7 +2203,9 @@ issue_texture(const TextureAttrib *attrib) {
|
|||
enable_texturing(true);
|
||||
Texture *tex = attrib->get_texture();
|
||||
nassertv(tex != (Texture *)NULL);
|
||||
tex->apply(this);
|
||||
|
||||
TextureContext *tc = tex->prepare_now(_prepared_objects, this);
|
||||
apply_texture(tc);
|
||||
}
|
||||
report_my_gl_errors();
|
||||
}
|
||||
|
|
@ -3916,7 +3901,8 @@ save_frame_buffer(const RenderBuffer &buffer,
|
|||
if (buffer._buffer_type & RenderBuffer::T_back) {
|
||||
// Save the color buffer.
|
||||
sfb->_back_rgba = new Texture;
|
||||
copy_texture(sfb->_back_rgba->prepare(this), dr, buffer);
|
||||
TextureContext *tc = sfb->_back_rgba->prepare_now(_prepared_objects, this);
|
||||
copy_texture(tc, dr, buffer);
|
||||
}
|
||||
|
||||
return sfb;
|
||||
|
|
@ -3934,8 +3920,8 @@ restore_frame_buffer(SavedFrameBuffer *frame_buffer) {
|
|||
if (sfb->_back_rgba != (Texture *)NULL &&
|
||||
(sfb->_buffer._buffer_type & RenderBuffer::T_back) != 0) {
|
||||
// Restore the color buffer.
|
||||
draw_texture(sfb->_back_rgba->prepare(this),
|
||||
sfb->_display_region, sfb->_buffer);
|
||||
TextureContext *tc = sfb->_back_rgba->prepare_now(_prepared_objects, this);
|
||||
draw_texture(tc, sfb->_display_region, sfb->_buffer);
|
||||
}
|
||||
|
||||
if (sfb->_depth != (PixelBuffer *)NULL &&
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ make_gsg(const FrameBufferProperties &properties) {
|
|||
|
||||
// Now we can make a GSG.
|
||||
PT(glxGraphicsStateGuardian) gsg =
|
||||
new glxGraphicsStateGuardian(new_properties, context, fbconfig,
|
||||
new glxGraphicsStateGuardian(new_properties, NULL, context, fbconfig,
|
||||
_display, _screen);
|
||||
return gsg.p();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ TypeHandle glxGraphicsStateGuardian::_type_handle;
|
|||
////////////////////////////////////////////////////////////////////
|
||||
glxGraphicsStateGuardian::
|
||||
glxGraphicsStateGuardian(const FrameBufferProperties &properties,
|
||||
glxGraphicsStateGuardian *share_with,
|
||||
GLXContext context, GLXFBConfig fbconfig,
|
||||
Display *display, int screen) :
|
||||
GLGraphicsStateGuardian(properties),
|
||||
|
|
@ -36,6 +37,9 @@ glxGraphicsStateGuardian(const FrameBufferProperties &properties,
|
|||
_display(display),
|
||||
_screen(screen)
|
||||
{
|
||||
if (share_with != (glxGraphicsStateGuardian *)NULL) {
|
||||
_prepared_objects = share_with->get_prepared_objects();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
class glxGraphicsStateGuardian : public GLGraphicsStateGuardian {
|
||||
public:
|
||||
glxGraphicsStateGuardian(const FrameBufferProperties &properties,
|
||||
glxGraphicsStateGuardian *share_with,
|
||||
GLXContext context, GLXFBConfig fbconfig,
|
||||
Display *display, int screen);
|
||||
virtual ~glxGraphicsStateGuardian();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#begin lib_target
|
||||
#define TARGET gobj
|
||||
#define LOCAL_LIBS \
|
||||
event linmath mathutil pnmimage gsgbase putil
|
||||
pstatclient event linmath mathutil pnmimage gsgbase putil
|
||||
|
||||
#define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx
|
||||
|
||||
|
|
@ -19,7 +19,9 @@
|
|||
matrixLens.I matrixLens.h \
|
||||
orthographicLens.I orthographicLens.h perspectiveLens.I \
|
||||
perspectiveLens.h pixelBuffer.I pixelBuffer.N \
|
||||
pixelBuffer.h lens.h lens.I \
|
||||
pixelBuffer.h \
|
||||
preparedGraphicsObjects.I preparedGraphicsObjects.h \
|
||||
lens.h lens.I \
|
||||
savedContext.I savedContext.h \
|
||||
texture.I texture.N texture.h \
|
||||
textureContext.I textureContext.h \
|
||||
|
|
@ -33,7 +35,9 @@
|
|||
geomQuad.cxx geomSphere.cxx geomSprite.cxx geomTri.cxx \
|
||||
geomTrifan.cxx geomTristrip.cxx imageBuffer.cxx material.cxx \
|
||||
materialPool.cxx matrixLens.cxx orthographicLens.cxx \
|
||||
perspectiveLens.cxx pixelBuffer.cxx lens.cxx \
|
||||
perspectiveLens.cxx pixelBuffer.cxx \
|
||||
preparedGraphicsObjects.cxx \
|
||||
lens.cxx \
|
||||
savedContext.cxx texture.cxx textureContext.cxx texturePool.cxx
|
||||
|
||||
#define INSTALL_HEADERS \
|
||||
|
|
@ -47,6 +51,7 @@
|
|||
materialPool.I materialPool.h matrixLens.I matrixLens.h \
|
||||
orthographicLens.I orthographicLens.h perspectiveLens.I \
|
||||
perspectiveLens.h pixelBuffer.I pixelBuffer.h \
|
||||
preparedGraphicsObjects.I preparedGraphicsObjects.h \
|
||||
lens.h lens.I \
|
||||
savedContext.I savedContext.h \
|
||||
texture.I texture.h \
|
||||
|
|
|
|||
|
|
@ -138,8 +138,10 @@ ostream &operator << (ostream &out, GeomAttrType t) {
|
|||
////////////////////////////////////////////////////////////////////
|
||||
Geom::
|
||||
Geom(void) : dDrawable() {
|
||||
/*
|
||||
_prepared_gsg = (GraphicsStateGuardianBase *)NULL;
|
||||
_prepared_context = (GeomContext *)NULL;
|
||||
*/
|
||||
init();
|
||||
}
|
||||
|
||||
|
|
@ -150,8 +152,10 @@ Geom(void) : dDrawable() {
|
|||
////////////////////////////////////////////////////////////////////
|
||||
Geom::
|
||||
Geom(const Geom& copy) : dDrawable() {
|
||||
/*
|
||||
_prepared_gsg = (GraphicsStateGuardianBase *)NULL;
|
||||
_prepared_context = (GeomContext *)NULL;
|
||||
*/
|
||||
*this = copy;
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +166,7 @@ Geom(const Geom& copy) : dDrawable() {
|
|||
////////////////////////////////////////////////////////////////////
|
||||
Geom::
|
||||
~Geom() {
|
||||
unprepare();
|
||||
// unprepare();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -463,9 +467,10 @@ draw(GraphicsStateGuardianBase *gsg) {
|
|||
if (is_dirty()) {
|
||||
config();
|
||||
}
|
||||
/*
|
||||
if (_prepared_gsg == gsg) {
|
||||
draw_immediate(gsg, _prepared_context);
|
||||
} else {
|
||||
} else */ {
|
||||
draw_immediate(gsg, (GeomContext *)NULL);
|
||||
}
|
||||
}
|
||||
|
|
@ -513,7 +518,7 @@ config() {
|
|||
}
|
||||
|
||||
// Mark the Geom as needing to be prepared again.
|
||||
unprepare();
|
||||
// unprepare();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -547,6 +552,7 @@ output(ostream &out) const {
|
|||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Geom::prepare
|
||||
// Access: Public
|
||||
|
|
@ -620,6 +626,7 @@ clear_gsg(GraphicsStateGuardianBase *gsg) {
|
|||
_prepared_context = (GeomContext *)NULL;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Geom::init
|
||||
|
|
|
|||
|
|
@ -224,10 +224,12 @@ public:
|
|||
INLINE ColorIterator make_color_iterator() const;
|
||||
INLINE const Colorf &get_next_color(ColorIterator &citerator) const;
|
||||
|
||||
/*
|
||||
GeomContext *prepare(GraphicsStateGuardianBase *gsg);
|
||||
void unprepare();
|
||||
void unprepare(GraphicsStateGuardianBase *gsg);
|
||||
void clear_gsg(GraphicsStateGuardianBase *gsg);
|
||||
*/
|
||||
|
||||
protected:
|
||||
void init();
|
||||
|
|
@ -261,8 +263,10 @@ protected:
|
|||
// intended to reduce memory overhead that would otherwise be
|
||||
// required to support a little-used feature (having two
|
||||
// simultaneous GSG's).
|
||||
/*
|
||||
GraphicsStateGuardianBase *_prepared_gsg;
|
||||
GeomContext *_prepared_context;
|
||||
*/
|
||||
|
||||
public:
|
||||
//static void register_with_read_factory(void);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "matrixLens.cxx"
|
||||
#include "perspectiveLens.cxx"
|
||||
#include "pixelBuffer.cxx"
|
||||
#include "preparedGraphicsObjects.cxx"
|
||||
#include "lens.cxx"
|
||||
#include "savedContext.cxx"
|
||||
#include "texture.cxx"
|
||||
|
|
|
|||
|
|
@ -43,10 +43,6 @@ PUBLISHED:
|
|||
public:
|
||||
virtual void config( void ) { WritableConfigurable::config(); }
|
||||
|
||||
virtual bool copy(GraphicsStateGuardianBase *, const DisplayRegion *)=0;
|
||||
virtual bool copy(GraphicsStateGuardianBase *, const DisplayRegion *,
|
||||
const RenderBuffer &rb)=0;
|
||||
|
||||
PUBLISHED:
|
||||
INLINE bool has_filename() const;
|
||||
INLINE const Filename &get_filename() const;
|
||||
|
|
|
|||
|
|
@ -421,13 +421,3 @@ copy(const PixelBuffer *pb) {
|
|||
if (!pb->_image.empty())
|
||||
_image.v() = pb->_image.v();
|
||||
}
|
||||
|
||||
bool PixelBuffer::copy(GraphicsStateGuardianBase *gsg, const DisplayRegion *dr) {
|
||||
return gsg->copy_pixel_buffer(this, dr);
|
||||
}
|
||||
|
||||
bool PixelBuffer::copy(GraphicsStateGuardianBase *gsg, const DisplayRegion *dr,
|
||||
const RenderBuffer &rb) {
|
||||
return gsg->copy_pixel_buffer(this, dr, rb);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -105,10 +105,6 @@ public:
|
|||
bool store( PNMImage& pnmimage ) const;
|
||||
|
||||
void copy(const PixelBuffer *pb);
|
||||
|
||||
virtual bool copy(GraphicsStateGuardianBase *gsg, const DisplayRegion *dr);
|
||||
virtual bool copy(GraphicsStateGuardianBase *gsg, const DisplayRegion *dr,
|
||||
const RenderBuffer &rb);
|
||||
|
||||
INLINE void set_xsize(int size);
|
||||
INLINE void set_ysize(int size);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
// Filename: preparedGraphicsObjects.I
|
||||
// Created by: drose (23Feb04)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PANDA 3D SOFTWARE
|
||||
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
||||
//
|
||||
// All use of this software is subject to the terms of the Panda 3d
|
||||
// Software license. You should have received a copy of this license
|
||||
// along with this source code; you will also find a current copy of
|
||||
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
||||
//
|
||||
// To contact the maintainers of this program write to
|
||||
// panda3d-general@lists.sourceforge.net .
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
// Filename: preparedGraphicsObjects.cxx
|
||||
// Created by: drose (19Feb04)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PANDA 3D SOFTWARE
|
||||
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
||||
//
|
||||
// All use of this software is subject to the terms of the Panda 3d
|
||||
// Software license. You should have received a copy of this license
|
||||
// along with this source code; you will also find a current copy of
|
||||
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
||||
//
|
||||
// To contact the maintainers of this program write to
|
||||
// panda3d-general@lists.sourceforge.net .
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "preparedGraphicsObjects.h"
|
||||
#include "textureContext.h"
|
||||
#include "mutexHolder.h"
|
||||
|
||||
#ifndef CPPPARSER
|
||||
PStatCollector PreparedGraphicsObjects::_total_texusage_pcollector("Texture usage");
|
||||
#endif // CPPPARSER
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PreparedGraphicsObjects::Constructor
|
||||
// Access: Public
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
PreparedGraphicsObjects::
|
||||
PreparedGraphicsObjects() {
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PreparedGraphicsObjects::Destructor
|
||||
// Access: Public
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
PreparedGraphicsObjects::
|
||||
~PreparedGraphicsObjects() {
|
||||
// There may be texture objects that are still prepared when we
|
||||
// destruct. If this is so, then all of the GSG's that own them
|
||||
// have already destructed, so we can assume their resources were
|
||||
// internally cleaned up. Quietly erase these remaining textures.
|
||||
|
||||
MutexHolder holder(_lock);
|
||||
|
||||
Textures::iterator tci;
|
||||
for (tci = _prepared_textures.begin();
|
||||
tci != _prepared_textures.end();
|
||||
++tci) {
|
||||
TextureContext *tc = (*tci);
|
||||
_total_texusage_pcollector.sub_level(tc->estimate_texture_memory());
|
||||
tc->_texture->clear_prepared(this);
|
||||
}
|
||||
|
||||
_prepared_textures.clear();
|
||||
_released_textures.clear();
|
||||
_enqueued_textures.clear();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PreparedGraphicsObjects::enqueue_texture
|
||||
// Access: Public
|
||||
// Description: Indicates that a texture would like to be put on the
|
||||
// list to be prepared when the GSG is next ready to
|
||||
// do this (presumably at the next frame).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void PreparedGraphicsObjects::
|
||||
enqueue_texture(Texture *tex) {
|
||||
MutexHolder holder(_lock);
|
||||
|
||||
_enqueued_textures.insert(tex);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PreparedGraphicsObjects::dequeue_texture
|
||||
// Access: Public
|
||||
// Description: Removes a texture from the queued list of textures to
|
||||
// be prepared. Normally it is not necessary to call
|
||||
// this, unless you change your mind about preparing it
|
||||
// at the last minute, since the texture will
|
||||
// automatically be dequeued and prepared at the next
|
||||
// frame.
|
||||
//
|
||||
// The return value is true if the texture is
|
||||
// successfully dequeued, false if it had not been
|
||||
// queued.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool PreparedGraphicsObjects::
|
||||
dequeue_texture(Texture *tex) {
|
||||
MutexHolder holder(_lock);
|
||||
|
||||
EnqueuedTextures::iterator qi = _enqueued_textures.find(tex);
|
||||
if (qi != _enqueued_textures.end()) {
|
||||
_enqueued_textures.erase(qi);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PreparedGraphicsObjects::release_texture
|
||||
// Access: Public
|
||||
// Description: Indicates that a texture context, created by a
|
||||
// previous call to prepare_texture(), is no longer
|
||||
// needed. The driver resources will not be freed until
|
||||
// some GSG calls update(), indicating it is at a
|
||||
// stage where it is ready to release textures--this
|
||||
// prevents conflicts from threading or multiple GSG's
|
||||
// sharing textures (we have no way of knowing which
|
||||
// graphics context is currently active, or what state
|
||||
// it's in, at the time release_texture is called).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void PreparedGraphicsObjects::
|
||||
release_texture(TextureContext *tc) {
|
||||
MutexHolder holder(_lock);
|
||||
|
||||
tc->_texture->clear_prepared(this);
|
||||
_total_texusage_pcollector.sub_level(tc->estimate_texture_memory());
|
||||
|
||||
// We have to set the Texture pointer to NULL at this point, since
|
||||
// the Texture itself might destruct at any time after it has been
|
||||
// released.
|
||||
tc->_texture = (Texture *)NULL;
|
||||
|
||||
bool removed = (_prepared_textures.erase(tc) != 0);
|
||||
nassertv(removed);
|
||||
|
||||
_released_textures.insert(tc);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PreparedGraphicsObjects::release_all_textures
|
||||
// Access: Public
|
||||
// Description: Releases all textures at once. This will force them
|
||||
// to be reloaded into texture memory for all GSG's that
|
||||
// share this object. Returns the number of textures
|
||||
// released.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int PreparedGraphicsObjects::
|
||||
release_all_textures() {
|
||||
MutexHolder holder(_lock);
|
||||
|
||||
int num_textures = (int)_prepared_textures.size();
|
||||
|
||||
Textures::iterator tci;
|
||||
for (tci = _prepared_textures.begin();
|
||||
tci != _prepared_textures.end();
|
||||
++tci) {
|
||||
TextureContext *tc = (*tci);
|
||||
tc->_texture->clear_prepared(this);
|
||||
_total_texusage_pcollector.sub_level(tc->estimate_texture_memory());
|
||||
tc->_texture = (Texture *)NULL;
|
||||
|
||||
_released_textures.insert(tc);
|
||||
}
|
||||
|
||||
_prepared_textures.clear();
|
||||
|
||||
return num_textures;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PreparedGraphicsObjects::prepare_texture_now
|
||||
// Access: Public
|
||||
// Description: Immediately creates a new TextureContext for the
|
||||
// indicated texture and returns it. This assumes that
|
||||
// the GraphicsStateGuardian is the currently active
|
||||
// rendering context and that it is ready to accept new
|
||||
// textures. If this is not necessarily the case, you
|
||||
// should use enqueue_texture() instead.
|
||||
//
|
||||
// Normally, this function is not called directly. Call
|
||||
// Texture::prepare_now() instead.
|
||||
//
|
||||
// The TextureContext contains all of the pertinent
|
||||
// information needed by the GSG to keep track of this
|
||||
// one particular texture, and will exist as long as the
|
||||
// texture is ready to be rendered.
|
||||
//
|
||||
// When either the Texture or the
|
||||
// PreparedGraphicsObjects object destructs, the
|
||||
// TextureContext will be deleted.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
TextureContext *PreparedGraphicsObjects::
|
||||
prepare_texture_now(Texture *tex, GraphicsStateGuardianBase *gsg) {
|
||||
MutexHolder holder(_lock);
|
||||
|
||||
// Ask the GSG to create a brand new TextureContext. There might
|
||||
// be several GSG's sharing the same set of textures; if so, it
|
||||
// doesn't matter which of them creates the context (since they're
|
||||
// all shared anyway).
|
||||
TextureContext *tc = gsg->prepare_texture(tex);
|
||||
|
||||
bool prepared = _prepared_textures.insert(tc).second;
|
||||
nassertr(prepared, tc);
|
||||
|
||||
_total_texusage_pcollector.add_level(tc->estimate_texture_memory());
|
||||
return tc;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PreparedGraphicsObjects::update
|
||||
// Access: Public
|
||||
// Description: This is called by the GraphicsStateGuardian to
|
||||
// indicate that it is in a state to load or release
|
||||
// textures.
|
||||
//
|
||||
// Any texture contexts that were previously passed to
|
||||
// release_texture() are actually passed to the GSG to
|
||||
// be freed at this point; textures that were previously
|
||||
// passed to prepare_texture are actually loaded.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void PreparedGraphicsObjects::
|
||||
update(GraphicsStateGuardianBase *gsg) {
|
||||
MutexHolder holder(_lock);
|
||||
|
||||
// First, release all the textures awaiting release.
|
||||
Textures::iterator tci;
|
||||
for (tci = _released_textures.begin();
|
||||
tci != _released_textures.end();
|
||||
++tci) {
|
||||
TextureContext *tc = (*tci);
|
||||
gsg->release_texture(tc);
|
||||
}
|
||||
|
||||
_released_textures.clear();
|
||||
|
||||
// Now prepare all the textures awaiting preparation.
|
||||
EnqueuedTextures::iterator qi;
|
||||
for (qi = _enqueued_textures.begin();
|
||||
qi != _enqueued_textures.end();
|
||||
++qi) {
|
||||
Texture *tex = (*qi);
|
||||
tex->prepare_now(this, gsg);
|
||||
}
|
||||
|
||||
_enqueued_textures.clear();
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// Filename: preparedGraphicsObjects.h
|
||||
// Created by: drose (19Feb04)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PANDA 3D SOFTWARE
|
||||
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
||||
//
|
||||
// All use of this software is subject to the terms of the Panda 3d
|
||||
// Software license. You should have received a copy of this license
|
||||
// along with this source code; you will also find a current copy of
|
||||
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
||||
//
|
||||
// To contact the maintainers of this program write to
|
||||
// panda3d-general@lists.sourceforge.net .
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef PREPAREDGRAPHICSOBJECTS_H
|
||||
#define PREPAREDGRAPHICSOBJECTS_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "referenceCount.h"
|
||||
#include "texture.h"
|
||||
#include "pStatCollector.h"
|
||||
#include "pset.h"
|
||||
#include "pmutex.h"
|
||||
|
||||
class TextureContext;
|
||||
class GeomContext;
|
||||
class GeomNodeContext;
|
||||
class GraphicsStateGuardianBase;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Class : PreparedGraphicsObjects
|
||||
// Description : A table of objects that are saved within the graphics
|
||||
// context for reference by handle later. Generally,
|
||||
// this represents things like OpenGL texture objects or
|
||||
// display lists.
|
||||
//
|
||||
// This object simply records the pointers to the
|
||||
// context objects created by the individual GSG's;
|
||||
// these context objects will contain enough information
|
||||
// to reference or release the actual object stored
|
||||
// within the graphics context.
|
||||
//
|
||||
// These tables may potentially be shared between
|
||||
// related graphics contexts, hence their storage here
|
||||
// in a separate object rather than as a part of the
|
||||
// GraphicsStateGuardian.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
class EXPCL_PANDA PreparedGraphicsObjects : public ReferenceCount {
|
||||
public:
|
||||
PreparedGraphicsObjects();
|
||||
~PreparedGraphicsObjects();
|
||||
|
||||
void enqueue_texture(Texture *tex);
|
||||
bool dequeue_texture(Texture *tex);
|
||||
void release_texture(TextureContext *tc);
|
||||
int release_all_textures();
|
||||
|
||||
TextureContext *prepare_texture_now(Texture *tex, GraphicsStateGuardianBase *gsg);
|
||||
void update(GraphicsStateGuardianBase *gsg);
|
||||
|
||||
private:
|
||||
typedef pset<TextureContext *> Textures;
|
||||
typedef pset< PT(Texture) > EnqueuedTextures;
|
||||
|
||||
Mutex _lock;
|
||||
Textures _prepared_textures, _released_textures;
|
||||
EnqueuedTextures _enqueued_textures;
|
||||
|
||||
static PStatCollector _total_texusage_pcollector;
|
||||
|
||||
friend class GraphicsStateGuardian;
|
||||
};
|
||||
|
||||
#include "preparedGraphicsObjects.I"
|
||||
|
||||
#endif
|
||||
|
|
@ -150,14 +150,4 @@ get_keep_ram_image() const {
|
|||
return _keep_ram_image;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Texture::apply
|
||||
// Access: Public
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE void Texture::
|
||||
apply(GraphicsStateGuardianBase *gsg) {
|
||||
gsg->apply_texture(prepare(gsg));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include "bamReader.h"
|
||||
#include "bamWriter.h"
|
||||
#include "string_utils.h"
|
||||
#include "preparedGraphicsObjects.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
|
@ -126,7 +127,7 @@ consider_downgrade(PNMImage &pnmimage, int num_channels,
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Constructor
|
||||
// Function: Texture::Constructor
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -146,7 +147,7 @@ Texture() : ImageBuffer() {
|
|||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Constructor
|
||||
// Function: Texture::Constructor
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -166,17 +167,17 @@ Texture(int xsize, int ysize, int components, int component_width, PixelBuffer::
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Destructor
|
||||
// Function: Texture::Destructor
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
Texture::
|
||||
~Texture() {
|
||||
unprepare();
|
||||
release_all();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: read
|
||||
// Function: Texture::read
|
||||
// Access: Published
|
||||
// Description: Reads the texture from the indicated filename. If
|
||||
// num_channels is not 0, it specifies the number of
|
||||
|
|
@ -215,7 +216,7 @@ read(const Filename &fullpath, int primary_file_num_channels) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: read
|
||||
// Function: Texture::read
|
||||
// Access: Published
|
||||
// Description: Combine a 3-component image with a grayscale image
|
||||
// to get a 4-component image
|
||||
|
|
@ -309,7 +310,7 @@ read(const Filename &fullpath, const Filename &alpha_fullpath,
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: write
|
||||
// Function: Texture::write
|
||||
// Access: Published
|
||||
// Description: Writes the texture to the indicated filename.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -330,7 +331,7 @@ write(const Filename &name) const {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: set_wrapu
|
||||
// Function: Texture::set_wrapu
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -343,7 +344,7 @@ set_wrapu(Texture::WrapMode wrap) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: set_wrapv
|
||||
// Function: Texture::set_wrapv
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -356,7 +357,7 @@ set_wrapv(Texture::WrapMode wrap) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: set_minfilter
|
||||
// Function: Texture::set_minfilter
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -373,7 +374,7 @@ set_minfilter(Texture::FilterType filter) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: set_magfilter
|
||||
// Function: Texture::set_magfilter
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -386,7 +387,7 @@ set_magfilter(Texture::FilterType filter) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: set_anisotropic_degree
|
||||
// Function: Texture::set_anisotropic_degree
|
||||
// Access: Published
|
||||
// Description: Specifies the level of anisotropic filtering to apply
|
||||
// to the texture. Normally, this is 1, to indicate
|
||||
|
|
@ -403,7 +404,7 @@ set_anisotropic_degree(int anisotropic_degree) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: set_border_color
|
||||
// Function: Texture::set_border_color
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -413,7 +414,24 @@ set_border_color(const Colorf &color) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: load
|
||||
// Function: Texture::prepare
|
||||
// Access: Published
|
||||
// Description: Indicates that the texture should be enqueued to be
|
||||
// prepared in the indicated prepared_objects at the
|
||||
// beginning of the next frame. This will ensure the
|
||||
// texture is already loaded into texture memory if it
|
||||
// is expected to be rendered soon.
|
||||
//
|
||||
// Use this function instead of prepare_now() to preload
|
||||
// textures from a user interface standpoint.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void Texture::
|
||||
prepare(PreparedGraphicsObjects *prepared_objects) {
|
||||
prepared_objects->enqueue_texture(this);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Texture::load
|
||||
// Access: Public
|
||||
// Description: Creates the texture from the already-read PNMImage.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -428,7 +446,7 @@ load(const PNMImage &pnmimage) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: store
|
||||
// Function: Texture::store
|
||||
// Access: Public
|
||||
// Description: Saves the texture to the indicated PNMImage, but does
|
||||
// not write it to disk.
|
||||
|
|
@ -459,22 +477,32 @@ is_mipmap(FilterType type) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: prepare
|
||||
// Function: Texture::prepare_now
|
||||
// Access: Public
|
||||
// Description: Creates a context for the texture on the particular
|
||||
// GSG, if it does not already exist. Returns the new
|
||||
// (or old) TextureContext.
|
||||
// (or old) TextureContext. This assumes that the
|
||||
// GraphicsStateGuardian is the currently active
|
||||
// rendering context and that it is ready to accept new
|
||||
// textures. If this is not necessarily the case, you
|
||||
// should use prepare_later() instead.
|
||||
//
|
||||
// Normally, this is not called directly except by the
|
||||
// GraphicsStateGuardian; a texture does not need to be
|
||||
// explicitly prepared by the user before it may be
|
||||
// rendered.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
TextureContext *Texture::
|
||||
prepare(GraphicsStateGuardianBase *gsg) {
|
||||
prepare_now(PreparedGraphicsObjects *prepared_objects,
|
||||
GraphicsStateGuardianBase *gsg) {
|
||||
Contexts::const_iterator ci;
|
||||
ci = _contexts.find(gsg);
|
||||
ci = _contexts.find(prepared_objects);
|
||||
if (ci != _contexts.end()) {
|
||||
return (*ci).second;
|
||||
}
|
||||
|
||||
TextureContext *tc = gsg->prepare_texture(this);
|
||||
_contexts[gsg] = tc;
|
||||
TextureContext *tc = prepared_objects->prepare_texture_now(this, gsg);
|
||||
_contexts[prepared_objects] = tc;
|
||||
|
||||
// Now that we have a new TextureContext with zero dirty flags, our
|
||||
// intersection of all dirty flags must be zero. This doesn't mean
|
||||
|
|
@ -498,67 +526,54 @@ prepare(GraphicsStateGuardianBase *gsg) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Texture::unprepare
|
||||
// Function: Texture::release
|
||||
// Access: Public
|
||||
// Description: Frees the context allocated on all GSG's for which
|
||||
// the texture has been declared.
|
||||
// Description: Frees the texture context only on the indicated object,
|
||||
// if it exists there. Returns true if it was released,
|
||||
// false if it had not been prepared.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void Texture::
|
||||
unprepare() {
|
||||
// We have to traverse a copy of the _contexts list, because the GSG
|
||||
// will call clear_gsg() in response to each release_texture(), and
|
||||
// we don't want to be modifying the _contexts list while we're
|
||||
// traversing it.
|
||||
bool Texture::
|
||||
release(PreparedGraphicsObjects *prepared_objects) {
|
||||
Contexts::iterator ci;
|
||||
ci = _contexts.find(prepared_objects);
|
||||
if (ci != _contexts.end()) {
|
||||
TextureContext *tc = (*ci).second;
|
||||
prepared_objects->release_texture(tc);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Maybe it wasn't prepared yet, but it's about to be.
|
||||
return prepared_objects->dequeue_texture(this);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Texture::release_all
|
||||
// Access: Public
|
||||
// Description: Frees the context allocated on all objects for which
|
||||
// the texture has been declared. Returns the number of
|
||||
// contexts which have been freed.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int Texture::
|
||||
release_all() {
|
||||
// We have to traverse a copy of the _contexts list, because the
|
||||
// PreparedGraphicsObjects object will call clear_prepared() in response
|
||||
// to each release_texture(), and we don't want to be modifying the
|
||||
// _contexts list while we're traversing it.
|
||||
Contexts temp = _contexts;
|
||||
int num_freed = (int)_contexts.size();
|
||||
|
||||
Contexts::const_iterator ci;
|
||||
for (ci = temp.begin(); ci != temp.end(); ++ci) {
|
||||
GraphicsStateGuardianBase *gsg = (*ci).first;
|
||||
PreparedGraphicsObjects *prepared_objects = (*ci).first;
|
||||
TextureContext *tc = (*ci).second;
|
||||
gsg->release_texture(tc);
|
||||
prepared_objects->release_texture(tc);
|
||||
}
|
||||
|
||||
// Now that we've called release_texture() on every known GSG, the
|
||||
// _contexts list should have completely emptied itself.
|
||||
nassertv(_contexts.empty());
|
||||
}
|
||||
// Now that we've called release_texture() on every known context,
|
||||
// the _contexts list should have completely emptied itself.
|
||||
nassertr(_contexts.empty(), num_freed);
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Texture::unprepare
|
||||
// Access: Public
|
||||
// Description: Frees the texture context only on the indicated GSG,
|
||||
// if it exists there.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void Texture::
|
||||
unprepare(GraphicsStateGuardianBase *gsg) {
|
||||
Contexts::iterator ci;
|
||||
ci = _contexts.find(gsg);
|
||||
if (ci != _contexts.end()) {
|
||||
TextureContext *tc = (*ci).second;
|
||||
gsg->release_texture(tc);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Texture::clear_gsg
|
||||
// Access: Public
|
||||
// Description: Removes the indicated GSG from the Texture's known
|
||||
// GSG's, without actually releasing the texture on that
|
||||
// GSG. This is intended to be called only from
|
||||
// GSG::release_texture(); it should never be called by
|
||||
// user code.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void Texture::
|
||||
clear_gsg(GraphicsStateGuardianBase *gsg) {
|
||||
Contexts::iterator ci;
|
||||
ci = _contexts.find(gsg);
|
||||
if (ci != _contexts.end()) {
|
||||
_contexts.erase(ci);
|
||||
} else {
|
||||
// If this assertion fails, clear_gsg() was called on a GSG which
|
||||
// the texture didn't know about.
|
||||
nassertv(false);
|
||||
}
|
||||
return num_freed;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -606,17 +621,6 @@ get_ram_image() {
|
|||
}
|
||||
}
|
||||
|
||||
bool Texture::copy(GraphicsStateGuardianBase *gsg, const DisplayRegion *dr) {
|
||||
gsg->copy_texture(prepare(gsg), dr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Texture::copy(GraphicsStateGuardianBase *gsg, const DisplayRegion *dr,
|
||||
const RenderBuffer &rb) {
|
||||
gsg->copy_texture(prepare(gsg), dr, rb);
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Texture::mark_dirty
|
||||
// Access: Public
|
||||
|
|
@ -650,6 +654,28 @@ mark_dirty(int flags_to_set) {
|
|||
_all_dirty_flags |= flags_to_set;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Texture::clear_prepared
|
||||
// Access: Private
|
||||
// Description: Removes the indicated PreparedGraphicsObjects table
|
||||
// from the Texture's table, without actually releasing
|
||||
// the texture. This is intended to be called only from
|
||||
// PreparedGraphicsObjects::release_texture(); it should
|
||||
// never be called by user code.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void Texture::
|
||||
clear_prepared(PreparedGraphicsObjects *prepared_objects) {
|
||||
Contexts::iterator ci;
|
||||
ci = _contexts.find(prepared_objects);
|
||||
if (ci != _contexts.end()) {
|
||||
_contexts.erase(ci);
|
||||
} else {
|
||||
// If this assertion fails, clear_prepared() was given a
|
||||
// prepared_objects which the texture didn't know about.
|
||||
nassertv(false);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: Texture::string_wrap_mode
|
||||
// Access: Public
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
class PNMImage;
|
||||
class TextureContext;
|
||||
class FactoryParams;
|
||||
class PreparedGraphicsObjects;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Class : Texture
|
||||
|
|
@ -98,28 +99,24 @@ PUBLISHED:
|
|||
INLINE int get_anisotropic_degree() const;
|
||||
INLINE bool uses_mipmaps() const;
|
||||
|
||||
void prepare(PreparedGraphicsObjects *prepared_objects);
|
||||
|
||||
public:
|
||||
bool load(const PNMImage &pnmimage);
|
||||
bool store(PNMImage &pnmimage) const;
|
||||
|
||||
static bool is_mipmap(FilterType type);
|
||||
|
||||
TextureContext *prepare(GraphicsStateGuardianBase *gsg);
|
||||
void unprepare();
|
||||
void unprepare(GraphicsStateGuardianBase *gsg);
|
||||
void clear_gsg(GraphicsStateGuardianBase *gsg);
|
||||
TextureContext *prepare_now(PreparedGraphicsObjects *prepared_objects,
|
||||
GraphicsStateGuardianBase *gsg);
|
||||
bool release(PreparedGraphicsObjects *prepared_objects);
|
||||
int release_all();
|
||||
|
||||
INLINE bool has_ram_image() const;
|
||||
PixelBuffer *get_ram_image();
|
||||
INLINE void set_keep_ram_image(bool keep_ram_image);
|
||||
INLINE bool get_keep_ram_image() const;
|
||||
|
||||
INLINE void apply(GraphicsStateGuardianBase *gsg);
|
||||
|
||||
virtual bool copy(GraphicsStateGuardianBase *gsg, const DisplayRegion *dr);
|
||||
virtual bool copy(GraphicsStateGuardianBase *gsg, const DisplayRegion *dr,
|
||||
const RenderBuffer &rb);
|
||||
|
||||
// These bits are used as parameters to Texture::mark_dirty() and
|
||||
// also TextureContext::mark_dirty() (and related functions in
|
||||
// TextureContext).
|
||||
|
|
@ -136,6 +133,8 @@ public:
|
|||
static FilterType string_filter_type(const string &string);
|
||||
|
||||
private:
|
||||
void clear_prepared(PreparedGraphicsObjects *prepared_objects);
|
||||
|
||||
WrapMode _wrapu;
|
||||
WrapMode _wrapv;
|
||||
FilterType _minfilter;
|
||||
|
|
@ -144,11 +143,12 @@ private:
|
|||
bool _keep_ram_image;
|
||||
Colorf _border_color;
|
||||
|
||||
// A Texture keeps a list (actually, a map) of all the GSG's that it
|
||||
// has been prepared into. Each GSG conversely keeps a list (a set)
|
||||
// of all the Textures that have been prepared there. When either
|
||||
// destructs, it removes itself from the other's list.
|
||||
typedef pmap<GraphicsStateGuardianBase *, TextureContext *> Contexts;
|
||||
// A Texture keeps a list (actually, a map) of all the
|
||||
// PreparedGraphicsObjects tables that it has been prepared into.
|
||||
// Each PGO conversely keeps a list (a set) of all the Textures that
|
||||
// have been prepared there. When either destructs, it removes
|
||||
// itself from the other's list.
|
||||
typedef pmap<PreparedGraphicsObjects *, TextureContext *> Contexts;
|
||||
Contexts _contexts;
|
||||
|
||||
// This value represents the intersection of all the dirty flags of
|
||||
|
|
@ -201,6 +201,7 @@ private:
|
|||
static TypeHandle _type_handle;
|
||||
|
||||
friend class TextureContext;
|
||||
friend class PreparedGraphicsObjects;
|
||||
};
|
||||
|
||||
#include "texture.I"
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class GeomTristrip;
|
|||
class GeomTrifan;
|
||||
class GeomSphere;
|
||||
|
||||
class PreparedGraphicsObjects;
|
||||
class TextureContext;
|
||||
class Texture;
|
||||
class PixelBuffer;
|
||||
|
|
@ -111,6 +112,8 @@ public:
|
|||
// These are some general interface functions; they're defined here
|
||||
// mainly to make it easy to call these from code in some directory
|
||||
// that display depends on.
|
||||
virtual PreparedGraphicsObjects *get_prepared_objects()=0;
|
||||
|
||||
virtual TextureContext *prepare_texture(Texture *tex)=0;
|
||||
virtual void apply_texture(TextureContext *tc)=0;
|
||||
virtual void release_texture(TextureContext *tc)=0;
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
#include "globPattern.h"
|
||||
#include "config_gobj.h"
|
||||
#include "bamFile.h"
|
||||
#include "preparedGraphicsObjects.h"
|
||||
#include "dcast.h"
|
||||
|
||||
// stack seems to overflow on Intel C++ at 7000. If we need more than
|
||||
|
|
@ -3246,19 +3247,18 @@ verify_complete() const {
|
|||
// but this may take some of the overhead away from that
|
||||
// process.
|
||||
//
|
||||
// If force_retained_mode is true, retained mode is set
|
||||
// on the geometry encountered, regardless of the
|
||||
// setting of the retained-mode Config variable.
|
||||
// Otherwise, retained mode is set only if the
|
||||
// retained-mode Config variable is true.
|
||||
// In particular, this will ensure that textures within
|
||||
// the scene are loaded in texture memory, and display
|
||||
// lists are built up from static geometry.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void NodePath::
|
||||
prepare_scene(GraphicsStateGuardianBase *gsg, bool force_retained_mode) {
|
||||
prepare_scene(GraphicsStateGuardianBase *gsg) {
|
||||
nassertv_always(!is_empty());
|
||||
|
||||
PreparedGraphicsObjects *prepared_objects = gsg->get_prepared_objects();
|
||||
|
||||
CPT(RenderState) net_state = get_net_state();
|
||||
r_prepare_scene(node(), net_state, gsg,
|
||||
retained_mode || force_retained_mode);
|
||||
r_prepare_scene(node(), net_state, prepared_objects);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -3895,14 +3895,14 @@ r_find_all_textures(PandaNode *node, const RenderState *state,
|
|||
////////////////////////////////////////////////////////////////////
|
||||
void NodePath::
|
||||
r_prepare_scene(PandaNode *node, const RenderState *state,
|
||||
GraphicsStateGuardianBase *gsg, bool do_retained_mode) {
|
||||
PreparedGraphicsObjects *prepared_objects) {
|
||||
if (node->is_geom_node()) {
|
||||
GeomNode *gnode;
|
||||
DCAST_INTO_V(gnode, node);
|
||||
|
||||
/*
|
||||
Not implemented yet in pgraph. Maybe we don't need this anyway.
|
||||
if (do_retained_mode) {
|
||||
if (retained_mode) {
|
||||
gnode->prepare(gsg);
|
||||
}
|
||||
*/
|
||||
|
|
@ -3917,7 +3917,7 @@ r_prepare_scene(PandaNode *node, const RenderState *state,
|
|||
DCAST_INTO_V(ta, attrib);
|
||||
Texture *texture = ta->get_texture();
|
||||
if (texture != (Texture *)NULL) {
|
||||
texture->prepare(gsg);
|
||||
texture->prepare(prepared_objects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3927,6 +3927,6 @@ r_prepare_scene(PandaNode *node, const RenderState *state,
|
|||
for (int i = 0; i < num_children; i++) {
|
||||
PandaNode *child = node->get_child(i);
|
||||
CPT(RenderState) child_state = state->compose(child->get_state());
|
||||
r_prepare_scene(child, child_state, gsg, do_retained_mode);
|
||||
r_prepare_scene(child, child_state, prepared_objects);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class Texture;
|
|||
class Material;
|
||||
class Fog;
|
||||
class GlobPattern;
|
||||
class PreparedGraphicsObjects;
|
||||
|
||||
//
|
||||
// A NodePath is the fundamental unit of high-level interaction with
|
||||
|
|
@ -577,7 +578,7 @@ PUBLISHED:
|
|||
// Miscellaneous
|
||||
bool verify_complete() const;
|
||||
|
||||
void prepare_scene(GraphicsStateGuardianBase *gsg, bool force_retained_mode = false);
|
||||
void prepare_scene(GraphicsStateGuardianBase *gsg);
|
||||
|
||||
void show_bounds();
|
||||
void hide_bounds();
|
||||
|
|
@ -635,7 +636,7 @@ private:
|
|||
void r_find_all_textures(PandaNode *node, const RenderState *state,
|
||||
Textures &textures) const;
|
||||
void r_prepare_scene(PandaNode *node, const RenderState *state,
|
||||
GraphicsStateGuardianBase *gsg, bool do_retained_mode);
|
||||
PreparedGraphicsObjects *prepared_objects);
|
||||
|
||||
PT(NodePathComponent) _head;
|
||||
int _backup_key;
|
||||
|
|
|
|||
|
|
@ -227,7 +227,9 @@ make_image(Texture *texture, float radius) {
|
|||
pb->set_uchar_rgb_texel(C, size - tx2 - 1, size - ty2 - 1, size);
|
||||
}
|
||||
}
|
||||
texture->unprepare();
|
||||
|
||||
// Force the texture to be reloaded into texture memory.
|
||||
texture->mark_dirty(Texture::DF_image);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue