Fitting a Renderer into the current Framework

So I've been thinking about this for a bit and trying to work out how a renderer (for instance openGL) would fit into the current framework. In the comments, Casey writes the following TODO (handmade_platform.h)

1
// TODO(casey): In the future, rendering _specifically_ will become a three-tiered abstraction!!!

I was just curious if anyone (or Casey) could help us out in understanding this a little bit more before we tackle it a ways down the road? Would it be another DLL layer that gets loaded like the game? or...would it be something that gets compiled into the game layer itself? Also, how would it fit into the memory scheme that we're using? Would we divvy up some of the transient memory block and pass it to the renderer, or something else entirely?

I appreciate any info anyone can offer, as this has been confusing me for a bit.

Thanks!
I suppose HH renderer will be mostly about sorting sprites/entities and pasting them into framebuffer, nothing terribly complicated.
And also some font/text support in the renderer.

May be interesting to see how Casey handles animation/physics, will it be part of the renderer unit or not.

And yes, I think it will be compiled into game layer.
Actually, by a three-tiered abstraction, I think Casey means there'll be three layers in the renderer. And I guess it's going to be something along these lines:
  1. Layer 0: OpenGL initialization/context creation in the platform layer (Win32) and the implementation of a custom API with functions to do the most basic operations: create buffers, shaders, textures, bind resources and so on. Access to this API can be supplied to the game via a set of function pointers, for example.
  2. Layer1: GPU resource management: sprite loading/"atlasing"/rendering, background loading/rendering, effects simulation/rendering (e.g. particle effects), etc...
  3. Layer2: game specific: render queues (for batch sprite submission and sorting), resource loading/unloading into/from the Layer2 as needed by the game (e.g. enter a dungeon: load dungeon enemies, background, etc...), effects triggering/stopping (e.g.add new particle effect for special sword attack -> remove effect after attack)

What's more interesting in an architecture like this is what I've called the Layer0: it allows every system to provide their own graphics API (doesn't need to be OpenGL, and even OpenGL is loaded differently in each OS) while keeping the rest of the rendering engine API agnostic.

Edited by Marc Costa on
marcc

What's more interesting in an architecture like this is what I've called the Layer0: it allows every system to provide their own graphics API (doesn't need to be OpenGL, and even OpenGL is loaded differently in each OS) while keeping the rest of the rendering engine API agnostic.


That really is interesting question, OpenGL is quite portable so most of the code can be shared between systems but on the other hand we can have different graphics API: software rendering, OpenGL, Direct3D.
marcc
Actually, by a three-tiered abstraction, I think Casey means there'll be three layers in the renderer. And I guess it's going to be something along these lines:


Huh. I had a slightly different idea of what Casey meant by that. Resource management seems like something that is only tangential to the renderer. I thought more something like this:

  1. Platform: handles initialization of a window and everything else the renderer needs, like a context for OpenGL or memory for a software renderer's framebuffer.

  2. Renderer: Translates the render instructions from the game (see below) into draw calls to the underlying render API (OpenGL, software renderer or even DirectX).

  3. Game: For each frame, creates a list of renderer-agnostic draw instructions (mostly just "draw this sprite at position X,Y,Z"), to be consumed by the renderer, either at the end of frame or in chunks during the game update.


This way, you can switch out the renderer when necessary without touching the game code, and the renderer doesn't need to handle the nasty platform specific initialization stuff itself. It also allows you to reuse a renderer originally written for another platform when implementing a new platform layer. Even better, for new platforms, you can always start out with the simple software renderer that doesn't need much initialization. Once you got that working, you can start working on initializing OpenGL or another renderer.

Edited by Benjamin Kloster on
Nimbal

Huh. I had a slightly different idea of what Casey meant by that. Resource management seems like something that is only tangential to the renderer. I thought more something like this:


This is exactly how I was thinking it would be done as far as the separation of responsibilities. But how would this then fit into the framework as it is now? Would we compile the renderer into the platform compilation unit, would it be its own thing, or would it be part of the game dll (as the rendering code currently is)?

Also, for something like openGL, there are things that need to be dynamically allocated as per the API (sometimes with openGL calls). How would that fit into the memory management system that Casey is creating?

Enjoying the discussion...thanks guys/girls :)
I've actually implemented this for OpenGL. It was clear that the renderer could not be part of the platform layer, as I wanted to be able to hot-reload the render code for tweaking. At first, I had it in a separate DLL, and the platform layer reloaded it just like the game DLL. It worked, but I wasn't happy with it. This structure required the game update to complete before the first draw call of a frame. I believe Casey mentioned sometime that this is suboptimal for today's graphics cards (I think it was in a Q&A when discussing why he called his function gameUpdateAndRender instead of just gameUpdate and a separate render). It also required the platform layer to allocate additional memory just for the renderer, just like for the game (not that much of a deal, though).

I have since pulled the render code into the game DLL and call my render method whenever a reasonable sized chunk of draw instructions is available. This way, the GPU can get to work on the first draw calls while the game state is still updating. The renderer's memory is also assigned by the game from its own memory. Switching renderers requires a rebuild, but if I ever need configurable renderers at startup or even runtime, it shouldn't be too hard to implement.
The method you chose was the one most comfortable to me when thinking about this as well. But I wasn't sure that comfortable meant "correct". But reading over your thoughts I think this structure makes the most sense. Although, would you say the renderer you wrote and the game dll are tightly coupled? How difficult would it be do replace it with a direct3d renderer? I mean, I guess you could always abstract the code in any way you wish. My mind always goes to oop methods of handling this, but I'd be interested to see how Casey handles it with his style.

I'm writing an openGL renderer as well at the moment and I figure the best way to throw render data to it from the game (without the game knowing too much about the renderer) would be to just pass it large lists of vertices that can be filled into VBOs and such (from tile quads, etc.). But I guess I'll figure that part out. :)


