Progress Report May 2017

Posted on May 23, 2017

Hi everyone. Since I don't have any plans to release any more preview builds of the engine until final release, I thought I'd keep you updated with what has been going on lately.

First off, the development is going as strong as ever. Most of the implemented systems are proving to be very solid foundation for future work. Adding new features is really starting to be a breeze with all the low-level features fairly polished.

In the remainder of this text I’ll talk about major additions/changes/plans for Banshee that happened over the last few months. The main talking points are:

  • Physically based renderer progress
  • Making Banshee a proper C++ gamedev framework
  • Addition of the unified shading language
  • Addition of script binding generation

Renderer

In the last few months I have been mainly focusing on developing Banshee's renderer. For the longest time Banshee ran a very basic deferred renderer, which I never intended to keep as a permanent solution. It was there just so the engine can render something, and I wanted to develop the actual renderer as one of the last parts of the engine, after I had a solid foundation to build upon.

The renderer will be fully physically based, using hybrid tiled deferred / clustered forward rendering, gamma correct, HDR ready, with screen space reflections, support of reflection and irradiance probes, area lights, soft shadows and variety of post-processing effects like depth of field, SSAO, FXAA, tone mapping, color grading and more.

Ultimately I’m aiming for Banshee to be graphically on par (at least) with AAA engines, and the features above are the ones I have found to be most important in terms of image quality vs. implementation time.

Once I’m done with core engine systems and have more time I’ll be adding even more features to the renderer at a later stage, especially experimental features that hopefully push the graphical fidelity to the bleeding edge.

This is the current state of planned features:

  • Multi-threaded rendering - IMPLEMENTED
  • Tiled deferred renderer - IMPLEMENTED
  • Clustered forward renderer - IMPLEMENTED
  • Physically based shading - IMPLEMENTED
  • Area light sources - IMPLEMENTED
  • Reflection probes with geometry proxies - IMPLEMENTED BUT NEEDS TESTING
  • Irradiance maps - IMPLEMENTED
  • Screen space reflections - PENDING
  • HDR rendering - IMPLEMENTED
    • Automatic eye adaptation - IMPLEMENTED
    • Tone mapping with adjustable curve - IMPLEMENTED
    • White balance - IMPLEMENTED
    • Color grading - IMPLEMENTED
  • Gamma correct rendering - IMPLEMENTED
  • MSAA support for both forward and deferred - IMPLEMENTED
  • Shadows - WIP
    • Percentage closer soft shadows - WIP
    • Cascaded shadow maps - WIP
  • Post processing effects
    • Screen space ambient occlusion (SSAO) - PENDING
    • Bokeh depth of field - PENDING
    • Fast approximate anti-aliasing (FXAA) - PENDING

You can checkout the current progress yourself by compiling the ExamplePhysicallyBasedShading provided with the source code. This article's title image also contains a WIP rendering made with the new renderer.

Banshee as C++ gamedev framework

A lot of people seem to assume Banshee is only C# + Editor (i.e. very Unity-like), and while that is partly true, it is also perfectly usable through its C++ API, which is fully featured (even more so than the C# API). In fact due to its modular nature you can fully detach the editor/C# components and use Banshee as a C++ only gamedev framework, similar to SFML or SDL (but with a lot more features).

// Start application
Application::startUp(
    VideoMode(1280, 720), // Window resolution
    "My app", // Window title
    false); // True for fullscreen, false for windowed

// Import some assets
HMesh dragonModel = gImporter().import<Mesh>("Dragon.fbx");
HTexture dragonTexture = gImporter().import<Texture>("Dragon.psd");
HShader diffuse = gImporter().import<Shader>("Diffuse.bsl");

// Create a material
HMaterial dragonMaterial = Material::create(diffuse);
dragonMaterial->setTexture("albedo", dragonTexture);

// Add an 3D object to the scene
HSceneObject dragonSO = SceneObject::create("Dragon");
HRenderable renderable = dragonSO->addComponent<CRenderable>();

renderable->setMesh(dragonModel);
renderable->setMaterial(dragonMaterial);

// Add a camera and position it
SPtr<RenderWindow> window = Application::instance().getPrimaryWindow(); 

HSceneObject sceneCameraSO = SceneObject::create("SceneCamera");
HCamera sceneCamera = sceneCameraSO->addComponent<CCamera>(window);

sceneCameraSO->setPosition(Vector3(40.0f, 30.0f, 230.0f));
sceneCameraSO->lookAt(Vector3(0, 0, 0));

// Run application and cleanup when done
Application::instance().runMainLoop();
Application::shutDown();

Banshee's C++ API

Initially I made the C++ API only because I wanted to keep everything modular, without making much fuss about it being Banshee’s selling point. Lately I’ve seen a lot of developers that prefer to work with lower level frameworks instead of the editor workflow that many of the bigger engines (including Banshee) offer.

