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()
6
  • 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. Commented May 28, 2022 at 19:30
  • @Tsyvarev I added one of my attempts. let me know if you need more info. Commented May 28, 2022 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. Commented May 28, 2022 at 22:41
  • @Tsyvarev Ok, I tried that. But it still won't work. The command is always triggered. Commented May 29, 2022 at 6: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. Commented May 29, 2022 at 8:00

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.