Skip to content
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

Allowing EnsoGL mouse to interact with more than 4096 sprites at the same time. #3351

Merged
merged 25 commits into from
Mar 29, 2022

Conversation

wdanilo
Copy link
Member

@wdanilo wdanilo commented Mar 20, 2022

[ci no chfangelog needed]

Pull Request Description

This PR implements Enso mouse system should allow working with more than 4096 objects.. Until now, all sprites were distinguished by two values, symbol_id and instance_id. These values were encoded in an off-screen texture, on 12 bits each. This PR introduces a new abstraction, GlobalInstanceId, which unifies symbol_id and instance_id for the purpose of mouse target discovery. Both symbol_id and instance_id still exist, because they are usable in other parts of the codebase. Moreover, this PR introduces a few improvements:

  1. It introduces a few helper structures, allowing for easier custom drop management of display objects and attributes.
  2. It introduces a new derive macro NoCloneBecauseOfCustomDrop, which prevents the structure from implementing Clone and checks if the structure implements custom Drop behavior.
  3. It abstracts part of Sprite implementation to a new concept SymbolInstance.
  4. It implements shared2 macro, which is similar to shared, but unlike shared it is understandable by IntelliJ. However, shared2 does not support all constructs yet.
  5. It extends EnsoGL renderer to support integer vertex input attributes.
  6. It implements new debug utilities for generated shaders debugging and improves error messages.
  7. It implements a new #[entry_point] macro, which simplifies entry point definition.
  8. It implements ID overflow checking and reporting for GLSL shaders (more than 16M objects created).
  9. It fixes the long-standing bug that symbols mouse down/up was not recorded. It is recorded correctly now.

FIXMES

Please note that one FIXME is left in the lib/rust/ensogl/core/src/debug/stats.rs file and should be investigated in the future. The cause is not introduced by this PR and because it requires some additional investigation time, it's not covered here.

Testing

⚠️⚠️⚠️ As this code changes a lot of basic rendering / logical concepts, it has to be heavily tested before being merged to develop ⚠️⚠️⚠️

Checklist

Please include the following checklist in your PR:

  • The documentation has been updated if necessary.
  • All code conforms to the Scala, Java, and Rust style guides.
  • All code has been tested:
    • Unit tests have been written where possible.
    • If GUI codebase was changed: Enso GUI was tested when built using BOTH ./run dist and ./run watch.

@wdanilo wdanilo changed the title [WIP] Allowing EnsoGL mouse to interact with more than 4096 sprites at the same time. Allowing EnsoGL mouse to interact with more than 4096 sprites at the same time. Mar 25, 2022
@wdanilo wdanilo marked this pull request as ready for review March 25, 2022 04:56
@wdanilo wdanilo requested a review from 4e6 as a code owner March 25, 2022 04:56
// === UnsetParentOnDrop ===
// =========================

/// Wrapper that unsets parent of a display object when dropped.
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe there should be a special note that this does not imply "taking ownership" of the Instance (as they are clone_ref able, and there is no guarantee, that there are no other instances that will be alive and usable after this is dropped.

I'm mentioning this, as this at first glance looks like a pattern where some Rust object is put in a wrapper that makes guarantees about the inner object, but it is not. It is purely a utility to call a method on the inner object once this UnsetParentOnDrop sentinel is dropped.

/// Decode the [`PointerTarget`] from an RGBA value. If alpha is set to 0, the result will be
/// background. In case alpha is 255, the result will be decoded based on the first 3 bytes,
/// which allows for storing up to 16 581 375 unique IDs.
fn decode_from_rgba(v: Vector4<u32>) -> Result<Self, DecodeError> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Some additional description about how the encoding and decoding is done should be in this doc. Just to ensure it is clear in which order the R, G, and B values are used and that there is no trickery with the endianness.

}
}
}

#[test]
Copy link
Contributor

Choose a reason for hiding this comment

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

The new encoding should still be tested.

Copy link
Member Author

Choose a reason for hiding this comment

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

Comment from call: let's add docs to the decode line pointing to GLSL impl instead

let item_byte_size = T::item_gpu_byte_size() as i32;
let item_type = T::item_gl_enum().into();
let rows = T::rows() as i32;
let cols = T::cols() as i32;
let col_byte_size = item_byte_size * rows;
let stride = col_byte_size * cols;
let normalize = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this renamed? normalize seems much more explicit.

@@ -359,6 +359,14 @@ ops! { ReflectOps for Reflect
/// [`get_nested`] to learn more.
fn get_nested_object(target: &JsValue, keys: &[&str]) -> Result<Object, JsValue>;

/// Get the nested value of the provided object and cast it to [`Object`]. In case the
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be the same doc as below?

#[derive(Clone, CloneRef)]
#[clone_ref(bound = "T:CloneRef")]
pub struct EraseOnLastDrop<T: Erase> {
elem: T,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this need to store a second naked version of the element instead of just the Wrapped one?

Copy link
Member Author

Choose a reason for hiding this comment

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

Performance reasons, however, this code was not longer used and it was removed.

GlobalInstanceId(u32);
}

shared2! { GlobalInstanceIdProvider
Copy link
Contributor

Choose a reason for hiding this comment

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

This data could be static--because this is a truly global object (it would be wrong for more than one instance of it ever to exist), it feels weird to me that objects have fields identifying an instance of it.

Copy link
Member Author

Choose a reason for hiding this comment

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

It is possible for multiple GlobalInstanceProviders to exist – every scene contains one. Right now, we have one scene everywhere, but our World supports having multiple scenes. Each scene is connected to a separate WebGL canvas (and separate WebGL context), thus has separate symbol buffers and separate instance counters.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense. The names or at least the docs could be more clear that these are actually scene-scoped, to ensure we avoid confusion when we have multiple scenes.

@@ -1188,6 +1188,32 @@ impl<Host> Object<Host> for Any<Host> {



// =========================
Copy link
Contributor

@kazcw kazcw Mar 25, 2022

Choose a reason for hiding this comment

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

This UnsetParentOnDrop is cleaner than what it replaces, but the need for it it suggests a complex lifetime dynamic I don't fully understand.

Why is it that Sprite's display Instance needs to be unparented if the Sprite is dropped, but an Instance for any other object doesn't need this?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is an interesting question. You know, right now all things that you see on the screen are Sprites, so this works for everything we have. We could theoretically use this pattern in every object (like a node, that contains a lot of sprites), but I'm not yet sure what benefits it would give us. But this is an interesting potential change (definitely not part of this PR as it could be really big). CC @farmaazon, what do you think?

@wdanilo wdanilo merged commit 546c333 into develop Mar 29, 2022
@wdanilo wdanilo deleted the wip/wd/ensogl-object-count branch March 29, 2022 02:15
akavel added a commit that referenced this pull request Mar 29, 2022
sylwiabr pushed a commit that referenced this pull request Mar 29, 2022
wdanilo added a commit that referenced this pull request Mar 29, 2022
… sprites at the same time. (#3351)" (#3368)"

This reverts commit 7152e0d.
wdanilo added a commit that referenced this pull request Mar 29, 2022
… sprites at the same time. (#3351)" (#3368)"

This reverts commit 7152e0d.
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