| // Copyright 2021 The Dawn Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "tests/unittests/validation/ValidationTest.h" |
| |
| #include "common/Constants.h" |
| #include "utils/ComboRenderPipelineDescriptor.h" |
| #include "utils/WGPUHelpers.h" |
| |
| namespace { |
| constexpr uint32_t kRTSize = 4; |
| constexpr uint32_t kFloat32x2Stride = 2 * sizeof(float); |
| constexpr uint32_t kFloat32x4Stride = 4 * sizeof(float); |
| |
| class DrawVertexAndIndexBufferOOBValidationTests : public ValidationTest { |
| public: |
| // Parameters for testing index buffer |
| struct IndexBufferParams { |
| wgpu::IndexFormat indexFormat; |
| uint64_t indexBufferSize; // Size for creating index buffer |
| uint64_t indexBufferOffsetForEncoder; // Offset for SetIndexBuffer in encoder |
| uint64_t indexBufferSizeForEncoder; // Size for SetIndexBuffer in encoder |
| uint32_t maxValidIndexNumber; // max number of {indexCount + firstIndex} for this set |
| // of parameters |
| }; |
| |
| // Parameters for testing vertex-step-mode and instance-step-mode vertex buffer |
| struct VertexBufferParams { |
| uint32_t bufferStride; |
| uint64_t bufferSize; // Size for creating vertex buffer |
| uint64_t bufferOffsetForEncoder; // Offset for SetVertexBuffer in encoder |
| uint64_t bufferSizeForEncoder; // Size for SetVertexBuffer in encoder |
| uint32_t maxValidAccessNumber; // max number of valid access time for this set of |
| // parameters, i.e. {vertexCount + firstVertex} for |
| // vertex-step-mode, and {instanceCount + firstInstance} |
| // for instance-step-mode |
| }; |
| |
| // Parameters for setIndexBuffer |
| struct IndexBufferDesc { |
| const wgpu::Buffer buffer; |
| wgpu::IndexFormat indexFormat; |
| uint64_t offset = 0; |
| uint64_t size = wgpu::kWholeSize; |
| }; |
| |
| // Parameters for setVertexBuffer |
| struct VertexBufferSpec { |
| uint32_t slot; |
| const wgpu::Buffer buffer; |
| uint64_t offset = 0; |
| uint64_t size = wgpu::kWholeSize; |
| }; |
| using VertexBufferList = std::vector<VertexBufferSpec>; |
| |
| // Buffer layout parameters for creating pipeline |
| struct PipelineVertexBufferAttributeDesc { |
| uint32_t shaderLocation; |
| wgpu::VertexFormat format; |
| uint64_t offset = 0; |
| }; |
| struct PipelineVertexBufferDesc { |
| uint64_t arrayStride; |
| wgpu::VertexStepMode stepMode; |
| std::vector<PipelineVertexBufferAttributeDesc> attributes = {}; |
| }; |
| |
| void SetUp() override { |
| ValidationTest::SetUp(); |
| |
| renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); |
| |
| fsModule = utils::CreateShaderModule(device, R"( |
| [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> { |
| return vec4<f32>(0.0, 1.0, 0.0, 1.0); |
| })"); |
| } |
| |
| const wgpu::RenderPassDescriptor* GetBasicRenderPassDescriptor() const { |
| return &renderPass.renderPassInfo; |
| } |
| |
| wgpu::Buffer CreateBuffer(uint64_t size, |
| wgpu::BufferUsage usage = wgpu::BufferUsage::Vertex) { |
| wgpu::BufferDescriptor descriptor; |
| descriptor.size = size; |
| descriptor.usage = usage; |
| |
| return device.CreateBuffer(&descriptor); |
| } |
| |
| wgpu::ShaderModule CreateVertexShaderModuleWithBuffer( |
| std::vector<PipelineVertexBufferDesc> bufferDescList) { |
| uint32_t attributeCount = 0; |
| std::stringstream inputStringStream; |
| |
| for (auto buffer : bufferDescList) { |
| for (auto attr : buffer.attributes) { |
| // [[location({shaderLocation})]] var_{id} : {typeString}, |
| inputStringStream << "[[location(" << attr.shaderLocation << ")]] var_" |
| << attributeCount << " : vec4<f32>,"; |
| attributeCount++; |
| } |
| } |
| |
| std::stringstream shaderStringStream; |
| |
| shaderStringStream << R"( |
| [[stage(vertex)]] |
| fn main()" << inputStringStream.str() |
| << R"() -> [[builtin(position)]] vec4<f32> { |
| return vec4<f32>(0.0, 1.0, 0.0, 1.0); |
| })"; |
| |
| return utils::CreateShaderModule(device, shaderStringStream.str().c_str()); |
| } |
| |
| // Create a render pipeline with given buffer layout description, using a vertex shader |
| // module automatically generated from the buffer description. |
| wgpu::RenderPipeline CreateRenderPipelineWithBufferDesc( |
| std::vector<PipelineVertexBufferDesc> bufferDescList) { |
| utils::ComboRenderPipelineDescriptor descriptor; |
| |
| descriptor.vertex.module = CreateVertexShaderModuleWithBuffer(bufferDescList); |
| descriptor.cFragment.module = fsModule; |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; |
| |
| descriptor.vertex.bufferCount = bufferDescList.size(); |
| |
| size_t attributeCount = 0; |
| |
| for (size_t bufferCount = 0; bufferCount < bufferDescList.size(); bufferCount++) { |
| auto bufferDesc = bufferDescList[bufferCount]; |
| descriptor.cBuffers[bufferCount].arrayStride = bufferDesc.arrayStride; |
| descriptor.cBuffers[bufferCount].stepMode = bufferDesc.stepMode; |
| if (bufferDesc.attributes.size() > 0) { |
| descriptor.cBuffers[bufferCount].attributeCount = bufferDesc.attributes.size(); |
| descriptor.cBuffers[bufferCount].attributes = |
| &descriptor.cAttributes[attributeCount]; |
| for (auto attribute : bufferDesc.attributes) { |
| descriptor.cAttributes[attributeCount].shaderLocation = |
| attribute.shaderLocation; |
| descriptor.cAttributes[attributeCount].format = attribute.format; |
| descriptor.cAttributes[attributeCount].offset = attribute.offset; |
| attributeCount++; |
| } |
| } else { |
| descriptor.cBuffers[bufferCount].attributeCount = 0; |
| descriptor.cBuffers[bufferCount].attributes = nullptr; |
| } |
| } |
| |
| descriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| return device.CreateRenderPipeline(&descriptor); |
| } |
| |
| // Create a render pipeline using only one vertex-step-mode Float32x4 buffer |
| wgpu::RenderPipeline CreateBasicRenderPipeline(uint32_t bufferStride = kFloat32x4Stride) { |
| DAWN_ASSERT(bufferStride >= kFloat32x4Stride); |
| |
| std::vector<PipelineVertexBufferDesc> bufferDescList = { |
| {bufferStride, wgpu::VertexStepMode::Vertex, {{0, wgpu::VertexFormat::Float32x4}}}, |
| }; |
| |
| return CreateRenderPipelineWithBufferDesc(bufferDescList); |
| } |
| |
| // Create a render pipeline using one vertex-step-mode Float32x4 buffer and one |
| // instance-step-mode Float32x2 buffer |
| wgpu::RenderPipeline CreateBasicRenderPipelineWithInstance( |
| uint32_t bufferStride1 = kFloat32x4Stride, |
| uint32_t bufferStride2 = kFloat32x2Stride) { |
| DAWN_ASSERT(bufferStride1 >= kFloat32x4Stride); |
| DAWN_ASSERT(bufferStride2 >= kFloat32x2Stride); |
| |
| std::vector<PipelineVertexBufferDesc> bufferDescList = { |
| {bufferStride1, wgpu::VertexStepMode::Vertex, {{0, wgpu::VertexFormat::Float32x4}}}, |
| {bufferStride2, |
| wgpu::VertexStepMode::Instance, |
| {{3, wgpu::VertexFormat::Float32x2}}}, |
| }; |
| |
| return CreateRenderPipelineWithBufferDesc(bufferDescList); |
| } |
| |
| // Create a render pipeline using one vertex-step-mode and one instance-step-mode buffer, |
| // both with a zero array stride. The minimal size of vertex step mode buffer should be 28, |
| // and the minimal size of instance step mode buffer should be 20. |
| wgpu::RenderPipeline CreateBasicRenderPipelineWithZeroArrayStride() { |
| std::vector<PipelineVertexBufferDesc> bufferDescList = { |
| {0, |
| wgpu::VertexStepMode::Vertex, |
| {{0, wgpu::VertexFormat::Float32x4, 0}, {1, wgpu::VertexFormat::Float32x2, 20}}}, |
| {0, |
| wgpu::VertexStepMode::Instance, |
| // Two attributes are overlapped within this instance step mode vertex buffer |
| {{3, wgpu::VertexFormat::Float32x4, 4}, {7, wgpu::VertexFormat::Float32x3, 0}}}, |
| }; |
| |
| return CreateRenderPipelineWithBufferDesc(bufferDescList); |
| } |
| |
| void TestRenderPassDraw(const wgpu::RenderPipeline& pipeline, |
| VertexBufferList vertexBufferList, |
| uint32_t vertexCount, |
| uint32_t instanceCount, |
| uint32_t firstVertex, |
| uint32_t firstInstance, |
| bool isSuccess) { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = |
| encoder.BeginRenderPass(GetBasicRenderPassDescriptor()); |
| renderPassEncoder.SetPipeline(pipeline); |
| |
| for (auto vertexBufferParam : vertexBufferList) { |
| renderPassEncoder.SetVertexBuffer(vertexBufferParam.slot, vertexBufferParam.buffer, |
| vertexBufferParam.offset, vertexBufferParam.size); |
| } |
| renderPassEncoder.Draw(vertexCount, instanceCount, firstVertex, firstInstance); |
| renderPassEncoder.EndPass(); |
| |
| if (isSuccess) { |
| encoder.Finish(); |
| } else { |
| ASSERT_DEVICE_ERROR(encoder.Finish()); |
| } |
| } |
| |
| void TestRenderPassDrawIndexed(const wgpu::RenderPipeline& pipeline, |
| IndexBufferDesc indexBuffer, |
| VertexBufferList vertexBufferList, |
| uint32_t indexCount, |
| uint32_t instanceCount, |
| uint32_t firstIndex, |
| int32_t baseVertex, |
| uint32_t firstInstance, |
| bool isSuccess) { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = |
| encoder.BeginRenderPass(GetBasicRenderPassDescriptor()); |
| renderPassEncoder.SetPipeline(pipeline); |
| |
| renderPassEncoder.SetIndexBuffer(indexBuffer.buffer, indexBuffer.indexFormat, |
| indexBuffer.offset, indexBuffer.size); |
| |
| for (auto vertexBufferParam : vertexBufferList) { |
| renderPassEncoder.SetVertexBuffer(vertexBufferParam.slot, vertexBufferParam.buffer, |
| vertexBufferParam.offset, vertexBufferParam.size); |
| } |
| renderPassEncoder.DrawIndexed(indexCount, instanceCount, firstIndex, baseVertex, |
| firstInstance); |
| renderPassEncoder.EndPass(); |
| |
| if (isSuccess) { |
| encoder.Finish(); |
| } else { |
| ASSERT_DEVICE_ERROR(encoder.Finish()); |
| } |
| } |
| |
| // Parameters list for index buffer. Should cover all IndexFormat, and the zero/non-zero |
| // offset and size case in SetIndexBuffer |
| const std::vector<IndexBufferParams> kIndexParamsList = { |
| {wgpu::IndexFormat::Uint32, 12 * sizeof(uint32_t), 0, wgpu::kWholeSize, 12}, |
| {wgpu::IndexFormat::Uint32, 13 * sizeof(uint32_t), sizeof(uint32_t), wgpu::kWholeSize, |
| 12}, |
| {wgpu::IndexFormat::Uint32, 13 * sizeof(uint32_t), 0, 12 * sizeof(uint32_t), 12}, |
| {wgpu::IndexFormat::Uint32, 14 * sizeof(uint32_t), sizeof(uint32_t), |
| 12 * sizeof(uint32_t), 12}, |
| |
| {wgpu::IndexFormat::Uint16, 12 * sizeof(uint16_t), 0, wgpu::kWholeSize, 12}, |
| {wgpu::IndexFormat::Uint16, 13 * sizeof(uint16_t), sizeof(uint16_t), wgpu::kWholeSize, |
| 12}, |
| {wgpu::IndexFormat::Uint16, 13 * sizeof(uint16_t), 0, 12 * sizeof(uint16_t), 12}, |
| {wgpu::IndexFormat::Uint16, 14 * sizeof(uint16_t), sizeof(uint16_t), |
| 12 * sizeof(uint16_t), 12}, |
| }; |
| // Parameters list for vertex-step-mode buffer. These parameters should cover different |
| // stride, buffer size, SetVertexBuffer size and offset. |
| const std::vector<VertexBufferParams> kVertexParamsList = { |
| // For stride = kFloat32x4Stride |
| {kFloat32x4Stride, 3 * kFloat32x4Stride, 0, wgpu::kWholeSize, 3}, |
| // Non-zero offset |
| {kFloat32x4Stride, 4 * kFloat32x4Stride, kFloat32x4Stride, wgpu::kWholeSize, 3}, |
| // Non-default size |
| {kFloat32x4Stride, 4 * kFloat32x4Stride, 0, 3 * kFloat32x4Stride, 3}, |
| // Non-zero offset and size |
| {kFloat32x4Stride, 5 * kFloat32x4Stride, kFloat32x4Stride, 3 * kFloat32x4Stride, 3}, |
| // For stride = 2 * kFloat32x4Stride |
| {(2 * kFloat32x4Stride), 3 * (2 * kFloat32x4Stride), 0, wgpu::kWholeSize, 3}, |
| // Non-zero offset |
| {(2 * kFloat32x4Stride), 4 * (2 * kFloat32x4Stride), (2 * kFloat32x4Stride), |
| wgpu::kWholeSize, 3}, |
| // Non-default size |
| {(2 * kFloat32x4Stride), 4 * (2 * kFloat32x4Stride), 0, 3 * (2 * kFloat32x4Stride), 3}, |
| // Non-zero offset and size |
| {(2 * kFloat32x4Stride), 5 * (2 * kFloat32x4Stride), (2 * kFloat32x4Stride), |
| 3 * (2 * kFloat32x4Stride), 3}, |
| }; |
| // Parameters list for instance-step-mode buffer. |
| const std::vector<VertexBufferParams> kInstanceParamsList = { |
| // For stride = kFloat32x2Stride |
| {kFloat32x2Stride, 5 * kFloat32x2Stride, 0, wgpu::kWholeSize, 5}, |
| // Non-zero offset |
| {kFloat32x2Stride, 6 * kFloat32x2Stride, kFloat32x2Stride, wgpu::kWholeSize, 5}, |
| // Non-default size |
| {kFloat32x2Stride, 6 * kFloat32x2Stride, 0, 5 * kFloat32x2Stride, 5}, |
| // Non-zero offset and size |
| {kFloat32x2Stride, 7 * kFloat32x2Stride, kFloat32x2Stride, 5 * kFloat32x2Stride, 5}, |
| // For stride = 3 * kFloat32x2Stride |
| {(3 * kFloat32x2Stride), 5 * (3 * kFloat32x2Stride), 0, wgpu::kWholeSize, 5}, |
| // Non-zero offset |
| {(3 * kFloat32x2Stride), 6 * (3 * kFloat32x2Stride), (3 * kFloat32x2Stride), |
| wgpu::kWholeSize, 5}, |
| // Non-default size |
| {(3 * kFloat32x2Stride), 6 * (3 * kFloat32x2Stride), 0, 5 * (3 * kFloat32x2Stride), 5}, |
| // Non-zero offset and size |
| {(3 * kFloat32x2Stride), 7 * (3 * kFloat32x2Stride), (3 * kFloat32x2Stride), |
| 5 * (3 * kFloat32x2Stride), 5}, |
| }; |
| |
| private: |
| wgpu::ShaderModule fsModule; |
| utils::BasicRenderPass renderPass; |
| }; |
| |
| // Control case for Draw |
| TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawBasic) { |
| wgpu::RenderPipeline pipeline = CreateBasicRenderPipeline(); |
| |
| wgpu::Buffer vertexBuffer = CreateBuffer(3 * kFloat32x4Stride); |
| |
| { |
| // Implicit size |
| VertexBufferList vertexBufferList = {{0, vertexBuffer, 0, wgpu::kWholeSize}}; |
| TestRenderPassDraw(pipeline, vertexBufferList, 3, 1, 0, 0, true); |
| } |
| |
| { |
| // Explicit zero size |
| VertexBufferList vertexBufferList = {{0, vertexBuffer, 0, 0}}; |
| TestRenderPassDraw(pipeline, vertexBufferList, 3, 1, 0, 0, false); |
| } |
| } |
| |
| // Verify vertex buffer OOB for non-instanced Draw are caught in command encoder |
| TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawVertexBufferOutOfBoundWithoutInstance) { |
| for (VertexBufferParams params : kVertexParamsList) { |
| // Create a render pipeline without instance step mode buffer |
| wgpu::RenderPipeline pipeline = CreateBasicRenderPipeline(params.bufferStride); |
| |
| // Build vertex buffer for 3 vertices |
| wgpu::Buffer vertexBuffer = CreateBuffer(params.bufferSize); |
| VertexBufferList vertexBufferList = { |
| {0, vertexBuffer, params.bufferOffsetForEncoder, params.bufferSizeForEncoder}}; |
| |
| uint32_t n = params.maxValidAccessNumber; |
| // It is ok to draw n vertices with vertex buffer |
| TestRenderPassDraw(pipeline, vertexBufferList, n, 1, 0, 0, true); |
| // It is ok to draw n-1 vertices with offset 1 |
| TestRenderPassDraw(pipeline, vertexBufferList, n - 1, 1, 1, 0, true); |
| // Drawing more vertices will cause OOB, even if not enough for another primitive |
| TestRenderPassDraw(pipeline, vertexBufferList, n + 1, 1, 0, 0, false); |
| // Drawing n vertices will non-zero offset will cause OOB |
| TestRenderPassDraw(pipeline, vertexBufferList, n, 1, 1, 0, false); |
| // It is ok to draw any number of instances, as we have no instance-mode buffer |
| TestRenderPassDraw(pipeline, vertexBufferList, n, 5, 0, 0, true); |
| TestRenderPassDraw(pipeline, vertexBufferList, n, 5, 0, 5, true); |
| } |
| } |
| |
| // Verify vertex buffer OOB for instanced Draw are caught in command encoder |
| TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawVertexBufferOutOfBoundWithInstance) { |
| for (VertexBufferParams vertexParams : kVertexParamsList) { |
| for (VertexBufferParams instanceParams : kInstanceParamsList) { |
| // Create pipeline with given buffer stride |
| wgpu::RenderPipeline pipeline = CreateBasicRenderPipelineWithInstance( |
| vertexParams.bufferStride, instanceParams.bufferStride); |
| |
| // Build vertex buffer |
| wgpu::Buffer vertexBuffer = CreateBuffer(vertexParams.bufferSize); |
| wgpu::Buffer instanceBuffer = CreateBuffer(instanceParams.bufferSize); |
| |
| VertexBufferList vertexBufferList = { |
| {0, vertexBuffer, vertexParams.bufferOffsetForEncoder, |
| vertexParams.bufferSizeForEncoder}, |
| {1, instanceBuffer, instanceParams.bufferOffsetForEncoder, |
| instanceParams.bufferSizeForEncoder}, |
| }; |
| |
| uint32_t vert = vertexParams.maxValidAccessNumber; |
| uint32_t inst = instanceParams.maxValidAccessNumber; |
| // It is ok to draw vert vertices |
| TestRenderPassDraw(pipeline, vertexBufferList, vert, 1, 0, 0, true); |
| TestRenderPassDraw(pipeline, vertexBufferList, vert - 1, 1, 1, 0, true); |
| // It is ok to draw vert vertices and inst instences |
| TestRenderPassDraw(pipeline, vertexBufferList, vert, inst, 0, 0, true); |
| TestRenderPassDraw(pipeline, vertexBufferList, vert, inst - 1, 0, 1, true); |
| // more vertices causing OOB |
| TestRenderPassDraw(pipeline, vertexBufferList, vert + 1, 1, 0, 0, false); |
| TestRenderPassDraw(pipeline, vertexBufferList, vert, 1, 1, 0, false); |
| TestRenderPassDraw(pipeline, vertexBufferList, vert + 1, inst, 0, 0, false); |
| TestRenderPassDraw(pipeline, vertexBufferList, vert, inst, 1, 0, false); |
| // more instances causing OOB |
| TestRenderPassDraw(pipeline, vertexBufferList, vert, inst + 1, 0, 0, false); |
| TestRenderPassDraw(pipeline, vertexBufferList, vert, inst, 0, 1, false); |
| // Both OOB |
| TestRenderPassDraw(pipeline, vertexBufferList, vert, inst + 1, 0, 0, false); |
| TestRenderPassDraw(pipeline, vertexBufferList, vert, inst, 1, 1, false); |
| } |
| } |
| } |
| |
| // Control case for DrawIndexed |
| TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawIndexedBasic) { |
| wgpu::RenderPipeline pipeline = CreateBasicRenderPipeline(); |
| |
| // Build index buffer for 12 indexes |
| wgpu::Buffer indexBuffer = CreateBuffer(12 * sizeof(uint32_t), wgpu::BufferUsage::Index); |
| |
| // Build vertex buffer for 3 vertices |
| wgpu::Buffer vertexBuffer = CreateBuffer(3 * kFloat32x4Stride); |
| VertexBufferList vertexBufferList = {{0, vertexBuffer, 0, wgpu::kWholeSize}}; |
| |
| IndexBufferDesc indexBufferDesc = {indexBuffer, wgpu::IndexFormat::Uint32}; |
| |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, 1, 0, 0, 0, |
| true); |
| } |
| |
| // Verify index buffer OOB for DrawIndexed are caught in command encoder |
| TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawIndexedIndexBufferOOB) { |
| wgpu::RenderPipeline pipeline = CreateBasicRenderPipelineWithInstance(); |
| |
| for (IndexBufferParams params : kIndexParamsList) { |
| // Build index buffer use given params |
| wgpu::Buffer indexBuffer = |
| CreateBuffer(params.indexBufferSize, wgpu::BufferUsage::Index); |
| // Build vertex buffer for 3 vertices |
| wgpu::Buffer vertexBuffer = CreateBuffer(3 * kFloat32x4Stride); |
| // Build vertex buffer for 5 instances |
| wgpu::Buffer instanceBuffer = CreateBuffer(5 * kFloat32x2Stride); |
| |
| VertexBufferList vertexBufferList = {{0, vertexBuffer, 0, wgpu::kWholeSize}, |
| {1, instanceBuffer, 0, wgpu::kWholeSize}}; |
| |
| IndexBufferDesc indexBufferDesc = {indexBuffer, params.indexFormat, |
| params.indexBufferOffsetForEncoder, |
| params.indexBufferSizeForEncoder}; |
| |
| uint32_t n = params.maxValidIndexNumber; |
| |
| // Control case |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n, 5, 0, 0, 0, |
| true); |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n - 1, 5, 1, 0, |
| 0, true); |
| // Index buffer OOB, indexCount too large |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n + 1, 5, 0, 0, |
| 0, false); |
| // Index buffer OOB, indexCount + firstIndex too large |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n, 5, 1, 0, 0, |
| false); |
| |
| if (!HasToggleEnabled("disable_base_vertex")) { |
| // baseVertex is not considered in CPU validation and has no effect on validation |
| // Although baseVertex is too large, it will still pass |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n, 5, 0, 100, |
| 0, true); |
| // Index buffer OOB, indexCount too large |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, n + 1, 5, 0, |
| 100, 0, false); |
| } |
| } |
| } |
| |
| // Verify instance mode vertex buffer OOB for DrawIndexed are caught in command encoder |
| TEST_F(DrawVertexAndIndexBufferOOBValidationTests, DrawIndexedVertexBufferOOB) { |
| for (VertexBufferParams vertexParams : kVertexParamsList) { |
| for (VertexBufferParams instanceParams : kInstanceParamsList) { |
| // Create pipeline with given buffer stride |
| wgpu::RenderPipeline pipeline = CreateBasicRenderPipelineWithInstance( |
| vertexParams.bufferStride, instanceParams.bufferStride); |
| |
| auto indexFormat = wgpu::IndexFormat::Uint32; |
| auto indexStride = sizeof(uint32_t); |
| |
| // Build index buffer for 12 indexes |
| wgpu::Buffer indexBuffer = CreateBuffer(12 * indexStride, wgpu::BufferUsage::Index); |
| // Build vertex buffer for vertices |
| wgpu::Buffer vertexBuffer = CreateBuffer(vertexParams.bufferSize); |
| // Build vertex buffer for instances |
| wgpu::Buffer instanceBuffer = CreateBuffer(instanceParams.bufferSize); |
| |
| VertexBufferList vertexBufferList = { |
| {0, vertexBuffer, vertexParams.bufferOffsetForEncoder, |
| vertexParams.bufferSizeForEncoder}, |
| {1, instanceBuffer, instanceParams.bufferOffsetForEncoder, |
| instanceParams.bufferSizeForEncoder}}; |
| |
| IndexBufferDesc indexBufferDesc = {indexBuffer, indexFormat}; |
| |
| uint32_t inst = instanceParams.maxValidAccessNumber; |
| // Control case |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, inst, 0, |
| 0, 0, true); |
| // Vertex buffer (stepMode = instance) OOB, instanceCount too large |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, inst + 1, |
| 0, 0, 0, false); |
| |
| if (!HasToggleEnabled("disable_base_instance")) { |
| // firstInstance is considered in CPU validation |
| // Vertex buffer (stepMode = instance) in bound |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, |
| inst - 1, 0, 0, 1, true); |
| // Vertex buffer (stepMode = instance) OOB, instanceCount + firstInstance too |
| // large |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, inst, |
| 0, 0, 1, false); |
| } |
| } |
| } |
| } |
| |
| // Verify instance mode vertex buffer OOB for DrawIndexed are caught in command encoder |
| TEST_F(DrawVertexAndIndexBufferOOBValidationTests, ZeroArrayStrideVertexBufferOOB) { |
| // In this test, we use VertexBufferParams.maxValidAccessNumber > 0 to indicate that such |
| // buffer parameter meet the requirement of pipeline, and maxValidAccessNumber == 0 to |
| // indicate that such buffer parameter will cause OOB. |
| const std::vector<VertexBufferParams> kVertexParamsListForZeroStride = { |
| // Control case |
| {0, 28, 0, wgpu::kWholeSize, 1}, |
| // Non-zero offset |
| {0, 28, 4, wgpu::kWholeSize, 0}, |
| {0, 28, 28, wgpu::kWholeSize, 0}, |
| // Non-default size |
| {0, 28, 0, 28, 1}, |
| {0, 28, 0, 27, 0}, |
| // Non-zero offset and size |
| {0, 32, 4, 28, 1}, |
| {0, 31, 4, 27, 0}, |
| {0, 31, 4, wgpu::kWholeSize, 0}, |
| }; |
| |
| const std::vector<VertexBufferParams> kInstanceParamsListForZeroStride = { |
| // Control case |
| {0, 20, 0, wgpu::kWholeSize, 1}, |
| // Non-zero offset |
| {0, 24, 4, wgpu::kWholeSize, 1}, |
| {0, 23, 4, wgpu::kWholeSize, 0}, |
| {0, 20, 4, wgpu::kWholeSize, 0}, |
| {0, 20, 20, wgpu::kWholeSize, 0}, |
| // Non-default size |
| {0, 21, 0, 20, 1}, |
| {0, 20, 0, 19, 0}, |
| // Non-zero offset and size |
| {0, 30, 4, 20, 1}, |
| {0, 30, 4, 19, 0}, |
| }; |
| |
| // Build a pipeline that require a vertex step mode vertex buffer no smaller than 28 bytes |
| // and an instance step mode buffer no smaller than 20 bytes |
| wgpu::RenderPipeline pipeline = CreateBasicRenderPipelineWithZeroArrayStride(); |
| |
| for (VertexBufferParams vertexParams : kVertexParamsListForZeroStride) { |
| for (VertexBufferParams instanceParams : kInstanceParamsListForZeroStride) { |
| auto indexFormat = wgpu::IndexFormat::Uint32; |
| auto indexStride = sizeof(uint32_t); |
| |
| // Build index buffer for 12 indexes |
| wgpu::Buffer indexBuffer = CreateBuffer(12 * indexStride, wgpu::BufferUsage::Index); |
| // Build vertex buffer for vertices |
| wgpu::Buffer vertexBuffer = CreateBuffer(vertexParams.bufferSize); |
| // Build vertex buffer for instances |
| wgpu::Buffer instanceBuffer = CreateBuffer(instanceParams.bufferSize); |
| |
| VertexBufferList vertexBufferList = { |
| {0, vertexBuffer, vertexParams.bufferOffsetForEncoder, |
| vertexParams.bufferSizeForEncoder}, |
| {1, instanceBuffer, instanceParams.bufferOffsetForEncoder, |
| instanceParams.bufferSizeForEncoder}}; |
| |
| IndexBufferDesc indexBufferDesc = {indexBuffer, indexFormat}; |
| |
| const bool isSuccess = (vertexParams.maxValidAccessNumber > 0) && |
| (instanceParams.maxValidAccessNumber > 0); |
| // vertexCount and instanceCount doesn't matter, as array stride is zero and all |
| // vertex/instance access the same space of buffer |
| TestRenderPassDraw(pipeline, vertexBufferList, 100, 100, 0, 0, isSuccess); |
| // indexCount doesn't matter as long as no index buffer OOB happened |
| TestRenderPassDrawIndexed(pipeline, indexBufferDesc, vertexBufferList, 12, 100, 0, |
| 0, 0, isSuccess); |
| } |
| } |
| } |
| |
| // Verify that if setVertexBuffer and/or setIndexBuffer for multiple times, only the last one is |
| // taken into account |
| TEST_F(DrawVertexAndIndexBufferOOBValidationTests, SetBufferMultipleTime) { |
| wgpu::IndexFormat indexFormat = wgpu::IndexFormat::Uint32; |
| uint32_t indexStride = sizeof(uint32_t); |
| |
| // Build index buffer for 11 indexes |
| wgpu::Buffer indexBuffer11 = CreateBuffer(11 * indexStride, wgpu::BufferUsage::Index); |
| // Build index buffer for 12 indexes |
| wgpu::Buffer indexBuffer12 = CreateBuffer(12 * indexStride, wgpu::BufferUsage::Index); |
| // Build vertex buffer for 2 vertices |
| wgpu::Buffer vertexBuffer2 = CreateBuffer(2 * kFloat32x4Stride); |
| // Build vertex buffer for 3 vertices |
| wgpu::Buffer vertexBuffer3 = CreateBuffer(3 * kFloat32x4Stride); |
| // Build vertex buffer for 4 instances |
| wgpu::Buffer instanceBuffer4 = CreateBuffer(4 * kFloat32x2Stride); |
| // Build vertex buffer for 5 instances |
| wgpu::Buffer instanceBuffer5 = CreateBuffer(5 * kFloat32x2Stride); |
| |
| // Test for setting vertex buffer for multiple times |
| { |
| wgpu::RenderPipeline pipeline = CreateBasicRenderPipelineWithInstance(); |
| |
| // Set to vertexBuffer3 and instanceBuffer5 at last |
| VertexBufferList vertexBufferList = {{0, vertexBuffer2, 0, wgpu::kWholeSize}, |
| {1, instanceBuffer4, 0, wgpu::kWholeSize}, |
| {1, instanceBuffer5, 0, wgpu::kWholeSize}, |
| {0, vertexBuffer3, 0, wgpu::kWholeSize}}; |
| |
| // For Draw, the max vertexCount is 3 and the max instanceCount is 5 |
| TestRenderPassDraw(pipeline, vertexBufferList, 3, 5, 0, 0, true); |
| TestRenderPassDraw(pipeline, vertexBufferList, 4, 5, 0, 0, false); |
| TestRenderPassDraw(pipeline, vertexBufferList, 3, 6, 0, 0, false); |
| // For DrawIndex, the max instanceCount is 5 |
| TestRenderPassDrawIndexed(pipeline, {indexBuffer12, indexFormat}, vertexBufferList, 12, |
| 5, 0, 0, 0, true); |
| TestRenderPassDrawIndexed(pipeline, {indexBuffer12, indexFormat}, vertexBufferList, 12, |
| 6, 0, 0, 0, false); |
| |
| // Set to vertexBuffer2 and instanceBuffer4 at last |
| vertexBufferList = VertexBufferList{{0, vertexBuffer3, 0, wgpu::kWholeSize}, |
| {1, instanceBuffer5, 0, wgpu::kWholeSize}, |
| {0, vertexBuffer2, 0, wgpu::kWholeSize}, |
| {1, instanceBuffer4, 0, wgpu::kWholeSize}}; |
| |
| // For Draw, the max vertexCount is 2 and the max instanceCount is 4 |
| TestRenderPassDraw(pipeline, vertexBufferList, 2, 4, 0, 0, true); |
| TestRenderPassDraw(pipeline, vertexBufferList, 3, 4, 0, 0, false); |
| TestRenderPassDraw(pipeline, vertexBufferList, 2, 5, 0, 0, false); |
| // For DrawIndex, the max instanceCount is 4 |
| TestRenderPassDrawIndexed(pipeline, {indexBuffer12, indexFormat}, vertexBufferList, 12, |
| 4, 0, 0, 0, true); |
| TestRenderPassDrawIndexed(pipeline, {indexBuffer12, indexFormat}, vertexBufferList, 12, |
| 5, 0, 0, 0, false); |
| } |
| |
| // Test for setIndexBuffer multiple times |
| { |
| wgpu::RenderPipeline pipeline = CreateBasicRenderPipeline(); |
| |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = |
| encoder.BeginRenderPass(GetBasicRenderPassDescriptor()); |
| renderPassEncoder.SetPipeline(pipeline); |
| |
| // Index buffer is set to indexBuffer12 at last |
| renderPassEncoder.SetIndexBuffer(indexBuffer11, indexFormat); |
| renderPassEncoder.SetIndexBuffer(indexBuffer12, indexFormat); |
| |
| renderPassEncoder.SetVertexBuffer(0, vertexBuffer3); |
| // It should be ok to draw 12 index |
| renderPassEncoder.DrawIndexed(12, 1, 0, 0, 0); |
| renderPassEncoder.EndPass(); |
| |
| // Expect success |
| encoder.Finish(); |
| } |
| |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = |
| encoder.BeginRenderPass(GetBasicRenderPassDescriptor()); |
| renderPassEncoder.SetPipeline(pipeline); |
| |
| // Index buffer is set to indexBuffer12 at last |
| renderPassEncoder.SetIndexBuffer(indexBuffer11, indexFormat); |
| renderPassEncoder.SetIndexBuffer(indexBuffer12, indexFormat); |
| |
| renderPassEncoder.SetVertexBuffer(0, vertexBuffer3); |
| // It should be index buffer OOB to draw 13 index |
| renderPassEncoder.DrawIndexed(13, 1, 0, 0, 0); |
| renderPassEncoder.EndPass(); |
| |
| // Expect failure |
| ASSERT_DEVICE_ERROR(encoder.Finish()); |
| } |
| |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = |
| encoder.BeginRenderPass(GetBasicRenderPassDescriptor()); |
| renderPassEncoder.SetPipeline(pipeline); |
| |
| // Index buffer is set to indexBuffer11 at last |
| renderPassEncoder.SetIndexBuffer(indexBuffer12, indexFormat); |
| renderPassEncoder.SetIndexBuffer(indexBuffer11, indexFormat); |
| |
| renderPassEncoder.SetVertexBuffer(0, vertexBuffer3); |
| // It should be ok to draw 11 index |
| renderPassEncoder.DrawIndexed(11, 1, 0, 0, 0); |
| renderPassEncoder.EndPass(); |
| |
| // Expect success |
| encoder.Finish(); |
| } |
| |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::RenderPassEncoder renderPassEncoder = |
| encoder.BeginRenderPass(GetBasicRenderPassDescriptor()); |
| renderPassEncoder.SetPipeline(pipeline); |
| |
| // Index buffer is set to indexBuffer11 at last |
| renderPassEncoder.SetIndexBuffer(indexBuffer12, indexFormat); |
| renderPassEncoder.SetIndexBuffer(indexBuffer11, indexFormat); |
| |
| renderPassEncoder.SetVertexBuffer(0, vertexBuffer3); |
| // It should be index buffer OOB to draw 12 index |
| renderPassEncoder.DrawIndexed(12, 1, 0, 0, 0); |
| renderPassEncoder.EndPass(); |
| |
| // Expect failure |
| ASSERT_DEVICE_ERROR(encoder.Finish()); |
| } |
| } |
| } |
| |
| } // anonymous namespace |