From 0957d9712b2abf6a51baa793beac7a1b8ac5bbf8 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Fri, 16 Oct 2026 19:14:52 +0400 Subject: [PATCH] test(globe): pixel dashes at DPR 1 and 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of Task 3 (DPI-independent dashes; the shader change landed with the overlay commit): qa-globe-contract --dpr=2 runs the suite on a high-DPI page, and a new check measures a 16/16 px dashed line along its middle row in CSS pixels: the dashes must be regular, and on Navara 16/16 within 2 px. The harness prints what each engine draws. Measured: Navara 16/16 (period 32) at DPR 1 and 2. Cesium draws 16 on / 16 off (PolylineDash dashLength 32, pattern 0xff00, uniforms checked on the primitive) as 14 on / 2 off with a 16 px period at both ratios — a Cesium-side difference to look at before a layer relies on pixel dashes (none does yet). At DPR 2 the Navara harness's stars-at-midnight check counts 4 star pixels (its detector is tuned for DPR 1); every suite check passes at both ratios. --- src/globe/testing/contractSuite.mjs | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/globe/testing/contractSuite.mjs b/src/globe/testing/contractSuite.mjs index 386b7ad8d..b89315e80 100644 --- a/src/globe/testing/contractSuite.mjs +++ b/src/globe/testing/contractSuite.mjs @@ -799,6 +799,75 @@ export function createContractSuite( } }, ], + [ + 'lines: pixel dashes are regular, in CSS pixels', + async () => { + if (!readRegion) return; + await nadirView(1e6); + const line = engine.lines.createLine({ + id: 'contract-dashes', + widthPx: 6, + color: [1, 0, 1, 1], + dash: { lengthPx: 16, gapPx: 16 }, + }); + try { + const a = cartesian3.fromDegrees(19, 9, 20_000); + const b = cartesian3.fromDegrees(21, 9, 20_000); + line.setPositions([a, b]); + const sa = engine.projector.worldToWindow(a); + const sb = engine.projector.worldToWindow(b); + // On/off runs (CSS px) along the middle of the line. + const runs = () => { + const left = Math.round(Math.min(sa.x, sb.x)) + 4; + const width = Math.round(Math.abs(sb.x - sa.x)) - 8; + const y = Math.round((sa.y + sb.y) / 2); + const data = readRegion(left, y, width, 1); + const on = []; + const off = []; + let current = null; + let length = 0; + for (let x = 0; x < width; x++) { + const i = x * 4; + const lit = + Math.min(data[i], data[i + 2]) - data[i + 1] > 60 && + data[i] > 150; + if (lit === current) length++; + else { + if (current !== null) (current ? on : off).push(length); + current = lit; + length = 1; + } + } + // The first and last runs are cut by the window. + return { on: on.slice(1), off: off.slice(1) }; + }; + const median = (values) => + [...values].sort((x, y) => x - y)[Math.floor(values.length / 2)]; + await until(() => runs().on.length >= 3, 'dashes drawn'); + const { on, off } = runs(); + const spread = (values) => Math.max(...values) - Math.min(...values); + // What each engine draws for 16/16, for the harness to compare + // across device pixel ratios (Cesium's PolylineDash is the + // reference; Navara's must not change with the ratio). + globalThis.__qaDashes = { + dash: median(on), + gap: median(off), + period: median(on) + median(off), + }; + ensure( + on.length >= 3 && spread(on) <= 2 && spread(off) <= 2, + `irregular dashes: runs ${JSON.stringify(on)} / ${JSON.stringify(off)}`, + ); + if (engine.kind === 'navara') + ensure( + Math.abs(median(on) - 16) <= 2 && Math.abs(median(off) - 16) <= 2, + `dash ${median(on)} px, gap ${median(off)} px (16/16 asked)`, + ); + } finally { + line.destroy(); + } + }, + ], [ 'render: continuous mode toggles', async () => {