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

[Question] Object limit: "Too large scene" #314

Closed
helmesjo opened this issue Feb 8, 2019 · 17 comments
Closed

[Question] Object limit: "Too large scene" #314

helmesjo opened this issue Feb 8, 2019 · 17 comments

Comments

@helmesjo
Copy link

helmesjo commented Feb 8, 2019

So far I've pretty much only dissected one of the examples and got it up and running in my context, hence the simplicity, but anyways:

Yesterday while rendering a bunch of stuff my little prototype spazzed out and spawned a lot of 3D objects. After a few seconds I was hit by this error:

"SceneGraph::Object::transformations(): too large scene" // max(uint16_t)
(there are two identical, so don't know which printed this)

I scimmed a little but didn't find any concrete info regarding this particular limit. No obvious comments around that area either.

My code basically boils down to this (vastly simplified & ignoring memory management for now 😇 ):

using Object3D = Magnum::SceneGraph::Object<Magnum::SceneGraph::MatrixTransformation3D>;
// constructor()
{
    drawableGroup_ = SceneGraph::DrawableGroup3D();
    sphereMesh_ = MeshTools::compile(Primitives::uvSphereSolid(16, 32));
    shader_ = Shaders::Phong{};
    shader_.setAmbientColor(0x111111_rgbf)
           .setSpecularColor(0x330000_rgbf)
           .setLightPosition({ 10.0f, 15.0f, 5.0f });
}

// create_object()
{
    auto color = Magnum::Color4::red();
    auto object = new Object3D();
    new ColoredDrawable(*object, shader_, sphereMesh_, color, drawableGroup_);
    return object;
}

Given that the roof is not extraordinarily high (though I'm not planning on reaching it anyways), was I doing something obviously wrong when I hit that limit (besides the program spazzing out, that is)? Some flyweight approach I'm not utilizing?

Thanks!

@mosra
Copy link
Owner

mosra commented Feb 8, 2019

Hi, wow, today is busy :D

There's a limit on 65k objects passed to one camera->draw() (or less than that in some very pathological scene organization schemes), it's because there's a tree traversal algorithm that's calculating transformation of all the objects in a way that avoids duplicate calculations and it's doing some counting there. I think. Too much time since I wrote this code, so I don't remember what is it exactly.

Are you actually rendering a scene with 65k objects? Or do you have some special topology that's causing this (deep hierarchies etc.)?

If you really have that many objects, one way to circumvent this limitation is to split the objects into multiple drawable groups and then draw each separately. That should make it fit back under this limit. Looking at the code, it might also be possible to increase this limit to 24bit (16M objects), but so far to me it seems that this assertion is only pointing to a problem somewhere else :)

@mosra mosra added this to TODO in Project management via automation Feb 8, 2019
@helmesjo
Copy link
Author

helmesjo commented Feb 8, 2019

Are you actually rendering a scene with 65k objects? Or do you have some special topology that's causing this (deep hierarchies etc.)?

Oh, I totally agree that 65k objects indicate some issue in the client code! 😅 It was mostly me being curious about "why the actual limit" and what would happen if it was "infinite" (besides eventual rendering slowdowns). Also, in case my application goes haywire again I don't want the crash, so for now I use std::numeric_limits<uint16_t>::max() to limit my object pool (and perhaps show a diagnostic on-screen message, or something...). Only problem is that this number, in client context, feels extremely "magic". 😇
Given this, or the fact that 0xFFFFu is repeated 7 times inside Object.cpp, could it be worth considering having this limit as an accessible constant instead of a hardcoded value?

But alright, the limit is actually contained to a single camera->draw(), that makes it easy to work with!

Thank you!

@helmesjo helmesjo closed this as completed Feb 8, 2019
Project management automation moved this from TODO to Done Feb 8, 2019
@mosra
Copy link
Owner

mosra commented Feb 8, 2019

could it be worth considering having this limit as an accessible constant instead of a hardcoded value?

The only way to be sure is to nuke this whole thing from orbit 💥 😆

In all seriousness, the SceneGraph library, as it currently is, has a few (mostly historical) design decisions that make it less efficient than it could be, not to mention forcing users to the naked new. On the roadmap I have an ECS-style rewrite, but that obviously can't be done in a way that's backwards-compatible with the current API. Which means I'd rather invest time into the new thing and keep the current code in a maintenance mode -- and, as they say, if it ain't broken, don't fix it :)

@helmesjo
Copy link
Author

helmesjo commented Feb 8, 2019

On the roadmap I have an ECS-style rewrite, but that obviously can't be done in a way that's backwards-compatible with the current API. Which means I'd rather invest time into the new thing and keep the current code in a maintenance mode -- and, as they say, if it ain't broken, don't fix it :)

Oh, I really like this! And I agree, backwards-compatibility is always a... 😏

@OvermindDL1
Copy link

As for ECS, I use https://github.com/skypjack/entt with magnum in a playground app and it works really well. I've tested every ECS I could get my hands on in a large variety of languages and this one was so much significantly faster than everything else I tried except for one in Rust that was about on par. So if you wanted to use a library instead of making your own... It's already very well optimized and quite well used.

@mosra
Copy link
Owner

mosra commented Feb 8, 2019

Yup, entt comes up pretty often, everybody seems to be using it :)

Re using a 3rd party lib vs making my own -- as with everything, I am giving the users the freedom to use whatever they please, be it a math lib or a scene abstraction. So using entt instead of the builtin SceneGraph is a completely valid thing to do, and is encouraged -- and if there's anything that could be done in the style of other integration libs to make the usage more comfortable, let me know.

But on the other hand I want to keep my options open -- if, at any point, it would make sense to implement a "magnum native" ECS solution that takes advantage of the tighter integration for increased performance or ease of use, then I'm all for that.

