2

I have certain files (.hlsl) in one of my targets that are compiled using a custom compiler (glslc). After compilation, I use a different tool to embed the compiled binary in a c-file containing c-array like described here.

This workflow is summarized in the following code which invokes 2 custom commands:

function(build_hlsl_shader shader_file)
    get_source_file_property(shader_type ${shader_file} ShaderType)    
    get_filename_component(shader_name ${shader_file} NAME_WE)

    # special command: .hlsl file to a .c / .h file
    add_custom_command(  
        TARGET shaders_custom_target # a custom target
        #OUTPUT ${shader_name}.cpp # maybe this? 
        #DEPENDS ${shader_file} # maybe that?
        MAIN_DEPENDENCY ${shader_file} # or maybe thee?
        COMMAND # special compiler
        ${glslc_executable} 
        -fshader-stage=${shader_type} 
        ${CMAKE_CURRENT_SOURCE_DIR}/${shader_file}
        -o
        ${CMAKE_CURRENT_SOURCE_DIR}/${shader_name}.spv
        COMMAND # special tool that generates the c header.
        bin2h 
        ${CMAKE_CURRENT_SOURCE_DIR}/${shader_name}.spv
    )
endfunction()

I then define a custom target that that generates the said files whenever it is built.

add_custom_target(
    shaders_custom_target 
    DEPENDS
    "shader.hlsl"
)

build_hlsl_shader("shader.hlsl")

# this library consumes the headers generated by the first target
add_library(library2 STATIC "file1.cpp""file1.hpp")
add_dependencies(library2  shaders_custom_target)

The main issue is that the custom target build process is triggered every time I build my project regardless of the fact the none of the .hlsl files are changed. This is undesirable, I want my custom command to work just like when compiling regular .c or cpp files. They are compiled only when changed.

My requirements are as follows:

  • Each time one of the .hlsl files are changed, the custom command should trigger.
  • The custom command should not get triggered whenever I build the target / project.

So how can this achieved?

I tried several methods like using DEPENDS, MAIN_DEPENDENCY or using OUTPUT instead of TARGET. None worked. I also tried the solution offered here and here. They all suffer from the same issue.


As requested in the comments, Here is my attempt to use OUTPUT instead of TARGET:

function(build_hlsl_shader shader_file)
    get_source_file_property(shader_type ${shader_file} ShaderType)    
    get_filename_component(shader_name ${shader_file} NAME_WE)

    add_custom_command(
        #TARGET shaders_custom_target
        OUTPUT 
        #${shader_name}.hpp # the generated header, tried with or without
        ${shader_file} # tried with and without
        DEPENDS ${shader_file} # tried with and without
        #MAIN_DEPENDENCY ${shader_file} # tried with and without

        COMMAND
        ${glslc_executable}
        -fshader-stage=${shader_type} 
        ${CMAKE_CURRENT_SOURCE_DIR}/${shader_file}
        -o
        ${CMAKE_CURRENT_SOURCE_DIR}/${shader_name}.spv
        COMMAND
        bin2h
        ${CMAKE_CURRENT_SOURCE_DIR}/${shader_name}.spv
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
     )
endfunction()
Elad Maimoni
  • 3,703
  • 3
  • 20
  • 37
  • Correct way for specify a file which will be rebuilt only when its dependencies are changed is `add_custom_command` with `OUTPUT` instead of `TARGET`. Please, show you attempt when use given command flow. – Tsyvarev May 28 '22 at 19:30
  • @Tsyvarev I added one of my attempts. let me know if you need more info. – Elad Maimoni May 28 '22 at 19:39
  • Well, no command may depend on `${shader_file}` file and produce the very **same file**. OUTPUT denotes files which are created by the COMMAND, DEPENDS denotes files which are **required** by the COMMAND because it uses them. – Tsyvarev May 28 '22 at 22:41
  • @Tsyvarev Ok, I tried that. But it still won't work. The command is always triggered. – Elad Maimoni May 29 '22 at 06:39
  • 1
    So, which exact file you specify as OUTPUT for `add_custom_command`? Is this file **actually** created after executing the command? (That is, after building your project first time you could check whether OUTPUT file exists). Note, that **relative** path specified in OUTPUT is interpreted relative to the **build directory**, not relative to the *source* one. – Tsyvarev May 29 '22 at 08:00
  • @Tsyvarev the the relative path was indeed the issue! after changing it to absolute path it works! thanks!! – Elad Maimoni May 29 '22 at 09:00

1 Answers1

3

It depends on whether you consider the shaders to be "part of the library" or just some data you need at runtime.

For my projects that produce HLSL shaders, I use a custom target similar to what you are doing:

add_custom_target(shaders)

set_source_files_properties(VertexShader.hlsl PROPERTIES ShaderType "vs")
set_source_files_properties(PixelShader.hlsl PROPERTIES ShaderType "ps")
    
foreach(FILE VertexShader.hlsl PixelShader.hlsl)
    get_filename_component(FILE_WE ${FILE} NAME_WE)
    get_source_file_property(shadertype ${FILE} ShaderType)
    add_custom_command(TARGET shaders
        COMMAND dxc.exe /nologo /Emain /T${shadertype}_6_0 $<IF:$<CONFIG:DEBUG>,/Od,/O3> /Zi /Fo ${CMAKE_BINARY_DIR}/${FILE_WE}.cso /Fd ${CMAKE_BINARY_DIR}/${FILE_WE}.pdb ${FILE}
    MAIN_DEPENDENCY ${FILE}
    COMMENT "HLSL ${FILE}"
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    VERBATIM)
endforeach(FILE)

add_dependencies(${PROJECT_NAME} shaders)

This essentially sets up a 'post build' event. If any of the shader source files change, then they all get rebuilt.

The other way to set it up is to use an output file, but here you need some extra cmake logic so you can add the output files to the add_library or add_executable:

# SOURCES variable is all the C/C++ source files in the library

# Build HLSL shaders
set_source_files_properties(VertexShader.hlsl PROPERTIES ShaderType "vs")
set_source_files_properties(PixelShader.hlsl PROPERTIES ShaderType "ps")
    
foreach(FILE VertexShader.hlsl PixelShader.hlsl)
    get_filename_component(FILE_WE ${FILE} NAME_WE)
    get_source_file_property(shadertype ${FILE} ShaderType)
    list(APPEND CSO_FILES ${CMAKE_BINARY_DIR}/${FILE_WE}.cso)
    add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/${FILE_WE}.cso
        COMMAND dxc.exe /nologo /Emain /T${shadertype}_6_0 $<IF:$<CONFIG:DEBUG>,/Od,/O3> /Zi /Fo ${CMAKE_BINARY_DIR}/${FILE_WE}.cso /Fd ${CMAKE_BINARY_DIR}/${FILE_WE}.pdb ${FILE}
    MAIN_DEPENDENCY ${FILE}
    COMMENT "HLSL ${FILE}"
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    VERBATIM)
endforeach(FILE)

add_library(library2 ${SOURCES} ${CSO_FILES})

This second version only builds a specific output file if it's source changed, but generally speaking you don't need this level of granularity with shaders.

Chuck Walbourn
  • 38,259
  • 2
  • 58
  • 81