Happy New Year – Progress Report!

Been over due a blog post as usual but lots of work has been going on in the background, mainly on my 2d sprite renderer that I’m using to render the editor and all the particles. Made a huge amount of progress with it and learnt a lot more about Vulkan and graphics coding in general. I think this will pay off as my ultimate goal is to have all the particles get updated in a compute shader.

So last year was the last year that I was going to work on web sites (my main source of income). So happy that I’ve now moved on from web dev and will be persuing TimelineFX and other projects full time so expect a lot more updates coming this year.

So as mentioned the last 6 months when I had time I worked mostly on my renderer. I realised that I reached a point in the editor and particle library that I needed the renderer to be more flexible and able to help me experiment with different shaders and such. Here’s some of the things I worked on to help with that (a little bit technical):

Rendering Set Ups

It’s much easier now to set up a rendering pipeline where you can specify which render targets to draw to and in what order. I guess it’s basically a scene graph of some sort where you can set up dependencies between the different render passes. It’s also much more optimised now, I was doing things before that just weren’t necessary like compute shaders in there own command buffers and having to syncronise with the renderpass in another command buffer. Compute shaders can just be in the same command buffer with a pipeline barrier to make things a lot more straight forward.

Additionally everything used to render to a render target before being rendered to the swap chain but now you have the option to just render straight to the swap chain if you want for more flexibility in simpler applications/games. Setting up a scene for rendering (I call them command queues as they’re all recorded in a Vulkan command buffer) looks something like this:

//Create the render queue
//For blur effect we need to draw the scene to a base render target first, then have 2 render passes that draw to a smaller texture with horizontal and then vertical blur effects
//then we can draw the base target to the swap chain and draw the blur texture over the top by drawing it as a textured rectangle on a top layer that doesn't get blurred.

//First create a queue and set it's dependency to the present queue. This means that it will wait on the swap chain to present the final render to the screen before rendering again.
//But bear in mind that there are multiple frames in flight (as long as QULKAN_MAX_FIF is >1) so while one queue is being executed on the gpu the next one will be created in the meantime.
//The best explaination I've seen of this can be found here: https://software.intel.com/content/www/us/en/develop/articles/practical-approach-to-vulkan-part-1.html
render_queue = NewRenderSetup(CommandDependencyType_present);
{
	//Create a renderpass that renders to a base render target
	NewRenderPassSetup(base_target_index);
	{
		//base target needs a layer that we can use to draw to
		//You can also use AddLayer(layer_index) to add a layer that you already created with CreateLayer() previously in your code
		base_layer_index = NewLayerSetup("Base Layer");
	}
	//Start the render pass that applies vertical blur to the base target
	NewRenderPassSetup(vertical_blur_index);
	{
		//Use a render pass function that renders to the whole texture
		SetRenderPassFunction(RenderRenderTarget);
	}
	//Start the render pass that applies horizontal blur to the base target
	NewRenderPassSetup(final_blur_index);
	{
		//Use a render pass function that renders to the whole texture
		SetRenderPassFunction(RenderRenderTarget);
	}
	//Start the render pass that we can use to draw on top of the blur effect
	NewRenderPassSetup(top_target_index);
	{
		//Create a draw layer to draw to it
		top_target_layer_index = NewLayerSetup("Top layer");
	}
	//Finally we won't see anything unless we tell the render queue to render to the swap chain to be presented to the screen, but we
	//need to specify which render targets we want to be drawn to the swap chain.
	//We can use NewRenderPassSetupSC which sets up a render pass to the swap chain specifying the render target to draw to it
	NewRenderPassSetupSC(base_target_index);
	{
		//We can add as many render targets as we need to get drawn to the swap chain. In this case we'll add the top target where we can
		//draw a textured rectangle that samples the blurred texture.
		AddRenderTarget(top_target_index);
	}
	//Connect the render queue to the Present queue so that the swap chain has to wait until the rendering is complete before
	//presenting to the screen
	ConnectQueueToPresent();
	Finish();
}

Memory Management

My original memory management was very straight forward in that I would create a new buffer for every single allocation. This isn’t a great way of doing it especially because GPUs have a limit on how many allocations you can assign at one time. I’ve now implemented much better memory management where large allocations are created and then ranges are set up within those larger allocations. If a range runs out of memory then it will automatically be resized. You should of course make your ranges big enough in the first place but while you’re working on a program you don’t really know much much memory you need so it’s useful to have this feature.

It also outputs a debug memory report when the program ends which lets you know a lot of details about memory usage including how many times allocations were resized so you can size your allocations ahead of time to avoid that.

Instanced Rendering

My first approach to drawing lots of sprites was to use a compute shader to build the vertex buffer which would then be sent to the vertex/fragment shader for rendering. This was ok but I kept hearing about instanced rendering so thought I’d give it a go. I heard mixed things about it for rendering small instances like quads but actually they work extrememly well. It’s faster then using the compute shader which is great, always nice to get a speed boost plus it also uses less memory as you don’t need a vertex or index buffer at all, all you need as a buffer containing the position, size etc of each sprite.

You upload this buffer each frame and then you can just build the quad inside the vertex shader based on the gl_VertexIndex. Rather then call VkCmdDrawIndexed you simply call VkCmdDraw and send 6 vertices along with the number of instances that you want to draw.

At some point I’ll release the render as open source on github.

 

There’s still a few more things that I’d like to tidy up this month but overall I’m very happy with the render, then it will be back to the next Alpha vertsion of the TimelineFX editor where I’ll be working on stability!