blob: 85f303a8b458c25b58f2ce1e7f22d4013fdef07c [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)
94 socket.addEventListener('message', runCtsTestViaSocket);
95}
96
97async function runCtsTestViaSocket(event) {
98 let input = JSON.parse(event.data);
99 runCtsTest(input['q'], input['w']);
100}
101
Austin Eng92b32e82022-11-21 15:16:51 +0000102dataCache.setStore({
103 load: async (path) => {
104 return await (await fetch(`/third_party/webgpu-cts/cache/data/${path}`)).text();
105 }
106});
107
Austin Engcaa9bae2022-08-18 22:49:10 +0000108// Make a rate-limited version `sendMessageTestHeartbeat` that executes
109// at most once every 500 ms.
110const [sendHeartbeat, {
111 start: beginHeartbeatScope,
112 stop: endHeartbeatScope
113}] = rateLimited(sendMessageTestHeartbeat, 500);
114
115function wrapPromiseWithHeartbeat(prototype, key) {
116 const old = prototype[key];
117 prototype[key] = function (...args) {
118 return new Promise((resolve, reject) => {
119 // Send the heartbeat both before and after resolve/reject
120 // so that the heartbeat is sent ahead of any potentially
121 // long-running synchronous code awaiting the Promise.
122 old.call(this, ...args)
123 .then(val => { sendHeartbeat(); resolve(val) })
124 .catch(err => { sendHeartbeat(); reject(err) })
125 .finally(sendHeartbeat);
126 });
127 }
128}
129
130wrapPromiseWithHeartbeat(GPU.prototype, 'requestAdapter');
131wrapPromiseWithHeartbeat(GPUAdapter.prototype, 'requestAdapterInfo');
132wrapPromiseWithHeartbeat(GPUAdapter.prototype, 'requestDevice');
133wrapPromiseWithHeartbeat(GPUDevice.prototype, 'createRenderPipelineAsync');
134wrapPromiseWithHeartbeat(GPUDevice.prototype, 'createComputePipelineAsync');
135wrapPromiseWithHeartbeat(GPUDevice.prototype, 'popErrorScope');
136wrapPromiseWithHeartbeat(GPUQueue.prototype, 'onSubmittedWorkDone');
137wrapPromiseWithHeartbeat(GPUBuffer.prototype, 'mapAsync');
James Price55509fa2023-03-10 11:06:36 +0000138wrapPromiseWithHeartbeat(GPUShaderModule.prototype, 'getCompilationInfo');
Austin Engcaa9bae2022-08-18 22:49:10 +0000139
Austin Engb00c50e2022-08-26 22:34:27 +0000140globalTestConfig.testHeartbeatCallback = sendHeartbeat;
Austin Enge0cbb0c2022-09-13 22:16:42 +0000141globalTestConfig.noRaceWithRejectOnTimeout = true;
Austin Engcaa9bae2022-08-18 22:49:10 +0000142
Ben Clayton33bfc9882023-01-05 21:44:37 +0000143// FXC is very slow to compile unrolled const-eval loops, where the metal shader
144// compiler (Intel GPU) is very slow to compile rolled loops. Intel drivers for
145// linux may also suffer the same performance issues, so unroll const-eval loops
146// if we're not running on Windows.
147if (navigator.userAgent.indexOf("Windows") !== -1) {
148 globalTestConfig.unrollConstEvalLoops = true;
149}
150
Austin Eng1cdea902022-03-24 00:21:55 +0000151async function runCtsTest(query, use_worker) {
152 const workerEnabled = use_worker;
153 const worker = workerEnabled ? new TestWorker(false) : undefined;
154
155 const loader = new DefaultTestFileLoader();
156 const filterQuery = parseQuery(query);
157 const testcases = await loader.loadCases(filterQuery);
158
159 const expectations = [];
160
161 const log = new Logger();
162
163 for (const testcase of testcases) {
164 const name = testcase.query.toString();
165
166 const wpt_fn = async () => {
Brian Sheedy06535012022-08-11 14:39:51 +0000167 sendMessageTestStarted();
Austin Eng1cdea902022-03-24 00:21:55 +0000168 const [rec, res] = log.record(name);
Austin Engcaa9bae2022-08-18 22:49:10 +0000169
170 beginHeartbeatScope();
Austin Eng1cdea902022-03-24 00:21:55 +0000171 if (worker) {
Austin Engb00c50e2022-08-26 22:34:27 +0000172 await worker.run(rec, name, expectations);
Austin Eng1cdea902022-03-24 00:21:55 +0000173 } else {
Austin Engb00c50e2022-08-26 22:34:27 +0000174 await testcase.run(rec, expectations);
Austin Eng1cdea902022-03-24 00:21:55 +0000175 }
Austin Engcaa9bae2022-08-18 22:49:10 +0000176 endHeartbeatScope();
Austin Eng1cdea902022-03-24 00:21:55 +0000177
Brian Sheedy06535012022-08-11 14:39:51 +0000178 sendMessageTestStatus(res.status, res.timems);
Austin Engcaa9bae2022-08-18 22:49:10 +0000179 sendMessageTestLog(res.logs);
Brian Sheedy06535012022-08-11 14:39:51 +0000180 sendMessageTestFinished();
Austin Eng1cdea902022-03-24 00:21:55 +0000181 };
182 await wpt_fn();
183 }
184}
185
Brian Sheedy06535012022-08-11 14:39:51 +0000186function splitLogsForPayload(fullLogs) {
187 let logPieces = [fullLogs]
188 // Split the log pieces until they all are guaranteed to fit into a
189 // websocket payload.
190 while (true) {
191 let tempLogPieces = []
192 for (const piece of logPieces) {
193 if (byteSize(piece) > LOGS_MAX_BYTES) {
194 let midpoint = Math.floor(piece.length / 2);
195 tempLogPieces.push(piece.substring(0, midpoint));
196 tempLogPieces.push(piece.substring(midpoint));
197 } else {
198 tempLogPieces.push(piece)
199 }
200 }
201 // Didn't make any changes - all pieces are under the size limit.
202 if (logPieces.every((value, index) => value == tempLogPieces[index])) {
203 break;
204 }
205 logPieces = tempLogPieces;
206 }
207 return logPieces
208}
209
210function sendMessageTestStarted() {
Austin Engcaa9bae2022-08-18 22:49:10 +0000211 socket.send('{"type":"TEST_STARTED"}');
212}
213
214function sendMessageTestHeartbeat() {
215 socket.send('{"type":"TEST_HEARTBEAT"}');
Brian Sheedy06535012022-08-11 14:39:51 +0000216}
217
218function sendMessageTestStatus(status, jsDurationMs) {
Ben Clayton33bfc9882023-01-05 21:44:37 +0000219 socket.send(JSON.stringify({
220 'type': 'TEST_STATUS',
221 'status': status,
222 'js_duration_ms': jsDurationMs
223 }));
Brian Sheedy06535012022-08-11 14:39:51 +0000224}
225
Austin Engcaa9bae2022-08-18 22:49:10 +0000226function sendMessageTestLog(logs) {
227 splitLogsForPayload((logs ?? []).map(prettyPrintLog).join('\n\n'))
228 .forEach((piece) => {
229 socket.send(JSON.stringify({
230 'type': 'TEST_LOG',
231 'log': piece
232 }));
233 });
Brian Sheedy06535012022-08-11 14:39:51 +0000234}
235
236function sendMessageTestFinished() {
Austin Engcaa9bae2022-08-18 22:49:10 +0000237 socket.send('{"type":"TEST_FINISHED"}');
Brian Sheedy06535012022-08-11 14:39:51 +0000238}
239
Austin Eng1cdea902022-03-24 00:21:55 +0000240window.runCtsTest = runCtsTest;
241window.setupWebsocket = setupWebsocket