• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / luma.gl / 26178691674

20 May 2026 05:24PM UTC coverage: 74.942% (+0.09%) from 74.85%
26178691674

push

github

web-flow
feat(gpgpu): Consolidate arithmetic operations (#2630)

7494 of 11282 branches covered (66.42%)

Branch coverage included in aggregate %.

281 of 344 new or added lines in 30 files covered. (81.69%)

26 existing lines in 5 files now uncovered.

16166 of 20289 relevant lines covered (79.68%)

1206.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.32
/modules/gpgpu/src/operations/webgpu/arithmetic.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import type {Device} from '@luma.gl/core';
6
import {OperationHandler} from '../../operation/operation';
7
import {compileExpression} from '../../utils/expression';
8
import {ARITHMETIC_OPERATIONS, ArithmeticOperationInputs} from '../arithmetic-operation';
9
import {runRowComputation} from './common/row-transform';
10
import {formatLiteralValue, getWGSLType, getZeroValue} from './common/helper';
11

12
// Port of the shadertools fp32 tan workaround for platforms with poor built-in tan precision.
13
const tanFP32WGSL = `const TWO_PI: f32 = 6.2831854820251465;
11✔
14
const PI_2: f32 = 1.5707963705062866;
15
const PI_16: f32 = 0.1963495463132858;
16

17
const SIN_TABLE_0: f32 = 0.19509032368659973;
18
const SIN_TABLE_1: f32 = 0.3826834261417389;
19
const SIN_TABLE_2: f32 = 0.5555702447891235;
20
const SIN_TABLE_3: f32 = 0.7071067690849304;
21

22
const COS_TABLE_0: f32 = 0.9807852506637573;
23
const COS_TABLE_1: f32 = 0.9238795042037964;
24
const COS_TABLE_2: f32 = 0.8314695954322815;
25
const COS_TABLE_3: f32 = 0.7071067690849304;
26

27
const INVERSE_FACTORIAL_3: f32 = 1.666666716337204e-01;
28
const INVERSE_FACTORIAL_5: f32 = 8.333333767950535e-03;
29
const INVERSE_FACTORIAL_7: f32 = 1.9841270113829523e-04;
30
const INVERSE_FACTORIAL_9: f32 = 2.75573188446287533e-06;
31

32
const FP32_OVERFLOW = 3.402823466e+38;
33

34
fn sin_taylor_fp32(a: f32) -> f32 {
35
  if (a == 0.0) {
36
    return 0.0;
37
  }
38

39
  let x = -a * a;
40
  var s = a;
41
  var r = a;
42

43
  r = r * x;
44
  s = s + r * INVERSE_FACTORIAL_3;
45

46
  r = r * x;
47
  s = s + r * INVERSE_FACTORIAL_5;
48

49
  r = r * x;
50
  s = s + r * INVERSE_FACTORIAL_7;
51

52
  r = r * x;
53
  s = s + r * INVERSE_FACTORIAL_9;
54

55
  return s;
56
}
57

58
fn sincos_taylor_fp32(a: f32) -> vec2<f32> {
59
  if (a == 0.0) {
60
    return vec2<f32>(0.0, 1.0);
61
  }
62

63
  let sin_t = sin_taylor_fp32(a);
64
  let cos_t = sqrt(1.0 - sin_t * sin_t);
65
  return vec2<f32>(sin_t, cos_t);
66
}
67

68
fn tan_taylor_fp32(a: f32) -> f32 {
69
  if (a == 0.0) {
70
    return 0.0;
71
  }
72

73
  let z = floor(a / TWO_PI);
74
  let r = a - TWO_PI * z;
75

76
  var q = floor(r / PI_2 + 0.5);
77
  let j = i32(q);
78

79
  if (j < -2 || j > 2) {
80
    return FP32_OVERFLOW;
81
  }
82

83
  var t = r - PI_2 * q;
84

85
  q = floor(t / PI_16 + 0.5);
86
  let k = i32(q);
87
  let abs_k = abs(k);
88

89
  if (abs_k > 4) {
90
    return FP32_OVERFLOW;
91
  }
92

93
  t = t - PI_16 * q;
94

95
  let sincos_t = sincos_taylor_fp32(t);
96
  let sin_t = sincos_t.x;
97
  let cos_t = sincos_t.y;
98

99
  var u = 0.0;
100
  var v = 0.0;
101

102
  if (abs_k == 1) {
103
    u = COS_TABLE_0;
104
    v = SIN_TABLE_0;
105
  } else if (abs_k == 2) {
106
    u = COS_TABLE_1;
107
    v = SIN_TABLE_1;
108
  } else if (abs_k == 3) {
109
    u = COS_TABLE_2;
110
    v = SIN_TABLE_2;
111
  } else if (abs_k == 4) {
112
    u = COS_TABLE_3;
113
    v = SIN_TABLE_3;
114
  }
115

116
  var s = sin_t;
117
  var c = cos_t;
118

119
  if (k > 0) {
120
    s = u * sin_t + v * cos_t;
121
    c = u * cos_t - v * sin_t;
122
  } else if (k < 0) {
123
    s = u * sin_t - v * cos_t;
124
    c = u * cos_t + v * sin_t;
125
  }
126

127
  var sin_a = 0.0;
128
  var cos_a = 0.0;
129

130
  if (j == 0) {
131
    sin_a = s;
132
    cos_a = c;
133
  } else if (j == 1) {
134
    sin_a = c;
135
    cos_a = -s;
136
  } else if (j == -1) {
137
    sin_a = -c;
138
    cos_a = s;
139
  } else {
140
    sin_a = -s;
141
    cos_a = -c;
142
  }
143

144
  return sin_a / cos_a;
145
}
146

147
fn tan_fp32(a: f32) -> f32 {
148
  return tan_taylor_fp32(a);
149
}
150
`;
151

152
const arithmeticSource = `fn arithmetic_add(x: {TYPE}, y: {TYPE}) -> {TYPE} {
11✔
153
  return x + y;
154
}
155

156
fn arithmetic_subtract(x: {TYPE}, y: {TYPE}) -> {TYPE} {
157
  return x - y;
158
}
159

160
fn arithmetic_multiply(x: {TYPE}, y: {TYPE}) -> {TYPE} {
161
  return x * y;
162
}
163

164
fn arithmetic_divide(x: {TYPE}, y: {TYPE}) -> {TYPE} {
165
  return x / y;
166
}
167

168
fn arithmetic_tan(x: f32) -> f32 {
169
  return {TAN_IMPL}(x);
170
}
171
`;
172

173
function getArithmeticSource(device: Device): string {
174
  const {gpu} = device.info;
19✔
175
  // Equivalent to modules/shadertools/src/lib/shader-assembly/platform-defines.ts LUMA_FP32_TAN_PRECISION_WORKAROUND
176
  const useFp32TanPrecisionWorkaround = gpu !== 'nvidia' && gpu !== 'amd';
19✔
177
  if (useFp32TanPrecisionWorkaround) {
19!
178
    return `${tanFP32WGSL}\n${arithmeticSource.replace('{TAN_IMPL}', 'tan_fp32')}`;
19✔
179
  }
NEW
180
  return arithmeticSource.replace('{TAN_IMPL}', 'tan');
×
181
}
182

183
export const arithmetic: OperationHandler<ArithmeticOperationInputs> = async ({
11✔
184
  device,
185
  inputs,
186
  output,
187
  target
188
}) => {
189
  const operationType = output.type;
19✔
190
  const scalarType = getWGSLType(operationType);
19✔
191
  const zeroLiteral = getZeroValue(operationType);
19✔
192
  const namedInputs = inputs.namedInputs;
19✔
193
  const source = getArithmeticSource(device);
19✔
194

195
  runRowComputation({
19✔
196
    module: {name: 'arithmetic', source},
197
    inputs: namedInputs,
198
    output,
199
    operationType,
200
    outputBuffer: target,
201
    expression: laneIndex =>
202
      compileExpression(inputs.expression, {
61✔
203
        operations: ARITHMETIC_OPERATIONS,
204
        inputs: namedInputs,
205
        laneIndex,
206
        formatInput: name => `${name}[${laneIndex}]`,
104✔
207
        formatOutOfBoundsInput: name => (namedInputs[name].size === 1 ? `${name}[0]` : zeroLiteral),
7✔
208
        formatLiteral: value => {
209
          const v = Array.isArray(value) ? (value[laneIndex] ?? 0) : value;
18!
210
          return `${scalarType}(${formatLiteralValue(operationType, v)})`;
18✔
211
        },
212
        formatCall: (symbol, args) => `${symbol}(${args.join(', ')})`
70✔
213
      })
214
  });
215
  return {success: true};
19✔
216
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc