john01dav
john01dav

Reputation: 1962

Why won't Cmake build my vulkan/spirv shaders?

I am currently working on learning Vulkan by following this website. I am currently at the point where I need to compile my GLSL shaders to SPIR-V. Unlike the website, which just creates a separate compile script for shaders (e.g. with bash), I want to fully use Cmake for my code.

Because glslc, the SPIR-V/GLSL compiler that the website recommends, is installed on my system's path (I'll worry about portability later.), I decided to use Cmake's add_custom_command in a cmake function that acts as a wrapper to add a shader to be compiled.

When I attempted to do this, however, it is as if I made no changes to the Cmake code at all. No .spv files are generated in either my out of tree build folder, or in my source folder. When I run VERBOSE=1 make on my real project, with C++ source, then I see the C++ compiler's commands but I do not see any glslc commands.

I considered that the culprit could be some sort of scoping with the Cmake function, I moved one of the shader calls to outside any function call by copying and pasting the function's source then manually substituting values for its parameters. Both styles are shown in the below MCVE.

In addition to add_custom_command Cmake has an add_custom_target function, but Cmake's documentation says the following:

[add_custom_target] Adds a target with the given name that executes the given commands. The target has no output file and is always considered out of date even if the commands try to create a file with the name of the target. Use the add_custom_command() command to generate a file with dependencies. By default nothing depends on the custom target. This is not what I want as I do not want my shaders to be build every time that Cmake is run, but rather only when the glsl files have been changed since the last build, as a dependency of the linked C++ application as that is what they are and I want Clion to recognize that.

Here is an MCVE:

Directory structure:

$ tree
.
├── CMakeLists.txt
├── CMakeLists.txt~
├── fragmentshader.glsl
└── vertexshader.glsl

Commands to reproduce the issue on Linux:

cd /path/to/folder/with/these/files
mkdir cmake-build-test
cd cmake-build-test
cmake ..
make

CMakeLists.txt:

    cmake_minimum_required(VERSION 3.14)
    project(vulkan_cmake)

    function(add_spirv_shader SHADER_STAGE INPUT_FILE OUTPUT_FILE)
        add_custom_command(
                OUTPUT ${OUTPUT_FILE}
                COMMAND "glslc -fshader-stage=${SHADER_STAGE} ${INPUT_FILE} -o ${OUTPUT_FILE}" #glslc is on the system path on my computer, so I am not currently worried about `find_package`ing it.
                MAIN_DEPENDENCY ${INPUT_FILE}
                WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        )
    endfunction()

    add_spirv_shader(vector vertexshader.glsl vertexshader.hpv)

    add_custom_command(
            OUTPUT fragment.spv
            COMMAND "glslc -fshader-stage=fragment fragmentshader.glsl -o fragment.spv" #glslc is on the system path on my computer, so I am not currently worried about `find_package`ing it.
            MAIN_DEPENDENCY fragmentshader.glsl
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    )

fragmentshader.glsl:

    #version 450
    #extension GL_ARB_separate_shader_objects : enable

    layout (location = 0) out vec4 outColor;

    void main(){
        outColor = vec4(1.0, 0.0, 0.0, 1.0);
    }

vertexshader.glsl:

    #version 450
    #extension GL_ARB_separate_shader_objects : enable

    vec2 positions[3] = vec2[](
        vec2(0.0, -0.5),
        vec2(-0.5, 0.5),
        vec2(0.5, 0.5)
    );

    void main(){
        gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
    }

Upvotes: 3

Views: 6333

Answers (1)

Carlo Wood
Carlo Wood

Reputation: 6791

Here is how I did it:

[...]

find_package(Vulkan REQUIRED COMPONENTS glslc)
find_program(glslc_executable NAMES glslc HINTS Vulkan::glslc)

#==============================================================================
# COMPILE SHADERS
#

set(SHADER_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/shaders)
set(SHADER_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/shaders)

file(GLOB SHADERS
  ${SHADER_SOURCE_DIR}/*.vert
  ${SHADER_SOURCE_DIR}/*.frag
  ${SHADER_SOURCE_DIR}/*.comp
  ${SHADER_SOURCE_DIR}/*.geom
  ${SHADER_SOURCE_DIR}/*.tesc
  ${SHADER_SOURCE_DIR}/*.tese
  ${SHADER_SOURCE_DIR}/*.mesh
  ${SHADER_SOURCE_DIR}/*.task
  ${SHADER_SOURCE_DIR}/*.rgen
  ${SHADER_SOURCE_DIR}/*.rchit
  ${SHADER_SOURCE_DIR}/*.rmiss)

add_custom_command(
  COMMAND
    ${CMAKE_COMMAND} -E make_directory ${SHADER_BINARY_DIR}
  OUTPUT ${SHADER_BINARY_DIR}
  COMMENT "Creating ${SHADER_BINARY_DIR}"
)

foreach(source IN LISTS SHADERS)
  get_filename_component(FILENAME ${source} NAME)
  add_custom_command(
    COMMAND
      ${glslc_executable}
      #      -MD -MF ${SHADER_BINARY_DIR}/${FILENAME}.d
      -o ${SHADER_BINARY_DIR}/${FILENAME}.spv
      ${source}
    OUTPUT ${SHADER_BINARY_DIR}/${FILENAME}.spv
    DEPENDS ${source} ${SHADER_BINARY_DIR}
    COMMENT "Compiling ${FILENAME}"
  )
  list(APPEND SPV_SHADERS ${SHADER_BINARY_DIR}/${FILENAME}.spv)
endforeach()

add_custom_target(shaders ALL DEPENDS ${SPV_SHADERS})

Note that I commented out the -MD -MF because it appears not to be needed (on my linux box).

For the complete (and most recent) version, see my project on github

Upvotes: 6

Related Questions