chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
285 / 327
87.2%
Branches:
255 / 308
82.8%
Functions:
14 / 17
82.4%

render/shaders.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 #include "chrono_batch_render/render/shaders.h"
3
4 #include <algorithm>
5 #include <cstring>
6 #include <fstream>
7 #include <iterator>
8 #include <map>
9 #include <mutex>
10
11 #include "chrono_batch_render/device/slang/reflection.h"
12 #include "chrono_batch_render/material/codegen.h"
13 #include "chrono_batch_render/material/node.h"
14 #include "chrono_batch_render/render/kernels.h"
15 #include "chrono_batch_render/scene/scene_impl.h"
16 #include "chrono_batch_render/sensor_pipeline/node.h"
17 #include "chrono_batch_render/stage/schema/runtime.h"
18 #include "chrono_batch_render/util/disk_cache.h"
19 #include "chrono_batch_render/util/error.h"
20 #include "chrono_batch_render/util/log.h"
21 #include "chrono_batch_render/util/text.h"
22
23 namespace chrono::batch_render {
24
25 namespace {
26
27 56 template <class T> T Read(const std::vector<std::byte>& bytes) {
28 56 T value{};
29
4/12
chrono::batch_render::Float2 chrono::batch_render::(anonymous namespace)::Read<chrono::batch_render::Float2>(std::vector<std::byte, std::allocator<std::byte> > const&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
chrono::batch_render::Float3 chrono::batch_render::(anonymous namespace)::Read<chrono::batch_render::Float3>(std::vector<std::byte, std::allocator<std::byte> > const&):
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
chrono::batch_render::Float4 chrono::batch_render::(anonymous namespace)::Read<chrono::batch_render::Float4>(std::vector<std::byte, std::allocator<std::byte> > const&):
✗ Branch 0 not taken.
✗ Branch 1 not taken.
float chrono::batch_render::(anonymous namespace)::Read<float>(std::vector<std::byte, std::allocator<std::byte> > const&):
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
int chrono::batch_render::(anonymous namespace)::Read<int>(std::vector<std::byte, std::allocator<std::byte> > const&):
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
unsigned int chrono::batch_render::(anonymous namespace)::Read<unsigned int>(std::vector<std::byte, std::allocator<std::byte> > const&):
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
56 std::memcpy(&value, bytes.data(), std::min(bytes.size(), sizeof(T)));
30 56 return value;
31 }
32
33 48 RuntimeColumnDesc Column(const device::ReflectedField& field) {
34 48 RuntimeColumnDesc column{.field = field.name,
35
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 9 times.
48 .role = field.color ? ColumnRole::Color : ColumnRole::None,
36
1/1
✓ Branch 1 taken 48 times.
48 .displayName = field.displayName,
37 .group = "Inputs",
38 48 .minimum = field.minimum,
39
7/10
✓ Branch 2 taken 48 times.
✓ Branch 5 taken 48 times.
✓ Branch 7 taken 8 times.
✓ Branch 8 taken 4 times.
✓ Branch 9 taken 20 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 15 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 1 time.
✗ Branch 14 not taken.
96 .maximum = field.maximum};
40
5/8
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
48 switch (field.type) {
41 8 case device::FieldType::Bool:
42 8 column.type = ColumnType::Bool;
43 8 column.fallback = Read<uint32_t>(field.fallback) != 0;
44 8 break;
45 4 case device::FieldType::Int:
46 4 column.type = ColumnType::Int;
47 4 column.fallback = Read<int32_t>(field.fallback);
48 4 break;
49 20 case device::FieldType::Float:
50 20 column.type = ColumnType::Float;
51 20 column.fallback = Read<float>(field.fallback);
52 20 break;
53 ✗ case device::FieldType::Float2:
54 ✗ column.type = ColumnType::Float2;
55 ✗ column.fallback = Read<Float2>(field.fallback);
56 ✗ break;
57 15 case device::FieldType::Float3:
58 15 column.type = ColumnType::Float3;
59 15 column.fallback = Read<Float3>(field.fallback);
60 15 break;
61 ✗ case device::FieldType::Float4:
62 ✗ column.type = ColumnType::Float4;
63 ✗ column.fallback = Read<Float4>(field.fallback);
64 ✗ break;
65 1 case device::FieldType::Spectral:
66 1 column.type = ColumnType::Float3;
67 1 column.fallback = Read<Float3>(field.fallback);
68 1 break;
69 }
70 48 return column;
71 }
72
73 50 material::MaterialInputType InputType(device::FieldType type) {
74
4/7
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 32 times.
✓ Branch 6 taken 4 times.
50 switch (type) {
75 case device::FieldType::Bool:
76 return material::MaterialInputType::Bool;
77 ✗ case device::FieldType::Int:
78 ✗ return material::MaterialInputType::Int;
79 ✗ case device::FieldType::Float2:
80 ✗ return material::MaterialInputType::Float2;
81 13 case device::FieldType::Float3:
82 13 return material::MaterialInputType::Float3;
83 ✗ case device::FieldType::Float4:
84 ✗ return material::MaterialInputType::Float4;
85 1 case device::FieldType::Spectral:
86 1 return material::MaterialInputType::Spectral;
87 32 default:
88 32 return material::MaterialInputType::Float;
89 }
90 }
91
92 25 struct LoadedFile {
93 std::string digest;
94 std::vector<std::string> names;
95 };
96
97 3 struct LoadedFiles {
98 std::mutex mutex;
99 std::map<std::filesystem::path, LoadedFile> files;
100 };
101
102 118 LoadedFiles& Loaded() {
103
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 115 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
118 static LoadedFiles loaded;
104 118 return loaded;
105 }
106
107 85 std::string ReadSource(const std::filesystem::path& file, const std::filesystem::path& requested) {
108 85 std::ifstream stream(file, std::ios::binary);
109
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 83 times.
85 if (!stream) {
110
2/2
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
4 throw Error("Can't read shaders from {}", requested.string());
111 }
112
1/1
✓ Branch 1 taken 83 times.
166 return std::string((std::istreambuf_iterator<char>(stream)), {});
113 85 }
114
115 5 const bool kLoaderRegistered = [] {
116
1/1
✓ Branch 1 taken 5 times.
5 SetShaderLoader(
117 3 [](Scene& scene, const std::filesystem::path& file) { return LoadShaders(scene, file); });
118 5 return true;
119 }();
120
121 }
122
123 42 std::vector<std::string> LoadShaders(Scene& scene, const std::filesystem::path& requested) {
124 42 const auto sceneLock = SceneImpl::Get(scene).Lock();
125
1/1
✓ Branch 1 taken 42 times.
42 const std::filesystem::path file = std::filesystem::weakly_canonical(requested);
126
1/1
✓ Branch 1 taken 40 times.
42 const std::string source = ReadSource(file, requested);
127
1/1
✓ Branch 1 taken 40 times.
40 const std::string digest = Digest(source);
128 40 {
129 40 LoadedFiles& loaded = Loaded();
130
1/1
✓ Branch 1 taken 40 times.
40 const std::lock_guard lock(loaded.mutex);
131 40 if (const auto found = loaded.files.find(file);
132
4/4
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 7 times.
40 found != loaded.files.end() && found->second.digest == digest) {
133
1/1
✓ Branch 1 taken 2 times.
2 return found->second.names;
134 }
135 2 }
136
1/1
✓ Branch 1 taken 38 times.
38 const std::string module = "chrono_batch_render.nodes.n" + digest;
137
1/1
✓ Branch 1 taken 38 times.
38 const std::string path = ModulePath(module);
138
1/1
✓ Branch 1 taken 38 times.
38 const std::pair<std::string, std::string> origins[] = {{path, file.string()},
139
2/4
✓ Branch 1 taken 38 times.
✓ Branch 4 taken 38 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
304 {module + ".slang", file.string()}};
140 48 const auto mapped = [&](const Error& error) {
141
2/2
✓ Branch 3 taken 10 times.
✓ Branch 6 taken 10 times.
20 return Error("{}", MapOrigins(error.what(), origins));
142 38 };
143
1/1
✓ Branch 1 taken 38 times.
38 const device::Backend backend = scene.GetDevice().Adapter().backend;
144
2/2
✓ Branch 1 taken 38 times.
✓ Branch 4 taken 38 times.
51 auto sources = material::ClosurePrimitiveSources(material::ClosurePrimitives());
145
2/2
✓ Branch 1 taken 38 times.
✓ Branch 4 taken 38 times.
38 sources[path] = source;
146 38 device::ReflectedNodes reflection;
147 38 try {
148
3/3
✓ Branch 1 taken 38 times.
✓ Branch 4 taken 38 times.
✓ Branch 7 taken 38 times.
114 const DiskCache binaries(scene.GetDevice().KernelCacheDirectory() / "slang");
149
1/1
✓ Branch 1 taken 38 times.
38 reflection =
150
2/2
✓ Branch 1 taken 38 times.
✓ Branch 4 taken 29 times.
76 device::ReflectNodes(backend, &kRenderKernels, {sources.begin(), sources.end()}, module,
151
1/1
✓ Branch 1 taken 38 times.
105 file.string(), &binaries);
152
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 } catch (const Error& error) {
153
1/1
✓ Branch 2 taken 9 times.
9 throw mapped(error);
154 9 }
155
4/4
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 1 time.
29 if (!reflection.pipeline.empty() && !reflection.material.empty()) {
156 1 throw Error("{} defines both pipeline nodes ({}) and material nodes ({}); put them in "
157 "separate files",
158
1/1
✓ Branch 1 taken 1 time.
1 file.string(), reflection.pipeline.front().name,
159
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
2 reflection.material.front().name);
160 }
161
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 20 times.
28 if (!reflection.pipeline.empty()) {
162 8 const std::vector<device::ReflectedSensorPipelineNode>& reflected = reflection.pipeline;
163 53 const auto signal = [&](const std::string& node, const std::string& type) {
164 45 const auto found = std::ranges::find(sensor_pipeline::kSignals, type,
165 &sensor_pipeline::SignalInfo::slangType);
166
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 44 times.
45 if (found == std::end(sensor_pipeline::kSignals)) {
167 1 std::vector<std::string> types;
168
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 time.
11 for (const sensor_pipeline::SignalInfo& info : sensor_pipeline::kSignals) {
169
1/1
✓ Branch 1 taken 10 times.
10 types.emplace_back(info.slangType);
170 }
171 1 throw Error("{} in {} uses {}, which isn't a pipeline signal; signals are {}", node,
172
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
2 file.string(), type, Join(types));
173 1 }
174 44 return sensor_pipeline::SignalType(found - std::begin(sensor_pipeline::kSignals));
175 8 };
176
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 7 times.
19 for (const device::ReflectedSensorPipelineNode& node : reflected) {
177
1/1
✓ Branch 1 taken 11 times.
12 signal(node.name, node.result);
178
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 11 times.
22 for (const device::ReflectedPort& port : node.inputs) {
179
1/1
✓ Branch 1 taken 11 times.
11 signal(node.name, port.type);
180 }
181 }
182 7 std::vector<std::string> names;
183
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 6 times.
17 for (const device::ReflectedSensorPipelineNode& node : reflected) {
184
1/1
✓ Branch 1 taken 11 times.
11 const sensor_pipeline::SignalType result = signal(node.name, node.result);
185 11 sensor_pipeline::NodeDef def{
186 .id = node.name,
187 11 .kind = node.kind == device::SensorPipelineNodeKind::Stencil
188
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 4 times.
11 ? sensor_pipeline::NodeKind::Stencil
189 : sensor_pipeline::NodeKind::Point,
190
1/1
✓ Branch 1 taken 11 times.
11 .outputs = {{std::string(sensor_pipeline::Signal(result).port), result}},
191 11 .radius = node.radius,
192 .code = {.module = module,
193 .type = node.name,
194 .file = file.string(),
195 .source = source,
196 .version = digest,
197 .applies = true,
198
8/10
✓ Branch 1 taken 11 times.
✓ Branch 4 taken 11 times.
✓ Branch 7 taken 11 times.
✓ Branch 10 taken 11 times.
✓ Branch 13 taken 11 times.
✓ Branch 16 taken 11 times.
✓ Branch 18 taken 11 times.
✓ Branch 19 taken 11 times.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
33 .context = node.context}};
199
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 11 times.
22 for (const device::ReflectedPort& port : node.inputs) {
200
1/1
✓ Branch 1 taken 11 times.
22 def.inputs.push_back({port.name, signal(node.name, port.type)});
201 }
202 11 RuntimeSchemaDesc desc{.name = node.name,
203 .base = SchemaId::SensorNode,
204
1/1
✓ Branch 1 taken 11 times.
22 .doc = "A sensor pipeline node from " + file.string() + ".",
205
5/7
✓ Branch 1 taken 11 times.
✓ Branch 4 taken 11 times.
✓ Branch 8 taken 11 times.
✓ Branch 9 taken 11 times.
✓ Branch 10 taken 11 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
33 .inherited = {{"id", node.name}}};
206 11 std::vector<std::string> inputs;
207
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 11 times.
29 for (const device::ReflectedField& field : node.fields) {
208
1/1
✓ Branch 1 taken 18 times.
18 RuntimeColumnDesc column = Column(field);
209
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2 times.
18 column.group = node.displayName.empty() ? node.name : node.displayName;
210
1/1
✓ Branch 1 taken 18 times.
18 desc.columns.push_back(std::move(column));
211
1/1
✓ Branch 1 taken 18 times.
18 sensor_pipeline::ParameterDef parameter{.name = field.name};
212
3/3
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 10 times.
18 switch (field.type) {
213 4 case device::FieldType::Bool:
214 4 parameter.type = sensor_pipeline::ParameterType::Bool;
215
2/3
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
8 parameter.fallback = {Read<uint32_t>(field.fallback) != 0 ? 1.0f : 0.0f};
216 4 break;
217 4 case device::FieldType::Int:
218 4 parameter.type = sensor_pipeline::ParameterType::Int;
219
1/1
✓ Branch 2 taken 4 times.
4 parameter.fallback = {float(Read<int32_t>(field.fallback))};
220 4 break;
221 10 default:
222
1/1
✓ Branch 1 taken 10 times.
10 parameter.words = uint32_t(field.fallback.size() / sizeof(float));
223
1/1
✓ Branch 1 taken 10 times.
10 parameter.fallback.resize(parameter.words);
224 10 std::memcpy(parameter.fallback.data(), field.fallback.data(),
225 field.fallback.size());
226 10 break;
227 }
228
1/1
✓ Branch 1 taken 18 times.
18 def.parameters.push_back(std::move(parameter));
229
1/1
✓ Branch 1 taken 18 times.
18 inputs.push_back(field.name);
230 18 }
231 11 const std::shared_ptr<const sensor_pipeline::NodeRegistry> known =
232
1/1
✓ Branch 1 taken 11 times.
12 sensor_pipeline::SensorPipelineNodes();
233
1/1
✓ Branch 1 taken 11 times.
11 const uint32_t found = known->Find(node.name);
234
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 7 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1 time.
11 if (found != sensor_pipeline::kNoDef && (*known)[found].code.file.empty()) {
235 1 throw Error("{} in {} has the name of a built-in pipeline node; rename it",
236
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
2 node.name, file.string());
237 }
238
1/1
✓ Branch 1 taken 10 times.
10 const SchemaId previous = FindSchemaByName(node.name);
239 10 const bool reloading = found != sensor_pipeline::kNoDef && previous != SchemaId::None;
240
1/1
✓ Branch 1 taken 10 times.
10 const SchemaId schema = RegisterSchema(desc, reloading);
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (reloading && previous != schema) {
242
0/1
✗ Branch 1 not taken.
✗ Log(LogLevel::Info,
243 "{} in {} changed its inputs, so its prims move to the new ones", node.name,
244
0/1
✗ Branch 1 not taken.
✗ file.string());
245
0/1
✗ Branch 1 not taken.
✗ RetypePrims(scene, previous, schema);
246 }
247
1/1
✓ Branch 2 taken 10 times.
10 sensor_pipeline::RegisterSensorPipelineNode(std::move(def));
248 20 Log(LogLevel::Debug, "loaded {} pipeline node {} from {} with inputs {}",
249
1/1
✓ Branch 1 taken 10 times.
10 node.kind == device::SensorPipelineNodeKind::Stencil ? "stencil" : "point",
250
7/8
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 4 times.
✓ Branch 6 taken 6 times.
✓ Branch 8 taken 6 times.
✓ Branch 9 taken 4 times.
✓ Branch 12 taken 1 time.
✗ Branch 13 not taken.
31 node.name, file.string(), inputs.empty() ? std::string("(none)") : Join(inputs));
251
1/1
✓ Branch 1 taken 10 times.
10 names.push_back(node.name);
252 13 }
253 6 LoadedFiles& loaded = Loaded();
254
1/1
✓ Branch 1 taken 6 times.
6 const std::lock_guard lock(loaded.mutex);
255
1/1
✓ Branch 1 taken 6 times.
6 loaded.files[file] = {digest, names};
256 6 return names;
257 7 }
258 40 std::vector<device::ReflectedNode> nodes = std::move(reflection.material);
259 19 std::vector<std::string> names;
260 44 const auto primitive = [](const device::ReflectedNode& node) {
261
3/4
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
24 return node.role == device::NodeRole::Primitive;
262 };
263
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 18 times.
20 if (std::ranges::any_of(nodes, primitive)) {
264
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
4 if (!std::ranges::all_of(nodes, primitive) ||
265 2 source.find("chrono_batch_render.material") != std::string::npos) {
266 ✗ throw Error("{} defines a closure primitive, so it may only import "
267 "chrono_batch_render.primitive "
268 "and define primitives",
269
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ file.string());
270 }
271
1/1
✓ Branch 1 taken 2 times.
2 material::ClosurePrimitiveList candidates = material::ClosurePrimitives();
272
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (const device::ReflectedNode& reflected : nodes) {
273 2 candidates =
274
1/1
✓ Branch 1 taken 2 times.
4 material::WithClosurePrimitive(std::move(candidates), {.name = reflected.name,
275 .file = file.string(),
276 .module = module,
277 2 .source = source});
278 }
279
1/1
✓ Branch 1 taken 2 times.
2 const auto checked = material::ClosurePrimitiveSources(candidates);
280 2 try {
281
3/3
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 2 times.
6 const DiskCache binaries(scene.GetDevice().KernelCacheDirectory() / "slang");
282
3/3
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 1 time.
3 device::ReflectMaterialNodes(backend, &kRenderKernels, {checked.begin(), checked.end()},
283 "chrono_batch_render.render.kernels.material.registered",
284
1/1
✓ Branch 1 taken 2 times.
4 file.string(), &binaries);
285
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 } catch (const Error& error) {
286
1/1
✓ Branch 2 taken 1 time.
1 throw mapped(error);
287 1 }
288
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (const device::ReflectedNode& reflected : nodes) {
289
1/1
✓ Branch 1 taken 1 time.
1 material::RegisterClosurePrimitive({.name = reflected.name,
290 .file = file.string(),
291 .module = module,
292 .source = source});
293
1/1
✓ Branch 1 taken 1 time.
1 const auto loaded = std::ranges::find(candidates, reflected.name,
294 1 [](const auto& entry) { return entry->name; });
295
1/1
✓ Branch 1 taken 1 time.
1 Log(LogLevel::Debug, "loaded closure primitive {} from {} as lobe kind {}",
296
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
2 reflected.name, file.string(), (*loaded)->kind);
297
1/1
✓ Branch 1 taken 1 time.
1 names.push_back(reflected.name);
298 }
299 1 nodes.clear();
300 2 }
301
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 19 times.
39 for (const device::ReflectedNode& reflected : nodes) {
302 20 RuntimeSchemaDesc desc{
303 20 .name = reflected.name,
304 .base = SchemaId::Shader,
305
1/1
✓ Branch 1 taken 20 times.
40 .doc = "A material node from " + file.string() + ".",
306 20 .inherited = {{"implementationSource", int32_t(ImplementationSource::SourceAsset)},
307 ✗ {"slangSourceAsset", file.string()},
308
6/8
✓ Branch 1 taken 20 times.
✓ Branch 4 taken 20 times.
✓ Branch 7 taken 20 times.
✓ Branch 11 taken 20 times.
✓ Branch 12 taken 60 times.
✓ Branch 13 taken 20 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
140 {"slangSourceAssetSubIdentifier", reflected.name}}};
309 20 material::MaterialNodeDefinition node{.name = reflected.name,
310 20 .role = reflected.role == device::NodeRole::Pattern
311
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 2 times.
20 ? material::MaterialNodeRole::Pattern
312 : reflected.role == device::NodeRole::Surface
313
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 3 times.
18 ? material::MaterialNodeRole::Surface
314 : material::MaterialNodeRole::Material,
315 .file = file.string(),
316 .module = module,
317 .source = source,
318
4/4
✓ Branch 1 taken 20 times.
✓ Branch 4 taken 20 times.
✓ Branch 7 taken 20 times.
✓ Branch 10 taken 20 times.
40 .result = InputType(reflected.result)};
319
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 20 times.
24 for (const device::ReflectedParameter& parameter : reflected.parameters) {
320 4 node.parameters.push_back(
321 4 {.source = parameter.source == device::ParameterSource::Context
322
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 ? material::MaterialNodeParameter::Source::Context
323 : parameter.source == device::ParameterSource::Point
324
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 ? material::MaterialNodeParameter::Source::Point
325 : material::MaterialNodeParameter::Source::Input,
326
1/1
✓ Branch 1 taken 4 times.
4 .input = parameter.input});
327 }
328 20 std::vector<std::string> inputs;
329
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 20 times.
50 for (const device::ReflectedField& field : reflected.fields) {
330
1/1
✓ Branch 1 taken 30 times.
60 desc.columns.push_back(Column(field));
331 30 node.inputs.push_back({.name = field.name, .type = InputType(field.type)});
332
1/1
✓ Branch 1 taken 30 times.
30 inputs.push_back(field.name);
333 }
334 20 const std::shared_ptr<const material::MaterialNodeDefinition> previous =
335
1/1
✓ Branch 1 taken 20 times.
20 material::FindMaterialNode(reflected.name);
336
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
26 const bool reloading = previous && previous->file == file.string();
337
1/1
✓ Branch 1 taken 20 times.
20 const SchemaId schema = RegisterSchema(desc, reloading);
338
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
20 if (reloading && SchemaId(previous->schema) != schema) {
339
1/1
✓ Branch 1 taken 2 times.
2 Log(LogLevel::Info, "{} in {} changed its inputs, so its prims move to the new ones",
340
1/1
✓ Branch 1 taken 2 times.
2 reflected.name, file.string());
341
1/1
✓ Branch 1 taken 2 times.
2 RetypePrims(scene, SchemaId(previous->schema), schema);
342 }
343 20 node.schema = uint32_t(schema);
344
1/1
✓ Branch 2 taken 20 times.
20 material::RegisterMaterialNode(std::move(node));
345 40 Log(LogLevel::Debug, "loaded {} {} from {} with inputs {}",
346
1/1
✓ Branch 1 taken 20 times.
20 reflected.role == device::NodeRole::Pattern ? "pattern"
347
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 3 times.
18 : reflected.role == device::NodeRole::Surface ? "surface"
348 : "material node",
349
6/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
✓ Branch 3 taken 2 times.
✓ Branch 6 taken 18 times.
✓ Branch 8 taken 18 times.
✓ Branch 9 taken 2 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
60 reflected.name, file.string(), inputs.empty() ? std::string("(none)") : Join(inputs));
350
1/1
✓ Branch 1 taken 20 times.
20 names.push_back(reflected.name);
351 20 }
352
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 18 times.
19 if (names.empty()) {
353
1/1
✓ Branch 1 taken 1 time.
1 Log(LogLevel::Warning,
354 "{} has no nodes; a material node is a struct with a "
355 "Material(inout SurfaceInputs, ShadeContext) method or a function marked [Pattern], "
356 "and a pipeline node is a struct conforming to IPointNode or IStencilNode",
357
1/1
✓ Branch 1 taken 1 time.
3 file.string());
358 }
359 19 {
360 19 LoadedFiles& loaded = Loaded();
361
1/1
✓ Branch 1 taken 19 times.
19 const std::lock_guard lock(loaded.mutex);
362
1/1
✓ Branch 1 taken 19 times.
38 loaded.files[file] = {digest, names};
363 ✗ }
364 19 return names;
365 419 }
366
367 10 std::vector<std::string> ReloadShaders(Scene& scene) {
368 10 const auto sceneLock = SceneImpl::Get(scene).Lock();
369 10 std::vector<std::string> reloaded;
370
3/3
✓ Branch 1 taken 10 times.
✓ Branch 3 taken 43 times.
✓ Branch 4 taken 10 times.
53 for (const std::filesystem::path& file : LoadedShaderFiles()) {
371
1/1
✓ Branch 1 taken 43 times.
43 std::string digest;
372 43 try {
373
2/2
✓ Branch 1 taken 43 times.
✓ Branch 4 taken 43 times.
43 digest = Digest(ReadSource(file, file));
374
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ } catch (const Error& error) {
375
0/1
✗ Branch 2 not taken.
✗ Log(LogLevel::Warning, "{}; its nodes keep their last version", error.what());
376 ✗ continue;
377 ✗ }
378 43 {
379 43 LoadedFiles& loaded = Loaded();
380
1/1
✓ Branch 1 taken 43 times.
43 const std::lock_guard lock(loaded.mutex);
381
3/3
✓ Branch 1 taken 43 times.
✓ Branch 3 taken 36 times.
✓ Branch 4 taken 7 times.
43 if (loaded.files.at(file).digest == digest) {
382 36 continue;
383 }
384 36 }
385 7 try {
386
1/1
✓ Branch 1 taken 5 times.
7 const std::vector<std::string> names = LoadShaders(scene, file);
387
1/1
✓ Branch 1 taken 5 times.
5 reloaded.insert(reloaded.end(), names.begin(), names.end());
388
3/3
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
✓ Branch 7 taken 5 times.
10 Log(LogLevel::Info, "reloaded {} from {}", Join(names), file.string());
389
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
7 } catch (const Error& error) {
390 4 Log(LogLevel::Error, "reloading {} failed, so its nodes keep their last version: {}",
391
2/2
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
2 file.string(), error.what());
392 2 }
393 53 }
394 10 return reloaded;
395 10 }
396
397 ✗ std::vector<SchemaId> SurfaceShaders() {
398 ✗ std::vector<SchemaId> surfaces = {SchemaId::UsdPreviewSurface, SchemaId::HapkeSurface,
399 SchemaId::LommelSeeligerSurface,
400 ✗ SchemaId::LunarLambertSurface, SchemaId::OrenNayarSurface};
401
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ for (const auto& node : material::MaterialNodes()) {
402
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (node->role != material::MaterialNodeRole::Pattern) {
403
0/1
✗ Branch 1 not taken.
✗ surfaces.push_back(SchemaId(node->schema));
404 }
405 ✗ }
406 ✗ return surfaces;
407 ✗ }
408
409 10 std::vector<std::filesystem::path> LoadedShaderFiles() {
410 10 LoadedFiles& loaded = Loaded();
411 10 const std::lock_guard lock(loaded.mutex);
412 10 std::vector<std::filesystem::path> files;
413
3/3
✓ Branch 1 taken 43 times.
✓ Branch 3 taken 43 times.
✓ Branch 4 taken 10 times.
53 for (const auto& [file, entry] : loaded.files) {
414
1/1
✓ Branch 1 taken 43 times.
43 files.push_back(file);
415 }
416 10 return files;
417 10 }
418
419 }
420