Good luck, and thanks!
dsuse
Although, would you say the renderer you wrote and the game dll are tightly coupled? How difficult would it be do replace it with a direct3d renderer?


I would say that the coupling between platform and renderer is stronger than the coupling between game and renderer, due to the necessary platform-specific initialization. There are about 1.5 contact points between game code and render code. Half a contact point when reserving memory for the renderer from the game's own memory. I only count it as half because the game doesn't care what this memory will contain, it just needs to know the minimum size.

The other contact point is the render function itself. If I wanted to switch to a DirectX renderer, I'd need to write this function (plus all the initialization stuff in the platform) and change my build to compile "dx_renderer.cpp" instead of the current "ogl_renderer.cpp". That's it. If I wanted the option to switch renderers at application startup without recompiling, I would change "render" to be a function pointer, compile both ogl_renderer.cpp and dx_renderer.cpp, and then set this function pointer appropriately, probably in the platform layer. For switching renderers at runtime, I'd have to figure out clean ways to handle the initialization, but that's a problem of the platform layer again, the game code doesn't need to know about it. It just calls its supplied render function.
Nimbal
I have since pulled the render code into the game DLL


So, the platform sets up the context and things that go along with the window (I'm using SDL for this). Then the renderer is built into the game DLL as you stated above. How would you communicate platform layer things to the renderer like window resize events, fullscreen switches, etc.? I figure the renderer needs to manage projection mats, viewports and the like.
What I meant by "three tiered" is that it introduces a third tier to the two-tier model we have right now.

Right now we have platform-dependent code and platform-independent code. The platform-dependent is mostly meant to be replaced entirely for each platform, because it doesn't really contain anything that could be reused between platforms. The platform-independent code, on the other hand, has no tie to the system beyond perhaps some inlined macro'd intrinsics.

The renderer will (assuming it goes the way I normally do it) see us introduce a middle ground here. There will be a module that is "OpenGL code", right, which is _neither platform independent nor platform dependent_. Because you don't want to rewrite all the OpenGL code between the Linus and Mac versions, it can mostly be shared - but you also can't weld the OpenGL code directly into the platform-independent code, because then what do you do for the Mantle port, or the D3D port, or the XBox One port, etc.?

So you end up with this sort of "virtual platform" piece, where you have a switchable modules like with the platform-dependent code, but which is permuted differently than that code, so it is not really in the same tier.

Hope that makes some sense?

- Casey
dsuse
How would you communicate platform layer things to the renderer like window resize events, fullscreen switches, etc.?


The window size is passed to the update function from the platform layer. At the moment, I don't pass anything special about windowed / fullscreen (in fact, I don't have fullscreen support at all yet), but if the game ever needed to know about that (why?), it would just be a flag that is passed to the update function as well.

dsuse
I figure the renderer needs to manage projection mats, viewports and the like.


If you want something like mouse picking, the projection and view matrices must even be available to the game, not just the renderer, to convert mouse coordinates into rays from the camera. I'm only using one viewport right now, which is managed completely by the platform. I don't think more than one viewport will ever be necessary.
dsuse
But how would this then fit into the framework as it is now? Would we compile the renderer into the platform compilation unit, would it be its own thing, or would it be part of the game dll (as the rendering code currently is)

I just realized it may be somewhat tricky, e.g. OGL may be missing in the system and you can't use it.
Software rendering may be compiled in the game, why not, but OGL may be better kept in its own library, if loading OGL fails you fallback to software rendering.
So I assume by this it will also mean a new Compilation Unit will be added?

1.Platform Dependent

2.Platform Independent

3.Rendering API for your target graphics API

Edited by Ray Garner on
Hard to say. Probably not? They'll probably just be compiled into the OS layer as #includes, where you just #include all the renderers for the APIs that platform supports.

- Casey