Conversation
|
Thanks @byjtew for the contribution! This looks like a straightforward and logical fix -- feel free to add this to CHANGES.md. We can try to sneak it into the upcoming release but I know that it's cutting it close, that's on us for not reviewing sooner.
That's a major improvement! Were you testing this with Unreal Engine? If so, were you using the Tracing Insights to measure the time difference? Also, are you able to add a unit test for this in |
|
One concern I thought of is making sure that We know SSE isn't computed for any tiles that we shouldn't visit because of the early return for culled tiles: cesium-native/Cesium3DTilesSelection/src/TilesetSelection.cpp Lines 1086 to 1121 in 1f4e85d So the tile is definitely selected, but I do worry about the logic in cesium-native/Cesium3DTilesSelection/src/TilesetSelection.cpp Lines 209 to 232 in 1f4e85d I'm going to dig into this later to confirm whether or not this is a concern, but wanted to mention it early in case. |
This case can happen when |
|
I don't manage to repro in the cesium samples project with enableFrustumCulling set to true. I think we can call it a false alarm. |
|
Hi @byjtew, can you clarify what you didn't manage to repro and what is a false alarm? |
|
Got busy sorry about that, but I didn't want to come back with a random fix not backed up by tests. Honest disclaimer: I got help from LLMs for the new fixes and commits that are on that PR. I know the CesiumForUnreal code well, not the cesium-native as well at all. Feel free to question who/what/how as much as your policy requires. Correction to my earlier comment: not a false alarm. Two paths reach it.
Checking Current approach: Notes:
Tests are committed before the fix so they can be run against the old behavior.
|
|
After rebase on main update: Two cases: culling disabled, where the tile gets visited with nothing seeing it; and Each test in one sentence:
|
| // bit i is set if frustum i can see this tile, or one of its children when | ||
| // culling with children bounds. Frustums past bit 63 are treated as seeing | ||
| // every tile. | ||
| uint64_t visibleFrustums = 0; |
There was a problem hiding this comment.
Instead of iterating over all of the frustums in frustumCull and saving a bitfield, you could save the index of the first visible frustum found by frustumCull like so:
std::optional<size_t> firstVisibleFrustumIndex;If it's std::nullopt, then computeSse can exit early with an SSE of 0.0. Otherwise, you can use that as the starting index in the for loop.
This would eliminate any size restraints from using a uint64_t bitfield. It's also less data than a std::vector<bool>. And because frustumCull returns early, we're only iterating over the N frustums once.
| TEST_CASE("A view that sees nothing does not change selection") { | ||
| // Screen-space error depends on a view's projection and its distance to the | ||
| // tile, not on where the view points, so a view facing away from the globe | ||
| // reports a large error for tiles it cannot see. | ||
| TilesetOptions options; | ||
| options.maximumScreenSpaceError = 16.0; | ||
| options.renderTilesUnderCamera = false; | ||
|
|
||
| const ViewState wide = makeViewState(40.0); | ||
| const ViewState blind = makeViewState(1.0, true); | ||
|
|
||
| const ViewUpdateResult alone = selectAfterLoading({wide}, options); | ||
| const ViewUpdateResult withBlind = selectAfterLoading({wide, blind}, options); | ||
|
|
||
| REQUIRE(alone.tilesVisited > 0); | ||
| REQUIRE(alone.tilesToRenderThisFrame.size() > 1); | ||
|
|
||
| CHECK(withBlind.tilesVisited == alone.tilesVisited); | ||
| CHECK(withBlind.tilesCulled == alone.tilesCulled); | ||
| CHECK( | ||
| withBlind.tilesToRenderThisFrame.size() == | ||
| alone.tilesToRenderThisFrame.size()); | ||
| CHECK( | ||
| withBlind.tileScreenSpaceErrorThisFrame == | ||
| alone.tileScreenSpaceErrorThisFrame); | ||
| } |
There was a problem hiding this comment.
This test is a bit confusing because of the multiple frustums. It would be stronger if it only used blind to select tiles, observing that no tiles were rendered in the frame.
|
Done: merged main and moved the changelog entry (the clean merge had quietly dropped it into the released 0.64.0 section), folded the helper back into the tests, both renames. On the frustum index, I don't think it works as described, two things.
Your point about the cap was right though, so I dropped the bitfield anyway. About the blind test: I checked and blind alone renders 0 tiles both with and without the fix, so that version would pass on unfixed code. |
|
Janine asked me to take a look at this. The logic in this part of the code (even before this PR) is surprisingly tricky. The direction in this PR looks great (thanks for the PR @byjtew!), but I don't think it addresses all the corner cases yet. By the way, I believe this PR fixes CesiumGS/cesium-unreal#1823.
|
|
@kring One addition: the culled SSE should only count when enableFrustumCulling is false. With culling on, a view that can't see a tile wants it gone, so it shouldn't drive its refinement. Otherwise the 1 degree view keeps refining tiles it can't see and the fix stops working. Idea: struct CullResult {
bool shouldVisit = true;
bool culled = false;
// Largest error among the frustums that can see this tile.
std::optional<double> visibleScreenSpaceError;
// Largest among those that can't. Only filled when frustum culling is off,
// since otherwise such a view culls the tile away entirely.
std::optional<double> culledViewScreenSpaceError;
};bool meetsSse = true;
if (cullResult.visibleScreenSpaceError) {
meetsSse = *cullResult.visibleScreenSpaceError <
context.options.maximumScreenSpaceError;
}
if (meetsSse && cullResult.culledViewScreenSpaceError) {
meetsSse = !context.options.enforceCulledScreenSpaceError ||
*cullResult.culledViewScreenSpaceError <
context.options.culledScreenSpaceError;
}Existing On Unrelated to your comments: Side-note: I'm okay if your teams wants to take over this PR, I didn't think it would extend that far tbh. |
Description
The fix is straightforward imo: if a tile is not visible by the narrow camera but only by the wide one, then don't use the narrow computed-quality on the tiles only visible from the wide camera.
Issue number or link
CesiumGS/cesium-unreal#1823
https://community.cesium.com/t/multiple-registred-cameras-performance-issue/45482/11
Author checklist
CHANGES.mdwith a short summary of my change (for user-facing changes).Remaining Tasks
I have found this to be extremely effective for this weird special context with multiple cameras, but it may break other things. There may be a better approach, or maybe not ?
Testing plan
Call Tileset::updateView with two ViewStates sharing the same position and target, one wide ~40deg FoV and one narrow, then sweep the narrow view's FoV down to ~1deg.
Before this change, getViewUpdateResult().tilesVisited climbs ~34x (e.g. ~1600 to ~52000) as the narrow FoV shrinks while tilesToRenderThisFrame stays flat, since tiles only the wide view sees get refined to the narrow view's SSE. After the change, tilesVisited stays bounded (~2300) at every FoV, with no change to either view's rendered tiles.
Reviewer checklist
Thank you for taking the time to review this PR. By approving a PR you are taking as much responsibility for these changes as the author.
As you review, please go through the checklist below:
CHANGES.mdto make sure they accurately cover the work in this PR.