Skip to content

Conversation

GhisBntly
Copy link
Contributor

Proposed implementation for #1658

@GhisBntly GhisBntly marked this pull request as draft May 9, 2025 08:06
Copy link
Contributor

@j9liu j9liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for opening this for review @GhisBntly ! Just looking at this from a high level, I had a few questions about some of the choices made. But I think this approach is promising 🤞

class UStaticMeshComponent;

UINTERFACE()
class UCesiumLoadedTileBase : public UInterface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer this interface over exposing CesiumGltfPrimitiveComponent, so thanks for working on it! I might add more feedback later, but I think this approach works for now.

UPROPERTY(EditAnywhere, Category = "Rendering")
FCustomDepthParameters CustomDepthParameters{};

Cesium3DTilesSelection::Tile* pTile = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know why you did this, but I'm hesitant to give this component the power to modify the Native tile. I'm not sure if this could affect the tileset loading algorithm in some unexpected way... maybe @kring would know off the top of his head?

Can you explain when you would use SetRenderReady for your use case? I'm hoping there's a better or equal way to achieve this without reaching into the Native tile state 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm generally ok with the Tile being accessible, so long as it's only accessible from the main thread. The public API of Tile could definitely be better, to make it clear what sort of modifications are ok, but as an advanced API here I think it's fine for now.

I would like to understand better why the "render ready" concept is used, and why it's needed, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah adding this member made me uncomfortable too ;-) But I didn't see any other way, and at least UCesiumGltfComponent is Private. The Public interface I added only gives access to the Tile ID and to the setter I needed (SetRenderReady).

Now for the use case: it will be complex to explain, as we reached this solution after much trial and error...
Our app logic means we need to create textures for some of the mesh components. Initially, we were doing everything in OnMeshConstructed:

  1. discover per-vertex metadata to identify mesh subsets and map them to app domain data
  2. in tiles where it was needed, create property textures (with a size matching the max FeatureID found on vertices), initialized (UTexture2D::UpdateTextureRegions) with app-specific data used by our custom material's shaders
  3. assign (SetTextureParameterValueByInfo) these textures to all materials of the tile's meshes

But we realized that, randomly, some textures were not coming through properly, ie. the render was showing as if the material was accessing garbage data, even though we had triple-checked that all data was correct and the right methods called on our side. We finally found that we had to wait for the render thread to process the render command enqueued by the UpdateTextureRegions call to guarantee that the texture could be assigned to the materials :-/

So we added a synchronization mechanism through UpdateTextureRegions's DataCleanupFunc parameter: to avoid blocking the game thread while waiting for the RHI thread, we had to defer the finalization of our materials setup to the next tick which in turn meant we had to tell the tiles selection algorithm that the tile was not quite ready for render yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation @GhisBntly! I'm still a little confused as to why it's necessary to delay, though.

UpdateTextureRegions enqueues a command to the renderer thread, that makes sense. But as long as any draw commands for that tile are executed by the render thread after that command enqueued by UpdateTextureRegions, I don't think any particular synchronization or delay should be necessary. Certainly this is true when we create textures, at least.

Is it possible something else was happening here? Like the UpdateTextureRegions wasn't invoked until after the tile was drawn with the non-updated texture somehow?

Or is there more going on in UpdateTextureRegions than I expect?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree our problem and solution doesn't feel "right" ... but we have spent a fair amount of time debugging and didn't find anything so far that could be a bug in our code. Our assumption is that assigning the texture with SetTextureParameterValueByInfo has immediate side effects on render commands already enqueued :-/

It was our early days with Unreal last year so maybe we missed something. If you think this is too intrusive a change to merge without a better understanding of what's happening, I can remove that part from the PR, and we'll open a new one with it later if need be, after we have had another look at it with the experience gained.

Copy link
Member

@kring kring left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for opening this PR @GhisBntly! I have a bunch of small comments, but overall it looks great.

UPROPERTY(EditAnywhere, Category = "Rendering")
FCustomDepthParameters CustomDepthParameters{};

Cesium3DTilesSelection::Tile* pTile = nullptr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm generally ok with the Tile being accessible, so long as it's only accessible from the main thread. The public API of Tile could definitely be better, to make it clear what sort of modifications are ok, but as an advanced API here I think it's fine for now.

I would like to understand better why the "render ready" concept is used, and why it's needed, though.

@GhisBntly
Copy link
Contributor Author

Note: I had to add GetVertexPositionScaleFactor() to the UCesiumLoadedTile interface in order to get rid of CPU access to Unreal memory buffers in our implementation of the lifecycle events receiver: the function replicates the logic hardcoded in loadPrimitive (multiplication by the internal CesiumPrimitiveData::positionScaleFactor as well as Y axis inversion).
I could have added a vertex visiting method instead in UCesiumLoadedTilePrimitive, but it didn't really seem worth the trouble(?)

