Refactor/skeleton frontend#151
Merged
afonsobspinto merged 22 commits intofeature/edit-modefrom May 8, 2026
Merged
Conversation
Considering to have another pass and also updating the overlay pass. Having what's common extracted is useful for this
this is in case multiple places, e.g. panels, all ask to update the chunk
This refactor aims to remove some casting to types and instead infer the types. And also aims to change the meaning of the interfaces. The interfaces previously often indicated where they were used, as opposed to what they represented. As an example SkeletonChunkInterface is a bit confusing, what is it an interface for. It's not really for a SkeletonChunk, as it's not complete enough.
The overlay memory had a bit of churn in how often we were creating memory to then immediately send it to the GPU and recreate all that memory again on the next build. Instead we build a monotically increasing scratch buffer. This buffer can be reused across sources also because it's not running in parallel. We later might want to look at removing elements from the scratch, but as a first step it should help. For uploading the textures, we packed them, then unpacked them. So introduces new utility to upload these unpacked versions to avoid this. For rebuilding the overlay, we don't keep "selected" as part of the cache key anymore. So now we just cache the geoemtry, and handle the selection updating as a separate update to the geometry.
also revert back to master branch style on these
Replace the loose `dynamicSegmentAppearance` boolean on `SkeletonShaderContext` with a typed `SkeletonShaderParameters` interface passed as `extraParameters` to `parameterizedEmitterDependentShaderGetter`. `hasSegmentStatedColors` and `hasSegmentDefaultColor` now participate in shader memoization rather than being toggled via runtime uniforms (`uUseSegmentStatedColors`, `uUseSegmentDefaultColor`), so shaders are only recompiled when color state changes and the branches are resolved at compile time. GPU hash tables for visible segments, excluded segments, and stated colors are now cached on `RenderHelper` and recreated only when the underlying hash table changes, matching the pattern in `segmentation_render_layer.ts` and fixing repeated GPU allocations on every draw call. `RenderHelper.disposed()` is extended to cover all three tables (previously only stated colors were disposed), and `endLayerSeenTextureUnits` is promoted to a reused field to avoid a per-call Set allocation. `SkeletonLayer` explicitly implements `SkeletonShaderContext` with a static `skeletonShaderParameters` value (all false), since non-spatial skeletons resolve color upstream and pass it via uniforms.
these were posing a bit like the custom user attrs, but they are not. Could use a further refactor in the future along with the seg ID fix, but another card about that
we made a general change which doesn't need to be in place. For our own attrs, we know the type and don't need to handle them in the general loop. For user types, they are fixed to being float only
afonsobspinto
approved these changes
May 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Summary
Refactor: Align skeleton render layer with segmentation layer patterns
This PR refactors
src/skeleton/frontend.tsand related files to bring the skeleton rendering architecture in line with the more mature segmentation render layer patterns — cleaner interface boundaries, shared GPU utilities, reactive shader parameters, and elimination of duplicated pick-handling and rendering logic.What changed
Interface modernization — Old mixed-concern interfaces (
SkeletonLayerInterface,SkeletonChunkInterface) replaced with clearly separated types:SkeletonShaderContext— rendering context (gl, displayState, shader parameters)SkeletonShaderParameters— shader feature flags as aWatchableValue(replaces scattered booleans)SkeletonGPUGeometry— pure GPU geometry interfacePackedSkeletonGeometry— CPU-side vertex/index dataGPU upload consolidation —
gpu_upload_utils.tsdeleted; its core logic was already intexture_access.ts. AddedupdateOneDimensionalTextureElement()for in-place single-element texture updates, and extracteduploadSkeletonChunkToGPU()as a shared helper for both chunk types.SkeletonOverlayChunkpromoted to full class — Now implementsSkeletonGPUGeometry, owns its GPU resources (textures, buffers, picking data), and exposesupdateSelectedNode()which patches a single texture element in-place instead of rebuilding all GPU state when only node selection changes.Overlay geometry memory optimization — Introduced a reusable scratch buffer for arrays that are only needed during GPU upload (
segmentIds,selected,edgeIndices,edgeSegIds), avoiding repeated allocation. Arrays needed for CPU-side picking (positions,nodeIds,pickSegmentIds) are still freshly allocated and owned by the chunk.Two-level overlay caching — Geometry key excludes
selectedNodeId, so selection-only changes trigger the fastupdateSelectedNode()path. Geometry changes trigger a full rebuild. A frame-number guard prevents redundant work across multi-pass rendering.Shader parameter management —
skeletonShaderParametersas aWatchableValue<SkeletonShaderParameters>replaces scattered booleans. Reactive update registration listens tosegmentStatedColorsandsegmentDefaultColor, decoupling shader feature flags from layer state.Picking logic deduplicated — Both
PerspectiveViewSpatiallyIndexedSkeletonLayerandSliceViewPanelSpatiallyIndexedSkeletonLayerhad verbatim copies (~75 lines each) oftransformPickedValueandupdateMouseState. Extracted as free functionstransformSpatiallyIndexedSkeletonPickedValueandupdateSpatiallyIndexedSkeletonMouseState; each class method now delegates in 1–4 lines.Cast cleanup —
as SkeletonLayerDisplayState & SpatialSkeletonDisplayStateintersection casts removed throughoutdraw(),isReady(), and constructors.chunkGeometryRenderLayerInterfacerenamed tobrowsePassLayerView.Naming and removals —
SkeletonRenderModeenum deleted (render_mode.tsremoved);SpatiallyIndexedSkeletonParameterAccessunion type removed;DEBUG_SPATIAL_SKELETON_CHUNKSset tofalse.What's Left to Refactor
High priority
Segment ID precision bug (
frontend.ts~L625)The existing TODO captures this:
segmentAttributeisUint32but segments areUint64. Fixing this also unblocks the structural cleanup below — once the attribute is known to be internal, it can be removed fromvertexAttributesentirely.Internal attributes mixed into
vertexAttributes(frontend.ts~L625, ~L630)segmentAttributeandselectedNodeAttributelive in the samevertexAttributesarray as user-defined attributes but are skipped by index check in both shader getters:These are internal implementation details, not user-defined. They should be stored separately so the user attribute loop needs no special-casing and the boundary is explicit. This also makes the code clearer about what a user can address in the shader vs. what is managed internally.
Medium priority
Edge and node shader getters share ~80% of their body (
frontend.tsL516–860)Both
edgeShaderGetterandnodeShaderGettercallparameterizedEmitterDependentShaderGetterwith identical config (parameters, fallback, extraParameters, shaderError), then insidedefineShader:defineCommonShader,defineAttributeAccess,defineDynamicSegmentAppearancesegmentColor()definition logicThe only genuine differences are the primitive-specific parts: edge uses
defineLineShader+ paired index vertex logic; node usesdefineCircleShader+gl_InstanceID+ selected-node outline. The shared preamble, color branch, and user attribute loop could be extracted into helpers — e.g.defineShaderPreamble(),defineSegmentColorFragment(),defineUserAttributeVaryings()— so each getter only contains the primitive-specific parts.attach()bodies are identical (frontend.ts~L3229, ~L3500)Both view classes have ~30 lines of identical
attach()logic; the only difference is"3d"vs"2d"passed togetSources(). Same situation asupdateMouseStatewas before this PR — can become a shared free functionattachSpatiallyIndexedSkeletonLayer(base, self, attachment, view: "2d" | "3d").Low priority / cleanup
data: anyinupdateMouseState— Both view classes still havedata: any; should beunknownto match the direction of removing casts.getValueAtunused param convention —positionshould be_positionto match the_pickedValueconvention already in the same methods.registerDisposer(base.redrawNeeded...)placement — Registered near the top of the 3d constructor, at the bottom of the 2d constructor; should be consistent.PackedSkeletonGeometry.lodfield (~L152) — Unused, should be deleted.overlay_geometry.ts) —gpuScratchBuffergrows monotonically; needs a trim/clear strategy under sustained use.