Stanislav Arnaudov

AssetBuilder

· Stanislav Arnaudov · 3 minute read · 675 words

Abstract

AssetBuilder is my solution for quickly loading assets in some applications in DirectXer. There is a lot that needs to happen for an image to be turned into a texture on the GPU. Most of this work can be done offline and there are no reasons why it should not be done offline. AssetBuilder turns an asset definition file in JSON format into a packed asset bundle file in a custom binary format suitable for fast loading.

The runtime asset loading logic can accept this binary file, read its entire contents into memory with one read and then load every resource necessary as quickly as possible. Having all the assets that a game (or a level of a game) needs in one binary blob makes loading performance incredibly high. Currently, the system can load about 230 MBs worth of assets in under a second.

In this article, I’ll go over the assets that AssetBuilder can currently pack in a binary file.

Principles

The core principles I follow and how they apply to AssetBuilder:

  • Performance – I don’t want to wait for assets to get packed
  • Usability – define assets in a JSON file and let AssetBuilder do its job - nothing more, nothing less.

Notable achievements

  • JSON for asset file definition – as said before, AssetBuilder uses a JSON file for defining what assets are to be packed. The JSON file contains information about every asset and how exactly it should be packed into the binary file. Some assets also have defined Id for them which can be used in the C++ code to refer to them.

  • Packing images in atlases – AssetBuilder can pack image assets in texture atlases to optimize the GPU memory usage by having a small number of big textures instead of lots of small ones. AssetBuilder can do this automatically and uses stb_rect_pack under the hood to generate packed atlases with images.

  • Loadable assets – currently AssetBuilder can pack the following asset types:

    • Texture – simple GPU texture. Textures can optionally be encoded in the BC6 format through a compute shader (this happens really fast and it hardly costs packing time)
    • Vertex Buffer, – GPU vertex buffer
    • Index Buffer – GPU index buffer
    • Constant Buffer, - GPU constant buffer
    • Image – an image that can be loaded with a specific size
    • Legacy Font, – font rendered through FreeType
    • Font – SDF font rendered through FreeType
    • MSDF Font – MSDF font rendered through msdf-atlas-gen
    • Wav – a WAV audio file
    • Skybox – six images that will be loaded as a cube texture
    • Skybox HDR – an HDR image that will be converted to a cube texture and then packed
    • Mesh – a mesh loaded from an OBJ file that will be packed as vertex and index buffers
    • Packed Meshes – a bunch of meshes from a folder that will be packed as a single index and vertex buffers
    • FBX Mesh – a mesh loaded from an FBX file that will be packed as vertex and index buffers
    • Animated Mesh – a mesh that with animations that will be packed as vertex and index buffers and skinning data
    • **Animation ** – skinning data for a mesh
    • Material – PBR or Phong material definition that will be packed as a constant buffer
    • BuiCollection – a collection of BUI documents (my UI solution) that will be serialized and their resources packed
    • PBREnv – maps for image-based lightning that will be packed as diffuse irradiance and specular reflectance cube textures. These maps will be generated at the time of packing from a single HDR image through the use of compute shaders.
  • Header generation with assets names and Ids – AssetBuilder also generates a C++ header file with every packed asset’s id exposed as #define together with several useful arrays with the names of different asset types. This makes working with the assets inside of C++ code extremely easy. Also, it gives an easy way for creating ImGui widgets that can select a particular array based on name and associate this name with asset id.