@jbrowse/render-core 4.3.0 → 5.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/RenderCanvas.d.ts +2 -2
- package/esm/RenderLifecycleMixin.d.ts +52 -20
- package/esm/RenderLifecycleMixin.js +89 -23
- package/esm/canvas2dUtils.d.ts +90 -23
- package/esm/canvas2dUtils.js +119 -36
- package/esm/colorRampLut.d.ts +5 -5
- package/esm/colorRampLut.js +6 -6
- package/esm/computePipeline.d.ts +14 -0
- package/esm/computePipeline.js +62 -0
- package/esm/contractReports.d.ts +54 -0
- package/esm/contractReports.js +112 -0
- package/esm/coverageBand.d.ts +13 -8
- package/esm/coverageBand.js +15 -5
- package/esm/coverageBandBuffers.d.ts +2 -9
- package/esm/coverageBandBuffers.js +1 -13
- package/esm/createRenderingBackend.d.ts +14 -18
- package/esm/createRenderingBackend.js +4 -2
- package/esm/displayPhase.d.ts +83 -104
- package/esm/displayPhase.js +42 -82
- package/esm/encodeMemo.d.ts +14 -0
- package/esm/encodeMemo.js +48 -0
- package/esm/gpuContextLostError.d.ts +30 -0
- package/esm/gpuContextLostError.js +40 -0
- package/esm/gpuDevice.d.ts +29 -10
- package/esm/gpuDevice.js +35 -16
- package/esm/graphicsCapabilities.d.ts +27 -19
- package/esm/graphicsCapabilities.js +40 -18
- package/esm/hal/createHal.d.ts +4 -5
- package/esm/hal/createHal.js +14 -4
- package/esm/hal/deviceGpuCache.d.ts +25 -30
- package/esm/hal/deviceGpuCache.js +88 -97
- package/esm/hal/gpuHalBase.d.ts +5 -4
- package/esm/hal/gpuHalBase.js +25 -14
- package/esm/hal/index.d.ts +2 -2
- package/esm/hal/index.js +1 -1
- package/esm/hal/mockHal.d.ts +15 -10
- package/esm/hal/mockHal.js +31 -18
- package/esm/hal/oomReporter.d.ts +7 -0
- package/esm/hal/oomReporter.js +10 -0
- package/esm/hal/passIds.d.ts +8 -7
- package/esm/hal/passIds.js +11 -10
- package/esm/hal/regionRegistry.d.ts +0 -1
- package/esm/hal/regionRegistry.js +3 -15
- package/esm/hal/types.d.ts +55 -18
- package/esm/hal/webgl2Hal.d.ts +23 -6
- package/esm/hal/webgl2Hal.js +161 -155
- package/esm/hal/webgpuHal.d.ts +29 -13
- package/esm/hal/webgpuHal.js +221 -153
- package/esm/installUpload.d.ts +16 -3
- package/esm/installUpload.js +13 -41
- package/esm/instanceCache.d.ts +10 -7
- package/esm/instanceCache.js +14 -15
- package/esm/instancePass.d.ts +2 -1
- package/esm/mapUploadSync.d.ts +2 -10
- package/esm/mapUploadSync.js +2 -3
- package/esm/marks/barMark.d.ts +30 -0
- package/esm/marks/barMark.js +149 -0
- package/esm/marks/colorFill.d.ts +17 -0
- package/esm/marks/colorFill.js +26 -0
- package/esm/marks/drawAgainstHit.d.ts +113 -0
- package/esm/marks/drawAgainstHit.js +407 -0
- package/esm/marks/ellipseDistance.d.ts +10 -0
- package/esm/marks/ellipseDistance.js +136 -0
- package/esm/marks/glyphPaint.d.ts +36 -0
- package/esm/marks/glyphPaint.js +77 -0
- package/esm/marks/index.d.ts +33 -0
- package/esm/marks/index.js +20 -0
- package/esm/marks/lineMark.d.ts +37 -0
- package/esm/marks/lineMark.js +416 -0
- package/esm/marks/linkMark.d.ts +76 -0
- package/esm/marks/linkMark.js +585 -0
- package/esm/marks/markBackend.d.ts +118 -0
- package/esm/marks/markBackend.js +196 -0
- package/esm/marks/markHit.d.ts +32 -0
- package/esm/marks/markHit.js +61 -0
- package/esm/marks/markInk.d.ts +18 -0
- package/esm/marks/markInk.js +71 -0
- package/esm/marks/markPaint.d.ts +14 -0
- package/esm/marks/markPaint.js +32 -0
- package/esm/marks/markPlan.d.ts +25 -0
- package/esm/marks/markPlan.js +20 -0
- package/esm/marks/markRamp.d.ts +72 -0
- package/esm/marks/markRamp.js +197 -0
- package/esm/marks/nearestMarkHit.d.ts +55 -0
- package/esm/marks/nearestMarkHit.js +158 -0
- package/esm/marks/pathRecorder.d.ts +14 -0
- package/esm/marks/pathRecorder.js +80 -0
- package/esm/marks/pointMark.d.ts +42 -0
- package/esm/marks/pointMark.js +125 -0
- package/esm/marks/rowLane.d.ts +39 -0
- package/esm/marks/rowLane.js +41 -0
- package/esm/marks/rowSpanIndex.d.ts +29 -0
- package/esm/marks/rowSpanIndex.js +141 -0
- package/esm/marks/rowTable.d.ts +41 -0
- package/esm/marks/rowTable.js +75 -0
- package/esm/marks/ruleMark.d.ts +21 -0
- package/esm/marks/ruleMark.js +91 -0
- package/esm/marks/spanMark.d.ts +42 -0
- package/esm/marks/spanMark.js +81 -0
- package/esm/marks/types.d.ts +261 -0
- package/esm/marks/types.js +175 -0
- package/esm/marks/valueScale.d.ts +23 -0
- package/esm/marks/valueScale.js +11 -0
- package/esm/namedReactions.d.ts +3 -4
- package/esm/namedReactions.js +3 -4
- package/esm/perRegionRenderingBackend.d.ts +21 -3
- package/esm/perRegionRenderingBackend.js +37 -3
- package/esm/regionDataMap.d.ts +2 -2
- package/esm/regionDataMap.js +25 -23
- package/esm/renderBlock.d.ts +14 -0
- package/esm/renderBlock.js +25 -0
- package/esm/renderingBackendBase.d.ts +20 -14
- package/esm/renderingBackendBase.js +12 -7
- package/esm/scoreScale.d.ts +27 -0
- package/esm/scoreScale.js +88 -0
- package/esm/shaders/barMark.generated.d.ts +3 -0
- package/esm/shaders/barMark.generated.js +7 -0
- package/esm/shaders/barMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/barMark.glsl.generated.js +4 -0
- package/esm/shaders/barMark.iface.generated.d.ts +90 -0
- package/esm/shaders/barMark.iface.generated.js +205 -0
- package/esm/shaders/barMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/barMark.wgsl.generated.js +3 -0
- package/esm/shaders/capsule.js.generated.d.ts +1 -0
- package/esm/shaders/capsule.js.generated.js +11 -0
- package/esm/shaders/clipStrip.generated.d.ts +2 -0
- package/esm/shaders/clipStrip.generated.js +4 -0
- package/esm/shaders/clipStrip.js.generated.d.ts +1 -0
- package/esm/shaders/clipStrip.js.generated.js +21 -0
- package/esm/shaders/colorRampLut.js.generated.d.ts +1 -0
- package/esm/shaders/colorRampLut.js.generated.js +17 -0
- package/esm/shaders/coverageBar.generated.d.ts +2 -3
- package/esm/shaders/coverageBar.generated.js +4 -3
- package/esm/shaders/coverageBar.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageBar.glsl.generated.js +4 -0
- package/esm/shaders/coverageBar.iface.generated.js +1 -1
- package/esm/shaders/coverageBar.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageBar.wgsl.generated.js +3 -0
- package/esm/shaders/coverageClip.generated.d.ts +3 -0
- package/esm/shaders/coverageClip.generated.js +7 -0
- package/esm/shaders/coverageClip.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageClip.glsl.generated.js +4 -0
- package/esm/shaders/coverageClip.iface.generated.d.ts +85 -0
- package/esm/shaders/coverageClip.iface.generated.js +114 -0
- package/esm/shaders/coverageClip.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageClip.wgsl.generated.js +3 -0
- package/esm/shaders/coverageIndicator.generated.d.ts +2 -3
- package/esm/shaders/coverageIndicator.generated.js +4 -3
- package/esm/shaders/coverageIndicator.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageIndicator.glsl.generated.js +4 -0
- package/esm/shaders/coverageIndicator.iface.generated.js +1 -1
- package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageIndicator.wgsl.generated.js +3 -0
- package/esm/shaders/coverageInterbase.generated.d.ts +2 -3
- package/esm/shaders/coverageInterbase.generated.js +4 -3
- package/esm/shaders/coverageInterbase.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageInterbase.glsl.generated.js +4 -0
- package/esm/shaders/coverageInterbase.iface.generated.js +1 -1
- package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageInterbase.wgsl.generated.js +3 -0
- package/esm/shaders/coverageMod.generated.d.ts +2 -3
- package/esm/shaders/coverageMod.generated.js +4 -3
- package/esm/shaders/coverageMod.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageMod.glsl.generated.js +4 -0
- package/esm/shaders/coverageMod.iface.generated.js +1 -1
- package/esm/shaders/coverageMod.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageMod.wgsl.generated.js +3 -0
- package/esm/shaders/coverageSnp.generated.d.ts +2 -3
- package/esm/shaders/coverageSnp.generated.js +4 -3
- package/esm/shaders/coverageSnp.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageSnp.glsl.generated.js +4 -0
- package/esm/shaders/coverageSnp.iface.generated.js +1 -1
- package/esm/shaders/coverageSnp.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageSnp.wgsl.generated.js +3 -0
- package/esm/shaders/curveDistance.js.generated.d.ts +5 -0
- package/esm/shaders/curveDistance.js.generated.js +87 -0
- package/esm/shaders/hpmath.js.generated.d.ts +2 -1
- package/esm/shaders/hpmath.js.generated.js +5 -2
- package/esm/shaders/lineCenterMark.generated.d.ts +3 -0
- package/esm/shaders/lineCenterMark.generated.js +7 -0
- package/esm/shaders/lineCenterMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/lineCenterMark.glsl.generated.js +4 -0
- package/esm/shaders/lineCenterMark.iface.generated.d.ts +104 -0
- package/esm/shaders/lineCenterMark.iface.generated.js +237 -0
- package/esm/shaders/lineCenterMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/lineCenterMark.wgsl.generated.js +3 -0
- package/esm/shaders/lineCommon.generated.d.ts +2 -0
- package/esm/shaders/lineCommon.generated.js +4 -0
- package/esm/shaders/lineStepMark.generated.d.ts +3 -0
- package/esm/shaders/lineStepMark.generated.js +7 -0
- package/esm/shaders/lineStepMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/lineStepMark.glsl.generated.js +4 -0
- package/esm/shaders/lineStepMark.iface.generated.d.ts +98 -0
- package/esm/shaders/lineStepMark.iface.generated.js +225 -0
- package/esm/shaders/lineStepMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/lineStepMark.wgsl.generated.js +3 -0
- package/esm/shaders/linkMark.consts.generated.d.ts +14 -0
- package/esm/shaders/linkMark.consts.generated.js +16 -0
- package/esm/shaders/linkMark.generated.d.ts +4 -0
- package/esm/shaders/linkMark.generated.js +8 -0
- package/esm/shaders/linkMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/linkMark.glsl.generated.js +4 -0
- package/esm/shaders/linkMark.iface.generated.d.ts +142 -0
- package/esm/shaders/linkMark.iface.generated.js +1829 -0
- package/esm/shaders/linkMark.js.generated.d.ts +7 -0
- package/esm/shaders/linkMark.js.generated.js +159 -0
- package/esm/shaders/linkMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/linkMark.wgsl.generated.js +3 -0
- package/esm/shaders/markColor.generated.d.ts +9 -0
- package/esm/shaders/markColor.generated.js +11 -0
- package/esm/shaders/pointGlyph.js.generated.d.ts +2 -0
- package/esm/shaders/pointGlyph.js.generated.js +15 -0
- package/esm/shaders/pointMark.consts.generated.d.ts +7 -0
- package/esm/shaders/pointMark.consts.generated.js +9 -0
- package/esm/shaders/pointMark.generated.d.ts +4 -0
- package/esm/shaders/pointMark.generated.js +8 -0
- package/esm/shaders/pointMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/pointMark.glsl.generated.js +4 -0
- package/esm/shaders/pointMark.iface.generated.d.ts +98 -0
- package/esm/shaders/pointMark.iface.generated.js +219 -0
- package/esm/shaders/pointMark.js.generated.d.ts +3 -0
- package/esm/shaders/pointMark.js.generated.js +93 -0
- package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/pointMark.wgsl.generated.js +3 -0
- package/esm/shaders/rowRect.generated.d.ts +0 -1
- package/esm/shaders/rowRect.generated.js +0 -1
- package/esm/shaders/rowTable.generated.d.ts +1 -0
- package/esm/shaders/rowTable.generated.js +3 -0
- package/esm/shaders/rowTable.js.generated.d.ts +5 -0
- package/esm/shaders/rowTable.js.generated.js +29 -0
- package/esm/shaders/scoreScale.generated.d.ts +2 -0
- package/esm/shaders/scoreScale.generated.js +4 -0
- package/esm/shaders/scoreScale.js.generated.d.ts +1 -0
- package/esm/shaders/scoreScale.js.generated.js +50 -23
- package/esm/shaders/spanMark.generated.d.ts +3 -0
- package/esm/shaders/spanMark.generated.js +7 -0
- package/esm/shaders/spanMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/spanMark.glsl.generated.js +4 -0
- package/esm/shaders/spanMark.iface.generated.d.ts +81 -0
- package/esm/shaders/spanMark.iface.generated.js +215 -0
- package/esm/shaders/spanMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/spanMark.wgsl.generated.js +3 -0
- package/esm/sharedBackendKey.d.ts +18 -0
- package/esm/{keyedRenderingBackend.js → sharedBackendKey.js} +5 -0
- package/esm/slangPass.d.ts +18 -11
- package/esm/slangPass.js +12 -4
- package/esm/useGraphicsCapabilities.d.ts +1 -1
- package/esm/useGraphicsCapabilities.js +1 -1
- package/esm/useRenderingBackend.d.ts +13 -13
- package/esm/useRenderingBackend.js +58 -20
- package/esm/webgpuUtils.d.ts +3 -11
- package/esm/webgpuUtils.js +12 -33
- package/package.json +32 -7
- package/src/shaders/antialias.slang +46 -0
- package/src/shaders/arcHull.test.ts +267 -0
- package/src/shaders/barMark.generated.ts +11 -0
- package/src/shaders/barMark.glsl.generated.ts +6 -0
- package/src/shaders/barMark.iface.generated.ts +285 -0
- package/src/shaders/barMark.slang +167 -0
- package/src/shaders/barMark.wgsl.generated.ts +4 -0
- package/src/shaders/capsule.js.generated.ts +14 -0
- package/src/shaders/capsule.slang +12 -4
- package/src/shaders/clipStrip.generated.ts +6 -0
- package/src/shaders/clipStrip.js.generated.ts +22 -0
- package/src/shaders/clipStrip.slang +34 -0
- package/src/shaders/clipStrip.test.ts +39 -0
- package/src/shaders/colorPack.slang +5 -1
- package/src/shaders/colorRampLut.js.generated.ts +22 -0
- package/src/shaders/colorRampLut.slang +15 -1
- package/src/shaders/coverageBand.slang +64 -32
- package/src/shaders/coverageBar.generated.ts +6 -5
- package/src/shaders/coverageBar.glsl.generated.ts +6 -0
- package/src/shaders/coverageBar.iface.generated.ts +1 -1
- package/src/shaders/coverageBar.slang +10 -10
- package/src/shaders/coverageBar.wgsl.generated.ts +4 -0
- package/src/shaders/coverageClip.generated.ts +11 -0
- package/src/shaders/coverageClip.glsl.generated.ts +6 -0
- package/src/shaders/coverageClip.iface.generated.ts +172 -0
- package/src/shaders/coverageClip.slang +38 -0
- package/src/shaders/coverageClip.wgsl.generated.ts +4 -0
- package/src/shaders/coverageIndicator.generated.ts +6 -5
- package/src/shaders/coverageIndicator.glsl.generated.ts +6 -0
- package/src/shaders/coverageIndicator.iface.generated.ts +1 -1
- package/src/shaders/coverageIndicator.wgsl.generated.ts +4 -0
- package/src/shaders/coverageInterbase.generated.ts +6 -5
- package/src/shaders/coverageInterbase.glsl.generated.ts +6 -0
- package/src/shaders/coverageInterbase.iface.generated.ts +1 -1
- package/src/shaders/coverageInterbase.wgsl.generated.ts +4 -0
- package/src/shaders/coverageMod.generated.ts +6 -5
- package/src/shaders/coverageMod.glsl.generated.ts +6 -0
- package/src/shaders/coverageMod.iface.generated.ts +1 -1
- package/src/shaders/coverageMod.wgsl.generated.ts +4 -0
- package/src/shaders/coverageSnp.generated.ts +6 -5
- package/src/shaders/coverageSnp.glsl.generated.ts +6 -0
- package/src/shaders/coverageSnp.iface.generated.ts +1 -1
- package/src/shaders/coverageSnp.wgsl.generated.ts +4 -0
- package/src/shaders/curveDistance.js.generated.ts +91 -0
- package/src/shaders/curveDistance.slang +265 -0
- package/src/shaders/hpClampFloor.test.ts +87 -0
- package/src/shaders/hpmath.js.generated.ts +6 -2
- package/src/shaders/hpmath.slang +100 -48
- package/src/shaders/insetValueScale.test.ts +67 -0
- package/src/shaders/lineCenterMark.generated.ts +11 -0
- package/src/shaders/lineCenterMark.glsl.generated.ts +6 -0
- package/src/shaders/lineCenterMark.iface.generated.ts +328 -0
- package/src/shaders/lineCenterMark.slang +127 -0
- package/src/shaders/lineCenterMark.wgsl.generated.ts +4 -0
- package/src/shaders/lineCommon.generated.ts +6 -0
- package/src/shaders/lineCommon.slang +102 -0
- package/src/shaders/lineStepMark.generated.ts +11 -0
- package/src/shaders/lineStepMark.glsl.generated.ts +6 -0
- package/src/shaders/lineStepMark.iface.generated.ts +311 -0
- package/src/shaders/lineStepMark.slang +125 -0
- package/src/shaders/lineStepMark.wgsl.generated.ts +4 -0
- package/src/shaders/linkMark.consts.generated.ts +30 -0
- package/src/shaders/linkMark.generated.ts +12 -0
- package/src/shaders/linkMark.glsl.generated.ts +6 -0
- package/src/shaders/linkMark.iface.generated.ts +1954 -0
- package/src/shaders/linkMark.js.generated.ts +167 -0
- package/src/shaders/linkMark.slang +439 -0
- package/src/shaders/linkMark.wgsl.generated.ts +4 -0
- package/src/shaders/markColor.generated.ts +20 -0
- package/src/shaders/markColor.slang +128 -0
- package/src/shaders/pointGlyph.js.generated.ts +20 -0
- package/src/shaders/pointGlyph.slang +23 -7
- package/src/shaders/pointGlyphPad.test.ts +242 -0
- package/src/shaders/pointMark.consts.generated.ts +16 -0
- package/src/shaders/pointMark.generated.ts +12 -0
- package/src/shaders/pointMark.glsl.generated.ts +6 -0
- package/src/shaders/pointMark.iface.generated.ts +304 -0
- package/src/shaders/pointMark.js.generated.ts +99 -0
- package/src/shaders/pointMark.slang +227 -0
- package/src/shaders/pointMark.wgsl.generated.ts +4 -0
- package/src/shaders/rowRect.generated.ts +0 -2
- package/src/shaders/rowRect.slang +39 -58
- package/src/shaders/rowTable.generated.ts +4 -0
- package/src/shaders/rowTable.js.generated.ts +37 -0
- package/src/shaders/rowTable.slang +98 -0
- package/src/shaders/scoreScale.generated.ts +6 -0
- package/src/shaders/scoreScale.js.generated.ts +48 -23
- package/src/shaders/scoreScale.slang +49 -55
- package/src/shaders/sdEllipse.test.ts +211 -0
- package/src/shaders/spanMark.generated.ts +11 -0
- package/src/shaders/spanMark.glsl.generated.ts +6 -0
- package/src/shaders/spanMark.iface.generated.ts +289 -0
- package/src/shaders/spanMark.slang +77 -0
- package/src/shaders/spanMark.wgsl.generated.ts +4 -0
- package/src/shaders/valueScale.slang +26 -0
- package/src/shaders/wideCircleLeg.test.ts +85 -0
- package/esm/globalRenderingBackend.d.ts +0 -90
- package/esm/globalRenderingBackend.js +0 -53
- package/esm/keyedRenderingBackend.d.ts +0 -41
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// Exact distances to the two curves a stroked arc is measured against - an
|
|
2
|
+
// axis-aligned ellipse, and a circle whose radius dwarfs the band it is
|
|
3
|
+
// clipped to - and the hull vertices that have to clear them. Shared by the
|
|
4
|
+
// alignments arc band and the link mark, which draw the same half-ellipse and
|
|
5
|
+
// degenerate it to the same circle.
|
|
6
|
+
//
|
|
7
|
+
// The fragment ramps its alpha to zero at `halfWidth + aaHalfPx`, so the hull
|
|
8
|
+
// has to cover everything nearer the curve than that. Both flanks of a hull
|
|
9
|
+
// sampled from the offset curve fall short of it at a clamped dome's feet.
|
|
10
|
+
//! js-export: distToWideCirclePx, wideCircleLeg, wideCircleLegStep, ellipseHullPoint, wideCircleHullPoint
|
|
11
|
+
//! js-skip: nearCircleDistancePx — a float32 remedy with no float64 caller. The hit tests' copy of this file's solve (marks/ellipseDistance.ts) holds the ellipse at every aspect, so nothing outside the shader has an approximation to want.
|
|
12
|
+
//! oracle-skip: ellipseHullPoint — a miter between near-parallel tangent lines, so float32 and float64 part on the probe's arbitrary directions and radii; over the angles the hull steps through they agree to 0.004 px, and arcHull.test.ts referees the twin against every inked point instead
|
|
13
|
+
//! oracle-skip: wideCircleHullPoint — the foot-relative frame cancels at half steps and angles the band never reaches; arcHull.test.ts referees it with the legs
|
|
14
|
+
|
|
15
|
+
module curveDistance;
|
|
16
|
+
|
|
17
|
+
// How near a circle an ellipse has to be before `sdEllipse` stops solving and
|
|
18
|
+
// measures instead: radii within ~2.5%, as |ay² - ax²| against ay². The two
|
|
19
|
+
// formulas' worst errors over a 6px stroke meet here at ~0.01px — below it the
|
|
20
|
+
// solve degrades with radius (0.10px at r=4000), above it the measure degrades
|
|
21
|
+
// with curvature (0.01px at r=4).
|
|
22
|
+
static const float ELLIPSE_CIRCLE_TOL = 0.05;
|
|
23
|
+
|
|
24
|
+
// The distance to an ellipse the solve cannot reach. Every term in it divides
|
|
25
|
+
// by `l = ay² - ax²`, a difference of two nearly equal squares, so float32 has
|
|
26
|
+
// lost the ellipse before the quartic is assembled: at 250px radii 1e-5 apart
|
|
27
|
+
// the solve is 3.6px out and 36% of the stroke's samples flip. Not a corner —
|
|
28
|
+
// a clamped dome pins ry to the band while rx tracks the pair's width, so a
|
|
29
|
+
// zoom walks rx through ry and through every ratio on the way.
|
|
30
|
+
//
|
|
31
|
+
// The implicit field |p/ab| - 1 over its own gradient instead: exact on a true
|
|
32
|
+
// circle, which is why it replaces the exact-circle branch rather than sitting
|
|
33
|
+
// beside it, and first-order elsewhere.
|
|
34
|
+
float nearCircleDistancePx(float qx, float qy, float ax, float ay) {
|
|
35
|
+
float kx = qx / ax;
|
|
36
|
+
float ky = qy / ay;
|
|
37
|
+
float k = sqrt(kx * kx + ky * ky);
|
|
38
|
+
float gx = qx / (ax * ax);
|
|
39
|
+
float gy = qy / (ay * ay);
|
|
40
|
+
return abs(k * (k - 1.0) / max(sqrt(gx * gx + gy * gy), 1e-20));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Inigo Quilez's closed-form solve of the quartic. Chosen over the iterative
|
|
44
|
+
// alternatives because the arc band produces very flat ellipses (a 25px band
|
|
45
|
+
// under a 1500px pair is 44:1, and it goes past 88:1): measured against an
|
|
46
|
+
// fp64 reference at fp32 precision, this stays within 0.031px out to 88:1,
|
|
47
|
+
// where a seeded Newton on the parametric angle runs away entirely.
|
|
48
|
+
//
|
|
49
|
+
// THAT MEASUREMENT IS THE FLAT END, and the solve fails at the round one, in
|
|
50
|
+
// float32 alone — the same source in float64 is exact to 1e-9. It breaks the
|
|
51
|
+
// picture rather than nudging it, because the stroke reads this through a
|
|
52
|
+
// half-pixel ramp: a dome at 380x249px drew as scattered dots on WebGL where
|
|
53
|
+
// Canvas2D drew a line. Two guards below, each where its failure arises.
|
|
54
|
+
//
|
|
55
|
+
// NOT `//! js-export`ed, and the hit tests carry a float64 copy of this formula
|
|
56
|
+
// (render-core marks/ellipseDistance.ts) BECAUSE of that. The reasoning is
|
|
57
|
+
// worth keeping, since "lift it like linkRadiiPx" is the obvious first move and
|
|
58
|
+
// it was tried:
|
|
59
|
+
//
|
|
60
|
+
// - The emitter refuses float2 params, so lifting means splitting a scalar
|
|
61
|
+
// core out (the ADR-051 step-1 pattern). Necessary but NOT sufficient, and
|
|
62
|
+
// the shortfall is SILENT: the core still calls acos/cos/sin, none of which
|
|
63
|
+
// are in the emitter's MATH_BUILTINS, so the emitter refuses it — and
|
|
64
|
+
// check-oracle's `emittableOf` DROPS a refused candidate from the sweep
|
|
65
|
+
// rather than failing on it. Do the split alone and the oracle stays green
|
|
66
|
+
// on an unchanged comparison count, which reads as "the problem went away".
|
|
67
|
+
// It has not; nothing has been checked.
|
|
68
|
+
// - Add those three builtins and it does enter the sweep — which takes every
|
|
69
|
+
// declared function with an emittable scalar signature, not only the
|
|
70
|
+
// `//! js-export`ed ones, and `//! js-skip` does not exempt one either. Then
|
|
71
|
+
// it fails, at the oracle's 1e-5 relative: 90 of the 400 tuples, 42 of them
|
|
72
|
+
// on inputs with both radii positive (the sweep also generates negative
|
|
73
|
+
// radii, which `linkRadiiPx` does not produce).
|
|
74
|
+
// - And NOT because the twin is wrong. On every one of those 42 the float64
|
|
75
|
+
// twin matches a brute-force minimum over the parametric angle to 1.1e-6
|
|
76
|
+
// relative — inside the oracle's own tolerance — and slangc's float32 is the
|
|
77
|
+
// side that has drifted: `(1.5, 0, 65536, 3)` is 3.0, where the C++ answers
|
|
78
|
+
// 3.3541019. float32 loses the solver, float64 does not.
|
|
79
|
+
// - So the oracle cannot referee this function at any tolerance it could keep
|
|
80
|
+
// for the rest of the tree, the same way it cannot referee `mix`/`step`
|
|
81
|
+
// (SHADER_JS_CODEGEN.md). Exporting it would have meant a suppression in
|
|
82
|
+
// the strongest gate here, to share a NUMERICAL METHOD rather than a
|
|
83
|
+
// decision.
|
|
84
|
+
//
|
|
85
|
+
// What actually has to agree is WHICH ELLIPSE, and that is the radii pair
|
|
86
|
+
// `linkMark.slang` `//! js-export`s (`linkRadiiPx`), which is what its hit test
|
|
87
|
+
// reads. Given the same two radii, the distance to them is geometry with
|
|
88
|
+
// one right answer, and the copy is pinned against a brute-force reference
|
|
89
|
+
// rather than against this (see ellipseDistance.test.ts), which is the
|
|
90
|
+
// stronger check of the two.
|
|
91
|
+
public float sdEllipse(float2 p, float2 radii) {
|
|
92
|
+
float2 q = abs(p);
|
|
93
|
+
float2 ab = radii;
|
|
94
|
+
if (q.x > q.y) { q = q.yx; ab = ab.yx; }
|
|
95
|
+
// Both radii zero is the one shape the near-circle test below cannot catch:
|
|
96
|
+
// its own threshold is `ab.y * ab.y`, so it reads `abs(l) < 0` and lets a
|
|
97
|
+
// zero `l` through to divide. A link pair whose two ends land on the same
|
|
98
|
+
// pixel reaches it (`linkRadiiPx` of a zero half-width), and the ellipse is
|
|
99
|
+
// then the point at the origin.
|
|
100
|
+
if (dot(ab, ab) <= 0.0) { return length(q); }
|
|
101
|
+
float l = ab.y * ab.y - ab.x * ab.x;
|
|
102
|
+
if (abs(l) < ELLIPSE_CIRCLE_TOL * ab.y * ab.y) {
|
|
103
|
+
return nearCircleDistancePx(q.x, q.y, ab.x, ab.y);
|
|
104
|
+
}
|
|
105
|
+
float m = ab.x * q.x / l; float m2 = m * m;
|
|
106
|
+
float n = ab.y * q.y / l; float n2 = n * n;
|
|
107
|
+
float c = (m2 + n2 - 1.0) / 3.0;
|
|
108
|
+
float c3 = c * c * c;
|
|
109
|
+
float d = c3 + m2 * n2;
|
|
110
|
+
float g = m + m * n2;
|
|
111
|
+
float co;
|
|
112
|
+
if (d < 0.0) {
|
|
113
|
+
float h = acos(clamp((c3 + m2 * n2 * 2.0) / c3, -1.0, 1.0)) / 3.0;
|
|
114
|
+
float s = cos(h);
|
|
115
|
+
float t = sin(h) * sqrt(3.0);
|
|
116
|
+
float rx = sqrt(max(-c * (s + t + 2.0) + m2, 0.0));
|
|
117
|
+
float ry = sqrt(max(-c * (s - t + 2.0) + m2, 0.0));
|
|
118
|
+
co = (ry + sign(l) * rx + abs(g) / max(rx * ry, 1e-20) - m) / 2.0;
|
|
119
|
+
} else {
|
|
120
|
+
float qq = c3 + m2 * n2 * 2.0;
|
|
121
|
+
float h = 2.0 * m * n * sqrt(d);
|
|
122
|
+
// The two cube roots are of `qq + h` and `qq - h`, whose product is c3*c3
|
|
123
|
+
// identically. So the lower one comes from that quotient rather than from
|
|
124
|
+
// the subtraction, which cancels to nothing exactly where the dome lives:
|
|
125
|
+
// at 380x249px the two agree to every float32 digit they have, `qq - h`
|
|
126
|
+
// rounds to zero, and the distance lands up to 3px off the curve.
|
|
127
|
+
float w = qq + h;
|
|
128
|
+
float aw = abs(w);
|
|
129
|
+
float s = sign(w) * pow(aw, 1.0 / 3.0);
|
|
130
|
+
float t = aw > 0.0 ? sign(w) * pow(c3 * (c3 / aw), 1.0 / 3.0) : 0.0;
|
|
131
|
+
float rx = -s - t - c * 4.0 + 2.0 * m2;
|
|
132
|
+
float ry = (s - t) * sqrt(3.0);
|
|
133
|
+
float rm = sqrt(rx * rx + ry * ry);
|
|
134
|
+
co = (ry / sqrt(max(rm - rx, 1e-20)) + 2.0 * g / max(rm, 1e-20) - m) / 2.0;
|
|
135
|
+
}
|
|
136
|
+
co = clamp(co, -1.0, 1.0);
|
|
137
|
+
float2 r = ab * float2(co, sqrt(max(1.0 - co * co, 0.0)));
|
|
138
|
+
return length(r - q);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Distance from a point to a circle whose radius dwarfs the band it is clipped
|
|
142
|
+
// to — the far-pair case, where r reaches millions of px. `legLocal` is the
|
|
143
|
+
// point relative to the endpoint the circle meets the baseline at, x positive
|
|
144
|
+
// pointing AWAY from the circle.
|
|
145
|
+
//
|
|
146
|
+
// Exact, but written so nothing cancels: the centre sits at (-r, 0) in that
|
|
147
|
+
// frame, so |p-c|² = r² + 2r·x + x² + y² identically and
|
|
148
|
+
// |p-c| - r = rk / (sqrt(1+k) + 1) with both `rk` and `k` assembled from small
|
|
149
|
+
// terms. Evaluating `length(p - c) - r`, or even recovering x as
|
|
150
|
+
// `abs(p.x) - r` from a centre-relative point, loses every significant digit
|
|
151
|
+
// past r ≈ 1e6 px.
|
|
152
|
+
//
|
|
153
|
+
// Scalar core, `//! js-export`ed for the hit tests — and for the same reason
|
|
154
|
+
// it is written this way at all: a hit test asking "is the cursor on this leg"
|
|
155
|
+
// from `length(p - c) - r` would lose every significant digit exactly where
|
|
156
|
+
// far pairs live.
|
|
157
|
+
public float distToWideCirclePx(float x, float y, float r) {
|
|
158
|
+
float rk = 2.0 * x + (x * x + y * y) / r;
|
|
159
|
+
return abs(rk / (sqrt(max(1.0 + rk / r, 0.0)) + 1.0));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public float distToWideCircle(float2 legLocal, float r) {
|
|
163
|
+
return distToWideCirclePx(legLocal.x, legLocal.y, r);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// How far around a far circle each leg's hull sweeps, in radians from its foot:
|
|
167
|
+
// to the band's edge and no further. Sweeping the whole quarter spends nearly
|
|
168
|
+
// every segment off-screen, and at r = 3e6 px the whole band then falls inside
|
|
169
|
+
// the first chord, which recedes from the near-vertical curve at a rate set by
|
|
170
|
+
// the angle per segment — so the leg draws as a tapering triangle however many
|
|
171
|
+
// segments there are. Clamped, so a circle small enough to apex inside the band
|
|
172
|
+
// still gets its full quarter and draws as a dome.
|
|
173
|
+
public float legSweepAngle(float radiusPx, float yReachPx) {
|
|
174
|
+
return asin(clamp(yReachPx / max(radiusPx, 1e-6), 0.0, 1.0));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Which leg vertex pair `seg` of a `segments`-long strip builds (x: +1 the right
|
|
178
|
+
// leg, -1 the left), and its angle from that leg's foot (y). The strip has
|
|
179
|
+
// `segments + 1` pairs, one more than either leg needs, and at an even
|
|
180
|
+
// `segments` the right leg takes it. Each leg is spread over ITS OWN count, so
|
|
181
|
+
// both reach `legSweep` — and both do so in equal angular steps, which is what
|
|
182
|
+
// puts a chord's sagitta below a pixel everywhere the band can show. The quad
|
|
183
|
+
// between the two tips is the seam the fragment discards.
|
|
184
|
+
//
|
|
185
|
+
// `segments` is at least 3; below that the left leg is a single pair and has no
|
|
186
|
+
// span to spread. Both callers pass a compile-time 64.
|
|
187
|
+
public float2 wideCircleLeg(uint seg, uint segments, float legSweep) {
|
|
188
|
+
uint half = segments / 2u;
|
|
189
|
+
if (seg <= half) {
|
|
190
|
+
return float2(1.0, float(seg) / float(half) * legSweep);
|
|
191
|
+
}
|
|
192
|
+
return float2(-1.0,
|
|
193
|
+
float(segments - seg) / float(segments - half - 1u) * legSweep);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// The angle between neighbouring vertex pairs on `seg`'s leg, which is not the
|
|
197
|
+
// same on both legs.
|
|
198
|
+
public float wideCircleLegStep(uint seg, uint segments, float legSweep) {
|
|
199
|
+
uint half = segments / 2u;
|
|
200
|
+
return legSweep / float(seg <= half ? half : segments - half - 1u);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// One hull vertex at parametric angle `a` for a hull stepping `2 * halfStep`,
|
|
204
|
+
// `padPx` from the half-ellipse: positive on the outer flank, negative on the
|
|
205
|
+
// inner.
|
|
206
|
+
//
|
|
207
|
+
// Outside, the offset region is convex, so the vertex is where the offset
|
|
208
|
+
// tangent lines at `a` +/- the half step meet: `p(a) * sec(halfStep)` plus the
|
|
209
|
+
// miter `padPx * (n1 + n2) / (1 + n1 . n2)`. Chords through points on the
|
|
210
|
+
// offset curve would sag inside it. The divisor's floor keeps a collapsed band
|
|
211
|
+
// (ry 0, the foot's normals opposed) finite.
|
|
212
|
+
//
|
|
213
|
+
// Inside, a chord between offset points already stays clear of the ink, until
|
|
214
|
+
// the curve turns tighter than the pad and the offset folds past an axis. A
|
|
215
|
+
// folded vertex moves to where the fold crosses that axis, the one point there
|
|
216
|
+
// exactly the pad from the curve.
|
|
217
|
+
public float2 ellipseHullPoint(float cosA, float sinA, float cosHalf,
|
|
218
|
+
float sinHalf, float rx, float ry, float padPx) {
|
|
219
|
+
float px = cosA * rx;
|
|
220
|
+
float py = sinA * ry;
|
|
221
|
+
if (padPx <= 0.0) {
|
|
222
|
+
float d = -padPx;
|
|
223
|
+
float gx = ry * cosA;
|
|
224
|
+
float gy = rx * sinA;
|
|
225
|
+
float glen = sqrt(gx * gx + gy * gy);
|
|
226
|
+
bool acrossX = ry * glen < d * rx;
|
|
227
|
+
bool acrossY = rx * glen < d * ry;
|
|
228
|
+
if (acrossX && acrossY) { return float2(0.0, 0.0); }
|
|
229
|
+
if (acrossX) {
|
|
230
|
+
float foldX = sqrt(max(ry * ry - d * d, 0.0) * max(rx * rx - ry * ry, 0.0));
|
|
231
|
+
return float2(sign(cosA) * foldX / max(ry, 1e-6), 0.0);
|
|
232
|
+
}
|
|
233
|
+
if (acrossY) {
|
|
234
|
+
float foldY = sqrt(max(rx * rx - d * d, 0.0) * max(ry * ry - rx * rx, 0.0));
|
|
235
|
+
return float2(0.0, sign(sinA) * foldY / max(rx, 1e-6));
|
|
236
|
+
}
|
|
237
|
+
float k = d / max(glen, 1e-6);
|
|
238
|
+
return float2(px - gx * k, py - gy * k);
|
|
239
|
+
}
|
|
240
|
+
float ax = ry * (cosA * cosHalf + sinA * sinHalf);
|
|
241
|
+
float ay = rx * (sinA * cosHalf - cosA * sinHalf);
|
|
242
|
+
float bx = ry * (cosA * cosHalf - sinA * sinHalf);
|
|
243
|
+
float by = rx * (sinA * cosHalf + cosA * sinHalf);
|
|
244
|
+
float al = max(sqrt(ax * ax + ay * ay), 1e-20);
|
|
245
|
+
float bl = max(sqrt(bx * bx + by * by), 1e-20);
|
|
246
|
+
float nx = ax / al + bx / bl;
|
|
247
|
+
float ny = ay / al + by / bl;
|
|
248
|
+
float m = 2.0 * padPx / max(nx * nx + ny * ny, 2e-4);
|
|
249
|
+
return float2(px / cosHalf + nx * m, py / cosHalf + ny * m);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// The same vertex for a far pair's leg, in the foot-relative frame
|
|
253
|
+
// `distToWideCirclePx` measures in. A circle's normals are radial, so the outer
|
|
254
|
+
// flank scales the offset radius by `sec(halfStep)` and the inner one takes the
|
|
255
|
+
// offset point as it is. The vertex's standoff from the circle is carried as
|
|
256
|
+
// `grow` rather than recovered from `r * cos(b) - r`, which cancels at the radii
|
|
257
|
+
// far pairs reach.
|
|
258
|
+
public float2 wideCircleHullPoint(float sinB, float sinHalfB, float cosHalf,
|
|
259
|
+
float radiusPx, float padPx) {
|
|
260
|
+
float sec = padPx > 0.0 ? 1.0 / max(cosHalf, 1e-6) : 1.0;
|
|
261
|
+
float grow = radiusPx * (sec - 1.0) + sec * padPx;
|
|
262
|
+
float sh2 = sinHalfB * sinHalfB;
|
|
263
|
+
return float2(grow * (1.0 - 2.0 * sh2) - 2.0 * radiusPx * sh2,
|
|
264
|
+
(radiusPx + grow) * sinB);
|
|
265
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// `hpToClipX` and `hpScaleLinear` clamp each half of the hi/lo split to a floor
|
|
2
|
+
// nothing can reach, and the clamp is there so no backend folds the split into
|
|
3
|
+
// one large subtraction. The floor used to be `-1.0 / hpZero`, and `hpZero` is
|
|
4
|
+
// uploaded as exactly 0 — a division by zero in every emitted backend, which
|
|
5
|
+
// WGSL lets an implementation answer with an indeterminate value rather than an
|
|
6
|
+
// infinity. (`coverageBandMarks.test.ts`, `coverageBandUboParity.test.ts` and
|
|
7
|
+
// maf's `coverageBandDraw.test.ts` are what hold the uploaded value at 0.)
|
|
8
|
+
//
|
|
9
|
+
// So: the floor must be a finite literal, the two clamps must survive, and
|
|
10
|
+
// `hpZero` must reach neither as a divisor.
|
|
11
|
+
import { GLSL_VERTEX as barMarkGlsl } from './barMark.glsl.generated.ts'
|
|
12
|
+
import { WGSL_SOURCE as barMarkWgsl } from './barMark.wgsl.generated.ts'
|
|
13
|
+
import { GLSL_VERTEX as coverageBarGlsl } from './coverageBar.glsl.generated.ts'
|
|
14
|
+
import { WGSL_SOURCE as coverageBarWgsl } from './coverageBar.wgsl.generated.ts'
|
|
15
|
+
import { GLSL_VERTEX as coverageIndicatorGlsl } from './coverageIndicator.glsl.generated.ts'
|
|
16
|
+
import { WGSL_SOURCE as coverageIndicatorWgsl } from './coverageIndicator.wgsl.generated.ts'
|
|
17
|
+
import { GLSL_VERTEX as coverageInterbaseGlsl } from './coverageInterbase.glsl.generated.ts'
|
|
18
|
+
import { WGSL_SOURCE as coverageInterbaseWgsl } from './coverageInterbase.wgsl.generated.ts'
|
|
19
|
+
import { GLSL_VERTEX as coverageModGlsl } from './coverageMod.glsl.generated.ts'
|
|
20
|
+
import { WGSL_SOURCE as coverageModWgsl } from './coverageMod.wgsl.generated.ts'
|
|
21
|
+
import { GLSL_VERTEX as coverageSnpGlsl } from './coverageSnp.glsl.generated.ts'
|
|
22
|
+
import { WGSL_SOURCE as coverageSnpWgsl } from './coverageSnp.wgsl.generated.ts'
|
|
23
|
+
import { GLSL_VERTEX as pointMarkGlsl } from './pointMark.glsl.generated.ts'
|
|
24
|
+
import { WGSL_SOURCE as pointMarkWgsl } from './pointMark.wgsl.generated.ts'
|
|
25
|
+
import { GLSL_VERTEX as spanMarkGlsl } from './spanMark.glsl.generated.ts'
|
|
26
|
+
import { WGSL_SOURCE as spanMarkWgsl } from './spanMark.wgsl.generated.ts'
|
|
27
|
+
|
|
28
|
+
const SOURCES = {
|
|
29
|
+
'barMark wgsl': barMarkWgsl,
|
|
30
|
+
'barMark glsl': barMarkGlsl,
|
|
31
|
+
'coverageBar wgsl': coverageBarWgsl,
|
|
32
|
+
'coverageBar glsl': coverageBarGlsl,
|
|
33
|
+
'coverageIndicator wgsl': coverageIndicatorWgsl,
|
|
34
|
+
'coverageIndicator glsl': coverageIndicatorGlsl,
|
|
35
|
+
'coverageInterbase wgsl': coverageInterbaseWgsl,
|
|
36
|
+
'coverageInterbase glsl': coverageInterbaseGlsl,
|
|
37
|
+
'coverageMod wgsl': coverageModWgsl,
|
|
38
|
+
'coverageMod glsl': coverageModGlsl,
|
|
39
|
+
'coverageSnp wgsl': coverageSnpWgsl,
|
|
40
|
+
'coverageSnp glsl': coverageSnpGlsl,
|
|
41
|
+
'pointMark wgsl': pointMarkWgsl,
|
|
42
|
+
'pointMark glsl': pointMarkGlsl,
|
|
43
|
+
'spanMark wgsl': spanMarkWgsl,
|
|
44
|
+
'spanMark glsl': spanMarkGlsl,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function bodyOf(source: string, name: string) {
|
|
48
|
+
const open = source.indexOf('{', source.indexOf(`${name}(`))
|
|
49
|
+
const close = source.indexOf('}', open)
|
|
50
|
+
return source.slice(open + 1, close)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The bp coordinates the split carries are uint32, so no difference of two of
|
|
54
|
+
// them reaches -2^33.
|
|
55
|
+
const BP_REACH = -(2 ** 33)
|
|
56
|
+
|
|
57
|
+
const CASES = Object.entries(SOURCES).flatMap(([where, source]) =>
|
|
58
|
+
['hpToClipX_0', 'hpScaleLinear_0']
|
|
59
|
+
.filter(name => source.includes(`${name}(`))
|
|
60
|
+
.map(name => [`${where} ${name}`, bodyOf(source, name)] as const),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
test('every hp conversion the emitted backends carry is covered', () => {
|
|
64
|
+
expect(CASES.length).toBe(Object.keys(SOURCES).length)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe.each(CASES)('%s', (_where, body) => {
|
|
68
|
+
test('never divides by the hpZero uniform', () => {
|
|
69
|
+
expect(body).toMatch(/hpZero_\d+/)
|
|
70
|
+
expect(body).not.toMatch(/\/\s*(-\s*)?\(?\s*hpZero_\d+/)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('clamps both halves against a finite floor below any bp difference', () => {
|
|
74
|
+
const floors = [...body.matchAll(/max\([^,]+,\s*(\w+)\)/g)].map(m => m[1])
|
|
75
|
+
expect(floors).toHaveLength(2)
|
|
76
|
+
expect(new Set(floors).size).toBe(1)
|
|
77
|
+
|
|
78
|
+
const assigned = new RegExp(`${floors[0]}[^=]*=\\s*([^;]+);`).exec(body)
|
|
79
|
+
const literal = /-?\d[\d.]*e[+-]\d+/.exec(assigned?.[1] ?? '')?.[0]
|
|
80
|
+
expect(literal).toBeDefined()
|
|
81
|
+
|
|
82
|
+
const floor = Number(literal)
|
|
83
|
+
expect(Number.isFinite(floor)).toBe(true)
|
|
84
|
+
expect(floor).toBeLessThan(BP_REACH)
|
|
85
|
+
expect(Math.fround(floor)).toBe(floor)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
@@ -26,8 +26,12 @@ export function snapBoxHeightPx(heightPx: number): number {
|
|
|
26
26
|
return _t1
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export function
|
|
30
|
-
return
|
|
29
|
+
export function snapBoxTopPx(topY: number, heightPx: number, scrollY: number): number {
|
|
30
|
+
return Math.floor((((((topY + (heightPx * 0.5)) - (snapBoxHeightPx(heightPx) * 0.5)) - scrollY) + 0.5) + 0.015625))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function snapBoxCenterYPx(centerY: number, scrollY: number): number {
|
|
34
|
+
return (Math.floor(((centerY - scrollY) + 0.015625)) + 0.5)
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
export function extendToMinWidthPx(x1: number, x2: number, minWidth: number): number {
|
package/src/shaders/hpmath.slang
CHANGED
|
@@ -8,7 +8,14 @@
|
|
|
8
8
|
// must make identically to the GPU — which pixel row a glyph centers on, how a
|
|
9
9
|
// sub-pixel span is widened. `pnpm gen:shaders` transliterates them into
|
|
10
10
|
// hpmath.js.generated.ts; nothing downstream re-derives them by hand.
|
|
11
|
-
|
|
11
|
+
//
|
|
12
|
+
// `expandToMinWidthLeftPx`/`RightPx` are exported too, but by `coverageBar.slang`, into
|
|
13
|
+
// @jbrowse/alignments-core — the twin's consumer (`fillSpanRect`) lives in a
|
|
14
|
+
// package that cannot import render-core, and this file's own redirect would
|
|
15
|
+
// move every export above with it. adr-051 §"a shader may also export a
|
|
16
|
+
// function it only imports" is the mechanism; grep the name before assuming it
|
|
17
|
+
// is unlifted.
|
|
18
|
+
//! js-export: snapBoxHeightPx, snapBoxTopPx, snapBoxCenterYPx, extendToMinWidthPx
|
|
12
19
|
//! js-skip: hpSplitUint — the hi/lo float32 precision split exists because a GPU has no float64; the Canvas2D path just uses a number
|
|
13
20
|
//! js-skip: quadLocal — maps a vertex id to a quad corner — Canvas2D has no vertices, it calls fillRect
|
|
14
21
|
//! js-skip: yPxToClipY — clip space is the GPU coordinate system; the px-space decision it wraps is what gets exported (snapBoxCenterYPx and friends)
|
|
@@ -17,13 +24,14 @@
|
|
|
17
24
|
//! js-skip: pxToClipLen — the length half of the same conversion; a px dimension is already in px on the Canvas2D side, so there is nothing to convert
|
|
18
25
|
//! js-skip: clipLenToPx — the inverse of pxToClipLen, same reason
|
|
19
26
|
//! js-skip: extendToMinWidthX — clip-space wrapper over the exported extendToMinWidthPx, which is the decision
|
|
20
|
-
//! js-skip: expandToMinWidthPx — the midpoint rule has no Canvas2D counterpart: the pileup floors a cell width one-sidedly and adds a seam fudge the shader deliberately omits (rendererTypes.ts pileupCellWidth), and the variant matrix floors in physical px against a canvas the Canvas2D twin sizes itself. A different rule, not a twin
|
|
21
27
|
//! js-skip: expandToMinWidthX — clip-space wrapper over expandToMinWidthPx, same reason as extendToMinWidthX
|
|
28
|
+
//! js-skip: expandToMinWidthPx — the float2 over expandToMinWidthLeftPx and RightPx, which coverageBar.slang exports as the twins; a float2 twin is a tuple per call, and the pileup walk allocating one per span measured 0.85x against 0.78x of its hand painter
|
|
22
29
|
//! js-skip: snapToPixelX — clip in, clip out; its px core is `floor(x + 0.5)`, which is Math.round and needs no twin
|
|
23
30
|
//! js-skip: snapBoxCenterY — clip-space wrapper over the exported snapBoxCenterYPx
|
|
24
31
|
module hpmath;
|
|
25
32
|
|
|
26
33
|
public static const uint HP_LOW_MASK = 0xFFFu;
|
|
34
|
+
public static const float HP_LOW_SPAN = 4096.0;
|
|
27
35
|
|
|
28
36
|
public float2 hpSplitUint(uint value) {
|
|
29
37
|
uint lo = value & HP_LOW_MASK;
|
|
@@ -31,33 +39,52 @@ public float2 hpSplitUint(uint value) {
|
|
|
31
39
|
return float2(float(hi), float(lo));
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
42
|
+
// A floor no bp difference can reach, so clamping to it changes no result.
|
|
43
|
+
// The clamp is there for the compiler: `max()` and `dot()` together stop it
|
|
44
|
+
// collapsing (hi_a+lo_a)-(hi_b+lo_b) into one large subtraction, which loses
|
|
45
|
+
// the float32 precision the hi/lo split exists to preserve. `hpZero` arrives
|
|
46
|
+
// as exactly 0 and carries no magnitude of its own — it makes the floor a
|
|
47
|
+
// runtime value, which is what a backend cannot fold the `max` against. The
|
|
48
|
+
// earlier spelling built the floor as `-1.0 / hpZero`, an infinity WGSL lets
|
|
49
|
+
// an implementation answer with an indeterminate value instead.
|
|
50
|
+
static const float HP_CLAMP_FLOOR = -3.0e38;
|
|
51
|
+
|
|
52
|
+
// Add a bp offset to an already-split position, keeping the lo lane inside the
|
|
53
|
+
// 0..4095 band the split exists for. Dropping the offset straight into lo makes
|
|
54
|
+
// it carry genomic magnitude, and `hpToClipX` then multiplies two cancelling
|
|
55
|
+
// large terms by `step` — the catastrophic subtraction the split avoids, moved
|
|
56
|
+
// one line later.
|
|
57
|
+
public float2 hpAddBp(float2 splitPos, float offsetBp) {
|
|
58
|
+
float hi = floor(offsetBp / HP_LOW_SPAN) * HP_LOW_SPAN;
|
|
59
|
+
return float2(splitPos.x + hi, splitPos.y + (offsetBp - hi));
|
|
60
|
+
}
|
|
38
61
|
public float hpToClipX(float2 splitPos, float3 bpRange, float hpZero) {
|
|
39
|
-
float
|
|
62
|
+
float floorBp = HP_CLAMP_FLOOR + hpZero;
|
|
40
63
|
float step = 2.0 / bpRange.z;
|
|
41
|
-
float hi = max(splitPos.x - bpRange.x,
|
|
42
|
-
float lo = max(splitPos.y - bpRange.y,
|
|
64
|
+
float hi = max(splitPos.x - bpRange.x, floorBp);
|
|
65
|
+
float lo = max(splitPos.y - bpRange.y, floorBp);
|
|
43
66
|
return dot(float3(-1.0, hi, lo), float3(1.0, step, step));
|
|
44
67
|
}
|
|
45
68
|
|
|
46
69
|
public float hpScaleLinear(float2 splitPos, float3 bpRange, float hpZero) {
|
|
47
|
-
float
|
|
70
|
+
float floorBp = HP_CLAMP_FLOOR + hpZero;
|
|
48
71
|
float step = 1.0 / bpRange.z;
|
|
49
|
-
float hi = max(splitPos.x - bpRange.x,
|
|
50
|
-
float lo = max(splitPos.y - bpRange.y,
|
|
72
|
+
float hi = max(splitPos.x - bpRange.x, floorBp);
|
|
73
|
+
float lo = max(splitPos.y - bpRange.y, floorBp);
|
|
51
74
|
return dot(float2(hi, lo), float2(step, step));
|
|
52
75
|
}
|
|
53
76
|
|
|
54
77
|
// Exact-as-possible bp offset from a split position to the viewport start
|
|
55
|
-
// stored in bpRange.xy. Subtracts the hi/lo components separately
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
// stored in bpRange.xy. Subtracts the hi/lo components separately so the result
|
|
79
|
+
// stays precise at high genomic coordinates — recombining bpStartHi+bpStartLo
|
|
80
|
+
// first would lose ~256bp near 3e9 — and takes `hpZero` for the same clamp
|
|
81
|
+
// hpToClipX does, which is what stops the compiler recombining them. Unlike
|
|
82
|
+
// hpToClipX this returns bp, not clip space.
|
|
83
|
+
public float hpViewportOffsetBp(float2 splitPos, float3 bpRange, float hpZero) {
|
|
84
|
+
float floorBp = HP_CLAMP_FLOOR + hpZero;
|
|
85
|
+
float hi = max(bpRange.x - splitPos.x, floorBp);
|
|
86
|
+
float lo = max(bpRange.y - splitPos.y, floorBp);
|
|
87
|
+
return hi + lo;
|
|
61
88
|
}
|
|
62
89
|
|
|
63
90
|
public float snapToPixelX(float clipX, float canvasWidth) {
|
|
@@ -91,30 +118,44 @@ public float snapBoxHeightPx(float heightPx) {
|
|
|
91
118
|
return even && hPx >= 2.0 && hPx <= THIN_BOX_PX ? hPx + 1.0 : hPx;
|
|
92
119
|
}
|
|
93
120
|
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// and
|
|
121
|
+
// A whole-pixel box centered in a whole-pixel row puts the center of an
|
|
122
|
+
// odd-height box exactly on a row boundary, where float error in how each
|
|
123
|
+
// caller sums its center would otherwise pick the row above or below.
|
|
124
|
+
static const float SNAP_TIE_BIAS_PX = 1.0 / 64.0;
|
|
125
|
+
|
|
126
|
+
// The pixel row a box's top edge lands on. Snapped about the box's CENTER, not
|
|
127
|
+
// its top: `snapBoxHeightPx` rounds the height and sometimes nudges it a whole
|
|
128
|
+
// pixel, and anchoring the top spends all of that off the BOTTOM edge. A box the
|
|
129
|
+
// layout deliberately centered inside its row then isn't — a UTR is
|
|
130
|
+
// center-shrunk to 0.65 of the transcript body, so at superCompact's 3px row its
|
|
131
|
+
// 1.95px box rounded up to 3px and drew a full pixel below the CDS beside it,
|
|
132
|
+
// while the intron line and strand arrow (which snap to the row's center row)
|
|
133
|
+
// sat on its top edge. Top-anchored and center-anchored agree wherever the
|
|
134
|
+
// height survives the snap unchanged, which is every full-height feature box.
|
|
135
|
+
public float snapBoxTopPx(float topY, float heightPx, float scrollY) {
|
|
136
|
+
float centerY = topY + heightPx * 0.5;
|
|
137
|
+
return floor(
|
|
138
|
+
centerY - snapBoxHeightPx(heightPx) * 0.5 - scrollY + 0.5 + SNAP_TIE_BIAS_PX
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// The pixel row holding the box's center, which a thin glyph (intron line,
|
|
143
|
+
// chevron, strand arrow) draws on. It is the middle row of every odd-height box
|
|
144
|
+
// with that center, so a center-shrunk UTR and its transcript's arrow agree
|
|
145
|
+
// whatever their parities; an even-height box's center row is whichever of its
|
|
146
|
+
// two middle rows the center falls in. Taking the lower middle row of an even
|
|
147
|
+
// box instead put the arrow a pixel under the UTR on an isoform stack, whose
|
|
148
|
+
// rows sit at fractional tops.
|
|
102
149
|
//
|
|
103
|
-
// Split
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
public float snapBoxCenterY(
|
|
115
|
-
float centerY, float heightPx, float scrollY, float canvasHeight
|
|
116
|
-
) {
|
|
117
|
-
return yPxToClipY(snapBoxCenterYPx(centerY, heightPx, scrollY), canvasHeight);
|
|
150
|
+
// Split from `snapBoxCenterY` because the Canvas2D and SVG paths need this
|
|
151
|
+
// decision in screen px; only the clip-space conversion is GPU-specific
|
|
152
|
+
// (adr-051).
|
|
153
|
+
public float snapBoxCenterYPx(float centerY, float scrollY) {
|
|
154
|
+
return floor(centerY - scrollY + SNAP_TIE_BIAS_PX) + 0.5;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public float snapBoxCenterY(float centerY, float scrollY, float canvasHeight) {
|
|
158
|
+
return yPxToClipY(snapBoxCenterYPx(centerY, scrollY), canvasHeight);
|
|
118
159
|
}
|
|
119
160
|
|
|
120
161
|
// Extend the free edge `x2` away from the anchor `x1` so the span is at least
|
|
@@ -138,7 +179,7 @@ public float extendToMinWidthPx(float x1, float x2, float minWidth) {
|
|
|
138
179
|
public float extendToMinWidthX(
|
|
139
180
|
float x1, float x2, float minWidthPx, float canvasWidth
|
|
140
181
|
) {
|
|
141
|
-
return extendToMinWidthPx(x1, x2, minWidthPx
|
|
182
|
+
return extendToMinWidthPx(x1, x2, pxToClipLen(minWidthPx, canvasWidth));
|
|
142
183
|
}
|
|
143
184
|
|
|
144
185
|
// The other min-width rule: widen a [x1, x2] span about its MIDPOINT, so both
|
|
@@ -154,18 +195,29 @@ public float extendToMinWidthX(
|
|
|
154
195
|
// what lets the alignments passes (clip space, a 1 CSS px floor) and the variant
|
|
155
196
|
// matrix (physical px, its own MIN_COL_PX) run one rule instead of two spellings
|
|
156
197
|
// of it.
|
|
198
|
+
// The two edges as scalars, which is what the Canvas2D twins export: a
|
|
199
|
+
// `float2` twin comes back as a tuple, and the pileup walk allocating one per
|
|
200
|
+
// span measured 0.85x against 0.78x of its hand painter
|
|
201
|
+
// (plugins/alignments/benches/rectWalker.bench.ts).
|
|
202
|
+
public float expandToMinWidthLeftPx(float x1, float x2, float minWidth) {
|
|
203
|
+
return x2 - x1 < minWidth ? (x1 + x2) * 0.5 - minWidth * 0.5 : x1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public float expandToMinWidthRightPx(float x1, float x2, float minWidth) {
|
|
207
|
+
return x2 - x1 < minWidth ? (x1 + x2) * 0.5 + minWidth * 0.5 : x2;
|
|
208
|
+
}
|
|
209
|
+
|
|
157
210
|
public float2 expandToMinWidthPx(float x1, float x2, float minWidth) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return float2(x1, x2);
|
|
211
|
+
return float2(
|
|
212
|
+
expandToMinWidthLeftPx(x1, x2, minWidth),
|
|
213
|
+
expandToMinWidthRightPx(x1, x2, minWidth)
|
|
214
|
+
);
|
|
163
215
|
}
|
|
164
216
|
|
|
165
217
|
public float2 expandToMinWidthX(
|
|
166
218
|
float x1, float x2, float minWidthPx, float canvasWidth
|
|
167
219
|
) {
|
|
168
|
-
return expandToMinWidthPx(x1, x2, minWidthPx
|
|
220
|
+
return expandToMinWidthPx(x1, x2, pxToClipLen(minWidthPx, canvasWidth));
|
|
169
221
|
}
|
|
170
222
|
|
|
171
223
|
// vid%6: 0→(0,0) 1→(1,0) 2→(0,1) 3→(0,1) 4→(1,0) 5→(1,1)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as linkGlsl from './linkMark.glsl.generated.ts'
|
|
2
|
+
import { linkValuePx } from './linkMark.js.generated.ts'
|
|
3
|
+
import * as linkWgsl from './linkMark.wgsl.generated.ts'
|
|
4
|
+
import * as pointGlsl from './pointMark.glsl.generated.ts'
|
|
5
|
+
import { pointYPx } from './pointMark.js.generated.ts'
|
|
6
|
+
import * as pointWgsl from './pointMark.wgsl.generated.ts'
|
|
7
|
+
import { SCALE_TYPE_LOG, SCALE_TYPE_SYMLOG } from './scoreScale.generated.ts'
|
|
8
|
+
|
|
9
|
+
// `valueScale.slang`'s `insetValueYPx` — the value scale over a range inset at
|
|
10
|
+
// both ends — with the two anchors its consumers add. A point measures down
|
|
11
|
+
// from the top of its row band, a link measures up from the band's baseline,
|
|
12
|
+
// so the two are reflections of one number and were two open-coded copies of
|
|
13
|
+
// it until they shared one.
|
|
14
|
+
|
|
15
|
+
const HEIGHTS = [8, 12, 20, 40, 100]
|
|
16
|
+
const INSETS = [0, 1, 2.5, 5, 9]
|
|
17
|
+
const DOMAINS: [number, number][] = [
|
|
18
|
+
[0, 1],
|
|
19
|
+
[-1, 1],
|
|
20
|
+
[0, 100],
|
|
21
|
+
[1, 1024],
|
|
22
|
+
[0.01, 0.5],
|
|
23
|
+
[5, 5],
|
|
24
|
+
]
|
|
25
|
+
const SCALES = [0, SCALE_TYPE_LOG, SCALE_TYPE_SYMLOG]
|
|
26
|
+
|
|
27
|
+
test('a link measures the same value up that a point measures down', () => {
|
|
28
|
+
for (const h of HEIGHTS) {
|
|
29
|
+
for (const insetPx of INSETS) {
|
|
30
|
+
for (const [min, max] of DOMAINS) {
|
|
31
|
+
for (const scale of SCALES) {
|
|
32
|
+
for (const v of [min, max, (min + max) / 2, min - 1, max + 1, 0.02]) {
|
|
33
|
+
expect(linkValuePx(v, min, max, h, scale, insetPx, 1)).toBeCloseTo(
|
|
34
|
+
h - pointYPx(v, min, max, h, scale, insetPx, 1),
|
|
35
|
+
9,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// The half-height clamp, which is the part of this that is not obvious. A band
|
|
45
|
+
// shorter than its two insets has no range left, and without the clamp the
|
|
46
|
+
// range goes negative and the scale runs backwards — an edge value drawn on
|
|
47
|
+
// the wrong side of the plot rather than merely squeezed.
|
|
48
|
+
test('a band shorter than two insets collapses to its midline, never inverts', () => {
|
|
49
|
+
for (const scale of SCALES) {
|
|
50
|
+
expect(pointYPx(0, 0, 100, 8, scale, 5, 1)).toBe(4)
|
|
51
|
+
expect(pointYPx(100, 0, 100, 8, scale, 5, 1)).toBe(4)
|
|
52
|
+
expect(linkValuePx(0, 0, 100, 8, scale, 5, 1)).toBe(4)
|
|
53
|
+
expect(linkValuePx(100, 0, 100, 8, scale, 5, 1)).toBe(4)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// The numbers above pin what the inset scale is. This pins that both shapes
|
|
58
|
+
// reach it on both backends: a numeric test of a function a shader has quietly
|
|
59
|
+
// stopped calling passes whatever that shader draws.
|
|
60
|
+
test.each([
|
|
61
|
+
['pointMark', pointWgsl.WGSL_SOURCE, pointGlsl.GLSL_VERTEX],
|
|
62
|
+
['linkMark', linkWgsl.WGSL_SOURCE, linkGlsl.GLSL_VERTEX],
|
|
63
|
+
])('%s reads the shared inset scale on every backend', (_name, wgsl, glsl) => {
|
|
64
|
+
for (const src of [wgsl, glsl]) {
|
|
65
|
+
expect(src).toContain('insetValueYPx_0(')
|
|
66
|
+
}
|
|
67
|
+
})
|