Skip to content
Merged
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
42 changes: 37 additions & 5 deletions js/packages/pvm-browser-runtime/src/pvm-runtime-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ globalThis.createPvmRuntime = (endpoint) => {
let disposed = false;
let motionAvailability = 0;
let pendingMotionSample = null;
let pendingGpuCapabilities = null;
let timer;
let startedAt = 0;
let updateCount = 0;
Expand Down Expand Up @@ -368,6 +369,10 @@ globalThis.createPvmRuntime = (endpoint) => {
}
const program = validateStartMessage(message);
motionAvailability = message.motionAvailability ?? 0;
pendingGpuCapabilities =
message.gpuCapabilities instanceof ArrayBuffer
? new Uint8Array(message.gpuCapabilities).slice()
: null;
const bootStarted = performance.now();
let translationMs = 0;
let compilationMs = 0;
Expand Down Expand Up @@ -434,15 +439,14 @@ globalThis.createPvmRuntime = (endpoint) => {
MAX_TRANSLATED_LOOPS_PER_UPDATE,
message.audioEnabled,
message.graphicsProfile,
message.gpuCapabilities instanceof ArrayBuffer
? new Uint8Array(message.gpuCapabilities)
: null,
pendingGpuCapabilities,
motionAvailability,
);
if (pendingMotionSample !== null) {
translated.sendMotionSample(pendingMotionSample);
}
translated.initialize();
pendingGpuCapabilities = null;
pendingMotionSample = null;
backend = "compiler";
} catch (error) {
Expand Down Expand Up @@ -493,16 +497,17 @@ globalThis.createPvmRuntime = (endpoint) => {
pendingMotionSample = null;
}
if (message.graphicsProfile === "webgpu-raster") {
if (!(message.gpuCapabilities instanceof ArrayBuffer)) {
if (pendingGpuCapabilities === null) {
throw new Error(
"WebGPU capabilities are required before PVM initialization",
);
}
stage(new Uint8Array(message.gpuCapabilities));
stage(pendingGpuCapabilities);
check(
pvm.pvm_browser_set_gpu_capabilities(),
"set PolkaVM browser GPU capabilities",
);
pendingGpuCapabilities = null;
}
postMessage({ type: "startup", stage: "interpreter-initializing" });
try {
Expand Down Expand Up @@ -599,6 +604,25 @@ globalThis.createPvmRuntime = (endpoint) => {
);
}

function sendGpuCapabilities(bytes) {
if (bytes.byteLength < 56 || bytes.byteLength > 4096) {
throw new Error("invalid PolkaVM browser GPU capabilities");
}
if (!running || !pvm) {
pendingGpuCapabilities = bytes.slice();
return;
}
if (translated) {
translated.setGpuCapabilities(bytes);
return;
}
stage(bytes);
check(
pvm.pvm_browser_set_gpu_capabilities(),
"update PolkaVM browser GPU capabilities",
);
}

function sendGpuEvent(bytes) {
if (!running || !pvm || !bytes.byteLength) {
return;
Expand Down Expand Up @@ -658,6 +682,14 @@ globalThis.createPvmRuntime = (endpoint) => {
postMessage({ type: "error", message: error.message });
postMessage({ type: "terminated" });
}
} else if (message?.type === "gpu-capabilities") {
try {
sendGpuCapabilities(new Uint8Array(message.bytes));
} catch (error) {
stopRuntime();
postMessage({ type: "error", message: error.message });
postMessage({ type: "terminated" });
}
} else if (message?.type === "gpu-event") {
try {
sendGpuEvent(new Uint8Array(message.bytes));
Expand Down
12 changes: 12 additions & 0 deletions js/packages/pvm-browser-runtime/src/pvm-wasm-translated.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,18 @@
this.motionSample = bytes.slice();
}

setGpuCapabilities(bytes) {
if (
this.stopped ||
!(bytes instanceof Uint8Array) ||
bytes.byteLength < 56 ||
bytes.byteLength > 4096
) {
throw new Error("invalid translated WebGPU capabilities");
}
this.gpuCapabilities = bytes.slice();
}

sendGpuEvent(bytes) {
if (
this.stopped ||
Expand Down
72 changes: 72 additions & 0 deletions js/packages/pvm-browser-runtime/test/runtime-core.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,23 @@ function motionResult(bytes) {
};
}

function gpuCapabilities(surfaceGeneration) {
const bytes = new Uint8Array(56);
const view = new DataView(bytes.buffer);
bytes.set([0x45, 0x47, 0x43, 0x31]);
view.setUint16(4, 1, true);
view.setUint32(8, bytes.byteLength, true);
view.setUint16(12, 1, true);
view.setUint32(16, 640, true);
view.setUint32(20, 480, true);
view.setUint32(24, 640, true);
view.setUint32(28, 480, true);
view.setFloat32(32, 1, true);
view.setUint32(36, surfaceGeneration, true);
view.setUint32(40, 1, true);
return bytes;
}

test("browser runtime rejects unbounded launch inputs before compilation", async () => {
for (const [message, expected] of [
[invalidStart({ program: new Uint8Array() }), /program must contain/],
Expand Down Expand Up @@ -188,6 +205,61 @@ test("compiler backend enforces the declared graphics profile", async () => {
await waitForMessage(messages, "terminated");
});

test("compiler startup keeps the newest GPU capabilities", async () => {
const runtime = await readFile(
resolve(packageRoot, "dist/pvm-browser-runtime.wasm"),
);
const program = await readFile(
resolve(
repositoryRoot,
"rust/crates/pvm-runtime/tests/fixtures/framebuffer-test.polkavm",
),
);
const Runtime = globalThis.TranslatedPvmRuntime;
let observedCapabilities;
globalThis.TranslatedPvmRuntime = class extends Runtime {
constructor(...args) {
observedCapabilities = new Uint8Array(args[6]);
super(...args);
}
};
try {
const { messages, receiver } = endpoint();
receiver.onmessage({
data: {
type: "start",
runtime: bytesBuffer(runtime),
program: bytesBuffer(program),
assets: [],
graphicsProfile: "webgpu-raster",
gpuCapabilities: gpuCapabilities(1).buffer,
audioEnabled: false,
cacheKey: "gpu-capabilities-startup",
},
});
receiver.onmessage({
data: {
type: "gpu-capabilities",
bytes: gpuCapabilities(2).buffer,
},
});
const ready = await waitForMessage(messages, "ready");
assert.equal(ready.backend, "compiler");
assert.equal(
new DataView(
observedCapabilities.buffer,
observedCapabilities.byteOffset,
observedCapabilities.byteLength,
).getUint32(36, true),
2,
);
receiver.onmessage({ data: { type: "stop" } });
await waitForMessage(messages, "terminated");
} finally {
globalThis.TranslatedPvmRuntime = Runtime;
}
});

test("native-Wasm and translated backends round-trip opaque TrUAPI frames", async () => {
const runtime = await readFile(
resolve(packageRoot, "dist/pvm-browser-runtime.wasm"),
Expand Down
6 changes: 3 additions & 3 deletions rust/crates/pvm-runtime-assets/assets/SHA256SUMS
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
3b448399a39ee571765f41266032c621ce3668176d7f0d6372c85d6068e7d4a5 pvm-browser-runtime.wasm
de3d1cd9dde09c407cf46ba9c642f3e7923e23bfb30db9eef06bf7c2b949b707 pvm-worker.js
55ce96805e22f60ec9a4da072a316c34c1f650d99bdf1421805c1ba35c2d8161 pvm-worker.js
33418e2c81c117539569eb4cf91af4d058cdfae7dd4556b13f3687f6d6b3bae4 pvm-gpu-worker.js
488519d9d623e9586cc082d063fdaaec56adf88594a7c164b997ae6b5ef19c95 pvm-wasm-translated.js
0e9e36f1d253fc90d224ea46d02eba120a612cc18abfb11a9733d8dd87be625d pvm-runtime-core.js
a4c2a9ed4274c79b0c11ddbac163f1de200d41005ca1ce54909a5b8116d82c46 pvm-wasm-translated.js
79dce3ab925e78e9ef8e5df6e34fb2823e6f362cbc97d265a9ffdcc22cc24ed4 pvm-runtime-core.js
9c929f5d5c64a1b75e7e48485d7c3944ed6838112177ea778827a2c407c2d820 pvm-wasm-worker-entry.js
42 changes: 37 additions & 5 deletions rust/crates/pvm-runtime-assets/assets/pvm-runtime-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ globalThis.createPvmRuntime = (endpoint) => {
let disposed = false;
let motionAvailability = 0;
let pendingMotionSample = null;
let pendingGpuCapabilities = null;
let timer;
let startedAt = 0;
let updateCount = 0;
Expand Down Expand Up @@ -368,6 +369,10 @@ globalThis.createPvmRuntime = (endpoint) => {
}
const program = validateStartMessage(message);
motionAvailability = message.motionAvailability ?? 0;
pendingGpuCapabilities =
message.gpuCapabilities instanceof ArrayBuffer
? new Uint8Array(message.gpuCapabilities).slice()
: null;
const bootStarted = performance.now();
let translationMs = 0;
let compilationMs = 0;
Expand Down Expand Up @@ -434,15 +439,14 @@ globalThis.createPvmRuntime = (endpoint) => {
MAX_TRANSLATED_LOOPS_PER_UPDATE,
message.audioEnabled,
message.graphicsProfile,
message.gpuCapabilities instanceof ArrayBuffer
? new Uint8Array(message.gpuCapabilities)
: null,
pendingGpuCapabilities,
motionAvailability,
);
if (pendingMotionSample !== null) {
translated.sendMotionSample(pendingMotionSample);
}
translated.initialize();
pendingGpuCapabilities = null;
pendingMotionSample = null;
backend = "compiler";
} catch (error) {
Expand Down Expand Up @@ -493,16 +497,17 @@ globalThis.createPvmRuntime = (endpoint) => {
pendingMotionSample = null;
}
if (message.graphicsProfile === "webgpu-raster") {
if (!(message.gpuCapabilities instanceof ArrayBuffer)) {
if (pendingGpuCapabilities === null) {
throw new Error(
"WebGPU capabilities are required before PVM initialization",
);
}
stage(new Uint8Array(message.gpuCapabilities));
stage(pendingGpuCapabilities);
check(
pvm.pvm_browser_set_gpu_capabilities(),
"set PolkaVM browser GPU capabilities",
);
pendingGpuCapabilities = null;
}
postMessage({ type: "startup", stage: "interpreter-initializing" });
try {
Expand Down Expand Up @@ -599,6 +604,25 @@ globalThis.createPvmRuntime = (endpoint) => {
);
}

function sendGpuCapabilities(bytes) {
if (bytes.byteLength < 56 || bytes.byteLength > 4096) {
throw new Error("invalid PolkaVM browser GPU capabilities");
}
if (!running || !pvm) {
pendingGpuCapabilities = bytes.slice();
return;
}
if (translated) {
translated.setGpuCapabilities(bytes);
return;
}
stage(bytes);
check(
pvm.pvm_browser_set_gpu_capabilities(),
"update PolkaVM browser GPU capabilities",
);
}

function sendGpuEvent(bytes) {
if (!running || !pvm || !bytes.byteLength) {
return;
Expand Down Expand Up @@ -658,6 +682,14 @@ globalThis.createPvmRuntime = (endpoint) => {
postMessage({ type: "error", message: error.message });
postMessage({ type: "terminated" });
}
} else if (message?.type === "gpu-capabilities") {
try {
sendGpuCapabilities(new Uint8Array(message.bytes));
} catch (error) {
stopRuntime();
postMessage({ type: "error", message: error.message });
postMessage({ type: "terminated" });
}
} else if (message?.type === "gpu-event") {
try {
sendGpuEvent(new Uint8Array(message.bytes));
Expand Down
12 changes: 12 additions & 0 deletions rust/crates/pvm-runtime-assets/assets/pvm-wasm-translated.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,18 @@
this.motionSample = bytes.slice();
}

setGpuCapabilities(bytes) {
if (
this.stopped ||
!(bytes instanceof Uint8Array) ||
bytes.byteLength < 56 ||
bytes.byteLength > 4096
) {
throw new Error("invalid translated WebGPU capabilities");
}
this.gpuCapabilities = bytes.slice();
}

sendGpuEvent(bytes) {
if (
this.stopped ||
Expand Down
Loading
Loading