Honeycomb Engine: Dev Blog 2 ~ More Vulkan and Honeycode

Since last week, I had planned to shift my focus mainly onto Honeycode, but I’ve mostly been working on the renderer some more. Yesterday I did a bit of work on the Honeycode parser, but it’s nowhere near complete yet. However, I have thought a little more about how the language will work conceptually, such as the syntax it will use and the features it will support. The idea is to make a programming language for non-programmers or beginners to programming, so I want it to be super-intuitive. I’ll detail a few of my ideas below, although it’s currently just me throwing everything at the wall and seeing what sticks.

Vulkan

On the plus side, I’ve finished the set of Vulkan tutorials I was using now, resulting in a really cool rendering of a model of a chalet found on Sketchfab. Now that I have a better understanding of how Vulkan operates, the next step is to get multiple different models loaded. Once I achieve that, the next step is to think up a level data format that allows me to save and load level data from disk. And once I’ve done that, I’ll need to set up a proper component-based architecture for objects. By using the Component programming pattern, it’ll allow users of Honeycomb to write their own behaviour scripts and attach them freely to game objects while keeping those sets of behaviour nicely encapsulated and decoupled from each other.

vulkan_model

This hut is almost worth the couple of weeks spent trawling through tutorials.

I’ve been reading through Bob Nystrom’s brilliant book, Game Programming Patterns, which details many different ways of organising code for all sorts of situations; I’ll be using some of the patterns used in this book in spades. For example, I currently have an input class that’s using the Singleton pattern – you have a publicly accessible static member of a class – purely because it’s the quickest way of making something globally visible (well, that and making all of the member functions and variables of a class static). I wanted to quickly add functionality to rotate the model and move the viewport, so please don’t judge me.

As pretty much every source on programming patterns will tell you, Singleton is awful for almost every case, so I will replace the input system with one that uses the Command pattern, which replaces hard-coded function calls for a given input with a bunch of different encoded behaviours – called commands – assigned to variables for each input, which will be executed using some execute() function.

Honeycode

Since the idea behind Honeycode is to be beginner-friendly, I want to minimise the number of scary features in the language as possible. You know the sort of stuff I mean, like requiring the user to write a million lines of boilerplate code before anything actually happens. At the same time, I want it to be expressive and I need it to be easily translatable to equivalent C++ code so I can easily compile it into an executable together with the engine.

There are a few broad ideas I currently have – some may be useful, some may be scrapped later on:

  • When a user creates a new script, the code in that script is automatically inside a class that inherits Component (the base class for the Component programming pattern), but this information is hidden from the user. They just write functions and variables in the script and attach it to game objects in the editor and let the engine worry about types and stuff.
  • There are some event functions that automatically fire and are ignored by scripts that don’t bother to implement them. Those events include a start() function that gets called after everything gets set up, but it is the first user-created code that is called, an update() function called every frame, an exit() function called when the level switches and a quit() function called when the game is closed. All the user needs to know is that they exist and roughly when they are called.
  • There should be simple one-function APIs for doing simple tasks and there should be minimal #includes or imports (or, get rid of them entirely and do it behind the scenes), then those simple functions will get translated to the appropriate C++ code during compilation. For example, if a user wants to save data (as a string) to a file, they should be able to write something like:
write(myString, myFileName.dat);
  • I might only have one floating point data type and one integer data type. For new users, I think it might be too confusing to immediately have to distinguish between all sorts of number types, like float, int, long, signed int, unsigned short int, char, wchar_t and who the hell knows what else is lurking in C++. At that rate, I’m surprised they didn’t throw in short long int to make everyone blink twice. I think including only float and int should suffice. In some instances during compilation, I can replace a type with a more sensible one anyway – array indexing and iteration would always use unsigned integers, for example. I’ll also keep char hanging around, because at least it’s clear what char is doing to a new programmer.
  • Memory management should be as simple and automatic as possible because C++ is confusing enough as it is without having to worry about destructors and all that jazz. I should be able to do all that behind the scenes.

To make things clear, my language doesn’t exist to radically change the syntax of C++, nor is it intending to provide any fancy optimisations. It’s essentially just a metric ton of syntactic sugar to make C++ more palatable to newbies. Since I’m not aiming to squeeze the absolute most out of the hardware, I can be a bit more lax on the performance side of things as long as games are efficient enough. I have a few more ideas, but most of them are just things I’d remove from C++ rather than add.

As for the progress I’ve made regarding the lexer/parser, I’m currently at the stage where I can recognise some keywords from an input file and convert them into tokens, although it needs a lot more work before it’s finished. The next step after that is to add all those tokens into a parse tree, then I’ll need to look through that tree to make sure the syntax of the input file is valid. I’ve also settled on the file extension .hny for Honeycode files (I’ve clearly got my priorities straight when it comes to fulfilling features).

So far on my schedule, I’m supposed to have written my specification (which is complete), learned the tools I’m using (including Vulkan, although I’ve left myself an additional week for that) and started working on Honeycode (which is why I’ve been brainstorming ideas). This week, I’m supposed to have learned all the Vulkan I need to know and started on the end-of-term progress report; as I’ve finished the Vulkan tutorial ahead of time, I will wait until early December when my copy of the Vulkan Programming Guide is scheduled to get to me before I do much more Vulkan and probably work mainly on Honeycode instead.

