Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/build-quiche-owned.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ name: Build owned quiche dependency

on:
workflow_dispatch:
inputs:
dependency:
description: Owned dependency to build
type: choice
options: [quiche, dawn]
default: quiche
pull_request:
branches:
- main
Expand Down Expand Up @@ -38,6 +44,7 @@ concurrency:

jobs:
build:
if: github.event_name != 'workflow_dispatch' || inputs.dependency != 'dawn'
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -224,3 +231,56 @@ jobs:
--repo "${{ github.repository }}" \
--verify-tag --title "Owned quiche ${{ github.ref_name }}" \
--generate-notes

dawn:
if: github.event_name == 'workflow_dispatch' && inputs.dependency == 'dawn'
runs-on: ubuntu-24.04
timeout-minutes: 120
steps:
- uses: actions/checkout@v7
- name: Build pinned Linux x64 Dawn in isolated manylinux container
run: node packages/runtime-native/scripts/build-dawn-linux.mjs
- uses: actions/upload-artifact@v4
with:
name: dawn-linux-x64
path: |
packages/runtime-native/artifacts/dawn-linux-x64/Dawn-*.tar.gz
packages/runtime-native/artifacts/dawn-linux-x64/manifest-linux-x64.json
if-no-files-found: error

publish-dawn:
if: github.event_name == 'workflow_dispatch' && inputs.dependency == 'dawn'
needs: dawn
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
name: dawn-linux-x64
path: dist-dawn
- name: Publish immutable dependency cache
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
SOURCE_REF: ${{ github.sha }}
run: |
set -euo pipefail
ARCHIVE=dist-dawn/Dawn-d14ae3d97ad74100e9f382efef5e9c0872ddbeb2-ubuntu-latest-Release.tar.gz
test -s "$ARCHIVE"
test -s dist-dawn/manifest-linux-x64.json
python3 - <<'PY'
import hashlib, json, pathlib
root = pathlib.Path('dist-dawn')
manifest = json.loads((root / 'manifest-linux-x64.json').read_text())
sha = 'd14ae3d97ad74100e9f382efef5e9c0872ddbeb2'
archive = f'Dawn-{sha}-ubuntu-latest-Release.tar.gz'
assert manifest['source_commit'] == manifest['upstream_commit'] == sha
assert manifest['expected_tag'] == 'dawn-linux-x64-v1'
assert manifest['target'] == 'linux-x64' and manifest['archive'] == archive
assert manifest['archive_sha256'] == hashlib.sha256((root / archive).read_bytes()).hexdigest()
PY
gh release create dawn-linux-x64-v1 "$ARCHIVE" dist-dawn/manifest-linux-x64.json \
--target "$SOURCE_REF" --title "Dawn Linux x64 dependency cache v1" \
--prerelease --latest=false --generate-notes
134 changes: 134 additions & 0 deletions docs/PRDs/native/PRD-614-mystralnative-upstream-catch-up-v0.1.6.md

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions docs/verification/native-coverage-2026-08-28.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ targets could not be built and are named below.
| `src/fs/` | 235 | 189 | 80.43% |
| `src/http/` | 410 | 377 | 91.95% |
| `src/js/` | 2785 | 2212 | 79.43% |
| `src/platform/` | 1460 | 1003 | 68.70% |
| `src/platform/` | 1465 | 1008 | 68.81% |
| `src/raytracing/` | 461 | 399 | 86.55% |
| `src/runtime.cpp` | 2425 | 1890 | 77.94% |
| `src/runtime.cpp` | 2442 | 1896 | 77.64% |
| `src/screenshot_gate.cpp` | 27 | 24 | 88.89% |
| `src/storage/` | 327 | 286 | 87.46% |
| `src/utils/` | 0 | 0 | 0.00% |
| `src/vfs/` | 239 | 195 | 81.59% |
| `src/webgpu/` | 9842 | 7566 | 76.87% |
| `src/webgpu/` | 9870 | 7566 | 76.66% |
| `src/webtransport/` | 1391 | 1078 | 77.50% |
| `src/workers/` | 615 | 527 | 85.69% |
| **TOTAL** | **24785** | **19459** | **78.51%** |
| `src/workers/` | 615 | 524 | 85.20% |
| **TOTAL** | **24835** | **19467** | **78.39%** |

Source digest: `sha256:2828ae43f9f7f863426676c8b76acf9a02576361e410f0fafac09a8df706b1b9`
Source digest: `sha256:4694f6c260feec4929114d04f323617c5d8b260073cdb3394f70e751c2122dce`

The default `pnpm budgets` gate reads this committed measurement without configuring or compiling
the native host. Any native source, native C++ test, CTest registration, or coverage aggregation
Expand All @@ -41,7 +41,7 @@ change requires this opt-in command to refresh the record.
| `src/fs/` | 75.43% |
| `src/http/` | 86.95% |
| `src/js/` | 74.50% |
| `src/platform/` | 63.70% |
| `src/platform/` | 63.81% |
| `src/raytracing/` | 81.55% |
| `src/runtime.cpp` | 73.28% |
| `src/screenshot_gate.cpp` | 88.89% |
Expand Down
26 changes: 25 additions & 1 deletion packages/runtime-native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1096,10 +1096,22 @@ else()
endif()
else()
set(SKIA_PLATFORM_DIR "linux-gpu")
set(SKIA_LIB_SUBDIR "x64")
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
set(SKIA_LIB_SUBDIR "arm64")
else()
set(SKIA_LIB_SUBDIR "x64")
endif()
endif()
endif()

