// 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 #include #include #include "dawn/native/Toggles.h" #include "dawn/native/utils/WGPUHelpers.h" #include "dawn/tests/DawnNativeTest.h" #include "dawn/tests/MockCallback.h" #include "dawn/utils/ComboRenderPipelineDescriptor.h" #include "dawn/utils/WGPUHelpers.h" #include "dawn/webgpu_cpp.h" #include "mocks/BindGroupLayoutMock.h" #include "mocks/BindGroupMock.h" #include "mocks/BufferMock.h" #include "mocks/CommandBufferMock.h" #include "mocks/ComputePipelineMock.h" #include "mocks/DawnMockTest.h" #include "mocks/DeviceMock.h" #include "mocks/ExternalTextureMock.h" #include "mocks/PipelineLayoutMock.h" #include "mocks/QuerySetMock.h" #include "mocks/RenderPipelineMock.h" #include "mocks/SamplerMock.h" #include "mocks/ShaderModuleMock.h" #include "mocks/SwapChainMock.h" #include "mocks/TextureMock.h" namespace dawn::native { namespace { using ::testing::_; using ::testing::ByMove; using ::testing::InSequence; using ::testing::Mock; using testing::MockCallback; using ::testing::NiceMock; using ::testing::Return; using ::testing::StrictMock; using ::testing::Test; static constexpr std::string_view kComputeShader = R"( @compute @workgroup_size(1) fn main() {} )"; static constexpr std::string_view kVertexShader = R"( @vertex fn main() -> @builtin(position) vec4f { return vec4f(0.0, 0.0, 0.0, 0.0); } )"; static constexpr std::string_view kFragmentShader = R"( @fragment fn main() {} )"; // Stores and scopes a raw mock object ptr expectation. This is particularly useful on objects that // are expected to be destroyed at the end of the scope. In most cases, when the validation in this // class's destructor is ran, the pointer is probably already freed. class ScopedRawPtrExpectation { public: explicit ScopedRawPtrExpectation(void* ptr) : mPtr(ptr) {} ~ScopedRawPtrExpectation() { Mock::VerifyAndClearExpectations(mPtr); } private: void* mPtr = nullptr; }; class DestroyObjectTests : public DawnMockTest { public: DestroyObjectTests() : DawnMockTest() { // Skipping validation on descriptors as coverage for validation is already present. mDeviceMock->ForceSetToggleForTesting(Toggle::SkipValidation, true); } }; TEST_F(DestroyObjectTests, BindGroupNativeExplicit) { BindGroupDescriptor desc = {}; desc.layout = mDeviceMock->GetEmptyBindGroupLayoutMock(); desc.entryCount = 0; desc.entries = nullptr; Ref bindGroupMock = AcquireRef(new BindGroupMock(mDeviceMock, &desc)); EXPECT_CALL(*bindGroupMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(bindGroupMock->IsAlive()); bindGroupMock->Destroy(); EXPECT_FALSE(bindGroupMock->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, BindGroupImplicit) { BindGroupDescriptor desc = {}; desc.layout = mDeviceMock->GetEmptyBindGroupLayoutMock(); desc.entryCount = 0; desc.entries = nullptr; Ref bindGroupMock = AcquireRef(new BindGroupMock(mDeviceMock, &desc)); EXPECT_CALL(*bindGroupMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(bindGroupMock.Get()); EXPECT_CALL(*mDeviceMock, CreateBindGroupImpl) .WillOnce(Return(ByMove(std::move(bindGroupMock)))); wgpu::BindGroup bindGroup = device.CreateBindGroup(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(bindGroup.Get())->IsAlive()); } } TEST_F(DestroyObjectTests, BindGroupLayoutNativeExplicit) { // Use an non-empty bind group layout to avoid hitting the internal empty layout in the cache. BindGroupLayoutDescriptor desc = {}; std::vector entries; entries.push_back(utils::BindingLayoutEntryInitializationHelper( 0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform)); desc.entryCount = static_cast(entries.size()); desc.entries = entries.data(); Ref bindGroupLayoutMock = AcquireRef(new BindGroupLayoutMock(mDeviceMock, &desc)); EXPECT_CALL(*bindGroupLayoutMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(bindGroupLayoutMock->IsAlive()); bindGroupLayoutMock->Destroy(); EXPECT_FALSE(bindGroupLayoutMock->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, BindGroupLayoutImplicit) { // Use an non-empty bind group layout to avoid hitting the internal empty layout in the cache. BindGroupLayoutDescriptor desc = {}; std::vector entries; entries.push_back(utils::BindingLayoutEntryInitializationHelper( 0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform)); desc.entryCount = static_cast(entries.size()); desc.entries = entries.data(); Ref bindGroupLayoutMock = AcquireRef(new BindGroupLayoutMock(mDeviceMock, &desc)); EXPECT_CALL(*bindGroupLayoutMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(bindGroupLayoutMock.Get()); EXPECT_CALL(*mDeviceMock, CreateBindGroupLayoutImpl) .WillOnce(Return(ByMove(std::move(bindGroupLayoutMock)))); wgpu::BindGroupLayout bindGroupLayout = device.CreateBindGroupLayout(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(bindGroupLayout.Get())->IsAlive()); EXPECT_TRUE(FromAPI(bindGroupLayout.Get())->IsCachedReference()); } } TEST_F(DestroyObjectTests, BufferNativeExplicit) { BufferDescriptor desc = {}; desc.size = 16; desc.usage = wgpu::BufferUsage::Uniform; Ref bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc)); EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(bufferMock->IsAlive()); bufferMock->Destroy(); EXPECT_FALSE(bufferMock->IsAlive()); } TEST_F(DestroyObjectTests, BufferApiExplicit) { BufferDescriptor desc = {}; desc.size = 16; desc.usage = wgpu::BufferUsage::Uniform; Ref bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc)); EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(ByMove(std::move(bufferMock)))); wgpu::Buffer buffer = device.CreateBuffer(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(buffer.Get())->IsAlive()); buffer.Destroy(); EXPECT_FALSE(FromAPI(buffer.Get())->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, BufferImplicit) { BufferDescriptor desc = {}; desc.size = 16; desc.usage = wgpu::BufferUsage::Uniform; Ref bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc)); EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(bufferMock.Get()); EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(ByMove(std::move(bufferMock)))); wgpu::Buffer buffer = device.CreateBuffer(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(buffer.Get())->IsAlive()); } } TEST_F(DestroyObjectTests, MappedBufferApiExplicit) { BufferDescriptor desc = {}; desc.size = 16; desc.usage = wgpu::BufferUsage::MapRead; StrictMock> cb; EXPECT_CALL(cb, Call).Times(1); Ref bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc)); { InSequence seq; EXPECT_CALL(*bufferMock.Get(), MapAsyncImpl).WillOnce([]() -> MaybeError { return {}; }); EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*bufferMock.Get(), UnmapImpl).Times(1); } { EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(ByMove(std::move(bufferMock)))); wgpu::Buffer buffer = device.CreateBuffer(ToCppAPI(&desc)); buffer.MapAsync(wgpu::MapMode::Read, 0, 16, cb.Callback(), cb.MakeUserdata(this)); device.Tick(); EXPECT_TRUE(FromAPI(buffer.Get())->IsAlive()); buffer.Destroy(); EXPECT_FALSE(FromAPI(buffer.Get())->IsAlive()); } } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, MappedBufferImplicit) { BufferDescriptor desc = {}; desc.size = 16; desc.usage = wgpu::BufferUsage::MapRead; StrictMock> cb; EXPECT_CALL(cb, Call).Times(1); Ref bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc)); { InSequence seq; EXPECT_CALL(*bufferMock.Get(), MapAsyncImpl).WillOnce([]() -> MaybeError { return {}; }); EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*bufferMock.Get(), UnmapImpl).Times(1); } { ScopedRawPtrExpectation scoped(bufferMock.Get()); EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(ByMove(std::move(bufferMock)))); wgpu::Buffer buffer = device.CreateBuffer(ToCppAPI(&desc)); buffer.MapAsync(wgpu::MapMode::Read, 0, 16, cb.Callback(), cb.MakeUserdata(this)); device.Tick(); EXPECT_TRUE(FromAPI(buffer.Get())->IsAlive()); } } TEST_F(DestroyObjectTests, CommandBufferNativeExplicit) { CommandEncoderDescriptor commandEncoderDesc = {}; Ref commandEncoder = CommandEncoder::Create(mDeviceMock, &commandEncoderDesc); CommandBufferDescriptor commandBufferDesc = {}; Ref commandBufferMock = AcquireRef(new CommandBufferMock(mDeviceMock, commandEncoder.Get(), &commandBufferDesc)); EXPECT_CALL(*commandBufferMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(commandBufferMock->IsAlive()); commandBufferMock->Destroy(); EXPECT_FALSE(commandBufferMock->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, CommandBufferImplicit) { CommandEncoderDescriptor commandEncoderDesc = {}; wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(ToCppAPI(&commandEncoderDesc)); CommandBufferDescriptor commandBufferDesc = {}; Ref commandBufferMock = AcquireRef( new CommandBufferMock(mDeviceMock, FromAPI(commandEncoder.Get()), &commandBufferDesc)); EXPECT_CALL(*commandBufferMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(commandBufferMock.Get()); EXPECT_CALL(*mDeviceMock, CreateCommandBuffer) .WillOnce(Return(ByMove(std::move(commandBufferMock)))); wgpu::CommandBuffer commandBuffer = commandEncoder.Finish(ToCppAPI(&commandBufferDesc)); EXPECT_TRUE(FromAPI(commandBuffer.Get())->IsAlive()); } } TEST_F(DestroyObjectTests, ComputePipelineNativeExplicit) { Ref csModuleMock = ShaderModuleMock::Create(mDeviceMock, kComputeShader.data()); ComputePipelineDescriptor desc = {}; desc.compute.module = csModuleMock.Get(); desc.compute.entryPoint = "main"; Ref computePipelineMock = ComputePipelineMock::Create(mDeviceMock, &desc); EXPECT_CALL(*computePipelineMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(computePipelineMock->IsAlive()); computePipelineMock->Destroy(); EXPECT_FALSE(computePipelineMock->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, ComputePipelineImplicit) { Ref csModuleMock = ShaderModuleMock::Create(mDeviceMock, kComputeShader.data()); ComputePipelineDescriptor desc = {}; desc.compute.module = csModuleMock.Get(); desc.compute.entryPoint = "main"; // Compute pipelines are initialized during their creation via the device. Ref computePipelineMock = ComputePipelineMock::Create(mDeviceMock, &desc); EXPECT_CALL(*computePipelineMock.Get(), Initialize).Times(1); EXPECT_CALL(*computePipelineMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(computePipelineMock.Get()); EXPECT_CALL(*mDeviceMock, CreateUninitializedComputePipelineImpl) .WillOnce(Return(ByMove(std::move(computePipelineMock)))); wgpu::ComputePipeline computePipeline = device.CreateComputePipeline(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(computePipeline.Get())->IsAlive()); EXPECT_TRUE(FromAPI(computePipeline.Get())->IsCachedReference()); } } TEST_F(DestroyObjectTests, ExternalTextureNativeExplicit) { TextureDescriptor textureDesc = {}; textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; textureDesc.size.width = 1; textureDesc.size.height = 1; textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureMock = AcquireRef(new NiceMock(mDeviceMock, &textureDesc)); TextureViewDescriptor textureViewDesc = {}; textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureViewMock = AcquireRef(new NiceMock(textureMock.Get(), &textureViewDesc)); ExternalTextureDescriptor desc = {}; std::array placeholderConstantArray; desc.yuvToRgbConversionMatrix = placeholderConstantArray.data(); desc.gamutConversionMatrix = placeholderConstantArray.data(); desc.srcTransferFunctionParameters = placeholderConstantArray.data(); desc.dstTransferFunctionParameters = placeholderConstantArray.data(); desc.visibleSize = {1, 1}; desc.plane0 = textureViewMock.Get(); Ref externalTextureMock = ExternalTextureMock::Create(mDeviceMock, &desc); EXPECT_CALL(*externalTextureMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(externalTextureMock->IsAlive()); externalTextureMock->Destroy(); EXPECT_FALSE(externalTextureMock->IsAlive()); } TEST_F(DestroyObjectTests, ExternalTextureApiExplicit) { TextureDescriptor textureDesc = {}; textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; textureDesc.size.width = 1; textureDesc.size.height = 1; textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureMock = AcquireRef(new NiceMock(mDeviceMock, &textureDesc)); TextureViewDescriptor textureViewDesc = {}; textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureViewMock = AcquireRef(new NiceMock(textureMock.Get(), &textureViewDesc)); ExternalTextureDescriptor desc = {}; std::array placeholderConstantArray; desc.yuvToRgbConversionMatrix = placeholderConstantArray.data(); desc.gamutConversionMatrix = placeholderConstantArray.data(); desc.srcTransferFunctionParameters = placeholderConstantArray.data(); desc.dstTransferFunctionParameters = placeholderConstantArray.data(); desc.visibleSize = {1, 1}; desc.plane0 = textureViewMock.Get(); Ref externalTextureMock = ExternalTextureMock::Create(mDeviceMock, &desc); EXPECT_CALL(*externalTextureMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*mDeviceMock, CreateExternalTextureImpl) .WillOnce(Return(ByMove(std::move(externalTextureMock)))); wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(externalTexture.Get())->IsAlive()); externalTexture.Destroy(); EXPECT_FALSE(FromAPI(externalTexture.Get())->IsAlive()); } TEST_F(DestroyObjectTests, ExternalTextureImplicit) { TextureDescriptor textureDesc = {}; textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; textureDesc.size.width = 1; textureDesc.size.height = 1; textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureMock = AcquireRef(new NiceMock(mDeviceMock, &textureDesc)); TextureViewDescriptor textureViewDesc = {}; textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureViewMock = AcquireRef(new NiceMock(textureMock.Get(), &textureViewDesc)); ExternalTextureDescriptor desc = {}; std::array placeholderConstantArray; desc.yuvToRgbConversionMatrix = placeholderConstantArray.data(); desc.gamutConversionMatrix = placeholderConstantArray.data(); desc.srcTransferFunctionParameters = placeholderConstantArray.data(); desc.dstTransferFunctionParameters = placeholderConstantArray.data(); desc.visibleSize = {1, 1}; desc.plane0 = textureViewMock.Get(); Ref externalTextureMock = ExternalTextureMock::Create(mDeviceMock, &desc); EXPECT_CALL(*externalTextureMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(externalTextureMock.Get()); EXPECT_CALL(*mDeviceMock, CreateExternalTextureImpl) .WillOnce(Return(ByMove(std::move(externalTextureMock)))); wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(externalTexture.Get())->IsAlive()); } } TEST_F(DestroyObjectTests, PipelineLayoutNativeExplicit) { PipelineLayoutDescriptor desc = {}; std::vector bindGroupLayouts; bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayoutMock()); desc.bindGroupLayoutCount = static_cast(bindGroupLayouts.size()); desc.bindGroupLayouts = bindGroupLayouts.data(); Ref pipelineLayoutMock = AcquireRef(new PipelineLayoutMock(mDeviceMock, &desc)); EXPECT_CALL(*pipelineLayoutMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(pipelineLayoutMock->IsAlive()); pipelineLayoutMock->Destroy(); EXPECT_FALSE(pipelineLayoutMock->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, PipelineLayoutImplicit) { PipelineLayoutDescriptor desc = {}; std::vector bindGroupLayouts; bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayoutMock()); desc.bindGroupLayoutCount = static_cast(bindGroupLayouts.size()); desc.bindGroupLayouts = bindGroupLayouts.data(); Ref pipelineLayoutMock = AcquireRef(new PipelineLayoutMock(mDeviceMock, &desc)); EXPECT_CALL(*pipelineLayoutMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(pipelineLayoutMock.Get()); EXPECT_CALL(*mDeviceMock, CreatePipelineLayoutImpl) .WillOnce(Return(ByMove(std::move(pipelineLayoutMock)))); wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(pipelineLayout.Get())->IsAlive()); EXPECT_TRUE(FromAPI(pipelineLayout.Get())->IsCachedReference()); } } TEST_F(DestroyObjectTests, QuerySetNativeExplicit) { QuerySetDescriptor desc = {}; desc.type = wgpu::QueryType::Occlusion; desc.count = 1; Ref querySetMock = AcquireRef(new QuerySetMock(mDeviceMock, &desc)); EXPECT_CALL(*querySetMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(querySetMock->IsAlive()); querySetMock->Destroy(); EXPECT_FALSE(querySetMock->IsAlive()); } TEST_F(DestroyObjectTests, QuerySetApiExplicit) { QuerySetDescriptor desc = {}; desc.type = wgpu::QueryType::Occlusion; desc.count = 1; Ref querySetMock = AcquireRef(new QuerySetMock(mDeviceMock, &desc)); EXPECT_CALL(*querySetMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*mDeviceMock, CreateQuerySetImpl).WillOnce(Return(ByMove(std::move(querySetMock)))); wgpu::QuerySet querySet = device.CreateQuerySet(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(querySet.Get())->IsAlive()); querySet.Destroy(); EXPECT_FALSE(FromAPI(querySet.Get())->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, QuerySetImplicit) { QuerySetDescriptor desc = {}; desc.type = wgpu::QueryType::Occlusion; desc.count = 1; Ref querySetMock = AcquireRef(new QuerySetMock(mDeviceMock, &desc)); EXPECT_CALL(*querySetMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(querySetMock.Get()); EXPECT_CALL(*mDeviceMock, CreateQuerySetImpl) .WillOnce(Return(ByMove(std::move(querySetMock)))); wgpu::QuerySet querySet = device.CreateQuerySet(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(querySet.Get())->IsAlive()); } } TEST_F(DestroyObjectTests, RenderPipelineNativeExplicit) { Ref vsModuleMock = ShaderModuleMock::Create(mDeviceMock, kVertexShader.data()); RenderPipelineDescriptor desc = {}; desc.vertex.module = vsModuleMock.Get(); desc.vertex.entryPoint = "main"; Ref renderPipelineMock = RenderPipelineMock::Create(mDeviceMock, &desc); EXPECT_CALL(*renderPipelineMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(renderPipelineMock->IsAlive()); renderPipelineMock->Destroy(); EXPECT_FALSE(renderPipelineMock->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, RenderPipelineImplicit) { Ref vsModuleMock = ShaderModuleMock::Create(mDeviceMock, kVertexShader.data()); RenderPipelineDescriptor desc = {}; desc.vertex.module = vsModuleMock.Get(); desc.vertex.entryPoint = "main"; // Render pipelines are initialized during their creation via the device. Ref renderPipelineMock = RenderPipelineMock::Create(mDeviceMock, &desc); EXPECT_CALL(*renderPipelineMock.Get(), Initialize).Times(1); EXPECT_CALL(*renderPipelineMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(renderPipelineMock.Get()); EXPECT_CALL(*mDeviceMock, CreateUninitializedRenderPipelineImpl) .WillOnce(Return(ByMove(std::move(renderPipelineMock)))); wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(renderPipeline.Get())->IsAlive()); EXPECT_TRUE(FromAPI(renderPipeline.Get())->IsCachedReference()); } } TEST_F(DestroyObjectTests, SamplerNativeExplicit) { SamplerDescriptor desc = {}; Ref samplerMock = AcquireRef(new SamplerMock(mDeviceMock, &desc)); EXPECT_CALL(*samplerMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(samplerMock->IsAlive()); samplerMock->Destroy(); EXPECT_FALSE(samplerMock->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, SamplerImplicit) { SamplerDescriptor desc = {}; Ref samplerMock = AcquireRef(new SamplerMock(mDeviceMock, &desc)); EXPECT_CALL(*samplerMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(samplerMock.Get()); EXPECT_CALL(*mDeviceMock, CreateSamplerImpl) .WillOnce(Return(ByMove(std::move(samplerMock)))); wgpu::Sampler sampler = device.CreateSampler(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(sampler.Get())->IsAlive()); EXPECT_TRUE(FromAPI(sampler.Get())->IsCachedReference()); } } TEST_F(DestroyObjectTests, ShaderModuleNativeExplicit) { Ref shaderModuleMock = ShaderModuleMock::Create(mDeviceMock, kVertexShader.data()); EXPECT_CALL(*shaderModuleMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(shaderModuleMock->IsAlive()); shaderModuleMock->Destroy(); EXPECT_FALSE(shaderModuleMock->IsAlive()); } TEST_F(DestroyObjectTests, ShaderModuleImplicit) { ShaderModuleWGSLDescriptor wgslDesc = {}; wgslDesc.code = kVertexShader.data(); ShaderModuleDescriptor desc = {}; desc.nextInChain = &wgslDesc; Ref shaderModuleMock = ShaderModuleMock::Create(mDeviceMock, &desc); EXPECT_CALL(*shaderModuleMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(shaderModuleMock.Get()); EXPECT_CALL(*mDeviceMock, CreateShaderModuleImpl) .WillOnce(Return(ByMove(std::move(shaderModuleMock)))); wgpu::ShaderModule shaderModule = device.CreateShaderModule(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(shaderModule.Get())->IsAlive()); } } TEST_F(DestroyObjectTests, TextureNativeExplicit) { TextureDescriptor desc = {}; desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; desc.size.width = 1; desc.size.height = 1; desc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureMock = AcquireRef(new TextureMock(mDeviceMock, &desc)); EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(textureMock->IsAlive()); textureMock->Destroy(); EXPECT_FALSE(textureMock->IsAlive()); } TEST_F(DestroyObjectTests, TextureApiExplicit) { TextureDescriptor desc = {}; desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; desc.size.width = 1; desc.size.height = 1; desc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureMock = AcquireRef(new TextureMock(mDeviceMock, &desc)); EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*mDeviceMock, CreateTextureImpl).WillOnce(Return(ByMove(std::move(textureMock)))); wgpu::Texture texture = device.CreateTexture(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(texture.Get())->IsAlive()); texture.Destroy(); EXPECT_FALSE(FromAPI(texture.Get())->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, TextureImplicit) { TextureDescriptor desc = {}; desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; desc.size.width = 1; desc.size.height = 1; desc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureMock = AcquireRef(new TextureMock(mDeviceMock, &desc)); EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(textureMock.Get()); EXPECT_CALL(*mDeviceMock, CreateTextureImpl) .WillOnce(Return(ByMove(std::move(textureMock)))); wgpu::Texture texture = device.CreateTexture(ToCppAPI(&desc)); EXPECT_TRUE(FromAPI(texture.Get())->IsAlive()); } } TEST_F(DestroyObjectTests, TextureViewNativeExplicit) { TextureDescriptor textureDesc = {}; textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; textureDesc.size.width = 1; textureDesc.size.height = 1; textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureMock = AcquireRef(new NiceMock(mDeviceMock, &textureDesc)); TextureViewDescriptor desc = {}; desc.format = wgpu::TextureFormat::RGBA8Unorm; { // Explicitly destroy the texture view. Ref textureViewMock = AcquireRef(new TextureViewMock(textureMock.Get(), &desc)); EXPECT_CALL(*textureViewMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(textureViewMock->IsAlive()); textureViewMock->Destroy(); EXPECT_FALSE(textureViewMock->IsAlive()); } { // Destroying the owning texture should cause the view to be destroyed as well. Ref textureViewMock = AcquireRef(new TextureViewMock(textureMock.Get(), &desc)); EXPECT_CALL(*textureViewMock.Get(), DestroyImpl).Times(1); EXPECT_TRUE(textureViewMock->IsAlive()); textureMock->Destroy(); EXPECT_FALSE(textureViewMock->IsAlive()); } } TEST_F(DestroyObjectTests, TextureViewApiExplicit) { TextureDescriptor textureDesc = {}; textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; textureDesc.size.width = 1; textureDesc.size.height = 1; textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureMock = AcquireRef(new NiceMock(mDeviceMock, &textureDesc)); TextureViewDescriptor viewDesc = {}; viewDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureViewMock = AcquireRef(new TextureViewMock(textureMock.Get(), &viewDesc)); EXPECT_CALL(*textureViewMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*mDeviceMock, CreateTextureViewImpl(textureMock.Get(), _)) .WillOnce(Return(ByMove(std::move(textureViewMock)))); EXPECT_CALL(*mDeviceMock, CreateTextureImpl).WillOnce(Return(ByMove(std::move(textureMock)))); wgpu::Texture texture = device.CreateTexture(ToCppAPI(&textureDesc)); wgpu::TextureView textureView = texture.CreateView(ToCppAPI(&viewDesc)); EXPECT_TRUE(FromAPI(textureView.Get())->IsAlive()); texture.Destroy(); EXPECT_FALSE(FromAPI(textureView.Get())->IsAlive()); } // If the reference count on API objects reach 0, they should delete themselves. Note that GTest // will also complain if there is a memory leak. TEST_F(DestroyObjectTests, TextureViewImplicit) { TextureDescriptor textureDesc = {}; textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; textureDesc.size.width = 1; textureDesc.size.height = 1; textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureMock = AcquireRef(new NiceMock(mDeviceMock, &textureDesc)); TextureViewDescriptor viewDesc = {}; viewDesc.format = wgpu::TextureFormat::RGBA8Unorm; Ref textureViewMock = AcquireRef(new TextureViewMock(textureMock.Get(), &viewDesc)); EXPECT_CALL(*textureViewMock.Get(), DestroyImpl).Times(1); { ScopedRawPtrExpectation scoped(textureViewMock.Get()); EXPECT_CALL(*mDeviceMock, CreateTextureViewImpl(textureMock.Get(), _)) .WillOnce(Return(ByMove(std::move(textureViewMock)))); EXPECT_CALL(*mDeviceMock, CreateTextureImpl) .WillOnce(Return(ByMove(std::move(textureMock)))); wgpu::Texture texture = device.CreateTexture(ToCppAPI(&textureDesc)); wgpu::TextureView textureView = texture.CreateView(ToCppAPI(&viewDesc)); EXPECT_TRUE(FromAPI(textureView.Get())->IsAlive()); } } // Destroying the objects on the device explicitly should result in all created objects being // destroyed in order. TEST_F(DestroyObjectTests, DestroyObjectsApiExplicit) { Ref bindGroupMock; wgpu::BindGroup bindGroup; { BindGroupDescriptor desc = {}; desc.layout = mDeviceMock->GetEmptyBindGroupLayoutMock(); desc.entryCount = 0; desc.entries = nullptr; ScopedRawPtrExpectation scoped(mDeviceMock); bindGroupMock = AcquireRef(new BindGroupMock(mDeviceMock, &desc)); EXPECT_CALL(*mDeviceMock, CreateBindGroupImpl).WillOnce(Return(bindGroupMock)); bindGroup = device.CreateBindGroup(ToCppAPI(&desc)); } Ref bindGroupLayoutMock; wgpu::BindGroupLayout bindGroupLayout; { // Use an non-empty bind group layout to avoid hitting the internal empty layout in the // cache. BindGroupLayoutDescriptor desc = {}; std::vector entries; entries.push_back(utils::BindingLayoutEntryInitializationHelper( 0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform)); desc.entryCount = static_cast(entries.size()); desc.entries = entries.data(); ScopedRawPtrExpectation scoped(mDeviceMock); bindGroupLayoutMock = AcquireRef(new BindGroupLayoutMock(mDeviceMock, &desc)); EXPECT_CALL(*mDeviceMock, CreateBindGroupLayoutImpl).WillOnce(Return(bindGroupLayoutMock)); bindGroupLayout = device.CreateBindGroupLayout(ToCppAPI(&desc)); } Ref csModuleMock; wgpu::ShaderModule csModule; { ShaderModuleWGSLDescriptor wgslDesc = {}; wgslDesc.code = kComputeShader.data(); ShaderModuleDescriptor desc = {}; desc.nextInChain = &wgslDesc; ScopedRawPtrExpectation scoped(mDeviceMock); csModuleMock = ShaderModuleMock::Create(mDeviceMock, &desc); EXPECT_CALL(*mDeviceMock, CreateShaderModuleImpl).WillOnce(Return(csModuleMock)); csModule = device.CreateShaderModule(ToCppAPI(&desc)); } Ref vsModuleMock; wgpu::ShaderModule vsModule; { ShaderModuleWGSLDescriptor wgslDesc = {}; wgslDesc.code = kVertexShader.data(); ShaderModuleDescriptor desc = {}; desc.nextInChain = &wgslDesc; ScopedRawPtrExpectation scoped(mDeviceMock); vsModuleMock = ShaderModuleMock::Create(mDeviceMock, &desc); EXPECT_CALL(*mDeviceMock, CreateShaderModuleImpl).WillOnce(Return(vsModuleMock)); vsModule = device.CreateShaderModule(ToCppAPI(&desc)); } Ref bufferMock; wgpu::Buffer buffer; { BufferDescriptor desc = {}; desc.size = 16; desc.usage = wgpu::BufferUsage::Uniform; ScopedRawPtrExpectation scoped(mDeviceMock); bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc)); EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(bufferMock)); buffer = device.CreateBuffer(ToCppAPI(&desc)); } Ref commandBufferMock; wgpu::CommandBuffer commandBuffer; { CommandEncoderDescriptor commandEncoderDesc = {}; wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(ToCppAPI(&commandEncoderDesc)); CommandBufferDescriptor commandBufferDesc = {}; ScopedRawPtrExpectation scoped(mDeviceMock); commandBufferMock = AcquireRef( new CommandBufferMock(mDeviceMock, FromAPI(commandEncoder.Get()), &commandBufferDesc)); EXPECT_CALL(*mDeviceMock, CreateCommandBuffer).WillOnce(Return(commandBufferMock)); commandBuffer = commandEncoder.Finish(ToCppAPI(&commandBufferDesc)); } Ref computePipelineMock; wgpu::ComputePipeline computePipeline; { ComputePipelineDescriptor desc = {}; desc.compute.module = csModuleMock.Get(); desc.compute.entryPoint = "main"; ScopedRawPtrExpectation scoped(mDeviceMock); computePipelineMock = ComputePipelineMock::Create(mDeviceMock, &desc); EXPECT_CALL(*computePipelineMock.Get(), Initialize).Times(1); EXPECT_CALL(*mDeviceMock, CreateUninitializedComputePipelineImpl) .WillOnce(Return(computePipelineMock)); computePipeline = device.CreateComputePipeline(ToCppAPI(&desc)); } Ref pipelineLayoutMock; wgpu::PipelineLayout pipelineLayout; { PipelineLayoutDescriptor desc = {}; std::vector bindGroupLayouts; bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayoutMock()); desc.bindGroupLayoutCount = static_cast(bindGroupLayouts.size()); desc.bindGroupLayouts = bindGroupLayouts.data(); ScopedRawPtrExpectation scoped(mDeviceMock); pipelineLayoutMock = AcquireRef(new PipelineLayoutMock(mDeviceMock, &desc)); EXPECT_CALL(*mDeviceMock, CreatePipelineLayoutImpl).WillOnce(Return(pipelineLayoutMock)); pipelineLayout = device.CreatePipelineLayout(ToCppAPI(&desc)); } Ref querySetMock; wgpu::QuerySet querySet; { QuerySetDescriptor desc = {}; desc.type = wgpu::QueryType::Occlusion; desc.count = 1; ScopedRawPtrExpectation scoped(mDeviceMock); querySetMock = AcquireRef(new QuerySetMock(mDeviceMock, &desc)); EXPECT_CALL(*mDeviceMock, CreateQuerySetImpl).WillOnce(Return(querySetMock)); querySet = device.CreateQuerySet(ToCppAPI(&desc)); } Ref renderPipelineMock; wgpu::RenderPipeline renderPipeline; { RenderPipelineDescriptor desc = {}; desc.vertex.module = vsModuleMock.Get(); desc.vertex.entryPoint = "main"; ScopedRawPtrExpectation scoped(mDeviceMock); renderPipelineMock = RenderPipelineMock::Create(mDeviceMock, &desc); EXPECT_CALL(*renderPipelineMock.Get(), Initialize).Times(1); EXPECT_CALL(*mDeviceMock, CreateUninitializedRenderPipelineImpl) .WillOnce(Return(renderPipelineMock)); renderPipeline = device.CreateRenderPipeline(ToCppAPI(&desc)); } Ref samplerMock; wgpu::Sampler sampler; { SamplerDescriptor desc = {}; ScopedRawPtrExpectation scoped(mDeviceMock); samplerMock = AcquireRef(new SamplerMock(mDeviceMock, &desc)); EXPECT_CALL(*mDeviceMock, CreateSamplerImpl).WillOnce(Return(samplerMock)); sampler = device.CreateSampler(ToCppAPI(&desc)); } Ref textureMock; wgpu::Texture texture; { TextureDescriptor desc = {}; desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; desc.size.width = 1; desc.size.height = 1; desc.format = wgpu::TextureFormat::RGBA8Unorm; ScopedRawPtrExpectation scoped(mDeviceMock); textureMock = AcquireRef(new NiceMock(mDeviceMock, &desc)); EXPECT_CALL(*mDeviceMock, CreateTextureImpl).WillOnce(Return(textureMock)); texture = device.CreateTexture(ToCppAPI(&desc)); } Ref textureViewMock; wgpu::TextureView textureView; { TextureViewDescriptor desc = {}; desc.format = wgpu::TextureFormat::RGBA8Unorm; ScopedRawPtrExpectation scoped(mDeviceMock); textureViewMock = AcquireRef(new TextureViewMock(textureMock.Get(), &desc)); EXPECT_CALL(*mDeviceMock, CreateTextureViewImpl).WillOnce(Return(textureViewMock)); textureView = texture.CreateView(ToCppAPI(&desc)); } Ref externalTextureMock; wgpu::ExternalTexture externalTexture; { ExternalTextureDescriptor desc = {}; std::array placeholderConstantArray; desc.yuvToRgbConversionMatrix = placeholderConstantArray.data(); desc.gamutConversionMatrix = placeholderConstantArray.data(); desc.srcTransferFunctionParameters = placeholderConstantArray.data(); desc.dstTransferFunctionParameters = placeholderConstantArray.data(); desc.visibleSize = {1, 1}; desc.plane0 = textureViewMock.Get(); ScopedRawPtrExpectation scoped(mDeviceMock); externalTextureMock = ExternalTextureMock::Create(mDeviceMock, &desc); EXPECT_CALL(*mDeviceMock, CreateExternalTextureImpl).WillOnce(Return(externalTextureMock)); externalTexture = device.CreateExternalTexture(ToCppAPI(&desc)); } { InSequence seq; EXPECT_CALL(*commandBufferMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*renderPipelineMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*computePipelineMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*pipelineLayoutMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*bindGroupMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*bindGroupLayoutMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*vsModuleMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*csModuleMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*externalTextureMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*textureViewMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*querySetMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*samplerMock.Get(), DestroyImpl).Times(1); EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1); } EXPECT_TRUE(FromAPI(bindGroup.Get())->IsAlive()); EXPECT_TRUE(FromAPI(bindGroupLayout.Get())->IsAlive()); EXPECT_TRUE(FromAPI(buffer.Get())->IsAlive()); EXPECT_TRUE(FromAPI(commandBuffer.Get())->IsAlive()); EXPECT_TRUE(FromAPI(computePipeline.Get())->IsAlive()); EXPECT_TRUE(FromAPI(externalTexture.Get())->IsAlive()); EXPECT_TRUE(FromAPI(pipelineLayout.Get())->IsAlive()); EXPECT_TRUE(FromAPI(querySet.Get())->IsAlive()); EXPECT_TRUE(FromAPI(renderPipeline.Get())->IsAlive()); EXPECT_TRUE(FromAPI(sampler.Get())->IsAlive()); EXPECT_TRUE(FromAPI(vsModule.Get())->IsAlive()); EXPECT_TRUE(FromAPI(csModule.Get())->IsAlive()); EXPECT_TRUE(FromAPI(texture.Get())->IsAlive()); EXPECT_TRUE(FromAPI(textureView.Get())->IsAlive()); device.Destroy(); EXPECT_FALSE(FromAPI(bindGroup.Get())->IsAlive()); EXPECT_FALSE(FromAPI(bindGroupLayout.Get())->IsAlive()); EXPECT_FALSE(FromAPI(buffer.Get())->IsAlive()); EXPECT_FALSE(FromAPI(commandBuffer.Get())->IsAlive()); EXPECT_FALSE(FromAPI(computePipeline.Get())->IsAlive()); EXPECT_FALSE(FromAPI(externalTexture.Get())->IsAlive()); EXPECT_FALSE(FromAPI(pipelineLayout.Get())->IsAlive()); EXPECT_FALSE(FromAPI(querySet.Get())->IsAlive()); EXPECT_FALSE(FromAPI(renderPipeline.Get())->IsAlive()); EXPECT_FALSE(FromAPI(sampler.Get())->IsAlive()); EXPECT_FALSE(FromAPI(vsModule.Get())->IsAlive()); EXPECT_FALSE(FromAPI(csModule.Get())->IsAlive()); EXPECT_FALSE(FromAPI(texture.Get())->IsAlive()); EXPECT_FALSE(FromAPI(textureView.Get())->IsAlive()); } class DestroyObjectRegressionTests : public DawnNativeTest {}; // LastRefInCommand* tests are regression test(s) for https://crbug.com/chromium/1318792. The // regression tests here are not exhuastive. In order to have an exhuastive test case for this // class of failures, we should test every possible command with the commands holding the last // references (or as last as possible) of their needed objects. For now, including simple cases // including a stripped-down case from the original bug. // Tests that when a RenderPipeline's last reference is held in a command in an unfinished // CommandEncoder, that destroying the device still works as expected (and does not cause // double-free). TEST_F(DestroyObjectRegressionTests, LastRefInCommandRenderPipeline) { ::utils::BasicRenderPass pass = ::utils::CreateBasicRenderPass(device, 1, 1); ::utils::ComboRenderPassDescriptor passDesc{}; wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder renderEncoder = encoder.BeginRenderPass(&pass.renderPassInfo); ::utils::ComboRenderPipelineDescriptor pipelineDesc; pipelineDesc.cTargets[0].writeMask = wgpu::ColorWriteMask::None; pipelineDesc.vertex.module = ::utils::CreateShaderModule(device, kVertexShader.data()); pipelineDesc.vertex.entryPoint = "main"; pipelineDesc.cFragment.module = ::utils::CreateShaderModule(device, kFragmentShader.data()); pipelineDesc.cFragment.entryPoint = "main"; renderEncoder.SetPipeline(device.CreateRenderPipeline(&pipelineDesc)); device.Destroy(); } // Tests that when a ComputePipelines's last reference is held in a command in an unfinished // CommandEncoder, that destroying the device still works as expected (and does not cause // double-free). TEST_F(DestroyObjectRegressionTests, LastRefInCommandComputePipeline) { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder computeEncoder = encoder.BeginComputePass(); wgpu::ComputePipelineDescriptor pipelineDesc; pipelineDesc.compute.module = ::utils::CreateShaderModule(device, kComputeShader.data()); pipelineDesc.compute.entryPoint = "main"; computeEncoder.SetPipeline(device.CreateComputePipeline(&pipelineDesc)); device.Destroy(); } } // namespace } // namespace dawn::native