Advertisements

Honeycomb Engine: Dev Blog 1 ~ Working with Vulkan

Vulkan is a modern graphics API developed by the Khronos Group, the same group that maintains OpenGL, designed to bring high-efficiency graphics and computation to current generations of graphics cards. As mentioned in my previous post, I’m planning to use Vulkan for Honeycomb’s renderer, and as such I’ve been spending the best part of the last week learning the ins and outs of how it works. Believe me when I say it’s verbose; imagine if, to do your shopping, you had to go to the shop and buy each item in turn, go home, put the item away and then go back to the shop to get the next item – that’s what developing a Vulkan application often feels like. The upside is that the programmer has much more time to consider what the best items to buy are.

I won’t go into much detail about the inner workings of Vulkan, but as a broad explanation it’d suffice to say it’s like OpenGL without hand-holding. Not only do you have to create shaders and tell the GPU which vertices to render, which you’d of course have to do in OpenGL, but you have to deal with all the memory management – creation and safe deletion of buffers and queues, all of the device checks to determine if your hardware is capable of what you need and all of the semaphores and locks related to the asynchronous nature of how Vulkan processes things.

On top of that, whereas OpenGL gives you a graphics pipeline with predefined operations, some of which are programmable – a vertex array feeds into a programmable vertex shader, followed by rasterization etc etc, Vulkan requires the user to create the pipeline from scratch. This removes a ton of overhead by removing unused stages of the pipeline, but requires much more elbow grease on the programmer’s part. I really like the customisation and can clearly see how a well-behaved Vulkan program would end up more efficient than a similar OpenGL 4.5 or DirectX 11 program.

After about a week of following this amazing tutorial on and off, and after 1000-or-so lines of code, I finally reached a crescendo of joy when this popped up on my screen:

vulkan_triangle

It’s the most beautiful and efficient triangle I’ve ever seen.

Now that I have something rendered to screen, it’s much less of a jump to get more stuff on the screen than the journey to obtaining this triangle was. I’m using GLFW which handles all of the windowing for me, but all that does currently is remove the hassle of integrating my program with each platform’s native windowing system. If you’re planning to use Vulkan in your own project, be prepared to put in a lot of effort, but be excited when your application runs like silky-smooth butter.

One last thing I noticed – whereas OpenGL tends to fluctuate around 60fps when using Vsync (on my monitor at least), Vulkan tends to stick at exactly 60fps when left alone. It must just be very comfortable sat where it is. On the flipside, dragging the window around or switching programs tends to lag like hell to the point even my music stutters, although the actual program still reports 60fps so it could just be X11. I’ll report back if I ever find the solution to that issue.

d1-vulkan-60fps

Dat gradient, yo.

This week I’m scheduled to continue work on the Vulkan stuff, but I’m also supposed to be working on Honeycode. I’ll likely talk about Honeycode a bit more in my next post as I’ve only brainstormed ideas for the syntax and engine integration so far.

PS. I originally planned to write about 1,000 words for this post to match the verbosity of Vulkan, but I decided that might be annoying to read. Have 625 instead.

ShaderLab Programming #1 – Sepia Blend Shader

Now that exams are over, I’ve had time to actually breathe and, more importantly, do game dev stuff. I’ve rarely touched computer graphics in the past, but it’s an area I’ve always wanted to explore in detail. This summer, I plan to mostly spend my time learning the intricacies of computer graphics, and that’s partially where Unity’s ShaderLab steps in.

ShaderLab is a graphics language in its own right, but it also encompasses Cg, a high-level shader language by Nvidia, and HLSL, a Microsoft shader language for use with DirectX, both of which were developed together. We don’t need to know too much about the structure of ShaderLab programs for what we’re doing, so I’m largely going to ignore it for now. I needed to create a post-processing effect similar to Unity’s built-in Sepia Tone, which is an on/off effect, with a tweak that allows it to fade in and out instead. It’s for a game I’m working on in which you can turn back time – more on that in the coming days when I’ve put together something a bit more concrete hopefully! Let’s deconstruct this super-simple program and explore what a sepia effect actually is. Unfortunately, Unity’s Sepia Tone shader seems to use some sort of maths wizardry for its fragment shader that I don’t understand, so I’ll also be writing mine mostly from scratch. Here’s the number bullshit right here:

fixed4 frag (v2f_img i) : SV_Target
{ 
    fixed4 original = tex2D(_MainTex, i.uv);
 
    // get intensity value (Y part of YIQ color space)
    fixed Y = dot (fixed3(0.299, 0.587, 0.114), original.rgb);

    // Convert to Sepia Tone by adding constant
    fixed4 sepiaConvert = float4 (0.191, -0.054, -0.221, 0.0);
    fixed4 output = sepiaConvert + Y;
    output.a = original.a;
 
    return output;
}

The infinite wisdom of the Internet tells us that to convert a source image to a sepia-tone version, we just define a function of the input colours as follows:

