[Build] Convert buildshaders to custom build rules
This commit is contained in:
@@ -38,6 +38,9 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/obj/${XE_PLATFOR
|
|||||||
# Create scratch/ if needed
|
# Create scratch/ if needed
|
||||||
file(MAKE_DIRECTORY "${PROJECT_SOURCE_DIR}/scratch")
|
file(MAKE_DIRECTORY "${PROJECT_SOURCE_DIR}/scratch")
|
||||||
|
|
||||||
|
# Python for shader compilation scripts
|
||||||
|
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||||
|
|
||||||
# Include helpers
|
# Include helpers
|
||||||
include(cmake/XeniaHelpers.cmake)
|
include(cmake/XeniaHelpers.cmake)
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,96 @@ function(xe_target_defaults target)
|
|||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
# xe_shader_rules_spirv(target shader_dir)
|
||||||
|
#
|
||||||
|
# Ensures SPIR-V shaders are compiled before the target builds.
|
||||||
|
# Uses a stamp file; rebuilds when any shader source changes.
|
||||||
|
function(xe_shader_rules_spirv target shader_dir)
|
||||||
|
get_filename_component(shader_dir "${shader_dir}" ABSOLUTE)
|
||||||
|
file(GLOB _sources
|
||||||
|
"${shader_dir}/*.xesl" "${shader_dir}/*.glsl"
|
||||||
|
"${shader_dir}/*.xesli" "${shader_dir}/*.glsli")
|
||||||
|
set(_stamp "${CMAKE_CURRENT_BINARY_DIR}/${target}_spirv.stamp")
|
||||||
|
set(_script "${PROJECT_SOURCE_DIR}/tools/build/compile_shader_spirv.py")
|
||||||
|
set(_valid_stages vs hs ds gs ps cs)
|
||||||
|
set(_commands)
|
||||||
|
set(_bytecode_dir "${shader_dir}/bytecode/vulkan_spirv")
|
||||||
|
list(APPEND _commands COMMAND ${CMAKE_COMMAND} -E make_directory "${_bytecode_dir}")
|
||||||
|
foreach(src ${_sources})
|
||||||
|
get_filename_component(_name ${src} NAME)
|
||||||
|
string(REGEX REPLACE "\\.[^.]+$" "" _basename "${_name}")
|
||||||
|
string(REPLACE "." "_" _id "${_basename}")
|
||||||
|
string(LENGTH "${_id}" _len)
|
||||||
|
if(_len LESS 3)
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
math(EXPR _s "${_len} - 2")
|
||||||
|
string(SUBSTRING "${_id}" ${_s} 2 _stage)
|
||||||
|
if(NOT _stage IN_LIST _valid_stages)
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
list(APPEND _commands COMMAND ${Python3_EXECUTABLE} "${_script}" "${src}" "${_bytecode_dir}/${_id}.h")
|
||||||
|
endforeach()
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${_stamp}"
|
||||||
|
${_commands}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E touch "${_stamp}"
|
||||||
|
DEPENDS ${_sources} "${_script}"
|
||||||
|
COMMENT "Compiling SPIR-V shaders for ${target}..."
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(${target}-spirv-shaders DEPENDS "${_stamp}")
|
||||||
|
add_dependencies(${target} ${target}-spirv-shaders)
|
||||||
|
# Add sources for IDE visibility but prevent VS from compiling them itself
|
||||||
|
set_source_files_properties(${_sources} PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||||
|
target_sources(${target} PRIVATE ${_sources})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# xe_shader_rules_dxbc(target shader_dir)
|
||||||
|
#
|
||||||
|
# Ensures DXBC shaders are compiled before the target builds.
|
||||||
|
# Uses a stamp file; rebuilds when any shader source changes.
|
||||||
|
function(xe_shader_rules_dxbc target shader_dir)
|
||||||
|
get_filename_component(shader_dir "${shader_dir}" ABSOLUTE)
|
||||||
|
file(GLOB _sources
|
||||||
|
"${shader_dir}/*.xesl" "${shader_dir}/*.hlsl"
|
||||||
|
"${shader_dir}/*.xesli" "${shader_dir}/*.hlsli")
|
||||||
|
set(_stamp "${CMAKE_CURRENT_BINARY_DIR}/${target}_dxbc.stamp")
|
||||||
|
set(_script "${PROJECT_SOURCE_DIR}/tools/build/compile_shader_dxbc.py")
|
||||||
|
set(_valid_stages vs hs ds gs ps cs)
|
||||||
|
set(_commands)
|
||||||
|
set(_bytecode_dir "${shader_dir}/bytecode/d3d12_5_1")
|
||||||
|
list(APPEND _commands COMMAND ${CMAKE_COMMAND} -E make_directory "${_bytecode_dir}")
|
||||||
|
foreach(src ${_sources})
|
||||||
|
get_filename_component(_name ${src} NAME)
|
||||||
|
string(REGEX REPLACE "\\.[^.]+$" "" _basename "${_name}")
|
||||||
|
string(REPLACE "." "_" _id "${_basename}")
|
||||||
|
string(LENGTH "${_id}" _len)
|
||||||
|
if(_len LESS 3)
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
math(EXPR _s "${_len} - 2")
|
||||||
|
string(SUBSTRING "${_id}" ${_s} 2 _stage)
|
||||||
|
if(NOT _stage IN_LIST _valid_stages)
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
list(APPEND _commands COMMAND ${Python3_EXECUTABLE} "${_script}" "${src}" "${_bytecode_dir}/${_id}.h")
|
||||||
|
endforeach()
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${_stamp}"
|
||||||
|
${_commands}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E touch "${_stamp}"
|
||||||
|
DEPENDS ${_sources} "${_script}"
|
||||||
|
COMMENT "Compiling DXBC shaders for ${target}..."
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(${target}-dxbc-shaders DEPENDS "${_stamp}")
|
||||||
|
add_dependencies(${target} ${target}-dxbc-shaders)
|
||||||
|
# Add sources for IDE visibility but prevent VS from compiling them itself
|
||||||
|
set_source_files_properties(${_sources} PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||||
|
target_sources(${target} PRIVATE ${_sources})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
# xe_force_c(files...)
|
# xe_force_c(files...)
|
||||||
#
|
#
|
||||||
# Forces the given source files to compile as C.
|
# Forces the given source files to compile as C.
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
add_library(xenia-gpu-d3d12 STATIC)
|
add_library(xenia-gpu-d3d12 STATIC)
|
||||||
xe_platform_sources(xenia-gpu-d3d12 ${CMAKE_CURRENT_SOURCE_DIR})
|
xe_platform_sources(xenia-gpu-d3d12 ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
# Include shader bytecode headers
|
xe_shader_rules_dxbc(xenia-gpu-d3d12 ${CMAKE_CURRENT_SOURCE_DIR}/../shaders)
|
||||||
file(GLOB _d3d12_shader_headers "${CMAKE_CURRENT_SOURCE_DIR}/../shaders/bytecode/d3d12_5_1/*.h"
|
|
||||||
)
|
|
||||||
target_sources(xenia-gpu-d3d12 PRIVATE ${_d3d12_shader_headers})
|
|
||||||
target_link_libraries(xenia-gpu-d3d12 PUBLIC
|
target_link_libraries(xenia-gpu-d3d12 PUBLIC
|
||||||
fmt xxhash xenia-base xenia-gpu xenia-ui xenia-ui-d3d12
|
fmt xxhash xenia-base xenia-gpu xenia-ui xenia-ui-d3d12
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
add_library(xenia-gpu-vulkan STATIC)
|
add_library(xenia-gpu-vulkan STATIC)
|
||||||
xe_platform_sources(xenia-gpu-vulkan ${CMAKE_CURRENT_SOURCE_DIR})
|
xe_platform_sources(xenia-gpu-vulkan ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
# Include shader bytecode headers
|
xe_shader_rules_spirv(xenia-gpu-vulkan ${CMAKE_CURRENT_SOURCE_DIR}/../shaders)
|
||||||
file(GLOB _vk_shader_headers "${CMAKE_CURRENT_SOURCE_DIR}/../shaders/bytecode/vulkan_spirv/*.h"
|
|
||||||
)
|
|
||||||
target_sources(xenia-gpu-vulkan PRIVATE ${_vk_shader_headers})
|
|
||||||
target_include_directories(xenia-gpu-vulkan PRIVATE
|
target_include_directories(xenia-gpu-vulkan PRIVATE
|
||||||
${PROJECT_SOURCE_DIR}/third_party/Vulkan-Headers/include
|
${PROJECT_SOURCE_DIR}/third_party/Vulkan-Headers/include
|
||||||
${PROJECT_SOURCE_DIR}/third_party/glslang
|
${PROJECT_SOURCE_DIR}/third_party/glslang
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
add_library(xenia-ui-d3d12 STATIC)
|
add_library(xenia-ui-d3d12 STATIC)
|
||||||
xe_platform_sources(xenia-ui-d3d12 ${CMAKE_CURRENT_SOURCE_DIR})
|
xe_platform_sources(xenia-ui-d3d12 ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
# Include shader bytecode headers
|
xe_shader_rules_dxbc(xenia-ui-d3d12 ${CMAKE_CURRENT_SOURCE_DIR}/../shaders)
|
||||||
file(GLOB _d3d12_ui_shader_headers "${CMAKE_CURRENT_SOURCE_DIR}/../shaders/bytecode/d3d12_5_1/*.h"
|
|
||||||
)
|
|
||||||
target_sources(xenia-ui-d3d12 PRIVATE ${_d3d12_ui_shader_headers})
|
|
||||||
target_link_libraries(xenia-ui-d3d12 PUBLIC xenia-base xenia-ui)
|
target_link_libraries(xenia-ui-d3d12 PUBLIC xenia-base xenia-ui)
|
||||||
xe_target_defaults(xenia-ui-d3d12)
|
xe_target_defaults(xenia-ui-d3d12)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
add_library(xenia-ui-vulkan STATIC)
|
add_library(xenia-ui-vulkan STATIC)
|
||||||
xe_platform_sources(xenia-ui-vulkan ${CMAKE_CURRENT_SOURCE_DIR})
|
xe_platform_sources(xenia-ui-vulkan ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
xe_platform_sources(xenia-ui-vulkan ${CMAKE_CURRENT_SOURCE_DIR}/functions)
|
xe_platform_sources(xenia-ui-vulkan ${CMAKE_CURRENT_SOURCE_DIR}/functions)
|
||||||
# Include shader bytecode headers
|
xe_shader_rules_spirv(xenia-ui-vulkan ${CMAKE_CURRENT_SOURCE_DIR}/../shaders)
|
||||||
file(GLOB _vk_ui_shader_headers "${CMAKE_CURRENT_SOURCE_DIR}/../shaders/bytecode/vulkan_spirv/*.h"
|
|
||||||
)
|
|
||||||
target_sources(xenia-ui-vulkan PRIVATE ${_vk_ui_shader_headers})
|
|
||||||
target_include_directories(xenia-ui-vulkan PRIVATE
|
target_include_directories(xenia-ui-vulkan PRIVATE
|
||||||
${PROJECT_SOURCE_DIR}/third_party/Vulkan-Headers/include
|
${PROJECT_SOURCE_DIR}/third_party/Vulkan-Headers/include
|
||||||
${PROJECT_SOURCE_DIR}/third_party/glslang
|
${PROJECT_SOURCE_DIR}/third_party/glslang
|
||||||
|
|||||||
121
tools/build/compile_shader_dxbc.py
Normal file
121
tools/build/compile_shader_dxbc.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""Compiles a single shader to DXBC and generates a C header via FXC.
|
||||||
|
|
||||||
|
Usage: compile_shader_dxbc.py <input_path> <output_path>
|
||||||
|
|
||||||
|
FXC directly produces the .h file — no post-processing needed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
|
||||||
|
DXBC_STAGES = ["vs", "hs", "ds", "gs", "ps", "cs"]
|
||||||
|
|
||||||
|
|
||||||
|
def find_fxc():
|
||||||
|
"""Find FXC via FXC_PATH env or Windows SDK search."""
|
||||||
|
fxc = os.environ.get("FXC_PATH")
|
||||||
|
if fxc:
|
||||||
|
return fxc
|
||||||
|
|
||||||
|
# Search Windows Kits.
|
||||||
|
program_files_x86 = os.environ.get("ProgramFiles(x86)", "")
|
||||||
|
candidates = glob(os.path.join(
|
||||||
|
program_files_x86, "Windows Kits", "10", "bin", "*", "x64", "fxc.exe"))
|
||||||
|
if candidates:
|
||||||
|
return candidates[-1] # Highest version is last
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_stage(filename):
|
||||||
|
"""Extract the 2-char shader stage from filename like 'foo.cs.xesl'."""
|
||||||
|
basename = os.path.splitext(filename)[0] # 'foo.cs'
|
||||||
|
identifier = basename.replace(".", "_") # 'foo_cs'
|
||||||
|
stage_key = identifier[-2:]
|
||||||
|
if stage_key not in DXBC_STAGES:
|
||||||
|
return None
|
||||||
|
return stage_key
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print(f"Usage: {sys.argv[0]} <input_path> <output_path>", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
input_path = sys.argv[1]
|
||||||
|
output_path = sys.argv[2]
|
||||||
|
|
||||||
|
src_name = os.path.basename(input_path)
|
||||||
|
src_dir = os.path.dirname(input_path)
|
||||||
|
|
||||||
|
stage = parse_stage(src_name)
|
||||||
|
if stage is None:
|
||||||
|
print(f"ERROR: cannot determine shader stage from: {src_name}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
identifier = os.path.splitext(src_name)[0].replace(".", "_")
|
||||||
|
|
||||||
|
fxc = find_fxc()
|
||||||
|
if not fxc:
|
||||||
|
print("ERROR: could not find fxc! Set FXC_PATH environment variable "
|
||||||
|
"or install Windows SDK.", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Create output directory if needed.
|
||||||
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||||
|
|
||||||
|
is_dxc = "dxc" in os.path.basename(fxc).lower()
|
||||||
|
|
||||||
|
# Start with base command — use wine on non-Windows platforms.
|
||||||
|
if sys.platform != "win32":
|
||||||
|
compiler_args = ["wine", fxc]
|
||||||
|
else:
|
||||||
|
compiler_args = [fxc]
|
||||||
|
|
||||||
|
if is_dxc:
|
||||||
|
# DXC only supports SM 6.0+.
|
||||||
|
compiler_args.extend([
|
||||||
|
"-T", f"{stage}_6_0",
|
||||||
|
"-HV", "2017",
|
||||||
|
"-D", "SHADING_LANGUAGE_HLSL_XE=1",
|
||||||
|
"-I", src_dir,
|
||||||
|
"-Fh", output_path,
|
||||||
|
"-Vn", identifier,
|
||||||
|
"-nologo",
|
||||||
|
input_path,
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
# FXC uses traditional syntax.
|
||||||
|
compiler_args.extend([
|
||||||
|
"/D", "SHADING_LANGUAGE_HLSL_XE=1",
|
||||||
|
"/I", src_dir,
|
||||||
|
"/Fh", output_path,
|
||||||
|
"/T", f"{stage}_5_1",
|
||||||
|
"/Vn", identifier,
|
||||||
|
"/O3",
|
||||||
|
"/Qstrip_reflect",
|
||||||
|
"/Qstrip_debug",
|
||||||
|
"/Qstrip_priv",
|
||||||
|
"/Gfp",
|
||||||
|
"/nologo",
|
||||||
|
input_path,
|
||||||
|
])
|
||||||
|
|
||||||
|
result = subprocess.run(compiler_args, stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"ERROR: failed to compile DXBC shader: {src_name}", file=sys.stderr)
|
||||||
|
if result.stderr:
|
||||||
|
sys.stderr.buffer.write(result.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
171
tools/build/compile_shader_spirv.py
Normal file
171
tools/build/compile_shader_spirv.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""Compiles a single shader to SPIR-V and generates a C header.
|
||||||
|
|
||||||
|
Usage: compile_shader_spirv.py <input_path> <output_path>
|
||||||
|
|
||||||
|
Pipeline:
|
||||||
|
1. glslangValidator -> unoptimized .spv
|
||||||
|
2. spirv-opt -> optimized .spv
|
||||||
|
3. spirv-dis -> disassembly .txt
|
||||||
|
4. Generate .h with disassembly comment + uint32_t array
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
SPIRV_STAGES = {
|
||||||
|
"vs": "vert", "hs": "tesc", "ds": "tese",
|
||||||
|
"gs": "geom", "ps": "frag", "cs": "comp",
|
||||||
|
}
|
||||||
|
|
||||||
|
XESL_WRAPPER = (
|
||||||
|
"#version 460\n"
|
||||||
|
"#extension GL_EXT_control_flow_attributes : require\n"
|
||||||
|
"#extension GL_EXT_samplerless_texture_functions : require\n"
|
||||||
|
"#extension GL_GOOGLE_include_directive : require\n"
|
||||||
|
"#include \"%s\"\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def find_vulkan_tools():
|
||||||
|
"""Find Vulkan SDK tools via VULKAN_SDK env or PATH."""
|
||||||
|
vulkan_sdk = os.environ.get("VULKAN_SDK")
|
||||||
|
if vulkan_sdk:
|
||||||
|
bin_dir = os.path.join(vulkan_sdk, "bin")
|
||||||
|
if os.path.isdir(bin_dir):
|
||||||
|
return (
|
||||||
|
os.path.join(bin_dir, "glslangValidator"),
|
||||||
|
os.path.join(bin_dir, "spirv-opt"),
|
||||||
|
os.path.join(bin_dir, "spirv-dis"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fall back to PATH
|
||||||
|
return ("glslangValidator", "spirv-opt", "spirv-dis")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_stage(filename):
|
||||||
|
"""Extract the 2-char shader stage from filename like 'foo.cs.xesl'."""
|
||||||
|
basename = os.path.splitext(filename)[0] # 'foo.cs'
|
||||||
|
identifier = basename.replace(".", "_") # 'foo_cs'
|
||||||
|
stage_key = identifier[-2:]
|
||||||
|
if stage_key not in SPIRV_STAGES:
|
||||||
|
return None, None
|
||||||
|
return stage_key, SPIRV_STAGES[stage_key]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print(f"Usage: {sys.argv[0]} <input_path> <output_path>", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
input_path = sys.argv[1]
|
||||||
|
output_path = sys.argv[2]
|
||||||
|
|
||||||
|
src_name = os.path.basename(input_path)
|
||||||
|
src_dir = os.path.dirname(input_path)
|
||||||
|
src_is_xesl = src_name.endswith(".xesl")
|
||||||
|
|
||||||
|
stage_key, spirv_stage = parse_stage(src_name)
|
||||||
|
if spirv_stage is None:
|
||||||
|
print(f"ERROR: cannot determine shader stage from: {src_name}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Compute identifier (matches what Lua does: basename with dots -> underscores)
|
||||||
|
identifier = os.path.splitext(src_name)[0].replace(".", "_")
|
||||||
|
|
||||||
|
glslang, spirv_opt, spirv_dis = find_vulkan_tools()
|
||||||
|
|
||||||
|
# Create output directory if needed.
|
||||||
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||||
|
|
||||||
|
# Temp file paths next to output.
|
||||||
|
base = os.path.splitext(output_path)[0]
|
||||||
|
glslang_spv = base + ".glslang.spv"
|
||||||
|
opt_spv = base + ".spv"
|
||||||
|
dis_txt = base + ".txt"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Step 1: glslangValidator
|
||||||
|
glslang_args = [
|
||||||
|
glslang,
|
||||||
|
"--stdin" if src_is_xesl else input_path,
|
||||||
|
"-DSHADING_LANGUAGE_GLSL_XE=1",
|
||||||
|
"-S", spirv_stage,
|
||||||
|
"-o", glslang_spv,
|
||||||
|
"-V",
|
||||||
|
]
|
||||||
|
if src_is_xesl:
|
||||||
|
glslang_args.append(f"-I{src_dir}")
|
||||||
|
|
||||||
|
stdin_data = (XESL_WRAPPER % src_name) if src_is_xesl else None
|
||||||
|
result = subprocess.run(glslang_args, input=stdin_data, text=True,
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"ERROR: glslangValidator failed for {src_name}", file=sys.stderr)
|
||||||
|
if result.stderr:
|
||||||
|
sys.stderr.write(result.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Step 2: spirv-opt
|
||||||
|
result = subprocess.run([
|
||||||
|
spirv_opt, "-O", "-O", "--canonicalize-ids",
|
||||||
|
glslang_spv, "-o", opt_spv,
|
||||||
|
], stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"ERROR: spirv-opt failed for {src_name}", file=sys.stderr)
|
||||||
|
if result.stderr:
|
||||||
|
sys.stderr.write(result.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Step 3: spirv-dis
|
||||||
|
result = subprocess.run([spirv_dis, "-o", dis_txt, opt_spv],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"ERROR: spirv-dis failed for {src_name}", file=sys.stderr)
|
||||||
|
if result.stderr:
|
||||||
|
sys.stderr.write(result.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Step 4: Generate header
|
||||||
|
with open(output_path, "w") as out:
|
||||||
|
out.write("// Generated with `xb buildshaders`.\n#if 0\n")
|
||||||
|
with open(dis_txt, "r") as dis_file:
|
||||||
|
dis_data = dis_file.read()
|
||||||
|
if dis_data:
|
||||||
|
out.write(dis_data)
|
||||||
|
if dis_data[-1] != "\n":
|
||||||
|
out.write("\n")
|
||||||
|
out.write("#endif\n\nconst uint32_t %s[] = {" % identifier)
|
||||||
|
with open(opt_spv, "rb") as spv_file:
|
||||||
|
index = 0
|
||||||
|
while True:
|
||||||
|
word = spv_file.read(4)
|
||||||
|
if len(word) == 0:
|
||||||
|
break
|
||||||
|
if len(word) != 4:
|
||||||
|
print("ERROR: SPIR-V binary is misaligned", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if index % 6 == 0:
|
||||||
|
out.write("\n ")
|
||||||
|
else:
|
||||||
|
out.write(" ")
|
||||||
|
index += 1
|
||||||
|
value = struct.unpack("<I", word)[0]
|
||||||
|
out.write("0x%08X," % value)
|
||||||
|
out.write("\n};\n")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up intermediate files.
|
||||||
|
for f in (glslang_spv, opt_spv, dis_txt):
|
||||||
|
if os.path.exists(f):
|
||||||
|
os.remove(f)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
234
xenia-build.py
234
xenia-build.py
@@ -943,13 +943,6 @@ class BuildCommand(BaseBuildCommand):
|
|||||||
def execute(self, args, pass_args, cwd):
|
def execute(self, args, pass_args, cwd):
|
||||||
print(f"Building {args['config']}...\n")
|
print(f"Building {args['config']}...\n")
|
||||||
|
|
||||||
# Generate shader bytecode before building
|
|
||||||
shader_result = build_shaders()
|
|
||||||
if shader_result != 0:
|
|
||||||
print(f"{bcolors.FAIL}ERROR: Shader generation failed{bcolors.ENDC}")
|
|
||||||
return shader_result
|
|
||||||
|
|
||||||
|
|
||||||
result = super(BuildCommand, self).execute(args, pass_args, cwd)
|
result = super(BuildCommand, self).execute(args, pass_args, cwd)
|
||||||
|
|
||||||
print_status(ResultStatus.SUCCESS if not result else ResultStatus.FAILURE)
|
print_status(ResultStatus.SUCCESS if not result else ResultStatus.FAILURE)
|
||||||
@@ -981,7 +974,9 @@ class BuildShadersCommand(Command):
|
|||||||
|
|
||||||
|
|
||||||
def build_shaders(targets=None):
|
def build_shaders(targets=None):
|
||||||
"""Builds shader bytecode. Called by BuildShadersCommand and BuildCommand.
|
"""Builds shader bytecode. Called by BuildShadersCommand.
|
||||||
|
|
||||||
|
Delegates to the per-file compile scripts in tools/build/.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
targets: List of targets ("dxbc", "spirv"), or None/empty for all.
|
targets: List of targets ("dxbc", "spirv"), or None/empty for all.
|
||||||
@@ -1026,226 +1021,47 @@ def build_shaders(targets=None):
|
|||||||
targets = []
|
targets = []
|
||||||
all_targets = len(targets) == 0
|
all_targets = len(targets) == 0
|
||||||
|
|
||||||
# XeSL ("Xenia Shading Language") means shader files that can be
|
valid_stages = ["vs", "hs", "ds", "gs", "ps", "cs"]
|
||||||
# compiled as multiple languages from a single file. Whenever possible,
|
compile_spirv = os.path.join(self_path, "tools", "build", "compile_shader_spirv.py")
|
||||||
# this is achieved without the involvement of the build script, using
|
compile_dxbc = os.path.join(self_path, "tools", "build", "compile_shader_dxbc.py")
|
||||||
# just conditionals, macros and functions in shaders, however, in some
|
|
||||||
# cases, that's necessary (such as to prepend `#version` in GLSL, as
|
|
||||||
# well as to enable `#include` in GLSL, to include `xesl.xesli` itself,
|
|
||||||
# without writing the same `#if` / `#extension` / `#endif` in every
|
|
||||||
# shader). Also, not all shading languages provide a built-in
|
|
||||||
# preprocessor definition for identification of them, so
|
|
||||||
# `SHADING_LANGUAGE_*_XE` is also defined via the build arguments.
|
|
||||||
# `SHADING_LANGUAGE_*_XE` is set regardless of whether the file is XeSL
|
|
||||||
# or a raw source file in a specific language, as XeSL headers may be
|
|
||||||
# used in language-specific sources.
|
|
||||||
|
|
||||||
# Direct3D DXBC (Windows only).
|
# Direct3D DXBC (Windows only).
|
||||||
if (all_targets or "dxbc" in targets) and sys.platform == "win32":
|
if (all_targets or "dxbc" in targets) and sys.platform == "win32":
|
||||||
print("Building Direct3D 12 Shader Model 5.1 DXBC shaders...")
|
print("Building Direct3D 12 Shader Model 5.1 DXBC shaders...")
|
||||||
|
|
||||||
# Get the FXC path.
|
|
||||||
fxc = os.environ.get("FXC_PATH")
|
|
||||||
if not fxc:
|
|
||||||
# Fall back to searching Windows Kits
|
|
||||||
fxc = glob(os.path.join(os.environ.get("ProgramFiles(x86)", ""),
|
|
||||||
"Windows Kits", "10", "bin", "*", "x64", "fxc.exe"))
|
|
||||||
if not fxc:
|
|
||||||
print("ERROR: could not find fxc! Set FXC_PATH environment variable or install Windows SDK.")
|
|
||||||
return 1
|
|
||||||
fxc = fxc[-1] # Highest version is last
|
|
||||||
else:
|
|
||||||
print(f"Using FXC from environment variable: {fxc}")
|
|
||||||
|
|
||||||
# Build DXBC.
|
|
||||||
dxbc_stages = ["vs", "hs", "ds", "gs", "ps", "cs"]
|
|
||||||
for src_path in src_paths:
|
for src_path in src_paths:
|
||||||
src_name = os.path.basename(src_path)
|
src_name = os.path.basename(src_path)
|
||||||
if ((not src_name.endswith(".hlsl") and
|
if ((not src_name.endswith(".hlsl") and
|
||||||
not src_name.endswith(".xesl")) or
|
not src_name.endswith(".xesl")) or
|
||||||
len(src_name) <= 8 or src_name[-8] != "."):
|
len(src_name) <= 8 or src_name[-8] != "."):
|
||||||
continue
|
continue
|
||||||
dxbc_identifier = src_name[:-5].replace(".", "_")
|
identifier = src_name[:-5].replace(".", "_")
|
||||||
dxbc_stage = dxbc_identifier[-2:]
|
if identifier[-2:] not in valid_stages:
|
||||||
if dxbc_stage not in dxbc_stages:
|
|
||||||
continue
|
continue
|
||||||
print(f"- {src_path} > d3d12_5_1")
|
|
||||||
dxbc_dir_path = os.path.join(os.path.dirname(src_path),
|
|
||||||
"bytecode/d3d12_5_1")
|
|
||||||
os.makedirs(dxbc_dir_path, exist_ok=True)
|
|
||||||
dxbc_file_path_base = os.path.join(dxbc_dir_path, dxbc_identifier)
|
|
||||||
# Not enabling treating warnings as errors (/WX) because it
|
|
||||||
# overrides #pragma warning, and the FXAA shader triggers a
|
|
||||||
# bug in FXC causing an uninitialized variable warning if
|
|
||||||
# early exit from a function is done.
|
|
||||||
# FXC writes errors and warnings to stderr, not stdout, but
|
|
||||||
# stdout receives generic status messages that only add
|
|
||||||
# clutter in this case.
|
|
||||||
# Check if using DXC or FXC based on executable name
|
|
||||||
is_dxc = "dxc" in fxc.lower()
|
|
||||||
|
|
||||||
# Start with base command - use wine on non-Windows platforms
|
|
||||||
if sys.platform != "win32":
|
|
||||||
compiler_args = ["wine", fxc]
|
|
||||||
else:
|
|
||||||
compiler_args = [fxc]
|
|
||||||
|
|
||||||
src_dir = os.path.dirname(src_path)
|
src_dir = os.path.dirname(src_path)
|
||||||
if is_dxc:
|
output = os.path.join(src_dir, "bytecode", "d3d12_5_1", f"{identifier}.h")
|
||||||
# DXC only supports SM 6.0+, cannot compile SM 5.1
|
print(f"- {src_path} > d3d12_5_1")
|
||||||
print("WARNING: DXC doesn't support SM 5.1, using SM 6.0")
|
result = subprocess.call([sys.executable, compile_dxbc, src_path, output])
|
||||||
compiler_args.extend([
|
if result != 0:
|
||||||
"-T", f"{dxbc_stage}_6_0",
|
return result
|
||||||
"-HV", "2017",
|
|
||||||
"-D", "SHADING_LANGUAGE_HLSL_XE=1",
|
|
||||||
"-I", src_dir,
|
|
||||||
"-Fh", f"{dxbc_file_path_base}.h",
|
|
||||||
"-Vn", dxbc_identifier,
|
|
||||||
"-nologo",
|
|
||||||
src_path,
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
# FXC uses traditional syntax.
|
|
||||||
# Do NOT use /all_resources_bound — it alters the generated
|
|
||||||
# DXBC bytecode (changes dcl_globalFlags, register allocation
|
|
||||||
# and instruction scheduling) which causes rendering artifacts
|
|
||||||
# such as blue lightning/flash glitches at scaled resolutions.
|
|
||||||
compiler_args.extend([
|
|
||||||
"/D", "SHADING_LANGUAGE_HLSL_XE=1",
|
|
||||||
"/I", src_dir,
|
|
||||||
"/Fh", f"{dxbc_file_path_base}.h",
|
|
||||||
"/T", f"{dxbc_stage}_5_1",
|
|
||||||
"/Vn", dxbc_identifier,
|
|
||||||
"/O3",
|
|
||||||
"/Qstrip_reflect",
|
|
||||||
"/Qstrip_debug",
|
|
||||||
"/Qstrip_priv",
|
|
||||||
"/Gfp",
|
|
||||||
"/nologo",
|
|
||||||
src_path,
|
|
||||||
])
|
|
||||||
if subprocess.call(compiler_args, stdout=subprocess.DEVNULL) != 0:
|
|
||||||
print(f"ERROR: failed to compile DXBC shader: {src_path}")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Vulkan SPIR-V.
|
# Vulkan SPIR-V.
|
||||||
if all_targets or "spirv" in targets:
|
if all_targets or "spirv" in targets:
|
||||||
print("Building Vulkan SPIR-V shaders...")
|
print("Building Vulkan SPIR-V shaders...")
|
||||||
|
|
||||||
# Get the SPIR-V tool paths.
|
|
||||||
vulkan_sdk_path = os.environ.get("VULKAN_SDK")
|
|
||||||
if not vulkan_sdk_path:
|
|
||||||
print("ERROR: VULKAN_SDK environment variable is not set")
|
|
||||||
if sys.platform == "win32":
|
|
||||||
print("Please install Vulkan SDK from:")
|
|
||||||
print("https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-sdk.exe")
|
|
||||||
else:
|
|
||||||
print("Please install Vulkan SDK and set VULKAN_SDK environment variable")
|
|
||||||
return 1
|
|
||||||
if not os.path.exists(vulkan_sdk_path):
|
|
||||||
print(f"ERROR: could not find the Vulkan SDK at {vulkan_sdk_path}")
|
|
||||||
return 1
|
|
||||||
vulkan_bin_path = os.path.join(vulkan_sdk_path, "bin")
|
|
||||||
if not os.path.exists(vulkan_bin_path):
|
|
||||||
print("ERROR: could not find the Vulkan SDK binaries")
|
|
||||||
return 1
|
|
||||||
glslang = os.path.join(vulkan_bin_path, "glslangValidator")
|
|
||||||
if not has_bin(glslang):
|
|
||||||
print("ERROR: could not find glslangValidator")
|
|
||||||
return 1
|
|
||||||
spirv_opt = os.path.join(vulkan_bin_path, "spirv-opt")
|
|
||||||
if not has_bin(spirv_opt):
|
|
||||||
print("ERROR: could not find spirv-opt")
|
|
||||||
return 1
|
|
||||||
spirv_dis = os.path.join(vulkan_bin_path, "spirv-dis")
|
|
||||||
if not has_bin(spirv_dis):
|
|
||||||
print("ERROR: could not find spirv-dis")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Build SPIR-V.
|
|
||||||
spirv_stages = {
|
|
||||||
"vs": "vert", "hs": "tesc", "ds": "tese",
|
|
||||||
"gs": "geom", "ps": "frag", "cs": "comp",
|
|
||||||
}
|
|
||||||
spirv_xesl_wrapper = (
|
|
||||||
"#version 460\n"
|
|
||||||
"#extension GL_EXT_control_flow_attributes : require\n"
|
|
||||||
"#extension GL_EXT_samplerless_texture_functions : require\n"
|
|
||||||
"#extension GL_GOOGLE_include_directive : require\n"
|
|
||||||
"#include \"%s\"\n"
|
|
||||||
)
|
|
||||||
for src_path in src_paths:
|
for src_path in src_paths:
|
||||||
src_name = os.path.basename(src_path)
|
src_name = os.path.basename(src_path)
|
||||||
src_is_xesl = src_name.endswith(".xesl")
|
if ((not src_name.endswith(".glsl") and
|
||||||
if ((not src_is_xesl and not src_name.endswith(".glsl")) or
|
not src_name.endswith(".xesl")) or
|
||||||
len(src_name) <= 8 or src_name[-8] != "."):
|
len(src_name) <= 8 or src_name[-8] != "."):
|
||||||
continue
|
continue
|
||||||
spirv_identifier = src_name[:-5].replace(".", "_")
|
identifier = src_name[:-5].replace(".", "_")
|
||||||
spirv_stage = spirv_stages.get(spirv_identifier[-2:], None)
|
if identifier[-2:] not in valid_stages:
|
||||||
if spirv_stage is None:
|
|
||||||
continue
|
continue
|
||||||
print(f"- {src_path} > vulkan_spirv")
|
|
||||||
src_dir = os.path.dirname(src_path)
|
src_dir = os.path.dirname(src_path)
|
||||||
spirv_dir_path = os.path.join(src_dir, "bytecode/vulkan_spirv")
|
output = os.path.join(src_dir, "bytecode", "vulkan_spirv", f"{identifier}.h")
|
||||||
os.makedirs(spirv_dir_path, exist_ok=True)
|
print(f"- {src_path} > vulkan_spirv")
|
||||||
spirv_file_path_base = os.path.join(spirv_dir_path, spirv_identifier)
|
result = subprocess.call([sys.executable, compile_spirv, src_path, output])
|
||||||
spirv_glslang_file_path = f"{spirv_file_path_base}.glslang.spv"
|
if result != 0:
|
||||||
|
return result
|
||||||
glslang_arguments = [glslang,
|
|
||||||
"--stdin" if src_is_xesl else src_path,
|
|
||||||
"-DSHADING_LANGUAGE_GLSL_XE=1",
|
|
||||||
"-S", spirv_stage,
|
|
||||||
"-o", spirv_glslang_file_path,
|
|
||||||
"-V"]
|
|
||||||
if src_is_xesl:
|
|
||||||
glslang_arguments.append(f"-I{src_dir}")
|
|
||||||
if subprocess.run(
|
|
||||||
glslang_arguments,
|
|
||||||
input=(spirv_xesl_wrapper % src_name) if src_is_xesl else None,
|
|
||||||
text=True).returncode != 0:
|
|
||||||
print("ERROR: failed to build a SPIR-V shader")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
spirv_file_path = f"{spirv_file_path_base}.spv"
|
|
||||||
if subprocess.call([spirv_opt, "-O", "-O", "--canonicalize-ids",
|
|
||||||
spirv_glslang_file_path, "-o", spirv_file_path]) != 0:
|
|
||||||
print("ERROR: failed to optimize a SPIR-V shader")
|
|
||||||
return 1
|
|
||||||
os.remove(spirv_glslang_file_path)
|
|
||||||
|
|
||||||
spirv_dis_file_path = f"{spirv_file_path_base}.txt"
|
|
||||||
if subprocess.call([spirv_dis, "-o", spirv_dis_file_path,
|
|
||||||
spirv_file_path]) != 0:
|
|
||||||
print("ERROR: failed to disassemble a SPIR-V shader")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Generate the header from the disassembly and the binary.
|
|
||||||
with open(f"{spirv_file_path_base}.h", "w") as out_file:
|
|
||||||
out_file.write("// Generated with `xb buildshaders`.\n#if 0\n")
|
|
||||||
with open(spirv_dis_file_path, "r") as spirv_dis_file:
|
|
||||||
spirv_dis_data = spirv_dis_file.read()
|
|
||||||
if len(spirv_dis_data) > 0:
|
|
||||||
out_file.write(spirv_dis_data)
|
|
||||||
if spirv_dis_data[-1] != "\n":
|
|
||||||
out_file.write("\n")
|
|
||||||
out_file.write("#endif\n\nconst uint32_t %s[] = {" % spirv_identifier)
|
|
||||||
with open(spirv_file_path, "rb") as spirv_file:
|
|
||||||
index = 0
|
|
||||||
c = spirv_file.read(4)
|
|
||||||
while len(c) != 0:
|
|
||||||
if len(c) != 4:
|
|
||||||
print("ERROR: a SPIR-V shader is misaligned")
|
|
||||||
return 1
|
|
||||||
if index % 6 == 0:
|
|
||||||
out_file.write("\n ")
|
|
||||||
else:
|
|
||||||
out_file.write(" ")
|
|
||||||
index += 1
|
|
||||||
out_file.write("0x%08X," % int.from_bytes(c, sys.byteorder))
|
|
||||||
c = spirv_file.read(4)
|
|
||||||
out_file.write("\n};\n")
|
|
||||||
os.remove(spirv_dis_file_path)
|
|
||||||
os.remove(spirv_file_path)
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -1976,12 +1792,6 @@ class DevenvCommand(Command):
|
|||||||
else:
|
else:
|
||||||
print("IDE not detected. CMakeLists.txt is in the project root.")
|
print("IDE not detected. CMakeLists.txt is in the project root.")
|
||||||
|
|
||||||
print("\n- generating shaders...")
|
|
||||||
shader_result = build_shaders()
|
|
||||||
if shader_result != 0:
|
|
||||||
print_error("Shader generation failed")
|
|
||||||
return shader_result
|
|
||||||
|
|
||||||
print("\n- running cmake configure...")
|
print("\n- running cmake configure...")
|
||||||
run_cmake_configure()
|
run_cmake_configure()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user