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:
- It would remove the need to have vestigial intersection methods for
Surface
subclasses not intended for rendering, e.g. inMesh
,SurfaceGroup
, etc. - It would allow moving some stuff from surface
.h
files in the corresponding.cc
file, making the header files simpler and easier to read. - It follows similar splits in e.g. lights, with
Light::Sampler
.
Some disadvantages:
- 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)
- The extra bit of indirection might make things slower (though maybe not much in comparison to other intersection overhead)
- Some more code would be needed, e.g. every subclasses of
Surface
would need to overrideSurface::add_to_space
, whereas now many just inheritPrimitive::add_to_space
. - 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 likespace_builder.record_deleter (obj)
, whererecord_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