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