GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
72 / 79
91.1%
Branches:
71 / 140
50.7%
Functions:
9 / 9
100.0%

device/shader_compiler.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file shader_compiler.cpp
3 /// @brief Compiling through Slang's API: a session per compile, modules loaded from source.
4 #include "ernie/device/shader_compiler.h"
5
6 #include "ernie/util/profiler.h"
7
8 #include <slang-com-ptr.h>
9 #include <slang.h>
10
11 #include <array>
12 #include <cstring>
13 #include <mutex>
14 #include <stdexcept>
15
16 namespace ernie {
17 namespace {
18
19 4 std::string Text(slang::IBlob* blob) {
20
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
8 return blob != nullptr ? std::string(static_cast<const char*>(blob->getBufferPointer()),
21 4 blob->getBufferSize())
22 8 : std::string();
23 }
24
25 } // namespace
26
27 48 struct ShaderCompiler::Session {
28 Slang::ComPtr<slang::IGlobalSession> global;
29 std::mutex mutex;
30
31 /// @return The entry point compiled for the target, as Slang's output blob.
32 106 Slang::ComPtr<slang::IBlob> Compile(std::span<const ShaderModule> modules,
33 std::string_view entryPoint, SlangCompileTarget format,
34 const char* profile) {
35
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 106 times.
106 if (modules.empty()) {
36
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::invalid_argument("Compiling no shader modules");
37 }
38 106 const std::lock_guard lock(mutex);
39
1/2
✓ Branch 1 taken 106 times.
✗ Branch 2 not taken.
106 const Slang::ComPtr<slang::ISession> session = CreateSession(format, profile);
40
2/2
✓ Branch 1 taken 105 times.
✓ Branch 2 taken 1 time.
106 slang::IModule* last = LoadModules(*session, modules);
41
5/8
✓ Branch 1 taken 105 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 104 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
211 return Link(*session, *last, modules.back().name, std::string(entryPoint));
42 106 }
43
44 private:
45 /// @return A session compiling for the target, the global session started the first time.
46 106 Slang::ComPtr<slang::ISession> CreateSession(SlangCompileTarget format, const char* profile) {
47
3/4
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 58 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 48 times.
106 if (!global && SLANG_FAILED(slang::createGlobalSession(global.writeRef()))) {
48
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("Cannot start the Slang shader compiler");
49 }
50 106 const slang::TargetDesc target{.format = format, .profile = global->findProfile(profile)};
51 // Matrices column-major, as glm lays them out, so host structs match without transposes.
52 106 std::array<slang::CompilerOptionEntry, 1> options{};
53 106 options[0].name = slang::CompilerOptionName::MatrixLayoutColumn;
54 106 options[0].value.intValue0 = 1;
55 // WebGPU has no buffer pointers: ref.slang reads buffers through bound arenas there.
56 106 const slang::PreprocessorMacroDesc webGpu{.name = "ERNIE_WEBGPU", .value = "1"};
57 106 const bool wgsl = format == SLANG_WGSL;
58 106 const slang::SessionDesc description{.targets = &target,
59 .targetCount = 1,
60 106 .preprocessorMacros = wgsl ? &webGpu : nullptr,
61 106 .preprocessorMacroCount = wgsl ? 1 : 0,
62 106 .compilerOptionEntries = options.data(),
63 .compilerOptionEntryCount =
64
5/6
✓ Branch 0 taken 103 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 103 times.
✓ Branch 3 taken 3 times.
✓ Branch 5 taken 106 times.
✗ Branch 6 not taken.
312 static_cast<std::uint32_t>(options.size())};
65
1/2
✓ Branch 1 taken 106 times.
✗ Branch 2 not taken.
106 Slang::ComPtr<slang::ISession> session;
66
2/4
✓ Branch 1 taken 106 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 106 times.
106 if (SLANG_FAILED(global->createSession(description, session.writeRef()))) {
67
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("Cannot create a Slang session");
68 }
69 106 return session;
70 ✗ }
71
72 /// @return The last module, each loaded after the modules it imports.
73 106 static slang::IModule* LoadModules(slang::ISession& session,
74 std::span<const ShaderModule> modules) {
75 106 slang::IModule* last = nullptr;
76
2/2
✓ Branch 0 taken 407 times.
✓ Branch 1 taken 105 times.
512 for (const ShaderModule& module : modules) {
77
1/2
✓ Branch 1 taken 407 times.
✗ Branch 2 not taken.
407 Slang::ComPtr<slang::IBlob> diagnostics;
78
1/2
✓ Branch 1 taken 407 times.
✗ Branch 2 not taken.
407 const std::string path = module.name + ".slang";
79
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 407 times.
✓ Branch 3 taken 407 times.
✗ Branch 4 not taken.
407 last = session.loadModuleFromSourceString(
80 module.name.c_str(), path.c_str(), module.source.c_str(), diagnostics.writeRef());
81
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 406 times.
407 if (last == nullptr) {
82
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
2 throw std::invalid_argument("Shader module " + module.name +
83
3/6
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
4 " failed to compile:\n" + Text(diagnostics));
84 }
85
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 380 times.
433 }
86 105 return last;
87 }
88
89 /// @return The code of `module`'s entry point, linked with what it imports.
90 105 static Slang::ComPtr<slang::IBlob> Link(slang::ISession& session, slang::IModule& module,
91 const std::string& name, const std::string& entry) {
92
1/2
✓ Branch 1 taken 105 times.
✗ Branch 2 not taken.
105 Slang::ComPtr<slang::IEntryPoint> found;
93
3/4
✓ Branch 1 taken 105 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 104 times.
105 if (SLANG_FAILED(module.findEntryPointByName(entry.c_str(), found.writeRef()))) {
94
3/6
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
3 throw std::invalid_argument("Shader module " + name + " has no entry point " + entry);
95 }
96
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 std::array<slang::IComponentType*, 2> parts{&module, found};
97
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 Slang::ComPtr<slang::IComponentType> program;
98 104 Slang::ComPtr<slang::IComponentType> linked;
99 104 Slang::ComPtr<slang::IBlob> code;
100 104 Slang::ComPtr<slang::IBlob> diagnostics;
101 104 const bool failed =
102
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 SLANG_FAILED(session.createCompositeComponentType(
103
1/2
✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
104 parts.data(), parts.size(), program.writeRef(), diagnostics.writeRef())) ||
104
5/10
✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 104 times.
✓ Branch 7 taken 104 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 104 times.
208 SLANG_FAILED(program->link(linked.writeRef(), diagnostics.writeRef())) ||
105
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 104 times.
✓ Branch 5 taken 104 times.
✗ Branch 6 not taken.
104 SLANG_FAILED(linked->getEntryPointCode(0, 0, code.writeRef(), diagnostics.writeRef()));
106 104 if (failed) {
107
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ throw std::invalid_argument("Entry point " + entry + " of " + name +
108
0/6
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ " failed to compile:\n" + Text(diagnostics));
109 }
110
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 102 times.
104 return code;
111
3/6
✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 104 times.
✗ Branch 5 not taken.
416 }
112 };
113
114 48 ShaderCompiler::ShaderCompiler() : session_(std::make_unique<Session>()) {}
115
116 48 ShaderCompiler::~ShaderCompiler() = default;
117
118 103 std::vector<std::uint32_t> ShaderCompiler::Spirv(std::span<const ShaderModule> modules,
119 std::string_view entryPoint) const {
120 103 ERNIE_ZONE("Compile Slang to SPIR-V");
121 103 const Slang::ComPtr<slang::IBlob> code =
122
2/2
✓ Branch 1 taken 101 times.
✓ Branch 2 taken 2 times.
103 session_->Compile(modules, entryPoint, SLANG_SPIRV, "spirv_1_6");
123 101 constexpr std::size_t kWord = sizeof(std::uint32_t);
124
2/4
✓ Branch 1 taken 101 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 101 times.
101 if (code->getBufferSize() % kWord != 0) {
125
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error("Slang's SPIR-V is not whole 32-bit words");
126 }
127
2/6
✓ Branch 1 taken 101 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 101 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
101 std::vector<std::uint32_t> words(code->getBufferSize() / kWord);
128
3/6
✓ Branch 1 taken 101 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 101 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 101 times.
✗ Branch 7 not taken.
101 std::memcpy(words.data(), code->getBufferPointer(), code->getBufferSize());
129
1/2
✓ Branch 0 taken 101 times.
✗ Branch 1 not taken.
202 return words;
130 103 }
131
132 3 std::string ShaderCompiler::Wgsl(std::span<const ShaderModule> modules,
133 std::string_view entryPoint) const {
134 3 ERNIE_ZONE("Compile Slang to WGSL");
135
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
6 return Text(session_->Compile(modules, entryPoint, SLANG_WGSL, "wgsl").get());
136 3 }
137
138 } // namespace ernie
139