-
Notifications
You must be signed in to change notification settings - Fork 74
Port to luma.gl WebGL 2 #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughDevice-backed rendering replaces Regl across the codebase; shaders modernized to GLSL ES 3.00 with optional UBOs; ForceManyBodyQuadtree removed; many modules adopt luma.gl Model/Texture/Framebuffer/UniformStore APIs and explicit resource lifecycles. Changes
Sequence Diagram(s)sequenceDiagram
participant App as App / Story
participant Graph as Graph
participant Device as Device (`@luma.gl`)
participant GPU as GPU/Driver
rect rgba(70,130,180,0.5)
App->>Graph: new Graph(div, config, devicePromise?)
alt external devicePromise provided
App->>Device: provide devicePromise
Device-->>Graph: device resolved
else Graph creates device
Graph->>Device: createDevice(canvas, pixelRatio)
Device-->>Graph: device ready
end
end
rect rgba(34,139,34,0.5)
Graph->>Device: ensureDevice()
Graph->>Device: create Textures / Framebuffers / UniformStores / Models
Device-->>GPU: allocate GPU resources
end
rect rgba(255,165,0,0.5)
loop per-frame
Graph->>Device: beginRenderPass(target)
Graph->>Model: setUniforms / setBindings / draw()
Model->>GPU: draw()
alt readback needed
Graph->>Device: readPixelsToArrayWebGL(...)
Device-->>Graph: Float32Array
end
Graph->>Device: endRenderPass()
end
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
6295e9f to
e5582f8
Compare
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
… dynamic sizing Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 675-682: The clear render pass in both the render() and destroy()
methods currently calls device.beginRenderPass(...) and clearPass.end() but
never flushes commands; after the clearPass.end() call in each method (same
pattern as renderFrame()), call this.device.submit() (guarded by the this.device
existence check) so the GPU receives the commands; update the code paths around
device.beginRenderPass / clearPass.end in render() and destroy() to mirror
renderFrame() by invoking this.device.submit() immediately after ending the
pass.
♻️ Duplicate comments (1)
src/index.ts (1)
1415-1423: PreventensureDevicecallbacks after destroy.
ensureDevicestill invokes queued callbacks without checking_isDestroyed, so callbacks can fire after teardown. Please guard before invoking the callback (and optionally before logging) to avoid post-destroy work.🔧 Suggested fix
private ensureDevice (callback: () => void): boolean { if (!this.device) { this.deviceInitPromise .then(() => { + if (this._isDestroyed) return callback() }) .catch(error => { + if (this._isDestroyed) return console.error('Device initialization failed', error) }) return true } return false }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/index.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/index.ts (3)
src/config.ts (1)
GraphConfigInterface(20-612)src/helper.ts (2)
getRgbaColor(16-32)readPixels(75-84)src/variables.ts (1)
defaultConfigValues(12-62)
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| if (this.device) { | ||
| const clearPass = this.device.beginRenderPass({ | ||
| clearColor: this.store.backgroundColor, | ||
| clearDepth: 1, | ||
| clearStencil: 0, | ||
| }) | ||
| clearPass.end() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's check the file exists and see the size
wc -l src/index.tsRepository: cosmosgl/graph
Length of output: 75
🏁 Script executed:
# Look at the render() method around lines 675-682
sed -n '650,700p' src/index.ts | head -60Repository: cosmosgl/graph
Length of output: 2230
🏁 Script executed:
# Look at the destroy() method around lines 1315-1324
sed -n '1300,1340p' src/index.tsRepository: cosmosgl/graph
Length of output: 1204
🏁 Script executed:
# Search for renderFrame() method to see if it calls device.submit()
rg -A 30 'renderFrame\s*\(' src/index.ts | head -80Repository: cosmosgl/graph
Length of output: 1339
🏁 Script executed:
# Search for all device.submit() calls to understand the pattern
rg 'device\.submit\(\)' src/index.ts -B 3 -A 1Repository: cosmosgl/graph
Length of output: 126
🏁 Script executed:
# See all uses of device.submit() and surrounding context
rg 'device\.submit\(\)' -B 5 -A 2 src/Repository: cosmosgl/graph
Length of output: 347
🏁 Script executed:
# Check if there are multiple render passes in renderFrame to understand the pattern
sed -n '1000,1100p' src/index.ts | grep -A 50 'renderFrame'Repository: cosmosgl/graph
Length of output: 40
🏁 Script executed:
# Look for any imports or type definitions for device
rg 'import.*device|from.*luma' src/index.ts | head -20Repository: cosmosgl/graph
Length of output: 215
🏁 Script executed:
# Search for WebGLDevice or device type hints
rg 'WebGLDevice|device.*:' src/index.ts | head -15Repository: cosmosgl/graph
Length of output: 532
🌐 Web query:
luma.gl WebGLDevice submit() method requirement render pass
💡 Result:
Yes — you must end the active RenderPass before calling device.submit(). The luma.gl docs show the pattern:
const renderPass = device.beginRenderPass();
model.draw();
renderPass.end();
device.submit();
RenderPass.end() must be called after all draw calls to guarantee rendering; submit() then submits that work to the GPU. (luma.gl)
Citations:
Add device.submit() calls after render passes in render() and destroy() methods.
The luma.gl command pipeline requires calling device.submit() after calling renderPass.end() to flush the commands to the GPU. The renderFrame() method correctly follows this pattern with device.submit(), but the render() and destroy() methods omit the submit call after their clear passes. Without it, the GPU rendering commands may not execute.
Required changes
if (this.device) {
const clearPass = this.device.beginRenderPass({
clearColor: this.store.backgroundColor,
clearDepth: 1,
clearStencil: 0,
})
clearPass.end()
+ this.device.submit()
} if (this.device) {
// Clears the canvas after particle system is destroyed
const clearPass = this.device.beginRenderPass({
clearColor: this.store.backgroundColor,
clearDepth: 1,
clearStencil: 0,
})
clearPass.end()
+ this.device.submit()
this.device.destroy()
}🤖 Prompt for AI Agents
In `@src/index.ts` around lines 675 - 682, The clear render pass in both the
render() and destroy() methods currently calls device.beginRenderPass(...) and
clearPass.end() but never flushes commands; after the clearPass.end() call in
each method (same pattern as renderFrame()), call this.device.submit() (guarded
by the this.device existence check) so the GPU receives the commands; update the
code paths around device.beginRenderPass / clearPass.end in render() and
destroy() to mirror renderFrame() by invoking this.device.submit() immediately
after ending the pass.
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 1446-1454: validateDevice currently only checks for non-null
HTMLCanvasElement context but doesn't ensure the device is WebGL-capable, which
causes runtime failures when later code calls device.readPixelsToArrayWebGL() or
accesses (device as WebGLDevice).gl; update validateDevice to assert the device
is a WebGLDevice by checking for the WebGL-specific surface/GL properties (e.g.,
presence of readPixelsToArrayWebGL method and a gl property or
canvasContext.type indicating 'html-canvas' with WebGL support) and throw a
clear Error if not; reference validateDevice, WebGLDevice,
readPixelsToArrayWebGL, and any uses of (device as WebGLDevice).gl to locate and
change the guard so external devices are validated at runtime.
♻️ Duplicate comments (6)
src/modules/ForceManyBody/index.ts (2)
64-68: Clamplevelsto a non‑negative integer.
Math.log2can yield fractional or non‑finite values (e.g., 0/negative space sizes), which breaks the loop bounds and the deepest‑level centermass fallback. Please floor and guard it.Suggested fix
- this.levels = Math.log2(store.adjustedSpaceSize) + const rawLevels = Math.log2(store.adjustedSpaceSize ?? 0) + this.levels = Number.isFinite(rawLevels) && rawLevels > 0 + ? Math.floor(rawLevels) + : 0
87-90: Destroy the framebuffer before its attached texture.
Destroying the texture first can leave the framebuffer referencing a freed resource.Suggested fix
- if (existingTarget) { - if (!existingTarget.texture.destroyed) existingTarget.texture.destroy() - if (!existingTarget.fbo.destroyed) existingTarget.fbo.destroy() - } + if (existingTarget) { + if (!existingTarget.fbo.destroyed) existingTarget.fbo.destroy() + if (!existingTarget.texture.destroyed) existingTarget.texture.destroy() + }src/index.ts (1)
684-691: Submit after clear passes so commands flush.
Both clear passes end without adevice.submit(), so the clear may never be flushed.Suggested fix
if (this.device) { const clearPass = this.device.beginRenderPass({ clearColor: this.store.backgroundColor, clearDepth: 1, clearStencil: 0, }) clearPass.end() + this.device.submit() }if (this.device) { const clearPass = this.device.beginRenderPass({ clearColor: this.store.backgroundColor, clearDepth: 1, clearStencil: 0, }) clearPass.end() + this.device.submit() this.device.destroy() }Also applies to: 1324-1332
src/modules/Lines/index.ts (2)
377-391: Guard against zeropointsTextureSizebefore modulo/division.
IfpointsTextureSizeis 0/undefined, the index math yields NaN/Infinity and corrupts GPU data.Suggested fix
public updatePointsBuffer (): void { const { device, data, store } = this if (data.linksNumber === undefined || data.links === undefined) return + if (!store.pointsTextureSize) return
451-507: Validate link color/width/arrow array lengths before GPU writes.
linkColors,linkWidths, andlinkArrowscan be mismatched withlinksNumber, which can lead to partial writes or out‑of‑bounds GPU uploads. Consider validating and falling back to zero‑filled arrays.Example fix (apply similarly to width/arrow)
public updateColor (): void { const { device, data } = this const linksNumber = data.linksNumber ?? 0 - const colorData = data.linkColors ?? new Float32Array(linksNumber * 4).fill(0) + const expected = linksNumber * 4 + const colorData = + data.linkColors && data.linkColors.length === expected + ? data.linkColors + : new Float32Array(expected).fill(0)Also applies to: 515-542
src/modules/Clusters/index.ts (1)
396-401: GatevelocityFbocheck on!renderPassfor consistency.Line 401 returns early when
velocityFbois missing/destroyed, but this resource is only used in the fallback branch (lines 427-433) when no externalrenderPassis provided. This unnecessarily blocks rendering when a validrenderPassis supplied.🔧 Suggested fix
if (!this.clusterTexture || this.clusterTexture.destroyed) return if (!this.centermassTexture || this.centermassTexture.destroyed) return if (!this.clusterPositionsTexture || this.clusterPositionsTexture.destroyed) return if (!this.clusterForceCoefficientTexture || this.clusterForceCoefficientTexture.destroyed) return if (!this.points?.previousPositionTexture || this.points.previousPositionTexture.destroyed) return - if (!this.points?.velocityFbo || this.points.velocityFbo.destroyed) return + if (!renderPass && (!this.points?.velocityFbo || this.points.velocityFbo.destroyed)) return
🧹 Nitpick comments (1)
src/modules/Clusters/index.ts (1)
93-184: Consider extracting repetitive texture creation/update logic.The texture handling pattern (check existence → destroy old → create new → copy data OR just copy data) is repeated for
clusterTexture,clusterPositionsTexture, andclusterForceCoefficientTexture. This adds ~90 lines of similar code.♻️ Example helper to reduce duplication
// Could be added to texture-utils.ts function updateOrRecreateTexture( device: Device, texture: Texture | undefined, options: { width: number; height: number; format: TextureFormat }, data: Float32Array, shouldRecreate: boolean ): Texture { if (!texture || shouldRecreate) { if (texture && !texture.destroyed) { texture.destroy() } texture = device.createTexture({ ...options, usage: Texture.SAMPLE | Texture.RENDER | Texture.COPY_DST, }) } texture.copyImageData({ data, bytesPerRow: getBytesPerRow(options.format, options.width), mipLevel: 0, x: 0, y: 0, }) return texture }
| private validateDevice (device: Device): NonNullable<Device['canvasContext']> { | ||
| const deviceCanvasContext = device.canvasContext | ||
| // Cosmos requires an HTMLCanvasElement canvas context. | ||
| // OffscreenCanvas and compute-only devices are not supported. | ||
| if (deviceCanvasContext === null || deviceCanvasContext.type === 'offscreen-canvas') { | ||
| throw new Error('Device must have an HTMLCanvasElement canvas context. OffscreenCanvas and compute-only devices are not supported.') | ||
| } | ||
| return deviceCanvasContext | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the file structure and read the relevant section
cd /tmp/repo 2>/dev/null || cd - > /dev/null 2>&1
wc -l src/index.tsRepository: cosmosgl/graph
Length of output: 75
🏁 Script executed:
# Read the validateDevice method and surrounding context
sed -n '1440,1470p' src/index.tsRepository: cosmosgl/graph
Length of output: 1428
🏁 Script executed:
# Search for WebGLDevice references
rg "WebGLDevice" src/index.ts | head -20Repository: cosmosgl/graph
Length of output: 360
🏁 Script executed:
# Search for WebGL-specific APIs mentioned in the comment
rg "readPixelsToArrayWebGL|\.gl\." src/index.ts | head -20Repository: cosmosgl/graph
Length of output: 300
🏁 Script executed:
# Check Device type definition
rg "type Device" src/index.ts -A 5 | head -30Repository: cosmosgl/graph
Length of output: 40
🏁 Script executed:
# Search for devicePromise usage and where it's created/resolved
rg "devicePromise" src/index.ts -B 2 -A 2 | head -40Repository: cosmosgl/graph
Length of output: 338
🏁 Script executed:
# Let's look for where validateDevice is called
rg "validateDevice" src/index.ts -B 2 -A 2Repository: cosmosgl/graph
Length of output: 527
🏁 Script executed:
# Check if Device type is a union and what it could be
rg "from '@luma.gl" src/index.ts | head -20Repository: cosmosgl/graph
Length of output: 215
🏁 Script executed:
# Search for WebGL API usage more broadly - look for .gl access and WebGL calls
rg "\.gl\." src/index.ts -B 1 -A 1Repository: cosmosgl/graph
Length of output: 556
🏁 Script executed:
# Check what device types luma.gl supports - look for device creation patterns
rg "createDevice|Device" src/index.ts -B 1 | head -40Repository: cosmosgl/graph
Length of output: 1564
🏁 Script executed:
# Check if there's any type checking on device parameter
rg "typeof device|instanceof|device as WebGLDevice" src/index.tsRepository: cosmosgl/graph
Length of output: 300
🏁 Script executed:
# Let's check if there are other WebGL-specific usages throughout the file
rg "WebGLDevice|\.gl\." src/index.ts | wc -lRepository: cosmosgl/graph
Length of output: 59
🏁 Script executed:
# See full context of how createDevice works
sed -n '1455,1485p' src/index.tsRepository: cosmosgl/graph
Length of output: 1064
🏁 Script executed:
# Check the type of device passed and if there's a type constraint elsewhere
rg "Device\>" src/index.ts | grep -E "type.*Device|Device.*=" | head -10Repository: cosmosgl/graph
Length of output: 138
🏁 Script executed:
# Let's verify if WebGLDevice is available for instanceof check at runtime
rg "WebGLDevice" src/index.tsRepository: cosmosgl/graph
Length of output: 360
🏁 Script executed:
# Look for any render or initialization code that might assume WebGL
rg "render|submit" src/index.ts -B 1 | head -30Repository: cosmosgl/graph
Length of output: 1136
🏁 Script executed:
# Let's look at the render method to see if there are more WebGL dependencies
sed -n '1750,1850p' src/index.tsRepository: cosmosgl/graph
Length of output: 3562
🏁 Script executed:
# Check all usages of 'device' variable to see if WebGL operations are common
rg "device\." src/index.ts | grep -v "device\.canvasContext" | head -20Repository: cosmosgl/graph
Length of output: 550
🏁 Script executed:
# Let's see if there's any documentation or type definition about Device
rg "interface Device|type Device" src/index.ts -A 5Repository: cosmosgl/graph
Length of output: 40
🏁 Script executed:
# Check if there's any handling of different device types
rg "webgpu|webgl|device" src/index.ts -i | grep -i type | head -20Repository: cosmosgl/graph
Length of output: 169
🏁 Script executed:
# Let's search for render method implementation
sed -n '1650,1750p' src/index.tsRepository: cosmosgl/graph
Length of output: 3220
🏁 Script executed:
# Let's check the readPixels function
rg "readPixels" src/index.ts -B 2 -A 2Repository: cosmosgl/graph
Length of output: 2681
🏁 Script executed:
# Let's check imports for readPixels
rg "import.*readPixels" src/index.tsRepository: cosmosgl/graph
Length of output: 129
🏁 Script executed:
# Check what type Device actually is and if it's a union or interface
rg "Device.*from" src/index.tsRepository: cosmosgl/graph
Length of output: 175
🏁 Script executed:
# Let's look at the full initialization code to understand the devicePromise flow
sed -n '1350,1420p' src/index.tsRepository: cosmosgl/graph
Length of output: 2945
🏁 Script executed:
# Let's check the readPixels helper function to see if it has WebGL dependencies
rg -A 10 "export.*readPixels|function readPixels" src/graph/helper*Repository: cosmosgl/graph
Length of output: 115
🏁 Script executed:
# Check if there are any type guards or instanceof checks elsewhere
rg "instanceof.*Device|typeof.*device.*webgl" src/index.tsRepository: cosmosgl/graph
Length of output: 40
🏁 Script executed:
# Let's see what luma.gl Device actually exports and if there's a base Device class
rg "class Device|export.*Device" src/index.tsRepository: cosmosgl/graph
Length of output: 40
🏁 Script executed:
# Check the complete flow: who can pass devicePromise and what guarantees exist
sed -n '1320,1360p' src/index.tsRepository: cosmosgl/graph
Length of output: 1320
🏁 Script executed:
# Find the helper file location
fd "helper" src/Repository: cosmosgl/graph
Length of output: 71
🏁 Script executed:
# Now check readPixels implementation
find src -name "helper*" -type fRepository: cosmosgl/graph
Length of output: 71
🏁 Script executed:
# List files in src/graph directory
ls -la src/graph/Repository: cosmosgl/graph
Length of output: 220
🏁 Script executed:
# Check readPixels implementation in helper.ts
rg -A 15 "export.*readPixels|function readPixels" src/helper.tsRepository: cosmosgl/graph
Length of output: 649
🏁 Script executed:
# Check if readPixels or any WebGL operations are generic or device-specific
cat src/helper.ts | grep -A 20 "readPixels"Repository: cosmosgl/graph
Length of output: 1897
🏁 Script executed:
# Let's look at all imports in helper.ts to understand dependencies
head -30 src/helper.tsRepository: cosmosgl/graph
Length of output: 1239
Add runtime check to validate devices are WebGL-capable.
The readPixels helper calls device.readPixelsToArrayWebGL(), and the code accesses (device as WebGLDevice).gl for point size queries. If an external caller passes a non-WebGL device via devicePromise, this will fail at runtime. The internal createDevice() always creates a WebGL device, but external devices bypass this guarantee.
Add a runtime check in validateDevice:
Suggested fix
private validateDevice (device: Device): NonNullable<Device['canvasContext']> {
const deviceCanvasContext = device.canvasContext
+ if (!(device instanceof WebGLDevice)) {
+ throw new Error('Device must be a WebGLDevice.')
+ }
// Cosmos requires an HTMLCanvasElement canvas context.
// OffscreenCanvas and compute-only devices are not supported.
if (deviceCanvasContext === null || deviceCanvasContext.type === 'offscreen-canvas') {
throw new Error('Device must have an HTMLCanvasElement canvas context. OffscreenCanvas and compute-only devices are not supported.')
}
return deviceCanvasContext
}🤖 Prompt for AI Agents
In `@src/index.ts` around lines 1446 - 1454, validateDevice currently only checks
for non-null HTMLCanvasElement context but doesn't ensure the device is
WebGL-capable, which causes runtime failures when later code calls
device.readPixelsToArrayWebGL() or accesses (device as WebGLDevice).gl; update
validateDevice to assert the device is a WebGLDevice by checking for the
WebGL-specific surface/GL properties (e.g., presence of readPixelsToArrayWebGL
method and a gl property or canvasContext.type indicating 'html-canvas' with
WebGL support) and throw a clear Error if not; reference validateDevice,
WebGLDevice, readPixelsToArrayWebGL, and any uses of (device as WebGLDevice).gl
to locate and change the guard so external devices are validated at runtime.
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/stories/2. configuration.mdx (1)
82-85: Missing trailing pipe character breaks table formatting.Line 82 is missing the closing
|at the end of the row, which will break the markdown table rendering.📝 Proposed fix
-| simulationRepulsionFromMouse | Repulsion from the mouse pointer force coefficient. The repulsion force is activated by pressing the right mouse button. | 0.0 – 5.0 | `2.0` +| simulationRepulsionFromMouse | Repulsion from the mouse pointer force coefficient. The repulsion force is activated by pressing the right mouse button. | 0.0 – 5.0 | `2.0` |src/index.ts (1)
1266-1276: Adddevice.submit()after running the simulation stepWhen
step()is called manually outside the render loop, GPU commands fromrunSimulationStep(true)are queued but never submitted. Modulerun()methods andpoints.updatePosition()create and end render passes without callingsubmit(). Only theframe()method callsdevice.submit()at the end. For manualstep()calls to execute GPU work, add submission:Fix
public step (): void { if (this._isDestroyed) return if (this.ensureDevice(() => this.step())) return if (!this.config.enableSimulation) return if (!this.store.pointsTextureSize) return // Run one simulation step, forcing execution regardless of isSimulationRunning this.runSimulationStep(true) + this.device?.submit() }
🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 33-35: The constructor currently accepts an optional devicePromise
but the class always destroys the resolved Device; add an ownership flag (e.g.,
this.ownsDevice) set true only when Graph creates the device (the path that
constructs/awaits a new Device) and false when a devicePromise is supplied, then
change the initialization callback that currently destroys the Device when
this._isDestroyed is true (referencing device, deviceInitPromise,
this._isDestroyed) to only call device.destroy() if this.ownsDevice is true, and
similarly update the destroy method (the class destroy function) to
conditionally call device.destroy() only when this.ownsDevice is true so
externally supplied/shared devices are not torn down.
♻️ Duplicate comments (2)
src/index.ts (2)
690-697: Clear passes are never submitted (render/destroy)Line 690-697 and Line 1331-1338 end render passes without
device.submit(). luma.gl requires a submit to flush commands; otherwise the clear may not execute. This was raised earlier and still looks unresolved.✅ Add submit after clear passes
if (this.device) { const clearPass = this.device.beginRenderPass({ clearColor: this.store.backgroundColor, clearDepth: 1, clearStencil: 0, }) clearPass.end() + this.device.submit() }if (this.device) { const clearPass = this.device.beginRenderPass({ clearColor: this.store.backgroundColor, clearDepth: 1, clearStencil: 0, }) clearPass.end() + this.device.submit() this.device.destroy() }Also applies to: 1331-1339
1452-1459: Guard against non-WebGL devicesLine 1452-1457 only validates the canvas context, but the class later uses WebGL-only APIs (e.g.,
readPixelsToArrayWebGL,(device as WebGLDevice).gl). If an external non-WebGL Device is passed, this will crash at runtime. This was raised earlier and still looks unresolved.🛡️ Suggested runtime guard
private validateDevice (device: Device): NonNullable<Device['canvasContext']> { const deviceCanvasContext = device.canvasContext + if (!(device instanceof WebGLDevice)) { + throw new Error('Device must be a WebGLDevice.') + } if (deviceCanvasContext === null || deviceCanvasContext.type === 'offscreen-canvas') { throw new Error('Device must have an HTMLCanvasElement canvas context. OffscreenCanvas and compute-only devices are not supported.') } return deviceCanvasContext }
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/modules/Clusters/index.ts (1)
48-68: GuardpointsTextureSizebefore allocating arrays/textures.
Line 49 usespointsTextureSizefor array sizes and texture dimensions without checking it’s defined; this can throw (NaN sizes) ifcreate()runs before points are initialized.🛠️ Proposed fix
public create (): void { const { device, store, data } = this const { pointsTextureSize } = store + if (!pointsTextureSize) return if (data.pointsNumber === undefined || (!data.pointClusters && !data.clusterPositions)) return
🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Line 729: The code calls readPixels(this.device,
this.points.currentPositionFbo) which uses the WebGL-only API
device.readPixelsToArrayWebGL(); update validateDevice() to explicitly assert
the device is a WebGL-capable device (e.g., check for the presence of
readPixelsToArrayWebGL or a WebGL-specific type flag) and throw a clear error if
not, so functions like readPixels and operations referencing
points.currentPositionFbo fail fast with a descriptive message rather than at
runtime.
♻️ Duplicate comments (7)
src/modules/ForceManyBody/index.ts (1)
87-91: Fix destruction order: destroy framebuffer before texture.Lines 89-90 destroy the texture before the framebuffer, but luma.gl requires framebuffers to be destroyed before their attached textures to avoid use-after-free. The same issue exists at lines 117-118. The
destroy()method correctly documents this order.Proposed fix
// Destroy old resources if size changed if (existingTarget) { - if (!existingTarget.texture.destroyed) existingTarget.texture.destroy() - if (!existingTarget.fbo.destroyed) existingTarget.fbo.destroy() + if (!existingTarget.fbo.destroyed) existingTarget.fbo.destroy() + if (!existingTarget.texture.destroyed) existingTarget.texture.destroy() }Also fix lines 117-118:
for (const [level, target] of Array.from(this.levelTargets.entries())) { if (level >= this.levels) { - if (!target.texture.destroyed) target.texture.destroy() - if (!target.fbo.destroyed) target.fbo.destroy() + if (!target.fbo.destroyed) target.fbo.destroy() + if (!target.texture.destroyed) target.texture.destroy() this.levelTargets.delete(level) } }src/index.ts (5)
1452-1460: Insufficient device type validation.As noted earlier, this validation only checks canvas context type but doesn't verify the device is a
WebGLDevice, which is required for WebGL-specific operations used elsewhere (e.g.,.gl.getParameter(),readPixelsToArrayWebGL()).
690-697: Missingdevice.submit()after clear pass.The luma.gl pipeline requires calling
device.submit()afterrenderPass.end()to flush GPU commands. TherenderFrame()method correctly follows this pattern (line 1654), but this clear pass does not, so the clear may not execute.🔧 Proposed fix
if (this.device) { const clearPass = this.device.beginRenderPass({ clearColor: this.store.backgroundColor, clearDepth: 1, clearStencil: 0, }) clearPass.end() + this.device.submit() }
1331-1340: Missingdevice.submit()beforedevice.destroy().Similar to the
render()method, the clear pass here ends without callingdevice.submit(). While the subsequentdevice.destroy()might implicitly flush pending work, explicitly callingsubmit()ensures the clear operation completes before destruction.🔧 Proposed fix
if (this.device) { // Clears the canvas after particle system is destroyed const clearPass = this.device.beginRenderPass({ clearColor: this.store.backgroundColor, clearDepth: 1, clearStencil: 0, }) clearPass.end() + this.device.submit() this.device.destroy() }
197-198: WebGLDevice cast without runtime validation.The code casts
devicetoWebGLDeviceto access.gl.getParameter(), butvalidateDevice()only checks for HTMLCanvasElement context, not whether the device is actually a WebGLDevice. If an external caller provides a WebGPU device viadevicePromise, this cast will fail at runtime.🛠️ Add WebGLDevice check in validateDevice
private validateDevice (device: Device): NonNullable<Device['canvasContext']> { const deviceCanvasContext = device.canvasContext + if (!(device instanceof WebGLDevice)) { + throw new Error('Cosmos requires a WebGLDevice. WebGPU devices are not supported.') + } // Cosmos requires an HTMLCanvasElement canvas context. // OffscreenCanvas and compute-only devices are not supported. if (deviceCanvasContext === null || deviceCanvasContext.type === 'offscreen-canvas') { throw new Error('Device must have an HTMLCanvasElement canvas context. OffscreenCanvas and compute-only devices are not supported.') } return deviceCanvasContext }
86-104: External device ownership not tracked — may destroy shared devices.The constructor accepts an optional
devicePromise(line 89), but there's no tracking of whether the device was externally supplied. Both the initialization callback (line 102) anddestroy()method (line 1339) unconditionally calldevice.destroy(), which can tear down a shared device the caller still needs.🛠️ Suggested ownership guard
class Graph { private device: Device | undefined private deviceInitPromise: Promise<Device> + private ownsDevice = true public constructor ( div: HTMLDivElement, config?: GraphConfigInterface, devicePromise?: Promise<Device> ) { if (config) this.config.init(config) if (devicePromise) { this.deviceInitPromise = devicePromise + this.ownsDevice = false } else { const canvas = document.createElement('canvas') this.deviceInitPromise = this.createDevice(canvas) } this.deviceInitPromise.then(device => { if (this._isDestroyed) { - device.destroy() + if (this.ownsDevice) device.destroy() return device } // ... }) }Then in
destroy():if (this.device) { const clearPass = this.device.beginRenderPass({ ... }) clearPass.end() - this.device.destroy() + if (this.ownsDevice) this.device.destroy() }src/modules/Clusters/index.ts (1)
384-402: Verify size-stability inrun().
IfpointsTextureSize,clustersTextureSize, orclusterCountcan change betweencreate()andrun(), consider an early-return guard to avoid using stale textures.
🧹 Nitpick comments (2)
src/modules/ForceMouse/index.ts (1)
92-111: Consider consistent destroyed-state checks indestroy().The
vertexCoordBufferdestruction includes a!this.vertexCoordBuffer.destroyedguard (line 108), but this pattern isn't applied torunCommandoruniformStore. For defensive consistency against double-destroy scenarios, consider applying the same pattern to all resources:♻️ Suggested consistent pattern
public destroy (): void { // 1. Destroy Models FIRST (they destroy _gpuGeometry if exists, and _uniformStore) - this.runCommand?.destroy() + if (this.runCommand && !this.runCommand.destroyed) { + this.runCommand.destroy() + } this.runCommand = undefined // ... // 4. Destroy UniformStores (Models already destroyed their managed uniform buffers) - this.uniformStore?.destroy() + if (this.uniformStore && !this.uniformStore.destroyed) { + this.uniformStore.destroy() + } this.uniformStore = undefined // 5. Destroy Buffers (passed via attributes - NOT owned by Models, must destroy manually) if (this.vertexCoordBuffer && !this.vertexCoordBuffer.destroyed) { this.vertexCoordBuffer.destroy() } this.vertexCoordBuffer = undefined }src/modules/ForceManyBody/index.ts (1)
252-254: Consider adding explicit usage flags for consistency.The
forceVertexCoordBufferis created without explicitusageflags, unlikepointIndicesat line 162-165 which specifiesusage: Buffer.VERTEX | Buffer.COPY_DST. While luma.gl may use sensible defaults, adding explicit flags improves consistency and clarity.Suggested change
this.forceVertexCoordBuffer ||= device.createBuffer({ data: new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), + usage: Buffer.VERTEX, })
| if (!this.device || !this.points || !this.canvasD3Selection) return | ||
| const { store: { screenSize } } = this | ||
| const positionPixels = readPixels(this.reglInstance, this.points.currentPositionFbo as regl.Framebuffer2D) | ||
| const positionPixels = readPixels(this.device, this.points.currentPositionFbo as Framebuffer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat src/helper.ts | grep -A 10 "export function readPixels"Repository: cosmosgl/graph
Length of output: 447
🏁 Script executed:
# Check if readPixelsToArrayWebGL is device-specific or universal
rg "readPixelsToArrayWebGL" --type tsRepository: cosmosgl/graph
Length of output: 318
🏁 Script executed:
# Search for Device type definition and validateDevice function
rg "validateDevice|interface Device|type Device" --type ts -A 3Repository: cosmosgl/graph
Length of output: 606
🏁 Script executed:
# Get full validateDevice implementation
sed -n '729,850p' src/index.ts | head -50Repository: cosmosgl/graph
Length of output: 1906
🏁 Script executed:
# Check imports to understand Device type source
head -30 src/index.ts | grep -E "import.*Device|from.*luma"Repository: cosmosgl/graph
Length of output: 215
🏁 Script executed:
# Search for where device is created or assigned
rg "this\.device\s*=" --type ts -B 2 -A 2Repository: cosmosgl/graph
Length of output: 479
🏁 Script executed:
# Check if Device type has readPixelsToArrayWebGL method defined
rg "readPixelsToArrayWebGL" --type ts -B 5Repository: cosmosgl/graph
Length of output: 975
🏁 Script executed:
# Search for device creation and initialization in the codebase
rg "new WebGLDevice|new Device" --type ts -B 2 -A 2Repository: cosmosgl/graph
Length of output: 40
🏁 Script executed:
# Check if Device interface or type is extended/checked elsewhere
rg "instanceof.*Device|as WebGLDevice|WebGLDevice" --type tsRepository: cosmosgl/graph
Length of output: 399
🏁 Script executed:
# Get full validateDevice implementation to understand scope of validation
rg "private validateDevice" --type ts -A 20Repository: cosmosgl/graph
Length of output: 1187
🏁 Script executed:
# Check what the review comment context might be referring to by looking at surrounding code
sed -n '700,750p' src/index.tsRepository: cosmosgl/graph
Length of output: 2599
readPixels uses WebGL-specific API that should be protected by early device validation.
The readPixels helper calls device.readPixelsToArrayWebGL() (a WebGL-specific method, now deprecated in luma.gl v9). The method is not type-safe on the generic Device type, yet validateDevice() only checks canvasContext and doesn't validate the device is WebGL-capable. Add an explicit WebGL device type check in validateDevice() to ensure this and other WebGL-dependent operations fail fast with a clear error rather than at runtime.
🤖 Prompt for AI Agents
In `@src/index.ts` at line 729, The code calls readPixels(this.device,
this.points.currentPositionFbo) which uses the WebGL-only API
device.readPixelsToArrayWebGL(); update validateDevice() to explicitly assert
the device is a WebGL-capable device (e.g., check for the presence of
readPixelsToArrayWebGL or a WebGL-specific type flag) and throw a clear error if
not, so functions like readPixels and operations referencing
points.currentPositionFbo fail fast with a descriptive message rather than at
runtime.
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 83-85: Update the three luma.gl dependency versions in
package.json to the published 9.1.9 release: replace "@luma.gl/core": "^9.2.5",
"@luma.gl/engine": "^9.2.5", and "@luma.gl/webgl": "^9.2.5" with
"@luma.gl/core": "^9.1.9", "@luma.gl/engine": "^9.1.9", and "@luma.gl/webgl":
"^9.1.9" respectively so npm can resolve the packages; ensure the changes are
applied to the dependency entries that reference these package names.
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/index.ts (1)
1292-1354: Device cleanup logic is correct, but missingdevice.submit()before destroy.The ownership check with
shouldDestroyDeviceis properly implemented. However, the clear pass at lines 1346-1351 is missing thedevice.submit()call beforedevice.destroy().🔧 Proposed fix
if (this.shouldDestroyDevice) { // Clears the canvas after particle system is destroyed const clearPass = this.device.beginRenderPass({ clearColor: this.store.backgroundColor, clearDepth: 1, clearStencil: 0, }) clearPass.end() + this.device.submit() this.device.destroy() }
🤖 Fix all issues with AI agents
In `@src/stories/beginners/pinned-points/index.ts`:
- Around line 71-76: The destroy function currently destroys the device directly
via devicePromise.then(device => device.destroy()), but must first call
graph.destroy() to let the Graph release GPU resources; update the destroy
function to wait for devicePromise, call graph.destroy() (or graph?.destroy())
before calling device.destroy(), and ensure any promise ordering handles errors
so Graph cleanup runs prior to device.destroy().
♻️ Duplicate comments (2)
src/index.ts (2)
701-708: Missingdevice.submit()after the clear render pass.The luma.gl command pipeline requires calling
device.submit()afterrenderPass.end()to flush commands to the GPU. TherenderFrame()method at line 1669 correctly follows this pattern, but this clear pass inrender()is missing the submit call.🔧 Proposed fix
if (this.device) { const clearPass = this.device.beginRenderPass({ clearColor: this.store.backgroundColor, clearDepth: 1, clearStencil: 0, }) clearPass.end() + this.device.submit() }
1467-1475: Add runtime check to validate devices are WebGL-capable.The
readPixelshelper (insrc/helper.ts) callsdevice.readPixelsToArrayWebGL(), and line 208 casts toWebGLDeviceto access.gl. If an external caller passes a non-WebGL device viadevicePromise, these will fail at runtime. The internalcreateDevice()always creates a WebGL device, but external devices bypass this guarantee.🔧 Suggested fix to add WebGL validation
private validateDevice (device: Device): NonNullable<Device['canvasContext']> { const deviceCanvasContext = device.canvasContext + // Cosmos requires a WebGL device for readPixels and GL parameter queries + if (!(device instanceof WebGLDevice)) { + throw new Error('Device must be a WebGLDevice. WebGPU devices are not currently supported.') + } // Cosmos requires an HTMLCanvasElement canvas context. // OffscreenCanvas and compute-only devices are not supported. if (deviceCanvasContext === null || deviceCanvasContext.type === 'offscreen-canvas') { throw new Error('Device must have an HTMLCanvasElement canvas context. OffscreenCanvas and compute-only devices are not supported.') } return deviceCanvasContext }
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
package.json (1)
83-98: Add missing peer dependency@luma.gl/shadertools.
@luma.gl/engine@9.2.6declares@luma.gl/shadertools@~9.2.0as a peer dependency, but it is not listed inpackage.json. Add"@luma.gl/shadertools": "^9.2.6"to dependencies to satisfy the peer requirement and avoid runtime issues.
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 1836-1859: findHoveredItem currently skips detection when the
mouse hasn’t moved, which lets hover state stick while the scene animates;
update findHoveredItem to also detect scene movement and bypass the
MIN_MOUSE_MOVEMENT_THRESHOLD in that case by: add a sceneMoved check (e.g.
compare current scene/viewport transform/scale/translation — introduce/compare
this._viewportTransform or this._sceneTransform with a
this._lastCheckedViewportTransform/_lastCheckedSceneTransform), treat sceneMoved
as equivalent to mouseMoved in the early return condition, and after detection
update the last-checked scene transform fields and reset
this._shouldForceHoverDetection as you already do; ensure
hoveredPoint/hoveredLinkIndex get recomputed when sceneMoved so
mouseover/mouseout callbacks fire correctly.
In `@src/modules/Store/index.ts`:
- Around line 201-217: The adjustSpaceSize method uses webglMaxTextureSize
without validating it, which can produce NaN for this.adjustedSpaceSize; add a
guard in adjustSpaceSize to ensure webglMaxTextureSize is a finite number > 0
(e.g., Number.isFinite(webglMaxTextureSize) && webglMaxTextureSize > 0) and if
not, set a safe fallback (use defaultConfigValues.spaceSize or minSpaceSize)
before applying the WebGL-limits branch; update the branch that computes
this.adjustedSpaceSize (and its warning message) to use the validated/fallback
value so scale calculations downstream cannot become NaN.
| private findHoveredItem (): void { | ||
| if (this._isDestroyed || !this._isMouseOnCanvas || !this.reglInstance) return | ||
| if (this._isDestroyed || !this._isMouseOnCanvas) return | ||
| if (this._findHoveredItemExecutionCount < MAX_HOVER_DETECTION_DELAY) { | ||
| this._findHoveredItemExecutionCount += 1 | ||
| return | ||
| } | ||
|
|
||
| // Check if mouse has moved significantly since last hover detection | ||
| const deltaX = Math.abs(this._lastMouseX - this._lastCheckedMouseX) | ||
| const deltaY = Math.abs(this._lastMouseY - this._lastCheckedMouseY) | ||
| const mouseMoved = deltaX > MIN_MOUSE_MOVEMENT_THRESHOLD || deltaY > MIN_MOUSE_MOVEMENT_THRESHOLD | ||
|
|
||
| // Skip if mouse hasn't moved AND not forced | ||
| if (!mouseMoved && !this._shouldForceHoverDetection) { | ||
| return | ||
| } | ||
|
|
||
| // Update last checked position | ||
| this._lastCheckedMouseX = this._lastMouseX | ||
| this._lastCheckedMouseY = this._lastMouseY | ||
|
|
||
| // Reset force flag after use | ||
| this._shouldForceHoverDetection = false | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hover state can become stale while the scene moves.
With the movement threshold, hover detection is skipped whenever the mouse is stationary, even if points are moving (simulation, zoom animation, right‑click repulsion). This can leave hoveredPoint/hoveredLinkIndex “stuck” and prevent mouseout/mouseover callbacks from firing correctly.
Consider bypassing the threshold when the scene is moving, or forcing hover detection on those frames.
💡 Suggested tweak
- // Skip if mouse hasn't moved AND not forced
- if (!mouseMoved && !this._shouldForceHoverDetection) {
+ const isSceneMoving =
+ this.store.isSimulationRunning ||
+ this.zoomInstance.isRunning ||
+ this.isRightClickMouse
+
+ // Skip if mouse hasn't moved AND not forced AND scene is stable
+ if (!mouseMoved && !this._shouldForceHoverDetection && !isSceneMoving) {
return
}🤖 Prompt for AI Agents
In `@src/index.ts` around lines 1836 - 1859, findHoveredItem currently skips
detection when the mouse hasn’t moved, which lets hover state stick while the
scene animates; update findHoveredItem to also detect scene movement and bypass
the MIN_MOUSE_MOVEMENT_THRESHOLD in that case by: add a sceneMoved check (e.g.
compare current scene/viewport transform/scale/translation — introduce/compare
this._viewportTransform or this._sceneTransform with a
this._lastCheckedViewportTransform/_lastCheckedSceneTransform), treat sceneMoved
as equivalent to mouseMoved in the early return condition, and after detection
update the last-checked scene transform fields and reset
this._shouldForceHoverDetection as you already do; ensure
hoveredPoint/hoveredLinkIndex get recomputed when sceneMoved so
mouseover/mouseout callbacks fire correctly.
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/modules/Store/index.ts`:
- Around line 201-224: The webglMaxTextureSize handling in adjustSpaceSize can
be smaller than minSpaceSize (2) causing adjustedSpaceSize to exceed the WebGL
limit; update the validation to treat webglMaxTextureSize < minSpaceSize as
invalid and skip the WebGL-limit branch. In adjustSpaceSize, either change the
existing check for webglMaxTextureSize (currently using Number.isFinite(...) ||
<= 0) to require webglMaxTextureSize >= minSpaceSize, or add an explicit branch
that warns when webglMaxTextureSize < minSpaceSize and sets
this.adjustedSpaceSize = configSpaceSize (and returns) so the later clamp using
webglMaxTextureSize never produces a value larger than the WebGL limit.
- Around line 27-32: The Mat4Array type alias is used as the return type of the
public getter transformationMatrix4x4 but is not exported, causing "private name
in public API" errors; fix this by exporting the type alias (make Mat4Array
exported) so the public API references an exported type (ensure the declaration
export applies to the Mat4Array type alias).
…ace size check Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Signed-off-by: Stukova Olya <stukova.o@gmail.com>
Changes
reglwithluma.gl/coreandluma.gl/engineregl.Framebuffer2D, Texture2D, Bufferwith luma.glFramebuffer, Texture, BufferModelOf course nothing builds at this point, additional passes will be needed.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Removed
✏️ Tip: You can customize this high-level summary in your review settings.