Search code examples
cmakeglslninja

CMake invocation of GLSLC with respect to includes/dependencies


I'm using glslc to compile GLSL shaders with #includes (not part of the core spec IIRC but supported in shaderc, which is the engine behind glslc, distributed with the LunarG Vulkan SDK) into SPIR-V for Vulkan and GL 4.5. glslc emits gcc-style depsfiles ([my_shader].[ext].d) files containing dependency info.

My project is built with cmake/ninja/MSVC 2017.

Today, I use a cmake custom_command to invoke glslc when a shader has changed on disk, as a post-build step to my primary target. However, this doesn't catch the changes in included files (isn't at all aware of the .d files or their contents), so rebuilding shaders when an included glsl file is changed can trip up myself and other people on my team.

It looks like ninja can invoke arbitrary compilers, and since ninja knows how to handle the depsfiles, I should be able to coerce ninja into running glslc -- unsure about other build systems since right now we're standardized on ninja.

So how can I tell cmake to configure ninja to use glslc for a specific target? Or is there a paradigmatic way to get this done? It looks like a cmake pull request to add support for glslc as a compiler didn't make it in to cmake circa 2016, so whatever I do is going to be a workaround.


Solution

  • CMake understands depfiles when used in conjunction with ninja.

    DEPFILE

    Specify a .d depfile for the Ninja generator. A .d file holds dependencies usually emitted by the custom command itself. Using DEPFILE with other generators than Ninja is an error.

    add_custom_command(
        OUTPUT ${source}.h
        DEPENDS ${source}
        COMMAND
            glslc
            -MD -MF ${source}.d
            -o ${source}.h -mfmt=num
            --target-env=opengl
            ${CMAKE_CURRENT_SOURCE_DIR}/${source}
        DEPFILE ${source}.d
    )
    
      -M                Generate make dependencies. Implies -E and -w.
      -MM               An alias for -M.
      -MD               Generate make dependencies and compile.
      -MF <file>        Write dependency output to the given file.
      -MT <target>      Specify the target of the rule emitted by dependency
                        generation.
    

    EDIT: Getting fancier

    find_package(Vulkan COMPONENTS glslc)
    find_program(glslc_executable NAMES glslc HINTS Vulkan::glslc)
    
    function(compile_shader target)
        cmake_parse_arguments(PARSE_ARGV 1 arg "" "ENV;FORMAT" "SOURCES")
        foreach(source ${arg_SOURCES})
            add_custom_command(
                OUTPUT ${source}.${arg_FORMAT}
                DEPENDS ${source}
                DEPFILE ${source}.d
                COMMAND
                    ${glslc_executable}
                    $<$<BOOL:${arg_ENV}>:--target-env=${arg_ENV}>
                    $<$<BOOL:${arg_FORMAT}>:-mfmt=${arg_FORMAT}>
                    -MD -MF ${source}.d
                    -o ${source}.${arg_FORMAT}
                    ${CMAKE_CURRENT_SOURCE_DIR}/${source}
            )
            target_sources(${target} PRIVATE ${source}.${arg_FORMAT})
        endforeach()
    endfunction()
    
    add_executable(dummy dummy.c)
    compile_shader(dummy
        ENV opengl
        FORMAT num
        SOURCES
            dummy.vert
            dummy.frag
    )