@GhisBntly GhisBntly marked this pull request as ready for review June 11, 2025 14:27
@GhisBntly
Copy link
Contributor Author

I mark this PR as ready for review, although I'm still unsure about SetRenderReady. I will try to confirm (or debunk ;-)) our understanding of the problem with a simpler example tomorrow. In particular, I'm indeed keen on seeing whether the problem could also happen with cesium-created textures :-/

@GhisBntly
Copy link
Contributor Author

About SetRenderReady, note that I also considered using it to avoid blocking the game thread for too long, because each tile needs substantial post-processing to be made "render-ready" (parsing vertex metadata, map this data to internal domain data, create optimization structures for 4D schedule replay, and finally create the textures for the custom material shader...).

The method that does all that in our code already supports a time budget: even though I do not use it for this purpose at the moment, I may want to do so in the future to smooth out tile transitions, which may be a valid reason to include this feature?

@GhisBntly GhisBntly requested review from kring and j9liu June 17, 2025 11:50
@GhisBntly
Copy link
Contributor Author

Sadly, I was unable to reproduce the specific issue that led us to defer material texture assignments, but it doesn't mean the problem no longer happens, as it was witnessed only in rare circumstances, and the project data I had used to investigate it no longer exists :-(
Still, I would really prefer keeping SetRenderReady, if only to allow for better distribution of the tile post-process load on our end over several game ticks.

@j9liu j9liu added this to the August 2025 Release milestone Jun 30, 2025
@j9liu
Copy link
Contributor

j9liu commented Jul 8, 2025

Sorry for the delay on these PRs @GhisBntly :( I'll take a look at this sometime this week!

@kring
Copy link
Member

kring commented Jul 22, 2025

The method that does all that in our code already supports a time budget: even though I do not use it for this purpose at the moment, I may want to do so in the future to smooth out tile transitions, which may be a valid reason to include this feature?

cesium-native already has (and cesium-unreal already uses) a main thread loading time limit:
https://github.com/CesiumGS/cesium-native/blob/v0.49.0/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TilesetOptions.h#L302

Also, if you can do any of the work in a worker thread, then that process is allowed to take place over multiple frames, with a Future signaling when it's complete.

Does the "render ready" mechanism satisfy a use-case that those two mechanisms don't already satisfy?

Sadly, I was unable to reproduce the specific issue that led us to defer material texture assignments, but it doesn't mean the problem no longer happens, as it was witnessed only in rare circumstances, and the project data I had used to investigate it no longer exists :-(

It makes me a little uneasy, because the use-case is unclear (at least to me) and its the sort of thing that has a moderately high potential for error by causing violation of assumptions in the tile selection algorithm. From what I can see, this PR doesn't actually use the "render ready" flag at all. So it should be pretty easy to extract that part into a separate PR, right?

Copy link
Member

@kring kring left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of small comments here, but once again, it looks great overall!

@GhisBntly
Copy link
Contributor Author

Also, if you can do any of the work in a worker thread, then that process is allowed to take place over multiple frames, with a Future signaling when it's complete.

Does the "render ready" mechanism satisfy a use-case that those two mechanisms don't already satisfy?

No you're right, my time budget use case doesn't add anything indeed.

It makes me a little uneasy, because the use-case is unclear (at least to me) and its the sort of thing that has a moderately high potential for error by causing violation of assumptions in the tile selection algorithm. From what I can see, this PR doesn't actually use the "render ready" flag at all. So it should be pretty easy to extract that part into a separate PR, right?

I understand, I will remove it from this PR. I have started testing again our app without this deferred texture setup system, I guess if it proves stable and no longer show any bug like the one we had, we may remove it entirely on our side too in the future.

@kring kring removed this from the August 2025 Release milestone Jul 31, 2025
@GhisBntly GhisBntly force-pushed the enh-1658_mesh-build-callbacks branch from 0387394 to 8113a0a Compare August 14, 2025 11:51
Which was not possible with a
TObjectPtr<ICesium3DTilesetLifecycleEventReceiver>: tried a
TScriptInterface, but decided for a UObject after reading about caveats:
see comments over UCesium3DTilesetLifecycleEventReceiver.

Also flagged UCesium3DTilesetLifecycleEventReceiver with
'CannotImplementInterfaceInBlueprint'
This reverts commit 380fa13.

I was supposed to remove the F from our version, not add it
to the upstream... ;^^
and from SetGltfParameterValues, which it probably mimicked in the first
place
@GhisBntly
Copy link
Contributor Author

@kring , I believe I have accounted for all comments so far - ready for next round ;^^

@GhisBntly GhisBntly requested a review from kring August 14, 2025 15:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants