From 08f7ae4265b323d364701bf70ed1fe8f5e299d5b Mon Sep 17 00:00:00 2001 From: David Chavez Date: Mon, 15 Dec 2025 20:19:13 +0100 Subject: [PATCH 1/8] Add helpers for compiling shaders for downstream --- cmake/PlumeInternalShaders.cmake | 33 ++-- cmake/PlumeShaders.cmake | 221 ++++++++++++++++++++++++ cmake/modules/PlumeDXC.cmake | 104 ++++++++++++ cmake/modules/PlumeFileToC.cmake | 44 +++++ examples/CMakeLists.txt | 16 +- examples/cmake/ShaderCompilation.cmake | 223 ------------------------- examples/cube/CMakeLists.txt | 8 +- examples/triangle/CMakeLists.txt | 8 +- 8 files changed, 391 insertions(+), 266 deletions(-) create mode 100644 cmake/PlumeShaders.cmake create mode 100644 cmake/modules/PlumeDXC.cmake create mode 100644 cmake/modules/PlumeFileToC.cmake delete mode 100644 examples/cmake/ShaderCompilation.cmake diff --git a/cmake/PlumeInternalShaders.cmake b/cmake/PlumeInternalShaders.cmake index 3a60d26..d997853 100644 --- a/cmake/PlumeInternalShaders.cmake +++ b/cmake/PlumeInternalShaders.cmake @@ -1,27 +1,9 @@ # PlumeInternalShaders.cmake # Internal shader compilation for Plume's built-in shaders (clear, resolve) -# Build the file_to_c tool for the host system -function(plume_build_file_to_c) - if(TARGET plume_file_to_c) - return() - endif() - - set(FILE_TO_C_SOURCE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/tools/file_to_c.cpp") +include("${CMAKE_CURRENT_LIST_DIR}/modules/PlumeFileToC.cmake") - add_executable(plume_file_to_c ${FILE_TO_C_SOURCE}) - set_target_properties(plume_file_to_c PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plume_tools" - ) - - if(APPLE) - set_target_properties(plume_file_to_c PROPERTIES - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-" - ) - endif() -endfunction() - -# Compile Metal shaders to embedded C headers +# Compile Plume's internal Metal shaders to embedded C headers function(plume_compile_metal_shaders TARGET_NAME) if(NOT APPLE) return() @@ -37,7 +19,14 @@ function(plume_compile_metal_shaders TARGET_NAME) # Create output directory file(MAKE_DIRECTORY "${OUTPUT_DIR}") - # List of shaders to compile + # Get deployment target for Metal compilation + if(CMAKE_OSX_DEPLOYMENT_TARGET) + set(METAL_VERSION_FLAG "-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + else() + set(METAL_VERSION_FLAG "") + endif() + + # List of internal shaders to compile set(PLUME_METAL_SHADERS plume_clear plume_resolve @@ -56,7 +45,7 @@ function(plume_compile_metal_shaders TARGET_NAME) # Compile Metal to IR add_custom_command( OUTPUT ${IR_OUTPUT} - COMMAND xcrun -sdk macosx metal -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET} -o ${IR_OUTPUT} -c ${SHADER_SOURCE} + COMMAND xcrun -sdk macosx metal ${METAL_VERSION_FLAG} -o ${IR_OUTPUT} -c ${SHADER_SOURCE} DEPENDS ${SHADER_SOURCE} COMMENT "Compiling ${SHADER_NAME}.metal to IR" VERBATIM diff --git a/cmake/PlumeShaders.cmake b/cmake/PlumeShaders.cmake new file mode 100644 index 0000000..82323de --- /dev/null +++ b/cmake/PlumeShaders.cmake @@ -0,0 +1,221 @@ +# PlumeShaders.cmake +# Public shader compilation API for Plume RHI +# +# Usage: +# include(path/to/plume/cmake/PlumeShaders.cmake) +# plume_shaders_init() +# +# plume_compile_vertex_shader(my_target shaders/main.vert.hlsl mainVert VSMain) +# plume_compile_pixel_shader(my_target shaders/main.frag.hlsl mainFrag PSMain) +# plume_compile_compute_shader(my_target shaders/compute.hlsl computeShader CSMain) +# +# # Metal shaders (Apple only) +# if(APPLE) +# plume_compile_metal_shader(my_target shaders/main.metal mainShader) +# endif() +# +# Bring your own DXC: +# set(PLUME_DXC_EXECUTABLE "/path/to/dxc") +# set(PLUME_DXC_LIB_DIR "/path/to/lib") # Required on macOS/Linux for dylib/so +# plume_shaders_init(FETCH_DXC OFF) +# +# Output: +# HLSL shaders compile to: +# - SPIR-V (all platforms): {OUTPUT_NAME}BlobSPIRV in shaders/{OUTPUT_NAME}.hlsl.spirv.h +# - DXIL (Windows only): {OUTPUT_NAME}BlobDXIL in shaders/{OUTPUT_NAME}.hlsl.dxil.h +# Metal shaders compile to: +# - metallib: {OUTPUT_NAME}BlobMSL in shaders/{OUTPUT_NAME}.metal.h + +include("${CMAKE_CURRENT_LIST_DIR}/modules/PlumeFileToC.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/modules/PlumeDXC.cmake") + +# Initialize shader compilation infrastructure +# Call this once before using other plume_compile_* functions +# +# Options: +# FETCH_DXC - If ON (default), fetches DXC binaries automatically. +# Set to OFF if you want to provide your own DXC by setting +# PLUME_DXC_EXECUTABLE and optionally PLUME_DXC_LIB_DIR. +function(plume_shaders_init) + cmake_parse_arguments(PARSE_ARGV 0 ARG "" "FETCH_DXC" "") + + # Default FETCH_DXC to ON if not specified + if(NOT DEFINED ARG_FETCH_DXC) + set(ARG_FETCH_DXC ON) + endif() + + if(ARG_FETCH_DXC) + plume_fetch_dxc() + endif() + + plume_build_file_to_c() + + # Create output directory + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/shaders") +endfunction() + +# Internal: Compile HLSL to a specific format (spirv or dxil) +function(_plume_compile_hlsl_impl TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME OUTPUT_FORMAT ENTRY_POINT) + plume_get_dxc_command(DXC_CMD) + + if(OUTPUT_FORMAT STREQUAL "spirv") + set(OUTPUT_EXT "spv") + set(BLOB_SUFFIX "SPIRV") + set(FORMAT_FLAGS ${PLUME_DXC_SPV_OPTS}) + elseif(OUTPUT_FORMAT STREQUAL "dxil") + set(OUTPUT_EXT "dxil") + set(BLOB_SUFFIX "DXIL") + set(FORMAT_FLAGS ${PLUME_DXC_DXIL_OPTS}) + else() + message(FATAL_ERROR "Unknown output format: ${OUTPUT_FORMAT}") + endif() + + set(SHADER_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.${OUTPUT_EXT}") + set(C_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.${OUTPUT_FORMAT}.c") + set(H_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.${OUTPUT_FORMAT}.h") + + # Determine shader profile and extra args based on type + if(SHADER_TYPE STREQUAL "vertex") + set(SHADER_PROFILE "vs_6_0") + set(DXC_EXTRA_ARGS "-fvk-invert-y") + elseif(SHADER_TYPE STREQUAL "pixel" OR SHADER_TYPE STREQUAL "fragment") + set(SHADER_PROFILE "ps_6_0") + set(DXC_EXTRA_ARGS "") + elseif(SHADER_TYPE STREQUAL "compute") + set(SHADER_PROFILE "cs_6_0") + set(DXC_EXTRA_ARGS "") + elseif(SHADER_TYPE STREQUAL "ray") + set(SHADER_PROFILE "lib_6_3") + set(DXC_EXTRA_ARGS ${PLUME_DXC_RT_OPTS}) + else() + message(FATAL_ERROR "Unknown shader type: ${SHADER_TYPE}. Use: vertex, pixel/fragment, compute, or ray") + endif() + + set(BLOB_NAME "${OUTPUT_NAME}Blob${BLOB_SUFFIX}") + + # Compile using DXC + add_custom_command( + OUTPUT "${SHADER_OUTPUT}" + COMMAND ${DXC_CMD} ${PLUME_DXC_COMMON_OPTS} -E ${ENTRY_POINT} -T ${SHADER_PROFILE} + ${FORMAT_FLAGS} ${DXC_EXTRA_ARGS} -Fo "${SHADER_OUTPUT}" "${SHADER_SOURCE}" + DEPENDS "${SHADER_SOURCE}" + COMMENT "Compiling ${SHADER_TYPE} shader ${OUTPUT_NAME} to ${OUTPUT_FORMAT}" + VERBATIM + ) + + # Generate C header + add_custom_command( + OUTPUT "${C_OUTPUT}" "${H_OUTPUT}" + COMMAND plume_file_to_c "${SHADER_OUTPUT}" "${BLOB_NAME}" "${C_OUTPUT}" "${H_OUTPUT}" + DEPENDS "${SHADER_OUTPUT}" plume_file_to_c + COMMENT "Generating C header for ${OUTPUT_NAME} ${OUTPUT_FORMAT}" + VERBATIM + ) + + target_sources(${TARGET_NAME} PRIVATE "${C_OUTPUT}") + target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}") +endfunction() + +# Internal: Compile Metal shader to metallib +function(_plume_compile_metal_impl TARGET_NAME SHADER_SOURCE OUTPUT_NAME) + set(IR_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.ir") + set(METALLIB_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metallib") + set(C_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metal.c") + set(H_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metal.h") + + # Get deployment target for Metal compilation + if(CMAKE_OSX_DEPLOYMENT_TARGET) + set(METAL_VERSION_FLAG "-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + else() + set(METAL_VERSION_FLAG "") + endif() + + # Compile Metal to IR + add_custom_command( + OUTPUT "${IR_OUTPUT}" + COMMAND xcrun -sdk macosx metal ${METAL_VERSION_FLAG} -o "${IR_OUTPUT}" -c "${SHADER_SOURCE}" + DEPENDS "${SHADER_SOURCE}" + COMMENT "Compiling Metal shader ${OUTPUT_NAME} to IR" + VERBATIM + ) + + # Link IR to metallib + add_custom_command( + OUTPUT "${METALLIB_OUTPUT}" + COMMAND xcrun -sdk macosx metallib "${IR_OUTPUT}" -o "${METALLIB_OUTPUT}" + DEPENDS "${IR_OUTPUT}" + COMMENT "Linking ${OUTPUT_NAME} to metallib" + VERBATIM + ) + + # Generate C header + add_custom_command( + OUTPUT "${C_OUTPUT}" "${H_OUTPUT}" + COMMAND plume_file_to_c "${METALLIB_OUTPUT}" "${OUTPUT_NAME}BlobMSL" "${C_OUTPUT}" "${H_OUTPUT}" + DEPENDS "${METALLIB_OUTPUT}" plume_file_to_c + COMMENT "Generating C header for Metal shader ${OUTPUT_NAME}" + VERBATIM + ) + + target_sources(${TARGET_NAME} PRIVATE "${C_OUTPUT}") + target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}") +endfunction() + +# ============================================================================ +# Public API +# ============================================================================ + +# Compile a shader and add it to a target +# Usage: plume_compile_shader(TARGET SOURCE TYPE OUTPUT_NAME ENTRY_POINT) +# TARGET - CMake target to add shader to +# SOURCE - Path to shader source file (.hlsl or .metal) +# TYPE - Shader type: vertex, pixel, compute, or ray +# OUTPUT_NAME - Base name for output files (e.g., "mainVert") +# ENTRY_POINT - Shader entry point function name (e.g., "VSMain") +function(plume_compile_shader TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME ENTRY_POINT) + get_filename_component(SHADER_EXT "${SHADER_SOURCE}" EXT) + + if(SHADER_EXT MATCHES "\\.metal$") + if(APPLE) + _plume_compile_metal_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${OUTPUT_NAME}) + endif() + elseif(SHADER_EXT MATCHES "\\.hlsl$") + # Always compile to SPIR-V + _plume_compile_hlsl_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${SHADER_TYPE} ${OUTPUT_NAME} "spirv" ${ENTRY_POINT}) + + # Also compile to DXIL on Windows + if(WIN32) + _plume_compile_hlsl_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${SHADER_TYPE} ${OUTPUT_NAME} "dxil" ${ENTRY_POINT}) + endif() + else() + message(WARNING "Unsupported shader extension '${SHADER_EXT}' for ${SHADER_SOURCE}. Use .hlsl or .metal") + endif() +endfunction() + +# Compile a vertex shader +function(plume_compile_vertex_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME ENTRY_POINT) + plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "vertex" ${OUTPUT_NAME} ${ENTRY_POINT}) +endfunction() + +# Compile a pixel/fragment shader +function(plume_compile_pixel_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME ENTRY_POINT) + plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "pixel" ${OUTPUT_NAME} ${ENTRY_POINT}) +endfunction() + +# Compile a compute shader +function(plume_compile_compute_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME ENTRY_POINT) + plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "compute" ${OUTPUT_NAME} ${ENTRY_POINT}) +endfunction() + +# Compile a ray tracing shader +function(plume_compile_ray_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME ENTRY_POINT) + plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "ray" ${OUTPUT_NAME} ${ENTRY_POINT}) +endfunction() + +# Compile a Metal shader (Apple only, no-op on other platforms) +# Usage: plume_compile_metal_shader(TARGET SOURCE OUTPUT_NAME) +function(plume_compile_metal_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME) + if(APPLE) + _plume_compile_metal_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${OUTPUT_NAME}) + endif() +endfunction() diff --git a/cmake/modules/PlumeDXC.cmake b/cmake/modules/PlumeDXC.cmake new file mode 100644 index 0000000..9841a52 --- /dev/null +++ b/cmake/modules/PlumeDXC.cmake @@ -0,0 +1,104 @@ +# PlumeDXC.cmake +# Fetches and configures DXC (DirectX Shader Compiler) for HLSL compilation + +include(FetchContent) + +# Set up common DXC options (called regardless of fetch) +function(_plume_setup_dxc_options) + if(DEFINED PLUME_DXC_COMMON_OPTS) + return() + endif() + + set(PLUME_DXC_COMMON_OPTS "-I${CMAKE_SOURCE_DIR}" CACHE INTERNAL "DXC common options") + set(PLUME_DXC_DXIL_OPTS "-Wno-ignored-attributes" CACHE INTERNAL "DXC DXIL options") + set(PLUME_DXC_SPV_OPTS "-spirv;-fspv-target-env=vulkan1.0;-fvk-use-dx-layout" CACHE INTERNAL "DXC SPIR-V options") + set(PLUME_DXC_RT_OPTS "-D;RT_SHADER;-T;lib_6_3;-fspv-target-env=vulkan1.1spirv1.4;-fspv-extension=SPV_KHR_ray_tracing;-fspv-extension=SPV_EXT_descriptor_indexing" CACHE INTERNAL "DXC ray tracing options") +endfunction() + +# Fetch DXC binaries +function(plume_fetch_dxc) + if(DEFINED PLUME_DXC_EXECUTABLE) + _plume_setup_dxc_options() + return() + endif() + + FetchContent_Declare( + plume_dxc + GIT_REPOSITORY https://github.com/renderbag/dxc-bin.git + GIT_TAG 781065589d5dba23598b746b3d2e457e985b1442 + ) + FetchContent_MakeAvailable(plume_dxc) + + # Set up DXC paths based on platform + if(WIN32) + set(PLUME_DXC_EXECUTABLE "${plume_dxc_SOURCE_DIR}/bin/x64/dxc.exe" CACHE INTERNAL "DXC executable") + set(PLUME_DXC_LIB_DIR "" CACHE INTERNAL "DXC library directory") + + # Copy DLLs that must be next to the executable + if(EXISTS "${plume_dxc_SOURCE_DIR}/bin/x64/dxcompiler.dll") + configure_file("${plume_dxc_SOURCE_DIR}/bin/x64/dxcompiler.dll" "${CMAKE_BINARY_DIR}/bin/dxcompiler.dll" COPYONLY) + endif() + if(EXISTS "${plume_dxc_SOURCE_DIR}/bin/x64/dxil.dll") + configure_file("${plume_dxc_SOURCE_DIR}/bin/x64/dxil.dll" "${CMAKE_BINARY_DIR}/bin/dxil.dll" COPYONLY) + endif() + elseif(APPLE) + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(PLUME_DXC_EXECUTABLE "${plume_dxc_SOURCE_DIR}/bin/x64/dxc-macos" CACHE INTERNAL "DXC executable") + set(PLUME_DXC_LIB_DIR "${plume_dxc_SOURCE_DIR}/lib/x64" CACHE INTERNAL "DXC library directory") + else() + set(PLUME_DXC_EXECUTABLE "${plume_dxc_SOURCE_DIR}/bin/arm64/dxc-macos" CACHE INTERNAL "DXC executable") + set(PLUME_DXC_LIB_DIR "${plume_dxc_SOURCE_DIR}/lib/arm64" CACHE INTERNAL "DXC library directory") + endif() + + # Ensure executable permission + if(EXISTS "${PLUME_DXC_EXECUTABLE}") + execute_process(COMMAND chmod +x "${PLUME_DXC_EXECUTABLE}") + else() + message(FATAL_ERROR "DXC not found at ${PLUME_DXC_EXECUTABLE}") + endif() + else() + # Linux + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(PLUME_DXC_EXECUTABLE "${plume_dxc_SOURCE_DIR}/bin/x64/dxc-linux" CACHE INTERNAL "DXC executable") + set(PLUME_DXC_LIB_DIR "${plume_dxc_SOURCE_DIR}/lib/x64" CACHE INTERNAL "DXC library directory") + else() + set(PLUME_DXC_EXECUTABLE "${plume_dxc_SOURCE_DIR}/bin/arm64/dxc-linux" CACHE INTERNAL "DXC executable") + set(PLUME_DXC_LIB_DIR "${plume_dxc_SOURCE_DIR}/lib/arm64" CACHE INTERNAL "DXC library directory") + endif() + + # Ensure executable permission + if(EXISTS "${PLUME_DXC_EXECUTABLE}") + execute_process(COMMAND chmod +x "${PLUME_DXC_EXECUTABLE}") + else() + message(FATAL_ERROR "DXC not found at ${PLUME_DXC_EXECUTABLE}") + endif() + endif() + + _plume_setup_dxc_options() +endfunction() + +# Get the DXC command with proper environment setup +function(plume_get_dxc_command OUT_VAR) + if(NOT DEFINED PLUME_DXC_EXECUTABLE) + message(FATAL_ERROR "PLUME_DXC_EXECUTABLE not set. Call plume_shaders_init() or set PLUME_DXC_EXECUTABLE manually.") + endif() + + # Ensure options are set up + _plume_setup_dxc_options() + + if(WIN32) + set(${OUT_VAR} "${PLUME_DXC_EXECUTABLE}" PARENT_SCOPE) + elseif(APPLE) + if(PLUME_DXC_LIB_DIR) + set(${OUT_VAR} "${CMAKE_COMMAND}" -E env "DYLD_LIBRARY_PATH=${PLUME_DXC_LIB_DIR}" "${PLUME_DXC_EXECUTABLE}" PARENT_SCOPE) + else() + set(${OUT_VAR} "${PLUME_DXC_EXECUTABLE}" PARENT_SCOPE) + endif() + else() + if(PLUME_DXC_LIB_DIR) + set(${OUT_VAR} "${CMAKE_COMMAND}" -E env "LD_LIBRARY_PATH=${PLUME_DXC_LIB_DIR}" "${PLUME_DXC_EXECUTABLE}" PARENT_SCOPE) + else() + set(${OUT_VAR} "${PLUME_DXC_EXECUTABLE}" PARENT_SCOPE) + endif() + endif() +endfunction() diff --git a/cmake/modules/PlumeFileToC.cmake b/cmake/modules/PlumeFileToC.cmake new file mode 100644 index 0000000..f0b0c68 --- /dev/null +++ b/cmake/modules/PlumeFileToC.cmake @@ -0,0 +1,44 @@ +# PlumeFileToC.cmake +# Builds the file_to_c tool for embedding binary files as C arrays + +# Build the file_to_c tool for the host system +function(plume_build_file_to_c) + if(TARGET plume_file_to_c) + return() + endif() + + # Find the source file relative to this module + set(FILE_TO_C_SOURCE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../tools/file_to_c.cpp") + + if(NOT EXISTS "${FILE_TO_C_SOURCE}") + message(FATAL_ERROR "plume file_to_c.cpp not found at ${FILE_TO_C_SOURCE}") + endif() + + add_executable(plume_file_to_c ${FILE_TO_C_SOURCE}) + set_target_properties(plume_file_to_c PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plume_tools" + ) + + if(APPLE) + set_target_properties(plume_file_to_c PROPERTIES + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-" + ) + endif() +endfunction() + +# Convert a binary file to a C header +# Usage: plume_file_to_c_header(INPUT_FILE OUTPUT_C OUTPUT_H VARIABLE_NAME) +function(plume_file_to_c_header INPUT_FILE VARIABLE_NAME OUTPUT_C OUTPUT_H) + plume_build_file_to_c() + + get_filename_component(OUTPUT_DIR "${OUTPUT_C}" DIRECTORY) + file(MAKE_DIRECTORY "${OUTPUT_DIR}") + + add_custom_command( + OUTPUT "${OUTPUT_C}" "${OUTPUT_H}" + COMMAND plume_file_to_c "${INPUT_FILE}" "${VARIABLE_NAME}" "${OUTPUT_C}" "${OUTPUT_H}" + DEPENDS "${INPUT_FILE}" plume_file_to_c + COMMENT "Generating C header for ${VARIABLE_NAME}" + VERBATIM + ) +endfunction() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 69aca59..e1c2a18 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -6,20 +6,10 @@ set(PLUME_SDL_VULKAN_ENABLED ON CACHE BOOL "Enable SDL Vulkan integration" FORCE # Find SDL2 (required for examples) find_package(SDL2 REQUIRED) -# Include custom ShaderCompilation module -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -include(ShaderCompilation) - -# Create shaders directory for the examples -file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/shaders) +# Initialize shader compilation +include(${CMAKE_SOURCE_DIR}/cmake/PlumeShaders.cmake) +plume_shaders_init() # Add example subdirectories add_subdirectory(triangle) add_subdirectory(cube) - -# Set up a custom command to copy the shader files to the bin directory -add_custom_target(copy_shaders ALL - COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin/shaders - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_BINARY_DIR}/shaders ${CMAKE_BINARY_DIR}/bin/shaders - COMMENT "Copying shader files to bin directory" -) diff --git a/examples/cmake/ShaderCompilation.cmake b/examples/cmake/ShaderCompilation.cmake deleted file mode 100644 index dcfd13e..0000000 --- a/examples/cmake/ShaderCompilation.cmake +++ /dev/null @@ -1,223 +0,0 @@ -# Shader compilation functions for Plume -# Using DXC for HLSL to SPIR-V compilation and Metal for macOS - -# Build file_to_c tool (shared with plume core) -include(${CMAKE_SOURCE_DIR}/cmake/PlumeInternalShaders.cmake) -plume_build_file_to_c() - -# Fetch DXC for shader compilation -include(FetchContent) -FetchContent_Declare( - dxc - GIT_REPOSITORY https://github.com/renderbag/dxc-bin.git - GIT_TAG 781065589d5dba23598b746b3d2e457e985b1442 -) -FetchContent_MakeAvailable(dxc) - -# Set up DXC paths based on platform -if(WIN32) - set(DXC "${dxc_SOURCE_DIR}/bin/x64/dxc.exe") - - # Dependencies that must be next to the DLL if on Windows - if(EXISTS "${dxc_SOURCE_DIR}/bin/x64/dxcompiler.dll") - configure_file("${dxc_SOURCE_DIR}/bin/x64/dxcompiler.dll" "${CMAKE_BINARY_DIR}/bin/dxcompiler.dll" COPYONLY) - endif() - if(EXISTS "${dxc_SOURCE_DIR}/bin/x64/dxil.dll") - configure_file("${dxc_SOURCE_DIR}/bin/x64/dxil.dll" "${CMAKE_BINARY_DIR}/bin/dxil.dll" COPYONLY) - endif() -elseif(APPLE) - # On macOS, find the right DXC binary based on architecture - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") - set(DXC_EXECUTABLE "${dxc_SOURCE_DIR}/bin/x64/dxc-macos") - set(DXC_LIB_DIR "${dxc_SOURCE_DIR}/lib/x64") - else() - set(DXC_EXECUTABLE "${dxc_SOURCE_DIR}/bin/arm64/dxc-macos") - set(DXC_LIB_DIR "${dxc_SOURCE_DIR}/lib/arm64") - endif() - - # Make sure the executable is accessible and has execute permissions - if(EXISTS "${DXC_EXECUTABLE}") - # Set executable permission if needed - execute_process(COMMAND chmod +x "${DXC_EXECUTABLE}") - - # Set DXC command with DYLD_LIBRARY_PATH - set(DXC "DYLD_LIBRARY_PATH=${DXC_LIB_DIR}" "${DXC_EXECUTABLE}") - else() - message(FATAL_ERROR "DXC not found at ${DXC_EXECUTABLE} - required for shader compilation") - endif() -else() - # Linux - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") - set(DXC_EXECUTABLE "${dxc_SOURCE_DIR}/bin/x64/dxc-linux") - set(DXC_LIB_DIR "${dxc_SOURCE_DIR}/lib/x64") - else() - set(DXC_EXECUTABLE "${dxc_SOURCE_DIR}/bin/arm64/dxc-linux") - set(DXC_LIB_DIR "${dxc_SOURCE_DIR}/lib/arm64") - endif() - - # Make sure the executable is accessible and has execute permissions - if(EXISTS "${DXC_EXECUTABLE}") - # Set executable permission if needed - execute_process(COMMAND chmod +x "${DXC_EXECUTABLE}") - set(DXC "LD_LIBRARY_PATH=${DXC_LIB_DIR}" "${DXC_EXECUTABLE}") - else() - message(FATAL_ERROR "DXC not found at ${DXC_EXECUTABLE} - required for shader compilation") - endif() -endif() - -# Common DXC options -set(DXC_COMMON_OPTS "-I${CMAKE_SOURCE_DIR}") -set(DXC_DXIL_OPTS "-Wno-ignored-attributes") -set(DXC_SPV_OPTS "-spirv" "-fspv-target-env=vulkan1.0" "-fvk-use-dx-layout") -set(DXC_RT_OPTS "-D" "RT_SHADER" "-T" "lib_6_3" "-fspv-target-env=vulkan1.1spirv1.4" "-fspv-extension=SPV_KHR_ray_tracing" "-fspv-extension=SPV_EXT_descriptor_indexing") - -# Function to compile HLSL using DXC with common parameters -function(build_shader_dxc_impl TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME OUTPUT_FORMAT ENTRY_POINT) - # Create unique output names based on format - if(OUTPUT_FORMAT STREQUAL "spirv") - set(OUTPUT_EXT "spv") - set(BLOB_SUFFIX "SPIRV") - set(FORMAT_FLAGS ${DXC_SPV_OPTS}) - elseif(OUTPUT_FORMAT STREQUAL "dxil") - set(OUTPUT_EXT "dxil") - set(BLOB_SUFFIX "DXIL") - set(FORMAT_FLAGS ${DXC_DXIL_OPTS}) - else() - message(FATAL_ERROR "Unknown output format: ${OUTPUT_FORMAT}") - endif() - - set(SHADER_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.${OUTPUT_EXT}") - set(C_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.${OUTPUT_FORMAT}.c") - set(H_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.${OUTPUT_FORMAT}.h") - - # Create output directory - file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/shaders") - - # Determine the shader options based on type - if(SHADER_TYPE STREQUAL "vertex") - set(SHADER_PROFILE "vs_6_0") - set(DXC_EXTRA_ARGS "-fvk-invert-y") # Only include invert-y for vertex shaders - elseif(SHADER_TYPE STREQUAL "fragment") - set(SHADER_PROFILE "ps_6_0") - set(DXC_EXTRA_ARGS "") - elseif(SHADER_TYPE STREQUAL "compute") - set(SHADER_PROFILE "cs_6_0") - set(DXC_EXTRA_ARGS "") - elseif(SHADER_TYPE STREQUAL "ray") - set(SHADER_PROFILE "lib_6_3") - set(DXC_EXTRA_ARGS ${DXC_RT_OPTS}) - else() - message(FATAL_ERROR "Unknown shader type: ${SHADER_TYPE}") - endif() - - set(BLOB_NAME "${OUTPUT_NAME}Blob${BLOB_SUFFIX}") - - # Compile using DXC - add_custom_command( - OUTPUT ${SHADER_OUTPUT} - COMMAND ${DXC} ${DXC_COMMON_OPTS} -E ${ENTRY_POINT} -T ${SHADER_PROFILE} ${FORMAT_FLAGS} ${DXC_EXTRA_ARGS} - -Fo ${SHADER_OUTPUT} ${SHADER_SOURCE} - DEPENDS ${SHADER_SOURCE} - COMMENT "Compiling ${SHADER_TYPE} shader ${SHADER_SOURCE} to ${OUTPUT_FORMAT} using DXC" - ) - - # Generate C header - add_custom_command( - OUTPUT "${C_OUTPUT}" "${H_OUTPUT}" - COMMAND plume_file_to_c ${SHADER_OUTPUT} "${BLOB_NAME}" "${C_OUTPUT}" "${H_OUTPUT}" - DEPENDS ${SHADER_OUTPUT} plume_file_to_c - COMMENT "Generating C header for ${OUTPUT_FORMAT} shader ${OUTPUT_NAME}" - ) - - # Add the generated source file to the target - target_sources(${TARGET_NAME} PRIVATE "${C_OUTPUT}") - - # Make sure the target can find the generated header - target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}") -endfunction() - -# Function to compile HLSL to SPIR-V using DXC -function(build_shader_spirv_impl TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME ENTRY_POINT) - build_shader_dxc_impl(${TARGET_NAME} ${SHADER_SOURCE} ${SHADER_TYPE} ${OUTPUT_NAME} "spirv" ${ENTRY_POINT}) -endfunction() - -# Function to compile HLSL to DXIL using DXC -function(build_shader_dxil_impl TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME ENTRY_POINT) - build_shader_dxc_impl(${TARGET_NAME} ${SHADER_SOURCE} ${SHADER_TYPE} ${OUTPUT_NAME} "dxil" ${ENTRY_POINT}) -endfunction() - -# Function to compile Metal shaders -function(build_shader_metal_impl TARGET_NAME SHADER_SOURCE OUTPUT_NAME) - # Create unique output names - set(METALLIB_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metallib") - set(IR_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.ir") - set(METAL_C_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metal.c") - set(METAL_H_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metal.h") - - # Create output directory - file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/shaders") - - # Compile Metal to IR - add_custom_command( - OUTPUT ${IR_OUTPUT} - COMMAND xcrun -sdk macosx metal -o ${IR_OUTPUT} -c ${SHADER_SOURCE} - DEPENDS ${SHADER_SOURCE} - COMMENT "Compiling Metal shader to IR" - ) - - # Compile IR to metallib - add_custom_command( - OUTPUT ${METALLIB_OUTPUT} - COMMAND xcrun -sdk macosx metallib ${IR_OUTPUT} -o ${METALLIB_OUTPUT} - DEPENDS ${IR_OUTPUT} - COMMENT "Compiling Metal IR to metallib" - ) - - # Generate C header - add_custom_command( - OUTPUT "${METAL_C_OUTPUT}" "${METAL_H_OUTPUT}" - COMMAND plume_file_to_c ${METALLIB_OUTPUT} "${OUTPUT_NAME}BlobMSL" "${METAL_C_OUTPUT}" "${METAL_H_OUTPUT}" - DEPENDS ${METALLIB_OUTPUT} plume_file_to_c - COMMENT "Generating C header for Metal shader ${OUTPUT_NAME}" - ) - - # Add the generated source file to the target - target_sources(${TARGET_NAME} PRIVATE "${METAL_C_OUTPUT}") - - # Make sure the target can find the generated header - target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}") -endfunction() - -# Compile a shader based on its type -function(compile_shader TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME ENTRY_POINT) - # Get the file extension to determine the shader language - get_filename_component(SHADER_EXT ${SHADER_SOURCE} EXT) - - # Compile based on extension - if(SHADER_EXT MATCHES ".*\\.metal$") - # Compile Metal shader - if(APPLE) - build_shader_metal_impl(${TARGET_NAME} ${SHADER_SOURCE} ${OUTPUT_NAME}) - endif() - elseif(SHADER_SOURCE MATCHES ".*\\.hlsl$") - # Compile HLSL shader to SPIR-V using DXC - build_shader_spirv_impl(${TARGET_NAME} ${SHADER_SOURCE} ${SHADER_TYPE} ${OUTPUT_NAME} ${ENTRY_POINT}) - - # Also compile to DXIL on Windows - if(WIN32) - build_shader_dxil_impl(${TARGET_NAME} ${SHADER_SOURCE} ${SHADER_TYPE} ${OUTPUT_NAME} ${ENTRY_POINT}) - endif() - else() - message(WARNING "Unsupported shader extension ${SHADER_EXT} for ${SHADER_SOURCE} - only .hlsl and .metal files are supported") - endif() -endfunction() - -function(file_to_c_header INPUT_FILE OUTPUT_FILE VARIABLE_NAME) - add_custom_command( - OUTPUT ${OUTPUT_FILE} - COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/shaders - COMMAND plume_file_to_c ${INPUT_FILE} ${VARIABLE_NAME} ${OUTPUT_FILE} ${OUTPUT_FILE} - DEPENDS ${INPUT_FILE} plume_file_to_c - COMMENT "Converting ${INPUT_FILE} to C header" - ) -endfunction() diff --git a/examples/cube/CMakeLists.txt b/examples/cube/CMakeLists.txt index 28b3a15..50de43e 100644 --- a/examples/cube/CMakeLists.txt +++ b/examples/cube/CMakeLists.txt @@ -40,9 +40,9 @@ set_target_properties(plume_cube PROPERTIES # Compile shaders if(APPLE) - compile_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.vert.metal" "vertex" "cubeVert" "VSMain") - compile_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.frag.metal" "fragment" "cubeFrag" "PSMain") + plume_compile_metal_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.vert.metal" "cubeVert") + plume_compile_metal_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.frag.metal" "cubeFrag") endif() -compile_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.vert.hlsl" "vertex" "cubeVert" "VSMain") -compile_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.frag.hlsl" "fragment" "cubeFrag" "PSMain") +plume_compile_vertex_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.vert.hlsl" "cubeVert" "VSMain") +plume_compile_pixel_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.frag.hlsl" "cubeFrag" "PSMain") diff --git a/examples/triangle/CMakeLists.txt b/examples/triangle/CMakeLists.txt index db527c6..ad81545 100644 --- a/examples/triangle/CMakeLists.txt +++ b/examples/triangle/CMakeLists.txt @@ -40,9 +40,9 @@ set_target_properties(plume_triangle PROPERTIES # Compile shaders if(APPLE) - compile_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.vert.metal" "vertex" "triangleVert" "VSMain") - compile_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.frag.metal" "fragment" "triangleFrag" "PSMain") + plume_compile_metal_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.vert.metal" "triangleVert") + plume_compile_metal_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.frag.metal" "triangleFrag") endif() -compile_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.vert.hlsl" "vertex" "triangleVert" "VSMain") -compile_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.frag.hlsl" "fragment" "triangleFrag" "PSMain") +plume_compile_vertex_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.vert.hlsl" "triangleVert" "VSMain") +plume_compile_pixel_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.frag.hlsl" "triangleFrag" "PSMain") From a0ee3814f7504032129fb497cf5d492e0b4a6f92 Mon Sep 17 00:00:00 2001 From: David Chavez Date: Mon, 15 Dec 2025 20:37:23 +0100 Subject: [PATCH 2/8] Add spirv-cross support --- cmake/PlumeShaders.cmake | 98 ++++++++-- cmake/modules/PlumeSpirvCross.cmake | 64 +++++++ cmake/tools/spirv_cross_msl.cpp | 180 ++++++++++++++++++ examples/cube/CMakeLists.txt | 7 +- examples/cube/shaders/cube.frag.metal | 23 --- examples/cube/shaders/cube.vert.metal | 44 ----- examples/triangle/CMakeLists.txt | 7 +- examples/triangle/shaders/triangle.frag.metal | 13 -- examples/triangle/shaders/triangle.vert.metal | 22 --- 9 files changed, 331 insertions(+), 127 deletions(-) create mode 100644 cmake/modules/PlumeSpirvCross.cmake create mode 100644 cmake/tools/spirv_cross_msl.cpp delete mode 100644 examples/cube/shaders/cube.frag.metal delete mode 100644 examples/cube/shaders/cube.vert.metal delete mode 100644 examples/triangle/shaders/triangle.frag.metal delete mode 100644 examples/triangle/shaders/triangle.vert.metal diff --git a/cmake/PlumeShaders.cmake b/cmake/PlumeShaders.cmake index 82323de..8178b32 100644 --- a/cmake/PlumeShaders.cmake +++ b/cmake/PlumeShaders.cmake @@ -9,25 +9,20 @@ # plume_compile_pixel_shader(my_target shaders/main.frag.hlsl mainFrag PSMain) # plume_compile_compute_shader(my_target shaders/compute.hlsl computeShader CSMain) # -# # Metal shaders (Apple only) -# if(APPLE) -# plume_compile_metal_shader(my_target shaders/main.metal mainShader) -# endif() -# -# Bring your own DXC: +# Bring your own DXC/SPIRV-Cross: # set(PLUME_DXC_EXECUTABLE "/path/to/dxc") # set(PLUME_DXC_LIB_DIR "/path/to/lib") # Required on macOS/Linux for dylib/so -# plume_shaders_init(FETCH_DXC OFF) +# plume_shaders_init(FETCH_DXC OFF FETCH_SPIRV_CROSS OFF) # # Output: # HLSL shaders compile to: # - SPIR-V (all platforms): {OUTPUT_NAME}BlobSPIRV in shaders/{OUTPUT_NAME}.hlsl.spirv.h # - DXIL (Windows only): {OUTPUT_NAME}BlobDXIL in shaders/{OUTPUT_NAME}.hlsl.dxil.h -# Metal shaders compile to: -# - metallib: {OUTPUT_NAME}BlobMSL in shaders/{OUTPUT_NAME}.metal.h +# - Metal (Apple only): {OUTPUT_NAME}BlobMSL in shaders/{OUTPUT_NAME}.metal.h (via SPIR-V cross-compilation) include("${CMAKE_CURRENT_LIST_DIR}/modules/PlumeFileToC.cmake") include("${CMAKE_CURRENT_LIST_DIR}/modules/PlumeDXC.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/modules/PlumeSpirvCross.cmake") # Initialize shader compilation infrastructure # Call this once before using other plume_compile_* functions @@ -36,18 +31,33 @@ include("${CMAKE_CURRENT_LIST_DIR}/modules/PlumeDXC.cmake") # FETCH_DXC - If ON (default), fetches DXC binaries automatically. # Set to OFF if you want to provide your own DXC by setting # PLUME_DXC_EXECUTABLE and optionally PLUME_DXC_LIB_DIR. +# FETCH_SPIRV_CROSS - If ON (default on Apple), fetches and builds SPIRV-Cross. +# Set to OFF to skip SPIR-V to Metal conversion. function(plume_shaders_init) - cmake_parse_arguments(PARSE_ARGV 0 ARG "" "FETCH_DXC" "") + cmake_parse_arguments(PARSE_ARGV 0 ARG "" "FETCH_DXC;FETCH_SPIRV_CROSS" "") # Default FETCH_DXC to ON if not specified if(NOT DEFINED ARG_FETCH_DXC) set(ARG_FETCH_DXC ON) endif() + # Default FETCH_SPIRV_CROSS to ON on Apple, OFF otherwise + if(NOT DEFINED ARG_FETCH_SPIRV_CROSS) + if(APPLE) + set(ARG_FETCH_SPIRV_CROSS ON) + else() + set(ARG_FETCH_SPIRV_CROSS OFF) + endif() + endif() + if(ARG_FETCH_DXC) plume_fetch_dxc() endif() + if(ARG_FETCH_SPIRV_CROSS AND APPLE) + plume_fetch_spirv_cross() + endif() + plume_build_file_to_c() # Create output directory @@ -116,7 +126,62 @@ function(_plume_compile_hlsl_impl TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_N target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}") endfunction() -# Internal: Compile Metal shader to metallib +# Internal: Compile SPIR-V to Metal via spirv-cross +function(_plume_compile_spirv_to_metal_impl TARGET_NAME SPIRV_FILE OUTPUT_NAME) + set(METAL_SOURCE "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metal") + set(IR_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.ir") + set(METALLIB_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metallib") + set(C_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metal.c") + set(H_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metal.h") + + # Get deployment target for Metal compilation + if(CMAKE_OSX_DEPLOYMENT_TARGET) + set(METAL_VERSION_FLAG "-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + else() + set(METAL_VERSION_FLAG "") + endif() + + # Convert SPIR-V to Metal source + add_custom_command( + OUTPUT "${METAL_SOURCE}" + COMMAND plume_spirv_cross_msl "${SPIRV_FILE}" "${METAL_SOURCE}" + DEPENDS "${SPIRV_FILE}" plume_spirv_cross_msl + COMMENT "Converting ${OUTPUT_NAME} SPIR-V to Metal" + VERBATIM + ) + + # Compile Metal to IR + add_custom_command( + OUTPUT "${IR_OUTPUT}" + COMMAND xcrun -sdk macosx metal ${METAL_VERSION_FLAG} -o "${IR_OUTPUT}" -c "${METAL_SOURCE}" + DEPENDS "${METAL_SOURCE}" + COMMENT "Compiling Metal shader ${OUTPUT_NAME} to IR" + VERBATIM + ) + + # Link IR to metallib + add_custom_command( + OUTPUT "${METALLIB_OUTPUT}" + COMMAND xcrun -sdk macosx metallib "${IR_OUTPUT}" -o "${METALLIB_OUTPUT}" + DEPENDS "${IR_OUTPUT}" + COMMENT "Linking ${OUTPUT_NAME} to metallib" + VERBATIM + ) + + # Generate C header + add_custom_command( + OUTPUT "${C_OUTPUT}" "${H_OUTPUT}" + COMMAND plume_file_to_c "${METALLIB_OUTPUT}" "${OUTPUT_NAME}BlobMSL" "${C_OUTPUT}" "${H_OUTPUT}" + DEPENDS "${METALLIB_OUTPUT}" plume_file_to_c + COMMENT "Generating C header for Metal shader ${OUTPUT_NAME}" + VERBATIM + ) + + target_sources(${TARGET_NAME} PRIVATE "${C_OUTPUT}") + target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}") +endfunction() + +# Internal: Compile native Metal shader to metallib (for handwritten .metal files) function(_plume_compile_metal_impl TARGET_NAME SHADER_SOURCE OUTPUT_NAME) set(IR_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.ir") set(METALLIB_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metallib") @@ -183,10 +248,16 @@ function(plume_compile_shader TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME # Always compile to SPIR-V _plume_compile_hlsl_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${SHADER_TYPE} ${OUTPUT_NAME} "spirv" ${ENTRY_POINT}) - # Also compile to DXIL on Windows + # Compile to DXIL on Windows if(WIN32) _plume_compile_hlsl_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${SHADER_TYPE} ${OUTPUT_NAME} "dxil" ${ENTRY_POINT}) endif() + + # Compile SPIR-V to Metal on Apple (if spirv-cross is available) + if(APPLE AND TARGET plume_spirv_cross_msl) + set(SPIRV_FILE "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.spv") + _plume_compile_spirv_to_metal_impl(${TARGET_NAME} "${SPIRV_FILE}" ${OUTPUT_NAME}) + endif() else() message(WARNING "Unsupported shader extension '${SHADER_EXT}' for ${SHADER_SOURCE}. Use .hlsl or .metal") endif() @@ -212,7 +283,8 @@ function(plume_compile_ray_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME ENTRY_PO plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "ray" ${OUTPUT_NAME} ${ENTRY_POINT}) endfunction() -# Compile a Metal shader (Apple only, no-op on other platforms) +# Compile a native Metal shader (Apple only, no-op on other platforms) +# Use this for handwritten .metal files, not for cross-compiled HLSL # Usage: plume_compile_metal_shader(TARGET SOURCE OUTPUT_NAME) function(plume_compile_metal_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME) if(APPLE) diff --git a/cmake/modules/PlumeSpirvCross.cmake b/cmake/modules/PlumeSpirvCross.cmake new file mode 100644 index 0000000..ef8beca --- /dev/null +++ b/cmake/modules/PlumeSpirvCross.cmake @@ -0,0 +1,64 @@ +# PlumeSpirvCross.cmake +# Fetches pre-built SPIRV-Cross and builds our spirv_cross_msl tool + +include(FetchContent) + +# Fetch SPIRV-Cross pre-built binaries and build our tool +function(plume_fetch_spirv_cross) + if(TARGET plume_spirv_cross_msl) + return() + endif() + + FetchContent_Declare( + plume_spirv_cross + GIT_REPOSITORY https://github.com/renderbag/spriv-cross-bin.git + GIT_TAG main + ) + FetchContent_MakeAvailable(plume_spirv_cross) + + # Determine library path based on platform/architecture + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") + set(SPIRV_CROSS_ARCH "x64") + else() + set(SPIRV_CROSS_ARCH "arm64") + endif() + + set(SPIRV_CROSS_LIB_DIR "${plume_spirv_cross_SOURCE_DIR}/lib/${SPIRV_CROSS_ARCH}") + set(SPIRV_CROSS_INCLUDE_DIR "${plume_spirv_cross_SOURCE_DIR}/include") + + # Build our custom spirv_cross_msl tool + set(SPIRV_CROSS_MSL_SOURCE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../tools/spirv_cross_msl.cpp") + + if(NOT EXISTS "${SPIRV_CROSS_MSL_SOURCE}") + message(FATAL_ERROR "plume spirv_cross_msl.cpp not found at ${SPIRV_CROSS_MSL_SOURCE}") + endif() + + add_executable(plume_spirv_cross_msl ${SPIRV_CROSS_MSL_SOURCE}) + target_include_directories(plume_spirv_cross_msl PRIVATE ${SPIRV_CROSS_INCLUDE_DIR}) + + # Link against pre-built static libraries + # Order matters: msl depends on glsl depends on core + if(WIN32) + target_link_libraries(plume_spirv_cross_msl PRIVATE + "${SPIRV_CROSS_LIB_DIR}/spirv-cross-msl.lib" + "${SPIRV_CROSS_LIB_DIR}/spirv-cross-glsl.lib" + "${SPIRV_CROSS_LIB_DIR}/spirv-cross-core.lib" + ) + else() + target_link_libraries(plume_spirv_cross_msl PRIVATE + "${SPIRV_CROSS_LIB_DIR}/libspirv-cross-msl.a" + "${SPIRV_CROSS_LIB_DIR}/libspirv-cross-glsl.a" + "${SPIRV_CROSS_LIB_DIR}/libspirv-cross-core.a" + ) + endif() + + set_target_properties(plume_spirv_cross_msl PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plume_tools" + ) + + if(APPLE) + set_target_properties(plume_spirv_cross_msl PROPERTIES + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-" + ) + endif() +endfunction() diff --git a/cmake/tools/spirv_cross_msl.cpp b/cmake/tools/spirv_cross_msl.cpp new file mode 100644 index 0000000..675d9a1 --- /dev/null +++ b/cmake/tools/spirv_cross_msl.cpp @@ -0,0 +1,180 @@ +// spirv_cross_msl - Converts SPIR-V to Metal Shading Language +// Adapted from RT64's spirv_cross_msl tool + +#include "spirv_msl.hpp" +#include "spirv_parser.hpp" +#include +#include +#include +#include + +static std::vector read_spirv_file_stdin() { +#ifdef _WIN32 + setmode(fileno(stdin), O_BINARY); +#endif + + std::vector buffer; + uint32_t tmp[256]; + size_t ret; + + while ((ret = fread(tmp, sizeof(uint32_t), 256, stdin))) + buffer.insert(buffer.end(), tmp, tmp + ret); + + return buffer; +} + +static std::vector read_spirv_file(const char *path) { + if (path[0] == '-' && path[1] == '\0') + return read_spirv_file_stdin(); + + FILE *file = fopen(path, "rb"); + if (!file) { + fprintf(stderr, "Failed to open SPIR-V file: %s\n", path); + return {}; + } + + fseek(file, 0, SEEK_END); + long len = ftell(file) / sizeof(uint32_t); + rewind(file); + + std::vector spirv(len); + if (fread(spirv.data(), sizeof(uint32_t), len, file) != size_t(len)) + spirv.clear(); + + fclose(file); + return spirv; +} + +static bool write_string_to_file(const char *path, const char *string) { + FILE *file = fopen(path, "w"); + if (!file) { + fprintf(stderr, "Failed to write file: %s\n", path); + return false; + } + + fprintf(file, "%s", string); + fclose(file); + return true; +} + +static int extract_id(const std::string& line) { + std::regex id_regex("\\[\\[id\\((\\d+)\\)\\]\\]"); + std::smatch match; + + if (std::regex_search(line, match, id_regex) && match.size() > 1) { + return std::stoi(match[1].str()); + } + return -1; +} + +// Metal requires contiguous descriptor set buffer IDs. +// spirv-cross may generate non-contiguous IDs (e.g., 0, 2, 5). +// This function fills gaps with dummy padding resources. +static std::string pad_descriptor_sets(const std::string& source) { + std::stringstream result; + std::stringstream current_line; + std::string line; + + bool inside_set = false; + int current_id = 0; + std::string default_resource_prefix = "\tconstant float* _PadResource"; + + std::istringstream source_stream(source); + + while (std::getline(source_stream, line)) { + if (line.find("struct spvDescriptorSetBuffer") != std::string::npos) { + inside_set = true; + current_id = 0; + result << line << std::endl; + std::getline(source_stream, line); // the starting brace '{' + result << line << std::endl; + continue; + } else if (inside_set) { + if (line == "};") { + inside_set = false; + result << line << std::endl; + continue; + } + int line_id = extract_id(line); + if (line_id == -1) { + throw std::runtime_error("Could not extract id from line: " + line); + } + while (current_id != line_id) { + result << default_resource_prefix << current_id << " [[id(" << current_id << ")]];" << std::endl; + current_id++; + } + // Current line id consumes the current id + current_id++; + } + + result << line << std::endl; + } + + return result.str(); +} + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } + + try { + // Load SPIR-V + auto spirv_file = read_spirv_file(argv[1]); + spirv_cross::Parser spirv_parser(std::move(spirv_file)); + spirv_parser.parse(); + + // Initialize MSL compiler + spirv_cross::CompilerMSL msl(std::move(spirv_parser.get_parsed_ir())); + + // Configure MSL options + spirv_cross::CompilerMSL::Options msl_options; + msl_options.msl_version = spirv_cross::CompilerMSL::Options::make_msl_version(2, 1); + msl_options.argument_buffers = true; + msl_options.texture_buffer_native = true; + msl_options.enable_decoration_binding = true; + msl.set_msl_options(msl_options); + + // Map push constants to buffer(8) for all shader stages + // This matches plume's PUSH_CONSTANTS_BINDING_INDEX + spirv_cross::MSLResourceBinding msl_binding_vert; + msl_binding_vert.stage = spv::ExecutionModelVertex; + msl_binding_vert.desc_set = spirv_cross::kPushConstDescSet; + msl_binding_vert.binding = spirv_cross::kPushConstBinding; + msl_binding_vert.msl_buffer = 8; + msl.add_msl_resource_binding(msl_binding_vert); + + spirv_cross::MSLResourceBinding msl_binding_frag; + msl_binding_frag.stage = spv::ExecutionModelFragment; + msl_binding_frag.desc_set = spirv_cross::kPushConstDescSet; + msl_binding_frag.binding = spirv_cross::kPushConstBinding; + msl_binding_frag.msl_buffer = 8; + msl.add_msl_resource_binding(msl_binding_frag); + + spirv_cross::MSLResourceBinding msl_binding_compute; + msl_binding_compute.stage = spv::ExecutionModelGLCompute; + msl_binding_compute.desc_set = spirv_cross::kPushConstDescSet; + msl_binding_compute.binding = spirv_cross::kPushConstBinding; + msl_binding_compute.msl_buffer = 8; + msl.add_msl_resource_binding(msl_binding_compute); + + // Configure common options + spirv_cross::CompilerGLSL::Options common_options; + common_options.vertex.flip_vert_y = true; + msl.set_common_options(common_options); + + // Generate MSL source + std::string source = msl.compile(); + + // Pad descriptor sets to ensure contiguous IDs + std::string padded_source = pad_descriptor_sets(source); + + write_string_to_file(argv[2], padded_source.c_str()); + + return 0; + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + return 1; + } +} diff --git a/examples/cube/CMakeLists.txt b/examples/cube/CMakeLists.txt index 50de43e..43c824b 100644 --- a/examples/cube/CMakeLists.txt +++ b/examples/cube/CMakeLists.txt @@ -38,11 +38,6 @@ set_target_properties(plume_cube PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) -# Compile shaders -if(APPLE) - plume_compile_metal_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.vert.metal" "cubeVert") - plume_compile_metal_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.frag.metal" "cubeFrag") -endif() - +# Compile shaders (HLSL to SPIR-V/DXIL/Metal) plume_compile_vertex_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.vert.hlsl" "cubeVert" "VSMain") plume_compile_pixel_shader(plume_cube "${CMAKE_CURRENT_SOURCE_DIR}/shaders/cube.frag.hlsl" "cubeFrag" "PSMain") diff --git a/examples/cube/shaders/cube.frag.metal b/examples/cube/shaders/cube.frag.metal deleted file mode 100644 index 6e987f0..0000000 --- a/examples/cube/shaders/cube.frag.metal +++ /dev/null @@ -1,23 +0,0 @@ -#include -using namespace metal; - -// Cube texture fragment shader -// Samples from a cubemap texture - -struct PSInput { - float4 position [[position]]; - float3 viewDir; -}; - -// Argument buffer structure matching descriptor set layout -struct DescriptorSet0 { - texturecube cubeTexture [[id(0)]]; - sampler cubeSampler [[id(1)]]; -}; - -fragment float4 PSMain(PSInput in [[stage_in]], - constant DescriptorSet0& descriptors [[buffer(0)]]) { - // Normalize the view direction and sample the cubemap - float3 dir = normalize(in.viewDir); - return descriptors.cubeTexture.sample(descriptors.cubeSampler, dir); -} diff --git a/examples/cube/shaders/cube.vert.metal b/examples/cube/shaders/cube.vert.metal deleted file mode 100644 index 6d90912..0000000 --- a/examples/cube/shaders/cube.vert.metal +++ /dev/null @@ -1,44 +0,0 @@ -#include -using namespace metal; - -// Cube texture vertex shader -// Renders a fullscreen triangle and generates view directions for cubemap sampling - -struct VSOutput { - float4 position [[position]]; - float3 viewDir; -}; - -struct Constants { - float4x4 invViewProj; -}; - -// Fullscreen triangle - no vertex buffer needed -// Uses vertex ID to generate positions -vertex VSOutput VSMain(uint vertexID [[vertex_id]], - constant Constants& constants [[buffer(8)]]) { - VSOutput out; - - // Generate fullscreen triangle vertices - // Vertex 0: (-1, -1), Vertex 1: (3, -1), Vertex 2: (-1, 3) - float2 uv = float2((vertexID << 1) & 2, vertexID & 2); - float2 ndc = uv * 2.0 - 1.0; - - // Flip Y for Metal's coordinate system - ndc.y = -ndc.y; - - out.position = float4(ndc, 0.0, 1.0); - - // Transform NDC to world direction using inverse view-projection - // Use near and far plane points to get a proper ray direction - float4 nearPoint = constants.invViewProj * float4(ndc, -1.0, 1.0); - float4 farPoint = constants.invViewProj * float4(ndc, 1.0, 1.0); - - nearPoint /= nearPoint.w; - farPoint /= farPoint.w; - - // Ray direction from near to far plane - out.viewDir = farPoint.xyz - nearPoint.xyz; - - return out; -} diff --git a/examples/triangle/CMakeLists.txt b/examples/triangle/CMakeLists.txt index ad81545..4b416c8 100644 --- a/examples/triangle/CMakeLists.txt +++ b/examples/triangle/CMakeLists.txt @@ -38,11 +38,6 @@ set_target_properties(plume_triangle PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) -# Compile shaders -if(APPLE) - plume_compile_metal_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.vert.metal" "triangleVert") - plume_compile_metal_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.frag.metal" "triangleFrag") -endif() - +# Compile shaders (HLSL to SPIR-V/DXIL/Metal) plume_compile_vertex_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.vert.hlsl" "triangleVert" "VSMain") plume_compile_pixel_shader(plume_triangle "${CMAKE_CURRENT_SOURCE_DIR}/shaders/triangle.frag.hlsl" "triangleFrag" "PSMain") diff --git a/examples/triangle/shaders/triangle.frag.metal b/examples/triangle/shaders/triangle.frag.metal deleted file mode 100644 index c6edb15..0000000 --- a/examples/triangle/shaders/triangle.frag.metal +++ /dev/null @@ -1,13 +0,0 @@ -#include -using namespace metal; - -// Vertex to fragment data -struct VertexOutput { - float4 position [[position]]; - float4 color; -}; - -// Fragment shader -fragment float4 PSMain(VertexOutput in [[stage_in]]) { - return in.color; -} \ No newline at end of file diff --git a/examples/triangle/shaders/triangle.vert.metal b/examples/triangle/shaders/triangle.vert.metal deleted file mode 100644 index d2c3843..0000000 --- a/examples/triangle/shaders/triangle.vert.metal +++ /dev/null @@ -1,22 +0,0 @@ -#include -using namespace metal; - -// Vertex input -struct VertexInput { - float3 position [[attribute(0)]]; - float4 color [[attribute(1)]]; -}; - -// Vertex to fragment data -struct VertexOutput { - float4 position [[position]]; - float4 color; -}; - -// Vertex shader -vertex VertexOutput VSMain(VertexInput in [[stage_in]]) { - VertexOutput out; - out.position = float4(in.position, 1.0); - out.color = in.color; - return out; -} From 247da2010c197b1652aa93ad962d76553bf16ac3 Mon Sep 17 00:00:00 2001 From: David Chavez Date: Mon, 15 Dec 2025 21:43:44 +0100 Subject: [PATCH 3/8] Add support for also running spirv-cross --- cmake/PlumeShaders.cmake | 38 +++++++++-------------------- cmake/modules/PlumeSpirvCross.cmake | 36 +++++++++++++++------------ 2 files changed, 32 insertions(+), 42 deletions(-) diff --git a/cmake/PlumeShaders.cmake b/cmake/PlumeShaders.cmake index 8178b32..54ffb3a 100644 --- a/cmake/PlumeShaders.cmake +++ b/cmake/PlumeShaders.cmake @@ -9,10 +9,9 @@ # plume_compile_pixel_shader(my_target shaders/main.frag.hlsl mainFrag PSMain) # plume_compile_compute_shader(my_target shaders/compute.hlsl computeShader CSMain) # -# Bring your own DXC/SPIRV-Cross: +# Bring your own DXC/SPIRV-Cross (set before calling plume_shaders_init): # set(PLUME_DXC_EXECUTABLE "/path/to/dxc") -# set(PLUME_DXC_LIB_DIR "/path/to/lib") # Required on macOS/Linux for dylib/so -# plume_shaders_init(FETCH_DXC OFF FETCH_SPIRV_CROSS OFF) +# set(PLUME_DXC_LIB_DIR "/path/to/lib") # macOS/Linux only # # Output: # HLSL shaders compile to: @@ -27,34 +26,19 @@ include("${CMAKE_CURRENT_LIST_DIR}/modules/PlumeSpirvCross.cmake") # Initialize shader compilation infrastructure # Call this once before using other plume_compile_* functions # -# Options: -# FETCH_DXC - If ON (default), fetches DXC binaries automatically. -# Set to OFF if you want to provide your own DXC by setting -# PLUME_DXC_EXECUTABLE and optionally PLUME_DXC_LIB_DIR. -# FETCH_SPIRV_CROSS - If ON (default on Apple), fetches and builds SPIRV-Cross. -# Set to OFF to skip SPIR-V to Metal conversion. +# If you want to provide your own tools, set these before calling: +# PLUME_DXC_EXECUTABLE - Path to DXC binary +# PLUME_DXC_LIB_DIR - Path to DXC libraries (macOS/Linux only) +# PLUME_SPIRV_CROSS_LIB_DIR - Path to spirv-cross static libraries +# PLUME_SPIRV_CROSS_INCLUDE_DIR - Path to spirv-cross headers function(plume_shaders_init) - cmake_parse_arguments(PARSE_ARGV 0 ARG "" "FETCH_DXC;FETCH_SPIRV_CROSS" "") - - # Default FETCH_DXC to ON if not specified - if(NOT DEFINED ARG_FETCH_DXC) - set(ARG_FETCH_DXC ON) - endif() - - # Default FETCH_SPIRV_CROSS to ON on Apple, OFF otherwise - if(NOT DEFINED ARG_FETCH_SPIRV_CROSS) - if(APPLE) - set(ARG_FETCH_SPIRV_CROSS ON) - else() - set(ARG_FETCH_SPIRV_CROSS OFF) - endif() - endif() - - if(ARG_FETCH_DXC) + # Fetch DXC if not already provided + if(NOT DEFINED PLUME_DXC_EXECUTABLE) plume_fetch_dxc() endif() - if(ARG_FETCH_SPIRV_CROSS AND APPLE) + # Fetch/build spirv-cross on Apple if not already provided + if(APPLE AND NOT TARGET plume_spirv_cross_msl) plume_fetch_spirv_cross() endif() diff --git a/cmake/modules/PlumeSpirvCross.cmake b/cmake/modules/PlumeSpirvCross.cmake index ef8beca..f2e2b15 100644 --- a/cmake/modules/PlumeSpirvCross.cmake +++ b/cmake/modules/PlumeSpirvCross.cmake @@ -3,28 +3,34 @@ include(FetchContent) -# Fetch SPIRV-Cross pre-built binaries and build our tool +# Build the spirv_cross_msl tool, fetching libraries if not provided function(plume_fetch_spirv_cross) if(TARGET plume_spirv_cross_msl) return() endif() - FetchContent_Declare( - plume_spirv_cross - GIT_REPOSITORY https://github.com/renderbag/spriv-cross-bin.git - GIT_TAG main - ) - FetchContent_MakeAvailable(plume_spirv_cross) - - # Determine library path based on platform/architecture - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") - set(SPIRV_CROSS_ARCH "x64") + # Use provided paths or fetch pre-built binaries + if(DEFINED PLUME_SPIRV_CROSS_LIB_DIR AND DEFINED PLUME_SPIRV_CROSS_INCLUDE_DIR) + set(SPIRV_CROSS_LIB_DIR "${PLUME_SPIRV_CROSS_LIB_DIR}") + set(SPIRV_CROSS_INCLUDE_DIR "${PLUME_SPIRV_CROSS_INCLUDE_DIR}") else() - set(SPIRV_CROSS_ARCH "arm64") - endif() + FetchContent_Declare( + plume_spirv_cross + GIT_REPOSITORY https://github.com/renderbag/spriv-cross-bin.git + GIT_TAG main + ) + FetchContent_MakeAvailable(plume_spirv_cross) + + # Determine library path based on platform/architecture + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") + set(SPIRV_CROSS_ARCH "x64") + else() + set(SPIRV_CROSS_ARCH "arm64") + endif() - set(SPIRV_CROSS_LIB_DIR "${plume_spirv_cross_SOURCE_DIR}/lib/${SPIRV_CROSS_ARCH}") - set(SPIRV_CROSS_INCLUDE_DIR "${plume_spirv_cross_SOURCE_DIR}/include") + set(SPIRV_CROSS_LIB_DIR "${plume_spirv_cross_SOURCE_DIR}/lib/${SPIRV_CROSS_ARCH}") + set(SPIRV_CROSS_INCLUDE_DIR "${plume_spirv_cross_SOURCE_DIR}/include") + endif() # Build our custom spirv_cross_msl tool set(SPIRV_CROSS_MSL_SOURCE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../tools/spirv_cross_msl.cpp") From 64cef751105adf218527381a03308221a841e516 Mon Sep 17 00:00:00 2001 From: David Chavez Date: Mon, 15 Dec 2025 22:07:42 +0100 Subject: [PATCH 4/8] Some more clean up --- cmake/modules/PlumeFileToC.cmake | 2 +- cmake/modules/PlumeSpirvCross.cmake | 2 +- cmake/tools/spirv_cross_msl.cpp | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cmake/modules/PlumeFileToC.cmake b/cmake/modules/PlumeFileToC.cmake index f0b0c68..81de316 100644 --- a/cmake/modules/PlumeFileToC.cmake +++ b/cmake/modules/PlumeFileToC.cmake @@ -8,7 +8,7 @@ function(plume_build_file_to_c) endif() # Find the source file relative to this module - set(FILE_TO_C_SOURCE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../tools/file_to_c.cpp") + set(FILE_TO_C_SOURCE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../tools/file_to_c.cpp") if(NOT EXISTS "${FILE_TO_C_SOURCE}") message(FATAL_ERROR "plume file_to_c.cpp not found at ${FILE_TO_C_SOURCE}") diff --git a/cmake/modules/PlumeSpirvCross.cmake b/cmake/modules/PlumeSpirvCross.cmake index f2e2b15..7ddcfd1 100644 --- a/cmake/modules/PlumeSpirvCross.cmake +++ b/cmake/modules/PlumeSpirvCross.cmake @@ -33,7 +33,7 @@ function(plume_fetch_spirv_cross) endif() # Build our custom spirv_cross_msl tool - set(SPIRV_CROSS_MSL_SOURCE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../tools/spirv_cross_msl.cpp") + set(SPIRV_CROSS_MSL_SOURCE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../tools/spirv_cross_msl.cpp") if(NOT EXISTS "${SPIRV_CROSS_MSL_SOURCE}") message(FATAL_ERROR "plume spirv_cross_msl.cpp not found at ${SPIRV_CROSS_MSL_SOURCE}") diff --git a/cmake/tools/spirv_cross_msl.cpp b/cmake/tools/spirv_cross_msl.cpp index 675d9a1..f362921 100644 --- a/cmake/tools/spirv_cross_msl.cpp +++ b/cmake/tools/spirv_cross_msl.cpp @@ -1,5 +1,9 @@ -// spirv_cross_msl - Converts SPIR-V to Metal Shading Language -// Adapted from RT64's spirv_cross_msl tool +// +// plume +// +// Copyright (c) 2024 renderbag and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file for details. +// #include "spirv_msl.hpp" #include "spirv_parser.hpp" From 209fd027524ff98d8105e608f064f7382560f3a5 Mon Sep 17 00:00:00 2001 From: David Chavez Date: Mon, 15 Dec 2025 22:54:38 +0100 Subject: [PATCH 5/8] Fixes for real usage --- cmake/PlumeShaders.cmake | 276 +++++++++++++++++++++++++++++++++----- cmake/tools/file_to_c.cpp | 12 +- 2 files changed, 253 insertions(+), 35 deletions(-) diff --git a/cmake/PlumeShaders.cmake b/cmake/PlumeShaders.cmake index 54ffb3a..46408d7 100644 --- a/cmake/PlumeShaders.cmake +++ b/cmake/PlumeShaders.cmake @@ -9,6 +9,18 @@ # plume_compile_pixel_shader(my_target shaders/main.frag.hlsl mainFrag PSMain) # plume_compile_compute_shader(my_target shaders/compute.hlsl computeShader CSMain) # +# Advanced usage with extra options: +# plume_compile_pixel_shader(my_target shaders/main.frag.hlsl mainFrag PSMain +# EXTRA_ARGS -D MULTISAMPLING -O0 +# INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src +# SHADER_MODEL 6_3) +# +# # Spec constants mode (SPIRV + Metal only, no DXIL): +# plume_compile_pixel_shader(my_target shaders/main.frag.hlsl mainFrag PSMain SPEC_CONSTANTS) +# +# # Library shader (DXIL only, Windows): +# plume_compile_library_shader(my_target shaders/lib.hlsl libShader) +# # Bring your own DXC/SPIRV-Cross (set before calling plume_shaders_init): # set(PLUME_DXC_EXECUTABLE "/path/to/dxc") # set(PLUME_DXC_LIB_DIR "/path/to/lib") # macOS/Linux only @@ -49,7 +61,11 @@ function(plume_shaders_init) endfunction() # Internal: Compile HLSL to a specific format (spirv or dxil) +# Optional args: INCLUDE_DIRS, EXTRA_ARGS, SHADER_MODEL, OUTPUT_DIR function(_plume_compile_hlsl_impl TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME OUTPUT_FORMAT ENTRY_POINT) + # Parse optional arguments + cmake_parse_arguments(PARSE_ARGV 6 ARG "" "SHADER_MODEL;OUTPUT_DIR" "INCLUDE_DIRS;EXTRA_ARGS") + plume_get_dxc_command(DXC_CMD) if(OUTPUT_FORMAT STREQUAL "spirv") @@ -64,34 +80,68 @@ function(_plume_compile_hlsl_impl TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_N message(FATAL_ERROR "Unknown output format: ${OUTPUT_FORMAT}") endif() - set(SHADER_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.${OUTPUT_EXT}") - set(C_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.${OUTPUT_FORMAT}.c") - set(H_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.${OUTPUT_FORMAT}.h") + # Use custom output directory if provided + if(ARG_OUTPUT_DIR) + set(OUT_DIR "${ARG_OUTPUT_DIR}") + else() + set(OUT_DIR "${CMAKE_BINARY_DIR}/shaders") + endif() + file(MAKE_DIRECTORY "${OUT_DIR}") + + set(SHADER_OUTPUT "${OUT_DIR}/${OUTPUT_NAME}.hlsl.${OUTPUT_EXT}") + set(C_OUTPUT "${OUT_DIR}/${OUTPUT_NAME}.hlsl.${OUTPUT_FORMAT}.c") + set(H_OUTPUT "${OUT_DIR}/${OUTPUT_NAME}.hlsl.${OUTPUT_FORMAT}.h") + + # Use provided shader model or default to 6_0 + if(ARG_SHADER_MODEL) + set(SM_VERSION "${ARG_SHADER_MODEL}") + else() + set(SM_VERSION "6_0") + endif() - # Determine shader profile and extra args based on type + # Determine shader profile and type-specific args if(SHADER_TYPE STREQUAL "vertex") - set(SHADER_PROFILE "vs_6_0") - set(DXC_EXTRA_ARGS "-fvk-invert-y") + set(SHADER_PROFILE "vs_${SM_VERSION}") + set(DXC_TYPE_ARGS "-fvk-invert-y") elseif(SHADER_TYPE STREQUAL "pixel" OR SHADER_TYPE STREQUAL "fragment") - set(SHADER_PROFILE "ps_6_0") - set(DXC_EXTRA_ARGS "") + set(SHADER_PROFILE "ps_${SM_VERSION}") + set(DXC_TYPE_ARGS "") elseif(SHADER_TYPE STREQUAL "compute") - set(SHADER_PROFILE "cs_6_0") - set(DXC_EXTRA_ARGS "") + set(SHADER_PROFILE "cs_${SM_VERSION}") + set(DXC_TYPE_ARGS "") + elseif(SHADER_TYPE STREQUAL "geometry") + set(SHADER_PROFILE "gs_${SM_VERSION}") + set(DXC_TYPE_ARGS "") elseif(SHADER_TYPE STREQUAL "ray") set(SHADER_PROFILE "lib_6_3") - set(DXC_EXTRA_ARGS ${PLUME_DXC_RT_OPTS}) + set(DXC_TYPE_ARGS ${PLUME_DXC_RT_OPTS}) + elseif(SHADER_TYPE STREQUAL "library") + set(SHADER_PROFILE "lib_${SM_VERSION}") + set(DXC_TYPE_ARGS "-D;LIBRARY") else() - message(FATAL_ERROR "Unknown shader type: ${SHADER_TYPE}. Use: vertex, pixel/fragment, compute, or ray") + message(FATAL_ERROR "Unknown shader type: ${SHADER_TYPE}. Use: vertex, pixel/fragment, compute, geometry, ray, or library") endif() + # Build include directory flags + set(INCLUDE_FLAGS "") + foreach(INCLUDE_DIR ${ARG_INCLUDE_DIRS}) + list(APPEND INCLUDE_FLAGS "-I${INCLUDE_DIR}") + endforeach() + set(BLOB_NAME "${OUTPUT_NAME}Blob${BLOB_SUFFIX}") + # Build entry point args (library shaders don't have entry points) + if(ENTRY_POINT STREQUAL "") + set(ENTRY_POINT_ARGS "") + else() + set(ENTRY_POINT_ARGS "-E" "${ENTRY_POINT}") + endif() + # Compile using DXC add_custom_command( OUTPUT "${SHADER_OUTPUT}" - COMMAND ${DXC_CMD} ${PLUME_DXC_COMMON_OPTS} -E ${ENTRY_POINT} -T ${SHADER_PROFILE} - ${FORMAT_FLAGS} ${DXC_EXTRA_ARGS} -Fo "${SHADER_OUTPUT}" "${SHADER_SOURCE}" + COMMAND ${DXC_CMD} ${PLUME_DXC_COMMON_OPTS} ${INCLUDE_FLAGS} ${ENTRY_POINT_ARGS} -T ${SHADER_PROFILE} + ${FORMAT_FLAGS} ${DXC_TYPE_ARGS} ${ARG_EXTRA_ARGS} -Fo "${SHADER_OUTPUT}" "${SHADER_SOURCE}" DEPENDS "${SHADER_SOURCE}" COMMENT "Compiling ${SHADER_TYPE} shader ${OUTPUT_NAME} to ${OUTPUT_FORMAT}" VERBATIM @@ -111,12 +161,25 @@ function(_plume_compile_hlsl_impl TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_N endfunction() # Internal: Compile SPIR-V to Metal via spirv-cross +# Optional args: OUTPUT_DIR +# Note: For HLSL sources, OUTPUT_NAME should include .hlsl suffix for proper naming function(_plume_compile_spirv_to_metal_impl TARGET_NAME SPIRV_FILE OUTPUT_NAME) - set(METAL_SOURCE "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metal") - set(IR_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.ir") - set(METALLIB_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metallib") - set(C_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metal.c") - set(H_OUTPUT "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.metal.h") + cmake_parse_arguments(PARSE_ARGV 3 ARG "" "OUTPUT_DIR" "") + + # Use custom output directory if provided + if(ARG_OUTPUT_DIR) + set(OUT_DIR "${ARG_OUTPUT_DIR}") + else() + set(OUT_DIR "${CMAKE_BINARY_DIR}/shaders") + endif() + file(MAKE_DIRECTORY "${OUT_DIR}") + + # Use OUTPUT_NAME.hlsl for naming to match RT64's expected paths + set(METAL_SOURCE "${OUT_DIR}/${OUTPUT_NAME}.hlsl.metal") + set(IR_OUTPUT "${OUT_DIR}/${OUTPUT_NAME}.hlsl.ir") + set(METALLIB_OUTPUT "${OUT_DIR}/${OUTPUT_NAME}.hlsl.metallib") + set(C_OUTPUT "${OUT_DIR}/${OUTPUT_NAME}.hlsl.metal.c") + set(H_OUTPUT "${OUT_DIR}/${OUTPUT_NAME}.hlsl.metal.h") # Get deployment target for Metal compilation if(CMAKE_OSX_DEPLOYMENT_TARGET) @@ -215,13 +278,23 @@ endfunction() # ============================================================================ # Compile a shader and add it to a target -# Usage: plume_compile_shader(TARGET SOURCE TYPE OUTPUT_NAME ENTRY_POINT) +# Usage: plume_compile_shader(TARGET SOURCE TYPE OUTPUT_NAME ENTRY_POINT [options]) # TARGET - CMake target to add shader to # SOURCE - Path to shader source file (.hlsl or .metal) -# TYPE - Shader type: vertex, pixel, compute, or ray +# TYPE - Shader type: vertex, pixel, compute, geometry, ray, or library # OUTPUT_NAME - Base name for output files (e.g., "mainVert") # ENTRY_POINT - Shader entry point function name (e.g., "VSMain") +# +# Options: +# SPEC_CONSTANTS - Only compile SPIRV + Metal (no DXIL), for specialization constants +# SHADER_MODEL - Shader model version (default: 6_0) +# INCLUDE_DIRS - Additional include directories for DXC +# EXTRA_ARGS - Additional DXC arguments (e.g., -D MULTISAMPLING -O0) +# OUTPUT_DIR - Custom output directory (default: ${CMAKE_BINARY_DIR}/shaders) function(plume_compile_shader TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME ENTRY_POINT) + # Parse optional arguments + cmake_parse_arguments(ARG "SPEC_CONSTANTS" "SHADER_MODEL;OUTPUT_DIR" "INCLUDE_DIRS;EXTRA_ARGS" ${ARGN}) + get_filename_component(SHADER_EXT "${SHADER_SOURCE}" EXT) if(SHADER_EXT MATCHES "\\.metal$") @@ -229,18 +302,36 @@ function(plume_compile_shader TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME _plume_compile_metal_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${OUTPUT_NAME}) endif() elseif(SHADER_EXT MATCHES "\\.hlsl$") + # Build optional args to pass to impl + set(IMPL_ARGS "") + if(ARG_SHADER_MODEL) + list(APPEND IMPL_ARGS SHADER_MODEL "${ARG_SHADER_MODEL}") + endif() + if(ARG_INCLUDE_DIRS) + list(APPEND IMPL_ARGS INCLUDE_DIRS ${ARG_INCLUDE_DIRS}) + endif() + if(ARG_EXTRA_ARGS) + list(APPEND IMPL_ARGS EXTRA_ARGS ${ARG_EXTRA_ARGS}) + endif() + if(ARG_OUTPUT_DIR) + list(APPEND IMPL_ARGS OUTPUT_DIR "${ARG_OUTPUT_DIR}") + set(OUT_DIR "${ARG_OUTPUT_DIR}") + else() + set(OUT_DIR "${CMAKE_BINARY_DIR}/shaders") + endif() + # Always compile to SPIR-V - _plume_compile_hlsl_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${SHADER_TYPE} ${OUTPUT_NAME} "spirv" ${ENTRY_POINT}) + _plume_compile_hlsl_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${SHADER_TYPE} ${OUTPUT_NAME} "spirv" ${ENTRY_POINT} ${IMPL_ARGS}) - # Compile to DXIL on Windows - if(WIN32) - _plume_compile_hlsl_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${SHADER_TYPE} ${OUTPUT_NAME} "dxil" ${ENTRY_POINT}) + # Compile to DXIL on Windows (unless SPEC_CONSTANTS mode) + if(WIN32 AND NOT ARG_SPEC_CONSTANTS) + _plume_compile_hlsl_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${SHADER_TYPE} ${OUTPUT_NAME} "dxil" ${ENTRY_POINT} ${IMPL_ARGS}) endif() # Compile SPIR-V to Metal on Apple (if spirv-cross is available) if(APPLE AND TARGET plume_spirv_cross_msl) - set(SPIRV_FILE "${CMAKE_BINARY_DIR}/shaders/${OUTPUT_NAME}.hlsl.spv") - _plume_compile_spirv_to_metal_impl(${TARGET_NAME} "${SPIRV_FILE}" ${OUTPUT_NAME}) + set(SPIRV_FILE "${OUT_DIR}/${OUTPUT_NAME}.hlsl.spv") + _plume_compile_spirv_to_metal_impl(${TARGET_NAME} "${SPIRV_FILE}" ${OUTPUT_NAME} OUTPUT_DIR "${OUT_DIR}") endif() else() message(WARNING "Unsupported shader extension '${SHADER_EXT}' for ${SHADER_SOURCE}. Use .hlsl or .metal") @@ -248,23 +339,70 @@ function(plume_compile_shader TARGET_NAME SHADER_SOURCE SHADER_TYPE OUTPUT_NAME endfunction() # Compile a vertex shader +# Usage: plume_compile_vertex_shader(TARGET SOURCE OUTPUT_NAME ENTRY_POINT [options]) +# Options: SPEC_CONSTANTS, SHADER_MODEL, INCLUDE_DIRS, EXTRA_ARGS (see plume_compile_shader) function(plume_compile_vertex_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME ENTRY_POINT) - plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "vertex" ${OUTPUT_NAME} ${ENTRY_POINT}) + plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "vertex" ${OUTPUT_NAME} ${ENTRY_POINT} ${ARGN}) endfunction() # Compile a pixel/fragment shader +# Usage: plume_compile_pixel_shader(TARGET SOURCE OUTPUT_NAME ENTRY_POINT [options]) +# Options: SPEC_CONSTANTS, SHADER_MODEL, INCLUDE_DIRS, EXTRA_ARGS (see plume_compile_shader) function(plume_compile_pixel_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME ENTRY_POINT) - plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "pixel" ${OUTPUT_NAME} ${ENTRY_POINT}) + plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "pixel" ${OUTPUT_NAME} ${ENTRY_POINT} ${ARGN}) endfunction() # Compile a compute shader +# Usage: plume_compile_compute_shader(TARGET SOURCE OUTPUT_NAME ENTRY_POINT [options]) +# Options: SPEC_CONSTANTS, SHADER_MODEL, INCLUDE_DIRS, EXTRA_ARGS (see plume_compile_shader) function(plume_compile_compute_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME ENTRY_POINT) - plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "compute" ${OUTPUT_NAME} ${ENTRY_POINT}) + plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "compute" ${OUTPUT_NAME} ${ENTRY_POINT} ${ARGN}) +endfunction() + +# Compile a geometry shader +# Usage: plume_compile_geometry_shader(TARGET SOURCE OUTPUT_NAME ENTRY_POINT [options]) +# Options: SPEC_CONSTANTS, SHADER_MODEL, INCLUDE_DIRS, EXTRA_ARGS (see plume_compile_shader) +function(plume_compile_geometry_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME ENTRY_POINT) + plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "geometry" ${OUTPUT_NAME} ${ENTRY_POINT} ${ARGN}) endfunction() # Compile a ray tracing shader +# Usage: plume_compile_ray_shader(TARGET SOURCE OUTPUT_NAME ENTRY_POINT [options]) +# Options: SHADER_MODEL, INCLUDE_DIRS, EXTRA_ARGS (see plume_compile_shader) function(plume_compile_ray_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME ENTRY_POINT) - plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "ray" ${OUTPUT_NAME} ${ENTRY_POINT}) + plume_compile_shader(${TARGET_NAME} "${SHADER_SOURCE}" "ray" ${OUTPUT_NAME} ${ENTRY_POINT} ${ARGN}) +endfunction() + +# Compile a library shader (DXIL only, Windows) +# Usage: plume_compile_library_shader(TARGET SOURCE OUTPUT_NAME [options]) +# Options: SHADER_MODEL, INCLUDE_DIRS, EXTRA_ARGS, OUTPUT_DIR +function(plume_compile_library_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME) + # Parse optional arguments + cmake_parse_arguments(ARG "" "SHADER_MODEL;OUTPUT_DIR" "INCLUDE_DIRS;EXTRA_ARGS" ${ARGN}) + + if(NOT WIN32) + return() + endif() + + # Build optional args to pass to impl + set(IMPL_ARGS "") + if(ARG_SHADER_MODEL) + list(APPEND IMPL_ARGS SHADER_MODEL "${ARG_SHADER_MODEL}") + else() + list(APPEND IMPL_ARGS SHADER_MODEL "6_3") # Library shaders default to 6_3 + endif() + if(ARG_INCLUDE_DIRS) + list(APPEND IMPL_ARGS INCLUDE_DIRS ${ARG_INCLUDE_DIRS}) + endif() + if(ARG_EXTRA_ARGS) + list(APPEND IMPL_ARGS EXTRA_ARGS ${ARG_EXTRA_ARGS}) + endif() + if(ARG_OUTPUT_DIR) + list(APPEND IMPL_ARGS OUTPUT_DIR "${ARG_OUTPUT_DIR}") + endif() + + # Library shaders don't have an entry point - use empty string + _plume_compile_hlsl_impl(${TARGET_NAME} "${SHADER_SOURCE}" "library" ${OUTPUT_NAME} "dxil" "" ${IMPL_ARGS}) endfunction() # Compile a native Metal shader (Apple only, no-op on other platforms) @@ -275,3 +413,79 @@ function(plume_compile_metal_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME) _plume_compile_metal_impl(${TARGET_NAME} "${SHADER_SOURCE}" ${OUTPUT_NAME}) endif() endfunction() + +# Preprocess a shader header file and embed it as text +# Useful for runtime shader compilation where you need the preprocessed source +# Usage: plume_preprocess_shader(TARGET SOURCE OUTPUT_NAME [INCLUDE_DIRS dirs] [OUTPUT_DIR dir] [VAR_NAME name]) +# VAR_NAME - Optional variable name for the embedded data (defaults to OUTPUT_NAME) +function(plume_preprocess_shader TARGET_NAME SHADER_SOURCE OUTPUT_NAME) + cmake_parse_arguments(ARG "" "OUTPUT_DIR;VAR_NAME" "INCLUDE_DIRS" ${ARGN}) + + get_filename_component(SHADER_NAME "${SHADER_SOURCE}" NAME) + + # Use custom output directory if provided + if(ARG_OUTPUT_DIR) + set(OUT_DIR "${ARG_OUTPUT_DIR}") + else() + set(OUT_DIR "${CMAKE_BINARY_DIR}/shaders") + endif() + file(MAKE_DIRECTORY "${OUT_DIR}") + + # Variable name for embedded data (defaults to OUTPUT_NAME) + if(ARG_VAR_NAME) + set(VAR_NAME "${ARG_VAR_NAME}") + else() + set(VAR_NAME "${OUTPUT_NAME}") + endif() + + set(PREPROCESSED_OUTPUT "${OUT_DIR}/${OUTPUT_NAME}.rw") + set(C_OUTPUT "${OUT_DIR}/${OUTPUT_NAME}.rw.c") + set(H_OUTPUT "${OUT_DIR}/${OUTPUT_NAME}.rw.h") + + # Build include directory flags + set(INCLUDE_FLAGS "") + foreach(INCLUDE_DIR ${ARG_INCLUDE_DIRS}) + list(APPEND INCLUDE_FLAGS "-I${INCLUDE_DIR}") + endforeach() + + # Preprocess using C preprocessor + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + add_custom_command( + OUTPUT "${PREPROCESSED_OUTPUT}" + COMMAND clang -x c -E -P "${SHADER_SOURCE}" -o "${PREPROCESSED_OUTPUT}" ${INCLUDE_FLAGS} + DEPENDS "${SHADER_SOURCE}" + COMMENT "Preprocessing shader ${SHADER_NAME}" + VERBATIM + ) + else() + add_custom_command( + OUTPUT "${PREPROCESSED_OUTPUT}" + COMMAND ${CMAKE_CXX_COMPILER} /Zs /EP "${SHADER_SOURCE}" ${INCLUDE_FLAGS} > "${PREPROCESSED_OUTPUT}" + DEPENDS "${SHADER_SOURCE}" + COMMENT "Preprocessing shader ${SHADER_NAME}" + VERBATIM + ) + endif() + else() + add_custom_command( + OUTPUT "${PREPROCESSED_OUTPUT}" + COMMAND ${CMAKE_CXX_COMPILER} -x c -E -P "${SHADER_SOURCE}" -o "${PREPROCESSED_OUTPUT}" ${INCLUDE_FLAGS} + DEPENDS "${SHADER_SOURCE}" + COMMENT "Preprocessing shader ${SHADER_NAME}" + VERBATIM + ) + endif() + + # Generate C header with text content (use --text for char type compatibility) + add_custom_command( + OUTPUT "${C_OUTPUT}" "${H_OUTPUT}" + COMMAND plume_file_to_c "${PREPROCESSED_OUTPUT}" "${VAR_NAME}Text" "${C_OUTPUT}" "${H_OUTPUT}" --text + DEPENDS "${PREPROCESSED_OUTPUT}" plume_file_to_c + COMMENT "Generating C header for preprocessed shader ${OUTPUT_NAME}" + VERBATIM + ) + + target_sources(${TARGET_NAME} PRIVATE "${C_OUTPUT}") + target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}") +endfunction() diff --git a/cmake/tools/file_to_c.cpp b/cmake/tools/file_to_c.cpp index 174145e..40ae732 100644 --- a/cmake/tools/file_to_c.cpp +++ b/cmake/tools/file_to_c.cpp @@ -37,8 +37,9 @@ void create_parent_if_needed(const char* path) { } int main(int argc, const char** argv) { - if (argc != 5) { - printf("Usage: %s [input file] [array name] [output C file] [output C header]\n", argv[0]); + if (argc < 5 || argc > 6) { + printf("Usage: %s [input file] [array name] [output C file] [output C header] [--text]\n", argv[0]); + printf(" --text: Use 'char' instead of 'unsigned char' for text data compatibility\n"); return EXIT_SUCCESS; } @@ -46,6 +47,7 @@ int main(int argc, const char** argv) { const char* array_name = argv[2]; const char* output_c_path = argv[3]; const char* output_h_path = argv[4]; + bool use_char = (argc == 6 && std::string(argv[5]) == "--text"); // Read the input file's contents std::vector contents = read_file(input_path); @@ -59,12 +61,14 @@ int main(int argc, const char** argv) { create_parent_if_needed(output_c_path); create_parent_if_needed(output_h_path); + const char* type_name = use_char ? "char" : "unsigned char"; + // Write the C file with the array and size { std::ofstream output_c_file{output_c_path}; output_c_file << "// Auto-generated by plume file_to_c - do not edit\n\n"; output_c_file << "#include \n\n"; - output_c_file << "const unsigned char " << array_name << "[" << contents.size() << "] = {"; + output_c_file << "const " << type_name << " " << array_name << "[" << contents.size() << "] = {"; for (size_t i = 0; i < contents.size(); ++i) { if (i % 16 == 0) { @@ -90,7 +94,7 @@ int main(int argc, const char** argv) { "#ifdef __cplusplus\n" "extern \"C\" {\n" "#endif\n\n" - "extern const unsigned char " << array_name << "[" << contents.size() << "];\n" + "extern const " << type_name << " " << array_name << "[" << contents.size() << "];\n" "extern const size_t " << array_name << "_size;\n\n" "#ifdef __cplusplus\n" "}\n" From 4955353af2025c0258f6ab07e7afe77e573abc42 Mon Sep 17 00:00:00 2001 From: David Chavez Date: Mon, 15 Dec 2025 23:35:26 +0100 Subject: [PATCH 6/8] Fix examples --- examples/cube/main.cpp | 150 ++++++++++++++++++------------------- examples/triangle/main.cpp | 92 +++++++++++------------ 2 files changed, 121 insertions(+), 121 deletions(-) diff --git a/examples/cube/main.cpp b/examples/cube/main.cpp index d6cf00b..a2aeae8 100644 --- a/examples/cube/main.cpp +++ b/examples/cube/main.cpp @@ -24,8 +24,8 @@ #include "shaders/cubeVert.hlsl.spirv.h" #include "shaders/cubeFrag.hlsl.spirv.h" #ifdef __APPLE__ -#include "shaders/cubeVert.metal.h" -#include "shaders/cubeFrag.metal.h" +#include "shaders/cubeVert.hlsl.metal.h" +#include "shaders/cubeFrag.hlsl.metal.h" #endif namespace plume { @@ -40,7 +40,7 @@ namespace plume { static const uint32_t BufferCount = 2; static const RenderFormat SwapchainFormat = RenderFormat::B8G8R8A8_UNORM; static const uint32_t CubeMapSize = 256; - + // Toggle between TextureTypeCube (1 cube = 6 faces) and TextureTypeCubeArray (2 cubes = 12 faces) // This validates that both code paths in mapTextureType work correctly static const bool UseCubeArray = false; @@ -50,13 +50,13 @@ namespace plume { // Simple 4x4 matrix for inverse view-projection struct Mat4 { float m[16]; - + static Mat4 identity() { Mat4 result = {}; result.m[0] = result.m[5] = result.m[10] = result.m[15] = 1.0f; return result; } - + static Mat4 perspective(float fovY, float aspect, float nearZ, float farZ) { Mat4 result = {}; float tanHalfFov = std::tan(fovY / 2.0f); @@ -67,7 +67,7 @@ namespace plume { result.m[14] = -(2.0f * farZ * nearZ) / (farZ - nearZ); return result; } - + static Mat4 lookAt(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) { @@ -75,19 +75,19 @@ namespace plume { float fx = centerX - eyeX, fy = centerY - eyeY, fz = centerZ - eyeZ; float flen = std::sqrt(fx*fx + fy*fy + fz*fz); fx /= flen; fy /= flen; fz /= flen; - + // Right vector (cross product of forward and up) float rx = fy * upZ - fz * upY; float ry = fz * upX - fx * upZ; float rz = fx * upY - fy * upX; float rlen = std::sqrt(rx*rx + ry*ry + rz*rz); rx /= rlen; ry /= rlen; rz /= rlen; - + // True up vector float ux = ry * fz - rz * fy; float uy = rz * fx - rx * fz; float uz = rx * fy - ry * fx; - + Mat4 result = {}; result.m[0] = rx; result.m[4] = ry; result.m[8] = rz; result.m[1] = ux; result.m[5] = uy; result.m[9] = uz; @@ -98,7 +98,7 @@ namespace plume { result.m[15] = 1.0f; return result; } - + Mat4 operator*(const Mat4& other) const { Mat4 result = {}; for (int i = 0; i < 4; i++) { @@ -111,11 +111,11 @@ namespace plume { } return result; } - + Mat4 inverse() const { Mat4 inv = {}; float det; - + inv.m[0] = m[5]*m[10]*m[15] - m[5]*m[11]*m[14] - m[9]*m[6]*m[15] + m[9]*m[7]*m[14] + m[13]*m[6]*m[11] - m[13]*m[7]*m[10]; inv.m[4] = -m[4]*m[10]*m[15] + m[4]*m[11]*m[14] + m[8]*m[6]*m[15] - m[8]*m[7]*m[14] - m[12]*m[6]*m[11] + m[12]*m[7]*m[10]; inv.m[8] = m[4]*m[9]*m[15] - m[4]*m[11]*m[13] - m[8]*m[5]*m[15] + m[8]*m[7]*m[13] + m[12]*m[5]*m[11] - m[12]*m[7]*m[9]; @@ -132,10 +132,10 @@ namespace plume { inv.m[7] = m[0]*m[6]*m[11] - m[0]*m[7]*m[10] - m[4]*m[2]*m[11] + m[4]*m[3]*m[10] + m[8]*m[2]*m[7] - m[8]*m[3]*m[6]; inv.m[11] = -m[0]*m[5]*m[11] + m[0]*m[7]*m[9] + m[4]*m[1]*m[11] - m[4]*m[3]*m[9] - m[8]*m[1]*m[7] + m[8]*m[3]*m[5]; inv.m[15] = m[0]*m[5]*m[10] - m[0]*m[6]*m[9] - m[4]*m[1]*m[10] + m[4]*m[2]*m[9] + m[8]*m[1]*m[6] - m[8]*m[2]*m[5]; - + det = m[0]*inv.m[0] + m[1]*inv.m[4] + m[2]*inv.m[8] + m[3]*inv.m[12]; if (det == 0) return identity(); - + det = 1.0f / det; for (int i = 0; i < 16; i++) inv.m[i] *= det; return inv; @@ -154,7 +154,7 @@ namespace plume { std::unique_ptr m_acquireSemaphore; std::vector> m_releaseSemaphores; std::vector> m_framebuffers; - + // Cube texture resources std::unique_ptr m_pipeline; std::unique_ptr m_pipelineLayout; @@ -163,7 +163,7 @@ namespace plume { std::unique_ptr m_sampler; std::unique_ptr m_descriptorSet; std::unique_ptr m_constantBuffer; - + // Camera float m_cameraYaw = 0.0f; float m_cameraPitch = 0.0f; @@ -175,14 +175,14 @@ namespace plume { // faceIndex: which face of the cube (0-5: +X, -X, +Y, -Y, +Z, -Z) void generateCubeFaceData(uint32_t cubeIndex, uint32_t faceIndex, uint32_t size, std::vector& data) { data.resize(size * size * 4); - + // Different color schemes for each cube // Cube 0: warm colors, Cube 1: cool colors (inverted/shifted) float baseColors[2][6][3] = { // Cube 0: Original warm scheme { {1.0f, 0.3f, 0.3f}, // +X: Red - {0.3f, 1.0f, 0.3f}, // -X: Green + {0.3f, 1.0f, 0.3f}, // -X: Green {0.3f, 0.3f, 1.0f}, // +Y: Blue (sky) {0.4f, 0.25f, 0.1f}, // -Y: Brown (ground) {1.0f, 1.0f, 0.3f}, // +Z: Yellow @@ -198,15 +198,15 @@ namespace plume { {0.4f, 0.3f, 0.8f}, // -Z: Purple } }; - + const auto& colors = baseColors[cubeIndex % 2]; - + for (uint32_t y = 0; y < size; y++) { for (uint32_t x = 0; x < size; x++) { uint32_t idx = (y * size + x) * 4; - + float brightness = 1.0f; - + data[idx + 0] = (uint8_t)(colors[faceIndex][0] * brightness * 255.0f); data[idx + 1] = (uint8_t)(colors[faceIndex][1] * brightness * 255.0f); data[idx + 2] = (uint8_t)(colors[faceIndex][2] * brightness * 255.0f); @@ -217,15 +217,15 @@ namespace plume { void createFramebuffers(CubeContext& ctx) { ctx.m_framebuffers.clear(); - + for (uint32_t i = 0; i < ctx.m_swapChain->getTextureCount(); i++) { const RenderTexture* colorAttachment = ctx.m_swapChain->getTexture(i); - + RenderFramebufferDesc fbDesc; fbDesc.colorAttachments = &colorAttachment; fbDesc.colorAttachmentsCount = 1; fbDesc.depthAttachment = nullptr; - + auto framebuffer = ctx.m_device->createFramebuffer(fbDesc); ctx.m_framebuffers.push_back(std::move(framebuffer)); } @@ -233,9 +233,9 @@ namespace plume { void createCubeTexture(CubeContext& ctx) { const char* textureType = UseCubeArray ? "cube array" : "cube"; - std::cout << "Creating " << textureType << " texture (" << CubeMapSize << "x" << CubeMapSize + std::cout << "Creating " << textureType << " texture (" << CubeMapSize << "x" << CubeMapSize << " per face, " << NumCubes << " cube(s), " << NumFaces << " total faces)..." << std::endl; - + // Create cube texture (or cube array if NumFaces > 6) RenderTextureDesc cubeDesc = RenderTextureDesc::Texture( RenderTextureDimension::TEXTURE_2D, @@ -245,28 +245,28 @@ namespace plume { RenderFormat::R8G8B8A8_UNORM, RenderTextureFlag::CUBE ); - + ctx.m_cubeTexture = ctx.m_device->createTexture(cubeDesc); assert(ctx.m_cubeTexture != nullptr && "Failed to create cube texture"); - + // Create a cube texture view // When using cube array, view the SECOND cube (index 6-11) to prove the array works RenderTextureViewDesc viewDesc = RenderTextureViewDesc::TextureCube(RenderFormat::R8G8B8A8_UNORM); viewDesc.arrayIndex = UseCubeArray ? 6 : 0; // Start at cube 1 if array, else cube 0 viewDesc.arraySize = 6; // View one cube (6 faces) - std::cout << " Creating texture view starting at face " << viewDesc.arrayIndex + std::cout << " Creating texture view starting at face " << viewDesc.arrayIndex << " (cube " << (viewDesc.arrayIndex / 6) << ")" << std::endl; ctx.m_cubeTextureView = ctx.m_cubeTexture->createTextureView(viewDesc); assert(ctx.m_cubeTextureView != nullptr && "Failed to create cube texture view"); - + // Create staging buffer and upload face data size_t faceSize = CubeMapSize * CubeMapSize * 4; auto stagingBuffer = ctx.m_device->createBuffer( RenderBufferDesc::UploadBuffer(faceSize * NumFaces) ); - + uint8_t* mappedData = static_cast(stagingBuffer->map()); - + for (uint32_t face = 0; face < NumFaces; face++) { uint32_t cubeIndex = face / 6; uint32_t faceIndex = face % 6; @@ -274,21 +274,21 @@ namespace plume { generateCubeFaceData(cubeIndex, faceIndex, CubeMapSize, faceData); std::memcpy(mappedData + face * faceSize, faceData.data(), faceSize); } - + stagingBuffer->unmap(); - + // Upload to GPU ctx.m_commandList->begin(); - + ctx.m_commandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(ctx.m_cubeTexture.get(), RenderTextureLayout::COPY_DEST)); - + for (uint32_t face = 0; face < NumFaces; face++) { uint32_t cubeIndex = face / 6; uint32_t faceIndex = face % 6; - std::cout << " Uploading cube " << cubeIndex << " face " << faceIndex + std::cout << " Uploading cube " << cubeIndex << " face " << faceIndex << " (slice=" << face << ", offset=" << (face * faceSize) << ")" << std::endl; - + RenderTextureCopyLocation srcLocation = RenderTextureCopyLocation::PlacedFootprint( stagingBuffer.get(), RenderFormat::R8G8B8A8_UNORM, @@ -296,25 +296,25 @@ namespace plume { CubeMapSize, // rowWidth is in pixels, not bytes face * faceSize ); - + RenderTextureCopyLocation dstLocation = RenderTextureCopyLocation::Subresource( ctx.m_cubeTexture.get(), 0, // mipLevel face // arrayIndex (each face is a different array slice) ); - + ctx.m_commandList->copyTextureRegion(dstLocation, srcLocation, 0, 0, 0, nullptr); } - + ctx.m_commandList->barriers(RenderBarrierStage::GRAPHICS, RenderTextureBarrier(ctx.m_cubeTexture.get(), RenderTextureLayout::SHADER_READ)); - + ctx.m_commandList->end(); - + const RenderCommandList* cmdList = ctx.m_commandList.get(); ctx.m_commandQueue->executeCommandLists(&cmdList, 1, nullptr, 0, nullptr, 0, ctx.m_fence.get()); ctx.m_commandQueue->waitForCommandFence(ctx.m_fence.get()); - + std::cout << " Cube texture created and uploaded successfully!" << std::endl; } @@ -327,42 +327,42 @@ namespace plume { samplerDesc.addressV = RenderTextureAddressMode::CLAMP; samplerDesc.addressW = RenderTextureAddressMode::CLAMP; ctx.m_sampler = ctx.m_device->createSampler(samplerDesc); - + // Create constant buffer for inverse view-projection matrix ctx.m_constantBuffer = ctx.m_device->createBuffer( RenderBufferDesc::UploadBuffer(sizeof(Mat4)) ); - + // Create descriptor set layout and set RenderDescriptorRange ranges[2] = { RenderDescriptorRange(RenderDescriptorRangeType::TEXTURE, 0, 1), // binding 0 RenderDescriptorRange(RenderDescriptorRangeType::SAMPLER, 1, 1) // binding 1 }; RenderDescriptorSetDesc descSetDesc(ranges, 2); - + ctx.m_descriptorSet = ctx.m_device->createDescriptorSet(descSetDesc); ctx.m_descriptorSet->setTexture(0, ctx.m_cubeTexture.get(), RenderTextureLayout::SHADER_READ, ctx.m_cubeTextureView.get()); ctx.m_descriptorSet->setSampler(1, ctx.m_sampler.get()); - + // Create pipeline layout RenderPipelineLayoutDesc layoutDesc; layoutDesc.descriptorSetDescs = &descSetDesc; layoutDesc.descriptorSetDescsCount = 1; - + RenderPushConstantRange pushConstantRange; pushConstantRange.size = sizeof(Mat4); pushConstantRange.stageFlags = RenderShaderStageFlag::VERTEX; layoutDesc.pushConstantRanges = &pushConstantRange; layoutDesc.pushConstantRangesCount = 1; - + ctx.m_pipelineLayout = ctx.m_device->createPipelineLayout(layoutDesc); - + // Create shaders RenderShaderFormat shaderFormat = ctx.m_renderInterface->getCapabilities().shaderFormat; - + std::unique_ptr vertexShader; std::unique_ptr fragmentShader; - + switch (shaderFormat) { #ifdef __APPLE__ case RenderShaderFormat::METAL: @@ -383,7 +383,7 @@ namespace plume { default: assert(false && "Unknown shader format"); } - + // Create graphics pipeline (no vertex input - fullscreen triangle) RenderGraphicsPipelineDesc pipelineDesc; pipelineDesc.pipelineLayout = ctx.m_pipelineLayout.get(); @@ -393,7 +393,7 @@ namespace plume { pipelineDesc.renderTargetBlend[0] = RenderBlendDesc::Copy(); pipelineDesc.renderTargetCount = 1; pipelineDesc.primitiveTopology = RenderPrimitiveTopology::TRIANGLE_LIST; - + ctx.m_pipeline = ctx.m_device->createGraphicsPipeline(pipelineDesc); } @@ -405,7 +405,7 @@ namespace plume { ctx.m_swapChain->resize(); ctx.m_commandList = ctx.m_commandQueue->createCommandList(); ctx.m_acquireSemaphore = ctx.m_device->createCommandSemaphore(); - + createFramebuffers(ctx); createCubeTexture(ctx); createPipeline(ctx); @@ -420,7 +420,7 @@ namespace plume { void resize(CubeContext& ctx, int width, int height) { std::cout << "Resizing cube example to " << width << "x" << height << std::endl; - + if (ctx.m_swapChain) { ctx.m_framebuffers.clear(); ctx.m_swapChain->resize(); @@ -433,7 +433,7 @@ namespace plume { if (counter++ % 60 == 0) { std::cout << "Rendering frame " << counter << " using " << ctx.m_apiName << " backend" << std::endl; } - + // Update time for camera rotation ctx.m_time += 0.016f; // ~60fps ctx.m_cameraYaw = ctx.m_time * 0.3f; @@ -442,66 +442,66 @@ namespace plume { // Acquire swapchain image uint32_t imageIndex = 0; ctx.m_swapChain->acquireTexture(ctx.m_acquireSemaphore.get(), &imageIndex); - + // Calculate inverse view-projection matrix const uint32_t width = ctx.m_swapChain->getWidth(); const uint32_t height = ctx.m_swapChain->getHeight(); float aspect = (float)width / (float)height; - + // Camera looking from origin with yaw/pitch rotation float camX = std::cos(ctx.m_cameraPitch) * std::sin(ctx.m_cameraYaw); float camY = std::sin(ctx.m_cameraPitch); float camZ = std::cos(ctx.m_cameraPitch) * std::cos(ctx.m_cameraYaw); - + Mat4 view = Mat4::lookAt(0, 0, 0, camX, camY, camZ, 0, 1, 0); Mat4 proj = Mat4::perspective(1.2f, aspect, 0.1f, 100.0f); Mat4 viewProj = proj * view; Mat4 invViewProj = viewProj.inverse(); - + // Update constant buffer void* mapped = ctx.m_constantBuffer->map(); std::memcpy(mapped, invViewProj.m, sizeof(Mat4)); ctx.m_constantBuffer->unmap(); - + // Begin command recording ctx.m_commandList->begin(); - + RenderTexture* swapChainTexture = ctx.m_swapChain->getTexture(imageIndex); ctx.m_commandList->barriers(RenderBarrierStage::GRAPHICS, RenderTextureBarrier(swapChainTexture, RenderTextureLayout::COLOR_WRITE)); - + const RenderFramebuffer* framebuffer = ctx.m_framebuffers[imageIndex].get(); ctx.m_commandList->setFramebuffer(framebuffer); - + const RenderViewport viewport(0.0f, 0.0f, float(width), float(height)); const RenderRect scissor(0, 0, width, height); ctx.m_commandList->setViewports(viewport); ctx.m_commandList->setScissors(scissor); - + // Draw cube texture ctx.m_commandList->setGraphicsPipelineLayout(ctx.m_pipelineLayout.get()); ctx.m_commandList->setPipeline(ctx.m_pipeline.get()); ctx.m_commandList->setGraphicsDescriptorSet(ctx.m_descriptorSet.get(), 0); ctx.m_commandList->setGraphicsPushConstants(0, invViewProj.m); - + // Draw fullscreen triangle (3 vertices, no vertex buffer) ctx.m_commandList->drawInstanced(3, 1, 0, 0); - + ctx.m_commandList->barriers(RenderBarrierStage::NONE, RenderTextureBarrier(swapChainTexture, RenderTextureLayout::PRESENT)); - + ctx.m_commandList->end(); - + // Create semaphores if needed while (ctx.m_releaseSemaphores.size() < ctx.m_swapChain->getTextureCount()) { ctx.m_releaseSemaphores.emplace_back(ctx.m_device->createCommandSemaphore()); } - + // Submit and present const RenderCommandList* cmdList = ctx.m_commandList.get(); RenderCommandSemaphore* waitSemaphore = ctx.m_acquireSemaphore.get(); RenderCommandSemaphore* signalSemaphore = ctx.m_releaseSemaphores[imageIndex].get(); - + ctx.m_commandQueue->executeCommandLists(&cmdList, 1, &waitSemaphore, 1, &signalSemaphore, 1, ctx.m_fence.get()); ctx.m_swapChain->present(imageIndex, &signalSemaphore, 1); ctx.m_commandQueue->waitForCommandFence(ctx.m_fence.get()); @@ -517,7 +517,7 @@ namespace plume { #if defined(__APPLE__) flags |= SDL_WINDOW_METAL; #endif - + std::string windowTitle = "Plume Cube Texture Example (" + apiName + ")"; SDL_Window* window = SDL_CreateWindow(windowTitle.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); if (!window) { diff --git a/examples/triangle/main.cpp b/examples/triangle/main.cpp index e56e4cb..503a18a 100644 --- a/examples/triangle/main.cpp +++ b/examples/triangle/main.cpp @@ -22,8 +22,8 @@ #include "shaders/triangleVert.hlsl.spirv.h" #include "shaders/triangleFrag.hlsl.spirv.h" #ifdef __APPLE__ -#include "shaders/triangleVert.metal.h" -#include "shaders/triangleFrag.metal.h" +#include "shaders/triangleVert.hlsl.metal.h" +#include "shaders/triangleFrag.hlsl.metal.h" #endif // Function prototype for creating the Metal interface on Apple platforms @@ -52,7 +52,7 @@ namespace plume { std::vector> m_releaseSemaphores; std::unique_ptr m_commandFence; std::vector> m_framebuffers; - + // Pipeline and buffer resources std::unique_ptr m_pipeline; std::unique_ptr m_pipelineLayout; @@ -66,34 +66,34 @@ namespace plume { void createFramebuffers(TestContext& ctx) { // Create framebuffers for each swap chain image ctx.m_framebuffers.clear(); - + for (uint32_t i = 0; i < ctx.m_swapChain->getTextureCount(); i++) { const RenderTexture* colorAttachment = ctx.m_swapChain->getTexture(i); - + RenderFramebufferDesc fbDesc; fbDesc.colorAttachments = &colorAttachment; fbDesc.colorAttachmentsCount = 1; fbDesc.depthAttachment = nullptr; - + auto framebuffer = ctx.m_device->createFramebuffer(fbDesc); ctx.m_framebuffers.push_back(std::move(framebuffer)); } } - + void createPipeline(TestContext& ctx) { // Create a pipeline layout (without any descriptor sets or push constants) RenderPipelineLayoutDesc layoutDesc; layoutDesc.allowInputLayout = true; - + ctx.m_pipelineLayout = ctx.m_device->createPipelineLayout(layoutDesc); - + // Get the shader format from the render interface RenderShaderFormat shaderFormat = ctx.m_renderInterface->getCapabilities().shaderFormat; - + // Create shader objects std::unique_ptr vertexShader; std::unique_ptr fragmentShader; - + // Different entry point names depending on shader format switch (shaderFormat) { #ifdef __APPLE__ @@ -115,16 +115,16 @@ namespace plume { default: assert(false && "Unknown shader format"); } - + // Define vertex input layout // The vertex format has position (vec3) and color (vec4) ctx.m_inputSlot = RenderInputSlot(0, sizeof(float) * 7); // 3 floats for position + 4 floats for color - + std::vector inputElements = { RenderInputElement("POSITION", 0, 0, RenderFormat::R32G32B32_FLOAT, 0, 0), RenderInputElement("COLOR", 0, 1, RenderFormat::R32G32B32A32_FLOAT, 0, sizeof(float) * 3) }; - + // Create graphics pipeline RenderGraphicsPipelineDesc pipelineDesc; pipelineDesc.inputSlots = &ctx.m_inputSlot; @@ -138,10 +138,10 @@ namespace plume { pipelineDesc.renderTargetBlend[0] = RenderBlendDesc::Copy(); pipelineDesc.renderTargetCount = 1; pipelineDesc.primitiveTopology = RenderPrimitiveTopology::TRIANGLE_LIST; - + ctx.m_pipeline = ctx.m_device->createGraphicsPipeline(pipelineDesc); } - + void createVertexBuffer(TestContext& ctx) { // Define triangle vertices: position (x, y, z) and color (r, g, b, a) const float vertices[] = { @@ -149,15 +149,15 @@ namespace plume { -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // Bottom left vertex (green) 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f // Bottom right vertex (blue) }; - + // Create vertex buffer ctx.m_vertexBuffer = ctx.m_device->createBuffer(RenderBufferDesc::VertexBuffer(sizeof(vertices), RenderHeapType::UPLOAD)); - + // Map buffer and copy vertex data void* bufferData = ctx.m_vertexBuffer->map(); std::memcpy(bufferData, vertices, sizeof(vertices)); ctx.m_vertexBuffer->unmap(); - + // Create vertex buffer view ctx.m_vertexBufferView = RenderVertexBufferView(ctx.m_vertexBuffer.get(), sizeof(vertices)); } @@ -165,34 +165,34 @@ namespace plume { static void initializeRenderResources(TestContext& ctx, RenderInterface* renderInterface) { // Create device ctx.m_device = renderInterface->createDevice(); - + // Create command queue for graphics ctx.m_commandQueue = ctx.m_device->createCommandQueue(RenderCommandListType::DIRECT); - + // Create a command fence ctx.m_fence = ctx.m_device->createCommandFence(); - + // Create a swap chain for the window using the render window from init ctx.m_swapChain = ctx.m_commandQueue->createSwapChain(ctx.m_renderWindow, BufferCount, SwapchainFormat, 2); - + // Explicitly resize the swapchain to create the textures ctx.m_swapChain->resize(); - + // Create command list ctx.m_commandList = ctx.m_commandQueue->createCommandList(); - + // Create acquire semaphore for swap chain synchronization ctx.m_acquireSemaphore = ctx.m_device->createCommandSemaphore(); - + // Create command fence for synchronization ctx.m_commandFence = ctx.m_device->createCommandFence(); - + // Create framebuffers for each swap chain image createFramebuffers(ctx); - + // Create the graphics pipeline createPipeline(ctx); - + // Create the vertex buffer with triangle data createVertexBuffer(ctx); } @@ -210,19 +210,19 @@ namespace plume { static void resize(TestContext& ctx, int width, int height) { std::cout << "Resizing triangle example to " << width << "x" << height << std::endl; - + // Simply resize the swapchain if (ctx.m_swapChain) { // Clear old framebuffers ctx.m_framebuffers.clear(); - + // Resize the swap chain bool resized = ctx.m_swapChain->resize(); if (!resized) { std::cerr << "Failed to resize swap chain" << std::endl; return; } - + // Recreate framebuffers for the resized swap chain createFramebuffers(ctx); } @@ -237,42 +237,42 @@ namespace plume { // Acquire the next swapchain image uint32_t imageIndex = 0; ctx.m_swapChain->acquireTexture(ctx.m_acquireSemaphore.get(), &imageIndex); - + // Begin command recording ctx.m_commandList->begin(); // Get the current swap chain texture and transition to render target RenderTexture *swapChainTexture = ctx.m_swapChain->getTexture(imageIndex); ctx.m_commandList->barriers(RenderBarrierStage::GRAPHICS, RenderTextureBarrier(swapChainTexture, RenderTextureLayout::COLOR_WRITE)); - + // Get the current swapchain framebuffer const RenderFramebuffer* framebuffer = ctx.m_framebuffers[imageIndex].get(); ctx.m_commandList->setFramebuffer(framebuffer); - + // Set up viewport and scissor const uint32_t width = ctx.m_swapChain->getWidth(); const uint32_t height = ctx.m_swapChain->getHeight(); const RenderViewport viewport(0.0f, 0.0f, float(width), float(height)); const RenderRect scissor(0, 0, width, height); - + ctx.m_commandList->setViewports(viewport); ctx.m_commandList->setScissors(scissor); - + // Clear with a dark blue color RenderColor clearColor(0.0f, 0.0f, 0.2f, 1.0f); ctx.m_commandList->clearColor(0, clearColor); - + // Bind the pipeline and vertex buffer ctx.m_commandList->setGraphicsPipelineLayout(ctx.m_pipelineLayout.get()); ctx.m_commandList->setPipeline(ctx.m_pipeline.get()); ctx.m_commandList->setVertexBuffers(0, &ctx.m_vertexBufferView, 1, &ctx.m_inputSlot); - + // Draw the triangle ctx.m_commandList->drawInstanced(3, 1, 0, 0); - + // Transition to present layout ctx.m_commandList->barriers(RenderBarrierStage::NONE, RenderTextureBarrier(swapChainTexture, RenderTextureLayout::PRESENT)); - + // End command recording ctx.m_commandList->end(); @@ -280,14 +280,14 @@ namespace plume { while (ctx.m_releaseSemaphores.size() < ctx.m_swapChain->getTextureCount()) { ctx.m_releaseSemaphores.emplace_back(ctx.m_device->createCommandSemaphore()); } - + // Submit and present const RenderCommandList* cmdList = ctx.m_commandList.get(); RenderCommandSemaphore* waitSemaphore = ctx.m_acquireSemaphore.get(); RenderCommandSemaphore* signalSemaphore = ctx.m_releaseSemaphores[imageIndex].get(); - + ctx.m_commandQueue->executeCommandLists(&cmdList, 1, &waitSemaphore, 1, &signalSemaphore, 1, ctx.m_fence.get()); - + // Present the frame ctx.m_swapChain->present(imageIndex, &signalSemaphore, 1); ctx.m_commandQueue->waitForCommandFence(ctx.m_fence.get()); @@ -303,7 +303,7 @@ namespace plume { #if defined(__APPLE__) flags |= SDL_WINDOW_METAL; #endif - + std::string windowTitle = "Plume Example (" + apiName + ")"; SDL_Window* window = SDL_CreateWindow(windowTitle.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); if (!window) { @@ -338,7 +338,7 @@ namespace plume { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { int width = event.window.data1; int height = event.window.data2; - + resize(ctx, width, height); } break; From 6318f0d75d6960fbe847df496b8106481d2aea0e Mon Sep 17 00:00:00 2001 From: David Chavez Date: Wed, 17 Dec 2025 10:13:09 +0100 Subject: [PATCH 7/8] build spirv-cross instead of using binaries --- cmake/modules/PlumeFileToC.cmake | 2 + cmake/modules/PlumeSpirvCross.cmake | 74 ++++++++++++++++++----------- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/cmake/modules/PlumeFileToC.cmake b/cmake/modules/PlumeFileToC.cmake index 81de316..1bd6152 100644 --- a/cmake/modules/PlumeFileToC.cmake +++ b/cmake/modules/PlumeFileToC.cmake @@ -17,6 +17,8 @@ function(plume_build_file_to_c) add_executable(plume_file_to_c ${FILE_TO_C_SOURCE}) set_target_properties(plume_file_to_c PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plume_tools" + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON ) if(APPLE) diff --git a/cmake/modules/PlumeSpirvCross.cmake b/cmake/modules/PlumeSpirvCross.cmake index 7ddcfd1..81ce019 100644 --- a/cmake/modules/PlumeSpirvCross.cmake +++ b/cmake/modules/PlumeSpirvCross.cmake @@ -1,35 +1,45 @@ # PlumeSpirvCross.cmake -# Fetches pre-built SPIRV-Cross and builds our spirv_cross_msl tool +# Fetches and builds SPIRV-Cross from source, then builds our spirv_cross_msl tool include(FetchContent) -# Build the spirv_cross_msl tool, fetching libraries if not provided +# Build the spirv_cross_msl tool, fetching and compiling SPIRV-Cross if not provided function(plume_fetch_spirv_cross) if(TARGET plume_spirv_cross_msl) return() endif() - # Use provided paths or fetch pre-built binaries + # Use provided paths or fetch and build from source if(DEFINED PLUME_SPIRV_CROSS_LIB_DIR AND DEFINED PLUME_SPIRV_CROSS_INCLUDE_DIR) + # User provided prebuilt libraries set(SPIRV_CROSS_LIB_DIR "${PLUME_SPIRV_CROSS_LIB_DIR}") set(SPIRV_CROSS_INCLUDE_DIR "${PLUME_SPIRV_CROSS_INCLUDE_DIR}") + set(SPIRV_CROSS_USE_PREBUILT TRUE) else() + # Fetch and build from source + set(SPIRV_CROSS_STATIC ON CACHE BOOL "" FORCE) + set(SPIRV_CROSS_SHARED OFF CACHE BOOL "" FORCE) + set(SPIRV_CROSS_CLI OFF CACHE BOOL "" FORCE) + set(SPIRV_CROSS_ENABLE_TESTS OFF CACHE BOOL "" FORCE) + set(SPIRV_CROSS_ENABLE_GLSL ON CACHE BOOL "" FORCE) + set(SPIRV_CROSS_ENABLE_MSL ON CACHE BOOL "" FORCE) + set(SPIRV_CROSS_ENABLE_HLSL OFF CACHE BOOL "" FORCE) + set(SPIRV_CROSS_ENABLE_CPP OFF CACHE BOOL "" FORCE) + set(SPIRV_CROSS_ENABLE_REFLECT OFF CACHE BOOL "" FORCE) + set(SPIRV_CROSS_ENABLE_UTIL OFF CACHE BOOL "" FORCE) + set(SPIRV_CROSS_ENABLE_C_API OFF CACHE BOOL "" FORCE) + set(SPIRV_CROSS_SKIP_INSTALL ON CACHE BOOL "" FORCE) + FetchContent_Declare( - plume_spirv_cross - GIT_REPOSITORY https://github.com/renderbag/spriv-cross-bin.git - GIT_TAG main + spirv_cross + GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Cross.git + GIT_TAG vulkan-sdk-1.4.335.0 + GIT_SHALLOW TRUE ) - FetchContent_MakeAvailable(plume_spirv_cross) - - # Determine library path based on platform/architecture - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") - set(SPIRV_CROSS_ARCH "x64") - else() - set(SPIRV_CROSS_ARCH "arm64") - endif() + FetchContent_MakeAvailable(spirv_cross) - set(SPIRV_CROSS_LIB_DIR "${plume_spirv_cross_SOURCE_DIR}/lib/${SPIRV_CROSS_ARCH}") - set(SPIRV_CROSS_INCLUDE_DIR "${plume_spirv_cross_SOURCE_DIR}/include") + set(SPIRV_CROSS_INCLUDE_DIR "${spirv_cross_SOURCE_DIR}") + set(SPIRV_CROSS_USE_PREBUILT FALSE) endif() # Build our custom spirv_cross_msl tool @@ -41,20 +51,30 @@ function(plume_fetch_spirv_cross) add_executable(plume_spirv_cross_msl ${SPIRV_CROSS_MSL_SOURCE}) target_include_directories(plume_spirv_cross_msl PRIVATE ${SPIRV_CROSS_INCLUDE_DIR}) + set_target_properties(plume_spirv_cross_msl PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON) - # Link against pre-built static libraries - # Order matters: msl depends on glsl depends on core - if(WIN32) - target_link_libraries(plume_spirv_cross_msl PRIVATE - "${SPIRV_CROSS_LIB_DIR}/spirv-cross-msl.lib" - "${SPIRV_CROSS_LIB_DIR}/spirv-cross-glsl.lib" - "${SPIRV_CROSS_LIB_DIR}/spirv-cross-core.lib" - ) + if(SPIRV_CROSS_USE_PREBUILT) + # Link against pre-built static libraries + # Order matters: msl depends on glsl depends on core + if(WIN32) + target_link_libraries(plume_spirv_cross_msl PRIVATE + "${SPIRV_CROSS_LIB_DIR}/spirv-cross-msl.lib" + "${SPIRV_CROSS_LIB_DIR}/spirv-cross-glsl.lib" + "${SPIRV_CROSS_LIB_DIR}/spirv-cross-core.lib" + ) + else() + target_link_libraries(plume_spirv_cross_msl PRIVATE + "${SPIRV_CROSS_LIB_DIR}/libspirv-cross-msl.a" + "${SPIRV_CROSS_LIB_DIR}/libspirv-cross-glsl.a" + "${SPIRV_CROSS_LIB_DIR}/libspirv-cross-core.a" + ) + endif() else() + # Link against freshly built targets target_link_libraries(plume_spirv_cross_msl PRIVATE - "${SPIRV_CROSS_LIB_DIR}/libspirv-cross-msl.a" - "${SPIRV_CROSS_LIB_DIR}/libspirv-cross-glsl.a" - "${SPIRV_CROSS_LIB_DIR}/libspirv-cross-core.a" + spirv-cross-msl + spirv-cross-glsl + spirv-cross-core ) endif() From f57641d7177c0ed0fd07b01e272bb7fb4b1e10df Mon Sep 17 00:00:00 2001 From: David Chavez Date: Wed, 17 Dec 2025 12:24:38 +0100 Subject: [PATCH 8/8] Use new setup to compile internal shaders --- CMakeLists.txt | 8 +++- cmake/PlumeInternalShaders.cmake | 81 -------------------------------- plume_metal.cpp | 12 ++--- 3 files changed, 12 insertions(+), 89 deletions(-) delete mode 100644 cmake/PlumeInternalShaders.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 337d4a2..aaf1bde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,8 +111,12 @@ if(APPLE) ) # Compile and embed internal Metal shaders - include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/PlumeInternalShaders.cmake) - plume_compile_metal_shaders(plume) + include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/PlumeShaders.cmake) + plume_build_file_to_c() + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/shaders") + + _plume_compile_metal_impl(plume "${CMAKE_CURRENT_SOURCE_DIR}/shaders/plume_clear.metal" plume_clear) + _plume_compile_metal_impl(plume "${CMAKE_CURRENT_SOURCE_DIR}/shaders/plume_resolve.metal" plume_resolve) endif() # Add examples if requested diff --git a/cmake/PlumeInternalShaders.cmake b/cmake/PlumeInternalShaders.cmake deleted file mode 100644 index d997853..0000000 --- a/cmake/PlumeInternalShaders.cmake +++ /dev/null @@ -1,81 +0,0 @@ -# PlumeInternalShaders.cmake -# Internal shader compilation for Plume's built-in shaders (clear, resolve) - -include("${CMAKE_CURRENT_LIST_DIR}/modules/PlumeFileToC.cmake") - -# Compile Plume's internal Metal shaders to embedded C headers -function(plume_compile_metal_shaders TARGET_NAME) - if(NOT APPLE) - return() - endif() - - # Build the file_to_c tool - plume_build_file_to_c() - - # Get the shader source directory - set(SHADER_DIR "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../shaders") - set(OUTPUT_DIR "${CMAKE_BINARY_DIR}/plume_shaders") - - # Create output directory - file(MAKE_DIRECTORY "${OUTPUT_DIR}") - - # Get deployment target for Metal compilation - if(CMAKE_OSX_DEPLOYMENT_TARGET) - set(METAL_VERSION_FLAG "-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") - else() - set(METAL_VERSION_FLAG "") - endif() - - # List of internal shaders to compile - set(PLUME_METAL_SHADERS - plume_clear - plume_resolve - ) - - set(GENERATED_SOURCES "") - set(GENERATED_HEADERS "") - - foreach(SHADER_NAME ${PLUME_METAL_SHADERS}) - set(SHADER_SOURCE "${SHADER_DIR}/${SHADER_NAME}.metal") - set(IR_OUTPUT "${OUTPUT_DIR}/${SHADER_NAME}.ir") - set(METALLIB_OUTPUT "${OUTPUT_DIR}/${SHADER_NAME}.metallib") - set(C_OUTPUT "${OUTPUT_DIR}/${SHADER_NAME}.metallib.c") - set(H_OUTPUT "${OUTPUT_DIR}/${SHADER_NAME}.metallib.h") - - # Compile Metal to IR - add_custom_command( - OUTPUT ${IR_OUTPUT} - COMMAND xcrun -sdk macosx metal ${METAL_VERSION_FLAG} -o ${IR_OUTPUT} -c ${SHADER_SOURCE} - DEPENDS ${SHADER_SOURCE} - COMMENT "Compiling ${SHADER_NAME}.metal to IR" - VERBATIM - ) - - # Link IR to metallib - add_custom_command( - OUTPUT ${METALLIB_OUTPUT} - COMMAND xcrun -sdk macosx metallib ${IR_OUTPUT} -o ${METALLIB_OUTPUT} - DEPENDS ${IR_OUTPUT} - COMMENT "Linking ${SHADER_NAME} to metallib" - VERBATIM - ) - - # Generate C header with embedded data - add_custom_command( - OUTPUT ${C_OUTPUT} ${H_OUTPUT} - COMMAND plume_file_to_c ${METALLIB_OUTPUT} "${SHADER_NAME}_metallib" ${C_OUTPUT} ${H_OUTPUT} - DEPENDS ${METALLIB_OUTPUT} plume_file_to_c - COMMENT "Generating embedded header for ${SHADER_NAME}" - VERBATIM - ) - - list(APPEND GENERATED_SOURCES ${C_OUTPUT}) - list(APPEND GENERATED_HEADERS ${H_OUTPUT}) - endforeach() - - # Add generated sources to the target - target_sources(${TARGET_NAME} PRIVATE ${GENERATED_SOURCES}) - - # Add include directory for generated headers - target_include_directories(${TARGET_NAME} PRIVATE "${OUTPUT_DIR}") -endfunction() diff --git a/plume_metal.cpp b/plume_metal.cpp index a547850..16cdf62 100644 --- a/plume_metal.cpp +++ b/plume_metal.cpp @@ -18,8 +18,8 @@ #include #include "plume_metal.h" -#include "plume_clear.metallib.h" -#include "plume_resolve.metallib.h" +#include "shaders/plume_clear.metal.h" +#include "shaders/plume_resolve.metal.h" namespace plume { // MARK: - Constants @@ -3996,8 +3996,8 @@ namespace plume { void MetalDevice::createResolvePipelineState() { // Load pre-compiled metallib from embedded data dispatch_data_t dispatchData = dispatch_data_create( - plume_resolve_metallib, - plume_resolve_metallib_size, + plume_resolveBlobMSL, + plume_resolveBlobMSL_size, nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT ); @@ -4025,8 +4025,8 @@ namespace plume { void MetalDevice::createClearShaderLibrary() { // Load pre-compiled metallib from embedded data dispatch_data_t dispatchData = dispatch_data_create( - plume_clear_metallib, - plume_clear_metallib_size, + plume_clearBlobMSL, + plume_clearBlobMSL_size, nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT );