Added LLFace::canRenderAsMask()
Added LLVOVolume::isVisible() Avatar VBO now uses stream type under all scenarios. Todo: Perf test. Added other misc render-related tweaks that looked safe.
This commit is contained in:
@@ -693,6 +693,8 @@ void LLDrawable::updateDistance(LLCamera& camera, bool force_update)
|
|||||||
pos += volume->getRegion()->getOriginAgent();
|
pos += volume->getRegion()->getOriginAgent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isState(LLDrawable::HAS_ALPHA))
|
||||||
|
{
|
||||||
for (S32 i = 0; i < getNumFaces(); i++)
|
for (S32 i = 0; i < getNumFaces(); i++)
|
||||||
{
|
{
|
||||||
LLFace* facep = getFace(i);
|
LLFace* facep = getFace(i);
|
||||||
@@ -708,6 +710,7 @@ void LLDrawable::updateDistance(LLCamera& camera, bool force_update)
|
|||||||
facep->mDistance = v * camera.getAtAxis();
|
facep->mDistance = v * camera.getAtAxis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1239,7 +1242,9 @@ void LLSpatialBridge::setVisible(LLCamera& camera_in, std::vector<LLDrawable*>*
|
|||||||
(camera_in.AABBInFrustumNoFarClip(center, size) &&
|
(camera_in.AABBInFrustumNoFarClip(center, size) &&
|
||||||
AABBSphereIntersect(mExtents[0], mExtents[1], camera_in.getOrigin(), camera_in.mFrustumCornerDist)))
|
AABBSphereIntersect(mExtents[0], mExtents[1], camera_in.getOrigin(), camera_in.mFrustumCornerDist)))
|
||||||
{
|
{
|
||||||
if (!LLPipeline::sImpostorRender && LLPipeline::calcPixelArea(center, size, camera_in) < FORCE_INVISIBLE_AREA)
|
if (!LLPipeline::sImpostorRender &&
|
||||||
|
!LLPipeline::sShadowRender &&
|
||||||
|
LLPipeline::calcPixelArea(center, size, camera_in) < FORCE_INVISIBLE_AREA)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -267,7 +267,8 @@ public:
|
|||||||
BUILT = 0x08000000,
|
BUILT = 0x08000000,
|
||||||
FORCE_INVISIBLE = 0x10000000, // stay invis until CLEAR_INVISIBLE is set (set of orphaned)
|
FORCE_INVISIBLE = 0x10000000, // stay invis until CLEAR_INVISIBLE is set (set of orphaned)
|
||||||
CLEAR_INVISIBLE = 0x20000000, // clear FORCE_INVISIBLE next draw frame
|
CLEAR_INVISIBLE = 0x20000000, // clear FORCE_INVISIBLE next draw frame
|
||||||
REBUILD_SHADOW = 0x40000000
|
REBUILD_SHADOW = 0x40000000,
|
||||||
|
HAS_ALPHA = 0x80000000,
|
||||||
} EDrawableFlags;
|
} EDrawableFlags;
|
||||||
|
|
||||||
LLXformMatrix mXform;
|
LLXformMatrix mXform;
|
||||||
@@ -314,8 +315,20 @@ private:
|
|||||||
|
|
||||||
inline LLFace* LLDrawable::getFace(const S32 i) const
|
inline LLFace* LLDrawable::getFace(const S32 i) const
|
||||||
{
|
{
|
||||||
llassert((U32)i < mFaces.size());
|
//switch these asserts to llerrs -- davep
|
||||||
llassert(mFaces[i]);
|
//llassert((U32)i < mFaces.size());
|
||||||
|
//llassert(mFaces[i]);
|
||||||
|
|
||||||
|
if ((U32) i >= mFaces.size())
|
||||||
|
{
|
||||||
|
llerrs << "Invalid face index." << llendl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mFaces[i])
|
||||||
|
{
|
||||||
|
llerrs << "Null face found." << llendl;
|
||||||
|
}
|
||||||
|
|
||||||
return mFaces[i];
|
return mFaces[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,8 @@ void LLDrawPoolAvatar::prerender()
|
|||||||
|
|
||||||
if (sShaderLevel > 0)
|
if (sShaderLevel > 0)
|
||||||
{
|
{
|
||||||
sBufferUsage = GL_STATIC_DRAW_ARB;
|
sBufferUsage = GL_DYNAMIC_DRAW_ARB;
|
||||||
|
//sBufferUsage = GL_STATIC_DRAW_ARB;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -861,8 +862,8 @@ LLColor3 LLDrawPoolAvatar::getDebugColor() const
|
|||||||
|
|
||||||
LLVertexBufferAvatar::LLVertexBufferAvatar()
|
LLVertexBufferAvatar::LLVertexBufferAvatar()
|
||||||
: LLVertexBuffer(sDataMask,
|
: LLVertexBuffer(sDataMask,
|
||||||
LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) > 0 ?
|
//LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) > 0 ?
|
||||||
GL_DYNAMIC_DRAW_ARB :
|
//GL_DYNAMIC_DRAW_ARB :
|
||||||
GL_STREAM_DRAW_ARB)
|
GL_STREAM_DRAW_ARB)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ void LLDrawPoolGlow::render(S32 pass)
|
|||||||
LLFastTimer t(LLFastTimer::FTM_RENDER_GLOW);
|
LLFastTimer t(LLFastTimer::FTM_RENDER_GLOW);
|
||||||
LLGLEnable blend(GL_BLEND);
|
LLGLEnable blend(GL_BLEND);
|
||||||
LLGLDisable test(GL_ALPHA_TEST);
|
LLGLDisable test(GL_ALPHA_TEST);
|
||||||
|
gGL.flush();
|
||||||
|
/// Get rid of z-fighting with non-glow pass.
|
||||||
|
LLGLEnable polyOffset(GL_POLYGON_OFFSET_FILL);
|
||||||
|
glPolygonOffset(-1.0f, -1.0f);
|
||||||
gGL.setSceneBlendType(LLRender::BT_ADD);
|
gGL.setSceneBlendType(LLRender::BT_ADD);
|
||||||
|
|
||||||
U32 shader_level = LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT);
|
U32 shader_level = LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT);
|
||||||
|
|||||||
@@ -896,6 +896,25 @@ void LLFace::updateRebuildFlags()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool LLFace::canRenderAsMask()
|
||||||
|
{
|
||||||
|
const LLTextureEntry* te = getTextureEntry();
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
LLPipeline::sFastAlpha
|
||||||
|
/*(LLPipeline::sRenderDeferred && LLPipeline::sAutoMaskAlphaDeferred) ||
|
||||||
|
(!LLPipeline::sRenderDeferred && LLPipeline::sAutoMaskAlphaNonDeferred)*/
|
||||||
|
) // do we want masks at all?
|
||||||
|
&&
|
||||||
|
(te->getColor().mV[3] == 1.0f) && // can't treat as mask if we have face alpha
|
||||||
|
!(LLPipeline::sRenderDeferred && te->getFullbright()) && // hack: alpha masking renders fullbright faces invisible in deferred rendering mode, need to figure out why - for now, avoid
|
||||||
|
(te->getGlow() == 0.f) && // glowing masks are hard to implement - don't mask
|
||||||
|
|
||||||
|
getTexture()->getIsAlphaMask() // texture actually qualifies for masking (lazily recalculated but expensive)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
BOOL LLFace::getGeometryVolume(const LLVolume& volume,
|
BOOL LLFace::getGeometryVolume(const LLVolume& volume,
|
||||||
const S32 &f,
|
const S32 &f,
|
||||||
const LLMatrix4& mat_vert, const LLMatrix3& mat_normal,
|
const LLMatrix4& mat_vert, const LLMatrix3& mat_normal,
|
||||||
@@ -1049,17 +1068,20 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume,
|
|||||||
|
|
||||||
if (rebuild_color)
|
if (rebuild_color)
|
||||||
{
|
{
|
||||||
GLfloat alpha[4] =
|
if (tep)
|
||||||
{
|
{
|
||||||
0.00f,
|
GLfloat alpha[4] =
|
||||||
0.25f,
|
{
|
||||||
0.5f,
|
0.00f,
|
||||||
0.75f
|
0.25f,
|
||||||
};
|
0.5f,
|
||||||
|
0.75f
|
||||||
|
};
|
||||||
|
|
||||||
if (getPoolType() != LLDrawPool::POOL_ALPHA && (LLPipeline::sRenderDeferred || (LLPipeline::sRenderBump && tep->getShiny())))
|
if (getPoolType() != LLDrawPool::POOL_ALPHA && (LLPipeline::sRenderDeferred || (LLPipeline::sRenderBump && tep->getShiny())))
|
||||||
{
|
{
|
||||||
color.mV[3] = U8 (alpha[tep->getShiny()] * 255);
|
color.mV[3] = U8 (alpha[tep->getShiny()] * 255);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ public:
|
|||||||
|
|
||||||
//for volumes
|
//for volumes
|
||||||
void updateRebuildFlags();
|
void updateRebuildFlags();
|
||||||
|
bool canRenderAsMask(); // logic helper
|
||||||
BOOL getGeometryVolume(const LLVolume& volume,
|
BOOL getGeometryVolume(const LLVolume& volume,
|
||||||
const S32 &f,
|
const S32 &f,
|
||||||
const LLMatrix4& mat_vert, const LLMatrix3& mat_normal,
|
const LLMatrix4& mat_vert, const LLMatrix3& mat_normal,
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ void LLVOVolume::initClass()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void LLVOVolume::cleanupClass()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
U32 LLVOVolume::processUpdateMessage(LLMessageSystem *mesgsys,
|
U32 LLVOVolume::processUpdateMessage(LLMessageSystem *mesgsys,
|
||||||
void **user_data,
|
void **user_data,
|
||||||
@@ -415,6 +419,26 @@ void LLVOVolume::updateTextures()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOL LLVOVolume::isVisible() const
|
||||||
|
{
|
||||||
|
if(mDrawable.notNull() && mDrawable->isVisible())
|
||||||
|
{
|
||||||
|
return TRUE ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isAttachment())
|
||||||
|
{
|
||||||
|
LLViewerObject* objp = (LLViewerObject*)getParent() ;
|
||||||
|
while(objp && !objp->isAvatar())
|
||||||
|
{
|
||||||
|
objp = (LLViewerObject*)objp->getParent() ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return objp && objp->mDrawable.notNull() && objp->mDrawable->isVisible() ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE ;
|
||||||
|
}
|
||||||
void LLVOVolume::updateTextureVirtualSize()
|
void LLVOVolume::updateTextureVirtualSize()
|
||||||
{
|
{
|
||||||
// Update the pixel area of all faces
|
// Update the pixel area of all faces
|
||||||
@@ -938,7 +962,7 @@ BOOL LLVOVolume::setParent(LLViewerObject* parent)
|
|||||||
if (parent != getParent())
|
if (parent != getParent())
|
||||||
{
|
{
|
||||||
ret = LLViewerObject::setParent(parent);
|
ret = LLViewerObject::setParent(parent);
|
||||||
if (mDrawable)
|
if (ret && mDrawable)
|
||||||
{
|
{
|
||||||
gPipeline.markMoved(mDrawable);
|
gPipeline.markMoved(mDrawable);
|
||||||
gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME, TRUE);
|
gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME, TRUE);
|
||||||
@@ -1778,14 +1802,7 @@ void LLVOVolume::updateRadius()
|
|||||||
|
|
||||||
BOOL LLVOVolume::isAttachment() const
|
BOOL LLVOVolume::isAttachment() const
|
||||||
{
|
{
|
||||||
if (mState == 0)
|
return mState != 0 ;
|
||||||
{
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL LLVOVolume::isHUDAttachment() const
|
BOOL LLVOVolume::isHUDAttachment() const
|
||||||
@@ -1863,9 +1880,7 @@ F32 LLVOVolume::getBinRadius()
|
|||||||
{
|
{
|
||||||
LLFace* face = mDrawable->getFace(i);
|
LLFace* face = mDrawable->getFace(i);
|
||||||
if (face->getPoolType() == LLDrawPool::POOL_ALPHA &&
|
if (face->getPoolType() == LLDrawPool::POOL_ALPHA &&
|
||||||
(!LLPipeline::sFastAlpha ||
|
!face->canRenderAsMask())
|
||||||
face->getFaceColor().mV[3] != 1.f ||
|
|
||||||
!face->getTexture()->getIsAlphaMask()))
|
|
||||||
{
|
{
|
||||||
alpha_wrap = TRUE;
|
alpha_wrap = TRUE;
|
||||||
break;
|
break;
|
||||||
@@ -2059,7 +2074,7 @@ BOOL LLVOVolume::lineSegmentIntersect(const LLVector3& start, const LLVector3& e
|
|||||||
{
|
{
|
||||||
LLFace* face = mDrawable->getFace(face_hit);
|
LLFace* face = mDrawable->getFace(face_hit);
|
||||||
|
|
||||||
if (pick_transparent || !face->getTexture() || face->getTexture()->getMask(face->surfaceToTexture(tc, p, n)))
|
if (pick_transparent || !face->getTexture() || !face->getTexture()->getHasGLTexture() || face->getTexture()->getMask(face->surfaceToTexture(tc, p, n)))
|
||||||
{
|
{
|
||||||
v_end = p;
|
v_end = p;
|
||||||
if (face_hitp != NULL)
|
if (face_hitp != NULL)
|
||||||
@@ -2141,7 +2156,7 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep,
|
|||||||
const LLViewerObject* pObj = facep->getViewerObject();
|
const LLViewerObject* pObj = facep->getViewerObject();
|
||||||
if ( (pObj->isSelected() && gHideSelectedObjects) &&
|
if ( (pObj->isSelected() && gHideSelectedObjects) &&
|
||||||
((!rlv_handler_t::isEnabled()) || (!pObj->isHUDAttachment()) || (!gRlvAttachmentLocks.isLockedAttachment(pObj->getRootEdit()))) )
|
((!rlv_handler_t::isEnabled()) || (!pObj->isHUDAttachment()) || (!gRlvAttachmentLocks.isLockedAttachment(pObj->getRootEdit()))) )
|
||||||
// [/RVLa:KB]
|
// [/RLVa:KB]
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2151,9 +2166,9 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep,
|
|||||||
|
|
||||||
S32 idx = draw_vec.size()-1;
|
S32 idx = draw_vec.size()-1;
|
||||||
|
|
||||||
|
BOOL fullbright = (type == LLRenderPass::PASS_FULLBRIGHT) ||
|
||||||
BOOL fullbright = (type == LLRenderPass::PASS_FULLBRIGHT ||
|
(type == LLRenderPass::PASS_INVISIBLE) ||
|
||||||
type == LLRenderPass::PASS_ALPHA) ? facep->isState(LLFace::FULLBRIGHT) : FALSE;
|
(type == LLRenderPass::PASS_ALPHA && facep->isState(LLFace::FULLBRIGHT));
|
||||||
|
|
||||||
const LLMatrix4* tex_mat = NULL;
|
const LLMatrix4* tex_mat = NULL;
|
||||||
if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE)
|
if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE)
|
||||||
@@ -2309,6 +2324,8 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
|
|||||||
vobj->updateTextureVirtualSize();
|
vobj->updateTextureVirtualSize();
|
||||||
vobj->preRebuild();
|
vobj->preRebuild();
|
||||||
|
|
||||||
|
drawablep->clearState(LLDrawable::HAS_ALPHA);
|
||||||
|
|
||||||
//for each face
|
//for each face
|
||||||
for (S32 i = 0; i < drawablep->getNumFaces(); i++)
|
for (S32 i = 0; i < drawablep->getNumFaces(); i++)
|
||||||
{
|
{
|
||||||
@@ -2316,7 +2333,7 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
|
|||||||
drawablep->updateFaceSize(i);
|
drawablep->updateFaceSize(i);
|
||||||
LLFace* facep = drawablep->getFace(i);
|
LLFace* facep = drawablep->getFace(i);
|
||||||
|
|
||||||
if (cur_total > max_total)
|
if (cur_total > max_total || facep->getIndicesCount() <= 0 || facep->getGeomCount() <= 0)
|
||||||
{
|
{
|
||||||
facep->mVertexBuffer = NULL;
|
facep->mVertexBuffer = NULL;
|
||||||
facep->mLastVertexBuffer = NULL;
|
facep->mLastVertexBuffer = NULL;
|
||||||
@@ -2369,14 +2386,13 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
|
|||||||
|
|
||||||
if (type == LLDrawPool::POOL_ALPHA)
|
if (type == LLDrawPool::POOL_ALPHA)
|
||||||
{
|
{
|
||||||
if (LLPipeline::sFastAlpha &&
|
if (facep->canRenderAsMask())
|
||||||
(te->getColor().mV[VW] == 1.0f) &&
|
|
||||||
facep->getTexture()->getIsAlphaMask())
|
|
||||||
{ //can be treated as alpha mask
|
{ //can be treated as alpha mask
|
||||||
simple_faces.push_back(facep);
|
simple_faces.push_back(facep);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
drawablep->setState(LLDrawable::HAS_ALPHA);
|
||||||
alpha_faces.push_back(facep);
|
alpha_faces.push_back(facep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2474,8 +2490,7 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
|
|||||||
void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group)
|
void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group)
|
||||||
{
|
{
|
||||||
static int warningsCount = 20;
|
static int warningsCount = 20;
|
||||||
|
if (group && group->isState(LLSpatialGroup::MESH_DIRTY) && !group->isState(LLSpatialGroup::GEOM_DIRTY))
|
||||||
if (group->isState(LLSpatialGroup::MESH_DIRTY))
|
|
||||||
{
|
{
|
||||||
S32 num_mapped_vertex_buffer = LLVertexBuffer::sMappedCount ;
|
S32 num_mapped_vertex_buffer = LLVertexBuffer::sMappedCount ;
|
||||||
|
|
||||||
@@ -2703,6 +2718,11 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, std::
|
|||||||
|
|
||||||
BOOL force_simple = facep->mPixelArea < FORCE_SIMPLE_RENDER_AREA;
|
BOOL force_simple = facep->mPixelArea < FORCE_SIMPLE_RENDER_AREA;
|
||||||
BOOL fullbright = facep->isState(LLFace::FULLBRIGHT);
|
BOOL fullbright = facep->isState(LLFace::FULLBRIGHT);
|
||||||
|
if ((mask & LLVertexBuffer::MAP_NORMAL) == 0)
|
||||||
|
{ //paranoia check to make sure GL doesn't try to read non-existant normals
|
||||||
|
fullbright = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
const LLTextureEntry* te = facep->getTextureEntry();
|
const LLTextureEntry* te = facep->getTextureEntry();
|
||||||
|
|
||||||
BOOL is_alpha = facep->getPoolType() == LLDrawPool::POOL_ALPHA ? TRUE : FALSE;
|
BOOL is_alpha = facep->getPoolType() == LLDrawPool::POOL_ALPHA ? TRUE : FALSE;
|
||||||
@@ -2710,9 +2730,7 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, std::
|
|||||||
if (is_alpha)
|
if (is_alpha)
|
||||||
{
|
{
|
||||||
// can we safely treat this as an alpha mask?
|
// can we safely treat this as an alpha mask?
|
||||||
if (LLPipeline::sFastAlpha &&
|
if (facep->canRenderAsMask())
|
||||||
(te->getColor().mV[VW] == 1.0f) &&
|
|
||||||
facep->getTexture()->getIsAlphaMask())
|
|
||||||
{
|
{
|
||||||
if (te->getFullbright())
|
if (te->getFullbright())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ protected:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
static void initClass();
|
static void initClass();
|
||||||
|
static void cleanupClass();
|
||||||
static void preUpdateGeom();
|
static void preUpdateGeom();
|
||||||
|
|
||||||
enum
|
enum
|
||||||
@@ -99,6 +100,7 @@ public:
|
|||||||
void animateTextures();
|
void animateTextures();
|
||||||
/*virtual*/ BOOL idleUpdate(LLAgent &agent, LLWorld &world, const F64 &time);
|
/*virtual*/ BOOL idleUpdate(LLAgent &agent, LLWorld &world, const F64 &time);
|
||||||
|
|
||||||
|
BOOL isVisible() const ;
|
||||||
/*virtual*/ BOOL isActive() const;
|
/*virtual*/ BOOL isActive() const;
|
||||||
/*virtual*/ BOOL isAttachment() const;
|
/*virtual*/ BOOL isAttachment() const;
|
||||||
/*virtual*/ BOOL isRootEdit() const; // overridden for sake of attachments treating themselves as a root object
|
/*virtual*/ BOOL isRootEdit() const; // overridden for sake of attachments treating themselves as a root object
|
||||||
|
|||||||
Reference in New Issue
Block a user