For the past year, the main coding focus was on turning the in-game editor from a glorified data viewer into something more functional. Although the initial version of the editor had support for viewing the scene graph, node properties, render targets, basic gizmo support and texture loading/preview and a very early mesh loading subsystem, most of these were added as quick hacks just to get Dear ImGUI working with our data structures.

   The main issue with creating any kind of editor is trying to avoid feature creep (which I still failed at) and making sure any new addition does serve an actual purpose  and provides clear benefits (i.e. allows me to build my current <<and future>> games quicker). As such, a list of priorities was added to Trello to keep me focused.

   The main priorities that I came up with were:

  • Easy way of exposing editable fields with full Save/Load support
  • Full Gizmo support (using ImGuizmo, allowing me to translate/rotate/scale either a single or multiple nodes at once) with hot key support to avoid unnecessary clicks (Ctrl+T, Ctrl+R and Ctrl+S respectively).
  • Solidify Undo/Redo support.
  • Add as many debug views as possible to be able to quickly debug easier bugs before needing to fire up RenderDoc (e.g. BRDF debug views, wireframe, bounds components, etc)
  • Quickly move around the scene and select the nodes that I actually want to edit
  • Basic PostFX edit support because that changes the entire "feel" of a scene and it only requires exposing controls that were already available in constants or XML fields
  • Make sure we are taking full advantage of Dear ImGUI's docking branch and improve the SDL2 integration

Before jumping into the editor changes, a lot of focus was put into our API-agnostic multi-threaded command buffer system. A full write-up will be available >>here<< a later date.

We are fully GPU bound (but not draw call bound) with OpenGL on an R9 290 rendering (1080p) the full Sponza model, tessellated terrain, animated nodes, atmospheric sky, SSAO, MSAA 4x, SMAA, 3xVSM Cascades, ~200 point lights, full ImGui editor and CEGUI UI. Most of the performance at the moment is wasted in 3 calls: 3.655ms with SSAO, 3.22ms in FWD+ light culling (not found a balance yet between tile size and light count) and 2.2ms in the terrain shader (tessellation, tile-artifact reduced texturing, POM and more). These are the things I just haven't got around to optimizing, but the demo scene should be able to achieve full 16ms frame times with some effort put into it. No point in adjusting performance for a glorified test-box yet. A modern GPU (post 2018) can render the same scene at over 60FPS now.


Here are some quick demos of the stuff that's been added to the editor:

The gizmo widget was extended to support the new drag-select feature so that multiple nodes can be edited at once. This also involved a lot of fidgeting with input sharing between the editor, the gizmo and the rest of the scene. Not perfect, but mostly working:

 An editor should help with quick debugging of basic bugs (wrong shading, wrong bounds, etc). A lot of work was put in exposing as many features of the engine as possible to real-time toggles. If we want to render wireframe, there's a menu entry. If we want to debug a specific shadow-light, there's a toggle. View normals? View AO? Toggles. This greatly helped with developing and adding new features as well as fixing long-standing bugs: Culling and selection was improved, SMAA/Edge Detection/MSAA were fixed in days instead of weeks, etc. The iteration time went down tremendously compared to capturing frames in RenderDoc multiple times per day.

 This one was pretty easy. Add a filter to find nodes easier, double-click to teleport a la Unity. Editor camera and in-game cameras are decoupled, but that can be toggled on or off via a switch.

 Since the property editor required a rewrite to the way that we expose controls to ImGui, adding a basic PostFX editor was easy. 2-3 hours of work.



Obviously, we have to take full advantage of Dear ImGUI's docking branch with docking, multi-windows, menus, etc. Using a few addons from >>here<< for theme support.


No editor would be complete without node properties. These are pretty self-explanatory. Easy(-ish) to expose to the editor, saved to and loaded from files, grouped by component. Full support for most ImGui types: buttons, sliders, inputs, text fields, separators, colour edits, etc.

That's about it for now. A full write-up on the command buffer usage will follow later explaining code like the following (that renders a texture in the specified viewport into whatever the current render target bound is):

void GFXDevice::drawTextureInViewport(TextureData data,
                                      const Rect<I32>& viewport,
                                      bool convertToSrgb,
                                      bool drawToDepthOnly,
                                      GFX::CommandBuffer& bufferInOut)
    GenericDrawCommand triangleCmd = {};
    triangleCmd._primitiveType = PrimitiveType::TRIANGLES;
    triangleCmd._drawCount = 1;

    GFX::BeginDebugScopeCommand beginDebugScopeCmd = {};
    beginDebugScopeCmd._scopeID = 123456332;
    beginDebugScopeCmd._scopeName = "Draw Texture In Viewport";
    GFX::EnqueueCommand(bufferInOut, beginDebugScopeCmd);

    GFX::EnqueueCommand(bufferInOut, GFX::PushCameraCommand
    GFX::EnqueueCommand(bufferInOut, GFX::BindPipelineCommand
        drawToDepthOnly ? _DrawFSDepthPipeline : _DrawFSTexturePipeline)

    GFX::BindDescriptorSetsCommand bindDescriptorSetCmd = {};
    bindDescriptorSetCmd._set._textureData.setTexture(data, to_U8(TextureUsage::UNIT0));
    GFX::EnqueueCommand(bufferInOut, bindDescriptorSetCmd);

    GFX::EnqueueCommand(bufferInOut, GFX::PushViewportCommand{ viewport });

    if (!drawToDepthOnly) {

        GFX::SendPushConstantsCommand pushConstantsCommand = {};
             GFX::PushConstantType::UINT, convertToSrgb ? 1u : 0u);
        GFX::EnqueueCommand(bufferInOut, pushConstantsCommand);

    GFX::EnqueueCommand(bufferInOut, GFX::DrawCommand{ triangleCmd });

    GFX::EnqueueCommand(bufferInOut, GFX::PopViewportCommand{});
    GFX::EnqueueCommand(bufferInOut, GFX::PopCameraCommand{});
    GFX::EnqueueCommand(bufferInOut, GFX::EndDebugScopeCommand{});