chartai 0.0.11 → 1.0.0

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.
Files changed (78) hide show
  1. package/dist/chart-library.d.ts +33 -146
  2. package/dist/chart-library.d.ts.map +1 -1
  3. package/dist/chart-library.js +378 -321
  4. package/dist/chart-library.min.js +1 -1
  5. package/dist/charts/area.d.ts +6 -0
  6. package/dist/charts/area.d.ts.map +1 -0
  7. package/dist/charts/area.js +65 -0
  8. package/dist/charts/area.min.js +1 -0
  9. package/dist/charts/bar.d.ts +11 -0
  10. package/dist/charts/bar.d.ts.map +1 -0
  11. package/dist/charts/bar.js +65 -0
  12. package/dist/charts/bar.min.js +1 -0
  13. package/dist/charts/boids.js +167 -0
  14. package/dist/charts/boids.min.js +18 -0
  15. package/dist/charts/candlestick.d.ts +21 -0
  16. package/dist/charts/candlestick.d.ts.map +1 -0
  17. package/dist/charts/candlestick.js +132 -0
  18. package/dist/charts/candlestick.min.js +32 -0
  19. package/dist/charts/line.d.ts +12 -0
  20. package/dist/charts/line.d.ts.map +1 -0
  21. package/dist/charts/line.js +62 -0
  22. package/dist/charts/line.min.js +1 -0
  23. package/dist/charts/scatter.d.ts +11 -0
  24. package/dist/charts/scatter.d.ts.map +1 -0
  25. package/dist/charts/scatter.js +46 -0
  26. package/dist/charts/scatter.min.js +1 -0
  27. package/dist/chunk-0jepamv9.js +7 -0
  28. package/dist/chunk-1p45ex5n.min.js +2 -0
  29. package/dist/chunk-831dem4f.js +4 -0
  30. package/dist/chunk-93yrr7er.js +35 -0
  31. package/dist/chunk-94kc81rr.min.js +2 -0
  32. package/dist/chunk-a27be8p9.js +105 -0
  33. package/dist/chunk-bfyv7z27.min.js +2 -0
  34. package/dist/chunk-dmaxrg6s.min.js +2 -0
  35. package/dist/chunk-e7d3zgw5.min.js +2 -0
  36. package/dist/chunk-g6m56ptf.js +609 -0
  37. package/dist/chunk-m17t3vjq.js +9 -0
  38. package/dist/chunk-me3qaz3m.min.js +2 -0
  39. package/dist/chunk-qr6mweck.min.js +2 -0
  40. package/dist/chunk-yabjrff2.js +11 -0
  41. package/dist/gpu-worker.js +625 -686
  42. package/dist/gpu-worker.min.js +1 -1
  43. package/dist/msg.d.ts +33 -0
  44. package/dist/msg.d.ts.map +1 -0
  45. package/dist/plugins/coords.d.ts +18 -0
  46. package/dist/plugins/coords.d.ts.map +1 -0
  47. package/dist/plugins/hover.d.ts +15 -2
  48. package/dist/plugins/hover.d.ts.map +1 -1
  49. package/dist/plugins/hover.js +103 -16
  50. package/dist/plugins/hover.min.js +1 -1
  51. package/dist/plugins/labels-panel.d.ts +4 -0
  52. package/dist/plugins/labels-panel.d.ts.map +1 -0
  53. package/dist/plugins/labels-panel.js +122 -0
  54. package/dist/plugins/labels-panel.min.js +1 -0
  55. package/dist/plugins/labels.d.ts +17 -2
  56. package/dist/plugins/labels.d.ts.map +1 -1
  57. package/dist/plugins/labels.js +11 -99
  58. package/dist/plugins/labels.min.js +1 -1
  59. package/dist/plugins/legend.d.ts +16 -0
  60. package/dist/plugins/legend.d.ts.map +1 -0
  61. package/dist/plugins/legend.js +282 -0
  62. package/dist/plugins/legend.min.js +21 -0
  63. package/dist/plugins/shared.d.ts +7 -0
  64. package/dist/plugins/shared.d.ts.map +1 -0
  65. package/dist/plugins/zoom.d.ts +10 -2
  66. package/dist/plugins/zoom.d.ts.map +1 -1
  67. package/dist/plugins/zoom.js +227 -97
  68. package/dist/plugins/zoom.min.js +1 -1
  69. package/dist/types.d.ts +179 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +0 -0
  72. package/dist/types.min.js +0 -0
  73. package/dist/worker-inline.d.ts +1 -1
  74. package/dist/worker-inline.d.ts.map +1 -1
  75. package/package.json +13 -12
  76. package/readme.md +51 -42
  77. package/dist/chunk-bgfkgcmg.js +0 -25
  78. package/dist/chunk-cj3zanvs.min.js +0 -2
