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

visgl / luma.gl / 23536527544

25 Mar 2026 10:31AM UTC coverage: 73.58% (-0.02%) from 73.599%
23536527544

push

github

web-flow
chore(example): use effects module for persistence (#2434)

4801 of 7413 branches covered (64.76%)

Branch coverage included in aggregate %.

43 of 105 new or added lines in 7 files covered. (40.95%)

1 existing line in 1 file now uncovered.

10837 of 13840 relevant lines covered (78.3%)

285.52 hits per line

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

2.56
/modules/engine/src/debug/debug-framebuffer.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import type {Device, Framebuffer, RenderPass, Texture} from '@luma.gl/core';
6

7
const DEBUG_FRAMEBUFFER_STATE_KEY = '__debugFramebufferState';
67✔
8
const DEFAULT_MARGIN_PX = 8;
67✔
9

10
type DebugFramebufferOptions = {
11
  id: string;
12
  minimap?: boolean;
13
  opaque?: boolean;
14
  top?: string;
15
  left?: string;
16
  rgbaScale?: number;
17
};
18

19
type DebugFramebufferState = {
20
  flushing: boolean;
21
  queuedFramebuffers: Framebuffer[];
22
};
23

24
/**
25
 * Debug utility to blit queued offscreen framebuffers into the default framebuffer
26
 * without CPU readback. Currently implemented for WebGL only.
27
 */
28
export function debugFramebuffer(
29
  renderPass: RenderPass,
30
  source: Framebuffer | Texture | null,
31
  options: DebugFramebufferOptions
32
): void {
NEW
33
  if (renderPass.device.type !== 'webgl') {
×
NEW
34
    return;
×
35
  }
36

NEW
37
  const state = getDebugFramebufferState(renderPass.device);
×
NEW
38
  if (state.flushing) {
×
NEW
39
    return;
×
40
  }
41

NEW
42
  if (isDefaultRenderPass(renderPass)) {
×
NEW
43
    flushDebugFramebuffers(renderPass, options, state);
×
NEW
44
    return;
×
45
  }
46

NEW
47
  if (source && isFramebuffer(source) && source.handle !== null) {
×
NEW
48
    if (!state.queuedFramebuffers.includes(source)) {
×
NEW
49
      state.queuedFramebuffers.push(source);
×
50
    }
51
  }
52
}
53

54
function flushDebugFramebuffers(
55
  renderPass: RenderPass,
56
  options: DebugFramebufferOptions,
57
  state: DebugFramebufferState
58
): void {
NEW
59
  if (state.queuedFramebuffers.length === 0) {
×
NEW
60
    return;
×
61
  }
62

NEW
63
  const webglDevice = renderPass.device as Device & {gl: WebGL2RenderingContext};
×
NEW
64
  const {gl} = webglDevice;
×
NEW
65
  const previousReadFramebuffer = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING);
×
NEW
66
  const previousDrawFramebuffer = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING);
×
NEW
67
  const [targetWidth, targetHeight] = renderPass.device
×
68
    .getDefaultCanvasContext()
69
    .getDrawingBufferSize();
70

NEW
71
  let topPx = parseCssPixel(options.top, DEFAULT_MARGIN_PX);
×
NEW
72
  const leftPx = parseCssPixel(options.left, DEFAULT_MARGIN_PX);
×
73

NEW
74
  state.flushing = true;
×
NEW
75
  try {
×
NEW
76
    for (const framebuffer of state.queuedFramebuffers) {
×
NEW
77
      const [targetX0, targetY0, targetX1, targetY1, previewHeight] = getOverlayRect({
×
78
        framebuffer,
79
        targetWidth,
80
        targetHeight,
81
        topPx,
82
        leftPx,
83
        minimap: options.minimap
84
      });
85

NEW
86
      gl.bindFramebuffer(gl.READ_FRAMEBUFFER, framebuffer.handle as WebGLFramebuffer | null);
×
NEW
87
      gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
×
NEW
88
      gl.blitFramebuffer(
×
89
        0,
90
        0,
91
        framebuffer.width,
92
        framebuffer.height,
93
        targetX0,
94
        targetY0,
95
        targetX1,
96
        targetY1,
97
        gl.COLOR_BUFFER_BIT,
98
        gl.NEAREST
99
      );
100

NEW
101
      topPx += previewHeight + DEFAULT_MARGIN_PX;
×
102
    }
103
  } finally {
NEW
104
    gl.bindFramebuffer(gl.READ_FRAMEBUFFER, previousReadFramebuffer);
×
NEW
105
    gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, previousDrawFramebuffer);
×
NEW
106
    state.flushing = false;
×
107
  }
108
}
109

110
function getOverlayRect(options: {
111
  framebuffer: Framebuffer;
112
  targetWidth: number;
113
  targetHeight: number;
114
  topPx: number;
115
  leftPx: number;
116
  minimap?: boolean;
117
}): [number, number, number, number, number] {
NEW
118
  const {framebuffer, targetWidth, targetHeight, topPx, leftPx, minimap} = options;
×
NEW
119
  const maxWidth = minimap ? Math.max(Math.floor(targetWidth / 4), 1) : targetWidth;
×
NEW
120
  const maxHeight = minimap ? Math.max(Math.floor(targetHeight / 4), 1) : targetHeight;
×
NEW
121
  const scale = Math.min(maxWidth / framebuffer.width, maxHeight / framebuffer.height);
×
NEW
122
  const previewWidth = Math.max(Math.floor(framebuffer.width * scale), 1);
×
NEW
123
  const previewHeight = Math.max(Math.floor(framebuffer.height * scale), 1);
×
NEW
124
  const targetX0 = leftPx;
×
NEW
125
  const targetY0 = Math.max(targetHeight - topPx - previewHeight, 0);
×
NEW
126
  const targetX1 = targetX0 + previewWidth;
×
NEW
127
  const targetY1 = targetY0 + previewHeight;
×
NEW
128
  return [targetX0, targetY0, targetX1, targetY1, previewHeight];
×
129
}
130

131
function getDebugFramebufferState(device: Device): DebugFramebufferState {
NEW
132
  device.userData[DEBUG_FRAMEBUFFER_STATE_KEY] ||= {
×
133
    flushing: false,
134
    queuedFramebuffers: []
135
  } satisfies DebugFramebufferState;
NEW
136
  return device.userData[DEBUG_FRAMEBUFFER_STATE_KEY] as DebugFramebufferState;
×
137
}
138

139
function isFramebuffer(value: Framebuffer | Texture): value is Framebuffer {
NEW
140
  return 'colorAttachments' in value;
×
141
}
142

143
function isDefaultRenderPass(renderPass: RenderPass): boolean {
NEW
144
  const framebuffer = renderPass.props.framebuffer as {handle?: unknown} | null;
×
NEW
145
  return !framebuffer || framebuffer.handle === null;
×
146
}
147

148
function parseCssPixel(value: string | undefined, defaultValue: number): number {
NEW
149
  if (!value) {
×
NEW
150
    return defaultValue;
×
151
  }
NEW
152
  const parsedValue = Number.parseInt(value, 10);
×
NEW
153
  return Number.isFinite(parsedValue) ? parsedValue : defaultValue;
×
154
}
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