1 // Copyright 2020 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 15 #include "tests/unittests/validation/ValidationTest.h" 16 17 #include "utils/ComboRenderPipelineDescriptor.h" 18 #include "utils/WGPUHelpers.h" 19 20 namespace { 21 22 class ResourceUsageTrackingTest : public ValidationTest { 23 protected: CreateBuffer(uint64_t size,wgpu::BufferUsage usage)24 wgpu::Buffer CreateBuffer(uint64_t size, wgpu::BufferUsage usage) { 25 wgpu::BufferDescriptor descriptor; 26 descriptor.size = size; 27 descriptor.usage = usage; 28 29 return device.CreateBuffer(&descriptor); 30 } 31 CreateTexture(wgpu::TextureUsage usage,wgpu::TextureFormat format=wgpu::TextureFormat::RGBA8Unorm)32 wgpu::Texture CreateTexture(wgpu::TextureUsage usage, 33 wgpu::TextureFormat format = wgpu::TextureFormat::RGBA8Unorm) { 34 wgpu::TextureDescriptor descriptor; 35 descriptor.dimension = wgpu::TextureDimension::e2D; 36 descriptor.size = {1, 1, 1}; 37 descriptor.sampleCount = 1; 38 descriptor.mipLevelCount = 1; 39 descriptor.usage = usage; 40 descriptor.format = format; 41 42 return device.CreateTexture(&descriptor); 43 } 44 45 // Note that it is valid to bind any bind groups for indices that the pipeline doesn't use. 46 // We create a no-op render or compute pipeline without any bindings, and set bind groups 47 // in the caller, so it is always correct for binding validation between bind groups and 48 // pipeline. But those bind groups in caller can be used for validation for other purposes. CreateNoOpRenderPipeline()49 wgpu::RenderPipeline CreateNoOpRenderPipeline() { 50 wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( 51 [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { 52 return vec4<f32>(); 53 })"); 54 55 wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( 56 [[stage(fragment)]] fn main() { 57 })"); 58 utils::ComboRenderPipelineDescriptor pipelineDescriptor; 59 pipelineDescriptor.vertex.module = vsModule; 60 pipelineDescriptor.cFragment.module = fsModule; 61 pipelineDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; 62 pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, nullptr); 63 return device.CreateRenderPipeline(&pipelineDescriptor); 64 } 65 CreateNoOpComputePipeline(std::vector<wgpu::BindGroupLayout> bgls)66 wgpu::ComputePipeline CreateNoOpComputePipeline(std::vector<wgpu::BindGroupLayout> bgls) { 67 wgpu::ShaderModule csModule = utils::CreateShaderModule(device, R"( 68 [[stage(compute), workgroup_size(1)]] fn main() { 69 })"); 70 wgpu::ComputePipelineDescriptor pipelineDescriptor; 71 pipelineDescriptor.layout = utils::MakePipelineLayout(device, std::move(bgls)); 72 pipelineDescriptor.compute.module = csModule; 73 pipelineDescriptor.compute.entryPoint = "main"; 74 return device.CreateComputePipeline(&pipelineDescriptor); 75 } 76 77 static constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm; 78 }; 79 80 // Test that using a single buffer in multiple read usages in the same pass is allowed. TEST_F(ResourceUsageTrackingTest,BufferWithMultipleReadUsage)81 TEST_F(ResourceUsageTrackingTest, BufferWithMultipleReadUsage) { 82 // Test render pass 83 { 84 // Create a buffer, and use the buffer as both vertex and index buffer. 85 wgpu::Buffer buffer = 86 CreateBuffer(4, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Index); 87 88 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 89 DummyRenderPass dummyRenderPass(device); 90 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 91 pass.SetIndexBuffer(buffer, wgpu::IndexFormat::Uint32); 92 pass.SetVertexBuffer(0, buffer); 93 pass.EndPass(); 94 encoder.Finish(); 95 } 96 97 // Test compute pass 98 { 99 // Create buffer and bind group 100 wgpu::Buffer buffer = 101 CreateBuffer(4, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::Storage); 102 103 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 104 device, 105 {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform}, 106 {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); 107 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}}); 108 109 // Use the buffer as both uniform and readonly storage buffer in compute pass. 110 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 111 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 112 pass.SetBindGroup(0, bg); 113 pass.EndPass(); 114 encoder.Finish(); 115 } 116 } 117 118 // Test that it is invalid to use the same buffer as both readable and writable in the same 119 // render pass. It is invalid in the same dispatch in compute pass. TEST_F(ResourceUsageTrackingTest,BufferWithReadAndWriteUsage)120 TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsage) { 121 // test render pass 122 { 123 // Create buffer and bind group 124 wgpu::Buffer buffer = 125 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); 126 127 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 128 device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); 129 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}}); 130 131 // It is invalid to use the buffer as both index and storage in render pass 132 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 133 DummyRenderPass dummyRenderPass(device); 134 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 135 pass.SetIndexBuffer(buffer, wgpu::IndexFormat::Uint32); 136 pass.SetBindGroup(0, bg); 137 pass.EndPass(); 138 ASSERT_DEVICE_ERROR(encoder.Finish()); 139 } 140 141 // test compute pass 142 { 143 // Create buffer and bind group 144 wgpu::Buffer buffer = CreateBuffer(512, wgpu::BufferUsage::Storage); 145 146 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 147 device, 148 {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, 149 {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); 150 wgpu::BindGroup bg = 151 utils::MakeBindGroup(device, bgl, {{0, buffer, 0, 4}, {1, buffer, 256, 4}}); 152 153 // Create a no-op compute pipeline 154 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); 155 156 // It is valid to use the buffer as both storage and readonly storage in a single 157 // compute pass if dispatch command is not called. 158 { 159 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 160 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 161 pass.SetBindGroup(0, bg); 162 pass.EndPass(); 163 encoder.Finish(); 164 } 165 166 // It is invalid to use the buffer as both storage and readonly storage in a single 167 // dispatch. 168 { 169 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 170 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 171 pass.SetPipeline(cp); 172 pass.SetBindGroup(0, bg); 173 pass.Dispatch(1); 174 pass.EndPass(); 175 ASSERT_DEVICE_ERROR(encoder.Finish()); 176 } 177 } 178 } 179 180 // Test the use of a buffer as a storage buffer multiple times in the same synchronization 181 // scope. TEST_F(ResourceUsageTrackingTest,BufferUsedAsStorageMultipleTimes)182 TEST_F(ResourceUsageTrackingTest, BufferUsedAsStorageMultipleTimes) { 183 // Create buffer and bind group 184 wgpu::Buffer buffer = CreateBuffer(512, wgpu::BufferUsage::Storage); 185 186 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 187 device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, 188 wgpu::BufferBindingType::Storage}, 189 {1, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, 190 wgpu::BufferBindingType::Storage}}); 191 wgpu::BindGroup bg = 192 utils::MakeBindGroup(device, bgl, {{0, buffer, 0, 4}, {1, buffer, 256, 4}}); 193 194 // test render pass 195 { 196 // It is valid to use multiple storage usages on the same buffer in render pass 197 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 198 DummyRenderPass dummyRenderPass(device); 199 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 200 pass.SetBindGroup(0, bg); 201 pass.EndPass(); 202 encoder.Finish(); 203 } 204 205 // test compute pass 206 { 207 // It is valid to use multiple storage usages on the same buffer in a dispatch 208 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); 209 210 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 211 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 212 pass.SetPipeline(cp); 213 pass.SetBindGroup(0, bg); 214 pass.Dispatch(1); 215 pass.EndPass(); 216 encoder.Finish(); 217 } 218 } 219 220 // Test that using the same buffer as both readable and writable in different passes is allowed TEST_F(ResourceUsageTrackingTest,BufferWithReadAndWriteUsageInDifferentPasses)221 TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsageInDifferentPasses) { 222 // Test render pass 223 { 224 // Create buffers that will be used as index and storage buffers 225 wgpu::Buffer buffer0 = 226 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); 227 wgpu::Buffer buffer1 = 228 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); 229 230 // Create bind groups to use the buffer as storage 231 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 232 device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); 233 wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, buffer0}}); 234 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, buffer1}}); 235 236 // Use these two buffers as both index and storage in different render passes 237 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 238 DummyRenderPass dummyRenderPass(device); 239 240 wgpu::RenderPassEncoder pass0 = encoder.BeginRenderPass(&dummyRenderPass); 241 pass0.SetIndexBuffer(buffer0, wgpu::IndexFormat::Uint32); 242 pass0.SetBindGroup(0, bg1); 243 pass0.EndPass(); 244 245 wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&dummyRenderPass); 246 pass1.SetIndexBuffer(buffer1, wgpu::IndexFormat::Uint32); 247 pass1.SetBindGroup(0, bg0); 248 pass1.EndPass(); 249 250 encoder.Finish(); 251 } 252 253 // Test compute pass 254 { 255 // Create buffer and bind groups that will be used as storage and uniform bindings 256 wgpu::Buffer buffer = 257 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Uniform); 258 259 wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( 260 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); 261 wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( 262 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform}}); 263 wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}}); 264 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}}); 265 266 // Use the buffer as both storage and uniform in different compute passes 267 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 268 269 wgpu::ComputePassEncoder pass0 = encoder.BeginComputePass(); 270 pass0.SetBindGroup(0, bg0); 271 pass0.EndPass(); 272 273 wgpu::ComputePassEncoder pass1 = encoder.BeginComputePass(); 274 pass1.SetBindGroup(1, bg1); 275 pass1.EndPass(); 276 277 encoder.Finish(); 278 } 279 280 // Test render pass and compute pass mixed together with resource dependency. 281 { 282 // Create buffer and bind groups that will be used as storage and uniform bindings 283 wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); 284 285 wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( 286 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); 287 wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( 288 device, 289 {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}}); 290 wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}}); 291 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}}); 292 293 // Use the buffer as storage and uniform in render pass and compute pass respectively 294 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 295 296 wgpu::ComputePassEncoder pass0 = encoder.BeginComputePass(); 297 pass0.SetBindGroup(0, bg0); 298 pass0.EndPass(); 299 300 DummyRenderPass dummyRenderPass(device); 301 wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&dummyRenderPass); 302 pass1.SetBindGroup(1, bg1); 303 pass1.EndPass(); 304 305 encoder.Finish(); 306 } 307 } 308 309 // Test that it is invalid to use the same buffer as both readable and writable in different 310 // draws in a single render pass. But it is valid in different dispatches in a single compute 311 // pass. TEST_F(ResourceUsageTrackingTest,BufferWithReadAndWriteUsageInDifferentDrawsOrDispatches)312 TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsageInDifferentDrawsOrDispatches) { 313 // Test render pass 314 { 315 // Create a buffer and a bind group 316 wgpu::Buffer buffer = 317 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); 318 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 319 device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); 320 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}}); 321 322 // Create a no-op render pipeline. 323 wgpu::RenderPipeline rp = CreateNoOpRenderPipeline(); 324 325 // It is not allowed to use the same buffer as both readable and writable in different 326 // draws within the same render pass. 327 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 328 DummyRenderPass dummyRenderPass(device); 329 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 330 pass.SetPipeline(rp); 331 332 pass.SetIndexBuffer(buffer, wgpu::IndexFormat::Uint32); 333 pass.Draw(3); 334 335 pass.SetBindGroup(0, bg); 336 pass.Draw(3); 337 338 pass.EndPass(); 339 ASSERT_DEVICE_ERROR(encoder.Finish()); 340 } 341 342 // test compute pass 343 { 344 // Create a buffer and bind groups 345 wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); 346 347 wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( 348 device, 349 {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); 350 wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( 351 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); 352 wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}}); 353 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}}); 354 355 // Create a no-op compute pipeline. 356 wgpu::ComputePipeline cp0 = CreateNoOpComputePipeline({bgl0}); 357 wgpu::ComputePipeline cp1 = CreateNoOpComputePipeline({bgl1}); 358 359 // It is valid to use the same buffer as both readable and writable in different 360 // dispatches within the same compute pass. 361 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 362 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 363 364 pass.SetPipeline(cp0); 365 pass.SetBindGroup(0, bg0); 366 pass.Dispatch(1); 367 368 pass.SetPipeline(cp1); 369 pass.SetBindGroup(0, bg1); 370 pass.Dispatch(1); 371 372 pass.EndPass(); 373 encoder.Finish(); 374 } 375 } 376 377 // Test that it is invalid to use the same buffer as both readable and writable in a single 378 // draw or dispatch. TEST_F(ResourceUsageTrackingTest,BufferWithReadAndWriteUsageInSingleDrawOrDispatch)379 TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsageInSingleDrawOrDispatch) { 380 // Test render pass 381 { 382 // Create a buffer and a bind group 383 wgpu::Buffer buffer = 384 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); 385 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 386 device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); 387 wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, buffer}}); 388 389 // Create a no-op render pipeline. 390 wgpu::RenderPipeline rp = CreateNoOpRenderPipeline(); 391 392 // It is invalid to use the same buffer as both readable and writable usages in a single 393 // draw 394 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 395 DummyRenderPass dummyRenderPass(device); 396 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 397 pass.SetPipeline(rp); 398 399 pass.SetIndexBuffer(buffer, wgpu::IndexFormat::Uint32); 400 pass.SetBindGroup(0, writeBG); 401 pass.Draw(3); 402 403 pass.EndPass(); 404 ASSERT_DEVICE_ERROR(encoder.Finish()); 405 } 406 407 // test compute pass 408 { 409 // Create a buffer and bind groups 410 wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); 411 412 wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( 413 device, 414 {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); 415 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 416 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); 417 wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, buffer}}); 418 wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, buffer}}); 419 420 // Create a no-op compute pipeline. 421 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({readBGL, writeBGL}); 422 423 // It is invalid to use the same buffer as both readable and writable usages in a single 424 // dispatch 425 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 426 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 427 pass.SetPipeline(cp); 428 429 pass.SetBindGroup(0, readBG); 430 pass.SetBindGroup(1, writeBG); 431 pass.Dispatch(1); 432 433 pass.EndPass(); 434 ASSERT_DEVICE_ERROR(encoder.Finish()); 435 } 436 } 437 438 // Test that using the same buffer as copy src/dst and writable/readable usage is allowed. TEST_F(ResourceUsageTrackingTest,BufferCopyAndBufferUsageInPass)439 TEST_F(ResourceUsageTrackingTest, BufferCopyAndBufferUsageInPass) { 440 // Create buffers that will be used as both a copy src/dst buffer and a storage buffer 441 wgpu::Buffer bufferSrc = 442 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); 443 wgpu::Buffer bufferDst = 444 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst); 445 446 // Create the bind group to use the buffer as storage 447 wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( 448 device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); 449 wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, bufferSrc}}); 450 wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( 451 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); 452 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, bufferDst}}); 453 454 // Use the buffer as both copy src and storage in render pass 455 { 456 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 457 encoder.CopyBufferToBuffer(bufferSrc, 0, bufferDst, 0, 4); 458 DummyRenderPass dummyRenderPass(device); 459 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 460 pass.SetBindGroup(0, bg0); 461 pass.EndPass(); 462 encoder.Finish(); 463 } 464 465 // Use the buffer as both copy dst and readonly storage in compute pass 466 { 467 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl1}); 468 469 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 470 encoder.CopyBufferToBuffer(bufferSrc, 0, bufferDst, 0, 4); 471 472 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 473 pass.SetBindGroup(0, bg1); 474 pass.SetPipeline(cp); 475 pass.Dispatch(1); 476 pass.EndPass(); 477 478 encoder.Finish(); 479 } 480 } 481 482 // Test that all index buffers and vertex buffers take effect even though some buffers are 483 // not used because they are overwritten by another consecutive call. TEST_F(ResourceUsageTrackingTest,BufferWithMultipleSetIndexOrVertexBuffer)484 TEST_F(ResourceUsageTrackingTest, BufferWithMultipleSetIndexOrVertexBuffer) { 485 // Create buffers that will be used as both vertex and index buffer. 486 wgpu::Buffer buffer0 = CreateBuffer( 487 4, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Index | wgpu::BufferUsage::Storage); 488 wgpu::Buffer buffer1 = 489 CreateBuffer(4, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Index); 490 491 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 492 device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); 493 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer0}}); 494 495 DummyRenderPass dummyRenderPass(device); 496 497 // Set index buffer twice. The second one overwrites the first one. No buffer is used as 498 // both read and write in the same pass. But the overwritten index buffer (buffer0) still 499 // take effect during resource tracking. 500 { 501 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 502 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 503 pass.SetIndexBuffer(buffer0, wgpu::IndexFormat::Uint32); 504 pass.SetIndexBuffer(buffer1, wgpu::IndexFormat::Uint32); 505 pass.SetBindGroup(0, bg); 506 pass.EndPass(); 507 ASSERT_DEVICE_ERROR(encoder.Finish()); 508 } 509 510 // Set index buffer twice. The second one overwrites the first one. buffer0 is used as both 511 // read and write in the same pass 512 { 513 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 514 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 515 pass.SetIndexBuffer(buffer1, wgpu::IndexFormat::Uint32); 516 pass.SetIndexBuffer(buffer0, wgpu::IndexFormat::Uint32); 517 pass.SetBindGroup(0, bg); 518 pass.EndPass(); 519 ASSERT_DEVICE_ERROR(encoder.Finish()); 520 } 521 522 // Set vertex buffer on the same index twice. The second one overwrites the first one. No 523 // buffer is used as both read and write in the same pass. But the overwritten vertex buffer 524 // (buffer0) still take effect during resource tracking. 525 { 526 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 527 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 528 pass.SetVertexBuffer(0, buffer0); 529 pass.SetVertexBuffer(0, buffer1); 530 pass.SetBindGroup(0, bg); 531 pass.EndPass(); 532 ASSERT_DEVICE_ERROR(encoder.Finish()); 533 } 534 535 // Set vertex buffer on the same index twice. The second one overwrites the first one. 536 // buffer0 is used as both read and write in the same pass 537 { 538 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 539 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 540 pass.SetVertexBuffer(0, buffer1); 541 pass.SetVertexBuffer(0, buffer0); 542 pass.SetBindGroup(0, bg); 543 pass.EndPass(); 544 ASSERT_DEVICE_ERROR(encoder.Finish()); 545 } 546 } 547 548 // Test that all consecutive SetBindGroup()s take effect even though some bind groups are not 549 // used because they are overwritten by a consecutive call. TEST_F(ResourceUsageTrackingTest,BufferWithMultipleSetBindGroupsOnSameIndex)550 TEST_F(ResourceUsageTrackingTest, BufferWithMultipleSetBindGroupsOnSameIndex) { 551 // test render pass 552 { 553 // Create buffers that will be used as index and storage buffers 554 wgpu::Buffer buffer0 = 555 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); 556 wgpu::Buffer buffer1 = 557 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); 558 559 // Create the bind group to use the buffer as storage 560 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 561 device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); 562 wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, buffer0}}); 563 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, buffer1}}); 564 565 DummyRenderPass dummyRenderPass(device); 566 567 // Set bind group on the same index twice. The second one overwrites the first one. 568 // No buffer is used as both read and write in the same pass. But the overwritten 569 // bind group still take effect during resource tracking. 570 { 571 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 572 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 573 pass.SetIndexBuffer(buffer0, wgpu::IndexFormat::Uint32); 574 pass.SetBindGroup(0, bg0); 575 pass.SetBindGroup(0, bg1); 576 pass.EndPass(); 577 ASSERT_DEVICE_ERROR(encoder.Finish()); 578 } 579 580 // Set bind group on the same index twice. The second one overwrites the first one. 581 // buffer0 is used as both read and write in the same pass 582 { 583 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 584 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 585 pass.SetIndexBuffer(buffer0, wgpu::IndexFormat::Uint32); 586 pass.SetBindGroup(0, bg1); 587 pass.SetBindGroup(0, bg0); 588 pass.EndPass(); 589 ASSERT_DEVICE_ERROR(encoder.Finish()); 590 } 591 } 592 593 // test compute pass 594 { 595 // Create buffers that will be used as readonly and writable storage buffers 596 wgpu::Buffer buffer0 = CreateBuffer(512, wgpu::BufferUsage::Storage); 597 wgpu::Buffer buffer1 = CreateBuffer(4, wgpu::BufferUsage::Storage); 598 599 // Create the bind group to use the buffer as storage 600 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 601 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); 602 wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( 603 device, 604 {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); 605 wgpu::BindGroup writeBG0 = utils::MakeBindGroup(device, writeBGL, {{0, buffer0, 0, 4}}); 606 wgpu::BindGroup readBG0 = utils::MakeBindGroup(device, readBGL, {{0, buffer0, 256, 4}}); 607 wgpu::BindGroup readBG1 = utils::MakeBindGroup(device, readBGL, {{0, buffer1, 0, 4}}); 608 609 // Create a no-op compute pipeline. 610 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({writeBGL, readBGL}); 611 612 // Set bind group against the same index twice. The second one overwrites the first one. 613 // Then no buffer is used as both read and write in the same dispatch. But the 614 // overwritten bind group still take effect. 615 { 616 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 617 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 618 pass.SetBindGroup(0, writeBG0); 619 pass.SetBindGroup(1, readBG0); 620 pass.SetBindGroup(1, readBG1); 621 pass.SetPipeline(cp); 622 pass.Dispatch(1); 623 pass.EndPass(); 624 encoder.Finish(); 625 } 626 627 // Set bind group against the same index twice. The second one overwrites the first one. 628 // Then buffer0 is used as both read and write in the same dispatch 629 { 630 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 631 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 632 pass.SetBindGroup(0, writeBG0); 633 pass.SetBindGroup(1, readBG1); 634 pass.SetBindGroup(1, readBG0); 635 pass.SetPipeline(cp); 636 pass.Dispatch(1); 637 pass.EndPass(); 638 ASSERT_DEVICE_ERROR(encoder.Finish()); 639 } 640 } 641 } 642 643 // Test that it is invalid to have resource usage conflicts even when all bindings are not 644 // visible to the programmable pass where it is used. TEST_F(ResourceUsageTrackingTest,BufferUsageConflictBetweenInvisibleStagesInBindGroup)645 TEST_F(ResourceUsageTrackingTest, BufferUsageConflictBetweenInvisibleStagesInBindGroup) { 646 wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); 647 648 // Test render pass for bind group. The conflict of readonly storage and storage usage 649 // doesn't reside in render related stages at all 650 { 651 // Create a bind group whose bindings are not visible in render pass 652 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 653 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, 654 {1, wgpu::ShaderStage::None, wgpu::BufferBindingType::ReadOnlyStorage}}); 655 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}}); 656 657 // These two bindings are invisible in render pass. But we still track these bindings. 658 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 659 DummyRenderPass dummyRenderPass(device); 660 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 661 pass.SetBindGroup(0, bg); 662 pass.EndPass(); 663 ASSERT_DEVICE_ERROR(encoder.Finish()); 664 } 665 666 // Test compute pass for bind group. The conflict of readonly storage and storage usage 667 // doesn't reside in compute related stage at all 668 { 669 // Create a bind group whose bindings are not visible in compute pass 670 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 671 device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}, 672 {1, wgpu::ShaderStage::None, wgpu::BufferBindingType::Storage}}); 673 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}}); 674 675 // Create a no-op compute pipeline. 676 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); 677 678 // These two bindings are invisible in the dispatch. But we still track these bindings. 679 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 680 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 681 pass.SetPipeline(cp); 682 pass.SetBindGroup(0, bg); 683 pass.Dispatch(1); 684 pass.EndPass(); 685 ASSERT_DEVICE_ERROR(encoder.Finish()); 686 } 687 } 688 689 // Test that it is invalid to have resource usage conflicts even when one of the bindings is not 690 // visible to the programmable pass where it is used. TEST_F(ResourceUsageTrackingTest,BufferUsageConflictWithInvisibleStageInBindGroup)691 TEST_F(ResourceUsageTrackingTest, BufferUsageConflictWithInvisibleStageInBindGroup) { 692 // Test render pass for bind group and index buffer. The conflict of storage and index 693 // buffer usage resides between fragment stage and compute stage. But the compute stage 694 // binding is not visible in render pass. 695 { 696 wgpu::Buffer buffer = 697 CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index); 698 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 699 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); 700 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}}); 701 702 // Buffer usage in compute stage in bind group conflicts with index buffer. And binding 703 // for compute stage is not visible in render pass. But we still track this binding. 704 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 705 DummyRenderPass dummyRenderPass(device); 706 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 707 pass.SetIndexBuffer(buffer, wgpu::IndexFormat::Uint32); 708 pass.SetBindGroup(0, bg); 709 pass.EndPass(); 710 ASSERT_DEVICE_ERROR(encoder.Finish()); 711 } 712 713 // Test compute pass for bind group. The conflict of readonly storage and storage buffer 714 // usage resides between compute stage and fragment stage. But the fragment stage binding is 715 // not visible in the dispatch. 716 { 717 wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); 718 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 719 device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}, 720 {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); 721 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}}); 722 723 // Create a no-op compute pipeline. 724 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); 725 726 // Buffer usage in compute stage conflicts with buffer usage in fragment stage. And 727 // binding for fragment stage is not visible in the dispatch. But we still track this 728 // invisible binding. 729 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 730 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 731 pass.SetPipeline(cp); 732 pass.SetBindGroup(0, bg); 733 pass.Dispatch(1); 734 pass.EndPass(); 735 ASSERT_DEVICE_ERROR(encoder.Finish()); 736 } 737 } 738 739 // Test that it is invalid to have resource usage conflicts even when one of the bindings is not 740 // used in the pipeline. TEST_F(ResourceUsageTrackingTest,BufferUsageConflictWithUnusedPipelineBindings)741 TEST_F(ResourceUsageTrackingTest, BufferUsageConflictWithUnusedPipelineBindings) { 742 wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage); 743 744 // Test render pass for bind groups with unused bindings. The conflict of readonly storage 745 // and storage usages resides in different bind groups, although some bindings may not be 746 // used because its bind group layout is not designated in pipeline layout. 747 { 748 // Create bind groups. The bindings are visible for render pass. 749 wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( 750 device, 751 {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}}); 752 wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( 753 device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); 754 wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}}); 755 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}}); 756 757 // Create a passthrough render pipeline with a readonly buffer 758 wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( 759 [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { 760 return vec4<f32>(); 761 })"); 762 763 wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( 764 [[block]] struct RBuffer { 765 value : f32; 766 }; 767 [[group(0), binding(0)]] var<storage, read> rBuffer : RBuffer; 768 [[stage(fragment)]] fn main() { 769 })"); 770 utils::ComboRenderPipelineDescriptor pipelineDescriptor; 771 pipelineDescriptor.vertex.module = vsModule; 772 pipelineDescriptor.cFragment.module = fsModule; 773 pipelineDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; 774 pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl0); 775 wgpu::RenderPipeline rp = device.CreateRenderPipeline(&pipelineDescriptor); 776 777 // Resource in bg1 conflicts with resources used in bg0. However, bindings in bg1 is 778 // not used in pipeline. But we still track this binding. 779 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 780 DummyRenderPass dummyRenderPass(device); 781 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 782 pass.SetBindGroup(0, bg0); 783 pass.SetBindGroup(1, bg1); 784 pass.SetPipeline(rp); 785 pass.Draw(3); 786 pass.EndPass(); 787 ASSERT_DEVICE_ERROR(encoder.Finish()); 788 } 789 790 // Test that an unused bind group is not used to detect conflicts between bindings in 791 // compute passes. 792 { 793 // Create bind groups. The bindings are visible for compute pass. 794 wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout( 795 device, 796 {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); 797 wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout( 798 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); 799 wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}}); 800 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}}); 801 802 // Create a compute pipeline with only one of the two BGLs. 803 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl0}); 804 805 // Resource in bg1 conflicts with resources used in bg0. However, the binding in bg1 is 806 // not used in pipeline so no error is produced in the dispatch. 807 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 808 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 809 pass.SetBindGroup(0, bg0); 810 pass.SetBindGroup(1, bg1); 811 pass.SetPipeline(cp); 812 pass.Dispatch(1); 813 pass.EndPass(); 814 encoder.Finish(); 815 } 816 } 817 818 // Test that it is invalid to use the same texture as both readable and writable in the same 819 // render pass. It is invalid in the same dispatch in compute pass. TEST_F(ResourceUsageTrackingTest,TextureWithReadAndWriteUsage)820 TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsage) { 821 // Test render pass 822 { 823 // Create a texture 824 wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::TextureBinding | 825 wgpu::TextureUsage::RenderAttachment); 826 wgpu::TextureView view = texture.CreateView(); 827 828 // Create a bind group to use the texture as sampled binding 829 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 830 device, {{0, wgpu::ShaderStage::Vertex, wgpu::TextureSampleType::Float}}); 831 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}}); 832 833 // Create a render pass to use the texture as a render target 834 utils::ComboRenderPassDescriptor renderPass({view}); 835 836 // It is invalid to use the texture as both sampled and render target in the same pass 837 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 838 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); 839 pass.SetBindGroup(0, bg); 840 pass.EndPass(); 841 ASSERT_DEVICE_ERROR(encoder.Finish()); 842 } 843 844 // Test compute pass 845 { 846 // Create a texture 847 wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::TextureBinding | 848 wgpu::TextureUsage::StorageBinding); 849 wgpu::TextureView view = texture.CreateView(); 850 851 // Create a bind group to use the texture as sampled and writeonly bindings 852 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 853 device, 854 {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}, 855 {1, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 856 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); 857 858 // Create a no-op compute pipeline 859 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); 860 861 // It is valid to use the texture as both sampled and writeonly storage in a single 862 // compute pass if dispatch command is not called. 863 { 864 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 865 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 866 pass.SetBindGroup(0, bg); 867 pass.EndPass(); 868 encoder.Finish(); 869 } 870 871 // It is invalid to use the texture as both sampled and writeonly storage in a single 872 // dispatch 873 { 874 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 875 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 876 pass.SetPipeline(cp); 877 pass.SetBindGroup(0, bg); 878 pass.Dispatch(1); 879 pass.EndPass(); 880 ASSERT_DEVICE_ERROR(encoder.Finish()); 881 } 882 } 883 } 884 885 // Test that it is invalid to use the same texture as both readable and writable depth/stencil 886 // attachment in the same render pass. But it is valid to use it as both readable and readonly 887 // depth/stencil attachment in the same render pass. 888 // Note that depth/stencil attachment is a special render attachment, it can be readonly. TEST_F(ResourceUsageTrackingTest,TextureWithSamplingAndDepthStencilAttachment)889 TEST_F(ResourceUsageTrackingTest, TextureWithSamplingAndDepthStencilAttachment) { 890 // Create a texture 891 wgpu::Texture texture = 892 CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment, 893 wgpu::TextureFormat::Depth32Float); 894 wgpu::TextureView view = texture.CreateView(); 895 896 // Create a bind group to use the texture as sampled binding 897 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 898 device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Depth}}); 899 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}}); 900 901 // Create a render pass to use the texture as a render target 902 utils::ComboRenderPassDescriptor passDescriptor({}, view); 903 passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load; 904 passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store; 905 906 // It is invalid to use the texture as both sampled and writeable depth/stencil attachment 907 // in the same pass 908 { 909 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 910 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); 911 pass.SetBindGroup(0, bg); 912 pass.EndPass(); 913 ASSERT_DEVICE_ERROR(encoder.Finish()); 914 } 915 916 // It is valid to use the texture as both sampled and readonly depth/stencil attachment in 917 // the same pass 918 { 919 passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true; 920 921 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 922 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); 923 pass.SetBindGroup(0, bg); 924 pass.EndPass(); 925 encoder.Finish(); 926 } 927 } 928 929 // Test using multiple writable usages on the same texture in a single pass/dispatch TEST_F(ResourceUsageTrackingTest,TextureWithMultipleWriteUsage)930 TEST_F(ResourceUsageTrackingTest, TextureWithMultipleWriteUsage) { 931 // Test render pass 932 { 933 // Create a texture 934 wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::StorageBinding | 935 wgpu::TextureUsage::RenderAttachment); 936 wgpu::TextureView view = texture.CreateView(); 937 938 // Create a bind group to use the texture as writeonly storage binding 939 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 940 device, 941 {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 942 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}}); 943 944 // It is invalid to use the texture as both writeonly storage and render target in 945 // the same pass 946 { 947 utils::ComboRenderPassDescriptor renderPass({view}); 948 949 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 950 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); 951 pass.SetBindGroup(0, bg); 952 pass.EndPass(); 953 ASSERT_DEVICE_ERROR(encoder.Finish()); 954 } 955 956 // It is valid to use multiple writeonly storage usages on the same texture in render 957 // pass 958 { 959 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, view}}); 960 961 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 962 DummyRenderPass dummyRenderPass(device); 963 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 964 pass.SetBindGroup(0, bg); 965 pass.SetBindGroup(1, bg1); 966 pass.EndPass(); 967 encoder.Finish(); 968 } 969 } 970 971 // Test compute pass 972 { 973 // Create a texture 974 wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::StorageBinding); 975 wgpu::TextureView view = texture.CreateView(); 976 977 // Create a bind group to use the texture as sampled and writeonly bindings 978 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 979 device, 980 {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}, 981 {1, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 982 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); 983 984 // Create a no-op compute pipeline 985 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); 986 987 // It is valid to use the texture as multiple writeonly storage usages in a single 988 // dispatch 989 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 990 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 991 pass.SetPipeline(cp); 992 pass.SetBindGroup(0, bg); 993 pass.Dispatch(1); 994 pass.EndPass(); 995 encoder.Finish(); 996 } 997 } 998 999 // Test that a single subresource of a texture cannot be used as a render attachment more than 1000 // once in the same pass. TEST_F(ResourceUsageTrackingTest,TextureWithMultipleRenderAttachmentUsage)1001 TEST_F(ResourceUsageTrackingTest, TextureWithMultipleRenderAttachmentUsage) { 1002 // Create a texture with two array layers 1003 wgpu::TextureDescriptor descriptor; 1004 descriptor.dimension = wgpu::TextureDimension::e2D; 1005 descriptor.size = {1, 1, 2}; 1006 descriptor.usage = wgpu::TextureUsage::RenderAttachment; 1007 descriptor.format = kFormat; 1008 1009 wgpu::Texture texture = device.CreateTexture(&descriptor); 1010 1011 wgpu::TextureViewDescriptor viewDesc = {}; 1012 viewDesc.arrayLayerCount = 1; 1013 1014 wgpu::TextureView viewLayer0 = texture.CreateView(&viewDesc); 1015 1016 viewDesc.baseArrayLayer = 1; 1017 wgpu::TextureView viewLayer1 = texture.CreateView(&viewDesc); 1018 1019 // Control: It is valid to use layer0 as a render target for one attachment, and 1020 // layer1 as the second attachment in the same pass 1021 { 1022 utils::ComboRenderPassDescriptor renderPass({viewLayer0, viewLayer1}); 1023 1024 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1025 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); 1026 pass.EndPass(); 1027 encoder.Finish(); 1028 } 1029 1030 // Control: It is valid to use layer0 as a render target in separate passes. 1031 { 1032 utils::ComboRenderPassDescriptor renderPass({viewLayer0}); 1033 1034 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1035 wgpu::RenderPassEncoder pass0 = encoder.BeginRenderPass(&renderPass); 1036 pass0.EndPass(); 1037 wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&renderPass); 1038 pass1.EndPass(); 1039 encoder.Finish(); 1040 } 1041 1042 // It is invalid to use layer0 as a render target for both attachments in the same pass 1043 { 1044 utils::ComboRenderPassDescriptor renderPass({viewLayer0, viewLayer0}); 1045 1046 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1047 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); 1048 pass.EndPass(); 1049 ASSERT_DEVICE_ERROR(encoder.Finish()); 1050 } 1051 1052 // It is invalid to use layer1 as a render target for both attachments in the same pass 1053 { 1054 utils::ComboRenderPassDescriptor renderPass({viewLayer1, viewLayer1}); 1055 1056 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1057 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); 1058 pass.EndPass(); 1059 ASSERT_DEVICE_ERROR(encoder.Finish()); 1060 } 1061 } 1062 1063 // Test that using the same texture as both readable and writable in different passes is 1064 // allowed TEST_F(ResourceUsageTrackingTest,TextureWithReadAndWriteUsageInDifferentPasses)1065 TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsageInDifferentPasses) { 1066 // Test render pass 1067 { 1068 // Create textures that will be used as both a sampled texture and a render target 1069 wgpu::Texture t0 = CreateTexture(wgpu::TextureUsage::TextureBinding | 1070 wgpu::TextureUsage::RenderAttachment); 1071 wgpu::TextureView v0 = t0.CreateView(); 1072 wgpu::Texture t1 = CreateTexture(wgpu::TextureUsage::TextureBinding | 1073 wgpu::TextureUsage::RenderAttachment); 1074 wgpu::TextureView v1 = t1.CreateView(); 1075 1076 // Create bind groups to use the texture as sampled 1077 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 1078 device, {{0, wgpu::ShaderStage::Vertex, wgpu::TextureSampleType::Float}}); 1079 wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, v0}}); 1080 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, v1}}); 1081 1082 // Create render passes that will use the textures as render attachments 1083 utils::ComboRenderPassDescriptor renderPass0({v1}); 1084 utils::ComboRenderPassDescriptor renderPass1({v0}); 1085 1086 // Use the textures as both sampled and render attachments in different passes 1087 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1088 1089 wgpu::RenderPassEncoder pass0 = encoder.BeginRenderPass(&renderPass0); 1090 pass0.SetBindGroup(0, bg0); 1091 pass0.EndPass(); 1092 1093 wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&renderPass1); 1094 pass1.SetBindGroup(0, bg1); 1095 pass1.EndPass(); 1096 1097 encoder.Finish(); 1098 } 1099 1100 // Test compute pass 1101 { 1102 // Create a texture that will be used storage texture 1103 wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::TextureBinding | 1104 wgpu::TextureUsage::StorageBinding); 1105 wgpu::TextureView view = texture.CreateView(); 1106 1107 // Create bind groups to use the texture as sampled and writeonly bindings 1108 wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( 1109 device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); 1110 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 1111 device, 1112 {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1113 wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); 1114 wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); 1115 1116 // Use the textures as both sampled and writeonly storages in different passes 1117 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1118 1119 wgpu::ComputePassEncoder pass0 = encoder.BeginComputePass(); 1120 pass0.SetBindGroup(0, readBG); 1121 pass0.EndPass(); 1122 1123 wgpu::ComputePassEncoder pass1 = encoder.BeginComputePass(); 1124 pass1.SetBindGroup(0, writeBG); 1125 pass1.EndPass(); 1126 1127 encoder.Finish(); 1128 } 1129 1130 // Test compute pass and render pass mixed together with resource dependency 1131 { 1132 // Create a texture that will be used a storage texture 1133 wgpu::Texture texture = CreateTexture(wgpu::TextureUsage::TextureBinding | 1134 wgpu::TextureUsage::StorageBinding); 1135 wgpu::TextureView view = texture.CreateView(); 1136 1137 // Create bind groups to use the texture as sampled and writeonly bindings 1138 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 1139 device, 1140 {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1141 wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( 1142 device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); 1143 wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); 1144 wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); 1145 1146 // Use the texture as writeonly and sampled storage in compute pass and render 1147 // pass respectively 1148 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1149 1150 wgpu::ComputePassEncoder pass0 = encoder.BeginComputePass(); 1151 pass0.SetBindGroup(0, writeBG); 1152 pass0.EndPass(); 1153 1154 DummyRenderPass dummyRenderPass(device); 1155 wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&dummyRenderPass); 1156 pass1.SetBindGroup(0, readBG); 1157 pass1.EndPass(); 1158 1159 encoder.Finish(); 1160 } 1161 } 1162 1163 // Test that it is invalid to use the same texture as both readable and writable in different 1164 // draws in a single render pass. But it is valid in different dispatches in a single compute 1165 // pass. TEST_F(ResourceUsageTrackingTest,TextureWithReadAndWriteUsageOnDifferentDrawsOrDispatches)1166 TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsageOnDifferentDrawsOrDispatches) { 1167 // Create a texture that will be used both as a sampled texture and a storage texture 1168 wgpu::Texture texture = 1169 CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding); 1170 wgpu::TextureView view = texture.CreateView(); 1171 1172 // Test render pass 1173 { 1174 // Create bind groups to use the texture as sampled and writeonly storage bindings 1175 wgpu::BindGroupLayout sampledBGL = utils::MakeBindGroupLayout( 1176 device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); 1177 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 1178 device, 1179 {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1180 wgpu::BindGroup sampledBG = utils::MakeBindGroup(device, sampledBGL, {{0, view}}); 1181 wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); 1182 1183 // Create a no-op render pipeline. 1184 wgpu::RenderPipeline rp = CreateNoOpRenderPipeline(); 1185 1186 // It is not allowed to use the same texture as both readable and writable in different 1187 // draws within the same render pass. 1188 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1189 DummyRenderPass dummyRenderPass(device); 1190 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 1191 pass.SetPipeline(rp); 1192 1193 pass.SetBindGroup(0, sampledBG); 1194 pass.Draw(3); 1195 1196 pass.SetBindGroup(0, writeBG); 1197 pass.Draw(3); 1198 1199 pass.EndPass(); 1200 ASSERT_DEVICE_ERROR(encoder.Finish()); 1201 } 1202 1203 // Test compute pass 1204 { 1205 // Create bind groups to use the texture as sampled and writeonly storage bindings 1206 wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( 1207 device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); 1208 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 1209 device, 1210 {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1211 wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); 1212 wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); 1213 1214 // Create a no-op compute pipeline. 1215 wgpu::ComputePipeline readCp = CreateNoOpComputePipeline({readBGL}); 1216 wgpu::ComputePipeline writeCp = CreateNoOpComputePipeline({writeBGL}); 1217 1218 // It is valid to use the same texture as both readable and writable in different 1219 // dispatches within the same compute pass. 1220 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1221 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 1222 1223 pass.SetPipeline(readCp); 1224 pass.SetBindGroup(0, readBG); 1225 pass.Dispatch(1); 1226 1227 pass.SetPipeline(writeCp); 1228 pass.SetBindGroup(0, writeBG); 1229 pass.Dispatch(1); 1230 1231 pass.EndPass(); 1232 encoder.Finish(); 1233 } 1234 } 1235 1236 // Test that it is invalid to use the same texture as both readable and writable in a single 1237 // draw or dispatch. TEST_F(ResourceUsageTrackingTest,TextureWithReadAndWriteUsageInSingleDrawOrDispatch)1238 TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsageInSingleDrawOrDispatch) { 1239 // Create a texture that will be used both as a sampled texture and a storage texture 1240 wgpu::Texture texture = 1241 CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding); 1242 wgpu::TextureView view = texture.CreateView(); 1243 1244 // Test render pass 1245 { 1246 // Create the bind group to use the texture as sampled and writeonly storage bindings 1247 wgpu::BindGroupLayout sampledBGL = utils::MakeBindGroupLayout( 1248 device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}}); 1249 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 1250 device, 1251 {{0, wgpu::ShaderStage::Fragment, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1252 wgpu::BindGroup sampledBG = utils::MakeBindGroup(device, sampledBGL, {{0, view}}); 1253 wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); 1254 1255 // Create a no-op render pipeline. 1256 wgpu::RenderPipeline rp = CreateNoOpRenderPipeline(); 1257 1258 // It is invalid to use the same texture as both readable and writable usages in a 1259 // single draw 1260 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1261 DummyRenderPass dummyRenderPass(device); 1262 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 1263 pass.SetPipeline(rp); 1264 1265 pass.SetBindGroup(0, sampledBG); 1266 pass.SetBindGroup(1, writeBG); 1267 pass.Draw(3); 1268 1269 pass.EndPass(); 1270 ASSERT_DEVICE_ERROR(encoder.Finish()); 1271 } 1272 1273 // Test compute pass 1274 { 1275 // Create the bind group to use the texture as sampled and writeonly storage bindings 1276 wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( 1277 device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); 1278 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 1279 device, 1280 {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1281 wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); 1282 wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); 1283 1284 // Create a no-op compute pipeline. 1285 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({readBGL, writeBGL}); 1286 1287 // It is invalid to use the same texture as both readable and writable usages in a 1288 // single dispatch 1289 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1290 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 1291 pass.SetPipeline(cp); 1292 1293 pass.SetBindGroup(0, readBG); 1294 pass.SetBindGroup(1, writeBG); 1295 pass.Dispatch(1); 1296 1297 pass.EndPass(); 1298 ASSERT_DEVICE_ERROR(encoder.Finish()); 1299 } 1300 } 1301 1302 // Test that using a single texture as copy src/dst and writable/readable usage in pass is 1303 // allowed. TEST_F(ResourceUsageTrackingTest,TextureCopyAndTextureUsageInPass)1304 TEST_F(ResourceUsageTrackingTest, TextureCopyAndTextureUsageInPass) { 1305 // Create textures that will be used as both a sampled texture and a render target 1306 wgpu::Texture texture0 = CreateTexture(wgpu::TextureUsage::CopySrc); 1307 wgpu::Texture texture1 = 1308 CreateTexture(wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding | 1309 wgpu::TextureUsage::RenderAttachment); 1310 wgpu::TextureView view0 = texture0.CreateView(); 1311 wgpu::TextureView view1 = texture1.CreateView(); 1312 1313 wgpu::ImageCopyTexture srcView = utils::CreateImageCopyTexture(texture0, 0, {0, 0, 0}); 1314 wgpu::ImageCopyTexture dstView = utils::CreateImageCopyTexture(texture1, 0, {0, 0, 0}); 1315 wgpu::Extent3D copySize = {1, 1, 1}; 1316 1317 // Use the texture as both copy dst and render attachment in render pass 1318 { 1319 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1320 encoder.CopyTextureToTexture(&srcView, &dstView, ©Size); 1321 utils::ComboRenderPassDescriptor renderPass({view1}); 1322 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); 1323 pass.EndPass(); 1324 encoder.Finish(); 1325 } 1326 1327 // Use the texture as both copy dst and readable usage in compute pass 1328 { 1329 // Create the bind group to use the texture as sampled 1330 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 1331 device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); 1332 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view1}}); 1333 1334 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); 1335 1336 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1337 encoder.CopyTextureToTexture(&srcView, &dstView, ©Size); 1338 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 1339 pass.SetBindGroup(0, bg); 1340 pass.SetPipeline(cp); 1341 pass.Dispatch(1); 1342 pass.EndPass(); 1343 encoder.Finish(); 1344 } 1345 } 1346 1347 // Test that all consecutive SetBindGroup()s take effect even though some bind groups are not 1348 // used because they are overwritten by a consecutive call. TEST_F(ResourceUsageTrackingTest,TextureWithMultipleSetBindGroupsOnSameIndex)1349 TEST_F(ResourceUsageTrackingTest, TextureWithMultipleSetBindGroupsOnSameIndex) { 1350 // Test render pass 1351 { 1352 // Create textures that will be used as both a sampled texture and a render target 1353 wgpu::Texture texture0 = CreateTexture(wgpu::TextureUsage::TextureBinding | 1354 wgpu::TextureUsage::RenderAttachment); 1355 wgpu::TextureView view0 = texture0.CreateView(); 1356 wgpu::Texture texture1 = CreateTexture(wgpu::TextureUsage::TextureBinding | 1357 wgpu::TextureUsage::RenderAttachment); 1358 wgpu::TextureView view1 = texture1.CreateView(); 1359 1360 // Create the bind group to use the texture as sampled 1361 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 1362 device, {{0, wgpu::ShaderStage::Vertex, wgpu::TextureSampleType::Float}}); 1363 wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, view0}}); 1364 wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, view1}}); 1365 1366 // Create the render pass that will use the texture as an render attachment 1367 utils::ComboRenderPassDescriptor renderPass({view0}); 1368 1369 // Set bind group on the same index twice. The second one overwrites the first one. 1370 // No texture is used as both sampled and render attachment in the same pass. But the 1371 // overwritten texture still take effect during resource tracking. 1372 { 1373 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1374 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); 1375 pass.SetBindGroup(0, bg0); 1376 pass.SetBindGroup(0, bg1); 1377 pass.EndPass(); 1378 ASSERT_DEVICE_ERROR(encoder.Finish()); 1379 } 1380 1381 // Set bind group on the same index twice. The second one overwrites the first one. 1382 // texture0 is used as both sampled and render attachment in the same pass 1383 { 1384 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1385 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); 1386 pass.SetBindGroup(0, bg1); 1387 pass.SetBindGroup(0, bg0); 1388 pass.EndPass(); 1389 ASSERT_DEVICE_ERROR(encoder.Finish()); 1390 } 1391 } 1392 1393 // Test compute pass 1394 { 1395 // Create a texture that will be used both as storage texture 1396 wgpu::Texture texture0 = CreateTexture(wgpu::TextureUsage::TextureBinding | 1397 wgpu::TextureUsage::StorageBinding); 1398 wgpu::TextureView view0 = texture0.CreateView(); 1399 wgpu::Texture texture1 = CreateTexture(wgpu::TextureUsage::TextureBinding); 1400 wgpu::TextureView view1 = texture1.CreateView(); 1401 1402 // Create the bind group to use the texture as sampled and writeonly bindings 1403 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 1404 device, 1405 {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1406 1407 wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( 1408 device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); 1409 1410 wgpu::BindGroup writeBG0 = utils::MakeBindGroup(device, writeBGL, {{0, view0}}); 1411 wgpu::BindGroup readBG0 = utils::MakeBindGroup(device, readBGL, {{0, view0}}); 1412 wgpu::BindGroup readBG1 = utils::MakeBindGroup(device, readBGL, {{0, view1}}); 1413 1414 // Create a no-op compute pipeline. 1415 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({writeBGL, readBGL}); 1416 1417 // Set bind group on the same index twice. The second one overwrites the first one. 1418 // No texture is used as both sampled and writeonly storage in the same dispatch so 1419 // there are no errors. 1420 { 1421 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1422 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 1423 pass.SetBindGroup(0, writeBG0); 1424 pass.SetBindGroup(1, readBG0); 1425 pass.SetBindGroup(1, readBG1); 1426 pass.SetPipeline(cp); 1427 pass.Dispatch(1); 1428 pass.EndPass(); 1429 encoder.Finish(); 1430 } 1431 1432 // Set bind group on the same index twice. The second one overwrites the first one. 1433 // texture0 is used as both writeonly and sampled storage in the same dispatch, which 1434 // is an error. 1435 { 1436 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1437 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 1438 pass.SetBindGroup(0, writeBG0); 1439 pass.SetBindGroup(1, readBG1); 1440 pass.SetBindGroup(1, readBG0); 1441 pass.SetPipeline(cp); 1442 pass.Dispatch(1); 1443 pass.EndPass(); 1444 ASSERT_DEVICE_ERROR(encoder.Finish()); 1445 } 1446 } 1447 } 1448 1449 // Test that it is invalid to have resource usage conflicts even when all bindings are not 1450 // visible to the programmable pass where it is used. TEST_F(ResourceUsageTrackingTest,TextureUsageConflictBetweenInvisibleStagesInBindGroup)1451 TEST_F(ResourceUsageTrackingTest, TextureUsageConflictBetweenInvisibleStagesInBindGroup) { 1452 // Create texture and texture view 1453 wgpu::Texture texture = 1454 CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding); 1455 wgpu::TextureView view = texture.CreateView(); 1456 1457 // Test render pass for bind group. The conflict of sampled storage and writeonly storage 1458 // usage doesn't reside in render related stages at all 1459 { 1460 // Create a bind group whose bindings are not visible in render pass 1461 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 1462 device, 1463 {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}, 1464 {1, wgpu::ShaderStage::None, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1465 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); 1466 1467 // These two bindings are invisible in render pass. But we still track these bindings. 1468 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1469 DummyRenderPass dummyRenderPass(device); 1470 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 1471 pass.SetBindGroup(0, bg); 1472 pass.EndPass(); 1473 ASSERT_DEVICE_ERROR(encoder.Finish()); 1474 } 1475 1476 // Test compute pass for bind group. The conflict of sampled storage and writeonly storage 1477 // usage doesn't reside in compute related stage at all 1478 { 1479 // Create a bind group whose bindings are not visible in compute pass 1480 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 1481 device, 1482 {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}, 1483 {1, wgpu::ShaderStage::None, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1484 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); 1485 1486 // Create a no-op compute pipeline. 1487 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); 1488 1489 // These two bindings are invisible in compute pass. But we still track these bindings. 1490 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1491 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 1492 pass.SetPipeline(cp); 1493 pass.SetBindGroup(0, bg); 1494 pass.Dispatch(1); 1495 pass.EndPass(); 1496 ASSERT_DEVICE_ERROR(encoder.Finish()); 1497 } 1498 } 1499 1500 // Test that it is invalid to have resource usage conflicts even when one of the bindings is not 1501 // visible to the programmable pass where it is used. TEST_F(ResourceUsageTrackingTest,TextureUsageConflictWithInvisibleStageInBindGroup)1502 TEST_F(ResourceUsageTrackingTest, TextureUsageConflictWithInvisibleStageInBindGroup) { 1503 // Create texture and texture view 1504 wgpu::Texture texture = 1505 CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding | 1506 wgpu::TextureUsage::RenderAttachment); 1507 wgpu::TextureView view = texture.CreateView(); 1508 1509 // Test render pass 1510 { 1511 // Create the render pass that will use the texture as an render attachment 1512 utils::ComboRenderPassDescriptor renderPass({view}); 1513 1514 // Create a bind group which use the texture as sampled storage in compute stage 1515 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 1516 device, {{0, wgpu::ShaderStage::Compute, wgpu::TextureSampleType::Float}}); 1517 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}}); 1518 1519 // Texture usage in compute stage in bind group conflicts with render target. And 1520 // binding for compute stage is not visible in render pass. But we still track this 1521 // binding. 1522 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1523 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); 1524 pass.SetBindGroup(0, bg); 1525 pass.EndPass(); 1526 ASSERT_DEVICE_ERROR(encoder.Finish()); 1527 } 1528 1529 // Test compute pass 1530 { 1531 // Create a bind group which contains both fragment and compute stages 1532 wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( 1533 device, 1534 {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float}, 1535 {1, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1536 wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}, {1, view}}); 1537 1538 // Create a no-op compute pipeline. 1539 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({bgl}); 1540 1541 // Texture usage in compute stage conflicts with texture usage in fragment stage. And 1542 // binding for fragment stage is not visible in compute pass. But we still track this 1543 // invisible binding. 1544 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1545 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 1546 pass.SetPipeline(cp); 1547 pass.SetBindGroup(0, bg); 1548 pass.Dispatch(1); 1549 pass.EndPass(); 1550 ASSERT_DEVICE_ERROR(encoder.Finish()); 1551 } 1552 } 1553 1554 // Test that it is invalid to have resource usage conflicts even when one of the bindings is not 1555 // used in the pipeline. TEST_F(ResourceUsageTrackingTest,TextureUsageConflictWithUnusedPipelineBindings)1556 TEST_F(ResourceUsageTrackingTest, TextureUsageConflictWithUnusedPipelineBindings) { 1557 // Create texture and texture view 1558 wgpu::Texture texture = 1559 CreateTexture(wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding); 1560 wgpu::TextureView view = texture.CreateView(); 1561 1562 // Create bind groups. 1563 wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( 1564 device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, 1565 wgpu::TextureSampleType::Float}}); 1566 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 1567 device, {{0, wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Compute, 1568 wgpu::StorageTextureAccess::WriteOnly, kFormat}}); 1569 wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}}); 1570 wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}}); 1571 1572 // Test render pass 1573 { 1574 // Create a passthrough render pipeline with a sampled storage texture 1575 wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( 1576 [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> { 1577 return vec4<f32>(); 1578 })"); 1579 1580 wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( 1581 [[group(0), binding(0)]] var tex : texture_2d<f32>; 1582 [[stage(fragment)]] fn main() { 1583 })"); 1584 utils::ComboRenderPipelineDescriptor pipelineDescriptor; 1585 pipelineDescriptor.vertex.module = vsModule; 1586 pipelineDescriptor.cFragment.module = fsModule; 1587 pipelineDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None; 1588 pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &readBGL); 1589 wgpu::RenderPipeline rp = device.CreateRenderPipeline(&pipelineDescriptor); 1590 1591 // Texture binding in readBG conflicts with texture binding in writeBG. The binding 1592 // in writeBG is not used in pipeline. But we still track this binding. 1593 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1594 DummyRenderPass dummyRenderPass(device); 1595 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 1596 pass.SetBindGroup(0, readBG); 1597 pass.SetBindGroup(1, writeBG); 1598 pass.SetPipeline(rp); 1599 pass.Draw(3); 1600 pass.EndPass(); 1601 ASSERT_DEVICE_ERROR(encoder.Finish()); 1602 } 1603 1604 // Test compute pass 1605 { 1606 wgpu::ComputePipeline cp = CreateNoOpComputePipeline({readBGL}); 1607 1608 // Texture binding in readBG conflicts with texture binding in writeBG. The binding 1609 // in writeBG is not used in pipeline's layout so it isn't an error. 1610 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1611 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 1612 pass.SetBindGroup(0, readBG); 1613 pass.SetBindGroup(1, writeBG); 1614 pass.SetPipeline(cp); 1615 pass.Dispatch(1); 1616 pass.EndPass(); 1617 encoder.Finish(); 1618 } 1619 } 1620 1621 // Test that using an indirect buffer is disallowed with a writable usage (like storage) but 1622 // allowed with a readable usage (like readonly storage). TEST_F(ResourceUsageTrackingTest,IndirectBufferWithReadOrWriteStorage)1623 TEST_F(ResourceUsageTrackingTest, IndirectBufferWithReadOrWriteStorage) { 1624 wgpu::Buffer buffer = 1625 CreateBuffer(20, wgpu::BufferUsage::Indirect | wgpu::BufferUsage::Storage); 1626 1627 wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout( 1628 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::ReadOnlyStorage}}); 1629 wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout( 1630 device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); 1631 1632 wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, buffer}}); 1633 wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, buffer}}); 1634 1635 // Test pipelines 1636 wgpu::RenderPipeline rp = CreateNoOpRenderPipeline(); 1637 wgpu::ComputePipeline readCp = CreateNoOpComputePipeline({readBGL}); 1638 wgpu::ComputePipeline writeCp = CreateNoOpComputePipeline({writeBGL}); 1639 1640 // Test that indirect + readonly is allowed in the same render pass. 1641 { 1642 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1643 DummyRenderPass dummyRenderPass(device); 1644 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 1645 pass.SetPipeline(rp); 1646 pass.SetBindGroup(0, readBG); 1647 pass.DrawIndirect(buffer, 0); 1648 pass.EndPass(); 1649 encoder.Finish(); 1650 } 1651 1652 // Test that indirect + writable is disallowed in the same render pass. 1653 { 1654 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1655 DummyRenderPass dummyRenderPass(device); 1656 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass); 1657 pass.SetPipeline(rp); 1658 pass.SetBindGroup(0, writeBG); 1659 pass.DrawIndirect(buffer, 0); 1660 pass.EndPass(); 1661 ASSERT_DEVICE_ERROR(encoder.Finish()); 1662 } 1663 1664 // Test that indirect + readonly is allowed in the same dispatch 1665 { 1666 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1667 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 1668 pass.SetPipeline(readCp); 1669 pass.SetBindGroup(0, readBG); 1670 pass.DispatchIndirect(buffer, 0); 1671 pass.EndPass(); 1672 encoder.Finish(); 1673 } 1674 1675 // Test that indirect + writable is disallowed in the same dispatch 1676 { 1677 wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); 1678 wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); 1679 pass.SetPipeline(writeCp); 1680 pass.SetBindGroup(0, writeBG); 1681 pass.DispatchIndirect(buffer, 0); 1682 pass.EndPass(); 1683 ASSERT_DEVICE_ERROR(encoder.Finish()); 1684 } 1685 } 1686 1687 // TODO (yunchao.he@intel.com): 1688 // 1689 // * Add tests for multiple encoders upon the same resource simultaneously. This situation fits 1690 // some cases like VR, multi-threading, etc. 1691 // 1692 // * Add tests for bundle 1693 1694 } // anonymous namespace 1695