@mytec: stack done, rust next
This commit is contained in:
@@ -889,11 +889,11 @@ export default function App() {
|
||||
<select
|
||||
value={coverageRenderer}
|
||||
onChange={(e) => setCoverageRenderer(e.target.value as 'webgl-radial' | 'webgl-texture' | 'canvas')}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-dark-border rounded-lg bg-white dark:bg-dark-card text-sm dark:text-dark-text"
|
||||
className="w-full mt-1 px-2 py-1.5 text-sm bg-white dark:bg-dark-border border border-gray-300 dark:border-dark-border rounded-md text-gray-700 dark:text-dark-text"
|
||||
>
|
||||
<option value="webgl-radial">WebGL Radial (smooth)</option>
|
||||
<option value="webgl-texture">WebGL Texture (fast)</option>
|
||||
<option value="canvas">Canvas (fallback)</option>
|
||||
<option value="webgl-radial" className="bg-white dark:bg-slate-800 text-gray-700 dark:text-white">WebGL Radial (smooth)</option>
|
||||
<option value="webgl-texture" className="bg-white dark:bg-slate-800 text-gray-700 dark:text-white">WebGL Texture (fast)</option>
|
||||
<option value="canvas" className="bg-white dark:bg-slate-800 text-gray-700 dark:text-white">Canvas (fallback)</option>
|
||||
</select>
|
||||
</div>
|
||||
{coverageRenderer === 'canvas' && (
|
||||
|
||||
@@ -195,6 +195,7 @@ export default function WebGLRadialCoverageLayer({
|
||||
const boundsRef = useRef<Bounds | null>(null);
|
||||
const initializedRef = useRef(false);
|
||||
const lastPointsHashRef = useRef<string>('');
|
||||
const instExtRef = useRef<ANGLE_instanced_arrays | null>(null);
|
||||
|
||||
// Track if points need to be re-rendered (expensive pass)
|
||||
const needsPointRenderRef = useRef(true);
|
||||
@@ -301,6 +302,8 @@ export default function WebGLRadialCoverageLayer({
|
||||
|
||||
// === Pass 1: Accumulate points into framebuffer (only when needed) ===
|
||||
if (needsPointRenderRef.current) {
|
||||
const t0 = performance.now();
|
||||
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
@@ -310,13 +313,8 @@ export default function WebGLRadialCoverageLayer({
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.ONE, gl.ONE); // Additive blending
|
||||
|
||||
// Bind quad buffer for point rendering
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
||||
// Get attribute locations
|
||||
const posLoc = gl.getAttribLocation(pointProgram, 'a_position');
|
||||
gl.enableVertexAttribArray(posLoc);
|
||||
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Get attribute locations for point data
|
||||
const pointPosLoc = gl.getAttribLocation(pointProgram, 'a_pointPos');
|
||||
const pointRsrpLoc = gl.getAttribLocation(pointProgram, 'a_pointRsrp');
|
||||
const pointRadiusLoc = gl.getAttribLocation(pointProgram, 'a_pointRadius');
|
||||
@@ -339,24 +337,90 @@ export default function WebGLRadialCoverageLayer({
|
||||
const normalizedRadiusLat = (avgCellLat * radiusMultiplier) / latRange;
|
||||
const normalizedRadiusLon = (avgCellLon * radiusMultiplier) / lonRange;
|
||||
const normalizedRadius = Math.max(normalizedRadiusLat, normalizedRadiusLon);
|
||||
|
||||
log(2, 'Grid estimate:', { points: points.length, gridDim: gridDim.toFixed(1), densityBoost: densityBoost.toFixed(2), radiusMultiplier: radiusMultiplier.toFixed(1), normalizedRadius: normalizedRadius.toFixed(4) });
|
||||
|
||||
// Draw each point as a quad
|
||||
const rsrpRange = maxRsrp - minRsrp;
|
||||
for (const p of points) {
|
||||
const normX = (p.lon - bounds.minLon) / lonRange;
|
||||
const normY = (p.lat - bounds.minLat) / latRange;
|
||||
const normRsrp = Math.max(0, Math.min(1, (p.rsrp - minRsrp) / rsrpRange));
|
||||
|
||||
gl.vertexAttrib2f(pointPosLoc, normX, normY);
|
||||
gl.vertexAttrib1f(pointRsrpLoc, normRsrp);
|
||||
gl.vertexAttrib1f(pointRadiusLoc, normalizedRadius);
|
||||
const instExt = instExtRef.current;
|
||||
const pointBuffer = pointBufferRef.current;
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
if (instExt && pointBuffer) {
|
||||
// === INSTANCED RENDERING: 1 draw call for ALL points ===
|
||||
|
||||
// Build instance data buffer: [posX, posY, rsrp, radius] × N points
|
||||
const instanceData = new Float32Array(points.length * 4);
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const p = points[i];
|
||||
const normX = (p.lon - bounds.minLon) / lonRange;
|
||||
const normY = (p.lat - bounds.minLat) / latRange;
|
||||
const normRsrp = Math.max(0, Math.min(1, (p.rsrp - minRsrp) / rsrpRange));
|
||||
instanceData[i * 4 + 0] = normX;
|
||||
instanceData[i * 4 + 1] = normY;
|
||||
instanceData[i * 4 + 2] = normRsrp;
|
||||
instanceData[i * 4 + 3] = normalizedRadius;
|
||||
}
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, pointBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, instanceData, gl.DYNAMIC_DRAW);
|
||||
|
||||
// Bind quad buffer for a_position (per-vertex)
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
||||
gl.enableVertexAttribArray(posLoc);
|
||||
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Bind instance buffer for per-instance attributes
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, pointBuffer);
|
||||
const stride = 4 * 4; // 4 floats × 4 bytes
|
||||
|
||||
gl.enableVertexAttribArray(pointPosLoc);
|
||||
gl.vertexAttribPointer(pointPosLoc, 2, gl.FLOAT, false, stride, 0);
|
||||
instExt.vertexAttribDivisorANGLE(pointPosLoc, 1); // per-instance
|
||||
|
||||
gl.enableVertexAttribArray(pointRsrpLoc);
|
||||
gl.vertexAttribPointer(pointRsrpLoc, 1, gl.FLOAT, false, stride, 8);
|
||||
instExt.vertexAttribDivisorANGLE(pointRsrpLoc, 1); // per-instance
|
||||
|
||||
gl.enableVertexAttribArray(pointRadiusLoc);
|
||||
gl.vertexAttribPointer(pointRadiusLoc, 1, gl.FLOAT, false, stride, 12);
|
||||
instExt.vertexAttribDivisorANGLE(pointRadiusLoc, 1); // per-instance
|
||||
|
||||
// ONE draw call for ALL points!
|
||||
instExt.drawArraysInstancedANGLE(gl.TRIANGLE_STRIP, 0, 4, points.length);
|
||||
|
||||
// Reset divisors
|
||||
instExt.vertexAttribDivisorANGLE(pointPosLoc, 0);
|
||||
instExt.vertexAttribDivisorANGLE(pointRsrpLoc, 0);
|
||||
instExt.vertexAttribDivisorANGLE(pointRadiusLoc, 0);
|
||||
|
||||
gl.disableVertexAttribArray(posLoc);
|
||||
gl.disableVertexAttribArray(pointPosLoc);
|
||||
gl.disableVertexAttribArray(pointRsrpLoc);
|
||||
gl.disableVertexAttribArray(pointRadiusLoc);
|
||||
|
||||
const t1 = performance.now();
|
||||
log(2, 'Instanced render:', points.length, 'points in 1 call,', (t1 - t0).toFixed(1) + 'ms');
|
||||
} else {
|
||||
// === FALLBACK: per-point draw calls ===
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
||||
gl.enableVertexAttribArray(posLoc);
|
||||
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
for (const p of points) {
|
||||
const normX = (p.lon - bounds.minLon) / lonRange;
|
||||
const normY = (p.lat - bounds.minLat) / latRange;
|
||||
const normRsrp = Math.max(0, Math.min(1, (p.rsrp - minRsrp) / rsrpRange));
|
||||
|
||||
gl.vertexAttrib2f(pointPosLoc, normX, normY);
|
||||
gl.vertexAttrib1f(pointRsrpLoc, normRsrp);
|
||||
gl.vertexAttrib1f(pointRadiusLoc, normalizedRadius);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
gl.disableVertexAttribArray(posLoc);
|
||||
|
||||
const t1 = performance.now();
|
||||
log(2, 'Fallback render:', points.length, 'points in', points.length, 'calls,', (t1 - t0).toFixed(1) + 'ms');
|
||||
}
|
||||
|
||||
gl.disableVertexAttribArray(posLoc);
|
||||
log(3, 'Grid estimate:', { points: points.length, gridDim: gridDim.toFixed(1), densityBoost: densityBoost.toFixed(2), radiusMultiplier: radiusMultiplier.toFixed(1), normalizedRadius: normalizedRadius.toFixed(4) });
|
||||
needsPointRenderRef.current = false;
|
||||
}
|
||||
|
||||
@@ -426,6 +490,15 @@ export default function WebGLRadialCoverageLayer({
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for instanced rendering support
|
||||
const instExt = gl.getExtension('ANGLE_instanced_arrays');
|
||||
if (instExt) {
|
||||
log(2, 'Instanced rendering supported');
|
||||
instExtRef.current = instExt;
|
||||
} else {
|
||||
log(1, 'Instanced rendering NOT supported, using fallback');
|
||||
}
|
||||
|
||||
gl.enable(gl.BLEND);
|
||||
|
||||
// Create point program
|
||||
|
||||
Reference in New Issue
Block a user