Therefore I have made the C++ API a first class citizen and can promise that Banshee will be usable as a low-level framework to those that desire it. The new changes/additions include:

  • Refactor/cleanup of the C++ API and some systems so it is more intuitive. These were mostly minor changes as I already kept the C++ API fairly clean and fully documented.
  • Over a HUNDRED C++ oriented manuals guiding you through every step of Banshee. The manuals slowly ramp up with difficulty, beginning with engine startup, working through all the engine components and systems, to advanced features. The manuals even cover engine internals teaching you how the engine works and how to modify it, so those that wish to develop engine technologies, rather than games, can easily jump in.
  • Editor has been modified so the scenes it generates can now be directly consumed by the low level rendering API. This way you can do level design in the editor, but keep everything else low level.
  • I’ll be releasing the core of the engine (everything but the editor and C# API) under the MIT license!

Unified shading language

While I was developing the renderer I realized just how much hassle it would be to keep maintaining separate HLSL and GLSL codebases, which is what I had so far (HLSL for DirectX, and GLSL for OpenGL & Vulkan). When I first started with Banshee it wasn’t much of an issue, especially considering how much longer it would have taken me to develop a unified shading language (something I wanted to do, but couldn’t spare the time).

But with all the changes the renderer was going through, and as the shaders grew bigger I had no option but to bite the bullet and finally support it. This was something I had originally planned for post v1.0 but I’m happy to have it out of the way. I'm happy to announce that currently all backends, including DirectX, OpenGL and Vulkan, are running on unified shader code!

The unified code is essentially HLSL with a few extensions, which I like to call BSL (Banshee Shading Language). Aside from being a unified language, it also comes with a few neat additions:

  • Non-programmable states like rasterizer/depth/stencil states can be specified in shader code itself (e.g. disable depth writes, change culling mode, enable blending)
  • Shaders can be composed of multiple pieces, which can be reused. You could call them classes, but I decided to call them mixins instead. Each mixin has its own code and states, and can then be added to other shaders. Therefore you can easily build complex shaders from different mixins, kind of like LEGO blocks.
  • Mixins can be overriden. This allows users to override parts of a more complex shader with their own code. For example, the user can override the part of the shader that generates surface information of the shader, or the part of the shader that performs lighting calculations. This ensures the renderer's shaders can remain as complex as necessary but normal developers can only concern themselves with extending the mixin they care about.
    • There is planned support for interfaces via abstract functions. This would allow mixins to provide interface which overriden mixins must respect. This would just add statical safety, but no benefit otherwise.
    • Another planned feature is the ability to specify stage inputs/outputs per mixin. So when a mixin is added, its inputs/outputs are merged with current shader code. Currently inputs/outputs need to be specified as a single struct or in a single function (as it is in HLSL). This would save on the use of #defines in a few places in current shader codebase.
  • Support for variations. Often a single shader needs slightly different code for various situations. For example a forward lighting shader will need different vertex shader code depending if the rendered mesh is static, animated via bones, animated via morph shapes or animated using both. Using variations you can easily provide bits of code which are only active for a specific variation, and the system will automatically generate all relevant combinations. This is primarily useful for lower level shaders, not visible by normal users.
  • Default values for uniforms. Data types like float, int, bool, float4 or float4x4 can now all be specified default values within shader code. This also works for sampler states and textures (which can choose from a few built-in texture types, like black, white and normal facing-up). This is important for shaders exposed to artists and designers so that things don't go wonky if they forget to set something.
  • Uniform modifiers. You can choose which uniforms are exposed to the user via custom attributes, as well as how they are presented to the user (e.g. whether a float4 is a 4D vector, or a color value). Same as above this is primarily important when exposing shader uniforms to artists and designers are presented with a nice clean interface in the editor. For example the editor would display a color picker if a shader uniform was marked with the [color] attribute.
#include "$ENGINE$\PerCameraData.bslinc"

technique Skybox
{
    // Add some code shared among different shaders
    mixin PerCameraData;

    // Set up non-programmable states
    raster
    {
        cull = cw;
    };

    depth
    {
        compare = lte;
        write = false;
    };

    // HLSL code with extensions
    code
    {
        void vsmain(
            in float3 inPos : POSITION,
            out float4 oPosition : SV_Position,
            out float3 oDir : TEXCOORD0)
        {
            float4 pos = mul(gMatViewProj, float4(inPos.xyz + gViewOrigin, 1));

            oPosition = pos.xyww;
            oDir = inPos;
        }

        // Uniforms are automatically exposed to the editor/material
        TextureCube gSkyTex = black; // Provide optional default value for any uniform
        SamplerState gSkySamp;

        float4 fsmain(
            in float4 inPos : SV_Position, 
            in float3 dir : TEXCOORD0) : SV_Target
        {
            #ifdef SOLID_COLOR
                return gClearColor;
            #else
                return gSkyTex.SampleLevel(gSkySamp, dir, 0);
            #endif
        }
    };  
};

Example BSL code

Now that I have a nice clean unified shading platform I'll be adding even more extensions to the language in the future. I'm likely to release the language as a standalone at some point as well, after I'm happy with its state.

Script binding generator

And final feature is something that was also unplanned, but I am very happy to have it implemented because it was bothering me for too long. Since I have added the C# API to Banshee, I had to manually write glue code between C++ and C#, which is 99% boilerplate and involves writing:

  • Glue code on the C++ end
  • Glue code on the C# end
  • API code on the C# end

This means that for every method I exported to C# I’d have to write over a dozen lines of boilerplate code. When exporting entire systems which had sometimes a dozen classes, it could take me a couple of days just to write the glue code.

Not only that but for things like enums and structs I had to ensure that they remain the same in both codebases, at the risk of breaking something. I also had to copy the documentation and maintain it separate for C++ and C# API.

This really ticked me the wrong way and I always wanted to automate this process but:

  • At the time I had more important things to worry about, like developing basic engine systems, rather than dealing on convenience that benefits mostly just myself.
  • My knowledge of Mono was very superficial and I didn’t see a clean pattern on how to expose the variety of C++ classes to C# in a generalized way. With time I developed the C++ scripting API which made me see a fairly general pattern I can use for the majority of exported classes.

But recently I have (once again) bit the bullet and developed a full scale script binding code generator. It only requires C++ class/struct/enum/method to be tagged with a simple macro, and the tool parses the code and generates everything else! The tool even parses Doxygen comments and automatically generates C# documentation.

/** Allows you to specify an environment map to use for sampling radiance of the sky. */
class BS_SCRIPT_EXPORT() Skybox
{
    public:
        Skybox();
        ~Skybox();

        /**
         * Environment map to use for sampling skybox radiance. 
         * Must be a cube-map texture, and should ideally contain HDR data.
         */
        BS_SCRIPT_EXPORT(n:Texture,pr:getter) // Export as a property named "Texture"
        HTexture getTexture() const;

        /** @copydoc getTexture */
        BS_SCRIPT_EXPORT(n:Texture,pr:setter)
        void setTexture(const HTexture& texture);

        /** 
         * Brightness multiplier that will be applied to skybox values before they're being used. Allows you to make the
         * skybox more or less bright. Equal to one by default. 
         */
        BS_SCRIPT_EXPORT(n:Brightness,pr:setter) // Export as a property named "Brightness"
        void setBrightness(float brightness);

        /** @copydoc setBrightness */
        BS_SCRIPT_EXPORT(n:Brightness,pr:getter)
        float getBrightness() const;
};

C++ interface to export

/// <summary>
/// Allows you to specify an environment map to use for sampling radiance of the sky.
/// </summary>
public partial class Skybox
{
    /// <summary>
    /// Environment map to use for sampling skybox radiance. 
    /// Must be a cube-map texture, and should ideally contain HDR data.
    /// </summary>
    public Texture Texture
    {
        get { ... }
        set { ... }
    }

    /// <summary>
    /// Brightness multiplier that will be applied to skybox values before they're being used. 
    /// Allows you to make the skybox more or less bright. Equal to one by default. 
    /// </summary>
    public float Brightness
    {
        get { ... }
        set { ... }
    }
}

Generated C# interface

Supporting entirety of C++ for binding generation is HARD. Therefore I decided not to do that, and instead focused on creating something that works well for 95% of the code.

But I also made sure that the system was extremely flexible, so when it cannot auto-generate some interface, you have a variety of ways to do it semi-manually, and manually:

  • Exported code can be part of “external” classes which can do some kind of pre- and post-processing, giving the developer full control over how stuff gets passed to C#. Often this is needed because C# interface is higher level than C++, rather than a 1:1 mapping.
  • All generated C# code is ‘partial’, ensuring the developer can add extra functionality as needed. The generator can also avoid generating the public API and instead just generate glue code, and leave the public API fully to the developer.
  • Finally, the manual way of exporting code is still fully functional and can be used if no other option is adequate.

The only downside to the generator is that it takes about 20 seconds to parse the codebase and generate the code. This is because the generator is running a C++ lexer/parser over the entire codebase. But considering you only need to generate the script code when some of the interface changes, this shouldn't be much of an issue. By default the generation step is always triggered during the build, but you can disable it and only enable it when you truly need it.

Majority of the code still uses the existing manual script bindings, but as time goes on I will slowly port all of it to the new system. Not only does it make maintenance easier but it ensures there is less chance of errors creeping in (which can be very hard to track down when they happen on the boundary between C++ and C#).

But perhaps the most important thing about the script binding generator is that it should be fairly easy to extend to new scripting languages! I have no immediate plans but maybe we see Lua, Java, Python or something else in the future.

What’s next?

For now I’m continuing work on the renderer. After that I’ll be adding support for Linux and Mac platforms, followed by release of v1.0 BETA later in the year (hopefully). Editor might take a few months more for release as I still need to do a lot of testing and polish.

That’s it for now, cheers!

Share

Facebook
Twitter
Google+
Pinterest
Reddit
LinkedIn
Email