1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
|
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script>
ok(
SpecialPowers.getBoolPref("dom.webgpu.enabled"),
"Pref should be enabled."
);
SimpleTest.waitForExplicitFinish();
// This test has 3 phases:
// 1) Repeatedly call a function that creates some WebGPU objects with
// some variations. One of the objects is always an encoder. Act on
// those objects in ways that might confuse the cycle detector. All
// of the objects *should* be garbage collectable, including the
// encoders. Store a weak link to each of the encoders.
// 2) Invoke garbage collection.
// 3) Confirm all the encoders were garbage collected.
// Define some stuff we'll use in the various phases.
const gc_promise = () =>
new Promise(resolve => SpecialPowers.exactGC(resolve));
// Define an array of structs containing a label and a weak reference
// to an encoder, then fill it by executing a bunch of WebGPU commands.
let results = [];
// Here's our WebGPU test function, which we'll call with permuted
// parameters:
// label: string label to use in error messages
// encoderType: string in ["render", "compute", "bundle"].
// resourceExtraParam: boolean should one of the resources get an
// added property with a scalar value. This can change the order that
// things are processed by the cycle collector.
// resourceCycle: boolean should one of the resources get an added
// property that is set to the encoder.
// endOrFinish: boolean should the encoder be ended. If not, it's just
// dropped.
let test_func = async (
label,
encoderType,
resourceExtraParam,
resourceCycle,
endOrFinish
) => {
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
let encoder;
let pass;
let resource;
if (encoderType == "render") {
// Create some resources, and setup the pass.
encoder = device.createCommandEncoder();
const texture = device.createTexture({
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
format: "rgba8unorm",
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const view = texture.createView();
pass = encoder.beginRenderPass({
colorAttachments: [
{
view,
loadOp: "load",
storeOp: "store",
},
],
});
resource = view;
} else if (encoderType == "compute") {
// Create some resources, and setup the pass.
encoder = device.createCommandEncoder();
const pipeline = device.createComputePipeline({
layout: "auto",
compute: {
module: device.createShaderModule({
code: `
struct Buffer { data: array<u32>, };
@group(0) @binding(0) var<storage, read_write> buffer: Buffer;
@compute @workgroup_size(1) fn main(
@builtin(global_invocation_id) id: vec3<u32>) {
buffer.data[id.x] = buffer.data[id.x] + 1u;
}
`,
}),
entryPoint: "main",
},
});
pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
resource = pipeline;
} else if (encoderType == "bundle") {
// Create some resources and setup the encoder.
const pipeline = device.createRenderPipeline({
layout: "auto",
vertex: {
module: device.createShaderModule({
code: `
@vertex fn vert_main() -> @builtin(position) vec4<f32> {
return vec4<f32>(0.5, 0.5, 0.0, 1.0);
}
`,
}),
entryPoint: "vert_main",
},
fragment: {
module: device.createShaderModule({
code: `
struct Data {
a : u32
};
@group(0) @binding(0) var<storage, read_write> data : Data;
@fragment fn frag_main() -> @location(0) vec4<f32> {
data.a = 0u;
return vec4<f32>();
}
`,
}),
entryPoint: "frag_main",
targets: [{ format: "rgba8unorm" }],
},
primitive: { topology: "point-list" },
});
encoder = device.createRenderBundleEncoder({
colorFormats: ["rgba8unorm"],
});
encoder.setPipeline(pipeline);
resource = pipeline;
}
if (resourceExtraParam) {
resource.extra = true;
}
if (resourceCycle) {
resource.encoder = encoder;
}
if (endOrFinish) {
if (encoderType == "render" || encoderType == "compute") {
pass.end();
} else if (encoderType == "bundle") {
encoder.finish();
}
}
// Get a weak ref to the encoder, which we'll check after GC to ensure
// that it got collected.
encoderWeakRef = SpecialPowers.Cu.getWeakReference(encoder);
ok(encoderWeakRef.get(), `${label} got encoder weak ref.`);
results.push({
label,
encoderWeakRef,
});
};
// The rest of the test will run in a promise chain. Define an async
// function to fill our results.
let call_test_func = async () => {
for (const encoderType of ["render", "compute", "bundle"]) {
for (const resourceExtraParam of [true, false]) {
for (const resourceCycle of [true, false]) {
for (const endOrFinish of [true, false]) {
const label = `[${encoderType}, ${resourceExtraParam}, ${resourceCycle}, ${endOrFinish}]`;
await test_func(
label,
encoderType,
resourceExtraParam,
resourceCycle,
endOrFinish
);
}
}
}
}
};
// Phase 1: Start the promise chain and call test_func repeated to fill
// our results struct.
call_test_func()
// Phase 2: Do our garbage collection.
.then(gc_promise)
.then(gc_promise)
.then(gc_promise)
// Phase 3: Iterate over results and check that all of the encoders
// were garbage collected.
.then(() => {
for (result of results) {
ok(
!result.encoderWeakRef.get(),
`${result.label} cycle collected encoder.`
);
}
})
.catch(e => {
ok(false, `unhandled exception ${e}`);
})
.finally(() => {
SimpleTest.finish();
});
</script>
</body>
</html>
|