# Linux arm64 ships an arm64 Skia prebuilt (library-builder chrome/m145), so a build that asked
# for Canvas2D and cannot find it is a missing dependency, not a host that never had Canvas2D.
# Disabling it there made the arm64 desktop artifact quietly differ from every other platform.
if(TN_ENABLE_CANVAS2D AND MYSTRAL_PLATFORM STREQUAL "linux"
AND CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
set(TN_SKIA_ARM64_REQUIRED ON)
endif()

if(TN_ENABLE_CANVAS2D AND EXISTS ${SKIA_TARGET_DIR})
message(STATUS "Found Skia directory: ${SKIA_TARGET_DIR}")

Expand Down Expand Up @@ -1131,6 +1143,13 @@ if(TN_ENABLE_CANVAS2D AND EXISTS ${SKIA_TARGET_DIR})
message(STATUS "Found Skia: ${SKIA_LIBRARY}")
message(STATUS "Skia includes: ${SKIA_INCLUDE_DIR}")
else()
if(TN_SKIA_ARM64_REQUIRED)
message(FATAL_ERROR
"TN_SKIA_ARM64_MISSING: Canvas2D was requested on arm64 but Skia is incomplete.\n"
" Library: ${SKIA_LIB_PATH}\n"
" Include: ${SKIA_INCLUDE_DIR}\n"
" Run 'node scripts/download-deps.mjs --only skia' on an arm64 host, or configure with -DTN_ENABLE_CANVAS2D=OFF to drop the surface deliberately.")
endif()
message(WARNING "Skia library or headers not found:")
message(WARNING " Library: ${SKIA_LIB_PATH}")
message(WARNING " Include: ${SKIA_INCLUDE_DIR}")
Expand All @@ -1139,6 +1158,11 @@ else()
if(NOT TN_ENABLE_CANVAS2D)
message(STATUS "Skia disabled because TN_ENABLE_CANVAS2D=OFF")
endif()
if(TN_SKIA_ARM64_REQUIRED)
message(FATAL_ERROR
"TN_SKIA_ARM64_MISSING: Canvas2D was requested on arm64 but no Skia was found at ${SKIA_TARGET_DIR}.\n"
" Run 'node scripts/download-deps.mjs --only skia' on an arm64 host, or configure with -DTN_ENABLE_CANVAS2D=OFF to drop the surface deliberately.")
endif()
if(MYSTRAL_PLATFORM STREQUAL "ios")
message(STATUS "Skia iOS not found - Canvas 2D support disabled. Run 'node scripts/download-deps.mjs --only skia-ios' to enable.")
else()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* The desktop core gate must run on the video driver a caller actually asked for.
*
* `SDL_VIDEODRIVER=wayland pnpm native:verify:desktop` is the documented Wayland command and SDL 3
* reads `SDL_VIDEO_DRIVER`, so the gate that used to pin `SDL_VIDEODRIVER=x11` and wrap every run in
* Xvfb proved X11 and called it Wayland. These cases pin the requested-driver routing.
*/

import { describe, expect, it } from "vitest";

// @ts-expect-error -- the desktop verifier is plain ESM with no type declarations.
import { requestedLinuxVideoDriver } from "../scripts/verify-desktop-core.mjs";

describe("requestedLinuxVideoDriver", () => {
it("honors the SDL 2 spelling, the wayland request included", () => {
expect(requestedLinuxVideoDriver({ SDL_VIDEODRIVER: "wayland" })).toEqual({
driver: "wayland",
wayland: true,
});
});

it("prefers the SDL 3 spelling when both are set", () => {
expect(
requestedLinuxVideoDriver({ SDL_VIDEO_DRIVER: "x11", SDL_VIDEODRIVER: "wayland" }),
).toEqual({
driver: "x11",
wayland: false,
});
});

it("keeps x11 as the default, and does not read an empty variable as a request", () => {
expect(requestedLinuxVideoDriver({})).toEqual({ driver: "x11", wayland: false });
expect(requestedLinuxVideoDriver({ SDL_VIDEO_DRIVER: "", SDL_VIDEODRIVER: "" })).toEqual({
driver: "x11",
wayland: false,
});
});
});
13 changes: 13 additions & 0 deletions packages/runtime-native/conformance/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,19 @@
"desktopGate": false,
"required": true
},
{
"id": "91-queue-write-buffer-zero",
"title": "zero-length queue writeBuffer preserves nonzero writes and rejects invalid sources",
"category": "runtime",
"status": "implemented",
"scene": "conformance/scenes/shared/queue-write-buffer-zero.js",
"tolerance": {
"pixelMismatchRatio": 0.01,
"perceptualDeltaE": 3.0
},
"desktopGate": true,
"required": true
},
{
"id": "91-frame-upload-stream",
"title": "steady-state frame upload stream",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
export async function verifyQueueWriteBuffer(device) {
if (!device?.queue || typeof device.createBuffer !== "function") {
throw new Error("writeBuffer proof requires a real GPU device");
}
const buffer = device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
});
const readback = device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
});
const failures = [];
try {
device.queue.writeBuffer(buffer, 0, new Uint32Array([11, 22, 33, 44]));
const source = new Uint32Array([99]);
const emptyWrites = [
[new Uint8Array(0)],
[new ArrayBuffer(0)],
[new DataView(new ArrayBuffer(0))],
[source, 0, 0],
[source, 1, 0],
];
for (const args of emptyWrites) {
try {
if (device.queue.writeBuffer(buffer, 0, ...args) !== undefined) {
failures.push("zero-length write did not return undefined");
}
} catch (error) {
failures.push(`zero-length ${args[0].constructor.name}: ${String(error.message || error)}`);
}
}
const invalidSources = [
[source, -1],
[source, 0, 2],
[{}],
[{ byteLength: 0 }],
[new Uint8Array(0), 1],
[new ArrayBuffer(0), 0, 1],
[new DataView(new ArrayBuffer(0)), 1],
];
for (const args of invalidSources) {
let rejected = false;
try {
device.queue.writeBuffer(buffer, 0, ...args);
} catch {
rejected = true;
}
if (!rejected) failures.push("invalid nonempty source was accepted");
}
device.queue.writeBuffer(buffer, 8, new Uint32Array([55, 66]));
const encoder = device.createCommandEncoder();
encoder.copyBufferToBuffer(buffer, 0, readback, 0, 16);
device.queue.submit([encoder.finish()]);
await readback.mapAsync(GPUMapMode.READ);
const observed = Array.from(new Uint32Array(readback.getMappedRange()));
readback.unmap();
if (observed.join(",") !== "11,22,55,66") {
failures.push(`nonzero writes or no-op preservation failed: ${observed}`);
}
if (failures.length) throw new Error(failures.join("; "));
return {
emptyWrites: emptyWrites.length,
invalidSourcesRejected: invalidSources.length,
observed,
};
} finally {
buffer.destroy();
readback.destroy();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { startBehaviorScene } from "./scene-support.js";
import { verifyQueueWriteBuffer } from "./queue-write-buffer-proof.js";

export function startScene(canvas, dimensions) {
return startBehaviorScene(canvas, dimensions, "queue-write-buffer-zero", async () => {
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) throw new Error("writeBuffer proof requires a GPU adapter");
const device = await adapter.requestDevice();
return verifyQueueWriteBuffer(device);
});
}
8 changes: 8 additions & 0 deletions packages/runtime-native/include/mystral/webgpu_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ typedef WGPUSurfaceDescriptorFromWindowsHWND WGPUSurfaceDescriptorFromWindowsHWN
typedef WGPUSurfaceDescriptorFromXlibWindow WGPUSurfaceDescriptorFromXlibWindow_Compat;
#define WGPUSType_SurfaceDescriptorFromXlibWindow_Compat WGPUSType_SurfaceDescriptorFromXlibWindow

