Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 122 additions & 21 deletions indra/llmath/llvolume.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2039,6 +2039,7 @@ LLVolume::LLVolume(const LLVolumeParams &params, const F32 detail, const bool ge
: mParams(params)
{
mUnique = is_unique;
mLODInvariant = false;
mFaceMask = 0x0;
mDetail = detail;
mSculptLevel = -2;
Expand Down Expand Up @@ -2108,6 +2109,120 @@ LLVolume::~LLVolume()
mHullIndices = NULL;
}

namespace
{
// Which details split a prim's profile edges.
enum class ProfileSplit
{
// Every detail splits them as it asks.
BY_DETAIL,
// None does.
NONE,
// None does unless the profile's hole is drawn in to its wall, which
// takes the split its detail asks for.
NONE_UNLESS_HOLE_TOUCHES_WALL,
};

ProfileSplit profile_split(const LLVolumeParams& params)
{
const LLPathParams& path_params = params.getPathParams();
if (path_params.getCurveType() != LL_PCODE_PATH_LINE)
{
return ProfileSplit::BY_DETAIL;
}

// A straight path tapering a square or a triangle whose hole type is
// the default, the profile's own shape.
const U8 profile_curve = params.getProfileParams().getCurveType();
const bool tapered = path_params.getScale().mV[0] != 1.0f || path_params.getScale().mV[1] != 1.0f;
if (tapered)
{
return profile_curve == LL_PCODE_PROFILE_SQUARE || profile_curve == LL_PCODE_PROFILE_ISOTRI ||
profile_curve == LL_PCODE_PROFILE_EQUALTRI || profile_curve == LL_PCODE_PROFILE_RIGHTTRI
? ProfileSplit::NONE
: ProfileSplit::BY_DETAIL;
}

// A straight path that neither twists nor tapers sweeps every profile
// edge into a parallelogram, and every face is flat or ruled straight
// along the path: positions and UVs are affine over each quad, and the
// split points and the rings between the ends lie on the triangles the
// ends make. A hole drawn in to its wall is the exception: the wall
// draws in some of its points and not others, its edges no longer run
// straight between its corners, and its split points shape it.
return path_params.getTwistBegin() == path_params.getTwist() ? ProfileSplit::NONE_UNLESS_HOLE_TOUCHES_WALL : ProfileSplit::BY_DETAIL;
}

// Whether a prim's parameters build it the same at every detail, as far
// as they say without laying its profile out.
enum class LODInvariance
{
NONE,
INVARIANT,
INVARIANT_UNLESS_HOLE_TOUCHES_WALL,
};

LODInvariance lod_invariance(const LLVolumeParams& params)
{
// A sculpt or a mesh has a resolution of its own.
if (params.getSculptType() != LL_SCULPT_TYPE_NONE || params.getSculptID().notNull())
{
return LODInvariance::NONE;
}

// The profile must take no split at any detail, and the path its two
// ends alone, which a line path does where it does not twist.
const ProfileSplit split = profile_split(params);
const LLPathParams& path_params = params.getPathParams();
if (split == ProfileSplit::BY_DETAIL || path_params.getTwistBegin() != path_params.getTwist())
{
return LODInvariance::NONE;
}

// Unsplit, a square or a triangle has the same points at every
// detail, and so does a hole of straight sides; a round hole has as
// many sides as its detail asks for.
const LLProfileParams& profile_params = params.getProfileParams();
const U8 profile_curve = profile_params.getCurveType() & LL_PCODE_PROFILE_MASK;
if (profile_curve != LL_PCODE_PROFILE_SQUARE && profile_curve != LL_PCODE_PROFILE_ISOTRI && profile_curve != LL_PCODE_PROFILE_EQUALTRI &&
profile_curve != LL_PCODE_PROFILE_RIGHTTRI)
{
return LODInvariance::NONE;
}
if (profile_params.getHollow() == 0.f)
{
return LODInvariance::INVARIANT;
}
if ((profile_params.getCurveType() & LL_PCODE_HOLE_MASK) == LL_PCODE_HOLE_CIRCLE)
{
return LODInvariance::NONE;
}
return split == ProfileSplit::NONE ? LODInvariance::INVARIANT : LODInvariance::INVARIANT_UNLESS_HOLE_TOUCHES_WALL;
}

// Whether a prim is LOD-invariant, from what its parameters say and its
// profile as LLVolume::generate first lays it out, without split points.
bool keeps_lod_invariance(LODInvariance invariance, const LLProfile& profile)
{
return invariance == LODInvariance::INVARIANT || (invariance == LODInvariance::INVARIANT_UNLESS_HOLE_TOUCHES_WALL && !profile.holeTouchesWall());
}
}

// static
bool LLVolume::isLODInvariant(const LLVolumeParams& params)
{
const LODInvariance invariance = lod_invariance(params);
if (invariance == LODInvariance::NONE)
{
return false;
}

// The profile as generate() first lays it out.
thread_local LLProfile profile;
profile.setDirty();
return profile.generate(params.getProfileParams(), true) && keeps_lod_invariance(invariance, profile);
}

bool LLVolume::generate()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
Expand All @@ -2120,27 +2235,9 @@ bool LLVolume::generate()
// stretched due to twisting or scaling on the path.
S32 split = (S32) ((mDetail)*0.66f);

const LLPathParams& path_params = mParams.getPathParams();
const bool tapered = path_params.getScale().mV[0] != 1.0f || path_params.getScale().mV[1] != 1.0f;
if (path_params.getCurveType() == LL_PCODE_PATH_LINE &&
tapered &&
(mParams.getProfileParams().getCurveType() == LL_PCODE_PROFILE_SQUARE ||
mParams.getProfileParams().getCurveType() == LL_PCODE_PROFILE_ISOTRI ||
mParams.getProfileParams().getCurveType() == LL_PCODE_PROFILE_EQUALTRI ||
mParams.getProfileParams().getCurveType() == LL_PCODE_PROFILE_RIGHTTRI))
{
split = 0;
}

// A straight path that neither twists nor tapers sweeps every profile
// edge into a parallelogram, and every face is flat or ruled straight
// along the path: positions and UVs are affine over each quad, and the
// split points and the rings between the ends lie on the triangles the
// ends make. A hole drawn in to its wall is the exception: the wall
// draws in some of its points and not others, its edges no longer run
// straight between its corners, and its split points shape it.
const S32 lighting_split = split;
if (path_params.getCurveType() == LL_PCODE_PATH_LINE && !tapered && path_params.getTwistBegin() == path_params.getTwist())
const ProfileSplit split_rule = profile_split(mParams);
const S32 lighting_split = split_rule == ProfileSplit::NONE ? 0 : split;
if (split_rule != ProfileSplit::BY_DETAIL)
{
split = 0;
}
Expand All @@ -2167,6 +2264,10 @@ bool LLVolume::generate()

bool regenPath = mPathp->generate(mParams.getPathParams(), path_detail, split);
bool regenProf = mProfilep->generate(mParams.getProfileParams(), mPathp->isOpen(),profile_detail, split);
if (regenProf)
{
mLODInvariant = !mUnique && !mGenerateSingleFace && keeps_lod_invariance(lod_invariance(mParams), *mProfilep);
}
if (regenProf && split < lighting_split && mProfilep->holeTouchesWall())
{
mProfilep->setDirty();
Expand Down
7 changes: 7 additions & 0 deletions indra/llmath/llvolume.h
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,12 @@ class LLVolume : public LLRefCount
bool isFlat(S32 face);
bool isUnique() const { return mUnique; }

// Whether generate() builds the same profile, path and faces from these
// parameters at every detail, so that one volume serves every LOD.
static bool isLODInvariant(const LLVolumeParams& params);
// Whether this volume is shared and its parameters are LOD-invariant.
bool isLODInvariant() const { return mLODInvariant; }

S32 getSculptLevel() const { return mSculptLevel; }
void setSculptLevel(S32 level) { mSculptLevel = level; }

Expand Down Expand Up @@ -1148,6 +1154,7 @@ class LLVolume : public LLRefCount

protected:
bool mUnique;
bool mLODInvariant;
F32 mDetail;
S32 mSculptLevel;
F32 mSurfaceArea; //unscaled surface area
Expand Down
11 changes: 7 additions & 4 deletions indra/llmath/llvolumemgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ std::ostream& operator<<(std::ostream& s, const LLVolumeMgr& volume_mgr)

LLVolumeLODGroup::LLVolumeLODGroup(const LLVolumeParams &params)
: mVolumeParams(params),
mLODInvariant(LLVolume::isLODInvariant(params)),
mRefs(0)
{
for (S32 i = 0; i < NUM_LODS; i++)
Expand Down Expand Up @@ -293,13 +294,15 @@ LLVolume* LLVolumeLODGroup::refLOD(const S32 lod)
llassert(lod >=0 && lod < NUM_LODS);
mAccessCount[lod]++;

const S32 slot = mLODInvariant ? NUM_LODS - 1 : lod;
mRefs++;
if (mVolumeLODs[lod].isNull())
if (mVolumeLODs[slot].isNull())
{
mVolumeLODs[lod] = new LLVolume(mVolumeParams, mDetailScales[lod]);
mVolumeLODs[slot] = new LLVolume(mVolumeParams, mDetailScales[slot]);
llassert(mVolumeLODs[slot]->isLODInvariant() == mLODInvariant);
}
mLODRefs[lod]++;
return mVolumeLODs[lod];
mLODRefs[slot]++;
return mVolumeLODs[slot];
}

bool LLVolumeLODGroup::derefLOD(LLVolume *volumep)
Expand Down
2 changes: 2 additions & 0 deletions indra/llmath/llvolumemgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class LLVolumeLODGroup
protected:
LLVolumeParams mVolumeParams;

// Every LOD of LOD-invariant parameters refs one volume, the highest LOD's.
bool mLODInvariant;
S32 mRefs;
S32 mLODRefs[NUM_LODS];
LLPointer<LLVolume> mVolumeLODs[NUM_LODS];
Expand Down
13 changes: 13 additions & 0 deletions indra/llmath/tests/llvolume_bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,19 @@ namespace
}
}

TEST_CASE("llvolume_bench: whether a prim of each class is LOD-invariant", "[llvolume_bench][!benchmark]")
{
ll_test::skipUnlessOptimised();
for (const Prim& prim : primClasses())
{
const LLVolumeParams params = prim.params;
BENCHMARK(fmt::format("{}, {}", prim.name, LLVolume::isLODInvariant(params) ? "invariant" : "not invariant"))
{
return LLVolume::isLODInvariant(params);
};
}
}

TEST_CASE("llvolume_bench: a sculpt of each stitching built from its map", "[llvolume_bench][!benchmark]")
{
ll_test::skipUnlessOptimised();
Expand Down
Loading
Loading