blob: ec3bf7679426c31075256a4f978b9c24c452f65f [file] [log] [blame]
Austin Eng1cdea902022-03-24 00:21:55 +00001// Copyright 2022 The Dawn Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Austin Engb00c50e2022-08-26 22:34:27 +000015import { globalTestConfig } from '../third_party/webgpu-cts/src/common/framework/test_config.js';
Austin Eng92b32e82022-11-21 15:16:51 +000016import { dataCache } from '../third_party/webgpu-cts/src/common/framework/data_cache.js';
Austin Eng1cdea902022-03-24 00:21:55 +000017import { DefaultTestFileLoader } from '../third_party/webgpu-cts/src/common/internal/file_loader.js';
18import { prettyPrintLog } from '../third_party/webgpu-cts/src/common/internal/logging/log_message.js';
19import { Logger } from '../third_party/webgpu-cts/src/common/internal/logging/logger.js';
20import { parseQuery } from '../third_party/webgpu-cts/src/common/internal/query/parseQuery.js';
21
22import { TestWorker } from '../third_party/webgpu-cts/src/common/runtime/helper/test_worker.js';
23
Brian Sheedy17b1a452022-04-14 17:19:11 +000024// The Python-side websockets library has a max payload size of 72638. Set the
25// max allowable logs size in a single payload to a bit less than that.
26const LOGS_MAX_BYTES = 72000;
27
Austin Eng1cdea902022-03-24 00:21:55 +000028var socket;
29
Austin Engcaa9bae2022-08-18 22:49:10 +000030// Returns a wrapper around `fn` which gets called at most once every `intervalMs`.
31// If the wrapper is called when `fn` was called too recently, `fn` is scheduled to
32// be called later in the future after the interval passes.
33// Returns [ wrappedFn, {start, stop}] where wrappedFn is the rate-limited function,
34// and start/stop control whether or not the function is enabled. If it is stopped, calls
35// to the fn will no-op. If it is started, calls will be rate-limited, starting from
36// the time `start` is called.
37function rateLimited(fn, intervalMs) {
38 let last = undefined;
39 let timer = undefined;
40 const wrappedFn = (...args) => {
Austin Eng26ffcd12022-09-13 18:28:31 +000041 if (last === undefined) {
42 // If the function is not enabled, return.
Austin Engcaa9bae2022-08-18 22:49:10 +000043 return;
44 }
45 // Get the current time as a number.
46 const now = +new Date();
47 const diff = now - last;
48 if (diff >= intervalMs) {
49 // Clear the timer, if there was one. This could happen if a timer
50 // is scheduled, but it never runs due to long-running synchronous
51 // code.
52 if (timer) {
53 clearTimeout(timer);
54 timer = undefined;
55 }
56
57 // Call the function.
58 last = now;
59 fn(...args);
60 } else if (timer === undefined) {
61 // Otherwise, we have called `fn` too recently.
62 // Schedule a future call.
63 timer = setTimeout(() => {
64 // Clear the timer to indicate nothing is scheduled.
65 timer = undefined;
66 last = +new Date();
67 fn(...args);
68 }, intervalMs - diff + 1);
69 }
70 };
71 return [
72 wrappedFn,
73 {
74 start: () => {
75 last = +new Date();
76 },
77 stop: () => {
78 last = undefined;
79 if (timer) {
80 clearTimeout(timer);
81 timer = undefined;
82 }
83 },
84 }
85 ];
86}
87
Brian Sheedy17b1a452022-04-14 17:19:11 +000088function byteSize(s) {
89 return new Blob([s]).size;
90}
91
Austin Eng1cdea902022-03-24 00:21:55 +000092async function setupWebsocket(port) {
93 socket = new WebSocket('ws://127.0.0.1:' + port)
Austin Eng20461632023-05-03 23:36:12 +000094 socket.addEventListener('open', () => {
95 socket.send('{"type":"CONNECTION_ACK"}');
96 });
Austin Eng1cdea902022-03-24 00:21:55 +000097 socket.addEventListener('message', runCtsTestViaSocket);
98}
99
100async function runCtsTestViaSocket(event) {
101 let input = JSON.parse(event.data);
102 runCtsTest(input['q'], input['w']);
103}
104
Austin Eng92b32e82022-11-21 15:16:51 +0000105dataCache.setStore({
106 load: async (path) => {
107 return await (await fetch(`/third_party/webgpu-cts/cache/data/${path}`)).text();
108 }
109});
110
Austin Engcaa9bae2022-08-18 22:49:10 +0000111// Make a rate-limited version `sendMessageTestHeartbeat` that executes
112// at most once every 500 ms.
113const [sendHeartbeat, {
114 start: beginHeartbeatScope,
115 stop: endHeartbeatScope
116}] = rateLimited(sendMessageTestHeartbeat, 500);
117
118function wrapPromiseWithHeartbeat(prototype, key) {
119 const old = prototype[key];
120 prototype[key] = function (...args) {
121 return new Promise((resolve, reject) => {
122 // Send the heartbeat both before and after resolve/reject
123 // so that the heartbeat is sent ahead of any potentially
124 // long-running synchronous code awaiting the Promise.
125 old.call(this, ...args)
126 .then(val => { sendHeartbeat(); resolve(val) })
127 .catch(err => { sendHeartbeat(); reject(err) })
128 .finally(sendHeartbeat);
129 });
130 }
131}
132
133wrapPromiseWithHeartbeat(GPU.prototype, 'requestAdapter');
134wrapPromiseWithHeartbeat(GPUAdapter.prototype, 'requestAdapterInfo');
135wrapPromiseWithHeartbeat(GPUAdapter.prototype, 'requestDevice');
136wrapPromiseWithHeartbeat(GPUDevice.prototype, 'createRenderPipelineAsync');
137wrapPromiseWithHeartbeat(GPUDevice.prototype, 'createComputePipelineAsync');
138wrapPromiseWithHeartbeat(GPUDevice.prototype, 'popErrorScope');
139wrapPromiseWithHeartbeat(GPUQueue.prototype, 'onSubmittedWorkDone');
140wrapPromiseWithHeartbeat(GPUBuffer.prototype, 'mapAsync');
James Price55509fa2023-03-10 11:06:36 +0000141wrapPromiseWithHeartbeat(GPUShaderModule.prototype, 'getCompilationInfo');
Austin Engcaa9bae2022-08-18 22:49:10 +0000142
Austin Engb00c50e2022-08-26 22:34:27 +0000143globalTestConfig.testHeartbeatCallback = sendHeartbeat;
Austin Enge0cbb0c2022-09-13 22:16:42 +0000144globalTestConfig.noRaceWithRejectOnTimeout = true;
Austin Engcaa9bae2022-08-18 22:49:10 +0000145
Ben Clayton33bfc9882023-01-05 21:44:37 +0000146// FXC is very slow to compile unrolled const-eval loops, where the metal shader
147// compiler (Intel GPU) is very slow to compile rolled loops. Intel drivers for
148// linux may also suffer the same performance issues, so unroll const-eval loops
149// if we're not running on Windows.
150if (navigator.userAgent.indexOf("Windows") !== -1) {
151 globalTestConfig.unrollConstEvalLoops = true;
152}
153
Austin Eng1cdea902022-03-24 00:21:55 +0000154async function runCtsTest(query, use_worker) {
155 const workerEnabled = use_worker;
156 const worker = workerEnabled ? new TestWorker(false) : undefined;
157
158 const loader = new DefaultTestFileLoader();
159 const filterQuery = parseQuery(query);
160 const testcases = await loader.loadCases(filterQuery);
161
162 const expectations = [];
163
164 const log = new Logger();
165
166 for (const testcase of testcases) {
167 const name = testcase.query.toString();
168
169 const wpt_fn = async () => {
Brian Sheedy06535012022-08-11 14:39:51 +0000170 sendMessageTestStarted();
Austin Eng1cdea902022-03-24 00:21:55 +0000171 const [rec, res] = log.record(name);
Austin Engcaa9bae2022-08-18 22:49:10 +0000172
173 beginHeartbeatScope();
Austin Eng1cdea902022-03-24 00:21:55 +0000174 if (worker) {
Austin Engb00c50e2022-08-26 22:34:27 +0000175 await worker.run(rec, name, expectations);
Austin Eng1cdea902022-03-24 00:21:55 +0000176 } else {
Austin Engb00c50e2022-08-26 22:34:27 +0000177 await testcase.run(rec, expectations);
Austin Eng1cdea902022-03-24 00:21:55 +0000178 }
Austin Engcaa9bae2022-08-18 22:49:10 +0000179 endHeartbeatScope();
Austin Eng1cdea902022-03-24 00:21:55 +0000180
Brian Sheedy06535012022-08-11 14:39:51 +0000181 sendMessageTestStatus(res.status, res.timems);
Austin Engcaa9bae2022-08-18 22:49:10 +0000182 sendMessageTestLog(res.logs);
Brian Sheedy06535012022-08-11 14:39:51 +0000183 sendMessageTestFinished();
Austin Eng1cdea902022-03-24 00:21:55 +0000184 };
185 await wpt_fn();
186 }
187}
188
Brian Sheedy06535012022-08-11 14:39:51 +0000189function splitLogsForPayload(fullLogs) {
190 let logPieces = [fullLogs]
191 // Split the log pieces until they all are guaranteed to fit into a
192 // websocket payload.
193 while (true) {
194 let tempLogPieces = []
195 for (const piece of logPieces) {
196 if (byteSize(piece) > LOGS_MAX_BYTES) {
197 let midpoint = Math.floor(piece.length / 2);
198 tempLogPieces.push(piece.substring(0, midpoint));
199 tempLogPieces.push(piece.substring(midpoint));
200 } else {
201 tempLogPieces.push(piece)
202 }
203 }
204 // Didn't make any changes - all pieces are under the size limit.
205 if (logPieces.every((value, index) => value == tempLogPieces[index])) {
206 break;
207 }
208 logPieces = tempLogPieces;
209 }
210 return logPieces
211}
212
213function sendMessageTestStarted() {
Austin Engcaa9bae2022-08-18 22:49:10 +0000214 socket.send('{"type":"TEST_STARTED"}');
215}
216
217function sendMessageTestHeartbeat() {
218 socket.send('{"type":"TEST_HEARTBEAT"}');
Brian Sheedy06535012022-08-11 14:39:51 +0000219}
220
221function sendMessageTestStatus(status, jsDurationMs) {
Ben Clayton33bfc9882023-01-05 21:44:37 +0000222 socket.send(JSON.stringify({
223 'type': 'TEST_STATUS',
224 'status': status,
225 'js_duration_ms': jsDurationMs
226 }));
Brian Sheedy06535012022-08-11 14:39:51 +0000227}
228
Austin Engcaa9bae2022-08-18 22:49:10 +0000229function sendMessageTestLog(logs) {
230 splitLogsForPayload((logs ?? []).map(prettyPrintLog).join('\n\n'))
231 .forEach((piece) => {
232 socket.send(JSON.stringify({
233 'type': 'TEST_LOG',
234 'log': piece
235 }));
236 });
Brian Sheedy06535012022-08-11 14:39:51 +0000237}
238
239function sendMessageTestFinished() {
Austin Engcaa9bae2022-08-18 22:49:10 +0000240 socket.send('{"type":"TEST_FINISHED"}');
Brian Sheedy06535012022-08-11 14:39:51 +0000241}
242
Austin Eng1cdea902022-03-24 00:21:55 +0000243window.runCtsTest = runCtsTest;
244window.setupWebsocket = setupWebsocket