@mosra mosra added this to the 2019.0b milestone Feb 8, 2019
@skypjack
Copy link

skypjack commented Feb 8, 2019

@mosra then be prepared to put all your efforts to develop it! During the weekend I'm introducing groups (and getting rid of persistent views) in EnTT and it will give it another boost of performance!! 😉

Disclaimer: I saw this issue by chance, but I didn't resist the urge of commenting! :-)

@mosra
Copy link
Owner

mosra commented Feb 8, 2019

Always putting in all the efforts, of course, all of them! 😎

Good, then I don't need to worry about having subpar solutions. If anyone is unhappy with the state of things, I'll be directing them to entt :)

@alanjfs
Copy link
Contributor

alanjfs commented Sep 30, 2019

Speaking of which, I'm currently looking into using EnTT with Magnum - to render just a simple plane with a box on it for a start. To e.g. replicate the Model Viewer tutorial using EnTT in place of the SceneGraph namespace. But am having some trouble grokking basic concepts and coming up with a minimal working version.. :(

For example, do I still use Mesh::draw? It seems counter to what ECS is about, whereby a System would handle drawing of a Mesh as a Component, rather than having the Mesh provide drawing capabilities by itself.

@OvermindDL1 Would it be possible to paste a minimal example of how you got along, for newbies such as myself to get on their way?

@OvermindDL1
Copy link

@alanjfs Sad to say but Rust has taken over all my playground code for the past half year now. ^.^;

It wasn't hard though. As for Mesh::draw you can still use that, all depends on your component structure and where the data comes from and how it needs to be handled. If you have a repo to point to I'm sure someone here could give pointers if you have questions about some specific code there.

@mosra
Copy link
Owner

mosra commented Sep 30, 2019

@alanjfs For a more data-oriented way, depending on what you render, it would be either instanced drawing (and then one Mesh::draw() renders more of them, each in a different position etc.), or using the list-taking Mesh::draw(), to draw multiple meshes in a single call. (Note that the builtin shaders currently don't support instanced drawing, that's something I'm planning to do once I put 2019.09 out... so you'd need to write your own instanced shaders.)

I don't think Mesh::draw() is counter to ECS, Drawable::draw() sure is, but you can have a list of Mesh instances, list of shaders, set uniform data and call draw() on each in a tight loop.

@alanjfs
Copy link
Contributor

alanjfs commented Sep 30, 2019

Thanks for the replies!

I don't think Mesh::draw() is counter to ECS, Drawable::draw() sure is, but you can have a list of Mesh instances, list of shaders, set uniform data and call draw() on each in a tight loop.

That's true, but what I'm looking for is to more easily reason about my data and function, preferably in isolation. That is, I'd like for meshes to store vertices and indices, for shaders to contain uniforms and textures, and for a System to make use of this data and actually produce an image.

I might be thinking of it the wrong way, which is partly why I'm out looking for more examples. I think what I'm looking for is something along these lines:

    registry.view<Mesh, Shader>().each([dt](auto &mesh, auto &shader)
    {
        // do drawing of mesh using shader
    });

Where meshes and shaders know nothing of the renderer, but the renderer knows about both of these and how they interact. Same goes for physics and audio etc.

However this is also where I get lost and need an example, even if it's just a hypothetical loop to illustrate where data resides and who does what with it.

@OvermindDL1
Copy link

OvermindDL1 commented Sep 30, 2019

The EnTT code has a set of examples that show that pattern to start with, and a couple other open source games, the machine part then is translating those to the right magnum calls then building up.

@alanjfs
Copy link
Contributor

alanjfs commented Oct 2, 2019

Hi again,

At risk of taking this issue too far off-topic, let me know if the Google Groups or a new issue is a better place for continuing this discussion, I've prepared an example to try and shed as bright of a light on the problem at hand as possible.

I figure we could use this as a starting point for further discussion; for you to get a sense of what I've got in mind and whether it's reasonable. I've gathered current questions near the bottom and figure I'd update this gist as we gain a more clear image of how to move forwards.

Let me know what you think!

@mosra
Copy link
Owner

mosra commented Oct 3, 2019

Let's move the conversation to the gist, then :) -- btw., I'm impressed in how much effort you put into that 👍 Coincidentally, the mid-term roadmap for Magnum is about creating a Vulkan-based renderer (and going very DoD with it), so we could theoretically join the efforts there :) Most of the issues you describe stem from how GL is designed (and there it's really hard to treat it in a pure DoD way, unfortunately, due to its everything-is-a-global-state nature, and converting it to a "less globalized" interface would make the now-quite-thin wrapper become quite massive). I'll give a more detailed reply there, as soon as my schedule allows that (being busy with release preparation right now).

Also, feel free to pop in on Gitter -- https://gitter.im/mosra/magnum -- that's probably the most realtime way of conversation. Simply log in with your GitHub account.

@skypjack
Copy link

skypjack commented Oct 3, 2019

@alanjfs @mosra

If it's not a problem for you, I'd join the discussion as well.
I'm really interested in a real world example of integration between magnum and EnTT, many people asked me about this actually.
If you plan to keep using EnTT, I'm willing to participate and contribute for what I can do. 👍

@alanjfs
Copy link
Contributor

alanjfs commented Oct 3, 2019

I'm impressed in how much effort you put into that

Thanks for noticing; it took way longer than I had anticipated. ;D And @skypjack you are most welcome to join!

Let's move the conversation to the gist, then :)

It used to be the case that gists wouldn't let commenters know when there was a reply, but I've noticed that new "Subscribe" button at the top of the gist that I'm keen to try out. So let's see! I'll be banging my head against this today as well and will post my discoveries and new questions in that gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests

5 participants