@@ -21,6 +21,7 @@ import {
2121 kTextureUsageCopyInfo ,
2222 kShaderStageKeys ,
2323} from '../../../../capability_info.js' ;
24+ import { GPUConst } from '../../../../constants.js' ;
2425import {
2526 getBlockInfoForColorTextureFormat ,
2627 kCompressedTextureFormats ,
@@ -52,16 +53,15 @@ class DeviceDestroyTests extends AllFeaturesMaxLimitsValidationTest {
5253 * `fn` after the device is destroyed without any specific expectation. If `awaitLost` is true, we
5354 * also wait for device.lost to resolve before executing `fn` in the destroy case.
5455 */
55- async executeAfterDestroy ( fn : ( ) => void , awaitLost : boolean ) : Promise < void > {
56+ async executeAfterDestroy ( fn : ( ) => void | Promise < void > , awaitLost : boolean ) : Promise < void > {
5657 this . expectDeviceLost ( 'destroyed' ) ;
5758
58- this . expectValidationError ( fn , false ) ;
5959 this . device . destroy ( ) ;
6060 if ( awaitLost ) {
6161 const lostInfo = await this . device . lost ;
6262 this . expect ( lostInfo . reason === 'destroyed' ) ;
6363 }
64- fn ( ) ;
64+ await fn ( ) ;
6565 }
6666
6767 /**
@@ -146,6 +146,109 @@ Tests creating buffers on destroyed device. Tests valid combinations of:
146146 } , awaitLost ) ;
147147 } ) ;
148148
149+ g . test ( 'mapping,mappedAtCreation' )
150+ . desc (
151+ `
152+ Tests behavior of mappedAtCreation buffers when destroying the device (multiple times).
153+ - Various usages
154+ - Wait for .lost or not
155+ `
156+ )
157+ . params ( u =>
158+ u
159+ . combine ( 'usage' , [
160+ GPUConst . BufferUsage . MAP_READ ,
161+ GPUConst . BufferUsage . MAP_WRITE ,
162+ GPUConst . BufferUsage . COPY_SRC ,
163+ ] )
164+ . combine ( 'awaitLost' , [ true , false ] )
165+ )
166+ . fn ( async t => {
167+ const { awaitLost, usage } = t . params ;
168+
169+ const b1 = t . createBufferTracked ( { size : 16 , usage, mappedAtCreation : true } ) ;
170+ t . expect ( b1 . mapState === 'mapped' , 'b1 before destroy 1' ) ;
171+
172+ await t . executeAfterDestroy ( ( ) => {
173+ // Destroy should have unmapped everything.
174+ t . expect ( b1 . mapState === 'unmapped' , 'b1 after destroy 1' ) ;
175+ // But unmap just in case, to reset state before continuing.
176+ b1 . unmap ( ) ;
177+
178+ // mappedAtCreation should still work.
179+ const b2 = t . createBufferTracked ( { size : 16 , usage, mappedAtCreation : true } ) ;
180+ t . expect ( b2 . mapState === 'mapped' , 'b2 at creation after destroy 1' ) ;
181+
182+ // Destroying again should unmap.
183+ t . device . destroy ( ) ;
184+ t . expect ( b2 . mapState === 'unmapped' , 'b2 after destroy 2' ) ;
185+ } , awaitLost ) ;
186+ } ) ;
187+
188+ g . test ( 'mapping,mapAsync' )
189+ . desc (
190+ `
191+ Tests behavior of mapAsync'd buffers when destroying the device.
192+ - Various usages
193+ - Wait for .lost or not
194+
195+ TODO(https://github.com/gpuweb/gpuweb/issues/5101): Test which error we got at [1].
196+ `
197+ )
198+ . params ( u =>
199+ u
200+ . combine ( 'usage' , [
201+ GPUConst . BufferUsage . MAP_READ ,
202+ GPUConst . BufferUsage . MAP_WRITE ,
203+ GPUConst . BufferUsage . COPY_SRC ,
204+ ] )
205+ . combine ( 'awaitLost' , [ true , false ] )
206+ )
207+ . fn ( async t => {
208+ const { awaitLost, usage } = t . params ;
209+
210+ const bAsync = t . createBufferTracked ( { size : 16 , usage } ) ;
211+ const mode =
212+ usage === GPUBufferUsage . MAP_READ
213+ ? GPUMapMode . READ
214+ : usage === GPUBufferUsage . MAP_WRITE
215+ ? GPUMapMode . WRITE
216+ : 0 ;
217+ if ( mode ) {
218+ await bAsync . mapAsync ( mode ) ;
219+ t . expect ( bAsync . mapState === 'mapped' , 'bAsync before destroy 1' ) ;
220+ } else {
221+ t . expect ( bAsync . mapState === 'unmapped' , 'bAsync before destroy 1' ) ;
222+ }
223+
224+ await t . executeAfterDestroy ( async ( ) => {
225+ // Destroy should have unmapped everything.
226+ t . expect ( bAsync . mapState === 'unmapped' , 'bAsync after destroy 1' ) ;
227+ // But unmap just in case, to reset state before continuing.
228+ bAsync . unmap ( ) ;
229+
230+ // Check that mapping after destroy fails.
231+ // Also check that if mapAsync fails validation, it still produces an OperationError.
232+ try {
233+ await bAsync . mapAsync ( mode ) ;
234+ t . expect ( false , 'bAsync mapAsync after destroy 1 should reject' ) ;
235+ } catch ( ex ) {
236+ if ( mode ) {
237+ // The mapAsync call is valid except for the fact that the device is lost.
238+ t . expect ( ex instanceof DOMException && ex . name === 'AbortError' ) ;
239+ } else {
240+ // The mapAsync call is also invalid for other reasons.
241+ // [1] Test which error type we got. (And maybe switch to shouldReject().)
242+ t . expect ( ex instanceof DOMException ) ;
243+ }
244+ }
245+ const mapPromise = bAsync . mapAsync ( mode ) ;
246+ t . shouldReject ( 'AbortError' , mapPromise ) ;
247+ await mapPromise . catch ( ( ) => { } ) ;
248+ t . expect ( bAsync . mapState === 'unmapped' , 'bAsync after mapAsync after destroy 1' ) ;
249+ } , awaitLost ) ;
250+ } ) ;
251+
149252g . test ( 'createTexture,2d,uncompressed_format' )
150253 . desc (
151254 `
0 commit comments