chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
102 / 107
95.3%
Branches:
103 / 128
80.5%
Functions:
8 / 8
100.0%

scene/tables/spectra.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 #include "chrono_batch_render/scene/tables/spectra.h"
3
4 #include <algorithm>
5 #include <cstring>
6
7 #include "chrono_batch_render/material/uplift.h"
8 #include "chrono_batch_render/scene/records/render_tables.h"
9 #include "chrono_batch_render/scene/schema/columns.h"
10 #include "chrono_batch_render/util/log.h"
11 #include "chrono_batch_render/util/profiler.h"
12 #include "chrono_batch_render/util/span.h"
13
14 namespace chrono::batch_render {
15
16 namespace {
17
18 constexpr float kVisibleFirst = 380;
19 constexpr float kVisibleLast = 780;
20 constexpr float kBasisFirst = 402.5f;
21 constexpr float kBasisStep = 45;
22 constexpr Float3 kBasis[] = {{0.05f, 0.00f, 0.60f}, {0.00f, 0.05f, 1.00f}, {0.00f, 0.45f, 0.70f},
23 {0.05f, 1.00f, 0.15f}, {0.45f, 0.85f, 0.00f}, {1.00f, 0.35f, 0.00f},
24 {0.80f, 0.05f, 0.00f}, {0.25f, 0.00f, 0.00f}};
25
26 8000 Float3 Basis(float nanometers) {
27
2/2
✓ Branch 0 taken 440 times.
✓ Branch 1 taken 7560 times.
8000 const float at = std::clamp((nanometers - kBasisFirst) / kBasisStep, 0.0f, 7.0f);
28
2/2
✓ Branch 0 taken 1260 times.
✓ Branch 1 taken 6740 times.
8000 const uint32_t below = std::min(uint32_t(at), 6u);
29 8000 const float t = at - float(below);
30 8000 const Float3& a = kBasis[below];
31 8000 const Float3& b = kBasis[below + 1];
32 8000 return {a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t};
33 }
34
35 15680 float Sample(std::span<const Float2> samples, float nanometers) {
36
2/2
✓ Branch 0 taken 10320 times.
✓ Branch 1 taken 5360 times.
15680 if (samples.empty()) {
37 return 0;
38 }
39
2/2
✓ Branch 0 taken 640 times.
✓ Branch 1 taken 9680 times.
10320 if (nanometers <= samples.front().x) {
40 640 return samples.front().y;
41 }
42
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9680 times.
9680 if (nanometers >= samples.back().x) {
43 ✗ return samples.back().y;
44 }
45 9680 const auto above = std::ranges::upper_bound(samples, nanometers, {}, &Float2::x);
46
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9680 times.
9680 const Float2& high = *above;
47
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9680 times.
9680 const Float2& low = *(above - 1);
48
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9680 times.
9680 const float t = (nanometers - low.x) / std::max(high.x - low.x, 1e-6f);
49 9680 return low.y + (high.y - low.y) * t;
50 }
51
52 120 float Average(std::span<const Float2> samples, float low, float high) {
53 120 constexpr uint32_t kSteps = 64;
54 120 float sum = 0;
55
2/2
✓ Branch 0 taken 7680 times.
✓ Branch 1 taken 120 times.
7800 for (uint32_t step = 0; step < kSteps; ++step) {
56 7680 sum += Sample(samples, low + (float(step) + 0.5f) * (high - low) / kSteps);
57 }
58 120 return sum / kSteps;
59 }
60
61 20 Float3 Rgb(std::span<const Float2> samples) {
62 20 Float3 sum{};
63 20 Float3 weight{};
64
2/2
✓ Branch 0 taken 8000 times.
✓ Branch 1 taken 20 times.
8020 for (float nanometers = kVisibleFirst + 0.5f; nanometers < kVisibleLast; nanometers += 1) {
65 8000 const Float3 basis = Basis(nanometers);
66 8000 const float value = Sample(samples, nanometers);
67 8000 sum = {sum.x + basis.x * value, sum.y + basis.y * value, sum.z + basis.z * value};
68 8000 weight = {weight.x + basis.x, weight.y + basis.y, weight.z + basis.z};
69 }
70 20 return {sum.x / weight.x, sum.y / weight.y, sum.z / weight.z};
71 }
72
73 40 std::vector<float> Floats(ColumnStore& store, ColumnRef column, uint32_t row) {
74 40 const ArrayRef array = store.Array(column, row);
75
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 26 times.
40 if (array.address == 0) {
76 14 return {};
77 }
78
1/1
✓ Branch 3 taken 26 times.
78 return Items<float>(store.Arrays(ColumnType::FloatArray).Read(array));
79 }
80
81 }
82
83 2332 std::span<const ColumnRef> SpectralTable::Inputs() const {
84 2332 static const ColumnRef kInputs[] = {kSampledSpectrumWavelengths, kSampledSpectrumValues,
85 kDetectorAPIDetectorSpectralRange};
86 2332 return kInputs;
87 }
88
89 478 void SpectralTable::Run(const ChangeSet&, TableVersions&) {
90
4/5
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 473 times.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 5 times.
478 CHRONO_BATCH_RENDER_ZONE("Spectra/Run");
91
1/1
✓ Branch 1 taken 478 times.
478 ColumnStore& store = m_structure.GetColumnStore();
92
1/1
✓ Branch 1 taken 478 times.
478 device::Device& device = store.GetDevice();
93
1/1
✓ Branch 1 taken 478 times.
478 const Prims& prims = m_structure.GetPrims();
94
1/1
✓ Branch 1 taken 478 times.
478 ColumnTable& spectra = store.Schema(SchemaId::SampledSpectrum);
95
1/1
✓ Branch 1 taken 478 times.
478 ColumnTable& detectors = store.Schema(SchemaId::DetectorAPI);
96
1/1
✓ Branch 1 taken 478 times.
478 m_count = spectra.Rows();
97 478 m_detectors = detectors.Rows();
98 478 const std::vector<Float2> ranges =
99
2/2
✓ Branch 1 taken 478 times.
✓ Branch 4 taken 478 times.
956 Items<Float2>(detectors.Read(kDetectorAPIDetectorSpectralRange.index, 0, detectors.Rows()));
100 478 const std::vector<uint32_t> owners =
101
3/3
✓ Branch 1 taken 478 times.
✓ Branch 4 taken 478 times.
✓ Branch 7 taken 478 times.
956 Items<uint32_t>(spectra.Read(store.PrimsColumn(SchemaId::SampledSpectrum), 0, m_count));
102
5/5
✓ Branch 0 taken 458 times.
✓ Branch 1 taken 20 times.
✓ Branch 3 taken 478 times.
✓ Branch 5 taken 463 times.
✓ Branch 6 taken 15 times.
936 std::vector<Float3> rgb(std::max(m_count, 1u));
103
5/5
✓ Branch 0 taken 463 times.
✓ Branch 1 taken 15 times.
✓ Branch 3 taken 478 times.
✓ Branch 5 taken 458 times.
✓ Branch 6 taken 20 times.
941 std::vector<float> bands(std::max<size_t>(size_t(m_count) * m_detectors * kDetectorBands, 1));
104
3/3
✓ Branch 0 taken 458 times.
✓ Branch 1 taken 20 times.
✓ Branch 3 taken 478 times.
936 std::vector<Uint2> spans(std::max(m_count, 1u));
105 478 std::vector<Float2> samples;
106
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 478 times.
498 for (uint32_t row = 0; row < m_count; ++row) {
107
3/5
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 20 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 20 times.
20 if (owners[row] == kNoPrim || !prims.Alive(owners[row])) {
108 ✗ continue;
109 }
110
1/1
✓ Branch 1 taken 20 times.
20 const std::vector<float> wavelengths = Floats(store, kSampledSpectrumWavelengths, row);
111
1/1
✓ Branch 1 taken 20 times.
20 const std::vector<float> values = Floats(store, kSampledSpectrumValues, row);
112 20 std::vector<Float2> curve;
113 20 bool increasing = true;
114
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
✓ Branch 2 taken 42 times.
✓ Branch 3 taken 20 times.
62 for (size_t i = 0; i < std::min(wavelengths.size(), values.size()); ++i) {
115
4/6
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
42 increasing = increasing && (curve.empty() || wavelengths[i] > curve.back().x);
116
1/1
✓ Branch 1 taken 42 times.
42 curve.push_back({wavelengths[i], values[i]});
117 }
118
2/4
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 20 times.
20 if (wavelengths.size() != values.size() || !increasing) {
119
0/1
✗ Branch 1 not taken.
✗ Log(LogLevel::Warning,
120 "{} needs increasing wavelengths and one value for each; it reads as zero",
121
0/1
✗ Branch 1 not taken.
✗ m_structure.GetPaths().Path(owners[row]));
122 ✗ curve.clear();
123 }
124
1/1
✓ Branch 1 taken 20 times.
20 spans[row] = {uint32_t(samples.size()), uint32_t(curve.size())};
125
1/1
✓ Branch 1 taken 20 times.
20 samples.insert(samples.end(), curve.begin(), curve.end());
126 20 rgb[row] = Rgb(curve);
127
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 20 times.
35 for (uint32_t detector = 0; detector < m_detectors; ++detector) {
128
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 const float first = std::max(ranges[detector].x, 0.0f);
129 15 const float width =
130
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 std::max((ranges[detector].y - first) / float(kDetectorBands), 1e-3f);
131
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 15 times.
135 for (uint32_t band = 0; band < kDetectorBands; ++band) {
132 120 bands[(size_t(detector) * m_count + row) * kDetectorBands + band] =
133 120 Average(curve, first + float(band) * width, first + float(band + 1) * width);
134 }
135 }
136 20 }
137
2/2
✓ Branch 0 taken 465 times.
✓ Branch 1 taken 13 times.
478 if (samples.empty()) {
138
1/1
✓ Branch 1 taken 465 times.
465 samples.push_back({});
139 }
140
2/2
✓ Branch 0 taken 274 times.
✓ Branch 1 taken 204 times.
478 if (!m_sigmoids) {
141
1/1
✓ Branch 1 taken 274 times.
274 const SigmoidTable& sigmoids = Sigmoids();
142
2/3
✓ Branch 1 taken 274 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 274 times.
274 m_sigmoids = device.CreateBuffer({.label = "sigmoid uplift"}, sigmoids.coefficients);
143
2/3
✓ Branch 1 taken 274 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 274 times.
274 m_sigmoidScales = device.CreateBuffer({.label = "sigmoid scales"}, sigmoids.scales);
144 }
145
3/3
✓ Branch 1 taken 478 times.
✓ Branch 3 taken 204 times.
✓ Branch 4 taken 274 times.
682 m_rgb = device.CreateBuffer({.label = "spectra rgb"}, rgb);
146
3/3
✓ Branch 1 taken 478 times.
✓ Branch 3 taken 204 times.
✓ Branch 4 taken 274 times.
682 m_bands = device.CreateBuffer({.label = "spectra bands"}, bands);
147
3/3
✓ Branch 1 taken 478 times.
✓ Branch 3 taken 204 times.
✓ Branch 4 taken 274 times.
682 m_ranges = device.CreateBuffer({.label = "spectra ranges"}, spans);
148
3/3
✓ Branch 1 taken 478 times.
✓ Branch 3 taken 204 times.
✓ Branch 4 taken 274 times.
682 m_samples = device.CreateBuffer({.label = "spectra samples"}, samples);
149
1/1
✓ Branch 1 taken 478 times.
478 Log(LogLevel::Debug, "projected {} spectra onto {}", m_count,
150
1/1
✓ Branch 1 taken 478 times.
478 Counted(m_detectors, "detector band set"));
151 3416 }
152
153 478 void SpectralTable::Describe(RenderTables& tables) const {
154
1/2
✓ Branch 0 taken 478 times.
✗ Branch 1 not taken.
478 tables.spectra.rgb.address = m_rgb ? m_rgb->Address() : 0;
155
1/2
✓ Branch 0 taken 478 times.
✗ Branch 1 not taken.
478 tables.spectra.bands.address = m_bands ? m_bands->Address() : 0;
156
1/2
✓ Branch 0 taken 478 times.
✗ Branch 1 not taken.
478 tables.spectra.ranges.address = m_ranges ? m_ranges->Address() : 0;
157
1/2
✓ Branch 0 taken 478 times.
✗ Branch 1 not taken.
478 tables.spectra.samples.address = m_samples ? m_samples->Address() : 0;
158
1/2
✓ Branch 0 taken 478 times.
✗ Branch 1 not taken.
478 tables.spectra.sigmoids.address = m_sigmoids ? m_sigmoids->Address() : 0;
159
1/2
✓ Branch 0 taken 478 times.
✗ Branch 1 not taken.
478 tables.spectra.sigmoidScales.address = m_sigmoidScales ? m_sigmoidScales->Address() : 0;
160
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 478 times.
478 tables.spectra.sigmoidResolution = m_sigmoids ? kSigmoidResolution : 0;
161 478 tables.spectra.count = m_count;
162 478 tables.spectra.detectors = m_detectors;
163 478 }
164
165 }
166