@@ -1,560 +1,313 @@
1
- import"./chunk-bgfkgcmg.js";
2
-
3
- // src/shaders/shared.ts
4
- var COMPUTE_WG = 256;
5
- var UNIFORM_STRUCT = `struct Uniforms{width: f32,height: f32,viewMinX: f32,viewMaxX: f32,viewMinY: f32,viewMaxY: f32,pointCount: u32,isDark: f32,bgR: f32,bgG: f32,bgB: f32,pointRadius: f32,dataMinY: f32,dataMaxY: f32,dataMinX: f32,dataMaxX: f32,visibleStart: u32,visibleCount: u32,dispatchXCount: u32,maxSamplesPerPixel: u32,seriesCount: u32,_pad2: u32,_pad3: u32,_pad4: u32,};struct SeriesInfo{color: vec4f,visibleRange: vec2u,pointSize: f32,_pad: f32,};struct SeriesIndex{index: u32,_pad0: u32,_pad1: u32,_pad2: u32,};`;
6
- var BINARY_SEARCH = `fn lowerBound(val: f32,count: u32)-> u32{var lo = 0u;var hi = count;while(lo < hi){let mid =(lo + hi)/ 2u;if(dataX[mid] < val){lo = mid + 1u;}else{hi = mid;}}return lo;}`;
7
- var FXAA_SHADER = `fn l(c:vec4f)->f32{return dot(c.rgb,vec3f(.299,.587,.114))+c.a*.25;}fn fxaa(u:vec2f,t:texture_2d<f32>,s:sampler)->vec4f{let r=1./vec2f(textureDimensions(t));let rM=textureSampleLevel(t,s,u,0.);let rN=textureSampleLevel(t,s,u+vec2f(0.,-r.y),0.);let rS=textureSampleLevel(t,s,u+vec2f(0.,r.y),0.);let rE=textureSampleLevel(t,s,u+vec2f(r.x,0.),0.);let rW=textureSampleLevel(t,s,u+vec2f(-r.x,0.),0.);let lM=l(rM);let lN=l(rN);let lS=l(rS);let lE=l(rE);let lW=l(rW);let mi=min(lM,min(min(lN,lS),min(lE,lW)));let ma=max(lM,max(max(lN,lS),max(lE,lW)));let ra=ma-mi;if(ra<max(.0833,ma*.166)){return rM;}let lNW=l(textureSampleLevel(t,s,u+vec2f(-r.x,-r.y),0.));let lNE=l(textureSampleLevel(t,s,u+vec2f(r.x,-r.y),0.));let lSW=l(textureSampleLevel(t,s,u+vec2f(-r.x,r.y),0.));let lSE=l(textureSampleLevel(t,s,u+vec2f(r.x,r.y),0.));let sB=min(.35,max(0.,abs((lN+lS+lE+lW)*.25-lM)/ra-.25)*1.33);let iH=abs(lNW+lNE-2.*lN)+abs(lW+lE-2.*lM)*2.+abs(lSW+lSE-2.*lS)>=abs(lNW+lSW-2.*lW)+abs(lN+lS-2.*lM)*2.+abs(lNE+lSE-2.*lE);let l1=select(lW,lN,iH);let l2=select(lE,lS,iH);let pD=abs(l2-lM)>abs(l1-lM);let sL=select(r.x,r.y,iH);let lA=.5*(select(l1,l2,pD)+lM);let gS=max(abs(l1-lM),abs(l2-lM))*.25;var eU=u;if(iH){eU.y+=select(-.5,.5,pD)*sL;}else{eU.x+=select(-.5,.5,pD)*sL;}let eS=select(vec2f(r.x,0.),vec2f(0.,r.y),iH);var uN=eU-eS;var uP=eU+eS;var eN=l(textureSampleLevel(t,s,uN,0.))-lA;var eP=l(textureSampleLevel(t,s,uP,0.))-lA;var dN=abs(eN)>=gS;var dP=abs(eP)>=gS;for(var i=1;i<8;i++){if(!dN){uN-=eS*1.5;eN=l(textureSampleLevel(t,s,uN,0.))-lA;dN=abs(eN)>=gS;}if(!dP){uP+=eS*1.5;eP=l(textureSampleLevel(t,s,uP,0.))-lA;dP=abs(eP)>=gS;}if(dN&&dP){break;}}let dtN=select(u.x-uN.x,u.y-uN.y,iH);let dtP=select(uP.x-u.x,uP.y-u.y,iH);let eB=select(0.,.5-min(dtN,dtP)/(dtN+dtP),(lM-lA<0.)!=(select(eP<0.,eN<0.,dtN<dtP)));var fU=u;let fL=max(eB,sB);if(iH){fU.y+=select(-1.,1.,pD)*fL*sL;}else{fU.x+=select(-1.,1.,pD)*fL*sL;}return textureSampleLevel(t,s,fU,0.);}`;
8
- var FXAA_RENDER_SHADER = `${UNIFORM_STRUCT}${FXAA_SHADER}@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var inputTex: texture_2d<f32>;@group(0)@binding(2)var samp: sampler;struct VertexOutput{@builtin(position)pos: vec4f,@location(0)uv: vec2f,};@vertex fn vs(@builtin(vertex_index)vi: u32)-> VertexOutput{var positions = array<vec2f,4>(vec2f(-1.0,-1.0),vec2f(1.0,-1.0),vec2f(-1.0,1.0),vec2f(1.0,1.0));var uvs = array<vec2f,4>(vec2f(0.0,1.0),vec2f(1.0,1.0),vec2f(0.0,0.0),vec2f(1.0,0.0));var out: VertexOutput;out.pos = vec4f(positions[vi],0.0,1.0);out.uv = uvs[vi];return out;}@fragment fn fs(in: VertexOutput)-> @location(0)vec4f{return fxaa(in.uv,inputTex,samp);}`;
9
-
10
- // src/shaders/line.ts
11
- var LINE_COMPUTE_SHADER = `${UNIFORM_STRUCT}struct LineData{screenX: f32,minScreenY: f32,maxScreenY: f32,valid: f32,};@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var<storage,read> dataX: array<f32>;@group(0)@binding(2)var<storage,read> dataY: array<f32>;@group(0)@binding(3)var<storage,read_write> lineData: array<LineData>;@group(0)@binding(4)var<storage,read> allSeries: array<SeriesInfo>;${BINARY_SEARCH}@compute @workgroup_size(${COMPUTE_WG})fn main(@builtin(global_invocation_id)id: vec3u){let outputIdx = id.x;let maxCols = u32(u.width);let count = u.pointCount;if(outputIdx >= maxCols || count == 0u){if(outputIdx < maxCols){lineData[outputIdx] = LineData(-1.0,-1.0,-1.0,0.0);}return;}let viewRangeX = u.viewMaxX - u.viewMinX;let viewRangeY = u.viewMaxY - u.viewMinY;if(viewRangeX < 0.0001 || viewRangeY < 0.0001){lineData[outputIdx] = LineData(-1.0,-1.0,-1.0,0.0);return;}let relPx = f32(outputIdx);let pixelMinX = u.viewMinX +(relPx / u.width)* viewRangeX;let pixelMaxX = u.viewMinX +((relPx + 1.0)/ u.width)* viewRangeX;let startIdx = lowerBound(pixelMinX,count);var endIdx = lowerBound(pixelMaxX,count);endIdx = min(endIdx,count);let centerX =(pixelMinX + pixelMaxX)* 0.5;if(startIdx >= endIdx){var bestIdx = startIdx;if(startIdx > 0u && startIdx < count){let distPrev = abs(dataX[startIdx - 1u] - centerX);let distCurr = abs(dataX[startIdx] - centerX);if(distPrev < distCurr){bestIdx = startIdx - 1u;}}else if(startIdx >= count && count > 0u){bestIdx = count - 1u;}if(bestIdx >= count){lineData[outputIdx] = LineData(-1.0,-1.0,-1.0,0.0);return;}let y = dataY[bestIdx];let normY =(y - u.viewMinY)/ viewRangeY;let screenY = 1.0 - normY;let normX =(dataX[bestIdx] - u.viewMinX)/ viewRangeX;let screenX = normX;lineData[outputIdx] = LineData(screenX,screenY,screenY,1.0);return;}var dataMinY = dataY[startIdx];var dataMaxY = dataY[startIdx];let rangeCount = endIdx - startIdx;let maxSamples = u.maxSamplesPerPixel;if(maxSamples > 1u && rangeCount > maxSamples){let stride = f32(rangeCount - 1u)/ f32(maxSamples - 1u);for(var s = 0u;s < maxSamples;s++){let idx = startIdx + u32(f32(s)* stride);if(idx < endIdx){let y = dataY[idx];dataMinY = min(dataMinY,y);dataMaxY = max(dataMaxY,y);}}let lastY = dataY[endIdx - 1u];dataMinY = min(dataMinY,lastY);dataMaxY = max(dataMaxY,lastY);}else{for(var i = startIdx + 1u;i < endIdx;i++){let y = dataY[i];dataMinY = min(dataMinY,y);dataMaxY = max(dataMaxY,y);}}let normX =(centerX - u.viewMinX)/ viewRangeX;let screenX = normX;let normMaxY =(dataMaxY - u.viewMinY)/ viewRangeY;let normMinY =(dataMinY - u.viewMinY)/ viewRangeY;let minScreenY = 1.0 - normMaxY;let maxScreenY = 1.0 - normMinY;lineData[outputIdx] = LineData(screenX,minScreenY,maxScreenY,1.0);}`;
12
- var LINE_RENDER_SHADER = `${UNIFORM_STRUCT}struct LineData{screenX: f32,minScreenY: f32,maxScreenY: f32,valid: f32,};@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var<storage,read> lineData: array<LineData>;@group(0)@binding(2)var<storage,read> allSeries: array<SeriesInfo>;struct VertexOutput{@builtin(position)pos: vec4f,@location(0)alpha: f32,@location(1)@interpolate(flat)seriesIdx: u32,};@vertex fn vs(@builtin(vertex_index)vi: u32,@builtin(instance_index)series_idx: u32)-> VertexOutput{var out: VertexOutput;out.seriesIdx = series_idx;let maxCols = u32(u.width);let segIdx = vi / 2u;let endpoint = vi % 2u;if(segIdx < maxCols){let d = lineData[segIdx];let y = select(d.maxScreenY,d.minScreenY,endpoint == 0u);out.pos = vec4f(d.screenX * 2.0 - 1.0,1.0 - y * 2.0,0.0,d.valid);out.alpha = d.valid;}else{let connIdx = segIdx - maxCols;if(connIdx + 1u >= maxCols){out.pos = vec4f(0.0,0.0,0.0,0.0);out.alpha = 0.0;return out;}let d0 = lineData[connIdx];let d1 = lineData[connIdx + 1u];let segValid = min(d0.valid,d1.valid);if(endpoint == 0u){let midY =(d0.minScreenY + d0.maxScreenY)* 0.5;out.pos = vec4f(d0.screenX * 2.0 - 1.0,1.0 - midY * 2.0,0.0,segValid);}else{let midY =(d1.minScreenY + d1.maxScreenY)* 0.5;out.pos = vec4f(d1.screenX * 2.0 - 1.0,1.0 - midY * 2.0,0.0,segValid);}out.alpha = segValid;}return out;}@fragment fn fs(in: VertexOutput)-> @location(0)vec4f{if(in.alpha < 0.1){discard;}let series = allSeries[in.seriesIdx];return vec4f(series.color.rgb,1.0);}`;
13
-
14
- // src/shaders/scatter.ts
15
- var SCATTER_COMPUTE_SHADER = `${UNIFORM_STRUCT}@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var<storage,read> dataX: array<f32>;@group(0)@binding(2)var<storage,read> dataY: array<f32>;@group(0)@binding(3)var outputTex: texture_storage_2d<rgba8unorm,write>;@group(0)@binding(4)var<storage,read> allSeries: array<SeriesInfo>;@group(0)@binding(5)var<uniform> seriesIdx: SeriesIndex;@compute @workgroup_size(${COMPUTE_WG})fn main(@builtin(global_invocation_id)id: vec3u){let series = allSeries[seriesIdx.index];let visStart = series.visibleRange.x;let visCount = series.visibleRange.y;let localIdx = id.y * u.dispatchXCount + id.x;if(localIdx >= visCount){return;}let idx = visStart + localIdx;let count = u.pointCount;if(idx >= count){return;}let x = dataX[idx];let y = dataY[idx];if(y < u.viewMinY || y > u.viewMaxY){return;}let width = u32(u.width);let height = u32(u.height);let rangeX = u.viewMaxX - u.viewMinX;let rangeY = u.viewMaxY - u.viewMinY;if(rangeX < 0.0001 || rangeY < 0.0001){return;}let normX =(x - u.viewMinX)/ rangeX;let normY =(y - u.viewMinY)/ rangeY;let screenX = normX;let screenY = 1.0 - normY;let pixelX = i32(screenX * f32(width));let pixelY = i32(screenY * f32(height));if(idx > visStart){let prevX = dataX[idx - 1u];let prevY = dataY[idx - 1u];let prevNormX =(prevX - u.viewMinX)/ rangeX;let prevNormY =(prevY - u.viewMinY)/ rangeY;let prevPx = i32(prevNormX * f32(width));let prevPy = i32((1.0 - prevNormY)* f32(height));if(pixelX == prevPx && pixelY == prevPy){return;}}let iWidth = i32(width);let iHeight = i32(height);if(pixelX < 0 || pixelX >= iWidth){return;}if(pixelY < 0 || pixelY >= iHeight){return;}let color = series.color;let radius = i32(series.pointSize);for(var dy = -radius;dy <= radius;dy++){for(var dx = -radius;dx <= radius;dx++){if(dx * dx + dy * dy > radius * radius){continue;}let px = pixelX + dx;let py = pixelY + dy;if(px >= 0 && px < iWidth && py >= 0 && py < iHeight){textureStore(outputTex,vec2i(px,py),color);}}}}`;
16
-
17
- // src/shaders/box.ts
18
- var BOX_COMPUTE_SHADER = `${UNIFORM_STRUCT}struct BarData{screenX: f32,minY: f32,maxY: f32,barWidth: f32,};@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var<storage,read> dataX: array<f32>;@group(0)@binding(2)var<storage,read> dataY: array<f32>;@group(0)@binding(3)var<storage,read_write> barData: array<BarData>;@group(0)@binding(4)var<storage,read> allSeries: array<SeriesInfo>;@group(0)@binding(5)var<uniform> seriesIdx: SeriesIndex;${BINARY_SEARCH}fn barHalfWidth(idx: u32,count: u32)-> f32{if(count <= 1u){return(u.viewMaxX - u.viewMinX)* 0.4;}var spacing: f32;if(idx == 0u){spacing = dataX[1u] - dataX[0u];}else if(idx >= count - 1u){spacing = dataX[count - 1u] - dataX[count - 2u];}else{spacing = min(dataX[idx + 1u] - dataX[idx],dataX[idx] - dataX[idx - 1u]);}let seriesCount = max(1u,u.seriesCount);return(spacing * 0.4)/ f32(seriesCount);}@compute @workgroup_size(${COMPUTE_WG})fn main(@builtin(global_invocation_id)id: vec3u){let outputIdx = id.x;let maxCols = u32(u.width);let count = u.pointCount;if(outputIdx >= maxCols || count == 0u){if(outputIdx < maxCols){barData[outputIdx] = BarData(0.0,0.0,0.0,0.0);}return;}let viewRangeX = u.viewMaxX - u.viewMinX;let viewRangeY = u.viewMaxY - u.viewMinY;if(viewRangeX < 0.0001 || viewRangeY < 0.0001){barData[outputIdx] = BarData(0.0,0.0,0.0,0.0);return;}let relPx = f32(outputIdx);let pixelMinX = u.viewMinX +(relPx / u.width)* viewRangeX;let pixelMaxX = u.viewMinX +((relPx + 1.0)/ u.width)* viewRangeX;let startIdx = lowerBound(pixelMinX,count);var endIdx = lowerBound(pixelMaxX,count);endIdx = min(endIdx,count);let centerX =(pixelMinX + pixelMaxX)* 0.5;let onePixel = 1.0 / u.width;if(startIdx >= endIdx){var hit = false;var bestX: f32 = 0.0;var bestY: f32 = 0.0;var bestHW: f32 = 0.0;var bestDist: f32 = 1e10;if(startIdx < count){let bx = dataX[startIdx];let hw = barHalfWidth(startIdx,count);if(pixelMinX < bx + hw && pixelMaxX > bx - hw){let d = abs(bx - centerX);bestX = bx;bestY = dataY[startIdx];bestHW = hw;bestDist = d;hit = true;}}if(startIdx > 0u){let prev = startIdx - 1u;let bx = dataX[prev];let hw = barHalfWidth(prev,count);if(pixelMinX < bx + hw && pixelMaxX > bx - hw){let d = abs(bx - centerX);if(d < bestDist){bestX = bx;bestY = dataY[prev];bestHW = hw;bestDist = d;}hit = true;}}if(!hit){barData[outputIdx] = BarData(0.0,0.0,0.0,0.0);return;}let seriesCount = max(1u,u.seriesCount);let barOffset =(f32(seriesIdx.index)- f32(seriesCount - 1u)* 0.5)*(bestHW * 2.0);let offsetX = bestX + barOffset;let normX =(offsetX - u.viewMinX)/ viewRangeX;let fullWidth = bestHW * 2.0 / viewRangeX;let gapSize = max(onePixel,fullWidth * 0.05);let bw = max(fullWidth - gapSize,onePixel);barData[outputIdx] = BarData(normX,bestY,bestY,bw);return;}var dataMinY = dataY[startIdx];var dataMaxY = dataY[startIdx];let rangeCount = endIdx - startIdx;let maxSamples = u.maxSamplesPerPixel;if(maxSamples > 0u && rangeCount > maxSamples){let stride = f32(rangeCount - 1u)/ f32(maxSamples - 1u);for(var s = 0u;s < maxSamples;s++){let idx = startIdx + u32(f32(s)* stride);if(idx < endIdx){let y = dataY[idx];dataMinY = min(dataMinY,y);dataMaxY = max(dataMaxY,y);}}let lastY = dataY[endIdx - 1u];dataMinY = min(dataMinY,lastY);dataMaxY = max(dataMaxY,lastY);}else{for(var i = startIdx + 1u;i < endIdx;i++){let y = dataY[i];dataMinY = min(dataMinY,y);dataMaxY = max(dataMaxY,y);}}let hw = barHalfWidth(startIdx,count);let fullWidth = hw * 2.0 / viewRangeX;let gapSize = max(onePixel,fullWidth * 0.05);let bw = max(fullWidth - gapSize,onePixel);let seriesCount = max(1u,u.seriesCount);let barOffset =(f32(seriesIdx.index)- f32(seriesCount - 1u)* 0.5)*(hw * 2.0);let dataX_centered = dataX[startIdx] + barOffset;let normX =(dataX_centered - u.viewMinX)/ viewRangeX;barData[outputIdx] = BarData(normX,dataMinY,dataMaxY,bw);}`;
19
- var BOX_RENDER_SHADER = `${UNIFORM_STRUCT}struct BarData{screenX: f32,minY: f32,maxY: f32,barWidth: f32,};@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var<storage,read> barData: array<BarData>;@group(0)@binding(2)var<storage,read> allSeries: array<SeriesInfo>;struct VertexOutput{@builtin(position)pos: vec4f,@location(0)normY: f32,@location(1)@interpolate(flat)seriesIdx: u32,};@vertex fn vs(@builtin(vertex_index)vi: u32,@builtin(instance_index)series_idx: u32)-> VertexOutput{var out: VertexOutput;out.seriesIdx = series_idx;let maxCols = u32(u.width);let colIdx = vi / 6u;let vertexType = vi % 6u;if(colIdx >= maxCols){out.pos = vec4f(0.0,0.0,0.0,0.0);out.normY = 0.0;return out;}let bd = barData[colIdx];if(bd.barWidth <= 0.0){out.pos = vec4f(0.0,0.0,0.0,0.0);out.normY = 0.0;return out;}let viewRangeY = u.viewMaxY - u.viewMinY;let safeRangeY = select(viewRangeY,1.0,viewRangeY < 0.0001);let normMinY =(min(bd.minY,0.0)- u.viewMinY)/ safeRangeY;let normMaxY =(max(bd.maxY,0.0)- u.viewMinY)/ safeRangeY;let top = 1.0 - normMaxY;let bottom = 1.0 - normMinY;let halfW = bd.barWidth * 0.5;let left = bd.screenX - halfW;let right = bd.screenX + halfW;var positions = array<vec2f,6>(vec2f(left,bottom),vec2f(right,bottom),vec2f(left,top),vec2f(left,top),vec2f(right,bottom),vec2f(right,top));let screenPos = positions[vertexType];let clipX = screenPos.x * 2.0 - 1.0;let clipY = 1.0 - screenPos.y * 2.0;out.pos = vec4f(clipX,clipY,0.0,1.0);out.normY = normMaxY;return out;}@fragment fn fs(in: VertexOutput)-> @location(0)vec4f{let series = allSeries[in.seriesIdx];return vec4f(series.color.rgb,0.85);}`;
1
+ import {
2
+ BLIT_SHADER
3
+ } from "./chunk-0jepamv9.js";
4
+ import {
5
+ E,
6
+ M
7
+ } from "./chunk-93yrr7er.js";
8
+ import"./chunk-m17t3vjq.js";
20
9
 
