[Build] Replace VULKAN_SDK toolchain with glslang / SPIRV-Tools

This commit is contained in:
Herman S.
2026-04-22 16:17:56 +09:00
parent 3d02eae07d
commit f0a2f1ffc5
10 changed files with 382 additions and 327 deletions
+1 -68
View File
@@ -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) {
+8
View File
@@ -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
+8 -9
View File
@@ -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
)
-11
View File
@@ -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)
+97 -1
View File
@@ -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
# ==============================================================================
Vendored Submodule
+1
Vendored Submodule
+1
Submodule third_party/SPIRV-Tools added at 7f2d9ee926
-171
View File
@@ -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())
+266
View File
@@ -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;
}
-67
View File
@@ -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...")