fix(rt): split Sponza's combined image sampler into a heap texture and sampler
examples/Sponza no longer built natively (#164). Its closest-hit declared a descriptor_heap sampler2D, which glslang has rejected since c8253623 ("Reject descriptor_heap combined image samplers"), and main.cpp wrote it through VkResourceDescriptorDataEXT::pCombinedImageSampler, which that union no longer has: the descriptor heap has no combined image samplers. Both went unnoticed because nothing builds the examples. Sponza - closesthit.glsl declares texture2D and sampler heap arrays and joins them where it samples, as ui-shared.glsl and HeapDynamicIndexHit do. The sampler slot is a second spec constant beside the image slot, so the shader assumes nothing about where the host put it, and a constant index keeps the example running on NVIDIA 610.43.02 (see its README). The comment no longer says the image slot is offset by bufferStartElement; only buffer slots are. - main.cpp gives the heap a sampler slot, writes the albedo as a SAMPLED_IMAGE from a view create-info, and writes the sampler into the sampler heap from a VkSamplerCreateInfo with vkWriteSamplerDescriptorsEXT, as UIRenderer::RegisterSampler does. SamplerVulkan's VkSampler had no use left. The sampler keeps its linear filtering and repeat addressing and drops anisotropy, which a hit shader cannot use: it samples at LOD 0, without derivatives. Docs - README.md and examples/Sponza/README.md quote the new declarations, and the Sponza README says why the texture and the sampler are two descriptors. Its texture2DArray materials pattern declared a descriptor_heap sampler2DArray, which glslang rejects as well; it now joins a texture2DArray and a sampler. - examples/SponzaBench/README.md drops its caveat that Sponza does not compile. Tests - ExampleShaders compiles every GLSL shader under examples/ with glslang as crafter-build does for the example (Vulkan rules, SPIR-V 1.4, shaders/ on the include path, and the descriptor-set build where the example registers one), so a glslang that starts rejecting one fails a test rather than an example build nobody runs. It fails on Sponza's old closest-hit and needs glslang only. Closes #164. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c97a3048fb
commit
6e94cc6209
7 changed files with 230 additions and 40 deletions
125
tests/ExampleShaders/main.cpp
Normal file
125
tests/ExampleShaders/main.cpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
//SPDX-License-Identifier: LGPL-3.0-only
|
||||
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// Every GLSL shader the examples ship still compiles. The examples are
|
||||
// projects of their own that nothing in this one builds, and crafter-build
|
||||
// compiles their shaders with whichever glslang it links, unpinned. When
|
||||
// glslang began rejecting a descriptor_heap combined image sampler (upstream
|
||||
// c8253623), examples/Sponza stopped building and nothing noticed until it
|
||||
// was built by hand (#164). This test compiles each examples/<Name>/*.glsl as
|
||||
// crafter-build does (Vulkan rules, SPIR-V 1.4, shaders/ on the include
|
||||
// path), with the stage taken from the file name, and a shader that includes
|
||||
// ui-shared.glsl a second time for the descriptor-set build its example also
|
||||
// registers. A file whose name gives no stage fails rather than being
|
||||
// skipped, so a new shader is never silently left out.
|
||||
//
|
||||
// Needs glslang on the PATH (a required tool in project.cpp), which need not
|
||||
// be the glslang crafter-build links, and the source tree, which it finds
|
||||
// from the build output directory. No GPU.
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
import std;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
int failures = 0;
|
||||
|
||||
void Check(bool ok, std::string_view what) {
|
||||
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||||
if (!ok) ++failures;
|
||||
}
|
||||
|
||||
int RunCommand(const std::string& cmd) {
|
||||
int status = std::system(cmd.c_str());
|
||||
if (status == -1) return -1;
|
||||
return (status & 0x7f) == 0 ? ((status >> 8) & 0xff) : 128 + (status & 0x7f);
|
||||
}
|
||||
|
||||
std::string ReadFile(const fs::path& path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
return {std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>()};
|
||||
}
|
||||
|
||||
// glslang's -S name for the stage, from the file names the examples use:
|
||||
// raygen.glsl, closesthit.glsl, ..., <name>.comp.glsl. Empty when unknown.
|
||||
std::string_view StageOf(const fs::path& shader) {
|
||||
const std::string stem = shader.stem().string();
|
||||
if (stem.ends_with(".comp")) return "comp";
|
||||
constexpr std::pair<std::string_view, std::string_view> stages[] = {
|
||||
{"raygen", "rgen"}, {"intersection", "rint"}, {"anyhit", "rahit"},
|
||||
{"closesthit", "rchit"}, {"miss", "rmiss"},
|
||||
};
|
||||
for (const auto& [name, stage] : stages) {
|
||||
if (stem == name) return stage;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// The directory holding examples/ and shaders/, whether the test runs from
|
||||
// its build output directory or the project root, as UIFusedShader finds its
|
||||
// shader.
|
||||
fs::path LocateRoot() {
|
||||
for (const fs::path candidate : {".", "..", "../..", "../../.."}) {
|
||||
std::error_code ec;
|
||||
if (fs::is_directory(candidate / "examples", ec)
|
||||
&& fs::is_regular_file(candidate / "shaders" / "ui-shared.glsl", ec)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const fs::path root = LocateRoot();
|
||||
if (root.empty()) {
|
||||
std::println(std::cerr, "could not find examples/ and shaders/ from {}", fs::current_path().string());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const fs::path out = fs::temp_directory_path() / "crafter-example-shaders-test";
|
||||
std::error_code ec;
|
||||
fs::create_directories(out, ec);
|
||||
|
||||
// Each example's own directory only: its bin/ and build/ hold outputs.
|
||||
std::vector<fs::path> shaders;
|
||||
for (const fs::directory_entry& example : fs::directory_iterator(root / "examples")) {
|
||||
if (!example.is_directory()) continue;
|
||||
for (const fs::directory_entry& file : fs::directory_iterator(example.path())) {
|
||||
if (file.is_regular_file() && file.path().extension() == ".glsl") shaders.push_back(file.path());
|
||||
}
|
||||
}
|
||||
std::ranges::sort(shaders);
|
||||
Check(!shaders.empty(), std::format("found the examples' shaders ({})", shaders.size()));
|
||||
|
||||
for (const fs::path& shader : shaders) {
|
||||
const std::string name = (shader.parent_path().filename() / shader.filename()).generic_string();
|
||||
const std::string_view stage = StageOf(shader);
|
||||
if (stage.empty()) {
|
||||
Check(false, std::format("{}: no stage known for this file name; name it after its stage or teach StageOf", name));
|
||||
continue;
|
||||
}
|
||||
// A shader on ui-shared.glsl's UI_HEAP_* macros is registered twice
|
||||
// by its example, the second build for drivers without the heap.
|
||||
const bool setsBuild = ReadFile(shader).contains("ui-shared.glsl");
|
||||
for (const bool sets : {false, true}) {
|
||||
if (sets && !setsBuild) continue;
|
||||
const std::string base = std::format("{}-{}{}", shader.parent_path().filename().string(),
|
||||
shader.stem().string(), sets ? ".sets" : "");
|
||||
const fs::path log = out / (base + ".log");
|
||||
const std::string compile = std::format(
|
||||
"glslang -V --target-env spirv1.4 -S {} {}-I\"{}\" -o \"{}\" \"{}\" > \"{}\" 2>&1",
|
||||
stage, sets ? "-DCRAFTER_GRAPHICS_DESCRIPTOR_SETS " : "", (root / "shaders").string(),
|
||||
(out / (base + ".spv")).string(), shader.string(), log.string());
|
||||
const bool compiled = RunCommand(compile) == 0;
|
||||
if (!compiled) std::print("{}", ReadFile(log));
|
||||
Check(compiled, std::format("{} compiles{}", name, sets ? " (descriptor-set build)" : ""));
|
||||
}
|
||||
}
|
||||
|
||||
std::println("{}", failures == 0 ? "all checks passed" : "check(s) failed");
|
||||
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
Loading…
Reference in a new issue