1export const description = ` 2createBindGroup validation tests. 3`; 4 5import { poptions, params } from '../../../common/framework/params_builder.js'; 6import { makeTestGroup } from '../../../common/framework/test_group.js'; 7import { unreachable } from '../../../common/framework/util/util.js'; 8import { 9 kBindingTypes, 10 kBindingTypeInfo, 11 kBindableResources, 12 kTextureUsages, 13 kTextureBindingTypes, 14 kTextureBindingTypeInfo, 15} from '../../capability_info.js'; 16 17import { ValidationTest } from './validation_test.js'; 18 19function clone<T extends GPUTextureDescriptor>(descriptor: T): T { 20 return JSON.parse(JSON.stringify(descriptor)); 21} 22 23export const g = makeTestGroup(ValidationTest); 24 25g.test('binding_count_mismatch').fn(async t => { 26 const bindGroupLayout = t.device.createBindGroupLayout({ 27 entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }], 28 }); 29 30 const goodDescriptor = { 31 entries: [{ binding: 0, resource: { buffer: t.getStorageBuffer() } }], 32 layout: bindGroupLayout, 33 }; 34 35 // Control case 36 t.device.createBindGroup(goodDescriptor); 37 38 // Another binding is not expected. 39 const badDescriptor = { 40 entries: [ 41 { binding: 0, resource: { buffer: t.getStorageBuffer() } }, 42 // Another binding is added. 43 { binding: 1, resource: { buffer: t.getStorageBuffer() } }, 44 ], 45 layout: bindGroupLayout, 46 }; 47 48 t.expectValidationError(() => { 49 t.device.createBindGroup(badDescriptor); 50 }); 51}); 52 53g.test('binding_must_be_present_in_layout').fn(async t => { 54 const bindGroupLayout = t.device.createBindGroupLayout({ 55 entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }], 56 }); 57 58 const goodDescriptor = { 59 entries: [{ binding: 0, resource: { buffer: t.getStorageBuffer() } }], 60 layout: bindGroupLayout, 61 }; 62 63 // Control case 64 t.device.createBindGroup(goodDescriptor); 65 66 // Binding index 0 must be present. 67 const badDescriptor = { 68 entries: [{ binding: 1, resource: { buffer: t.getStorageBuffer() } }], 69 layout: bindGroupLayout, 70 }; 71 72 t.expectValidationError(() => { 73 t.device.createBindGroup(badDescriptor); 74 }); 75}); 76 77g.test('buffer_binding_must_contain_exactly_one_buffer_of_its_type') 78 .params( 79 params() 80 .combine(poptions('bindingType', kBindingTypes)) 81 .combine(poptions('resourceType', kBindableResources)) 82 ) 83 .fn(t => { 84 const { bindingType, resourceType } = t.params; 85 const info = kBindingTypeInfo[bindingType]; 86 87 const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined; 88 const layout = t.device.createBindGroupLayout({ 89 entries: [ 90 { binding: 0, visibility: GPUShaderStage.COMPUTE, type: bindingType, storageTextureFormat }, 91 ], 92 }); 93 94 const resource = t.getBindingResource(resourceType); 95 96 const resourceBindingMatches = info.resource === resourceType; 97 t.expectValidationError(() => { 98 t.device.createBindGroup({ layout, entries: [{ binding: 0, resource }] }); 99 }, !resourceBindingMatches); 100 }); 101 102g.test('texture_binding_must_have_correct_usage') 103 .params( 104 params() 105 .combine(poptions('type', kTextureBindingTypes)) 106 .combine(poptions('usage', kTextureUsages)) 107 ) 108 .fn(async t => { 109 const { type, usage } = t.params; 110 const info = kTextureBindingTypeInfo[type]; 111 112 const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined; 113 const bindGroupLayout = t.device.createBindGroupLayout({ 114 entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type, storageTextureFormat }], 115 }); 116 117 const descriptor = { 118 size: { width: 16, height: 16, depth: 1 }, 119 format: 'rgba8unorm' as const, 120 usage, 121 }; 122 123 const shouldError = usage !== info.usage; 124 t.expectValidationError(() => { 125 t.device.createBindGroup({ 126 entries: [{ binding: 0, resource: t.device.createTexture(descriptor).createView() }], 127 layout: bindGroupLayout, 128 }); 129 }, shouldError); 130 }); 131 132g.test('texture_must_have_correct_component_type') 133 .params(poptions('textureComponentType', ['float', 'sint', 'uint'] as const)) 134 .fn(async t => { 135 const { textureComponentType } = t.params; 136 137 const bindGroupLayout = t.device.createBindGroupLayout({ 138 entries: [ 139 { 140 binding: 0, 141 visibility: GPUShaderStage.FRAGMENT, 142 type: 'sampled-texture', 143 textureComponentType, 144 }, 145 ], 146 }); 147 148 // TODO: Test more texture component types. 149 let format: GPUTextureFormat; 150 if (textureComponentType === 'float') { 151 format = 'r8unorm'; 152 } else if (textureComponentType === 'sint') { 153 format = 'r8sint'; 154 } else if (textureComponentType === 'uint') { 155 format = 'r8uint'; 156 } else { 157 unreachable('Unexpected texture component type'); 158 } 159 160 const goodDescriptor = { 161 size: { width: 16, height: 16, depth: 1 }, 162 format, 163 usage: GPUTextureUsage.SAMPLED, 164 }; 165 166 // Control case 167 t.device.createBindGroup({ 168 entries: [ 169 { 170 binding: 0, 171 resource: t.device.createTexture(goodDescriptor).createView(), 172 }, 173 ], 174 layout: bindGroupLayout, 175 }); 176 177 function* mismatchedTextureFormats(): Iterable<GPUTextureFormat> { 178 if (textureComponentType !== 'float') { 179 yield 'r8unorm'; 180 } 181 if (textureComponentType !== 'sint') { 182 yield 'r8sint'; 183 } 184 if (textureComponentType !== 'uint') { 185 yield 'r8uint'; 186 } 187 } 188 189 // Mismatched texture binding formats are not valid. 190 for (const mismatchedTextureFormat of mismatchedTextureFormats()) { 191 const badDescriptor: GPUTextureDescriptor = clone(goodDescriptor); 192 badDescriptor.format = mismatchedTextureFormat; 193 194 t.expectValidationError(() => { 195 t.device.createBindGroup({ 196 entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }], 197 layout: bindGroupLayout, 198 }); 199 }); 200 } 201 }); 202 203// TODO: Write test for all dimensions. 204g.test('texture_must_have_correct_dimension').fn(async t => { 205 const bindGroupLayout = t.device.createBindGroupLayout({ 206 entries: [ 207 { 208 binding: 0, 209 visibility: GPUShaderStage.FRAGMENT, 210 type: 'sampled-texture', 211 viewDimension: '2d', 212 }, 213 ], 214 }); 215 216 const goodDescriptor = { 217 size: { width: 16, height: 16, depth: 1 }, 218 format: 'rgba8unorm' as const, 219 usage: GPUTextureUsage.SAMPLED, 220 }; 221 222 // Control case 223 t.device.createBindGroup({ 224 entries: [{ binding: 0, resource: t.device.createTexture(goodDescriptor).createView() }], 225 layout: bindGroupLayout, 226 }); 227 228 // Mismatched texture binding formats are not valid. 229 const badDescriptor = clone(goodDescriptor); 230 badDescriptor.size.depth = 2; 231 232 t.expectValidationError(() => { 233 t.device.createBindGroup({ 234 entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }], 235 layout: bindGroupLayout, 236 }); 237 }); 238}); 239 240g.test('buffer_offset_and_size_for_bind_groups_match') 241 .params([ 242 { offset: 0, size: 512, _success: true }, // offset 0 is valid 243 { offset: 256, size: 256, _success: true }, // offset 256 (aligned) is valid 244 245 // Touching the end of the buffer 246 { offset: 0, size: 1024, _success: true }, 247 { offset: 0, size: undefined, _success: true }, 248 { offset: 256 * 3, size: 256, _success: true }, 249 { offset: 256 * 3, size: undefined, _success: true }, 250 251 // Zero-sized bindings 252 { offset: 0, size: 0, _success: true }, 253 { offset: 256, size: 0, _success: true }, 254 { offset: 1024, size: 0, _success: true }, 255 { offset: 1024, size: undefined, _success: true }, 256 257 // Unaligned buffer offset is invalid 258 { offset: 1, size: 256, _success: false }, 259 { offset: 1, size: undefined, _success: false }, 260 { offset: 128, size: 256, _success: false }, 261 { offset: 255, size: 256, _success: false }, 262 263 // Out-of-bounds 264 { offset: 256 * 5, size: 0, _success: false }, // offset is OOB 265 { offset: 0, size: 256 * 5, _success: false }, // size is OOB 266 { offset: 1024, size: 1, _success: false }, // offset+size is OOB 267 ]) 268 .fn(async t => { 269 const { offset, size, _success } = t.params; 270 271 const bindGroupLayout = t.device.createBindGroupLayout({ 272 entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }], 273 }); 274 275 const buffer = t.device.createBuffer({ 276 size: 1024, 277 usage: GPUBufferUsage.STORAGE, 278 }); 279 280 const descriptor = { 281 entries: [ 282 { 283 binding: 0, 284 resource: { buffer, offset, size }, 285 }, 286 ], 287 layout: bindGroupLayout, 288 }; 289 290 if (_success) { 291 // Control case 292 t.device.createBindGroup(descriptor); 293 } else { 294 // Buffer offset and/or size don't match in bind groups. 295 t.expectValidationError(() => { 296 t.device.createBindGroup(descriptor); 297 }); 298 } 299 }); 300