-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Metadata picking preparation #12075
Metadata picking preparation #12075
Conversation
Thank you for the pull request, @javagl! ✅ We can confirm we have a CLA on file for you. |
I think the overall approach makes sense, although perhaps @lilleyse should chime in. My main concern: calling We only need to rebuild the shader when these values change: const schemaId = frameState.pickedMetadataSchemaId;
const className = frameState.pickedMetadataClassName;
const propertyName = frameState.pickedMetadataPropertyName; Thinking about how an end user would pick: there would be some UI element letting them choose a class and property. These values would change only rarely--once every few seconds, at most. Then, once they mouse over the model, A faster approach might be:
Note: you will need to keep 2 compiled versions of the shader alive: one for the rendering pass, and one for the metadata picking pass. See The compiled shaders are stored on
Then for each render pass, you need to select the appropriate |
I expected the
I have seen the The answer may be "In a DrawCommand". But ... there are reasons why I did not try the DrawCommand-based approach. (Well, in fact, I did try it - this PR is only the condensed tip of an iceberg of internal work and experiments...). One reason is that the code is ... let's call it intimidating. I roughly understand the idea behind DrawCommands. And I did start with some Another reason is that there apparently aren't really "draw commands" for the model - at least, not on the same level as for other elements: There's the However, I'll try to read more, try to map your recommendations to the code, and see whether there is a way to achieve that pseudocode-ish BTW: I've seen in your voxel picking PR that you started adding comments. That's good. When reading though long, undocumented functions, I usually cannot help myself, and try to make things easier to digest - even though this will likely be in vain... |
Only a short update: Creating different draw commands for the picking/rendering case of a model is not as simple as it might be in other cases. As mentioned above: There aren't really "draw commands" for the model. There is the Trying to ignore the details (because there are waaaay too many, too undocumented details), one approach that I tried was to add a
Ignoring 1., I'll first try to "do whatever is done to the |
Another update. This can probably be ignored. It is rather a form of "Rubber Duck Debugging" right now. The For each The At some point, it does build a tl;dr: The
Now, there's probably a point where cloning is not necessary. But given that undocumented modifications of undocumented properties are smeared all over the code, and nobody really knows what has to be cloned or not, the only reasonable approach for me right now is to try and clone things "until it works". An anecdote: Many years ago, I created a small "renderer" library. And I did also use the concept of a (Draw)Command there. It was a tad simpler, though. |
I wrote this two days ago. And still, it took me several hours to find out why ~"sometimes, under certain conditions, the picking frame buffer was not filled with proper values". Right now, it seems like this is indeed related to the fact that the render resources - specifically, the
indicates that it "works" when saving and re-assigning that Now...was that modification intentional? Does it serve a purpose? Should I avoid this modification? Does something else break when I prevent this modification? Can someone add a comment to the line This code is in dire need of cleanups. Having to hunt "bugs" that are caused by things like this will let any form of actual development come to a grinding halt. |
That bounding sphere modification. Sigh.
However, here's a small update:
Technically, this seems to work in principle, with the caveats:
An aside: When picking the metadata values with |
The last commit contains a draft for how the "encoding and decoding" of metadata values into the frame buffer could be handled. It is really only a draft, to quickly sync on the overall approach.
The values are still returned as a In the meantime, I'm less convinced of the overall approach than I was in the beginning. Funneling "arbitrary values" through the frame buffer, using the whole DrawCommand-infrastructure, feels like a "detour" (not to mention that it is a debugging nightmare). It would be great if there was an option to implement this more like pseudocode
I even considered to just ditch the whole
So basically really assign a What's also concerning: There now are four properties in the |
Offline we talked about using the scene-level picking and derived command infrastructure instead of creating pick metadata commands in Model.
|
The place where the current "derived commands" for picking are created is in the There are a few questions related to that. Note that I'm NOT asking these questions. Nobody has to even consider answering them. I'm only mentioning them, to make clear that I'm spending about 99% of my time with questions like these:
|
Theoretically, this might be possible. It is possible to create a The problem is that at this point, only the But I'm pretty sure that I'm missing some point here, so I wanted to confirm whether "shader source code string manipulation with regex find-and-replace" is indeed the intended solution for this. |
In the previous state, the approach was
This duplication was probably "too high", so to speak, and did lead to some issues. It's hard to tell for sure what exactly was duplicated there to begin with. And it's hard to say where the The last commit changes this, based on the recommendation by @lilleyse , to do the metadata handling in derived commands. The current state contains several 'markers', specifically, There now is a
but all the parts that are varying are represented as In the That derived command is created by
These values end up in the frame buffer, from where they are read, and translated back into the actual metadata value (a 2-element array in the above example). Now. Funneling that data through the frame buffer seems quirky. Assembling the proper shader for that based on Is this the intended approach? EDIT, a small note: Right now, this is still rebuilding the draw commands when the picked metadata property changes. Updating the "minimal set" (of derived commands?) upon changes still remains to be implemented... |
The last point of rebuilding the draw commands was now addressed as part of a minor ... "consolidation". From my point of view, the functionality that is added with this PR does not warrant the number and complexity of the changes that have been necessary to accomplish it. But doing what is necessary to decrease the complexity cannot be part of this PR. Maybe I'll add follow-up PRs similar to #12058 , but it's hard to make promises here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @javagl, I did a final once over this PR to ensure this is good to merge for tomorrow's release.
- I've updated
CHANGES.md
to document the changes to the public API, based on my understanding of the updates. Please make sure to include updates toCHANGES.md
in the future, even for features marked as experimental, as it acts as a historical record. - I've gone and verified all existing picking examples, and everything seems to be working as before.
- I also tested out the new functionality with the Sandcastle below. I noticed that there was a big hit to the frame rate– When mousing around the scene, I'm getting 0-1 FPS. Is this expected?
- I also made several review comments. If we need to get this in for tomorrow's release, we can move forward without them (and therefore approved this PR), but I would like to see a cleanup PR follow-up soon after the release.
Sandcastle example using existing sample data:
const viewer = new Cesium.Viewer("cesiumContainer", {
terrain: Cesium.Terrain.fromWorldTerrain(),
});
const scene = viewer.scene;
scene.globe.depthTestAgainstTerrain = false;
scene.debugShowFramesPerSecond = true;
let tileset;
try {
// MAXAR OWT Muscatatuk photogrammetry dataset with property textures
// containing horizontal and vertical uncertainty
tileset = await Cesium.Cesium3DTileset.fromIonAssetId(2342602);
viewer.scene.primitives.add(tileset);
viewer.zoomTo(tileset);
} catch (error) {
console.log(`Error loading tileset: ${error}`);
}
const shaders = {
NO_TEXTURE: undefined,
UNCERTAINTY_CE90: new Cesium.CustomShader({
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
int horizontalUncertainty = fsInput.metadata.r3dm_uncertainty_ce90sum;
material.diffuse = vec3(float(horizontalUncertainty) / 255.0);
}
`,
}),
UNCERTAINTY_LE90: new Cesium.CustomShader({
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
int verticalUncertainty = fsInput.metadata.r3dm_uncertainty_le90sum;
material.diffuse = vec3(float(verticalUncertainty) / 255.0);
}
`,
}),
// combined uncertainty
UNCERTAINTY: new Cesium.CustomShader({
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
int uncertainty = fsInput.metadata.r3dm_uncertainty_ce90sum + fsInput.metadata.r3dm_uncertainty_le90sum;
material.diffuse = vec3(float(uncertainty) / 255.0);
}
`,
}),
};
Sandcastle.addDefaultToolbarMenu([
{
text: "Horizontal Uncertainty",
onselect: function () {
tileset.customShader = shaders.UNCERTAINTY_CE90;
},
},
{
text: "Vertical Uncertainty",
onselect: function () {
tileset.customShader = shaders.UNCERTAINTY_LE90;
},
},
{
text: "Combined Uncertainty",
onselect: function () {
tileset.customShader = shaders.UNCERTAINTY;
},
},
{
text: "No Uncertainty",
onselect: function () {
tileset.customShader = shaders.NO_TEXTURE;
},
},
]);
tileset.customShader = shaders.UNCERTAINTY_CE90;
// Create an HTML element that will serve as the
// tooltip that displays the metadata information
function createTooltip() {
const tooltip = document.createElement("div");
viewer.container.appendChild(tooltip);
tooltip.style.backgroundColor = "black";
tooltip.style.position = "absolute";
tooltip.style.left = "0";
tooltip.style.top = "0";
tooltip.style.padding = "14px";
tooltip.style["pointer-events"] = "none";
tooltip.style["block-size"] = "fit-content";
return tooltip;
}
const tooltip = createTooltip();
// Show the given HTML content in the tooltip
// at the given screen position
function showTooltip(screenX, screenY, htmlContent) {
tooltip.style.display = "block";
tooltip.style.left = `${screenX}px`;
tooltip.style.top = `${screenY}px`;
tooltip.innerHTML = htmlContent;
}
// Create an HTML string that contains information
// about the given metadata, under the given title
function createMetadataHtml(title, metadata) {
if (!Cesium.defined(metadata)) {
return `(No ${title})<br>`;
}
const propertyKeys = metadata.getPropertyIds();
if (!Cesium.defined(propertyKeys)) {
return `(No properties for ${title})<br>`;
}
let html = `<b>${title}:</b><br>`;
for (let i = 0; i < propertyKeys.length; i++) {
const propertyKey = propertyKeys[i];
const propertyValue = metadata.getProperty(propertyKey);
html += ` ${propertyKey} : ${propertyValue}<br>`;
}
return html;
}
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
const classNameA = "r3dm_uncertainty_ce90sum";
const propertyNameA = "r3dm_uncertainty_ce90sum";
const classNameB = "r3dm_uncertainty_le90sum";
const propertyNameB = "r3dm_uncertainty_le90sum";
const pickedA = viewer.scene.pickMetadata(
movement.endPosition,
undefined,
classNameA,
propertyNameA
);
const pickedB = viewer.scene.pickMetadata(
movement.endPosition,
undefined,
classNameB,
propertyNameB
);
let tooltipText = "";
tooltipText += propertyNameA + ": " + pickedA + "<br>";
tooltipText += propertyNameB + ": " + pickedB + "<br>";
const screenX = movement.endPosition.x;
const screenY = movement.endPosition.y;
showTooltip(screenX, screenY, tooltipText);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
* into the actual metadata values, according to the structure | ||
* defined by the `MetadataClassProperty`. | ||
* | ||
* This is marked as 'private', but supposed to be used in Picking.js. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@private
means the member will not be exposed in the API documentation, and can change without warning or deprecation. It does not mean it can't be used within other parts of CesiumJS.
Assuming that is how this function is intended to be used, I think this line is redundant and can be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, assuming these utility functions are generalizable and applicable to MetadataType
, should they live in MetadataType
instead? For example convertToObjectType
seems quite general to dealing with metadata.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to write them in a somewhat general form. And there already is quite a lot of existing functionality - for example, in https://github.com/CesiumGS/cesium/blob/e3e98371830a85d653c2f9e1f34f382c84984bce/packages/engine/Source/Scene/MetadataTableProperty.js . In both cases, ("typed") metadata values have to be read from binary data.
There are some subtle differences, though. Pulling out the parts that are really generic (and using these parts in both the MetdataPicking
and the MetadataTableProperty
) could be part of a follow-up/cleanup PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now summarized in #12225
case MetadataComponentType.FLOAT64: | ||
return dataView.getFloat64(index); | ||
} | ||
// Appropriate error handling? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove TODO comments like this from the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done - now throws a RuntimeError
return value; | ||
} | ||
if ( | ||
type === "SCALAR" || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be using MetadataType
rather than hardcoding strings? Likewise, should we enforce that by marking the type of type
as MetadataType
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
|
||
describe( | ||
"Scene/pickMetadata", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spec file organization should mirror the Source directory organization.
The structure of these specs allude to a file which would exist at packages/engine/Source/Scene/Model/pickMetadata.js
, which doesn't exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was mimicking the existing pickModelSpec.js
. I desired, I can merge the specs into the SceneSpec
, but ... that will be >1000 lines to a spec with already >2300 lines....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but pickModelSpec.js
is a standalone file.
Let's go with what is described in the guide and move the code. I agree it is a large file, but to break from recommendations, we should propose an update to the guide separate from this PR, which is already on the larger side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, just moved it over into SceneSpec
and removed pickMetadataSpec.js
Co-authored-by: Gabby Getz <[email protected]>
…iumGS/cesium into metadata-picking-preparation
Co-authored-by: Gabby Getz <[email protected]>
Co-authored-by: Gabby Getz <[email protected]>
…iumGS/cesium into metadata-picking-preparation
…aration # Conflicts: # CHANGES.md
I addressed most of the inlined comments.
No. From the description, it sounds like there might be an issue with the shader caching: Some of the earlier discussion revolved around the point of avoiding unnecessary recompilations, exactly for that case: The shaders that are built for picking the respective metadata property are supposed to be only built once and then re-used. There had been a last-minute change in that area, based on another comment - maybe something there had an unintended side-effect. But I could not reproduce the frame rate issue locally until now: With the sandcastle that you posted, and constant mouse movement, I got this: with stable 59-60 FPS. I'll take a note in a follow-up issue to investigate/check whether something went wrong with the shader caching. |
The build just failed with an error, saying that a required parameter cannot follow an optional one. This was caused by the change
suggested in #12075 (comment) . This is related to the point that was already brought up in #12075 (comment) . The I'll also add that to the notes for the follow-up PR. |
Thanks! Since this affects behavior, I want to be sure that this is documented if not addressed immediately.
OK, makes sense 👍 |
I summarized the open points here in a follow-up issue at #12225 (most of them refer to this PR, but for some of them, the specifics of the connection to the GPM support are relevant) |
@ggetz I think that the inlined comments have been addressed (or moved into a follow-up issue, accordingly). I had to re-run CI a few times - there seems to be s somewhat flaky-shaky test. But that seems to be independent of this PR. |
Thanks @javagl! Yes, the flakey should not be due to this PR. |
I could now reproduce this. By hitting F12 😬 So... when the DevTools are closed, I receive a smooth framerate of ~59, regardless of the mouse movement. I'll still check whether there's something wrong with the shader caching. And I know that the DevTools do not come "for free". But that drop is pretty significant... |
Now I'm at the point where I think that the Chrome DevTools are just fooling me. When running with the DevTools open, it drops to ~1FPS. But when also running the profiler, I get a stable 59FPS again 🤡 However, I did check two things:
I'd lean towards considering this as a DevTools glitch, somewhat unrelated to the |
I likely had the developer tools open 😅
Non-ideal to be sure, but this is not the first time we've seen happen. I believe @jjhembd ran into a similar issue when profiling 3D Tiles load times. |
The 3D Tiles Next Metadata Compatibility Matrix currently lists the functionalities of picking and styling based on property textures or property attributes as "not supported". Some details about this requirement are also summarized in #9852
This PR is supposed to help fleshing out possible approaches for this. I have not worked with the relevant parts of the code. And the compatibility matrix contains a footnote saying
but I have no idea what this really entails. So I'm opening this as an early DRAFT, to get an idea about how flawed the approach is, and gather feedback for "better" approaches.
30-second-summary
The naive description of the approach is:
Scene.pickMetadata(schemaId, className, propertyName)
Implementation draft
There is a
Scene.pickMetadata
function. This function receives theschemaId/className/propertyName
of the metadata that is supposed to be picked.Note: This is only a DRAFT. It assumes that the client already knows what should be picked. But the information about what can be picked may have to be exposed in one form or another. Maybe via some
pickMetadataSchema
function that returns theMetadataSchema
of the picked object. Details TBD.The function directly delegates to a
Picking.pickMetadata
function. This roughly follows the pattern of the otherpick...
functions: It sets up everything to prepare a "metadata picking pass", renders everything into a frame buffer, and returns the rendered pixels at the picked position.Now, the crucial question is how the actual metadata property values end up in the frame buffer during this "metadata picking pass". And I know that the current solution is not right, but hope for feedback about better approaches. The current flow of events is:
Scene.pickMetadata
function has to callmodel.resetDrawCommands();
to enforce re-building the model draw commands for with the "picking shader". It would be better to solve this with somemodel.drawCommands[42].pickMetadata = true;
. But the structure of the model draw commands is... not obvious, and has many intricacies, and ... eventually, the shaders (or at least, parts of them) will have to be re-built, because the selection of the pickedclassName/propertyName
does happen at runtime after all.Picking.pickMetadata
function puts theclassName/propertyName
into theFrameState
. This is probably far too global and too transient. But this information is passed in from the client, and has to end up in a shader. I don't know whether there is a better "path" to put this information into the right place. (There are some additional quirks there, related to OIT, but... let's ignore that for now)ModelRuntimePrimitive
- namely, theMetadataPickingPipelineStage
. This will set the required#define
s in the shader, based on the presence of theclassName/propertyName
in theFrameState
.ModelFS.glsl
now checks for theMETADATA_PICKING
define. When it is defined, then it skips certain operations, and instead, runs themetadataPickingStage
. This obtains the "color" for the fragment from the metadata values (namely, the value that was selected via theMETADATA_PICKING_PROPERTY_NAME
define, that was set to be thepropertyName
). (Note: The fact that theModelFS.glsl
has to be broken up like that is not "nice", but this could and would be structured "more nicely", eventually)Open quesitons
There are some obvious things that have to be handled. For example: How is the actual property value encoded into the RGBA components? Right now, it is just written into the
R
channel, just for a test. How could, for example, aDOUBLE
metadata value be encoded? Do we need (or should be use) "floating point frame buffers"? And eventually, regardless of how the metadata value is encoded into the RGBA values, these values will have to be de-coded again in theScene.pickMetadata
function, to return the actual value to the user.However, these are things that can be addressed after a general, sensible approach was developed. And my gut feeling is: The approach that is drafted here is likely not such a "general sensible" approach. I'm open for suggestions.
Example
The following is a Sandcastle that uses the Custom Shaders Property Textures Sandcatle data set to show the current state of the picking. It calls
scene.pickMetadata
using the class/property names of that data set, and prints the returned values to the console:Again, these are just returned as 4-element byte arrays with the "value" encoded in the first byte, but details about the encoding/decoding can be decided later.