380 lines
10 KiB
C++
380 lines
10 KiB
C++
/**
|
|
* @file llreflectionmap.cpp
|
|
* @brief LLReflectionMap class implementation
|
|
*
|
|
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2022, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "llreflectionmap.h"
|
|
#include "pipeline.h"
|
|
#include "llviewerwindow.h"
|
|
#include "llviewerregion.h"
|
|
#include "llworld.h"
|
|
#include "llshadermgr.h"
|
|
|
|
extern F32SecondsImplicit gFrameTimeSeconds;
|
|
|
|
extern U32 get_box_fan_indices(LLCamera* camera, const LLVector4a& center);
|
|
|
|
LLReflectionMap::LLReflectionMap()
|
|
{
|
|
}
|
|
|
|
LLReflectionMap::~LLReflectionMap()
|
|
{
|
|
if (mOcclusionQuery)
|
|
{
|
|
glDeleteQueries(1, &mOcclusionQuery);
|
|
}
|
|
}
|
|
|
|
void LLReflectionMap::update(U32 resolution, U32 face)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
|
|
mLastUpdateTime = gFrameTimeSeconds;
|
|
llassert(mCubeArray.notNull());
|
|
llassert(mCubeIndex != -1);
|
|
//llassert(LLPipeline::sRenderDeferred);
|
|
|
|
// make sure we don't walk off the edge of the render target
|
|
while (resolution > gPipeline.mRT->deferredScreen.getWidth() ||
|
|
resolution > gPipeline.mRT->deferredScreen.getHeight())
|
|
{
|
|
resolution /= 2;
|
|
}
|
|
gViewerWindow->cubeSnapshot(LLVector3(mOrigin), mCubeArray, mCubeIndex, face, getNearClip(), getIsDynamic());
|
|
}
|
|
|
|
void LLReflectionMap::autoAdjustOrigin()
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
|
|
|
|
|
|
if (mGroup && !mComplete && !mGroup->hasState(LLViewerOctreeGroup::DEAD))
|
|
{
|
|
const LLVector4a* bounds = mGroup->getBounds();
|
|
auto* node = mGroup->getOctreeNode();
|
|
LLSpatialPartition* part = mGroup->getSpatialPartition();
|
|
|
|
if (part && part->mPartitionType == LLViewerRegion::PARTITION_VOLUME)
|
|
{
|
|
mPriority = 0;
|
|
// cast a ray towards 8 corners of bounding box
|
|
// nudge origin towards center of empty space
|
|
|
|
if (!node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
mOrigin = bounds[0];
|
|
|
|
LLVector4a size = bounds[1];
|
|
|
|
LLVector4a corners[] =
|
|
{
|
|
{ 1, 1, 1 },
|
|
{ -1, 1, 1 },
|
|
{ 1, -1, 1 },
|
|
{ -1, -1, 1 },
|
|
{ 1, 1, -1 },
|
|
{ -1, 1, -1 },
|
|
{ 1, -1, -1 },
|
|
{ -1, -1, -1 }
|
|
};
|
|
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
corners[i].mul(size);
|
|
corners[i].add(bounds[0]);
|
|
}
|
|
|
|
LLVector4a extents[2];
|
|
extents[0].setAdd(bounds[0], bounds[1]);
|
|
extents[1].setSub(bounds[0], bounds[1]);
|
|
|
|
bool hit = false;
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
int face = -1;
|
|
LLVector4a intersection;
|
|
LLDrawable* drawable = mGroup->lineSegmentIntersect(bounds[0], corners[i], false, false, true, true, &face, &intersection);
|
|
if (drawable != nullptr)
|
|
{
|
|
hit = true;
|
|
update_min_max(extents[0], extents[1], intersection);
|
|
}
|
|
else
|
|
{
|
|
update_min_max(extents[0], extents[1], corners[i]);
|
|
}
|
|
}
|
|
|
|
if (hit)
|
|
{
|
|
mOrigin.setAdd(extents[0], extents[1]);
|
|
mOrigin.mul(0.5f);
|
|
}
|
|
|
|
// make sure origin isn't under ground
|
|
F32* fp = mOrigin.getF32ptr();
|
|
LLVector3 origin(fp);
|
|
F32 height = LLWorld::instance().resolveLandHeightAgent(origin) + 2.f;
|
|
fp[2] = llmax(fp[2], height);
|
|
|
|
// make sure radius encompasses all objects
|
|
LLSimdScalar r2 = 0.0;
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
LLVector4a v;
|
|
v.setSub(corners[i], mOrigin);
|
|
|
|
LLSimdScalar d = v.dot3(v);
|
|
|
|
if (d > r2)
|
|
{
|
|
r2 = d;
|
|
}
|
|
}
|
|
|
|
mRadius = llmax(sqrtf(r2.getF32()), 8.f);
|
|
|
|
// make sure near clip doesn't poke through ground
|
|
fp[2] = llmax(fp[2], height+mRadius*0.5f);
|
|
|
|
}
|
|
}
|
|
else if (mViewerObject)
|
|
{
|
|
mPriority = 1;
|
|
mOrigin.load3(mViewerObject->getPositionAgent().mV);
|
|
|
|
if (mViewerObject->getVolume() && ((LLVOVolume*)mViewerObject)->getReflectionProbeIsBox())
|
|
{
|
|
LLVector3 s = mViewerObject->getScale().scaledVec(LLVector3(0.5f, 0.5f, 0.5f));
|
|
mRadius = s.magVec();
|
|
}
|
|
else
|
|
{
|
|
mRadius = mViewerObject->getScale().mV[0] * 0.5f;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LLReflectionMap::intersects(LLReflectionMap* other)
|
|
{
|
|
LLVector4a delta;
|
|
delta.setSub(other->mOrigin, mOrigin);
|
|
|
|
F32 dist = delta.dot3(delta).getF32();
|
|
|
|
F32 r2 = mRadius + other->mRadius;
|
|
|
|
r2 *= r2;
|
|
|
|
return dist < r2;
|
|
}
|
|
|
|
extern LLControlGroup gSavedSettings;
|
|
|
|
F32 LLReflectionMap::getAmbiance()
|
|
{
|
|
F32 ret = 0.f;
|
|
if (mViewerObject && mViewerObject->getVolume())
|
|
{
|
|
ret = ((LLVOVolume*)mViewerObject)->getReflectionProbeAmbiance();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
F32 LLReflectionMap::getNearClip()
|
|
{
|
|
const F32 MINIMUM_NEAR_CLIP = 0.1f;
|
|
|
|
F32 ret = 0.f;
|
|
|
|
if (mViewerObject && mViewerObject->getVolume())
|
|
{
|
|
ret = ((LLVOVolume*)mViewerObject)->getReflectionProbeNearClip();
|
|
}
|
|
else if (mGroup)
|
|
{
|
|
ret = mRadius * 0.5f; // default to half radius for automatic object probes
|
|
}
|
|
else
|
|
{
|
|
ret = 1.f; // default to 1m for automatic terrain probes
|
|
}
|
|
|
|
return llmax(ret, MINIMUM_NEAR_CLIP);
|
|
}
|
|
|
|
bool LLReflectionMap::getIsDynamic()
|
|
{
|
|
if (gSavedSettings.getS32("RenderReflectionProbeDetail") > (S32) LLReflectionMapManager::DetailLevel::STATIC_ONLY &&
|
|
mViewerObject &&
|
|
mViewerObject->getVolume())
|
|
{
|
|
return ((LLVOVolume*)mViewerObject)->getReflectionProbeIsDynamic();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LLReflectionMap::getBox(LLMatrix4& box)
|
|
{
|
|
if (mViewerObject)
|
|
{
|
|
LLVolume* volume = mViewerObject->getVolume();
|
|
if (volume)
|
|
{
|
|
LLVOVolume* vobjp = (LLVOVolume*)mViewerObject;
|
|
|
|
if (vobjp->getReflectionProbeIsBox())
|
|
{
|
|
glh::matrix4f mv(gGLModelView);
|
|
glh::matrix4f scale;
|
|
LLVector3 s = vobjp->getScale().scaledVec(LLVector3(0.5f, 0.5f, 0.5f));
|
|
mRadius = s.magVec();
|
|
scale.set_scale(glh::vec3f(s.mV));
|
|
if (vobjp->mDrawable != nullptr)
|
|
{
|
|
// object to agent space (no scale)
|
|
glh::matrix4f rm((F32*)vobjp->mDrawable->getWorldMatrix().mMatrix);
|
|
|
|
// construct object to camera space (with scale)
|
|
mv = mv * rm * scale;
|
|
|
|
// inverse is camera space to object unit cube
|
|
mv = mv.inverse();
|
|
|
|
box = LLMatrix4(mv.m);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LLReflectionMap::isActive()
|
|
{
|
|
return mCubeIndex != -1;
|
|
}
|
|
|
|
bool LLReflectionMap::isRelevant()
|
|
{
|
|
static LLCachedControl<S32> RenderReflectionProbeLevel(gSavedSettings, "RenderReflectionProbeLevel", 3);
|
|
|
|
if (mViewerObject && RenderReflectionProbeLevel > 0)
|
|
{ // not an automatic probe
|
|
return true;
|
|
}
|
|
|
|
if (RenderReflectionProbeLevel == 3)
|
|
{ // all automatics are relevant
|
|
return true;
|
|
}
|
|
|
|
if (RenderReflectionProbeLevel == 2)
|
|
{ // terrain and water only, ignore probes that have a group
|
|
return !mGroup;
|
|
}
|
|
|
|
// no automatic probes, yes manual probes
|
|
return mViewerObject != nullptr;
|
|
}
|
|
|
|
|
|
void LLReflectionMap::doOcclusion(const LLVector4a& eye)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE;
|
|
if (LLGLSLShader::sProfileEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if 1
|
|
// super sloppy, but we're doing an occlusion cull against a bounding cube of
|
|
// a bounding sphere, pad radius so we assume if the eye is within
|
|
// the bounding sphere of the bounding cube, the node is not culled
|
|
F32 dist = mRadius * F_SQRT3 + 1.f;
|
|
|
|
LLVector4a o;
|
|
o.setSub(mOrigin, eye);
|
|
|
|
bool do_query = false;
|
|
|
|
if (o.getLength3().getF32() < dist)
|
|
{ // eye is inside radius, don't attempt to occlude
|
|
mOccluded = false;
|
|
return;
|
|
}
|
|
|
|
if (mOcclusionQuery == 0)
|
|
{ // no query was previously issued, allocate one and issue
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("rmdo - glGenQueries");
|
|
glGenQueries(1, &mOcclusionQuery);
|
|
do_query = true;
|
|
}
|
|
else
|
|
{ // query was previously issued, check it and only issue a new query
|
|
// if previous query is available
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("rmdo - glGetQueryObject");
|
|
GLuint result = 0;
|
|
glGetQueryObjectuiv(mOcclusionQuery, GL_QUERY_RESULT_AVAILABLE, &result);
|
|
|
|
if (result > 0)
|
|
{
|
|
do_query = true;
|
|
glGetQueryObjectuiv(mOcclusionQuery, GL_QUERY_RESULT, &result);
|
|
mOccluded = result == 0;
|
|
mOcclusionPendingFrames = 0;
|
|
}
|
|
else
|
|
{
|
|
mOcclusionPendingFrames++;
|
|
}
|
|
}
|
|
|
|
if (do_query)
|
|
{
|
|
LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("rmdo - push query");
|
|
glBeginQuery(GL_ANY_SAMPLES_PASSED, mOcclusionQuery);
|
|
|
|
LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
|
|
|
|
shader->uniform3fv(LLShaderMgr::BOX_CENTER, 1, mOrigin.getF32ptr());
|
|
shader->uniform3f(LLShaderMgr::BOX_SIZE, mRadius, mRadius, mRadius);
|
|
|
|
gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(LLViewerCamera::getInstance(), mOrigin));
|
|
|
|
glEndQuery(GL_ANY_SAMPLES_PASSED);
|
|
}
|
|
#endif
|
|
}
|