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

Node tree rewrite #155

Merged
merged 8 commits into from
Jan 4, 2018
Merged

Node tree rewrite #155

merged 8 commits into from
Jan 4, 2018

Conversation

kvark
Copy link
Collaborator

@kvark kvark commented Jan 3, 2018

Fixes #44

Core of the change is switching relationships from "child->parent" to "parent->first_child->sibling", which gives us the following benefits:

  • clean children lifetimes, as expected by users
    • no need to store all the meshes returned by gltf/obj loaders: perhaps, we can follow-up with a PR that allows querying meshes by name from their parents, so that we don't even return all the meshes directly in the loader API
  • nice Group::add(&something), like Three-js does
  • make Scene truly unique and node-less
  • no need for unique scene IDs
    - technically, objects can now be present in multiple scenes at once
  • immutable node data upon traversal (!):
    • we just compute the world visibility/transform on the fly
    • thus, no need for a separate update_transforms step
    • uses pretty iterators ;)
    • as a downside, not as straightforward to reflect the world info to the user (see TODO)

Has other changes making things nicer:

  • refactored frame render code
  • no &mut self on objects
  • idiomatic hub indexing
  • safer group/camera/text/audio spawning (internal)
  • more robust message handling (panic on unprocessed/unexpected messages)
  • can debug print the Operation

TODO:

  • check/fix docs
  • compute world transform/visibility for the user if needed (is it needed?)
  • provide API to detach from parent
  • reviews!
  • profile the cost of recomputing the world transforms

@kvark kvark requested review from vitvakatu and alteous January 3, 2018 15:57
Copy link
Member

@vitvakatu vitvakatu left a comment

Choose a reason for hiding this comment

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

That's soo cool. Can't wait we will finish it and merge.