// Surface descriptors - Linux Wayland
typedef WGPUSurfaceDescriptorFromWaylandSurface WGPUSurfaceDescriptorFromWaylandSurface_Compat;
#define WGPUSType_SurfaceDescriptorFromWaylandSurface_Compat WGPUSType_SurfaceDescriptorFromWaylandSurface

// Surface descriptors - Android ANativeWindow
typedef WGPUSurfaceDescriptorFromAndroidNativeWindow WGPUSurfaceDescriptorFromAndroidNativeWindow_Compat;
#define WGPUSType_SurfaceDescriptorFromAndroidNativeWindow_Compat WGPUSType_SurfaceDescriptorFromAndroidNativeWindow
Expand Down Expand Up @@ -133,6 +137,10 @@ typedef WGPUSurfaceSourceWindowsHWND WGPUSurfaceDescriptorFromWindowsHWND_Compat
typedef WGPUSurfaceSourceXlibWindow WGPUSurfaceDescriptorFromXlibWindow_Compat;
#define WGPUSType_SurfaceDescriptorFromXlibWindow_Compat WGPUSType_SurfaceSourceXlibWindow

// Surface descriptors - Linux Wayland
typedef WGPUSurfaceSourceWaylandSurface WGPUSurfaceDescriptorFromWaylandSurface_Compat;
#define WGPUSType_SurfaceDescriptorFromWaylandSurface_Compat WGPUSType_SurfaceSourceWaylandSurface

// Surface descriptors - Android ANativeWindow
typedef WGPUSurfaceSourceAndroidNativeWindow WGPUSurfaceDescriptorFromAndroidNativeWindow_Compat;
#define WGPUSType_SurfaceDescriptorFromAndroidNativeWindow_Compat WGPUSType_SurfaceSourceAndroidNativeWindow
Expand Down
Loading