21
10
  // src/gpu-worker.ts
22
11
  var device;
23
12
  var canvasFormat;
24
13
  var charts = new Map;
14
+ var renderers = new Map;
25
15
  var isDark = false;
26
- var layouts = {};
27
- var pipelines = {};
16
+ var blitPipeline;
17
+ var blitLayout;
18
+ var blitSampler;
28
19
  var frameCount = 0;
29
20
  var lastRenderMs = 0;
30
21
  var renderScheduled = false;
31
22
  var statsInterval = null;
32
- var uniformStagingBuffer = new ArrayBuffer(112);
23
+ var uniformStagingBuffer = new ArrayBuffer(64);
33
24
  var uniformStagingF32 = new Float32Array(uniformStagingBuffer);
34
25
  var uniformStagingU32 = new Uint32Array(uniformStagingBuffer);
35
- var fxaaSampler;
36
- var writeAllSeriesData = (chart, perSeriesPointSize) => {
26
+ function parseUsageFlags(usages) {
27
+ let flags = GPUBufferUsage.COPY_DST;
28
+ for (const u of usages) {
29
+ switch (u.toUpperCase()) {
30
+ case "STORAGE":
31
+ flags |= GPUBufferUsage.STORAGE;
32
+ break;
33
+ case "VERTEX":
34
+ flags |= GPUBufferUsage.VERTEX;
35
+ break;
36
+ case "UNIFORM":
37
+ flags |= GPUBufferUsage.UNIFORM;
38
+ break;
39
+ case "COPY_SRC":
40
+ flags |= GPUBufferUsage.COPY_SRC;
41
+ break;
42
+ case "COPY_DST":
43
+ flags |= GPUBufferUsage.COPY_DST;
44
+ break;
45
+ case "INDEX":
46
+ flags |= GPUBufferUsage.INDEX;
47
+ break;
48
+ case "INDIRECT":
49
+ flags |= GPUBufferUsage.INDIRECT;
50
+ break;
51
+ }
52
+ }
53
+ return flags;
54
+ }
55
+ function getBindingLayoutEntry(source, write, passType) {
56
+ const visibility = passType === "compute" ? GPUShaderStage.COMPUTE : GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT;
57
+ if (source === "uniforms" || source === "custom-uniforms" || source === "series-index") {
58
+ return { visibility, buffer: { type: "uniform" } };
59
+ }
60
+ if (source === "render-target") {
61
+ if (write) {
62
+ return {
63
+ visibility: GPUShaderStage.COMPUTE,
64
+ storageTexture: { access: "write-only", format: "rgba8unorm" }
65
+ };
66
+ }
67
+ return { visibility, texture: { sampleType: "float" } };
68
+ }
69
+ if (write) {
70
+ return { visibility, buffer: { type: "storage" } };
71
+ }
72
+ return { visibility, buffer: { type: "read-only-storage" } };
73
+ }
74
+ function getBindingResource(source, write, chart, series, renderer) {
75
+ switch (source) {
76
+ case "uniforms":
77
+ return { buffer: chart.uniformBuffer };
78
+ case "custom-uniforms":
79
+ return { buffer: chart.customUniformBuffer };
80
+ case "series-info":
81
+ return { buffer: chart.seriesStorageBuffer };
82
+ case "render-target":
83
+ return chart.outputTextureView;
84
+ case "x-data":
85
+ return { buffer: series.dataX };
86
+ case "y-data":
87
+ return { buffer: series.dataY };
88
+ case "series-index":
89
+ return { buffer: series.seriesIndexBuffer };
90
+ }
91
+ if (source.endsWith("-data")) {
92
+ const field = source.slice(0, -5);
93
+ return { buffer: series.extraBuffers.get(field) };
94
+ }
95
+ const bufDef = renderer.config.bufferDefs.find((b) => b.name === source);
96
+ if (bufDef?.perSeries) {
97
+ return { buffer: series.seriesBuffers.get(source) };
98
+ }
99
+ return { buffer: chart.chartBuffers.get(source) };
100
+ }
101
+ function compileRenderer(config) {
102
+ const pipelines = new Map;
103
+ const passLayouts = new Map;
104
+ for (let passIdx = 0;passIdx < config.passes.length; passIdx++) {
105
+ const pass = config.passes[passIdx];
106
+ const layoutEntries = pass.bindings.map((b) => ({
107
+ binding: b.binding,
108
+ ...getBindingLayoutEntry(b.source, b.write, pass.type)
109
+ }));
110
+ const layout = device.createBindGroupLayout({ entries: layoutEntries });
111
+ passLayouts.set(`pass-${passIdx}`, layout);
112
+ const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [layout] });
113
+ const shaderModule = device.createShaderModule({ code: config.shaders[pass.shader] });
114
+ if (pass.type === "compute") {
115
+ pipelines.set(`pass-${passIdx}`, device.createComputePipeline({
116
+ layout: pipelineLayout,
117
+ compute: { module: shaderModule, entryPoint: "main" }
118
+ }));
119
+ } else {
120
+ pipelines.set(`pass-${passIdx}`, device.createRenderPipeline({
121
+ layout: pipelineLayout,
122
+ vertex: { module: shaderModule, entryPoint: "vs" },
123
+ fragment: {
124
+ module: shaderModule,
125
+ entryPoint: "fs",
126
+ targets: [{ format: "rgba8unorm", blend: pass.blend }]
127
+ },
128
+ primitive: { topology: pass.topology ?? "triangle-list" }
129
+ }));
130
+ }
131
+ }
132
+ return { config, pipelines, passLayouts };
133
+ }
134
+ function writeAllSeriesData(chart) {
37
135
  if (!chart.seriesStorageBuffer || chart.series.length === 0)
38
136
  return;
39
137
  const data = new Float32Array(chart.series.length * 8);
40
138
  const dataU32 = new Uint32Array(data.buffer);
41
139
  for (let i = 0;i < chart.series.length; i++) {
42
140
  const s = chart.series[i];
43
- const offset = i * 8;
44
- data[offset + 0] = s.colorR;
45
- data[offset + 1] = s.colorG;
46
- data[offset + 2] = s.colorB;
47
- data[offset + 3] = 1;
48
- dataU32[offset + 4] = s.visibleStart;
49
- dataU32[offset + 5] = s.visibleCount;
50
- data[offset + 6] = perSeriesPointSize?.[i] ?? chart.pointSize;
51
- data[offset + 7] = 0;
141
+ const off = i * 8;
142
+ data[off + 0] = s.colorR;
143
+ data[off + 1] = s.colorG;
144
+ data[off + 2] = s.colorB;
145
+ data[off + 3] = 1;
146
+ dataU32[off + 4] = s.visibleStart;
147
+ dataU32[off + 5] = s.visibleCount;
52
148
  }
53
149
  device.queue.writeBuffer(chart.seriesStorageBuffer, 0, data);
54
- };
55
- var writeUniforms = (chart, series, seriesIndex, pointSize) => {
150
+ }
151
+ function writeUniforms(chart, series) {
56
152
  const f32 = uniformStagingF32;
57
153
  const u32 = uniformStagingU32;
58
- const rx = chart.maxX - chart.minX, ry = chart.maxY - chart.minY;
154
+ const rx = chart.maxX - chart.minX;
155
+ const ry = chart.maxY - chart.minY;
59
156
  const bg = chart.bgColor ?? (isDark ? [0.11, 0.11, 0.12] : [0.98, 0.98, 0.98]);
60
- f32.set([
61
- chart.width,
62
- chart.height,
63
- chart.minX + chart.panX * rx,
64
- chart.minX + chart.panX * rx + rx / chart.zoomX,
65
- chart.minY + chart.panY * ry,
66
- chart.minY + chart.panY * ry + ry / chart.zoomY
67
- ], 0);
157
+ f32[0] = chart.width;
158
+ f32[1] = chart.height;
159
+ f32[2] = chart.minX + chart.panX * rx;
160
+ f32[3] = chart.minX + chart.panX * rx + rx / chart.zoomX;
161
+ f32[4] = chart.minY + chart.panY * ry;
162
+ f32[5] = chart.minY + chart.panY * ry + ry / chart.zoomY;
68
163
  u32[6] = series.pointCount;
69
- f32.set([
70
- isDark ? 1 : 0,
71
- ...bg,
72
- pointSize ?? chart.pointSize,
73
- chart.minY,
74
- chart.maxY,
75
- chart.minX,
76
- chart.maxX
77
- ], 7);
78
- u32[16] = series.visibleStart;
79
- u32[17] = series.visibleCount;
80
- u32[18] = 0;
81
- u32[19] = chart.maxSamplesPerPixel;
82
- u32[20] = chart.series.length;
164
+ u32[7] = chart.series.length;
165
+ u32[8] = isDark ? 1 : 0;
166
+ f32[9] = bg[0];
167
+ f32[10] = bg[1];
168
+ f32[11] = bg[2];
169
+ f32[12] = chart.minX;
170
+ f32[13] = chart.maxX;
171
+ f32[14] = chart.minY;
172
+ f32[15] = chart.maxY;
83
173
  device.queue.writeBuffer(chart.uniformBuffer, 0, uniformStagingBuffer);
84
- };
85
- var createChartPipeline = (name, cShader, rShader, topology, blend) => {
86
- const cMod = device.createShaderModule({ code: cShader }), rMod = device.createShaderModule({ code: rShader });
87
- pipelines[`${name}Compute`] = device.createComputePipeline({
88
- layout: device.createPipelineLayout({
89
- bindGroupLayouts: [layouts[`${name}Compute`]]
90
- }),
91
- compute: { module: cMod, entryPoint: "main" }
92
- });
93
- pipelines[`${name}Render`] = device.createRenderPipeline({
94
- layout: device.createPipelineLayout({
95
- bindGroupLayouts: [layouts[`${name}Render`]]
96
- }),
97
- vertex: { module: rMod, entryPoint: "vs" },
98
- fragment: {
99
- module: rMod,
100
- entryPoint: "fs",
101
- targets: [{ format: "rgba8unorm", blend }]
102
- },
103
- primitive: { topology }
104
- });
105
- };
106
- async function init() {
107
- if (device)
108
- return true;
109
- if (!navigator.gpu) {
110
- postMessage({ type: "error", message: "WebGPU not supported" });
111
- return false;
112
- }
113
- const adapter = await navigator.gpu.requestAdapter();
114
- if (!adapter) {
115
- postMessage({ type: "error", message: "No GPU adapter found" });
116
- return false;
117
- }
118
- device = await adapter.requestDevice({
119
- requiredLimits: {
120
- maxBufferSize: adapter.limits.maxBufferSize,
121
- maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize
122
- }
123
- });
124
- canvasFormat = navigator.gpu.getPreferredCanvasFormat();
125
- device.lost.then((info) => {
126
- postMessage({
127
- type: "error",
128
- message: `GPU device lost: ${info.reason} - ${info.message}`
129
- });
130
- });
131
- createPipelines();
132
- postMessage({ type: "gpu-ready" });
133
- return true;
134
- }
135
- function createPipelines() {
136
- layouts.lineCompute = device.createBindGroupLayout({
137
- entries: [
138
- {
139
- binding: 0,
140
- visibility: GPUShaderStage.COMPUTE,
141
- buffer: { type: "uniform" }
142
- },
143
- {
144
- binding: 1,
145
- visibility: GPUShaderStage.COMPUTE,
146
- buffer: { type: "read-only-storage" }
147
- },
148
- {
149
- binding: 2,
150
- visibility: GPUShaderStage.COMPUTE,
151
- buffer: { type: "read-only-storage" }
152
- },
153
- {
154
- binding: 3,
155
- visibility: GPUShaderStage.COMPUTE,
156
- buffer: { type: "storage" }
157
- },
158
- {
159
- binding: 4,
160
- visibility: GPUShaderStage.COMPUTE,
161
- buffer: { type: "read-only-storage" }
162
- }
163
- ]
164
- });
165
- layouts.lineRender = device.createBindGroupLayout({
166
- entries: [
167
- {
168
- binding: 0,
169
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
170
- buffer: { type: "uniform" }
171
- },
172
- {
173
- binding: 1,
174
- visibility: GPUShaderStage.VERTEX,
175
- buffer: { type: "read-only-storage" }
176
- },
177
- {
178
- binding: 2,
179
- visibility: GPUShaderStage.FRAGMENT,
180
- buffer: { type: "read-only-storage" }
181
- }
182
- ]
183
- });
184
- layouts.scatterCompute = device.createBindGroupLayout({
185
- entries: [
186
- {
187
- binding: 0,
188
- visibility: GPUShaderStage.COMPUTE,
189
- buffer: { type: "uniform" }
190
- },
191
- {
192
- binding: 1,
193
- visibility: GPUShaderStage.COMPUTE,
194
- buffer: { type: "read-only-storage" }
195
- },
196
- {
197
- binding: 2,
198
- visibility: GPUShaderStage.COMPUTE,
199
- buffer: { type: "read-only-storage" }
200
- },
201
- {
202
- binding: 3,
203
- visibility: GPUShaderStage.COMPUTE,
204
- storageTexture: { access: "write-only", format: "rgba8unorm" }
205
- },
206
- {
207
- binding: 4,
208
- visibility: GPUShaderStage.COMPUTE,
209
- buffer: { type: "read-only-storage" }
210
- },
211
- {
212
- binding: 5,
213
- visibility: GPUShaderStage.COMPUTE,
214
- buffer: { type: "uniform" }
215
- }
216
- ]
217
- });
218
- layouts.fxaaRender = device.createBindGroupLayout({
219
- entries: [
220
- {
221
- binding: 0,
222
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
223
- buffer: { type: "uniform" }
224
- },
225
- {
226
- binding: 1,
227
- visibility: GPUShaderStage.FRAGMENT,
228
- texture: { sampleType: "float" }
229
- },
230
- { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: {} }
231
- ]
232
- });
233
- layouts.boxCompute = device.createBindGroupLayout({
234
- entries: [
235
- {
236
- binding: 0,
237
- visibility: GPUShaderStage.COMPUTE,
238
- buffer: { type: "uniform" }
239
- },
240
- {
241
- binding: 1,
242
- visibility: GPUShaderStage.COMPUTE,
243
- buffer: { type: "read-only-storage" }
244
- },
245
- {
246
- binding: 2,
247
- visibility: GPUShaderStage.COMPUTE,
248
- buffer: { type: "read-only-storage" }
249
- },
250
- {
251
- binding: 3,
252
- visibility: GPUShaderStage.COMPUTE,
253
- buffer: { type: "storage" }
254
- },
255
- {
256
- binding: 4,
257
- visibility: GPUShaderStage.COMPUTE,
258
- buffer: { type: "read-only-storage" }
259
- },
260
- {
261
- binding: 5,
262
- visibility: GPUShaderStage.COMPUTE,
263
- buffer: { type: "uniform" }
264
- }
265
- ]
266
- });
267
- layouts.boxRender = layouts.lineRender;
268
- createChartPipeline("line", LINE_COMPUTE_SHADER, LINE_RENDER_SHADER, "line-list", {
269
- color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha" },
270
- alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha" }
271
- });
272
- createChartPipeline("box", BOX_COMPUTE_SHADER, BOX_RENDER_SHADER, "triangle-list", {
273
- color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha" },
274
- alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha" }
275
- });
276
- const scatterComputeModule = device.createShaderModule({
277
- code: SCATTER_COMPUTE_SHADER
278
- });
279
- pipelines.scatterCompute = device.createComputePipeline({
280
- layout: device.createPipelineLayout({
281
- bindGroupLayouts: [layouts.scatterCompute]
282
- }),
283
- compute: { module: scatterComputeModule, entryPoint: "main" }
284
- });
285
- const fxaaRenderModule = device.createShaderModule({
286
- code: FXAA_RENDER_SHADER
287
- });
288
- pipelines.fxaaRender = device.createRenderPipeline({
289
- layout: device.createPipelineLayout({
290
- bindGroupLayouts: [layouts.fxaaRender]
291
- }),
292
- vertex: { module: fxaaRenderModule, entryPoint: "vs" },
293
- fragment: {
294
- module: fxaaRenderModule,
295
- entryPoint: "fs",
296
- targets: [{ format: canvasFormat }]
297
- },
298
- primitive: { topology: "triangle-strip" }
299
- });
300
- fxaaSampler = device.createSampler({
301
- magFilter: "linear",
302
- minFilter: "linear"
303
- });
304
174
  }
