1
\$\begingroup\$

I'm building my own pet engine and after struggling with how to handle resource ownership for a while, I came across this question and specifically Josh and Sean's responses (as well as Sean's blog on shared_ptr), which really made a whole lot of sense to me. So I went ahead and implemented my own similar system, and so far it's working great except for one minor snag - what do I do when I need to access a resource of a derived type when the handle only refers to the base type?

Say for instance I have a Material class that keeps a list of handles to textures it needs for rendering:

class Material
{
public:
    void SetTexture(int textureSlot, const Handle<Texture>& texture);
private:
    SomeContainer<Handle<Texture>> m_textures;
};

Then I grab a handle to a texture from my texture library, set it to the material, and everything works great:

Handle<Texture> fnorbTexture = TextureLibary::GetResource("fnorb.tga");
fnorbMaterial->SetTexture(0, fnorbTexture); // fnorbtastic!

But what happens now when I have a RenderTarget class that derives from Texture?

class GraphicsSystem
{
public:
    void SetRenderTarget(int slot, Handle<RenderTarget> renderTarg);
    // Other stuff
};

Handle<Texture> renderTarg = 
    TextureLibrary::PleaseGiveMeARenderTarget(width, height, flags);

// ERROR: Can't cast Handle<Texture> to Handle<RenderTarget>!
graphicsSystem->SetRenderTarget(0, renderTarg); 
// ... Render stuff to render target ...

// Set the current render target back to the standard frame buffer
graphicsSystem->SetRenderTarget(0, kNullHandle);
// Now use the render target as a texture
superAwesomeBlorgomaticPostEffectMaterial->SetTexture(0, renderTarg); // OK!

Or maybe:

Handle<RenderTarget> renderTarg = 
    RenderTargetLibrary::PleaseGiveMeARenderTarget(width, height, flags);

graphicsSystem->SetRenderTarget(0, renderTarg); // OK!
// ... Render stuff to render target ...

// Set the current render target back to the standard frame buffer
graphicsSystem->SetRenderTarget(0, kNullHandle);
// Now use the render target as a texture
// ERROR: Can't cast Handle<RenderTarget> to Handle<Texture>!
superAwesomeBlorgomaticPostEffectMaterial->SetTexture(0, renderTarg);

Possible solutions, and my thoughts on each:

Borrow a render target resource as a Handle<RenderTarget>, and in Material::SetTexture(), pass it using reinterpret_cast<const Handle<Texture>&>.

  • Compiles and probably works, but looks ugly and I feel dirty afterwards.

Or: Borrow a render target resource as a Handle<Texture>, and rewrite SetRenderTarget to take a const Handle<Texture>& instead. Then:

  • When I dereference the handle, use dynamic_cast to cast the pointer to a RenderTarget* as necessary.

    • RTTI is "Bad" TM, which is to say that it very often gets disabled (along with C++ exceptions, etc.) in game applications due to unnecessary overhead, particularly on consoles. Plus resorting to dynamic_cast feels like I'm "doing it wrong," architecturally speaking.
  • I know it's a RenderTarget; it was when I created it, and if it weren't, I wouldn't be passing it to SetRenderTarget in the first place! reinterpret_cast!

    • Do I REALLY know? If I'm wrong, then I'm casting something else to something it's not, and I can't even check if it's nullptr if I'm wrong. Also, again, I'd like to avoid casts if possible. But I don't entirely disagree with the logic here...
  • Implement a lightweight homegrown alternative to RTTI, and use that instead.

    • I've seen this done in one or two engines, but it sounds like some fairly heavy-duty wheel-reinventing...
  • Don't derive RenderTarget from Texture; instead, add a flag that says whether a Texture is a RenderTarget or not. If it's not, then SetRenderTarget will simply fail.

    • Potentially lots of excess baggage added to the Texture class. (What's another pointer to an ID3D11RenderTargetView among friends?) Doesn't sound like it scales very well.
  • Derive RenderTarget from Texture, but add a virtual method to Texture called GetRenderTarget() that normally returns nullptr but is overridden in RenderTarget to return this. Similar to the flag method above, but without the excess baggage.

    • Feels like a poor man's dynamic_cast. Again, doesn't scale well (add a new method for each derived class that needs it?). But if my RenderTarget scenario is maybe the only case I actually need something like this, I'm beginning to feel like maybe this solution isn't so bad...

Or maybe: Add another SetTexture() method to Material that takes a const Handle<RenderTarget>& instead?

  • Again, scaleability. And this almost certainly complicates the implementation...

Or finally: Somehow make a handle to a derived type automagically compatible with a handle to its base type (without say, deriving Handle<RenderTarget> from Handle<Texture>)? I feel like theoretically this might be possible, but I can't seem to wrap my head around it at the moment. Template metaprogramming magic?

Wow, this got a lot longer than I thought it would. Is there a better solution that I'm just missing? Or is one of the above a good enough answer? Feel free to tear apart my arguments if they're flawed. I feel like I'm vastly overthinking this...

\$\endgroup\$
3
\$\begingroup\$

But what happens now when I have a RenderTarget class that derives from Texture?

I highlighted the problem with your design.

A RenderTarget is not a Texture. It has a Texture. The RenderTarget is just a view (either literally as in ID3D11RenderTargetView in Direct3D or as an FBO in OpenGL) into a Texture. There's some potential issues with flags when creating a texture in some APIs and whether it can later be wrapped as a render target, but that's a data issue, not a type system issue.

You can use code like:

Handle<RenderTarget> rt = ...;
Handle<Texture> tex = rt->GetBackingTexture();

The RenderTarget class would have something like this at a minimum:

class RenderTarget : public Resource {
  Handle<Texture> m_BAckingTexture;
  CComPtr<ID3D11RenderTargetView> m_RenderTargetView;

public:
  const Handle<Texture>& GetBackingTexture() const { return m_BackingTexture; }
};

Now you can create RenderTarget resources from any compatible Texture resource and you can retrieve the original Texture from any RenderTarget. There is no need to "convert" one to the other because they are entirely separate objects.

As (almost) always, prefer aggregation over inheritance. has-a, not is-a.

\$\endgroup\$
  • 1
    \$\begingroup\$ Wow! Of course! Thanks, Sean; your answer makes so much sense I feel almost silly for not realizing it in the first place (and silly for posting such a huge question). Once again, you are the man, Sean! Sorry I don't have the reputation to upvote your answer. I think I may have been temporarily blinded by my experience with Unity, where RenderTexture derives from Texture... \$\endgroup\$ – PJdev Feb 19 '14 at 18:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.