GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
9 / 9
100.0%
Branches:
13 / 16
81.2%
Functions:
2 / 2, 1 excl
100.0%

shaders/scene/transform.slang
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file transform.slang
3 /// @brief The transform graph on the device: programs of ops evaluated into local transforms,
4 /// composed level by level into worlds relative to the render origin, all in double-float, and
5 /// the worlds read back as floats by the kernels that place things. The programs' layout is
6 /// shared with C++ (transform_graph.h).
7 ///
8 /// import transform;
9 /// Affine world = LoadWorld(parameters.worlds, slot);
10 #ifdef __cplusplus
11 #pragma once
12 #include "ernie/shaders/shared.h"
13 namespace ernie::shaders {
14 #else
15 import affine;
16 import double_float;
17 import buffer_ref;
18 #endif
19
20 /// A program's op kinds: a folded constant, then each of the host's transform op kinds after it,
21 /// in its order; the Euler rotations run from `kRotateXYZ` to `kOrient`.
22 static const uint kConstant = 0;
23 static const uint kTranslate = 1;
24 static const uint kScale = 2;
25 static const uint kRotateX = 3;
26 static const uint kRotateXYZ = 6;
27 static const uint kOrient = 12;
28
29 /// An op's flags: bit 0 inverts it; bits 8 to 15 count the floats it reads.
30 static const uint kInvert = 1;
31 static const uint kFloatsShift = 8;
32
33 /// The most ops a program holds: device ops and the folded host ops between them.
34 static const uint kMaxOps = 8;
35 /// No slot: a root's parent.
36 static const uint kNone = 0xffffffff;
37
38 /// A transform's top three rows in double-float, as the device keeps them: each value the sum of
39 /// its high float and its low one.
40 struct SplitRows {
41 37218 float4 high[3];
42 35775 float4 low[3];
43 #ifdef __cplusplus
44
7/8
✓ Branch 0 taken 35947 times.
✓ Branch 1 taken 1271 times.
✓ Branch 2 taken 37218 times.
✓ Branch 3 taken 11925 times.
✓ Branch 4 taken 35775 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 35775 times.
✓ Branch 7 taken 11925 times.
86189 bool operator==(const SplitRows&) const = default;
45 #endif
46 };
47
48 /// One op of a program.
49 2296 struct Op {
50 9139 uint kind;
51 8852 uint flags;
52 /// A bound op's row of floats.
53 8852 BufferRef<float> source;
54 /// A folded constant, exactly as the host split it.
55 8848 SplitRows constant;
56 #ifdef __cplusplus
57
6/8
✓ Branch 0 taken 8852 times.
✓ Branch 1 taken 287 times.
✓ Branch 2 taken 8852 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8848 times.
✓ Branch 5 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8848 times.
9139 bool operator==(const Op&) const = default;
58 #endif
59 };
60
61 struct LocalParameters {
62 /// Each programmed node's slot, program of `kMaxOps` ops and its length.
63 BufferRef<uint> slots;
64 BufferRef<Op> programs;
65 BufferRef<uint> lengths;
66 /// The locals, by slot.
67 BufferRef<float4> locals;
68 uint count;
69 };
70
71 struct ComposeParameters {
72 /// The level's slots.
73 BufferRef<uint> slots;
74 /// Each slot's parent, or `kNone`.
75 BufferRef<uint> parents;
76 /// The locals and worlds, by slot.
77 BufferRef<float4> locals;
78 BufferRef<float4> worlds;
79 uint count;
80 };
81
82 #ifdef __cplusplus
83 } // namespace ernie::shaders
84 #else
85
86 /// A slot's world transform relative to the render origin, as float rows.
87 Affine LoadWorld(BufferRef<float4> worlds, uint slot) {
88 return Round(LoadDouble(worlds, slot));
89 }
90
91 static const uint3 kAxes[6] = { uint3(0, 1, 2), uint3(0, 2, 1), uint3(1, 0, 2),
92 uint3(1, 2, 0), uint3(2, 0, 1), uint3(2, 1, 0) };
93
94 /// A right-handed turn about one axis, as the host's AxisRotation builds it.
95 Affine Turn(uint axis, float degrees) {
96 float c = cos(radians(degrees));
97 float s = sin(radians(degrees));
98 uint a = (axis + 1) % 3;
99 uint b = (axis + 2) % 3;
100 Affine m = Identity();
101 m.rows[a][a] = c;
102 m.rows[a][b] = -s;
103 m.rows[b][a] = s;
104 m.rows[b][b] = c;
105 return m;
106 }
107
108 Affine Inverse(Affine m) {
109 float3 a = m.rows[0].xyz;
110 float3 b = m.rows[1].xyz;
111 float3 c = m.rows[2].xyz;
112 float3 r0 = cross(b, c);
113 float3 r1 = cross(c, a);
114 float3 r2 = cross(a, b);
115 float invDet = 1 / dot(a, r0);
116 // The inverse's columns are the cofactor rows over the determinant.
117 float3x3 inv = transpose(float3x3(r0, r1, r2)) * invDet;
118 float3 t = float3(m.rows[0].w, m.rows[1].w, m.rows[2].w);
119 float3 back = -mul(inv, t);
120 Affine result;
121 for (uint r = 0; r < 3; ++r) {
122 result.rows[r] = float4(inv[r], back[r]);
123 }
124 return result;
125 }
126
127 /// A bound op's matrix from the floats it reads.
128 Affine OpMatrix(Op op) {
129 Affine m = Identity();
130 BufferRef<float> v = op.source;
131 uint floats = (op.flags >> kFloatsShift) & 0xff;
132 if (op.kind == kTranslate) {
133 m.rows[0].w = v[0];
134 m.rows[1].w = v[1];
135 m.rows[2].w = v[2];
136 } else if (op.kind == kScale) {
137 float3 s = floats == 1 ? float3(v[0]) : float3(v[0], v[1], v[2]);
138 m.rows[0].x = s.x;
139 m.rows[1].y = s.y;
140 m.rows[2].z = s.z;
141 } else if (op.kind < kRotateXYZ) {
142 m = Turn(op.kind - kRotateX, v[0]);
143 } else if (op.kind < kOrient) {
144 // The order's first axis turns first: R = Rk * Rj * Ri, each by its own axis' angle.
145 uint3 axes = kAxes[op.kind - kRotateXYZ];
146 m = Mul(Mul(Turn(axes.z, v[axes.z]), Turn(axes.y, v[axes.y])),
147 Turn(axes.x, v[axes.x]));
148 } else if (op.kind == kOrient) {
149 float4 q = normalize(float4(v[0], v[1], v[2], v[3]));
150 m.rows[0] = float4(1 - 2 * (q.y * q.y + q.z * q.z), 2 * (q.x * q.y - q.z * q.w),
151 2 * (q.x * q.z + q.y * q.w), 0);
152 m.rows[1] = float4(2 * (q.x * q.y + q.z * q.w), 1 - 2 * (q.x * q.x + q.z * q.z),
153 2 * (q.y * q.z - q.x * q.w), 0);
154 m.rows[2] = float4(2 * (q.x * q.z - q.y * q.w), 2 * (q.y * q.z + q.x * q.w),
155 1 - 2 * (q.x * q.x + q.y * q.y), 0);
156 } else {
157 // Column-major, as the scene's matrices are.
158 for (uint r = 0; r < 3; ++r) {
159 m.rows[r] = float4(v[r], v[4 + r], v[8 + r], v[12 + r]);
160 }
161 }
162 return (op.flags & kInvert) != 0 ? Inverse(m) : m;
163 }
164
165 /// An op's matrix: a folded constant exactly as the host split it, a bound op from its floats.
166 DoubleAffine OpDoubleMatrix(Op op) {
167 if (op.kind != kConstant) {
168 return Promote(OpMatrix(op));
169 }
170 DoubleAffine m;
171 for (uint r = 0; r < 3; ++r) {
172 m.rows[r].hi = op.constant.high[r];
173 m.rows[r].lo = op.constant.low[r];
174 }
175 return m;
176 }
177
178 /// One thread a programmed node, its program at its place in the list.
179 [shader("compute")]
180 [numthreads(64, 1, 1)]
181 void Local(uint3 id : SV_DispatchThreadID, uniform LocalParameters parameters) {
182 uint at = id.x;
183 if (at >= parameters.count) {
184 return;
185 }
186 DoubleAffine local = Promote(Identity());
187 for (uint k = 0; k < parameters.lengths[at]; ++k) {
188 local = Mul(local, OpDoubleMatrix(parameters.programs[at * kMaxOps + k]));
189 }
190 StoreDouble(parameters.locals, parameters.slots[at], local);
191 }
192
193 /// One level of the graph: each node's world from its parent's and its local.
194 [shader("compute")]
195 [numthreads(64, 1, 1)]
196 void Compose(uint3 id : SV_DispatchThreadID, uniform ComposeParameters parameters) {
197 if (id.x >= parameters.count) {
198 return;
199 }
200 uint slot = parameters.slots[id.x];
201 uint parent = parameters.parents[slot];
202 DoubleAffine local = LoadDouble(parameters.locals, slot);
203 StoreDouble(parameters.worlds, slot,
204 parent == kNone ? local : Mul(LoadDouble(parameters.worlds, parent), local));
205 }
206
207 #endif
208