305
- function createChart(id, canvas, type, pointSize = 3, maxSamplesPerPixel = 0, bgColor = null) {
306
- const ctx = device ? canvas.getContext("webgpu") : null;
307
- if (!ctx) {
308
- postMessage({
309
- type: "error",
310
- message: `Failed to initialize WebGPU context: ${id}`
311
- });
175
+ function writeCustomUniforms(chart, config) {
176
+ if (!chart.customUniformBuffer || config.uniformDefs.length === 0)
312
177
  return;
178
+ const n = config.uniformDefs.length;
179
+ const byteCount = Math.ceil(n * 4 / 16) * 16;
180
+ const buffer = new ArrayBuffer(byteCount);
181
+ const f32 = new Float32Array(buffer);
182
+ const u32 = new Uint32Array(buffer);
183
+ for (let i = 0;i < n; i++) {
184
+ const def = config.uniformDefs[i];
185
+ const val = chart.customUniformValues[def.name] ?? def.default;
186
+ if (def.type === "u32")
187
+ u32[i] = val >>> 0;
188
+ else
189
+ f32[i] = val;
313
190
  }
314
- try {
315
- ctx.configure({ device, format: canvasFormat, alphaMode: "premultiplied" });
316
- } catch (e) {
317
- postMessage({
318
- type: "error",
319
- message: `Failed to configure WebGPU context: ${id}`,
320
- err: e.toString()
321
- });
322
- return;
191
+ device.queue.writeBuffer(chart.customUniformBuffer, 0, buffer);
192
+ }
193
+ function allocateChartBuffers(chart, renderer, bufferSizes) {
194
+ for (const [, buf] of chart.chartBuffers)
195
+ buf.destroy();
196
+ chart.chartBuffers.clear();
197
+ for (const bufDef of renderer.config.bufferDefs) {
198
+ if (!bufDef.perSeries) {
199
+ const size = Math.max(16, bufferSizes[bufDef.name] ?? 16);
200
+ chart.chartBuffers.set(bufDef.name, device.createBuffer({ size, usage: parseUsageFlags(bufDef.usages) }));
201
+ }
323
202
  }
324
- const limit = device.limits.maxTextureDimension2D;
325
- const width = Math.min(Math.floor(Number(canvas.width) || 800), limit);
326
- const height = Math.min(Math.floor(Number(canvas.height) || 400), limit);
327
- const uniformBuffer = device.createBuffer({
328
- size: 112,
329
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
330
- });
331
- const chart = {
332
- id,
333
- canvas,
334
- ctx,
335
- type,
336
- visible: true,
337
- pointSize,
338
- maxSamplesPerPixel,
339
- series: [],
340
- uniformBuffer,
341
- seriesStorageBuffer: null,
342
- outputTexture: null,
343
- outputTextureView: null,
344
- fxaaBindGroup: null,
345
- width,
346
- height,
347
- panX: 0,
348
- panY: 0,
349
- zoomX: 1,
350
- zoomY: 1,
351
- minX: 0,
352
- maxX: 1,
353
- minY: 0,
354
- maxY: 1,
355
- bgColor,
356
- dirty: true
357
- };
358
- try {
359
- createChartResources(chart);
360
- } catch (e) {
361
- postMessage({
362
- type: "error",
363
- message: `Cannot create chart ${id}: resource creation failed - ${e}`
203
+ if (renderer.config.uniformDefs.length > 0) {
204
+ if (chart.customUniformBuffer)
205
+ chart.customUniformBuffer.destroy();
206
+ const alignedSize = Math.max(16, Math.ceil(renderer.config.uniformDefs.length * 4 / 16) * 16);
207
+ chart.customUniformBuffer = device.createBuffer({
208
+ size: alignedSize,
209
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
364
210
  });
365
- return;
211
+ writeCustomUniforms(chart, renderer.config);
366
212
  }
367
- charts.set(id, chart);
368
- postMessage({ type: "chart-registered", id });
369
213
  }
370
- function createChartResources(chart) {
371
- if (chart.outputTexture)
372
- chart.outputTexture.destroy();
373
- const w = Math.max(1, chart.width);
374
- const h = Math.max(1, chart.height);
375
- const usage = chart.type === "scatter" ? GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT;
376
- chart.outputTexture = device.createTexture({
377
- size: [w, h],
378
- format: "rgba8unorm",
379
- usage
380
- });
381
- chart.outputTextureView = chart.outputTexture.createView();
382
- chart.fxaaBindGroup = device.createBindGroup({
383
- layout: layouts.fxaaRender,
384
- entries: [
385
- { binding: 0, resource: { buffer: chart.uniformBuffer } },
386
- { binding: 1, resource: chart.outputTextureView },
387
- { binding: 2, resource: fxaaSampler }
388
- ]
214
+ function allocateSeriesBuffers(series, seriesIndex, renderer, bufferSizes) {
215
+ for (const [, buf] of series.seriesBuffers)
216
+ buf.destroy();
217
+ series.seriesBuffers.clear();
218
+ for (const bufDef of renderer.config.bufferDefs) {
219
+ if (bufDef.perSeries) {
220
+ const size = Math.max(16, bufferSizes[bufDef.name] ?? 16);
221
+ series.seriesBuffers.set(bufDef.name, device.createBuffer({ size, usage: parseUsageFlags(bufDef.usages) }));
222
+ }
223
+ }
224
+ if (series.seriesIndexBuffer)
225
+ series.seriesIndexBuffer.destroy();
226
+ series.seriesIndexBuffer = device.createBuffer({
227
+ size: 16,
228
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
389
229
  });
230
+ device.queue.writeBuffer(series.seriesIndexBuffer, 0, new Uint32Array([seriesIndex, 0, 0, 0]));
390
231
  }
391
- function buildSeriesBindGroups(chart, series, seriesIndex) {
232
+ function buildAllBindGroups(chart, renderer) {
392
233
  if (!chart.seriesStorageBuffer)
393
234
  return;
394
- if (chart.type === "scatter") {
395
- if (series.seriesIndexBuffer)
396
- series.seriesIndexBuffer.destroy();
397
- series.seriesIndexBuffer = device.createBuffer({
398
- size: 16,
399
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
400
- });
401
- const indexData = new Uint32Array([seriesIndex, 0, 0, 0]);
402
- device.queue.writeBuffer(series.seriesIndexBuffer, 0, indexData);
403
- series.computeBindGroup = device.createBindGroup({
404
- layout: layouts.scatterCompute,
405
- entries: [
406
- { binding: 0, resource: { buffer: chart.uniformBuffer } },
407
- { binding: 1, resource: { buffer: series.dataX } },
408
- { binding: 2, resource: { buffer: series.dataY } },
409
- { binding: 3, resource: chart.outputTextureView },
410
- { binding: 4, resource: { buffer: chart.seriesStorageBuffer } },
411
- { binding: 5, resource: { buffer: series.seriesIndexBuffer } }
412
- ]
413
- });
414
- } else {
415
- const computeLayout = chart.type === "line" ? layouts.lineCompute : layouts.boxCompute;
416
- const renderLayout = chart.type === "line" ? layouts.lineRender : layouts.boxRender;
417
- if (series.lineBuffer)
418
- series.lineBuffer.destroy();
419
- series.lineBuffer = device.createBuffer({
420
- size: chart.width * 16,
421
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX
422
- });
423
- if (chart.type === "box") {
424
- if (series.seriesIndexBuffer)
425
- series.seriesIndexBuffer.destroy();
426
- series.seriesIndexBuffer = device.createBuffer({
427
- size: 16,
428
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
429
- });
430
- const indexData = new Uint32Array([seriesIndex, 0, 0, 0]);
431
- device.queue.writeBuffer(series.seriesIndexBuffer, 0, indexData);
432
- }
433
- const computeEntries = [
434
- { binding: 0, resource: { buffer: chart.uniformBuffer } },
435
- { binding: 1, resource: { buffer: series.dataX } },
436
- { binding: 2, resource: { buffer: series.dataY } },
437
- { binding: 3, resource: { buffer: series.lineBuffer } },
438
- { binding: 4, resource: { buffer: chart.seriesStorageBuffer } }
439
- ];
440
- if (chart.type === "box" && series.seriesIndexBuffer) {
441
- computeEntries.push({
442
- binding: 5,
443
- resource: { buffer: series.seriesIndexBuffer }
444
- });
235
+ for (let si = 0;si < chart.series.length; si++) {
236
+ const series = chart.series[si];
237
+ series.passBindGroups = [];
238
+ for (let passIdx = 0;passIdx < renderer.config.passes.length; passIdx++) {
239
+ const pass = renderer.config.passes[passIdx];
240
+ if (!pass.perSeries) {
241
+ series.passBindGroups.push(null);
242
+ continue;
243
+ }
244
+ const layout = renderer.passLayouts.get(`pass-${passIdx}`);
245
+ try {
246
+ const entries = pass.bindings.map((b) => ({
247
+ binding: b.binding,
248
+ resource: getBindingResource(b.source, b.write, chart, series, renderer)
249
+ }));
250
+ series.passBindGroups.push(device.createBindGroup({ layout, entries }));
251
+ } catch (e) {
252
+ postMessage({ type: M.ERROR, code: E.BIND_S });
253
+ series.passBindGroups.push(null);
254
+ }
445
255
  }
446
- series.computeBindGroup = device.createBindGroup({
447
- layout: computeLayout,
448
- entries: computeEntries
449
- });
450
- series.renderBindGroup = device.createBindGroup({
451
- layout: renderLayout,
452
- entries: [
453
- { binding: 0, resource: { buffer: chart.uniformBuffer } },
454
- { binding: 1, resource: { buffer: series.lineBuffer } },
455
- { binding: 2, resource: { buffer: chart.seriesStorageBuffer } }
456
- ]
457
- });
458
256
  }
459
- }
460
- function resizeChart(chart, width, height) {
461
- if (width === chart.width && height === chart.height)
462
- return;
463
- if (width <= 0 || height <= 0)
464
- return;
465
- chart.width = width;
466
- chart.height = height;
467
- chart.canvas.width = width;
468
- chart.canvas.height = height;
469
- try {
470
- createChartResources(chart);
471
- for (let i = 0;i < chart.series.length; i++) {
472
- buildSeriesBindGroups(chart, chart.series[i], i);
257
+ chart.chartPassBindGroups = [];
258
+ for (let passIdx = 0;passIdx < renderer.config.passes.length; passIdx++) {
259
+ const pass = renderer.config.passes[passIdx];
260
+ if (pass.perSeries) {
261
+ chart.chartPassBindGroups.push(null);
262
+ continue;
263
+ }
264
+ const layout = renderer.passLayouts.get(`pass-${passIdx}`);
265
+ try {
266
+ const entries = pass.bindings.map((b) => ({
267
+ binding: b.binding,
268
+ resource: getBindingResource(b.source, b.write, chart, null, renderer)
269
+ }));
270
+ chart.chartPassBindGroups.push(device.createBindGroup({ layout, entries }));
271
+ } catch (e) {
272
+ postMessage({ type: M.ERROR, code: E.BIND_C });
273
+ chart.chartPassBindGroups.push(null);
473
274
  }
474
- } catch (e) {
475
- postMessage({
476
- type: "error",
477
- message: `resize failed for chart ${chart.id}: ${e}`
478
- });
479
275
  }
480
276
  }
481
- function updateSeries(id, seriesData, bounds) {
482
- const chart = charts.get(id);
483
- if (!chart || !device) {
484
- if (!chart)
485
- postMessage({
486
- type: "error",
487
- message: `update-series failed: chart ${id} not found`
488
- });
489
- return;
490
- }
491
- try {
492
- chart.minX = bounds.minX;
493
- chart.maxX = bounds.maxX;
494
- chart.minY = bounds.minY;
495
- chart.maxY = bounds.maxY;
496
- for (const s of chart.series) {
497
- s.dataX.destroy();
498
- s.dataY.destroy();
499
- if (s.lineBuffer)
500
- s.lineBuffer.destroy();
501
- if (s.seriesIndexBuffer)
502
- s.seriesIndexBuffer.destroy();
503
- }
504
- chart.series = [];
505
- if (chart.seriesStorageBuffer)
506
- chart.seriesStorageBuffer.destroy();
507
- if (seriesData.length > 0) {
508
- chart.seriesStorageBuffer = device.createBuffer({
509
- size: Math.max(32, seriesData.length * 32),
510
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
511
- });
512
- }
513
- for (const sd of seriesData) {
514
- const dataX = device.createBuffer({
515
- size: Math.max(16, sd.dataX.byteLength),
516
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
517
- });
518
- const dataY = device.createBuffer({
519
- size: Math.max(16, sd.dataY.byteLength),
520
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
521
- });
522
- device.queue.writeBuffer(dataX, 0, sd.dataX);
523
- device.queue.writeBuffer(dataY, 0, sd.dataY);
524
- const series = {
525
- label: sd.label,
526
- colorR: sd.colorR,
527
- colorG: sd.colorG,
528
- colorB: sd.colorB,
529
- dataX,
530
- dataY,
531
- lineBuffer: null,
532
- seriesIndexBuffer: null,
533
- pointCount: sd.dataX.length,
534
- visibleStart: 0,
535
- visibleCount: sd.dataX.length,
536
- computeBindGroup: null,
537
- renderBindGroup: null
538
- };
539
- chart.series.push(series);
540
- }
541
- for (let i = 0;i < chart.series.length; i++) {
542
- buildSeriesBindGroups(chart, chart.series[i], i);
277
+ function createChartTexture(chart) {
278
+ if (chart.outputTexture)
279
+ chart.outputTexture.destroy();
280
+ const w = Math.max(1, chart.width);
281
+ const h = Math.max(1, chart.height);
282
+ const renderer = renderers.get(chart.rendererName);
283
+ let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT;
284
+ if (renderer) {
285
+ for (const pass of renderer.config.passes) {
286
+ if (pass.type === "compute") {
287
+ for (const b of pass.bindings) {
288
+ if (b.source === "render-target" && b.write) {
289
+ usage |= GPUTextureUsage.STORAGE_BINDING;
290
+ break;
291
+ }
292
+ }
293
+ }
543
294
  }
544
- postMessage({ type: "bounds-update", id, ...bounds });
545
- } catch (e) {
546
- postMessage({
547
- type: "error",
548
- message: `update-series failed for chart ${id}: ${e}`
549
- });
550
295
  }
296
+ chart.outputTexture = device.createTexture({ size: [w, h], format: "rgba8unorm", usage });
297
+ chart.outputTextureView = chart.outputTexture.createView();
298
+ chart.blitBindGroup = device.createBindGroup({
299
+ layout: blitLayout,
300
+ entries: [
301
+ { binding: 0, resource: chart.outputTextureView },
302
+ { binding: 1, resource: blitSampler }
303
+ ]
304
+ });
551
305
  }
552
- function render(chart) {
553
- if (!chart.ctx)
554
- return;
555
- if (chart.width === 0 || chart.height === 0)
306
+ function renderChart(chart) {
307
+ const renderer = renderers.get(chart.rendererName);
308
+ if (!renderer)
556
309
  return;
557
- if (chart.series.length === 0)
310
+ if (!chart.ctx || chart.width === 0 || chart.height === 0 || chart.series.length === 0)
558
311
  return;
559
312
  let textureView;
560
313
  try {
@@ -562,102 +315,99 @@ function render(chart) {
562
315
  } catch {
563
316
  return;
564
317
  }
565
- renderChartWithFXAA(chart, textureView);
566
- }
567
- function renderChartWithFXAA(chart, textureView) {
568
318
  const encoder = device.createCommandEncoder();
569
- let perSeriesPointSizes;
570
- if (chart.type === "scatter") {
571
- const canvasArea = chart.width * chart.height;
572
- const budget = canvasArea * 4;
573
- perSeriesPointSizes = chart.series.map((s) => {
574
- const visCount = Math.max(1, s.visibleCount);
575
- const pixelsPerPoint = Math.PI * chart.pointSize * chart.pointSize;
576
- if (visCount * pixelsPerPoint > budget) {
577
- return Math.max(1, Math.sqrt(budget / (visCount * Math.PI)));
578
- }
579
- return chart.pointSize;
580
- });
581
- }
582
- writeAllSeriesData(chart, perSeriesPointSizes);
583
- if (chart.series.length > 0) {
584
- writeUniforms(chart, chart.series[0], 0);
585
- }
319
+ writeAllSeriesData(chart);
320
+ if (chart.series.length > 0)
321
+ writeUniforms(chart, chart.series[0]);
586
322
  const clearPass = encoder.beginRenderPass({
587
- colorAttachments: [
588
- {
589
- view: chart.outputTextureView,
590
- loadOp: "clear",
591
- storeOp: "store",
592
- clearValue: { r: 0, g: 0, b: 0, a: 0 }
593
- }
594
- ]
323
+ colorAttachments: [{
324
+ view: chart.outputTextureView,
325
+ loadOp: "clear",
326
+ storeOp: "store",
327
+ clearValue: { r: 0, g: 0, b: 0, a: 0 }
328
+ }]
595
329
  });
596
330
  clearPass.end();
597
- for (let seriesIndex = 0;seriesIndex < chart.series.length; seriesIndex++) {
598
- const series = chart.series[seriesIndex];
599
- if (series.pointCount === 0)
331
+ for (let passIdx = 0;passIdx < renderer.config.passes.length; passIdx++) {
332
+ const pass = renderer.config.passes[passIdx];
333
+ const pipeline = renderer.pipelines.get(`pass-${passIdx}`);
334
+ if (!pipeline)
600
335
  continue;
601
- if (chart.type === "scatter") {
602
- const MAX_WG_DIM = 65535;
603
- const totalWG = Math.ceil(series.visibleCount / COMPUTE_WG);
604
- const wgX = Math.min(totalWG, MAX_WG_DIM);
605
- const wgY = Math.ceil(totalWG / MAX_WG_DIM);
606
- const dispatchBuf = new Uint32Array([wgX * COMPUTE_WG]);
607
- device.queue.writeBuffer(chart.uniformBuffer, 72, dispatchBuf);
608
- const computePass = encoder.beginComputePass();
609
- computePass.setPipeline(pipelines.scatterCompute);
610
- computePass.setBindGroup(0, series.computeBindGroup);
611
- computePass.dispatchWorkgroups(wgX, wgY);
612
- computePass.end();
613
- } else {
614
- writeUniforms(chart, series, seriesIndex);
615
- const computePipeline = chart.type === "line" ? pipelines.lineCompute : pipelines.boxCompute;
616
- const computePass = encoder.beginComputePass();
617
- computePass.setPipeline(computePipeline);
618
- computePass.setBindGroup(0, series.computeBindGroup);
619
- computePass.dispatchWorkgroups(Math.ceil(chart.width / COMPUTE_WG));
620
- computePass.end();
621
- }
622
- }
623
- if (chart.type !== "scatter") {
624
- const renderPipeline = chart.type === "line" ? pipelines.lineRender : pipelines.boxRender;
625
- const drawCount = chart.type === "line" ? Math.max(0, chart.width * 4 - 2) : chart.width * 6;
626
- const renderPass = encoder.beginRenderPass({
627
- colorAttachments: [
628
- {
336
+ if (pass.type === "compute") {
337
+ if (pass.perSeries) {
338
+ for (let si = 0;si < chart.series.length; si++) {
339
+ const series = chart.series[si];
340
+ if (series.pointCount === 0)
341
+ continue;
342
+ writeUniforms(chart, series);
343
+ const meta = chart.perSeriesPassMeta[si]?.[passIdx];
344
+ const dispatch = meta?.dispatch ?? { x: 1 };
345
+ const bg = series.passBindGroups[passIdx];
346
+ if (!bg)
347
+ continue;
348
+ const cp = encoder.beginComputePass();
349
+ cp.setPipeline(pipeline);
350
+ cp.setBindGroup(0, bg);
351
+ cp.dispatchWorkgroups(dispatch.x, dispatch.y ?? 1, dispatch.z ?? 1);
352
+ cp.end();
353
+ }
354
+ } else {
355
+ const bg = chart.chartPassBindGroups[passIdx];
356
+ if (!bg)
357
+ continue;
358
+ const meta = chart.perSeriesPassMeta[0]?.[passIdx];
359
+ const dispatch = meta?.dispatch ?? { x: 1 };
360
+ const cp = encoder.beginComputePass();
361
+ cp.setPipeline(pipeline);
362
+ cp.setBindGroup(0, bg);
363
+ cp.dispatchWorkgroups(dispatch.x, dispatch.y ?? 1, dispatch.z ?? 1);
364
+ cp.end();
365
+ }
366
+ } else if (pass.type === "render") {
367
+ const meta0 = chart.perSeriesPassMeta[0]?.[passIdx];
368
+ const drawCount = meta0?.draw ?? 0;
369
+ const rp = encoder.beginRenderPass({
370
+ colorAttachments: [{
629
371
  view: chart.outputTextureView,
630
- loadOp: "load",
372
+ loadOp: pass.loadOp ?? "load",
631
373
  storeOp: "store"
374
+ }]
375
+ });
376
+ rp.setPipeline(pipeline);
377
+ if (pass.perSeries) {
378
+ for (let si = 0;si < chart.series.length; si++) {
379
+ const series = chart.series[si];
380
+ if (series.pointCount === 0)
381
+ continue;
382
+ const bg = series.passBindGroups[passIdx];
383
+ if (!bg)
384
+ continue;
385
+ rp.setBindGroup(0, bg);
386
+ rp.draw(drawCount, 1, 0, si);
632
387
  }
633
- ]
634
- });
635
- renderPass.setPipeline(renderPipeline);
636
- for (let seriesIndex = 0;seriesIndex < chart.series.length; seriesIndex++) {
637
- const series = chart.series[seriesIndex];
638
- if (series.pointCount === 0)
639
- continue;
640
- renderPass.setBindGroup(0, series.renderBindGroup);
641
- renderPass.draw(drawCount, 1, 0, seriesIndex);
388
+ } else {
389
+ const bg = chart.chartPassBindGroups[passIdx];
390
+ if (bg) {
391
+ rp.setBindGroup(0, bg);
392
+ rp.draw(drawCount, 1, 0, 0);
393
+ }
394
+ }
395
+ rp.end();
642
396
  }
643
- renderPass.end();
644
397
  }
645
- const fxaaPass = encoder.beginRenderPass({
646
- colorAttachments: [
647
- {
648
- view: textureView,
649
- loadOp: "clear",
650
- storeOp: "store",
651
- clearValue: { r: 0, g: 0, b: 0, a: 0 }
652
- }
653
- ]
398
+ const blitPass = encoder.beginRenderPass({
399
+ colorAttachments: [{
400
+ view: textureView,
401
+ loadOp: "clear",
402
+ storeOp: "store",
403
+ clearValue: { r: 0, g: 0, b: 0, a: 0 }
404
+ }]
654
405
  });
655
- fxaaPass.setPipeline(pipelines.fxaaRender);
656
- if (chart.fxaaBindGroup) {
657
- fxaaPass.setBindGroup(0, chart.fxaaBindGroup);
658
- }
659
- fxaaPass.draw(4);
660
- fxaaPass.end();
406
+ blitPass.setPipeline(blitPipeline);
407
+ if (chart.blitBindGroup)
408
+ blitPass.setBindGroup(0, chart.blitBindGroup);
409
+ blitPass.draw(4);
410
+ blitPass.end();
661
411
  device.queue.submit([encoder.finish()]);
662
412
  }
663
413
  function scheduleRender() {
@@ -681,128 +431,305 @@ function markAllDirty() {
681
431
  if (anyVisible)
682
432
  scheduleRender();
683
433
  }
684
- function countActiveCharts() {
685
- let n = 0;
434
+ function renderFrame() {
435
+ renderScheduled = false;
436
+ const t0 = performance.now();
686
437
  for (const chart of charts.values()) {
438
+ if (chart.visible && chart.dirty && chart.width > 0) {
439
+ renderChart(chart);
440
+ chart.dirty = false;
441
+ }
442
+ }
443
+ lastRenderMs = performance.now() - t0;
444
+ frameCount++;
445
+ }
446
+ function countActive() {
447
+ let n = 0;
448
+ for (const chart of charts.values())
687
449
  if (chart.visible && chart.width > 0)
688
450
  n++;
689
- }
690
451
  return n;
691
452
  }
692
- function startStats() {
693
- if (statsInterval !== null)
694
- return;
453
+ async function init() {
454
+ if (device)
455
+ return true;
456
+ if (!navigator.gpu) {
457
+ postMessage({ type: M.ERROR, code: E.NO_GPU });
458
+ return false;
459
+ }
460
+ const adapter = await navigator.gpu.requestAdapter();
461
+ if (!adapter) {
462
+ postMessage({ type: M.ERROR, code: E.NO_ADAPTER });
463
+ return false;
464
+ }
465
+ device = await adapter.requestDevice({
466
+ requiredLimits: {
467
+ maxBufferSize: adapter.limits.maxBufferSize,
468
+ maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize
469
+ }
470
+ });
471
+ canvasFormat = navigator.gpu.getPreferredCanvasFormat();
472
+ device.lost.then((info) => {
473
+ postMessage({ type: M.ERROR, code: E.DEVICE_LOST });
474
+ });
475
+ blitLayout = device.createBindGroupLayout({
476
+ entries: [
477
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
478
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {} }
479
+ ]
480
+ });
481
+ const blitModule = device.createShaderModule({ code: BLIT_SHADER });
482
+ blitPipeline = device.createRenderPipeline({
483
+ layout: device.createPipelineLayout({ bindGroupLayouts: [blitLayout] }),
484
+ vertex: { module: blitModule, entryPoint: "vs" },
485
+ fragment: { module: blitModule, entryPoint: "fs", targets: [{ format: canvasFormat }] },
486
+ primitive: { topology: "triangle-strip" }
487
+ });
488
+ blitSampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });
695
489
  statsInterval = setInterval(() => {
696
490
  postMessage({
697
- type: "stats",
491
+ type: M.STATS,
698
492
  fps: frameCount,
699
493
  renderMs: lastRenderMs,
700
494
  totalCharts: charts.size,
701
- activeCharts: countActiveCharts()
495
+ activeCharts: countActive()
702
496
  });
703
497
  frameCount = 0;
704
498
  }, 1000);
499
+ postMessage({ type: M.GPU_READY });
500
+ return true;
705
501
  }
706
- function renderFrame() {
707
- renderScheduled = false;
708
- const t0 = performance.now();
709
- for (const chart of charts.values()) {
710
- if (chart.visible && chart.dirty && chart.width > 0) {
711
- render(chart);
712
- chart.dirty = false;
502
+ function destroySeriesData(series) {
503
+ series.dataX.destroy();
504
+ series.dataY.destroy();
505
+ for (const [, buf] of series.extraBuffers)
506
+ buf.destroy();
507
+ for (const [, buf] of series.seriesBuffers)
508
+ buf.destroy();
509
+ if (series.seriesIndexBuffer)
510
+ series.seriesIndexBuffer.destroy();
511
+ }
512
+ function processUpdateSeries(id, seriesData, bounds, bufferSizes, perSeriesPassMeta) {
513
+ const chart = charts.get(id);
514
+ if (!chart || !device)
515
+ return;
516
+ const renderer = renderers.get(chart.rendererName);
517
+ if (!renderer) {
518
+ postMessage({ type: M.ERROR, code: E.NO_RENDERER });
519
+ return;
520
+ }
521
+ try {
522
+ chart.minX = bounds.minX;
523
+ chart.maxX = bounds.maxX;
524
+ chart.minY = bounds.minY;
525
+ chart.maxY = bounds.maxY;
526
+ chart.perSeriesPassMeta = perSeriesPassMeta;
527
+ for (const s of chart.series)
528
+ destroySeriesData(s);
529
+ chart.series = [];
530
+ if (chart.seriesStorageBuffer)
531
+ chart.seriesStorageBuffer.destroy();
532
+ if (seriesData.length > 0) {
533
+ chart.seriesStorageBuffer = device.createBuffer({
534
+ size: Math.max(32, seriesData.length * 32),
535
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
536
+ });
713
537
  }
538
+ allocateChartBuffers(chart, renderer, bufferSizes);
539
+ for (let i = 0;i < seriesData.length; i++) {
540
+ const sd = seriesData[i];
541
+ const dataX = device.createBuffer({
542
+ size: Math.max(16, sd.dataX.byteLength),
543
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
544
+ });
545
+ const dataY = device.createBuffer({
546
+ size: Math.max(16, sd.dataY.byteLength),
547
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
548
+ });
549
+ device.queue.writeBuffer(dataX, 0, sd.dataX);
550
+ device.queue.writeBuffer(dataY, 0, sd.dataY);
551
+ const extraBuffers = new Map;
552
+ for (const [key, arr] of Object.entries(sd.extra ?? {})) {
553
+ const buf = device.createBuffer({
554
+ size: Math.max(16, arr.byteLength),
555
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
556
+ });
557
+ device.queue.writeBuffer(buf, 0, arr);
558
+ extraBuffers.set(key, buf);
559
+ }
560
+ const series = {
561
+ label: sd.label,
562
+ colorR: sd.colorR,
563
+ colorG: sd.colorG,
564
+ colorB: sd.colorB,
565
+ dataX,
566
+ dataY,
567
+ extraBuffers,
568
+ seriesBuffers: new Map,
569
+ seriesIndexBuffer: null,
570
+ pointCount: sd.dataX.length,
571
+ visibleStart: 0,
572
+ visibleCount: sd.dataX.length,
573
+ passBindGroups: []
574
+ };
575
+ chart.series.push(series);
576
+ allocateSeriesBuffers(series, i, renderer, bufferSizes);
577
+ }
578
+ buildAllBindGroups(chart, renderer);
579
+ } catch (e) {
580
+ postMessage({ type: M.ERROR, code: E.UPDATE });
714
581
  }
715
- lastRenderMs = performance.now() - t0;
716
- frameCount++;
717
582
  }
718
583
  self.onmessage = async (e) => {
719
584
  const { type, ...data } = e.data;
720
585
  switch (type) {
721
- case "init":
586
+ case M.INIT:
722
587
  isDark = data.isDark || false;
723
- if (await init()) {
724
- startStats();
725
- }
588
+ await init();
726
589
  break;
727
- case "theme":
590
+ case M.THEME:
728
591
  isDark = data.isDark;
729
592
  markAllDirty();
730
593
  break;
731
- case "register-chart":
732
- createChart(data.id, data.canvas, data.chartType || "scatter", data.pointSize ?? 3, data.maxSamplesPerPixel ?? 100, data.bgColor ?? null);
733
- {
734
- const chart = charts.get(data.id);
735
- if (chart)
736
- markDirty(chart);
594
+ case M.REGISTER_RENDERER: {
595
+ if (!device) {
596
+ postMessage({ type: M.ERROR, code: E.NOT_READY });
597
+ break;
598
+ }
599
+ const config = {
600
+ name: data.name,
601
+ shaders: data.shaders,
602
+ passes: data.passes,
603
+ bufferDefs: data.bufferDefs ?? [],
604
+ uniformDefs: data.uniformDefs ?? []
605
+ };
606
+ try {
607
+ renderers.set(data.name, compileRenderer(config));
608
+ } catch (e2) {
609
+ postMessage({ type: M.ERROR, code: E.COMPILE });
737
610
  }
738
611
  break;
739
- case "unregister-chart": {
612
+ }
613
+ case M.REGISTER_CHART: {
614
+ if (!device)
615
+ break;
616
+ const ctx = data.canvas.getContext("webgpu");
617
+ if (!ctx) {
618
+ postMessage({ type: M.ERROR, code: E.CTX_GET });
619
+ break;
620
+ }
621
+ try {
622
+ ctx.configure({ device, format: canvasFormat, alphaMode: "premultiplied" });
623
+ } catch (e2) {
624
+ postMessage({ type: M.ERROR, code: E.CTX_CFG });
625
+ break;
626
+ }
627
+ const limit = device.limits.maxTextureDimension2D;
628
+ const w = Math.min(Math.max(1, Math.floor(Number(data.canvas.width) || 800)), limit);
629
+ const h = Math.min(Math.max(1, Math.floor(Number(data.canvas.height) || 400)), limit);
630
+ const chart = {
631
+ id: data.id,
632
+ canvas: data.canvas,
633
+ ctx,
634
+ rendererName: data.rendererName,
635
+ visible: true,
636
+ series: [],
637
+ uniformBuffer: device.createBuffer({
638
+ size: 64,
639
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
640
+ }),
641
+ seriesStorageBuffer: null,
642
+ outputTexture: null,
643
+ outputTextureView: null,
644
+ blitBindGroup: null,
645
+ chartBuffers: new Map,
646
+ customUniformBuffer: null,
647
+ customUniformValues: data.customUniformValues ?? {},
648
+ chartPassBindGroups: [],
649
+ perSeriesPassMeta: data.perSeriesPassMeta ?? [],
650
+ width: w,
651
+ height: h,
652
+ panX: 0,
653
+ panY: 0,
654
+ zoomX: 1,
655
+ zoomY: 1,
656
+ minX: 0,
657
+ maxX: 1,
658
+ maxY: 1,
659
+ minY: 0,
660
+ bgColor: data.bgColor ?? null,
661
+ dirty: true
662
+ };
663
+ try {
664
+ createChartTexture(chart);
665
+ } catch (e2) {
666
+ postMessage({ type: M.ERROR, code: E.TEX });
667
+ break;
668
+ }
669
+ charts.set(data.id, chart);
670
+ break;
671
+ }
672
+ case M.UNREGISTER_CHART: {
740
673
  const chart = charts.get(data.id);
741
674
  if (chart) {
742
675
  try {
743
676
  chart.ctx.unconfigure();
744
- } catch {
745
- }
677
+ } catch {}
746
678
  chart.uniformBuffer.destroy();
747
679
  if (chart.seriesStorageBuffer)
748
680
  chart.seriesStorageBuffer.destroy();
749
681
  if (chart.outputTexture)
750
682
  chart.outputTexture.destroy();
751
- for (const s of chart.series) {
752
- s.dataX.destroy();
753
- s.dataY.destroy();
754
- if (s.lineBuffer)
755
- s.lineBuffer.destroy();
756
- if (s.seriesIndexBuffer)
757
- s.seriesIndexBuffer.destroy();
758
- }
683
+ if (chart.customUniformBuffer)
684
+ chart.customUniformBuffer.destroy();
685
+ for (const [, buf] of chart.chartBuffers)
686
+ buf.destroy();
687
+ for (const s of chart.series)
688
+ destroySeriesData(s);
759
689
  charts.delete(data.id);
760
690
  }
761
- postMessage({ type: "chart-unregistered", id: data.id });
762
- break;
763
- }
764
- case "set-point-size": {
765
- const chart = charts.get(data.id);
766
- if (chart) {
767
- chart.pointSize = Math.max(1, Math.min(8, data.pointSize));
768
- markDirty(chart);
769
- }
770
- break;
771
- }
772
- case "set-max-samples": {
773
- const chart = charts.get(data.id);
774
- if (chart) {
775
- chart.maxSamplesPerPixel = Math.max(0, data.maxSamplesPerPixel | 0);
776
- markDirty(chart);
777
- }
778
- break;
779
- }
780
- case "set-style": {
781
- const chart = charts.get(data.id);
782
- if (chart) {
783
- if (data.bgColor !== undefined)
784
- chart.bgColor = data.bgColor;
785
- markDirty(chart);
786
- }
787
691
  break;
788
692
  }
789
- case "update-series": {
790
- updateSeries(data.id, data.series, data.bounds);
693
+ case M.UPDATE_SERIES: {
694
+ processUpdateSeries(data.id, data.series, data.bounds, data.bufferSizes ?? {}, data.perSeriesPassMeta ?? []);
791
695
  const chart = charts.get(data.id);
792
696
  if (chart)
793
697
  markDirty(chart);
794
698
  break;
795
699
  }
796
- case "set-visibility": {
700
+ case M.RESIZE: {
797
701
  const chart = charts.get(data.id);
798
- if (chart) {
799
- chart.visible = data.visible;
800
- if (data.visible && chart.dirty)
801
- scheduleRender();
702
+ if (!chart || data.width <= 0 || data.height <= 0)
703
+ break;
704
+ const limit = device.limits.maxTextureDimension2D;
705
+ const w = Math.min(data.width, limit);
706
+ const h = Math.min(data.height, limit);
707
+ if (w === chart.width && h === chart.height)
708
+ break;
709
+ chart.width = w;
710
+ chart.height = h;
711
+ chart.canvas.width = w;
712
+ chart.canvas.height = h;
713
+ if (data.perSeriesPassMeta?.length > 0) {
714
+ chart.perSeriesPassMeta = data.perSeriesPassMeta;
715
+ }
716
+ const renderer = renderers.get(chart.rendererName);
717
+ try {
718
+ createChartTexture(chart);
719
+ if (renderer && data.bufferSizes) {
720
+ allocateChartBuffers(chart, renderer, data.bufferSizes);
721
+ for (let i = 0;i < chart.series.length; i++) {
722
+ allocateSeriesBuffers(chart.series[i], i, renderer, data.bufferSizes);
723
+ }
724
+ buildAllBindGroups(chart, renderer);
725
+ }
726
+ } catch (e2) {
727
+ postMessage({ type: M.ERROR, code: E.RESIZE });
802
728
  }
729
+ markDirty(chart);
803
730
  break;
804
731
  }
805
- case "view-transform": {
732
+ case M.VIEW_TRANSFORM: {
806
733
  const chart = charts.get(data.id);
807
734
  if (chart) {
808
735
  chart.panX = data.panX;
@@ -813,15 +740,7 @@ self.onmessage = async (e) => {
813
740
  }
814
741
  break;
815
742
  }
816
- case "resize": {
817
- const chart = charts.get(data.id);
818
- if (chart && data.width > 0 && data.height > 0) {
819
- resizeChart(chart, data.width, data.height);
820
- markDirty(chart);
821
- }
822
- break;
823
- }
824
- case "batch-view-transform": {
743
+ case M.BATCH_VIEW_TRANSFORM: {
825
744
  const zX = Math.max(0.1, Math.min(1e6, data.zoomX));
826
745
  const zY = Math.max(0.1, Math.min(1e6, data.zoomY));
827
746
  for (const t of data.transforms) {
@@ -837,14 +756,34 @@ self.onmessage = async (e) => {
837
756
  scheduleRender();
838
757
  break;
839
758
  }
840
- case "sync-view":
841
- for (const chart of charts.values()) {
842
- chart.panX = data.panX;
843
- chart.panY = data.panY;
844
- chart.zoomX = data.zoomX;
845
- chart.zoomY = data.zoomY;
759
+ case M.SET_VISIBILITY: {
760
+ const chart = charts.get(data.id);
761
+ if (chart) {
762
+ chart.visible = data.visible;
763
+ if (data.visible && chart.dirty)
764
+ scheduleRender();
846
765
  }
847
- markAllDirty();
848
766
  break;
767
+ }
768
+ case M.SET_STYLE: {
769
+ const chart = charts.get(data.id);
770
+ if (chart) {
771
+ if (data.bgColor !== undefined)
772
+ chart.bgColor = data.bgColor;
773
+ markDirty(chart);
774
+ }
775
+ break;
776
+ }
777
+ case M.SET_UNIFORMS: {
778
+ const chart = charts.get(data.id);
779
+ if (!chart)
780
+ break;
781
+ Object.assign(chart.customUniformValues, data.values);
782
+ const renderer = renderers.get(chart.rendererName);
783
+ if (renderer)
784
+ writeCustomUniforms(chart, renderer.config);
785
+ markDirty(chart);
786
+ break;
787
+ }
849
788
  }
850
789
  };