Menu Share

How to compile shaders with CMake

by | Published on
category CMake

In this short article I will explore the topic of GLSL shaders compilation for Vulkan using CMake.

Table of Contents

Introduction

In grphics programming you have these things called shaders. Shaders are the programmable part of a graphics pipeline which in Vulkan is part of a render subpass. So shaders like your C++ code need to be compiled. In OpenGL shaders were loaded directly in GLSL which is one of the official shading languages. In OpenGL 4.6 compiled shaders were introduced.

Vulkan uses only compiled shaders though. So you have to write your GLSL code and then pass it through a compiler to convert to something called SPIR-V (Standard Portable Intermediate Representation). To do this we need to invoke the glslc.exe program which I will refer to as the compiler.

The Shaders Compiler

The compiler is this application called glslc which takes an input GLSL file and converts it into a SPIR-V. The best thing I can do to explain how this compiler works is with an example. Let’s take the following project directory:

  • project (command line)
    • CMakeLists.txt
    • shaders
      • basic.vert
      • basic.frag
      • common.glsl

And we want to compile this very simple basic.vert file:

#version 460
#include "common.glsl"

void main() {
    gl_Position = vec4(1.0, 1.0, 1.0, 1.0);
}

We would have to call a command similar to this one:

glslc.exe shaders/basic.vert -I shaders -o shaders/basic.vert.spv

This command will take the basic.vert shader from the shaders directory and then using the -o option it will specify that we want to output basic.vert.spv in the same directory. The -I here will also help to use the include directive inside the shader by specifying the include directory.

How do we compile shaders in CMake

To start we first have to add the find_package(Vulkan REQUIRED) since this package will get the Vulkan SDK as well as any other tools to use later. One of these tools is our glslc compiler. After find package is invoked correctly you should have access to the glslc program through Vulkan::glslc.

In CMake we can actually create a custom target. This custom target can help us invoke this glslc.exe command on each shader one by one until all are compiled. I usually do this by introducing the following CMake function:

function(add_shaders TARGET_NAME)
  set(SHADER_SOURCE_FILES ${ARGN}) # the rest of arguments to this function will be assigned as shader source files
  
  # Validate that source files have been passed
  list(LENGTH SHADER_SOURCE_FILES FILE_COUNT)
  if(FILE_COUNT EQUAL 0)
    message(FATAL_ERROR "Cannot create a shaders target without any source files")
  endif()

  set(SHADER_COMMANDS)
  set(SHADER_PRODUCTS)

  foreach(SHADER_SOURCE IN LISTS SHADER_SOURCE_FILES)
    cmake_path(ABOSULTE_PATH SHADER_SOURCE NORMALIZE)
    cmake_path(GET SHADER_SOURCE FILENAME SHADER_NAME)
    
    # Build command
    list(APPEND SHADER_COMMAND COMMAND)
    list(APPEND SHADER_COMMAND Vulkan::glslc)
    list(APPEND SHADER_COMMAND "${SHADER_SOURCE}")
    list(APPEND SHADER_COMMAND "-o")
    list(APPEND SHADER_COMMAND "${CMAKE_CURRENT_BINARY_DIR}/${SHADER_NAME}.spv")

    # Add product
    list(APPEND SHADER_PRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/${SHADER_NAME}.spv")

  endforeach()

  add_custom_targets(${TARGET_NAME} ALL
    ${SHADER_COMMANDS}
    COMMENT "Compiling Shaders [${TARGET_NAME}]"
    SOURCES ${SHADER_SOURCE_FILES}
    BYPRODUCTS ${SHADER_PRODUCTS}
  )
endfunction()

Now having this function we can easily compile shaders in our CMakeLists.txt file by calling this:

add_shaders(DemoProject shaders/basic.vert shaders/basic.frag)

Conclusion

This adds an easy way to include shaders in your CMake project when working with Vulkan. I encourage you to expand this function as well and give it more options like being able to accept vulkan version or compiling hlsl instead of glsl. If you liked this you might be interested in any of these courses that I’ve published on Udemy as well:

Leave a comment

Your email address will not be published. Required fields are marked *