AMBuild FXC API

From AlliedModders Wiki
Jump to: navigation, search

AMBuild has rudimentary support for compiling and integrating HLSL shaders into a C++ project, via Microsoft's FXC tool.

FXC is a somewhat tricky tool because it compiles only one function at a time. This results in many, many output files, which become difficult to manage for developers and can slow down the compilation process. AMBuild addresses this by generating "unified" files for a collection of shaders. The unified file acts a single include and translation unit for generated shader code.

Creating an HLSL job

The HLSL job constructor can be accessed via builder.tools.FXC, which takes the following parameters:

  • prefix - A prefix to use for naming unified files.
  • namespace - An optional string containing a fully-qualified namespace. For example, if you pass 'game::shaders', unified source and header code will be wrapped in a "shaders" namespace, inside a "games" namespace.

This function will construct an FxcJob object, which has the following additional properties:

  • listDefineName - Optional name for a compile-time macro that will be defined in the unified shader header. This macro (explained further below) allows iterating over defined shaders in the preprocessor.
  • shaders - A list of shader translation units.

Each shader translation unit should be a dictionary with the following keys:

  • 'source' - Path to the HLSL source file.
  • 'variable' - HLSL generates shaders as byte blobs assigned to a variable. This specifies the prefix that AMBuild will use to name the variable. For example, if you specify 'ModelShader', AMBuild will assign the bytes to 'ModelShader_Bytes' and the length to 'ModelShader_Length'.
  • 'profile' - The Shader Model profile to use.
  • 'entrypoint' - Optional. If specified, the entrypoint function of the shader. If not specified, AMBuild assumes it is 'main'.

The FxcJob object can be added to the custom list of a C++ job.

Example

shaders = builder.tools.FXC('all-shaders', 'game::shaders')
shaders.listDefineName = 'SHADER_LIST'
shaders.shaders += [
  {
    'source': 'quad_vertex_shader.hlsl',
    'variable': 'QuadVertexShader',
    'profile': 'vs_5_0',
  },
]

program.custom += [shaders]

Outputs

AMBuild's FXC tool will generate two files of note, using the prefix specified in the job constructor. For example, if the job's constructor specified 'all-shaders' as above, the following files will be generated:

  • all-shaders-include.h
  • all-shaders-bytecode.cxx

The include file declares two variables for every shader translation unit. Using the example above, it would contain the following:

  • extern const uint8_t* QuadVertexShader_Bytes;
  • extern const size_t QuadVertexShader_Length;

Respectively, these would contain the bytecode blob and blob length for each shader. The include file can be included from any C++ file in the binary - it's automatically placed in the include path.

The bytecode file provides definitions for all the variables declared in the include file. It is automatically added to the compilation step (and linker step), so you do not need to handle it.

Integration

With the example above, we also defined a listDefineName. This provides some metaprogramming potential without having to modify AMBuild. For example, we can do the following:

#include "all-shaders-include.h"

ID3D11VertexShader* QuadVertexShader = nullptr;

void InitializeShaders(ID3D11Device* device)
{
# define CREATE_SHADER(prefix) device->CreateVertexShader(prefix##_Bytes, prefix##_Length, &prefix);
  SHADER_LIST(CREATE_SHADER)
# undef CREATE_SHADER
}

Currently, the list define iterates over all shaders. As needed we can extend the tool to iterate over shader types as well, or to specify custom unifier methods.