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:
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()
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.