[fix](trx-frontend-http): optimize WebGL rendering and pin bookmarks to top
Improve WebGL runtime performance by caching/downsampling overview waterfall texture updates and batching marker/dashed-line draws; keep bookmark chips anchored at the top of the waterfall area.\n\nCo-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -764,6 +764,12 @@ let overviewSignalTimer = null;
|
|||||||
let overviewWaterfallRows = [];
|
let overviewWaterfallRows = [];
|
||||||
let overviewWaterfallPushCount = 0; // monotonically increments on every push
|
let overviewWaterfallPushCount = 0; // monotonically increments on every push
|
||||||
const HEADER_SIG_WINDOW_MS = 10_000;
|
const HEADER_SIG_WINDOW_MS = 10_000;
|
||||||
|
const OVERVIEW_WF_TEX_MAX_W = 512;
|
||||||
|
let overviewWfTexData = null;
|
||||||
|
let overviewWfTexWidth = 0;
|
||||||
|
let overviewWfTexHeight = 0;
|
||||||
|
let overviewWfTexPushCount = 0;
|
||||||
|
let overviewWfTexPalKey = "";
|
||||||
|
|
||||||
function cssColorToRgba(color, alphaMul = 1) {
|
function cssColorToRgba(color, alphaMul = 1) {
|
||||||
const parser = typeof window.trxParseCssColor === "function" ? window.trxParseCssColor : null;
|
const parser = typeof window.trxParseCssColor === "function" ? window.trxParseCssColor : null;
|
||||||
@@ -780,6 +786,18 @@ function rgbaWithAlpha(color, alphaMul = 1) {
|
|||||||
return cssColorToRgba(color, alphaMul);
|
return cssColorToRgba(color, alphaMul);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function overviewWfResetTextureCache() {
|
||||||
|
overviewWfTexData = null;
|
||||||
|
overviewWfTexWidth = 0;
|
||||||
|
overviewWfTexHeight = 0;
|
||||||
|
overviewWfTexPushCount = 0;
|
||||||
|
overviewWfTexPalKey = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function overviewWfPaletteKey(pal, viewKey = "") {
|
||||||
|
return `${pal.waterfallHue}|${pal.waterfallSat}|${pal.waterfallLight}|${pal.waterfallAlpha}|${spectrumFloor}|${spectrumRange}|${viewKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
function resizeHeaderSignalCanvas() {
|
function resizeHeaderSignalCanvas() {
|
||||||
if (!ensureOverviewCanvasBackingStore()) return;
|
if (!ensureOverviewCanvasBackingStore()) return;
|
||||||
positionRdsPsOverlay();
|
positionRdsPsOverlay();
|
||||||
@@ -794,6 +812,7 @@ function ensureOverviewCanvasBackingStore() {
|
|||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
const resized = overviewGl.ensureSize(cssW, cssH, dpr);
|
const resized = overviewGl.ensureSize(cssW, cssH, dpr);
|
||||||
if (resized) {
|
if (resized) {
|
||||||
|
overviewWfResetTextureCache();
|
||||||
trimOverviewWaterfallRows();
|
trimOverviewWaterfallRows();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -983,32 +1002,67 @@ function drawOverviewWaterfall(W, H, pal) {
|
|||||||
const rows = overviewWaterfallRows.slice(-maxVisible);
|
const rows = overviewWaterfallRows.slice(-maxVisible);
|
||||||
if (rows.length === 0) return;
|
if (rows.length === 0) return;
|
||||||
|
|
||||||
const iW = Math.max(1, Math.ceil(W));
|
const iW = Math.max(96, Math.min(OVERVIEW_WF_TEX_MAX_W, Math.ceil(W / 2)));
|
||||||
const iH = Math.max(1, Math.ceil(H));
|
const iH = Math.max(1, rows.length);
|
||||||
const rgba = new Uint8Array(iW * iH * 4);
|
|
||||||
const minDb = Number.isFinite(spectrumFloor) ? spectrumFloor : -115;
|
const minDb = Number.isFinite(spectrumFloor) ? spectrumFloor : -115;
|
||||||
const maxDb = minDb + Math.max(20, Number.isFinite(spectrumRange) ? spectrumRange : 90);
|
const maxDb = minDb + Math.max(20, Number.isFinite(spectrumRange) ? spectrumRange : 90);
|
||||||
|
const view = lastSpectrumData ? spectrumVisibleRange(lastSpectrumData) : null;
|
||||||
|
const viewKey = view ? `${Math.round(view.visLoHz)}:${Math.round(view.visHiHz)}` : "na";
|
||||||
|
const palKey = overviewWfPaletteKey(pal, viewKey);
|
||||||
|
const rowStride = iW * 4;
|
||||||
|
const expectedSize = iW * iH * 4;
|
||||||
|
const steadyState = rows.length >= maxVisible;
|
||||||
|
const newPushes = overviewWaterfallPushCount - overviewWfTexPushCount;
|
||||||
|
const sizeChanged = overviewWfTexWidth !== iW || overviewWfTexHeight !== iH;
|
||||||
|
const palChanged = overviewWfTexPalKey !== palKey;
|
||||||
|
const needsFull = !overviewWfTexData || sizeChanged || palChanged || overviewWfTexPushCount === 0;
|
||||||
|
|
||||||
for (let y = 0; y < iH; y++) {
|
if (!overviewWfTexData || overviewWfTexData.length !== expectedSize) {
|
||||||
const rowFrac = y / Math.max(1, iH - 1);
|
overviewWfTexData = new Uint8Array(expectedSize);
|
||||||
const rowIdx = Math.max(0, Math.min(rows.length - 1, Math.floor(rowFrac * rows.length)));
|
}
|
||||||
const bins = rows[rowIdx];
|
overviewWfTexWidth = iW;
|
||||||
if (!Array.isArray(bins) || bins.length === 0) continue;
|
overviewWfTexHeight = iH;
|
||||||
const { startIdx, endIdx } = overviewVisibleBinWindow(lastSpectrumData, bins.length);
|
|
||||||
|
function renderRow(dstY, srcBins) {
|
||||||
|
if (!Array.isArray(srcBins) || srcBins.length === 0) return;
|
||||||
|
const { startIdx, endIdx } = overviewVisibleBinWindow(lastSpectrumData, srcBins.length);
|
||||||
const spanBins = Math.max(1, endIdx - startIdx);
|
const spanBins = Math.max(1, endIdx - startIdx);
|
||||||
|
const rowBase = dstY * rowStride;
|
||||||
for (let x = 0; x < iW; x++) {
|
for (let x = 0; x < iW; x++) {
|
||||||
const frac = x / Math.max(1, iW - 1);
|
const frac = x / Math.max(1, iW - 1);
|
||||||
const binIdx = Math.min(endIdx, startIdx + Math.floor(frac * spanBins));
|
const binIdx = Math.min(endIdx, startIdx + Math.floor(frac * spanBins));
|
||||||
const c = waterfallColorRgba(bins[binIdx], pal, minDb, maxDb);
|
const c = waterfallColorRgba(srcBins[binIdx], pal, minDb, maxDb);
|
||||||
const p = (y * iW + x) * 4;
|
const p = rowBase + x * 4;
|
||||||
rgba[p + 0] = Math.round(c[0] * 255);
|
overviewWfTexData[p + 0] = Math.round(c[0] * 255);
|
||||||
rgba[p + 1] = Math.round(c[1] * 255);
|
overviewWfTexData[p + 1] = Math.round(c[1] * 255);
|
||||||
rgba[p + 2] = Math.round(c[2] * 255);
|
overviewWfTexData[p + 2] = Math.round(c[2] * 255);
|
||||||
rgba[p + 3] = Math.round(c[3] * 255);
|
overviewWfTexData[p + 3] = Math.round(c[3] * 255);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
overviewGl.uploadRgbaTexture("overview-waterfall", iW, iH, rgba, "linear");
|
if (needsFull) {
|
||||||
|
for (let y = 0; y < iH; y++) {
|
||||||
|
renderRow(y, rows[y]);
|
||||||
|
}
|
||||||
|
overviewWfTexPushCount = overviewWaterfallPushCount;
|
||||||
|
overviewWfTexPalKey = palKey;
|
||||||
|
} else if (steadyState && newPushes > 0) {
|
||||||
|
const newCount = Math.min(newPushes, iH);
|
||||||
|
if (newCount >= iH) {
|
||||||
|
for (let y = 0; y < iH; y++) renderRow(y, rows[y]);
|
||||||
|
} else {
|
||||||
|
const shiftBytes = newCount * rowStride;
|
||||||
|
overviewWfTexData.copyWithin(0, shiftBytes);
|
||||||
|
const startRow = iH - newCount;
|
||||||
|
for (let y = startRow; y < iH; y++) {
|
||||||
|
renderRow(y, rows[y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overviewWfTexPushCount = overviewWaterfallPushCount;
|
||||||
|
overviewWfTexPalKey = palKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
overviewGl.uploadRgbaTexture("overview-waterfall", iW, iH, overviewWfTexData, "linear");
|
||||||
overviewGl.drawTexture("overview-waterfall", 0, 0, W, H, 1, true);
|
overviewGl.drawTexture("overview-waterfall", 0, 0, W, H, 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6144,6 +6198,7 @@ function startSpectrumStreaming() {
|
|||||||
clearSpectrumPeakHoldFrames();
|
clearSpectrumPeakHoldFrames();
|
||||||
overviewWaterfallRows = [];
|
overviewWaterfallRows = [];
|
||||||
overviewWaterfallPushCount = 0;
|
overviewWaterfallPushCount = 0;
|
||||||
|
overviewWfResetTextureCache();
|
||||||
scheduleOverviewDraw();
|
scheduleOverviewDraw();
|
||||||
clearSpectrumCanvas();
|
clearSpectrumCanvas();
|
||||||
updateRdsPsOverlay(null);
|
updateRdsPsOverlay(null);
|
||||||
@@ -6192,6 +6247,7 @@ function stopSpectrumStreaming() {
|
|||||||
clearSpectrumPeakHoldFrames();
|
clearSpectrumPeakHoldFrames();
|
||||||
overviewWaterfallRows = [];
|
overviewWaterfallRows = [];
|
||||||
overviewWaterfallPushCount = 0;
|
overviewWaterfallPushCount = 0;
|
||||||
|
overviewWfResetTextureCache();
|
||||||
scheduleOverviewDraw();
|
scheduleOverviewDraw();
|
||||||
updateRdsPsOverlay(null);
|
updateRdsPsOverlay(null);
|
||||||
clearSpectrumCanvas();
|
clearSpectrumCanvas();
|
||||||
|
|||||||
@@ -2299,7 +2299,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 6;
|
z-index: 8;
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 0.68rem;
|
font-size: 0.68rem;
|
||||||
@@ -2311,8 +2311,8 @@ button:focus-visible, input:focus-visible, select:focus-visible {
|
|||||||
}
|
}
|
||||||
.spectrum-bookmark-chip {
|
.spectrum-bookmark-chip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translate(-50%, -50%);
|
transform: translateX(-50%);
|
||||||
top: 50%;
|
top: 2px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -373,9 +373,20 @@
|
|||||||
if (!Array.isArray(points) || points.length < 2) return;
|
if (!Array.isArray(points) || points.length < 2) return;
|
||||||
const radius = Math.max(1, Number(size) || 1);
|
const radius = Math.max(1, Number(size) || 1);
|
||||||
const rgba = normalizeColor(color);
|
const rgba = normalizeColor(color);
|
||||||
|
const verts = [];
|
||||||
for (let i = 0; i < points.length; i += 2) {
|
for (let i = 0; i < points.length; i += 2) {
|
||||||
this.fillRect(points[i] - radius, points[i + 1] - radius, radius * 2, radius * 2, rgba);
|
const x = points[i] - radius;
|
||||||
|
const y = points[i + 1] - radius;
|
||||||
|
const w = radius * 2;
|
||||||
|
const h = radius * 2;
|
||||||
|
pushColoredVertex(verts, x, y, rgba);
|
||||||
|
pushColoredVertex(verts, x + w, y, rgba);
|
||||||
|
pushColoredVertex(verts, x + w, y + h, rgba);
|
||||||
|
pushColoredVertex(verts, x, y, rgba);
|
||||||
|
pushColoredVertex(verts, x + w, y + h, rgba);
|
||||||
|
pushColoredVertex(verts, x, y + h, rgba);
|
||||||
}
|
}
|
||||||
|
this._drawColorGeometry(verts, this.gl.TRIANGLES);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawDashedVerticalLine(x, y0, y1, dashLen, gapLen, color, width = 1) {
|
drawDashedVerticalLine(x, y0, y1, dashLen, gapLen, color, width = 1) {
|
||||||
@@ -383,10 +394,12 @@
|
|||||||
const gap = Math.max(1, Number(gapLen) || 1);
|
const gap = Math.max(1, Number(gapLen) || 1);
|
||||||
const top = Math.min(y0, y1);
|
const top = Math.min(y0, y1);
|
||||||
const bottom = Math.max(y0, y1);
|
const bottom = Math.max(y0, y1);
|
||||||
|
const segments = [];
|
||||||
for (let y = top; y < bottom; y += dash + gap) {
|
for (let y = top; y < bottom; y += dash + gap) {
|
||||||
const segEnd = Math.min(bottom, y + dash);
|
const segEnd = Math.min(bottom, y + dash);
|
||||||
this.drawSegments([x, y, x, segEnd], color, width);
|
segments.push(x, y, x, segEnd);
|
||||||
}
|
}
|
||||||
|
this.drawSegments(segments, color, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadRgbaTexture(name, width, height, data, filter = "linear") {
|
uploadRgbaTexture(name, width, height, data, filter = "linear") {
|
||||||
|
|||||||
Reference in New Issue
Block a user