src/hub.rs Outdated
for (child_ptr, sibling) in deferred_sibling_updates {
let child = &mut self.nodes[&child_ptr];
if child.next_sibling.is_some() {
error!("Attaching a child that still has an old parent, discarding siblings");
Copy link
Member

@vitvakatu vitvakatu Jan 3, 2018

Choose a reason for hiding this comment

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

How can we change parent now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh right, I forgot about this :) Now added to TODO list for this PR.
Basically, we can either merge the old siblings with the new ones (like Scene::add does), or require the user to call Group::remove(&item) before adding to another group. Either way, the remove needs to be added.

src/hub.rs Outdated
Operation::AddChild(child_ptr) => match node.sub_node {
SubNode::Group { ref mut first_child } => {
let sibling = mem::replace(first_child, Some(child_ptr.clone()));
deferred_sibling_updates.push((child_ptr, sibling));
Copy link
Member

Choose a reason for hiding this comment

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

Interesting.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

borrow checker...

src/hub.rs Outdated
SubNode::Visual(_, ref mut gpu_data) => gpu_data.pending = Some(mesh.dynamic.clone()),
_ => unreachable!(),
}
}

pub(crate) fn walk(&self, base: &Option<NodePointer>) -> TreeWalker {
Copy link
Member

Choose a reason for hiding this comment

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

👍

src/node.rs Outdated
/// The same as `visible`, used internally.
pub world_visible: bool,
/// Relative to parent transform.
pub transform: Transform,
/// Material in case this `Node` has it.
Copy link
Member

Choose a reason for hiding this comment

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

Was this ever useful?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

wait, why is Material here? O_O

Copy link
Member

Choose a reason for hiding this comment

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

Why not?!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, this is user-exposed node, not the internal one.
Yeah, let it be. The whole reflection thing (with sync) I'd consider non-idiomatic, so we shouldn't be too eager to optimize it.

};
let mx_view = Matrix4::from(w.inverse_transform().unwrap());
let node = &hub[&camera];
let world_transform = node.transform; //TODO!!!
Copy link
Member

Choose a reason for hiding this comment

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

What's this for?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TODO: compute the world transform for the camera, which is expected here

src/scene.rs Outdated
use std::sync::MutexGuard;

/// Unique identifier for a scene.
pub type Uid = usize;
Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link
Member

@alteous alteous left a comment

Choose a reason for hiding this comment

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

It feels like we're going in the right direction here. The reduced verbosity from all the muts and set_parent calls is a breath of fresh air.

@kvark
Copy link
Collaborator Author

kvark commented Jan 4, 2018

Alright, I rebased the code and implemented the missing bits.
Only the profiling is left, plus maybe a few more looks, esp in the new code.
Note: unfortunately, there is a bit of duplication atm between Group reacting to events and Scene implementation. I'm sure we can make it nicer.

@kvark
Copy link
Collaborator Author

kvark commented Jan 4, 2018

Here are the profiling results (note: I only used callgrind_annotate, so don't have that much insight at the moment):

  1. descend does show up near the top of the profile
  2. cause likely being large amount of copying (and a bit of stack reallocation). This should be partially solved by future improvements in Rust itself, some possible workarounds from our side, or caching the transforms at least.
  3. group examples with 20K instances runs OK on my U-series CPU, so I think we are fine for now.

Looks like I addressed all the action items from my side. Waiting for more reviews now (@alteous , @vitvakatu ).

@kvark
Copy link
Collaborator Author

kvark commented Jan 4, 2018

I believe in the long term our architecture should work like this:

  • Renderer only walks the tree once, for each node
    • figures out what render bins does an object fall into (e.g.: main screen, shadow for light X, off-screen buffer, etc)
    • builds the GPU instance blocks for each bin (separately) and appends them to lists to be directly uploaded to GPU constant buffer memory
    • writes down GPU commands in the appropriate command buffers

TL;DR: there is no strong reason for Renderer to walk the tree multiple times. We can make rendering much faster on the CPU side, and tree traversal is not (going to be) a bottleneck.

const SPEEDS: [f32; 6] = [0.7, -1.0, 1.3, -1.6, 1.9, -2.2];
struct LevelDesc {
color: three::Color,
speed: f32,
Copy link
Member

Choose a reason for hiding this comment

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

What is the unit of this? I assume radians/second?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

correct

walker
}

pub(crate) fn walk(&self, base: &Option<NodePointer>) -> TreeWalker {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Perhaps walk_visible?

Copy link
Member

Choose a reason for hiding this comment

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

As in froggy, it's better to use shorter names.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it's for internal use only anyhow. Most of the time we are only concerned about visible nodes

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I remember your strategy 😄
alt

Copy link
Member

@alteous alteous left a comment

Choose a reason for hiding this comment

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

LGTM.

Copy link
Member

@vitvakatu vitvakatu left a comment

Choose a reason for hiding this comment

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

We're close

@@ -28,7 +31,7 @@ cgmath = { version = "0.15", features = ["mint"] }
derivative = "1.0"
froggy = "0.4.4"
genmesh = "0.5"
gfx = "0.16"
gfx = "0.16.3"
Copy link
Member

Choose a reason for hiding this comment

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

Why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because your instancing changes require it

Copy link
Member

Choose a reason for hiding this comment

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

I'm not a fan of strict version rules in Cargo. It will use the latest available version anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think your position is in the minority within the community then. Latest will be used on fresh checkout, if we don't check in Cargo.lock. But people who already have Cargo.lock will get breakage if you don't write the exact version in the Cargo.toml. Another case is - some crate that depends on you may say gfx = "=0.16.2" and it will try to build but will fail even after cargo update.

TL;DR: Cargo versioning rules are here to stay and be respected

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also, there is a proposal (which I supported) about making cargo publish checking out all the minimal compatible versions (as opposed to latest). Think about it ;)

Copy link
Member

Choose a reason for hiding this comment

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

Okay, you convicted me


let mut mline = {
// test removal from scene
win.scene.remove(&mcyl);
Copy link
Member

Choose a reason for hiding this comment

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

Is it supposed to be here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes - it's a small test for object removal. May not be the best place, but better than nothing

walker
}

pub(crate) fn walk(&self, base: &Option<NodePointer>) -> TreeWalker {
Copy link
Member

Choose a reason for hiding this comment

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

As in froggy, it's better to use shorter names.

Copy link
Member

@vitvakatu vitvakatu left a comment

Choose a reason for hiding this comment

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

Ready to go

@vitvakatu
Copy link
Member

bors r+

bors bot added a commit that referenced this pull request Jan 4, 2018
155: Node tree rewrite r=vitvakatu a=kvark

Fixes #44

Core of the change is switching relationships from "child->parent" to "parent->first_child->sibling", which gives us the following benefits:
  - clean children lifetimes, as expected by users
    - no need to store all the meshes returned by gltf/obj loaders: perhaps, we can follow-up with a PR that allows querying meshes by name from their parents, so that we don't even return all the meshes directly in the loader API
  - nice `Group::add(&something)`, like Three-js does
  - make `Scene` truly unique and node-less
  - no need for unique scene IDs
    ~~- technically, objects can now be present in multiple scenes at once~~
  - immutable node data upon traversal (!):
    - we just compute the world visibility/transform on the fly
    - thus, no need for a separate `update_transforms` step
    - uses pretty iterators ;)
    - as a downside, not as straightforward to reflect the world info to the user (see TODO)

Has other changes making things nicer:
  - refactored frame render code
  - no `&mut self` on objects
  - idiomatic hub indexing
  - safer group/camera/text/audio spawning (internal)
  - more robust message handling (panic on unprocessed/unexpected messages)
  - can debug print the `Operation`

TODO:
- [x] check/fix docs
- [x] compute world transform/visibility for the user if needed (is it needed?)
- [x] provide API to detach from parent
- [x] reviews!
- [x] profile the cost of recomputing the world transforms
@kvark
Copy link
Collaborator Author

kvark commented Jan 4, 2018

bors r-
Almost forgot to fix the text!!!

@bors
Copy link
Contributor

bors bot commented Jan 4, 2018

Canceled

@kvark
Copy link
Collaborator Author

kvark commented Jan 4, 2018

bors r=vitvakatu

bors bot added a commit that referenced this pull request Jan 4, 2018
155: Node tree rewrite r=vitvakatu a=kvark

Fixes #44

Core of the change is switching relationships from "child->parent" to "parent->first_child->sibling", which gives us the following benefits:
  - clean children lifetimes, as expected by users
    - no need to store all the meshes returned by gltf/obj loaders: perhaps, we can follow-up with a PR that allows querying meshes by name from their parents, so that we don't even return all the meshes directly in the loader API
  - nice `Group::add(&something)`, like Three-js does
  - make `Scene` truly unique and node-less
  - no need for unique scene IDs
    ~~- technically, objects can now be present in multiple scenes at once~~
  - immutable node data upon traversal (!):
    - we just compute the world visibility/transform on the fly
    - thus, no need for a separate `update_transforms` step
    - uses pretty iterators ;)
    - as a downside, not as straightforward to reflect the world info to the user (see TODO)

Has other changes making things nicer:
  - refactored frame render code
  - no `&mut self` on objects
  - idiomatic hub indexing
  - safer group/camera/text/audio spawning (internal)
  - more robust message handling (panic on unprocessed/unexpected messages)
  - can debug print the `Operation`

TODO:
- [x] check/fix docs
- [x] compute world transform/visibility for the user if needed (is it needed?)
- [x] provide API to detach from parent
- [x] reviews!
- [x] profile the cost of recomputing the world transforms
@bors
Copy link
Contributor

bors bot commented Jan 4, 2018

Build succeeded

@bors bors bot merged commit caf1afd into master Jan 4, 2018
@kvark kvark deleted the parent branch January 4, 2018 19:54
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.

Node children lifetime
3 participants