snogglethorpe / snogray

Snogray renderer

Home Page:http://www.nongnu.org/snogray

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Split Surface class into "scene description" and "rendering" classes

snogglethorpe opened this issue · comments

For instance, render-time surfaces could be a subclass of Surface::Renderable, and all the current Surface intersection methods would be moved there.

There's already something of a mismatch between the two concepts, with container groups like Mesh and SurfaceGroup not really being renderable (e.g. you shouldn't put them in a Space, as the result will be really slow), and classes such as Mesh::Triangle really being intended only for rendering.

It would basically be the duty of Surface::add_to_space to create a new Surface::Renderable subclasses and add that instead. Probably Space subclasses would then be responsible for deallocating them (but this is sort of annoying... see below).

This would have some advantages:

  1. It would remove the need to have vestigial intersection methods for Surface subclasses not intended for rendering, e.g. in Mesh, SurfaceGroup, etc.
  2. It would allow moving some stuff from surface .h files in the corresponding .cc file, making the header files simpler and easier to read.
  3. It follows similar splits in e.g. lights, with Light::Sampler.

Some disadvantages:

  1. A bit more memory use / fragmentation (although most surface "rendering" classes would be fairly small, often just a vtable and a pointer to the scene surface)
  2. The extra bit of indirection might make things slower (though maybe not much in comparison to other intersection overhead)
  3. Some more code would be needed, e.g. every subclasses of Surface would need to override Surface::add_to_space, whereas now many just inherit Primitive::add_to_space.
  4. Annoying Allocation Issues; see below.

Special care needs to be taken with Mesh surfaces, as they would need a Surface::Renderable for every triangle. The overall memory usage may be about the same though, as we could just get rid of the vtable and mesh back-pointer in Mesh::Triangle (putting them in Mesh::TriangleRenderable or whatever instead).

Annoying Allocation Issues

One annoying wrinkle is memory management of render-time objects. Who owns them, and how do they get deallocated without undue overhead/annoyance?

  • We can't just require every rendering class be allocated with new, as this isn't practical in important cases like meshes; so just having the space call delete on everything won't work.
  • In some cases (like maybe meshes) it's probably desirable to simply not allocate/deallocate at all, and just make the render-time object part of the scene-description object.
  • However in some cases, like instances, we do want to allocate a unique object for rendering and have it somehow deallocated when the space goes away.
  • Using a bulk-deallocation arena would work for some cases, but not others, because destructors won't be called. For instance, the sub-space referenced by Instance render-time objects needs to be deallocated.
  • We could maybe just have a vector of object pointers in spaces, and call delete on all of them, but we need to know at least some common base-class type of the objects so that the virtual destructor gets called properly, and it's not clear what class that should be. Using Subclass::Renderable probably won't work because we might want to e.g. allocate vectors of them instead (when the overhead of doing them individually is too high, e.g. for a meshlike object).
  • We could have a vector of memory-pointer + deallocator tuples (such as used by std::shared_ptr), but basically this requires the caller to specifically arrange for deallocation, we can't use nice syntax such as placement new. E.g. the allocating code would at least need to do something like space_builder.record_deleter (obj), where record_deleter is template function that constructs a proper typed deleter.

All of this is technically solvable, e.g. using the vector of memory-pointer + deallocator tuples, but it's ... ugly, and seems to require excessive code and be excessively fragile.

Multiple inheritance

One way to reduce overhead/complexity for the many surface classes where a distinct renderable object isn't necessary would be to just use multiple inheritance, making the surface class a subclass of both Surface and Surface::Renderable. Besides the overhead of having two vtables, this would basically allow using the existing code without much change, while still making it possible to have seperate renderable objects for cases where that is desirable.

Is multiple inheritance considered too ugly?

Performance

An initial cut at this suggest that it can be done pretty easily, with the only real overhead being the extra vtable pointer in some surfaces (plus whatever extra overhead comes from calling through a thunk for some methods). This overhead shouldn't exist in the case of Mesh::Triangle, however, as it is purely a subclass of Surface::Renderable, and doesn't use multiple-inheritance. As the great bulk of surfaces in big scenes tend to be meshes, this suggests there should be little performance penalty for doing this split.

Timings show a fairly consistent slowdown of about 1%... However it's not really clear where this comes from, as it occurs even when rendering scenes where essentially all the surfaces are meshes, and in that case, it doesn't seem like there should be any slowdown at all... maybe it's an unrelated artifact of e.g. alignment differences?

  • Try to analyze slowdown and see where the difference actually lies (using perf maybe?)

Closed by 00bd2cf