new red = min(in red * 0.393 + in green * 0.769 + in blue * 0.189, 255)
new green = min(in red * 0.349 + in green * 0.686 + in blue * 0.168, 255)
new blue = min(in red * 0.272 + in green * 0.534 + in blue * 0.131, 255)

This formula represents 100% sepia; that is, the source image completely turned into a sepia tone. But we don’t always want a completely sepia image – we need to incorporate some sort of progress measure. Using this formula, we can just interpolate between the input colour and the full sepia tone and use a separate script to control the ‘progress’ of the interpolation. I’ve talked about interpolation before, so if this really big word is confusing, it just means we’re picking a colour on an imaginary line between ‘original colour’ and ‘sepia colour’, with a parameter between 0 and 1. We can now create another function to get our final ‘blended’ sepia colour:

out red = parameter * new red + (1 - parameter) * in red
out green = parameter * new green + (1 - parameter) * in green
out blue = parameter * new blue + (1 - parameter) * blue

With the theory out of the way, we can now create the shader and the script that will control it. We’ll be utilising the post-processing effects provided by Unity, so make sure you import them from the Standard Assets using Assets->Import Package->Effects from the menu bar. We’ll start with the controller script.

There are two main ways to keep track of the progress, so I’ll cover both. The first way is to define a method such that another script can just pass in a float to act as the parameter.

using UnityEngine;

namespace UnityStandardAssets.ImageEffects
{
    [ExecuteInEditMode]
    [AddComponentMenu("Image Effects/Color Adjustments/Sepia Blend")]
    public class SepiaBlend : ImageEffectBase
    {
        private float progress = 0f;
        private bool active = false;

        private void SetProgress(float progress)
        {
            this.progress = progress;
            active = (progress == 0f);
        }

        // Called by camera to apply image effect.
        void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            if(active)
            {
                material.SetFloat("_Progress", progress);
                Graphics.Blit(source, destination, material);
            }
        }
    }
}

OnRenderImage() is the method called when we want to render a post-processing image. We set the ‘progress’ parameter of the material attached to this project – we’ll see how this works with that material’s shader shortly – and use Graphics.Blit() to apply the texture created by the shader to the screen. The second way to control the progress parameter is to allow external scripts to set the ‘active’ boolean and use a time-based interpolation to decide what the progress should be. The guts of the program will look more like this:

private float progress = 0f;
private bool active = false;

private void SetActive(bool active)
{
    this.active = active;
}

private void Update()
{
    progress = Mathf.Lerp(progress, active ? 1f : 0f, Time.deltaTime
        * 5f);
}

// Called by camera to apply image effect.
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
    material.SetFloat("_Progress", progress);
    Graphics.Blit(source, destination, material);
}

Now for the actual shader! I’ll explain very quickly what the fragment shader is doing and ignore the vertex shader since it’s doing nothing in this example; I may do another post in the future explaining the details of how the shader is constructed, but I’m glossing over a lot for now. Don’t even worry about it yet.

Shader "Hidden/SepiaBlendEffect"
{
    Properties
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _Progress ("Progress", Float) = 0
    }
    SubShader
    {
        Pass
        {
            ZTest Always Cull Off ZWrite Off

            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
 
            #include "UnityCG.cginc"

            uniform sampler2D _MainTex;
            uniform float _Progress;

            fixed4 frag(v2f_img i) : SV_Target
            {
                fixed4 original = tex2D(_MainTex, i.uv);
 
                half3x3 vals = half3x3
                (
                    0.393, 0.349, 0.272, // Values for input red.
                    0.769, 0.686, 0.534, // Values for input green.
                    0.189, 0.168, 0.131  // Values for input blue.
                );

                half3 input = half3(original.rgb);

                half progress = half(_Progress);

                half3 intermed = (progress * mul(input, vals)) + 
                    ((1 - progress) * input);

                fixed4 output = half4(intermed, original.a);

                return output;
            }
            ENDCG
        }
    }
    Fallback Off
}

We’re interested in the stuff between ‘CGPROGRAM’ and ‘ENDCG’ – this is written in Nvidia’s Cg. All we need to know about the structure of shaders for now is that the fragment shader is run on every pixel of a processed image. I’ll also ignore all the types of variables for now and cover those in another post at a later date. Jumping straight to the frag() method, which constitutes the fragment shader, we’ll see a 3×3 table of numbers, vals, that looks strikingly like the one defined for our first function; the table is the transpose of a matrix formed by those functions’s numbers. Our shader performs a matrix multiplication on a vector made of the original rgb values, input, and our magical sepia number matrix vals (which is why it had to be transposed), then we do our interpolation to get intermed. Afterwards, we put together the final output by adding the original colour’s alpha onto intermed, and boom! We have our Sepia Blend shader. Now all that remains is to attach the SepiaBlend script to the main camera and assign the SepiaBlendEffect shader to the script, then we’re good to go.

I hope this has been a helpful introduction to the world of computer graphics. I’ve uploaded the three scripts to avoid you having to copy everything out (both variants of SepiaBlend.cs are included; pick your preferred version). Let me know if everything turns into a bugfest when you attempt to run any of the examples and I’ll try to diagnose the problem.