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

visgl / luma.gl / 27877741225

20 Jun 2026 04:53PM UTC coverage: 70.762% (+0.03%) from 70.733%
27877741225

push

github

web-flow
feat(experimental): HTMLTexture via HTML-in-Canvas (#2674)

9977 of 15874 branches covered (62.85%)

Branch coverage included in aggregate %.

76 of 136 new or added lines in 8 files covered. (55.88%)

131 existing lines in 5 files now uncovered.

20249 of 26841 relevant lines covered (75.44%)

4021.12 hits per line

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

87.38
/modules/engine/src/utils/shader-module-utils.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import type {ComputeShaderLayout, ShaderLayout} from '@luma.gl/core';
6
import type {ShaderModule} from '@luma.gl/shadertools';
7

8
type AnyShaderLayout = ShaderLayout | ComputeShaderLayout;
9

10
export function mergeShaderModuleBindingsIntoLayout<TShaderLayout extends AnyShaderLayout>(
11
  shaderLayout: TShaderLayout | null | undefined,
12
  modules: ShaderModule[]
13
): TShaderLayout | null | undefined {
14
  if (!shaderLayout || !modules.some(module => module.bindingLayout?.length)) {
516✔
15
    return shaderLayout;
509✔
16
  }
17

18
  const mergedLayout = {
7✔
19
    ...shaderLayout,
20
    bindings: shaderLayout.bindings.map(binding => ({...binding}))
16✔
21
  } as TShaderLayout;
22

23
  if ('attributes' in (shaderLayout || {})) {
7!
24
    (mergedLayout as ShaderLayout).attributes = (shaderLayout as ShaderLayout)?.attributes || [];
7!
25
  }
26

27
  for (const module of modules) {
7✔
28
    for (const bindingLayout of module.bindingLayout || []) {
8!
29
      for (const relatedBindingName of getRelatedBindingNames(bindingLayout.name)) {
28✔
30
        const binding = mergedLayout.bindings.find(
84✔
31
          candidate => candidate.name === relatedBindingName
173✔
32
        );
33
        if (binding?.group === 0) {
84✔
34
          binding.group = bindingLayout.group;
10✔
35
        }
36
      }
37
    }
38
  }
39

40
  return mergedLayout;
7✔
41
}
42

43
export function mergeInferredShaderLayout(
44
  shaderLayout: ShaderLayout | null | undefined,
45
  inferredShaderLayout: ShaderLayout | null | undefined,
46
  inferredAttributeNames: readonly string[] = []
42✔
47
): ShaderLayout | null | undefined {
48
  if (!shaderLayout) {
42✔
49
    return inferredShaderLayout;
13✔
50
  }
51
  if (!inferredShaderLayout) {
29!
UNCOV
52
    return shaderLayout;
×
53
  }
54

55
  return {
29✔
56
    ...shaderLayout,
57
    attributes: shaderLayout.attributes.length
29✔
58
      ? mergeAttributeLayouts(
59
          shaderLayout.attributes,
60
          inferredShaderLayout.attributes.filter(attribute =>
61
            inferredAttributeNames.includes(attribute.name)
59✔
62
          )
63
        )
64
      : inferredShaderLayout.attributes,
65
    bindings: mergeBindingLayouts(shaderLayout.bindings, inferredShaderLayout.bindings)
66
  };
67
}
68

69
export function shaderModuleHasUniforms(module: ShaderModule): boolean {
70
  return Boolean(module.uniformTypes && !isObjectEmpty(module.uniformTypes));
506✔
71
}
72

73
export function mergeShaderModules(
74
  explicitModules: ShaderModule[] | undefined,
75
  shaderInputModules: ShaderModule[] | undefined
76
): ShaderModule[] {
77
  const modules: ShaderModule[] = [];
363✔
78
  const moduleNames = new Set<string>();
363✔
79

80
  for (const module of [...(explicitModules || []), ...(shaderInputModules || [])]) {
363!
81
    if (!moduleNames.has(module.name)) {
869✔
82
      moduleNames.add(module.name);
493✔
83
      modules.push(module);
493✔
84
    }
85
  }
86

87
  return modules;
363✔
88
}
89

90
/** Returns binding-name aliases that should share the module-declared bind group. */
91
function getRelatedBindingNames(bindingName: string): string[] {
92
  const bindingNames = new Set<string>([bindingName, `${bindingName}Uniforms`]);
28✔
93

94
  if (!bindingName.endsWith('Uniforms')) {
28!
95
    bindingNames.add(`${bindingName}Sampler`);
28✔
96
  }
97

98
  return [...bindingNames];
28✔
99
}
100

101
function isObjectEmpty(obj: object): boolean {
102
  for (const _key in obj) {
169✔
103
    return false;
169✔
104
  }
UNCOV
105
  return true;
×
106
}
107

108
function mergeBindingLayouts<TBindingLayout extends ShaderLayout['bindings'][number]>(
109
  explicitBindings: TBindingLayout[],
110
  inferredBindings: TBindingLayout[]
111
): TBindingLayout[] {
112
  const mergedBindings = explicitBindings.map(binding => ({...binding}));
27✔
113
  const explicitBindingNames = new Set(explicitBindings.map(binding => binding.name));
27✔
114
  const explicitBindingLocations = new Set(
27✔
115
    explicitBindings.map(binding => `${binding.group}:${binding.location}`)
3✔
116
  );
117

118
  for (const inferredBinding of inferredBindings) {
27✔
119
    const inferredBindingLocation = `${inferredBinding.group}:${inferredBinding.location}`;
236✔
120
    if (
236✔
121
      !explicitBindingNames.has(inferredBinding.name) &&
469✔
122
      !explicitBindingLocations.has(inferredBindingLocation)
123
    ) {
124
      mergedBindings.push({...inferredBinding});
233✔
125
    }
126
  }
127

128
  return mergedBindings;
27✔
129
}
130

131
function mergeAttributeLayouts(
132
  explicitAttributes: ShaderLayout['attributes'],
133
  inferredAttributes: ShaderLayout['attributes']
134
): ShaderLayout['attributes'] {
135
  const mergedAttributes = explicitAttributes.map(attribute => ({...attribute}));
64✔
136
  const explicitAttributesByName = new Map(
25✔
137
    explicitAttributes.map(attribute => [attribute.name, attribute])
64✔
138
  );
139
  const explicitAttributesByLocation = new Map(
25✔
140
    explicitAttributes.map(attribute => [attribute.location, attribute])
64✔
141
  );
142

143
  for (const inferredAttribute of inferredAttributes) {
25✔
144
    const explicitByName = explicitAttributesByName.get(inferredAttribute.name);
3✔
145
    if (explicitByName) {
3✔
146
      if (
1!
147
        explicitByName.type !== inferredAttribute.type ||
1!
148
        explicitByName.location !== inferredAttribute.location
149
      ) {
150
        throw new Error(
1✔
151
          `Shader attribute "${inferredAttribute.name}" conflicts with its inferred type or location`
152
        );
153
      }
UNCOV
154
      continue;
×
155
    }
156

157
    const explicitByLocation = explicitAttributesByLocation.get(inferredAttribute.location);
2✔
158
    if (explicitByLocation) {
2✔
159
      throw new Error(
1✔
160
        `Shader attributes "${explicitByLocation.name}" and "${inferredAttribute.name}" both use location ${inferredAttribute.location}`
161
      );
162
    }
163

164
    mergedAttributes.push({...inferredAttribute});
1✔
165
  }
166

167
  return mergedAttributes;
23✔
168
}
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