From 655795fdac4e8a2e20aff3805933d34fcc91153b Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 5 May 2026 11:46:11 +0200 Subject: [PATCH 01/11] debug: add visual debugging for overlay and browse interaction --- src/skeleton/frontend.ts | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index b265eb49b..c0cd911d0 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -185,6 +185,9 @@ import { import { defineVertexId, VertexIdHelper } from "#src/webgl/vertex_id.js"; import type { RPC } from "#src/worker_rpc.js"; +const DEBUG_SPATIAL_SKELETON_OVERLAY = true; +const DEBUG_EXCLUDED_SEGMENTS = true; + const tempMat2 = mat4.create(); const DEFAULT_FRAGMENT_MAIN = `void main() { emitDefault(); @@ -325,6 +328,22 @@ class RenderHelper extends RefCounted { } private defineDynamicSegmentAppearance(builder: ShaderBuilder) { + // Show overlay as red, excluded as blue + // if overlay debug is on and excluded debug, assume intention + // is to look for places that the overlay should cover + // the excluded, but does not + const excludedSegmentAlpha = DEBUG_EXCLUDED_SEGMENTS ? "1.0" : "0.0"; + let colorExpression = `return ${this.segmentColorShaderManager.prefix}(segmentId);`; + let alphaExpression = `return isVisible ? uVisibleAlpha : uHiddenAlpha;`; + if (DEBUG_EXCLUDED_SEGMENTS) { + colorExpression = ` + if (${this.excludedSegmentsShaderManager.hasFunctionName}(segmentId)) { + return vec3(0.0, 0.0, 1.0); + } + ${colorExpression} + `; + if (!DEBUG_SPATIAL_SKELETON_OVERLAY) alphaExpression = `return 0.0;`; + } this.visibleSegmentsShaderManager.defineShader(builder); this.excludedSegmentsShaderManager.defineShader(builder); this.segmentColorShaderManager.defineShader(builder); @@ -350,17 +369,17 @@ vec3 getSegmentLookupColor(uint64_t segmentId) { if (uUseSegmentDefaultColor != 0u) { return uSegmentDefaultColor; } - return ${this.segmentColorShaderManager.prefix}(segmentId); + ${colorExpression} } float getSegmentLookupAlpha(uint64_t segmentId) { if (${this.excludedSegmentsShaderManager.hasFunctionName}(segmentId)) { - return 0.0; + return ${excludedSegmentAlpha}; } bool isVisible = ${this.visibleSegmentsShaderManager.hasFunctionName}(segmentId); if (uSkipVisibleSegments != 0u && isVisible) { return 0.0; } - return isVisible ? uVisibleAlpha : uHiddenAlpha; + ${alphaExpression} } vec4 getSegmentAppearance(highp uint segmentValue) { uint64_t segmentId = getSegmentAppearanceId(segmentValue); @@ -427,6 +446,12 @@ vec4 getSegmentAppearance(highp uint segmentValue) { segmentDefaultColor[2], ); } + if (DEBUG_SPATIAL_SKELETON_OVERLAY && excludedSegments === undefined) { + // Use a red color for everything in the overlay + // or if there is only a regular skeleton layer + gl.uniform1ui(shader.uniform("uUseSegmentDefaultColor"), 1); + gl.uniform3f(shader.uniform("uSegmentDefaultColor"), 1.0, 0.0, 0.0); + } const segmentStatedColors = colorGroupState.segmentStatedColors; if (segmentStatedColors.size === 0) { From 40ff1cb147c2b2d766ad5e9121b8c42adc63aa4b Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 5 May 2026 12:16:16 +0200 Subject: [PATCH 02/11] perf: small perf, not check excluded segs if no shader --- src/skeleton/frontend.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index c0cd911d0..e18daeb58 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -185,8 +185,8 @@ import { import { defineVertexId, VertexIdHelper } from "#src/webgl/vertex_id.js"; import type { RPC } from "#src/worker_rpc.js"; -const DEBUG_SPATIAL_SKELETON_OVERLAY = true; -const DEBUG_EXCLUDED_SEGMENTS = true; +const DEBUG_SPATIAL_SKELETON_OVERLAY = false; +const DEBUG_EXCLUDED_SEGMENTS = false; const tempMat2 = mat4.create(); const DEFAULT_FRAGMENT_MAIN = `void main() { @@ -3065,7 +3065,6 @@ export class SpatiallyIndexedSkeletonLayer view: SpatiallyIndexedSkeletonView, ) { const { gl } = this; - const excludedSegments = this.getBrowsePassExcludedSegments(); const edgeShaderResult = renderHelper.edgeShaderGetter( renderContext.emitter, ); @@ -3079,6 +3078,7 @@ export class SpatiallyIndexedSkeletonLayer if (edgeShader === null || nodeShader === null) { return; } + const excludedSegments = this.getBrowsePassExcludedSegments(); const { shaderControlState } = this.displayState.skeletonRenderingOptions; edgeShader.bind(); renderHelper.beginLayer(gl, edgeShader, renderContext, modelMatrix); From 62c5822b27f492acb65fe00ab99782fe6b7db76e Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 5 May 2026 12:30:24 +0200 Subject: [PATCH 03/11] typing: clean up setColor type --- src/skeleton/frontend.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index e18daeb58..a32808a5d 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -907,18 +907,10 @@ void emitDefault() { this.vertexIdHelper.enable(); } - setColor(gl: GL, shader: ShaderProgram, color: Float32Array | number[]) { + setColor(gl: GL, shader: ShaderProgram, color: Float32Array) { const a = - (color as Float32Array).length >= 4 - ? (color as Float32Array)[3] - : this.base.displayState.objectAlpha.value; - gl.uniform4f( - shader.uniform("uColor"), - (color as Float32Array)[0], - (color as Float32Array)[1], - (color as Float32Array)[2], - a, - ); + color.length >= 4 ? color[3] : this.base.displayState.objectAlpha.value; + gl.uniform4f(shader.uniform("uColor"), color[0], color[1], color[2], a); } setPickID(gl: GL, shader: ShaderProgram, pickID: number) { @@ -3230,7 +3222,6 @@ export class SpatiallyIndexedSkeletonLayer nodeShaderParameters.parseResult.controls, ); const baseColor = new Float32Array([1, 1, 1, 1]); - edgeShader.bind(); renderHelper.setColor(gl, edgeShader, baseColor); renderHelper.enableDynamicSegmentAppearance( gl, From d9e1b00718ce2626de6801c738c8653695bb8a27 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 5 May 2026 12:40:31 +0200 Subject: [PATCH 04/11] perf: avoid flipping bind back and forth between node/edge shader also reuse float array for color --- src/skeleton/frontend.ts | 57 ++++++++++++++++++---------------------- src/util/geom.ts | 1 + 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index a32808a5d..ac336880c 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -132,7 +132,7 @@ import { DATA_TYPE_SIGNED, DataType } from "#src/util/data_type.js"; import { RefCounted } from "#src/util/disposable.js"; import type { ValueOrError } from "#src/util/error.js"; import { makeValueOrError, valueOrThrow } from "#src/util/error.js"; -import { mat4 } from "#src/util/geom.js"; +import { kOneVec4, mat4, vec4 } from "#src/util/geom.js"; import { verifyFinitePositiveFloat } from "#src/util/json.js"; import * as matrix from "#src/util/matrix.js"; import { getObjectId } from "#src/util/object_id.js"; @@ -907,10 +907,8 @@ void emitDefault() { this.vertexIdHelper.enable(); } - setColor(gl: GL, shader: ShaderProgram, color: Float32Array) { - const a = - color.length >= 4 ? color[3] : this.base.displayState.objectAlpha.value; - gl.uniform4f(shader.uniform("uColor"), color[0], color[1], color[2], a); + setColor(gl: GL, shader: ShaderProgram, color: vec4) { + gl.uniform4fv(shader.uniform("uColor"), color); } setPickID(gl: GL, shader: ShaderProgram, pickID: number) { @@ -1254,9 +1252,9 @@ export class SkeletonLayer extends RefCounted { } if (color !== undefined) { edgeShader.bind(); - renderHelper.setColor(gl, edgeShader, color as Float32Array); + renderHelper.setColor(gl, edgeShader, color); nodeShader.bind(); - renderHelper.setColor(gl, nodeShader, color as Float32Array); + renderHelper.setColor(gl, nodeShader, color); } if (pickIndex !== undefined) { edgeShader.bind(); @@ -3082,6 +3080,18 @@ export class SpatiallyIndexedSkeletonLayer edgeShaderParameters.parseResult.controls, ); gl.uniform1f(edgeShader.uniform("uLineWidth"), lineWidth); + renderHelper.setColor(gl, edgeShader, kOneVec4); + renderHelper.enableDynamicSegmentAppearance( + gl, + edgeShader, + hasRegularSkeletonLayer, + excludedSegments, + ); + if (renderContext.emitPickID) { + renderHelper.setPickID(gl, edgeShader, 0); + renderHelper.setEdgePickInstanceStride(gl, edgeShader, 0); + } + nodeShader.bind(); renderHelper.beginLayer(gl, nodeShader, renderContext, modelMatrix); gl.uniform1f(nodeShader.uniform("uNodeDiameter"), pointDiameter); @@ -3092,17 +3102,7 @@ export class SpatiallyIndexedSkeletonLayer shaderControlState, nodeShaderParameters.parseResult.controls, ); - const baseColor = new Float32Array([1, 1, 1, 1]); - edgeShader.bind(); - renderHelper.setColor(gl, edgeShader, baseColor); - renderHelper.enableDynamicSegmentAppearance( - gl, - edgeShader, - hasRegularSkeletonLayer, - excludedSegments, - ); - nodeShader.bind(); - renderHelper.setColor(gl, nodeShader, baseColor); + renderHelper.setColor(gl, nodeShader, kOneVec4); renderHelper.enableDynamicSegmentAppearance( gl, nodeShader, @@ -3110,10 +3110,6 @@ export class SpatiallyIndexedSkeletonLayer excludedSegments, ); if (renderContext.emitPickID) { - edgeShader.bind(); - renderHelper.setPickID(gl, edgeShader, 0); - renderHelper.setEdgePickInstanceStride(gl, edgeShader, 0); - nodeShader.bind(); renderHelper.setPickID(gl, nodeShader, 0); renderHelper.setNodePickInstanceStride(gl, nodeShader, 0); } @@ -3211,6 +3207,13 @@ export class SpatiallyIndexedSkeletonLayer edgeShaderParameters.parseResult.controls, ); gl.uniform1f(edgeShader.uniform("uLineWidth"), lineWidth); + renderHelper.setColor(gl, edgeShader, kOneVec4); + renderHelper.enableDynamicSegmentAppearance( + gl, + edgeShader, + hasRegularSkeletonLayer, + ); + nodeShader.bind(); renderHelper.beginLayer(gl, nodeShader, renderContext, modelMatrix); gl.uniform1f(nodeShader.uniform("uNodeDiameter"), pointDiameter); @@ -3221,15 +3224,7 @@ export class SpatiallyIndexedSkeletonLayer shaderControlState, nodeShaderParameters.parseResult.controls, ); - const baseColor = new Float32Array([1, 1, 1, 1]); - renderHelper.setColor(gl, edgeShader, baseColor); - renderHelper.enableDynamicSegmentAppearance( - gl, - edgeShader, - hasRegularSkeletonLayer, - ); - nodeShader.bind(); - renderHelper.setColor(gl, nodeShader, baseColor); + renderHelper.setColor(gl, nodeShader, kOneVec4); renderHelper.enableDynamicSegmentAppearance( gl, nodeShader, diff --git a/src/util/geom.ts b/src/util/geom.ts index 8801ee9a7..5c428cc19 100644 --- a/src/util/geom.ts +++ b/src/util/geom.ts @@ -32,6 +32,7 @@ export const kAxes = [ export const kZeroVec = vec3.fromValues(0, 0, 0); export const kZeroVec4 = vec4.fromValues(0, 0, 0, 0); export const kOneVec = vec3.fromValues(1, 1, 1); +export const kOneVec4 = vec4.fromValues(1, 1, 1, 1); export const kInfinityVec = vec3.fromValues(Infinity, Infinity, Infinity); export const kIdentityQuat = quat.create(); From 1fd3a6c1cbaf6adb46f75540fcb056babcb23755 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 5 May 2026 12:51:58 +0200 Subject: [PATCH 05/11] refactor: continue cleaning up draw --- src/skeleton/frontend.ts | 54 +++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index ac336880c..a02a68a50 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -923,7 +923,7 @@ void emitDefault() { gl.uniform1ui(shader.uniform("uPickInstanceStride"), stride); } - drawSkeleton( + drawSkeletons( gl: GL, edgeShader: ShaderProgram, nodeShader: ShaderProgram | null, @@ -1262,7 +1262,7 @@ export class SkeletonLayer extends RefCounted { nodeShader.bind(); renderHelper.setPickID(gl, nodeShader, pickIndex); } - renderHelper.drawSkeleton( + renderHelper.drawSkeletons( gl, edgeShader, nodeShader, @@ -3070,8 +3070,10 @@ export class SpatiallyIndexedSkeletonLayer } const excludedSegments = this.getBrowsePassExcludedSegments(); const { shaderControlState } = this.displayState.skeletonRenderingOptions; + edgeShader.bind(); renderHelper.beginLayer(gl, edgeShader, renderContext, modelMatrix); + gl.uniform1f(edgeShader.uniform("uLineWidth"), lineWidth); renderHelper.setEdgePickInstanceStride(gl, edgeShader, 0); setControlsInShader( gl, @@ -3079,7 +3081,6 @@ export class SpatiallyIndexedSkeletonLayer shaderControlState, edgeShaderParameters.parseResult.controls, ); - gl.uniform1f(edgeShader.uniform("uLineWidth"), lineWidth); renderHelper.setColor(gl, edgeShader, kOneVec4); renderHelper.enableDynamicSegmentAppearance( gl, @@ -3113,6 +3114,7 @@ export class SpatiallyIndexedSkeletonLayer renderHelper.setPickID(gl, nodeShader, 0); renderHelper.setNodePickInstanceStride(gl, nodeShader, 0); } + for (const chunk of this.iterateCandidateChunks( selectedSources, targetLod, @@ -3156,7 +3158,7 @@ export class SpatiallyIndexedSkeletonLayer renderHelper.setPickID(gl, nodeShader, nodePickId); renderHelper.setNodePickInstanceStride(gl, nodeShader, nodePickStride); } - renderHelper.drawSkeleton( + renderHelper.drawSkeletons( gl, edgeShader, nodeShader, @@ -3197,6 +3199,7 @@ export class SpatiallyIndexedSkeletonLayer return; } const { shaderControlState } = this.displayState.skeletonRenderingOptions; + edgeShader.bind(); renderHelper.beginLayer(gl, edgeShader, renderContext, modelMatrix); renderHelper.setEdgePickInstanceStride(gl, edgeShader, 0); @@ -3213,23 +3216,6 @@ export class SpatiallyIndexedSkeletonLayer edgeShader, hasRegularSkeletonLayer, ); - - nodeShader.bind(); - renderHelper.beginLayer(gl, nodeShader, renderContext, modelMatrix); - gl.uniform1f(nodeShader.uniform("uNodeDiameter"), pointDiameter); - renderHelper.setNodePickInstanceStride(gl, nodeShader, 0); - setControlsInShader( - gl, - nodeShader, - shaderControlState, - nodeShaderParameters.parseResult.controls, - ); - renderHelper.setColor(gl, nodeShader, kOneVec4); - renderHelper.enableDynamicSegmentAppearance( - gl, - nodeShader, - hasRegularSkeletonLayer, - ); if (renderContext.emitPickID) { const edgePickId = overlayChunk.numIndices > 0 && @@ -3245,14 +3231,31 @@ export class SpatiallyIndexedSkeletonLayer } satisfies SpatiallyIndexedSkeletonPickData, ) : 0; - edgeShader.bind(); renderHelper.setPickID(gl, edgeShader, edgePickId); renderHelper.setEdgePickInstanceStride( gl, edgeShader, edgePickId === 0 ? 0 : 1, ); - nodeShader.bind(); + } + + nodeShader.bind(); + renderHelper.beginLayer(gl, nodeShader, renderContext, modelMatrix); + gl.uniform1f(nodeShader.uniform("uNodeDiameter"), pointDiameter); + renderHelper.setNodePickInstanceStride(gl, nodeShader, 0); + setControlsInShader( + gl, + nodeShader, + shaderControlState, + nodeShaderParameters.parseResult.controls, + ); + renderHelper.setColor(gl, nodeShader, kOneVec4); + renderHelper.enableDynamicSegmentAppearance( + gl, + nodeShader, + hasRegularSkeletonLayer, + ); + if (renderContext.emitPickID) { const nodePickId = overlayChunk.numVertices > 0 && overlayChunk.pickNodeIds !== undefined && @@ -3277,7 +3280,8 @@ export class SpatiallyIndexedSkeletonLayer nodePickId === 0 ? 0 : 1, ); } - renderHelper.drawSkeleton( + + renderHelper.drawSkeletons( gl, edgeShader, nodeShader, @@ -3305,7 +3309,6 @@ export class SpatiallyIndexedSkeletonLayer lod?: number; }, ) { - const lineWidth = renderOptions.lineWidth.value; const { displayState } = this; if ( displayState.objectAlpha.value <= 0.0 && @@ -3323,6 +3326,7 @@ export class SpatiallyIndexedSkeletonLayer const hasRegularSkeletonLayer = this.updateHasRegularSkeletonLayerWatchable( layer.userLayer, ); + const lineWidth = renderOptions.lineWidth.value; const targetLod = drawOptions?.lod; const view = drawOptions?.view ?? "3d"; const pointDiameter = getSkeletonNodeDiameter( From b39d84bb01451536406c55b84adbfd06d1acc12d Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 5 May 2026 12:56:24 +0200 Subject: [PATCH 06/11] fix: correct lint --- src/skeleton/frontend.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index a02a68a50..0e583d09c 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -132,7 +132,7 @@ import { DATA_TYPE_SIGNED, DataType } from "#src/util/data_type.js"; import { RefCounted } from "#src/util/disposable.js"; import type { ValueOrError } from "#src/util/error.js"; import { makeValueOrError, valueOrThrow } from "#src/util/error.js"; -import { kOneVec4, mat4, vec4 } from "#src/util/geom.js"; +import { kOneVec4, mat4, type vec4 } from "#src/util/geom.js"; import { verifyFinitePositiveFloat } from "#src/util/json.js"; import * as matrix from "#src/util/matrix.js"; import { getObjectId } from "#src/util/object_id.js"; From 20d2d39951836791e926d445e9eaf5ed5e100dd8 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 5 May 2026 17:47:58 +0200 Subject: [PATCH 07/11] feat: remove uneeded skeleton regular source check from rendering previously was used to avoid double rendering with visible segments --- src/skeleton/frontend.ts | 78 ++-------------------------------------- 1 file changed, 2 insertions(+), 76 deletions(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index 559e1051f..637729c76 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -26,7 +26,6 @@ import type { LayerView, MouseSelectionState, PickState, - UserLayer, VisibleLayerInfo, } from "#src/layer/index.js"; import type { PerspectivePanel } from "#src/perspective_view/panel.js"; @@ -349,7 +348,6 @@ class RenderHelper extends RefCounted { builder.addUniform("highp float", "uVisibleAlpha"); builder.addUniform("highp float", "uHiddenAlpha"); builder.addUniform("highp vec3", "uSegmentDefaultColor"); - builder.addUniform("highp uint", "uSkipVisibleSegments"); builder.addUniform("highp uint", "uUseSegmentDefaultColor"); builder.addUniform("highp uint", "uUseSegmentStatedColors"); builder.addFragmentCode(` @@ -374,9 +372,6 @@ float getSegmentLookupAlpha(uint64_t segmentId) { return ${excludedSegmentAlpha}; } bool isVisible = ${this.visibleSegmentsShaderManager.hasFunctionName}(segmentId); - if (uSkipVisibleSegments != 0u && isVisible) { - return 0.0; - } ${alphaExpression} } vec4 getSegmentAppearance(highp uint segmentValue) { @@ -389,7 +384,6 @@ vec4 getSegmentAppearance(highp uint segmentValue) { enableDynamicSegmentAppearance( gl: GL, shader: ShaderProgram, - skipVisibleSegments: boolean, excludedSegments?: Uint64Set, ) { if (!this.dynamicSegmentAppearance) return; @@ -420,10 +414,6 @@ vec4 getSegmentAppearance(highp uint segmentValue) { shader.uniform("uHiddenAlpha"), this.base.displayState.hiddenObjectAlpha.value, ); - gl.uniform1ui( - shader.uniform("uSkipVisibleSegments"), - skipVisibleSegments ? 1 : 0, - ); const colorGroupState = this.base.displayState.segmentationColorGroupState.value; @@ -446,7 +436,6 @@ vec4 getSegmentAppearance(highp uint segmentValue) { } if (DEBUG_SPATIAL_SKELETON_OVERLAY && excludedSegments === undefined) { // Use a red color for everything in the overlay - // or if there is only a regular skeleton layer gl.uniform1ui(shader.uniform("uUseSegmentDefaultColor"), 1); gl.uniform3f(shader.uniform("uSegmentDefaultColor"), 1.0, 0.0, 0.0); } @@ -1944,11 +1933,6 @@ export class SpatiallyIndexedSkeletonLayer segmentTextureFormat, selectedNodeTextureFormat, ]; - private regularSkeletonLayerWatchable = new WatchableValue(false); - private regularSkeletonLayerUserLayer: UserLayer | undefined; - private removeRegularSkeletonLayerUserLayerListener: - | (() => boolean) - | undefined; private visibleChunksByView = new Map< SpatiallyIndexedSkeletonView, VisibleSpatialChunksBySource @@ -2242,44 +2226,6 @@ export class SpatiallyIndexedSkeletonLayer return this.overlayChunk; } - private computeHasRegularSkeletonLayer(userLayer: UserLayer) { - for (const renderLayer of userLayer.renderLayers) { - if ( - renderLayer instanceof PerspectiveViewSkeletonLayer || - renderLayer instanceof SliceViewPanelSkeletonLayer - ) { - return true; - } - } - return false; - } - - private updateHasRegularSkeletonLayerWatchable( - userLayer: UserLayer | undefined, - ) { - if (this.regularSkeletonLayerUserLayer !== userLayer) { - this.removeRegularSkeletonLayerUserLayerListener?.(); - this.removeRegularSkeletonLayerUserLayerListener = undefined; - this.regularSkeletonLayerUserLayer = userLayer; - if (userLayer !== undefined) { - const update = () => { - const nextValue = this.computeHasRegularSkeletonLayer(userLayer); - if (this.regularSkeletonLayerWatchable.value !== nextValue) { - this.regularSkeletonLayerWatchable.value = nextValue; - this.redrawNeeded.dispatch(); - } - }; - update(); - this.removeRegularSkeletonLayerUserLayerListener = - userLayer.layersChanged.add(update); - } else if (this.regularSkeletonLayerWatchable.value) { - this.regularSkeletonLayerWatchable.value = false; - this.redrawNeeded.dispatch(); - } - } - return this.regularSkeletonLayerWatchable.value; - } - private lodMatches( chunk: SpatiallyIndexedSkeletonChunk, targetLod: number | undefined, @@ -2306,9 +2252,6 @@ export class SpatiallyIndexedSkeletonLayer ) { super(); this.registerDisposer(() => { - this.removeRegularSkeletonLayerUserLayerListener?.(); - this.removeRegularSkeletonLayerUserLayerListener = undefined; - this.regularSkeletonLayerUserLayer = undefined; this.disposeOverlayChunk(); }); let sources3d: SpatiallyIndexedSkeletonSourceEntry[]; @@ -2784,7 +2727,6 @@ export class SpatiallyIndexedSkeletonLayer modelMatrix: mat4, lineWidth: number, pointDiameter: number, - hasRegularSkeletonLayer: boolean, selectedSources: readonly SpatiallyIndexedSkeletonSourceEntry[], targetLod: number | undefined, view: SpatiallyIndexedSkeletonView, @@ -2820,7 +2762,6 @@ export class SpatiallyIndexedSkeletonLayer renderHelper.enableDynamicSegmentAppearance( gl, edgeShader, - hasRegularSkeletonLayer, excludedSegments, ); if (renderContext.emitPickID) { @@ -2842,7 +2783,6 @@ export class SpatiallyIndexedSkeletonLayer renderHelper.enableDynamicSegmentAppearance( gl, nodeShader, - hasRegularSkeletonLayer, excludedSegments, ); if (renderContext.emitPickID) { @@ -2913,7 +2853,6 @@ export class SpatiallyIndexedSkeletonLayer modelMatrix: mat4, lineWidth: number, pointDiameter: number, - hasRegularSkeletonLayer: boolean, ) { const overlayChunk = this.resolveSourceBackedOverlayChunk(); if (overlayChunk === undefined) { @@ -2946,11 +2885,7 @@ export class SpatiallyIndexedSkeletonLayer ); gl.uniform1f(edgeShader.uniform("uLineWidth"), lineWidth); renderHelper.setColor(gl, edgeShader, kOneVec4); - renderHelper.enableDynamicSegmentAppearance( - gl, - edgeShader, - hasRegularSkeletonLayer, - ); + renderHelper.enableDynamicSegmentAppearance(gl, edgeShader); if (renderContext.emitPickID) { const edgePickId = overlayChunk.numIndices > 0 && @@ -2985,11 +2920,7 @@ export class SpatiallyIndexedSkeletonLayer nodeShaderParameters.parseResult.controls, ); renderHelper.setColor(gl, nodeShader, kOneVec4); - renderHelper.enableDynamicSegmentAppearance( - gl, - nodeShader, - hasRegularSkeletonLayer, - ); + renderHelper.enableDynamicSegmentAppearance(gl, nodeShader); if (renderContext.emitPickID) { const nodePickId = overlayChunk.numVertices > 0 && @@ -3058,9 +2989,6 @@ export class SpatiallyIndexedSkeletonLayer ); if (modelMatrix === undefined) return; - const hasRegularSkeletonLayer = this.updateHasRegularSkeletonLayerWatchable( - layer.userLayer, - ); const lineWidth = renderOptions.lineWidth.value; const targetLod = drawOptions?.lod; const view = drawOptions?.view ?? "3d"; @@ -3080,7 +3008,6 @@ export class SpatiallyIndexedSkeletonLayer modelMatrix, lineWidth, pointDiameter, - hasRegularSkeletonLayer, selectedSources, targetLod, view, @@ -3092,7 +3019,6 @@ export class SpatiallyIndexedSkeletonLayer modelMatrix, lineWidth, pointDiameter, - hasRegularSkeletonLayer, ); } From 6b63aefd107fc5a15ca0a1c8f9ca39a85e5aa37b Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 5 May 2026 19:25:23 +0200 Subject: [PATCH 08/11] refactor: clean up debug --- src/skeleton/frontend.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index 637729c76..fc6efae10 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -325,13 +325,12 @@ class RenderHelper extends RefCounted { } private defineDynamicSegmentAppearance(builder: ShaderBuilder) { - // Show overlay as red, excluded as blue - // if overlay debug is on and excluded debug, assume intention - // is to look for places that the overlay should cover - // the excluded, but does not - const excludedSegmentAlpha = DEBUG_EXCLUDED_SEGMENTS ? "1.0" : "0.0"; + // Regular path no debugging alpha and color let colorExpression = `return ${this.segmentColorShaderManager.prefix}(segmentId);`; let alphaExpression = `return isVisible ? uVisibleAlpha : uHiddenAlpha;`; + let excludedSegmentAlpha = "0.0"; + + // Override usual alpha and color calculations to enable some debug modes if (DEBUG_EXCLUDED_SEGMENTS) { colorExpression = ` if (${this.excludedSegmentsShaderManager.hasFunctionName}(segmentId)) { @@ -340,7 +339,9 @@ class RenderHelper extends RefCounted { ${colorExpression} `; if (!DEBUG_SPATIAL_SKELETON_OVERLAY) alphaExpression = `return 0.0;`; + excludedSegmentAlpha = "1.0"; } + this.visibleSegmentsShaderManager.defineShader(builder); this.excludedSegmentsShaderManager.defineShader(builder); this.segmentColorShaderManager.defineShader(builder); From 27785785095dedd982d33f969e3864392c9a3624 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 6 May 2026 10:45:38 +0200 Subject: [PATCH 09/11] refactor: clean up chunk usage in FE for browse pass the previous version was leaving some logic in the draw method for filtering chunks. What should instead happen is that the FE filters to only the chunks to draw, and directly gives the chunks to draw to the draw method. The draw method early returns if no chunks, and otherwise just draws them all --- src/skeleton/frontend.ts | 139 ++++++++------------------------------- 1 file changed, 26 insertions(+), 113 deletions(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index fc6efae10..b222665b0 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -1891,11 +1891,6 @@ function updateSpatialSkeletonGridRenderScaleHistogram( } } -type VisibleSpatialChunksBySource = Map< - string, - readonly SpatiallyIndexedSkeletonChunk[] ->; - interface SpatialSkeletonDisplayState { spatialSkeletonGridLevel2d?: WatchableValueInterface; spatialSkeletonGridLevel3d?: WatchableValueInterface; @@ -1934,10 +1929,6 @@ export class SpatiallyIndexedSkeletonLayer segmentTextureFormat, selectedNodeTextureFormat, ]; - private visibleChunksByView = new Map< - SpatiallyIndexedSkeletonView, - VisibleSpatialChunksBySource - >(); gridLevel: WatchableValueInterface; lod: WatchableValueInterface; private selectedNodeId: @@ -2227,16 +2218,6 @@ export class SpatiallyIndexedSkeletonLayer return this.overlayChunk; } - private lodMatches( - chunk: SpatiallyIndexedSkeletonChunk, - targetLod: number | undefined, - ) { - if (targetLod === undefined || chunk.lod === undefined) { - return true; - } - return Math.abs(chunk.lod - targetLod) < 1e-6; - } - sources: SpatiallyIndexedSkeletonSourceEntry[]; sources2d: SpatiallyIndexedSkeletonSourceEntry[]; source: SpatiallyIndexedSkeletonSource; @@ -2440,46 +2421,6 @@ export class SpatiallyIndexedSkeletonLayer }; } - private getVisibleChunksForView(view: SpatiallyIndexedSkeletonView) { - return this.visibleChunksByView.get(view); - } - - private *iterateCandidateChunks( - selectedSources: readonly SpatiallyIndexedSkeletonSourceEntry[], - targetLod: number | undefined, - options: { - view?: SpatiallyIndexedSkeletonView; - } = {}, - ): Iterable { - const visibleChunksBySource = - options.view === undefined - ? undefined - : this.getVisibleChunksForView(options.view); - const useVisibleChunks = visibleChunksBySource !== undefined; - for (const sourceEntry of selectedSources) { - if (useVisibleChunks) { - const visibleChunks = visibleChunksBySource.get( - getObjectId(sourceEntry.chunkSource), - ); - if (visibleChunks === undefined) { - continue; - } - for (const chunk of visibleChunks) { - if (!this.lodMatches(chunk, targetLod)) continue; - if (chunk.state !== ChunkState.GPU_MEMORY) continue; - yield chunk; - } - continue; - } - for (const chunk of sourceEntry.chunkSource.chunks.values()) { - const typedChunk = chunk as SpatiallyIndexedSkeletonChunk; - if (!this.lodMatches(typedChunk, targetLod)) continue; - if (typedChunk.state !== ChunkState.GPU_MEMORY) continue; - yield typedChunk; - } - } - } - invalidateSourceCaches() { let invalidated = false; for (const chunkSource of this.iterateUniqueChunkSources()) { @@ -2563,27 +2504,28 @@ export class SpatiallyIndexedSkeletonLayer }; } - updateVisibleChunksForView( + getVisibleChunksInCurrentViewAndLod( view: SpatiallyIndexedSkeletonView, + gridLevel: number | undefined, transformedSources: readonly TransformedSource[][], projectionParameters: any, lod: number | undefined, - ) { + ): SpatiallyIndexedSkeletonChunk[] { if (lod === undefined) { - this.visibleChunksByView.delete(view); - return; + return []; } + const selectedSourceIds = new Set( + this.selectSourcesForViewAndGrid(view, gridLevel).map((s) => + getObjectId(s.chunkSource), + ), + ); const lodSuffix = `:${lod}`; - const chunksBySource: VisibleSpatialChunksBySource = new Map(); + const result: SpatiallyIndexedSkeletonChunk[] = []; const seenChunkKeysBySource = new Map>(); for (const scales of transformedSources) { for (const tsource of scales) { const sourceId = getObjectId(tsource.source); - let visibleChunks = chunksBySource.get(sourceId); - if (visibleChunks === undefined) { - visibleChunks = []; - chunksBySource.set(sourceId, visibleChunks); - } + if (!selectedSourceIds.has(sourceId)) continue; let seenChunkKeys = seenChunkKeysBySource.get(sourceId); if (seenChunkKeys === undefined) { seenChunkKeys = new Set(); @@ -2595,24 +2537,20 @@ export class SpatiallyIndexedSkeletonLayer tsource, (positionInChunks) => { const chunkKey = `${positionInChunks.join()}${lodSuffix}`; - if (seenChunkKeys!.has(chunkKey)) { - return; - } + if (seenChunkKeys!.has(chunkKey)) return; seenChunkKeys!.add(chunkKey); const chunkSource = tsource.source as SpatiallyIndexedSkeletonSource; const chunk = chunkSource.chunks.get(chunkKey) as | SpatiallyIndexedSkeletonChunk | undefined; - if (chunk?.state !== ChunkState.GPU_MEMORY) { - return; - } - (visibleChunks as SpatiallyIndexedSkeletonChunk[]).push(chunk); + if (chunk?.state !== ChunkState.GPU_MEMORY) return; + result.push(chunk); }, ); } } - this.visibleChunksByView.set(view, chunksBySource); + return result; } private areVisibleChunksReady( @@ -2728,10 +2666,9 @@ export class SpatiallyIndexedSkeletonLayer modelMatrix: mat4, lineWidth: number, pointDiameter: number, - selectedSources: readonly SpatiallyIndexedSkeletonSourceEntry[], - targetLod: number | undefined, - view: SpatiallyIndexedSkeletonView, + visibleChunks: SpatiallyIndexedSkeletonChunk[], ) { + if (visibleChunks.length === 0) return; const { gl } = this; const edgeShaderResult = renderHelper.edgeShaderGetter( renderContext.emitter, @@ -2791,13 +2728,7 @@ export class SpatiallyIndexedSkeletonLayer renderHelper.setNodePickInstanceStride(gl, nodeShader, 0); } - for (const chunk of this.iterateCandidateChunks( - selectedSources, - targetLod, - { - view, - }, - )) { + for (const chunk of visibleChunks) { if (renderContext.emitPickID) { let edgePickId = 0; let edgePickStride = 0; @@ -2970,11 +2901,7 @@ export class SpatiallyIndexedSkeletonLayer LayerView, ThreeDimensionalRenderLayerAttachmentState >, - drawOptions?: { - view?: SpatiallyIndexedSkeletonView; - gridLevel?: number; - lod?: number; - }, + visibleChunks: SpatiallyIndexedSkeletonChunk[], ) { const { displayState } = this; if ( @@ -2991,17 +2918,11 @@ export class SpatiallyIndexedSkeletonLayer if (modelMatrix === undefined) return; const lineWidth = renderOptions.lineWidth.value; - const targetLod = drawOptions?.lod; - const view = drawOptions?.view ?? "3d"; const pointDiameter = getSkeletonNodeDiameter( renderOptions.mode.value, lineWidth, ); - const selectedSources = this.selectSourcesForViewAndGrid( - view, - drawOptions?.gridLevel, - ); this.drawBrowsePass( renderContext, layer, @@ -3009,9 +2930,7 @@ export class SpatiallyIndexedSkeletonLayer modelMatrix, lineWidth, pointDiameter, - selectedSources, - targetLod, - view, + visibleChunks, ); this.drawInspectionOverlayPass( renderContext, @@ -3264,8 +3183,9 @@ export class PerspectiveViewSpatiallyIndexedSkeletonLayer extends PerspectiveVie const displayState = this.base.displayState as SkeletonLayerDisplayState & SpatialSkeletonDisplayState; const lodValue = displayState.skeletonLod?.value; - this.base.updateVisibleChunksForView( + const visibleChunks = this.base.getVisibleChunksInCurrentViewAndLod( "3d", + displayState.spatialSkeletonGridLevel3d?.value, this.transformedSources, renderContext.projectionParameters, lodValue, @@ -3292,11 +3212,7 @@ export class PerspectiveViewSpatiallyIndexedSkeletonLayer extends PerspectiveVie this.browseRenderHelper, this.renderOptions, attachment, - { - view: "3d", - gridLevel: displayState.spatialSkeletonGridLevel3d?.value, - lod: lodValue, - }, + visibleChunks, ); } @@ -3564,8 +3480,9 @@ export class SliceViewPanelSpatiallyIndexedSkeletonLayer extends SliceViewPanelR const displayState = this.base.displayState as SkeletonLayerDisplayState & SpatialSkeletonDisplayState; const lodValue = displayState.spatialSkeletonLod2d?.value; - this.base.updateVisibleChunksForView( + const visibleChunks = this.base.getVisibleChunksInCurrentViewAndLod( "2d", + displayState.spatialSkeletonGridLevel2d?.value, this.transformedSources, renderContext.sliceView.projectionParameters.value, lodValue, @@ -3592,11 +3509,7 @@ export class SliceViewPanelSpatiallyIndexedSkeletonLayer extends SliceViewPanelR this.browseRenderHelper, this.renderOptions, attachment, - { - view: "2d", - gridLevel: displayState.spatialSkeletonGridLevel2d?.value, - lod: lodValue, - }, + visibleChunks, ); } From a1137bdbef166f1c65908e5bcc0073670f3fb77c Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 6 May 2026 11:17:28 +0200 Subject: [PATCH 10/11] refactor: small change to use 3fv not 3f --- src/skeleton/frontend.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index b222665b0..d2839a626 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -428,7 +428,7 @@ vec4 getSegmentAppearance(highp uint segmentValue) { gl.uniform1ui(shader.uniform("uUseSegmentDefaultColor"), 0); } else { gl.uniform1ui(shader.uniform("uUseSegmentDefaultColor"), 1); - gl.uniform3f( + gl.uniform3fv( shader.uniform("uSegmentDefaultColor"), segmentDefaultColor[0], segmentDefaultColor[1], @@ -2894,7 +2894,7 @@ export class SpatiallyIndexedSkeletonLayer draw( renderContext: SliceViewPanelRenderContext | PerspectiveViewRenderContext, layer: RenderLayer, - renderHelper: RenderHelper, + overlayRenderHelper: RenderHelper, browseRenderHelper: RenderHelper, renderOptions: ViewSpecificSkeletonRenderingOptions, attachment: VisibleLayerInfo< @@ -2935,7 +2935,7 @@ export class SpatiallyIndexedSkeletonLayer this.drawInspectionOverlayPass( renderContext, layer, - renderHelper, + overlayRenderHelper, modelMatrix, lineWidth, pointDiameter, From f714b04159620b2e9ef691d20596e3509bae2399 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Thu, 7 May 2026 09:56:13 +0200 Subject: [PATCH 11/11] fix: correct 3fv call to use vec3 --- src/skeleton/frontend.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index d2839a626..ba81a0dd3 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -430,9 +430,7 @@ vec4 getSegmentAppearance(highp uint segmentValue) { gl.uniform1ui(shader.uniform("uUseSegmentDefaultColor"), 1); gl.uniform3fv( shader.uniform("uSegmentDefaultColor"), - segmentDefaultColor[0], - segmentDefaultColor[1], - segmentDefaultColor[2], + segmentDefaultColor, ); } if (DEBUG_SPATIAL_SKELETON_OVERLAY && excludedSegments === undefined) {