mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
[Build] Replace VULKAN_SDK toolchain with glslang / SPIRV-Tools
This commit is contained in:
@@ -65,13 +65,6 @@ jobs:
|
||||
path: third_party
|
||||
key: ${{ runner.os }}-submodules-${{ hashFiles('.gitmodules') }}-${{ hashFiles('.git/modules/third_party/*/HEAD') }}
|
||||
|
||||
- name: Cache Vulkan SDK
|
||||
id: cache-vulkan-sdk-linux
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/vulkan-sdk
|
||||
key: ${{ runner.os }}-vulkan-sdk-latest
|
||||
|
||||
- name: Setup
|
||||
run: |
|
||||
UBUNTU_BASE=$(lsb_release -cs)
|
||||
@@ -79,17 +72,7 @@ jobs:
|
||||
sudo apt-add-repository "deb http://apt.llvm.org/${UBUNTU_BASE}/ llvm-toolchain-${UBUNTU_BASE}-21 main"
|
||||
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install mesa-vulkan-drivers valgrind libc++-dev libc++abi-dev libgtk-3-dev libsdl2-dev libvulkan-dev libx11-xcb-dev clang-21 lld-21 ninja-build cmake libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-cursor0 libxcb-shape0 libfontconfig1-dev libxtst-dev
|
||||
|
||||
# Install Vulkan SDK
|
||||
if [ '${{ steps.cache-vulkan-sdk-linux.outputs.cache-hit }}' != 'true' ]; then
|
||||
wget -qO vulkan-sdk.tar.xz https://sdk.lunarg.com/sdk/download/latest/linux/vulkan-sdk.tar.xz
|
||||
mkdir -p ~/vulkan-sdk
|
||||
tar -xf vulkan-sdk.tar.xz -C ~/vulkan-sdk
|
||||
fi
|
||||
VULKAN_SDK_VERSION=$(ls ~/vulkan-sdk)
|
||||
echo "VULKAN_SDK=$HOME/vulkan-sdk/$VULKAN_SDK_VERSION/x86_64" >> $GITHUB_ENV
|
||||
echo "$HOME/vulkan-sdk/$VULKAN_SDK_VERSION/x86_64/bin" >> $GITHUB_PATH
|
||||
sudo apt-get -y install valgrind libc++-dev libc++abi-dev libgtk-3-dev libsdl2-dev libx11-xcb-dev clang-21 lld-21 ninja-build cmake libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-cursor0 libxcb-shape0 libfontconfig1-dev libxtst-dev
|
||||
|
||||
# Install Qt 6.10.1 via aqtinstall
|
||||
if [ '${{ steps.cache-qt-linux.outputs.cache-hit }}' != 'true' ]; then
|
||||
@@ -117,11 +100,6 @@ jobs:
|
||||
fi
|
||||
echo "$HOME/linuxdeploy" >> $GITHUB_PATH
|
||||
|
||||
# Verify shader tools are available
|
||||
which glslangValidator && glslangValidator --version || echo "Warning: glslangValidator not found"
|
||||
which spirv-opt && spirv-opt --version || echo "Warning: spirv-opt not found"
|
||||
which spirv-dis || echo "Warning: spirv-dis not found"
|
||||
|
||||
# Update submodules if not cached
|
||||
if [ '${{ steps.cache-submodules.outputs.cache-hit }}' != 'true' ]; then
|
||||
git submodule update --init --depth=1 -j$(getconf _NPROCESSORS_ONLN) $(grep -oP '(?<=path = )(?!third_party\/DirectXShaderCompiler).+' .gitmodules)
|
||||
@@ -217,15 +195,6 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: false
|
||||
|
||||
- name: Cache Vulkan SDK
|
||||
id: cache-vulkan-sdk
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: C:\VulkanSDK
|
||||
key: ${{ runner.os }}-vulkan-sdk-${{ hashFiles('**/vulkan-sdk.exe') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-vulkan-sdk-
|
||||
|
||||
- name: Cache Qt6
|
||||
id: cache-qt
|
||||
uses: actions/cache@v3
|
||||
@@ -244,42 +213,6 @@ jobs:
|
||||
|
||||
- name: Setup
|
||||
run: |
|
||||
# Install Vulkan SDK for headers + loader
|
||||
if (Test-Path -Path "C:\VulkanSDK") {
|
||||
echo "Vulkan SDK found in cache."
|
||||
} else {
|
||||
Invoke-WebRequest -Uri "https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-sdk.exe" -OutFile "vulkan-sdk.exe"
|
||||
Start-Process -FilePath "vulkan-sdk.exe" -ArgumentList "--accept-licenses", "--default-answer", "--confirm-command", "install" -Wait
|
||||
}
|
||||
$env:VULKAN_SDK = "C:\VulkanSDK\$(Get-ChildItem -Path 'C:\VulkanSDK' -Directory | Select-Object -First 1 -ExpandProperty Name)"
|
||||
$env:PATH = "$env:VULKAN_SDK\Bin;$env:PATH"
|
||||
echo "VULKAN_SDK=$env:VULKAN_SDK" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "$env:VULKAN_SDK\Bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
# Verify shader tools are available
|
||||
$spirvOptPath = "$env:VULKAN_SDK\Bin\spirv-opt.exe"
|
||||
if (Test-Path $spirvOptPath) {
|
||||
& $spirvOptPath --version
|
||||
echo "spirv-opt found at: $spirvOptPath"
|
||||
} else {
|
||||
echo "Warning: spirv-opt.exe not found at expected location"
|
||||
}
|
||||
|
||||
$glslangPath = "$env:VULKAN_SDK\Bin\glslangValidator.exe"
|
||||
if (Test-Path $glslangPath) {
|
||||
& $glslangPath --version
|
||||
echo "glslangValidator found at: $glslangPath"
|
||||
} else {
|
||||
echo "Warning: glslangValidator.exe not found at expected location"
|
||||
}
|
||||
|
||||
$spirvDisPath = "$env:VULKAN_SDK\Bin\spirv-dis.exe"
|
||||
if (Test-Path $spirvDisPath) {
|
||||
echo "spirv-dis found at: $spirvDisPath"
|
||||
} else {
|
||||
echo "Warning: spirv-dis.exe not found at expected location"
|
||||
}
|
||||
|
||||
# Verify FXC is available (from Windows SDK)
|
||||
$fxcPaths = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\fxc.exe" -ErrorAction SilentlyContinue | Sort-Object FullName
|
||||
if ($fxcPaths) {
|
||||
|
||||
@@ -110,3 +110,11 @@
|
||||
[submodule "third_party/xbyak_aarch64"]
|
||||
path = third_party/xbyak_aarch64
|
||||
url = https://github.com/fujitsu/xbyak_aarch64.git
|
||||
[submodule "third_party/SPIRV-Headers"]
|
||||
path = third_party/SPIRV-Headers
|
||||
url = https://github.com/KhronosGroup/SPIRV-Headers.git
|
||||
branch = vulkan-sdk-1.4.341
|
||||
[submodule "third_party/SPIRV-Tools"]
|
||||
path = third_party/SPIRV-Tools
|
||||
url = https://github.com/KhronosGroup/SPIRV-Tools.git
|
||||
branch = vulkan-sdk-1.4.341
|
||||
|
||||
@@ -113,19 +113,17 @@ endfunction()
|
||||
|
||||
# xe_shader_rules_spirv(target shader_dir)
|
||||
#
|
||||
# Wires up SPIR-V shader compilation (glslang + spirv-opt + spirv-dis,
|
||||
# via tools/build/compile_shader_spirv.py) as a prerequisite of target.
|
||||
# Sources are *.xesl / *.glsl with a stage suffix (vs/hs/ds/gs/ps/cs);
|
||||
# outputs land in <shader_dir>/bytecode/vulkan_spirv/<id>.h. A stamp
|
||||
# file drives incremental rebuilds when any source or the compile
|
||||
# script changes.
|
||||
# Wires up SPIR-V shader compilation via the in-tree xenia-shader-cc host
|
||||
# tool (glslang-based) as a prerequisite of target. Sources are *.xesl /
|
||||
# *.glsl with a stage suffix (vs/hs/ds/gs/ps/cs); outputs land in
|
||||
# <shader_dir>/bytecode/vulkan_spirv/<id>.h. A stamp file drives
|
||||
# incremental rebuilds when any source or the tool 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")
|
||||
@@ -143,13 +141,14 @@ function(xe_shader_rules_spirv target shader_dir)
|
||||
if(NOT _stage IN_LIST _valid_stages)
|
||||
continue()
|
||||
endif()
|
||||
list(APPEND _commands COMMAND ${Python3_EXECUTABLE} "${_script}" "${src}" "${_bytecode_dir}/${_id}.h")
|
||||
list(APPEND _commands COMMAND $<TARGET_FILE:xenia-shader-cc>
|
||||
"${src}" "${_bytecode_dir}/${_id}.h")
|
||||
endforeach()
|
||||
add_custom_command(
|
||||
OUTPUT "${_stamp}"
|
||||
${_commands}
|
||||
COMMAND ${CMAKE_COMMAND} -E touch "${_stamp}"
|
||||
DEPENDS ${_sources} "${_script}"
|
||||
DEPENDS ${_sources} xenia-shader-cc
|
||||
COMMENT "Compiling SPIR-V shaders for ${target}..."
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
@@ -14,8 +14,6 @@ drivers.
|
||||
* Windows 11 SDK version 10.0.22000.0 (for Visual Studio 2022, this or any newer version)
|
||||
* [Python 3.6+ 64-bit](https://www.python.org/downloads/)
|
||||
* Ensure Python is in PATH.
|
||||
* [Vulkan SDK](https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-sdk.exe)
|
||||
* The build script will automatically detect it if installed at `C:\VulkanSDK`
|
||||
* Qt 6.10.1 (for the UI)
|
||||
* Install using aqtinstall:
|
||||
```
|
||||
@@ -70,15 +68,6 @@ xb gentests
|
||||
|
||||
# Run tests:
|
||||
xb test
|
||||
|
||||
# Run GPU tests:
|
||||
xb gputest
|
||||
|
||||
|
||||
## Other:
|
||||
|
||||
# Generate SPIR-V binaries and header files:
|
||||
xb genspirv
|
||||
-->
|
||||
|
||||
#### Cross-compiling (Windows ARM64 ↔ x64)
|
||||
|
||||
Vendored
+97
-1
@@ -156,12 +156,14 @@ add_library(fmt STATIC
|
||||
target_include_directories(fmt PUBLIC fmt/include)
|
||||
|
||||
# ==============================================================================
|
||||
# glslang-spirv (not needed on macOS — no Vulkan/SPIR-V backend)
|
||||
# glslang-spirv (runtime: SPIR-V emission primitives for Xenia's own
|
||||
# SpirvShaderTranslator; not needed on macOS — no Vulkan/SPIR-V backend)
|
||||
# ==============================================================================
|
||||
if(NOT APPLE)
|
||||
file(GLOB _glslang_spirv_cpp "glslang/SPIRV/*.cpp")
|
||||
file(GLOB _glslang_spirv_h "glslang/SPIRV/*.h")
|
||||
file(GLOB _glslang_spirv_hpp "glslang/SPIRV/*.hpp11")
|
||||
# Excluding GlslangToSpv keeps MachineIndependent out of the runtime link.
|
||||
list(FILTER _glslang_spirv_cpp EXCLUDE REGEX "GlslangToSpv\\.cpp$")
|
||||
add_library(glslang-spirv STATIC
|
||||
${_glslang_spirv_cpp}
|
||||
@@ -172,6 +174,100 @@ if(NOT APPLE)
|
||||
target_include_directories(glslang-spirv PUBLIC glslang)
|
||||
endif()
|
||||
|
||||
# ==============================================================================
|
||||
# SPIRV-Headers + SPIRV-Tools (host: SPIR-V optimization + disassembly
|
||||
# support for glslang's ENABLE_OPT path; used by the xenia-shader-cc tool)
|
||||
# ==============================================================================
|
||||
# Point SPIRV-Tools at our in-tree SPIRV-Headers checkout instead of the
|
||||
# external/spirv-headers path it expects by default.
|
||||
set(SPIRV-Headers_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/SPIRV-Headers"
|
||||
CACHE INTERNAL "")
|
||||
set(SPIRV_SKIP_TESTS ON CACHE INTERNAL "")
|
||||
set(SPIRV_SKIP_EXECUTABLES ON CACHE INTERNAL "")
|
||||
set(SKIP_SPIRV_TOOLS_INSTALL ON CACHE INTERNAL "")
|
||||
set(SPIRV_WERROR OFF CACHE INTERNAL "")
|
||||
set(SPIRV_BUILD_FUZZER OFF CACHE INTERNAL "")
|
||||
add_subdirectory(SPIRV-Tools EXCLUDE_FROM_ALL)
|
||||
|
||||
# SPIRV-Tools unconditionally forces /W2 /WX on MSVC via its own
|
||||
# target_compile_options, which conflicts with the directory-level /W0 and
|
||||
# produces D9025 "overriding /W0 with /W2" for every translation unit. Strip
|
||||
# the /W* and /WX flags it added so only the directory-level /W0 survives.
|
||||
if(MSVC)
|
||||
foreach(_spvt_target
|
||||
SPIRV-Tools-static SPIRV-Tools-shared SPIRV-Tools-opt
|
||||
SPIRV-Tools-link SPIRV-Tools-reduce SPIRV-Tools-diff)
|
||||
if(TARGET ${_spvt_target})
|
||||
get_target_property(_opts ${_spvt_target} COMPILE_OPTIONS)
|
||||
if(_opts)
|
||||
list(FILTER _opts EXCLUDE REGEX "^/[Ww]([0-9]|all|X)$")
|
||||
set_target_properties(${_spvt_target} PROPERTIES COMPILE_OPTIONS "${_opts}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# ==============================================================================
|
||||
# glslang (host: full frontend + SPIR-V emission for the xenia-shader-cc
|
||||
# build-time tool; all platforms, including macOS)
|
||||
# ==============================================================================
|
||||
# Read glslang's own version file to generate build_info.h in sync with the
|
||||
# pinned submodule, matching what glslang's own CMakeLists does.
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/glslang/parse_version.cmake)
|
||||
parse_version(${CMAKE_CURRENT_SOURCE_DIR}/glslang/CHANGES.md GLSLANG)
|
||||
set(major ${GLSLANG_VERSION_MAJOR})
|
||||
set(minor ${GLSLANG_VERSION_MINOR})
|
||||
set(patch ${GLSLANG_VERSION_PATCH})
|
||||
set(flavor ${GLSLANG_VERSION_FLAVOR})
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/glslang/build_info.h.tmpl
|
||||
${CMAKE_CURRENT_BINARY_DIR}/glslang-build-info/glslang/build_info.h
|
||||
@ONLY)
|
||||
|
||||
file(GLOB _glslang_mi
|
||||
"glslang/glslang/MachineIndependent/*.cpp"
|
||||
"glslang/glslang/MachineIndependent/preprocessor/*.cpp"
|
||||
"glslang/glslang/GenericCodeGen/*.cpp")
|
||||
file(GLOB _glslang_spirv_all "glslang/SPIRV/*.cpp")
|
||||
add_library(glslang STATIC
|
||||
${_glslang_mi}
|
||||
${_glslang_spirv_all}
|
||||
glslang/glslang/ResourceLimits/ResourceLimits.cpp
|
||||
)
|
||||
if(WIN32)
|
||||
target_sources(glslang PRIVATE glslang/glslang/OSDependent/Windows/ossource.cpp)
|
||||
else()
|
||||
target_sources(glslang PRIVATE glslang/glslang/OSDependent/Unix/ossource.cpp)
|
||||
endif()
|
||||
target_include_directories(glslang PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/glslang
|
||||
${CMAKE_CURRENT_BINARY_DIR}/glslang-build-info)
|
||||
target_compile_features(glslang PUBLIC cxx_std_17)
|
||||
# Enable glslang's SpvTools.cpp optimizer/disassembler bridge to SPIRV-Tools.
|
||||
target_compile_definitions(glslang PRIVATE ENABLE_OPT=1)
|
||||
target_link_libraries(glslang PUBLIC SPIRV-Tools-opt SPIRV-Tools-static)
|
||||
# glslang relies on exceptions internally.
|
||||
if(MSVC)
|
||||
target_compile_options(glslang PRIVATE /EHsc)
|
||||
endif()
|
||||
if(NOT WIN32)
|
||||
find_package(Threads)
|
||||
if(Threads_FOUND)
|
||||
target_link_libraries(glslang PUBLIC Threads::Threads)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ==============================================================================
|
||||
# xenia-shader-cc (build-time host tool: GLSL/XeSL -> SPIR-V -> .h)
|
||||
# ==============================================================================
|
||||
add_executable(xenia-shader-cc ${PROJECT_SOURCE_DIR}/tools/build/shader_cc.cc)
|
||||
target_link_libraries(xenia-shader-cc PRIVATE glslang SPIRV-Tools-static)
|
||||
target_include_directories(xenia-shader-cc PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/glslang/StandAlone)
|
||||
if(MSVC)
|
||||
target_compile_options(xenia-shader-cc PRIVATE /EHsc)
|
||||
endif()
|
||||
|
||||
# ==============================================================================
|
||||
# imgui
|
||||
# ==============================================================================
|
||||
|
||||
+1
Submodule third_party/SPIRV-Headers added at 01e0577914
+1
Submodule third_party/SPIRV-Tools added at 7f2d9ee926
@@ -1,171 +0,0 @@
|
||||
#!/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())
|
||||
@@ -0,0 +1,266 @@
|
||||
// Build-time shader compiler. Consumes a GLSL/XeSL source file and emits a C
|
||||
// header with the compiled SPIR-V as `const uint32_t <id>[]`.
|
||||
//
|
||||
// Usage: xenia-shader-cc <input> <output.h>
|
||||
//
|
||||
// Stage (vs/hs/ds/gs/ps/cs) is parsed from the filename stem, e.g. foo.cs.glsl
|
||||
// -> compute. XeSL sources are compiled with the XeSL wrapper (#version 460 +
|
||||
// Google include directive + #include of the file itself) and with includes
|
||||
// resolved relative to the input file's directory.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "DirStackFileIncluder.h"
|
||||
#include "SPIRV/GlslangToSpv.h"
|
||||
#include "glslang/Public/ResourceLimits.h"
|
||||
#include "glslang/Public/ShaderLang.h"
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
#include "spirv-tools/optimizer.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
struct StageInfo {
|
||||
const char* key;
|
||||
EShLanguage language;
|
||||
};
|
||||
|
||||
constexpr StageInfo kStages[] = {
|
||||
{"vs", EShLangVertex}, {"hs", EShLangTessControl},
|
||||
{"ds", EShLangTessEvaluation}, {"gs", EShLangGeometry},
|
||||
{"ps", EShLangFragment}, {"cs", EShLangCompute},
|
||||
};
|
||||
|
||||
constexpr const char* kXeslWrapperPrefix =
|
||||
"#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";
|
||||
|
||||
bool ParseStage(const std::string& filename, EShLanguage* out,
|
||||
std::string* stage_key_out) {
|
||||
// Strip extension, then take last 2 chars after the final dot.
|
||||
auto dot = filename.rfind('.');
|
||||
if (dot == std::string::npos || dot < 3 || filename[dot - 3] != '.') {
|
||||
return false;
|
||||
}
|
||||
std::string key = filename.substr(dot - 2, 2);
|
||||
for (const auto& stage : kStages) {
|
||||
if (key == stage.key) {
|
||||
*out = stage.language;
|
||||
*stage_key_out = key;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string IdentifierFromFilename(const std::string& filename) {
|
||||
// foo.bar.cs.glsl -> foo_bar_cs
|
||||
auto last_dot = filename.rfind('.');
|
||||
std::string stem = filename.substr(0, last_dot);
|
||||
std::replace(stem.begin(), stem.end(), '.', '_');
|
||||
return stem;
|
||||
}
|
||||
|
||||
bool ReadFile(const std::filesystem::path& path, std::string* out) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
if (!in) {
|
||||
std::fprintf(stderr, "failed to open %s\n", path.string().c_str());
|
||||
return false;
|
||||
}
|
||||
std::ostringstream buf;
|
||||
buf << in.rdbuf();
|
||||
*out = buf.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string DisassembleSpirv(const std::vector<uint32_t>& spirv) {
|
||||
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
|
||||
std::string text;
|
||||
const uint32_t options = SPV_BINARY_TO_TEXT_OPTION_INDENT |
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES;
|
||||
if (!tools.Disassemble(spirv, &text, options)) {
|
||||
text.clear();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
bool WriteSpirvHeader(const std::filesystem::path& path,
|
||||
const std::string& identifier,
|
||||
const std::vector<uint32_t>& spirv) {
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(path.parent_path(), ec);
|
||||
std::ofstream out(path, std::ios::binary);
|
||||
if (!out) {
|
||||
std::fprintf(stderr, "failed to open output %s\n", path.string().c_str());
|
||||
return false;
|
||||
}
|
||||
out << "// Generated by xenia-shader-cc. Do not edit.\n#if 0\n";
|
||||
std::string disasm = DisassembleSpirv(spirv);
|
||||
if (!disasm.empty()) {
|
||||
out << disasm;
|
||||
if (disasm.back() != '\n') {
|
||||
out << '\n';
|
||||
}
|
||||
}
|
||||
out << "#endif\n\n";
|
||||
out << "const uint32_t " << identifier << "[] = {";
|
||||
for (size_t i = 0; i < spirv.size(); ++i) {
|
||||
out << (i % 6 == 0 ? "\n " : " ");
|
||||
char word[16];
|
||||
std::snprintf(word, sizeof(word), "0x%08X,", spirv[i]);
|
||||
out << word;
|
||||
}
|
||||
out << "\n};\n";
|
||||
return bool(out);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 3) {
|
||||
std::fprintf(stderr, "Usage: %s <input> <output.h>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::filesystem::path input_path = argv[1];
|
||||
std::filesystem::path output_path = argv[2];
|
||||
std::string input_filename = input_path.filename().string();
|
||||
|
||||
EShLanguage stage;
|
||||
std::string stage_key;
|
||||
if (!ParseStage(input_filename, &stage, &stage_key)) {
|
||||
std::fprintf(stderr,
|
||||
"cannot determine shader stage from filename: %s\n",
|
||||
input_filename.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string source;
|
||||
if (!ReadFile(input_path, &source)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// .xesl files rely on a wrapper prepending #version / extensions and then
|
||||
// #include'ing the file itself. glslang needs the include directive to come
|
||||
// from an actual source string, not from disk, so we synthesize the wrapper
|
||||
// and let the includer fetch the real file.
|
||||
const bool is_xesl = input_filename.size() > 5 &&
|
||||
input_filename.compare(input_filename.size() - 5, 5,
|
||||
".xesl") == 0;
|
||||
std::string wrapper;
|
||||
std::string wrapper_name;
|
||||
if (is_xesl) {
|
||||
wrapper = kXeslWrapperPrefix;
|
||||
wrapper += "#include \"";
|
||||
wrapper += input_filename;
|
||||
wrapper += "\"\n";
|
||||
}
|
||||
|
||||
glslang::InitializeProcess();
|
||||
|
||||
glslang::TShader shader(stage);
|
||||
const char* source_strings[1];
|
||||
const char* source_names[1];
|
||||
int source_lengths[1];
|
||||
if (is_xesl) {
|
||||
source_strings[0] = wrapper.c_str();
|
||||
source_lengths[0] = static_cast<int>(wrapper.size());
|
||||
wrapper_name = "xesl_wrapper";
|
||||
source_names[0] = wrapper_name.c_str();
|
||||
} else {
|
||||
source_strings[0] = source.c_str();
|
||||
source_lengths[0] = static_cast<int>(source.size());
|
||||
source_names[0] = input_filename.c_str();
|
||||
}
|
||||
shader.setStringsWithLengthsAndNames(source_strings, source_lengths,
|
||||
source_names, 1);
|
||||
shader.setPreamble("#define SHADING_LANGUAGE_GLSL_XE 1\n");
|
||||
// Match the old `glslangValidator -V` default: Vulkan 1.0 / SPV 1.0, which
|
||||
// stays compatible with the Vulkan 1.0 devices the runtime still supports.
|
||||
shader.setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan,
|
||||
100);
|
||||
shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_0);
|
||||
shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0);
|
||||
|
||||
DirStackFileIncluder includer;
|
||||
std::string input_dir = input_path.parent_path().string();
|
||||
if (!input_dir.empty()) {
|
||||
includer.pushExternalLocalDirectory(input_dir);
|
||||
}
|
||||
|
||||
const int default_version = 460;
|
||||
const EShMessages messages = static_cast<EShMessages>(
|
||||
EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
|
||||
|
||||
if (!shader.parse(GetDefaultResources(), default_version, false, messages,
|
||||
includer)) {
|
||||
std::fprintf(stderr, "glslang parse failed for %s:\n%s%s",
|
||||
input_path.string().c_str(), shader.getInfoLog(),
|
||||
shader.getInfoDebugLog());
|
||||
glslang::FinalizeProcess();
|
||||
return 1;
|
||||
}
|
||||
|
||||
glslang::TProgram program;
|
||||
program.addShader(&shader);
|
||||
if (!program.link(messages)) {
|
||||
std::fprintf(stderr, "glslang link failed for %s:\n%s%s",
|
||||
input_path.string().c_str(), program.getInfoLog(),
|
||||
program.getInfoDebugLog());
|
||||
glslang::FinalizeProcess();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Emit raw SPIR-V from glslang; optimization is done explicitly below to
|
||||
// match the previous `spirv-opt -O -O --canonicalize-ids` behavior.
|
||||
glslang::SpvOptions spv_options;
|
||||
spv_options.generateDebugInfo = false;
|
||||
spv_options.stripDebugInfo = true;
|
||||
spv_options.disableOptimizer = true;
|
||||
spv_options.validate = false;
|
||||
|
||||
std::vector<uint32_t> spirv;
|
||||
glslang::GlslangToSpv(*program.getIntermediate(stage), spirv, &spv_options);
|
||||
|
||||
glslang::FinalizeProcess();
|
||||
|
||||
if (spirv.empty()) {
|
||||
std::fprintf(stderr, "GlslangToSpv produced empty output for %s\n",
|
||||
input_path.string().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_2);
|
||||
optimizer.SetMessageConsumer(
|
||||
[](spv_message_level_t, const char*, const spv_position_t&,
|
||||
const char* message) { std::fprintf(stderr, "%s\n", message); });
|
||||
optimizer.RegisterPerformancePasses();
|
||||
optimizer.RegisterPerformancePasses();
|
||||
optimizer.RegisterPass(spvtools::CreateCanonicalizeIdsPass());
|
||||
std::vector<uint32_t> optimized;
|
||||
if (!optimizer.Run(spirv.data(), spirv.size(), &optimized)) {
|
||||
std::fprintf(stderr, "spirv-opt failed for %s\n",
|
||||
input_path.string().c_str());
|
||||
return 1;
|
||||
}
|
||||
spirv = std::move(optimized);
|
||||
}
|
||||
|
||||
std::string identifier = IdentifierFromFilename(input_filename);
|
||||
if (!WriteSpirvHeader(output_path, identifier, spirv)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -82,7 +82,6 @@ def import_subprocess_environment(args):
|
||||
"WindowsSdkDir",
|
||||
"PROGRAMFILES",
|
||||
"ProgramFiles(x86)",
|
||||
"VULKAN_SDK"
|
||||
"CC",
|
||||
"CXX",
|
||||
)
|
||||
@@ -155,62 +154,6 @@ vs_version = import_vs_environment()
|
||||
|
||||
default_branch = "canary_experimental"
|
||||
|
||||
def setup_vulkan_sdk():
|
||||
"""Setup Vulkan SDK environment variables if not already set.
|
||||
|
||||
Returns:
|
||||
True if Vulkan SDK is available and valid, False otherwise.
|
||||
"""
|
||||
# Check if VULKAN_SDK is already set and valid
|
||||
existing_vulkan_sdk = os.environ.get("VULKAN_SDK")
|
||||
if existing_vulkan_sdk:
|
||||
# Validate that the path exists
|
||||
if os.path.exists(existing_vulkan_sdk):
|
||||
# Check if spirv-opt is accessible
|
||||
if has_bin("spirv-opt"):
|
||||
print(f"VULKAN_SDK is set to {existing_vulkan_sdk}")
|
||||
return True
|
||||
print_warning(f"VULKAN_SDK is set to {existing_vulkan_sdk} but spirv-opt not found in PATH")
|
||||
else:
|
||||
print_warning(f"VULKAN_SDK is set to {existing_vulkan_sdk} but directory does not exist")
|
||||
return False
|
||||
|
||||
if sys.platform != "win32":
|
||||
# On Linux, find spirv-opt in PATH and set VULKAN_SDK based on its location
|
||||
spirv_opt_path = get_bin("spirv-opt")
|
||||
if spirv_opt_path:
|
||||
# spirv-opt is typically in $VULKAN_SDK/bin/, so get parent directory
|
||||
spirv_bin_dir = os.path.dirname(spirv_opt_path)
|
||||
vulkan_sdk = os.path.dirname(spirv_bin_dir)
|
||||
os.environ["VULKAN_SDK"] = vulkan_sdk
|
||||
print(f"Found Vulkan SDK at {vulkan_sdk} (from spirv-opt location)")
|
||||
return True
|
||||
return False
|
||||
|
||||
# Windows: Check if Vulkan SDK is installed at the default location
|
||||
vulkan_base = "C:\\VulkanSDK"
|
||||
if not os.path.exists(vulkan_base):
|
||||
return False
|
||||
|
||||
# Get the first (latest) version directory
|
||||
try:
|
||||
subdirs = [d for d in os.listdir(vulkan_base)
|
||||
if os.path.isdir(os.path.join(vulkan_base, d))]
|
||||
if not subdirs:
|
||||
return False
|
||||
|
||||
vulkan_sdk = os.path.join(vulkan_base, subdirs[0])
|
||||
vulkan_bin = os.path.join(vulkan_sdk, "Bin")
|
||||
|
||||
os.environ["VULKAN_SDK"] = vulkan_sdk
|
||||
os.environ["PATH"] = f"{vulkan_bin}{os.pathsep}{os.environ['PATH']}"
|
||||
|
||||
print(f"Found Vulkan SDK at {vulkan_sdk}")
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def setup_qt(target_arch=None):
|
||||
"""Setup Qt environment variables if not already set.
|
||||
|
||||
@@ -337,9 +280,6 @@ def main():
|
||||
# Add self to the root search path.
|
||||
sys.path.insert(0, self_path)
|
||||
|
||||
# Setup Vulkan SDK and check if available
|
||||
vulkan_sdk_available = setup_vulkan_sdk()
|
||||
|
||||
# Augment path to include our fancy things.
|
||||
os.environ["PATH"] += os.pathsep + os.pathsep.join([
|
||||
self_path,
|
||||
@@ -1077,13 +1017,6 @@ class BaseBuildCommand(Command):
|
||||
f"\nSee: https://github.com/has207/xenia-edge/blob/{default_branch}/docs/building.md")
|
||||
return 1
|
||||
|
||||
if sys.platform != "darwin" and not os.environ.get("VULKAN_SDK"):
|
||||
print_error("Vulkan SDK not found!"
|
||||
"\nPlease install Vulkan SDK from:"
|
||||
"\nhttps://sdk.lunarg.com/sdk/download/latest/windows/vulkan-sdk.exe"
|
||||
f"\nSee: https://github.com/has207/xenia-edge/blob/{default_branch}/docs/building.md")
|
||||
return 1
|
||||
|
||||
target_arch = args.get("target_arch")
|
||||
if not args["no_configure"]:
|
||||
print("- running cmake configure...")
|
||||
|
||||
Reference in New Issue
Block a user