412 lines
12 KiB
C++
412 lines
12 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, bool force_dynamic, F32 near_clip, bool useClipPlane, LLPlane clipPlane)
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
|
|
if (!mCubeArray.notNull())
|
|
return;
|
|
|
|
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;
|
|
}
|
|
|
|
F32 clip = (near_clip > 0) ? near_clip : getNearClip();
|
|
bool dynamic = force_dynamic || getIsDynamic();
|
|
|
|
gViewerWindow->cubeSnapshot(LLVector3(mOrigin), mCubeArray, mCubeIndex, face, clip, dynamic, useClipPlane, clipPlane);
|
|
}
|
|
|
|
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 && !mViewerObject->isDead())
|
|
{
|
|
mPriority = 1;
|
|
mOrigin.load3(mViewerObject->getPositionAgent().mV);
|
|
|
|
if (mViewerObject->getVolume() && ((LLVOVolume*)mViewerObject.get())->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) const
|
|
{
|
|
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() const
|
|
{
|
|
F32 ret = 0.f;
|
|
if (mViewerObject && mViewerObject->getVolumeConst())
|
|
{
|
|
ret = mViewerObject->getReflectionProbeAmbiance();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
F32 LLReflectionMap::getNearClip() const
|
|
{
|
|
const F32 MINIMUM_NEAR_CLIP = 0.1f;
|
|
|
|
F32 ret = 0.f;
|
|
|
|
if (mViewerObject && mViewerObject->getVolumeConst())
|
|
{
|
|
ret = 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() const
|
|
{
|
|
static LLCachedControl<S32> detail(gSavedSettings, "RenderReflectionProbeDetail", 1);
|
|
if (detail() > (S32)LLReflectionMapManager::DetailLevel::STATIC_ONLY &&
|
|
mViewerObject &&
|
|
!mViewerObject->isDead() &&
|
|
mViewerObject->getVolumeConst())
|
|
{
|
|
return mViewerObject->getReflectionProbeIsDynamic();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LLReflectionMap::getBox(LLMatrix4& box)
|
|
{
|
|
if (mViewerObject)
|
|
{
|
|
LLVolume* volume = mViewerObject->getVolume();
|
|
if (volume && mViewerObject->getReflectionProbeIsBox())
|
|
{
|
|
glm::mat4 mv(get_current_modelview());
|
|
LLVector3 s = mViewerObject->getScale().scaledVec(LLVector3(0.5f, 0.5f, 0.5f));
|
|
mRadius = s.magVec();
|
|
glm::mat4 scale = glm::scale(glm::vec3(s));
|
|
if (mViewerObject->mDrawable != nullptr)
|
|
{
|
|
// object to agent space (no scale)
|
|
glm::mat4 rm(glm::make_mat4((F32*)mViewerObject->mDrawable->getWorldMatrix().mMatrix));
|
|
|
|
// construct object to camera space (with scale)
|
|
mv = mv * rm * scale;
|
|
|
|
// inverse is camera space to object unit cube
|
|
mv = glm::inverse(mv);
|
|
|
|
box = LLMatrix4(glm::value_ptr(mv));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LLReflectionMap::isActive() const
|
|
{
|
|
return mCubeIndex != -1;
|
|
}
|
|
|
|
bool LLReflectionMap::isRelevant() const
|
|
{
|
|
static LLCachedControl<S32> sRenderReflectionProbeLevel(gSavedSettings, "RenderReflectionProbeLevel", (S32)ProbeLevel::FULL_SCENE_WITH_AUTO);
|
|
// <FS:Beq> [FIRE-35070] Correct isRelevant() logic for coverage == None and refactor to make it less fragile
|
|
// 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;
|
|
// Implied logic: a probe has a group if it is either a manual or automatic, it has an object if it is manual
|
|
// hasGroup hasObject (in parenthesis means condition not checked)
|
|
// Manual true true
|
|
// Terrain/Water false (false)
|
|
// Automatic true false
|
|
|
|
const bool is_manual = mViewerObject != nullptr ;
|
|
const bool is_automatic = mGroup != nullptr && !is_manual;
|
|
const bool is_terrain = mGroup == nullptr;
|
|
switch (sRenderReflectionProbeLevel)
|
|
{
|
|
case (S32)ProbeLevel::NONE:
|
|
// no probes are relevant
|
|
return false;
|
|
case (S32)ProbeLevel::MANUAL_ONLY:
|
|
// only manual probes are relevant
|
|
return is_manual;
|
|
case (S32)ProbeLevel::MANUAL_AND_TERRAIN:
|
|
// manual probes and terrain/water probes are relevant
|
|
return !is_automatic;
|
|
case (S32)ProbeLevel::FULL_SCENE_WITH_AUTO:
|
|
// all probes are relevant
|
|
return true;
|
|
default:
|
|
LL_WARNS() << "Unknown RenderReflectionProbeLevel: " << (S32)sRenderReflectionProbeLevel()
|
|
<< " - returning false" << LL_ENDL;
|
|
return false;
|
|
}
|
|
// </FS:Beq>
|
|
}
|
|
|
|
|
|
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
|
|
}
|