blob: dd57ed547965c6100ff79e75d6c35a2adb1abb4c [file] [log] [blame]
// 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 <gtest/gtest.h>
#include "dawn/native/Toggles.h"
#include "dawn/tests/DawnNativeTest.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
#include "mocks/BindGroupLayoutMock.h"
#include "mocks/BindGroupMock.h"
#include "mocks/BufferMock.h"
#include "mocks/CommandBufferMock.h"
#include "mocks/ComputePipelineMock.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::Return;
using ::testing::Test;
class DestroyObjectTests : public Test {
public:
DestroyObjectTests() : Test() {
// Skipping validation on descriptors as coverage for validation is already present.
mDevice.SetToggle(Toggle::SkipValidation, true);
}
Ref<TextureMock> GetTexture() {
if (mTexture != nullptr) {
return mTexture;
}
mTexture = AcquireRef(new TextureMock(&mDevice, TextureBase::TextureState::OwnedInternal));
EXPECT_CALL(*mTexture.Get(), DestroyImpl).Times(1);
return mTexture;
}
Ref<PipelineLayoutMock> GetPipelineLayout() {
if (mPipelineLayout != nullptr) {
return mPipelineLayout;
}
mPipelineLayout = AcquireRef(new PipelineLayoutMock(&mDevice));
EXPECT_CALL(*mPipelineLayout.Get(), DestroyImpl).Times(1);
return mPipelineLayout;
}
Ref<ShaderModuleMock> GetVertexShaderModule() {
if (mVsModule != nullptr) {
return mVsModule;
}
DAWN_TRY_ASSIGN_WITH_CLEANUP(
mVsModule, ShaderModuleMock::Create(&mDevice, R"(
@stage(vertex) fn main() -> @builtin(position) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
})"),
{ ASSERT(false); }, mVsModule);
EXPECT_CALL(*mVsModule.Get(), DestroyImpl).Times(1);
return mVsModule;
}
Ref<ShaderModuleMock> GetComputeShaderModule() {
if (mCsModule != nullptr) {
return mCsModule;
}
DAWN_TRY_ASSIGN_WITH_CLEANUP(
mCsModule, ShaderModuleMock::Create(&mDevice, R"(
@stage(compute) @workgroup_size(1) fn main() {
})"),
{ ASSERT(false); }, mCsModule);
EXPECT_CALL(*mCsModule.Get(), DestroyImpl).Times(1);
return mCsModule;
}
protected:
DeviceMock mDevice;
// The following lazy-initialized objects are used to facilitate creation of dependent
// objects under test.
Ref<TextureMock> mTexture;
Ref<PipelineLayoutMock> mPipelineLayout;
Ref<ShaderModuleMock> mVsModule;
Ref<ShaderModuleMock> mCsModule;
};
TEST_F(DestroyObjectTests, BindGroupExplicit) {
BindGroupMock bindGroupMock(&mDevice);
EXPECT_CALL(bindGroupMock, 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) {
BindGroupMock* bindGroupMock = new BindGroupMock(&mDevice);
EXPECT_CALL(*bindGroupMock, DestroyImpl).Times(1);
{
BindGroupDescriptor desc = {};
Ref<BindGroupBase> bindGroup;
EXPECT_CALL(mDevice, CreateBindGroupImpl)
.WillOnce(Return(ByMove(AcquireRef(bindGroupMock))));
DAWN_ASSERT_AND_ASSIGN(bindGroup, mDevice.CreateBindGroup(&desc));
EXPECT_TRUE(bindGroup->IsAlive());
}
}
TEST_F(DestroyObjectTests, BindGroupLayoutExplicit) {
BindGroupLayoutMock bindGroupLayoutMock(&mDevice);
EXPECT_CALL(bindGroupLayoutMock, 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) {
BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&mDevice);
EXPECT_CALL(*bindGroupLayoutMock, DestroyImpl).Times(1);
{
BindGroupLayoutDescriptor desc = {};
Ref<BindGroupLayoutBase> bindGroupLayout;
EXPECT_CALL(mDevice, CreateBindGroupLayoutImpl)
.WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock))));
DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, mDevice.CreateBindGroupLayout(&desc));
EXPECT_TRUE(bindGroupLayout->IsAlive());
EXPECT_TRUE(bindGroupLayout->IsCachedReference());
}
}
TEST_F(DestroyObjectTests, BufferExplicit) {
{
BufferMock bufferMock(&mDevice, BufferBase::BufferState::Unmapped);
EXPECT_CALL(bufferMock, DestroyImpl).Times(1);
EXPECT_TRUE(bufferMock.IsAlive());
bufferMock.Destroy();
EXPECT_FALSE(bufferMock.IsAlive());
}
{
BufferMock bufferMock(&mDevice, BufferBase::BufferState::Mapped);
{
InSequence seq;
EXPECT_CALL(bufferMock, DestroyImpl).Times(1);
EXPECT_CALL(bufferMock, UnmapImpl).Times(1);
}
EXPECT_TRUE(bufferMock.IsAlive());
bufferMock.Destroy();
EXPECT_FALSE(bufferMock.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) {
{
BufferMock* bufferMock = new BufferMock(&mDevice, BufferBase::BufferState::Unmapped);
EXPECT_CALL(*bufferMock, DestroyImpl).Times(1);
{
BufferDescriptor desc = {};
Ref<BufferBase> buffer;
EXPECT_CALL(mDevice, CreateBufferImpl).WillOnce(Return(ByMove(AcquireRef(bufferMock))));
DAWN_ASSERT_AND_ASSIGN(buffer, mDevice.CreateBuffer(&desc));
EXPECT_TRUE(buffer->IsAlive());
}
}
{
BufferMock* bufferMock = new BufferMock(&mDevice, BufferBase::BufferState::Mapped);
{
InSequence seq;
EXPECT_CALL(*bufferMock, DestroyImpl).Times(1);
EXPECT_CALL(*bufferMock, UnmapImpl).Times(1);
}
{
BufferDescriptor desc = {};
Ref<BufferBase> buffer;
EXPECT_CALL(mDevice, CreateBufferImpl).WillOnce(Return(ByMove(AcquireRef(bufferMock))));
DAWN_ASSERT_AND_ASSIGN(buffer, mDevice.CreateBuffer(&desc));
EXPECT_TRUE(buffer->IsAlive());
}
}
}
TEST_F(DestroyObjectTests, CommandBufferExplicit) {
CommandBufferMock commandBufferMock(&mDevice);
EXPECT_CALL(commandBufferMock, 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) {
CommandBufferMock* commandBufferMock = new CommandBufferMock(&mDevice);
EXPECT_CALL(*commandBufferMock, DestroyImpl).Times(1);
{
CommandBufferDescriptor desc = {};
Ref<CommandBufferBase> commandBuffer;
EXPECT_CALL(mDevice, CreateCommandBuffer)
.WillOnce(Return(ByMove(AcquireRef(commandBufferMock))));
DAWN_ASSERT_AND_ASSIGN(commandBuffer, mDevice.CreateCommandBuffer(nullptr, &desc));
EXPECT_TRUE(commandBuffer->IsAlive());
}
}
TEST_F(DestroyObjectTests, ComputePipelineExplicit) {
ComputePipelineMock computePipelineMock(&mDevice);
EXPECT_CALL(computePipelineMock, 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) {
// ComputePipelines usually set their hash values at construction, but the mock does not, so
// we set it here.
constexpr size_t hash = 0x12345;
ComputePipelineMock* computePipelineMock = new ComputePipelineMock(&mDevice);
computePipelineMock->SetContentHash(hash);
ON_CALL(*computePipelineMock, ComputeContentHash).WillByDefault(Return(hash));
// Compute pipelines are initialized during their creation via the device.
EXPECT_CALL(*computePipelineMock, Initialize).Times(1);
EXPECT_CALL(*computePipelineMock, DestroyImpl).Times(1);
{
ComputePipelineDescriptor desc = {};
desc.layout = GetPipelineLayout().Get();
desc.compute.module = GetComputeShaderModule().Get();
Ref<ComputePipelineBase> computePipeline;
EXPECT_CALL(mDevice, CreateUninitializedComputePipelineImpl)
.WillOnce(Return(ByMove(AcquireRef(computePipelineMock))));
DAWN_ASSERT_AND_ASSIGN(computePipeline, mDevice.CreateComputePipeline(&desc));
EXPECT_TRUE(computePipeline->IsAlive());
EXPECT_TRUE(computePipeline->IsCachedReference());
}
}
TEST_F(DestroyObjectTests, ExternalTextureExplicit) {
ExternalTextureMock externalTextureMock(&mDevice);
EXPECT_CALL(externalTextureMock, DestroyImpl).Times(1);
EXPECT_TRUE(externalTextureMock.IsAlive());
externalTextureMock.Destroy();
EXPECT_FALSE(externalTextureMock.IsAlive());
}
TEST_F(DestroyObjectTests, ExternalTextureImplicit) {
ExternalTextureMock* externalTextureMock = new ExternalTextureMock(&mDevice);
EXPECT_CALL(*externalTextureMock, DestroyImpl).Times(1);
{
ExternalTextureDescriptor desc = {};
Ref<ExternalTextureBase> externalTexture;
EXPECT_CALL(mDevice, CreateExternalTextureImpl)
.WillOnce(Return(ByMove(AcquireRef(externalTextureMock))));
DAWN_ASSERT_AND_ASSIGN(externalTexture, mDevice.CreateExternalTextureImpl(&desc));
EXPECT_TRUE(externalTexture->IsAlive());
}
}
TEST_F(DestroyObjectTests, PipelineLayoutExplicit) {
PipelineLayoutMock pipelineLayoutMock(&mDevice);
EXPECT_CALL(pipelineLayoutMock, 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) {
PipelineLayoutMock* pipelineLayoutMock = new PipelineLayoutMock(&mDevice);
EXPECT_CALL(*pipelineLayoutMock, DestroyImpl).Times(1);
{
PipelineLayoutDescriptor desc = {};
Ref<PipelineLayoutBase> pipelineLayout;
EXPECT_CALL(mDevice, CreatePipelineLayoutImpl)
.WillOnce(Return(ByMove(AcquireRef(pipelineLayoutMock))));
DAWN_ASSERT_AND_ASSIGN(pipelineLayout, mDevice.CreatePipelineLayout(&desc));
EXPECT_TRUE(pipelineLayout->IsAlive());
EXPECT_TRUE(pipelineLayout->IsCachedReference());
}
}
TEST_F(DestroyObjectTests, QuerySetExplicit) {
QuerySetMock querySetMock(&mDevice);
EXPECT_CALL(querySetMock, DestroyImpl).Times(1);
EXPECT_TRUE(querySetMock.IsAlive());
querySetMock.Destroy();
EXPECT_FALSE(querySetMock.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) {
QuerySetMock* querySetMock = new QuerySetMock(&mDevice);
EXPECT_CALL(*querySetMock, DestroyImpl).Times(1);
{
QuerySetDescriptor desc = {};
Ref<QuerySetBase> querySet;
EXPECT_CALL(mDevice, CreateQuerySetImpl).WillOnce(Return(ByMove(AcquireRef(querySetMock))));
DAWN_ASSERT_AND_ASSIGN(querySet, mDevice.CreateQuerySet(&desc));
EXPECT_TRUE(querySet->IsAlive());
}
}
TEST_F(DestroyObjectTests, RenderPipelineExplicit) {
RenderPipelineMock renderPipelineMock(&mDevice);
EXPECT_CALL(renderPipelineMock, 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) {
// RenderPipelines usually set their hash values at construction, but the mock does not, so
// we set it here.
constexpr size_t hash = 0x12345;
RenderPipelineMock* renderPipelineMock = new RenderPipelineMock(&mDevice);
renderPipelineMock->SetContentHash(hash);
ON_CALL(*renderPipelineMock, ComputeContentHash).WillByDefault(Return(hash));
// Render pipelines are initialized during their creation via the device.
EXPECT_CALL(*renderPipelineMock, Initialize).Times(1);
EXPECT_CALL(*renderPipelineMock, DestroyImpl).Times(1);
{
RenderPipelineDescriptor desc = {};
desc.layout = GetPipelineLayout().Get();
desc.vertex.module = GetVertexShaderModule().Get();
Ref<RenderPipelineBase> renderPipeline;
EXPECT_CALL(mDevice, CreateUninitializedRenderPipelineImpl)
.WillOnce(Return(ByMove(AcquireRef(renderPipelineMock))));
DAWN_ASSERT_AND_ASSIGN(renderPipeline, mDevice.CreateRenderPipeline(&desc));
EXPECT_TRUE(renderPipeline->IsAlive());
EXPECT_TRUE(renderPipeline->IsCachedReference());
}
}
TEST_F(DestroyObjectTests, SamplerExplicit) {
SamplerMock samplerMock(&mDevice);
EXPECT_CALL(samplerMock, 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) {
SamplerMock* samplerMock = new SamplerMock(&mDevice);
EXPECT_CALL(*samplerMock, DestroyImpl).Times(1);
{
SamplerDescriptor desc = {};
Ref<SamplerBase> sampler;
EXPECT_CALL(mDevice, CreateSamplerImpl).WillOnce(Return(ByMove(AcquireRef(samplerMock))));
DAWN_ASSERT_AND_ASSIGN(sampler, mDevice.CreateSampler(&desc));
EXPECT_TRUE(sampler->IsAlive());
EXPECT_TRUE(sampler->IsCachedReference());
}
}
TEST_F(DestroyObjectTests, ShaderModuleExplicit) {
ShaderModuleMock shaderModuleMock(&mDevice);
EXPECT_CALL(shaderModuleMock, DestroyImpl).Times(1);
EXPECT_TRUE(shaderModuleMock.IsAlive());
shaderModuleMock.Destroy();
EXPECT_FALSE(shaderModuleMock.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, ShaderModuleImplicit) {
ShaderModuleMock* shaderModuleMock = new ShaderModuleMock(&mDevice);
EXPECT_CALL(*shaderModuleMock, DestroyImpl).Times(1);
{
ShaderModuleWGSLDescriptor wgslDesc;
wgslDesc.source = R"(
@stage(compute) @workgroup_size(1) fn main() {
}
)";
ShaderModuleDescriptor desc = {};
desc.nextInChain = &wgslDesc;
Ref<ShaderModuleBase> shaderModule;
EXPECT_CALL(mDevice, CreateShaderModuleImpl)
.WillOnce(Return(ByMove(AcquireRef(shaderModuleMock))));
DAWN_ASSERT_AND_ASSIGN(shaderModule, mDevice.CreateShaderModule(&desc));
EXPECT_TRUE(shaderModule->IsAlive());
EXPECT_TRUE(shaderModule->IsCachedReference());
}
}
TEST_F(DestroyObjectTests, SwapChainExplicit) {
SwapChainMock swapChainMock(&mDevice);
EXPECT_CALL(swapChainMock, DestroyImpl).Times(1);
EXPECT_TRUE(swapChainMock.IsAlive());
swapChainMock.Destroy();
EXPECT_FALSE(swapChainMock.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, SwapChainImplicit) {
SwapChainMock* swapChainMock = new SwapChainMock(&mDevice);
EXPECT_CALL(*swapChainMock, DestroyImpl).Times(1);
{
SwapChainDescriptor desc = {};
Ref<SwapChainBase> swapChain;
EXPECT_CALL(mDevice, CreateSwapChainImpl(_))
.WillOnce(Return(ByMove(AcquireRef(swapChainMock))));
DAWN_ASSERT_AND_ASSIGN(swapChain, mDevice.CreateSwapChain(nullptr, &desc));
EXPECT_TRUE(swapChain->IsAlive());
}
}
TEST_F(DestroyObjectTests, TextureExplicit) {
{
TextureMock textureMock(&mDevice, TextureBase::TextureState::OwnedInternal);
EXPECT_CALL(textureMock, DestroyImpl).Times(1);
EXPECT_TRUE(textureMock.IsAlive());
textureMock.Destroy();
EXPECT_FALSE(textureMock.IsAlive());
}
{
TextureMock textureMock(&mDevice, TextureBase::TextureState::OwnedExternal);
EXPECT_CALL(textureMock, DestroyImpl).Times(1);
EXPECT_TRUE(textureMock.IsAlive());
textureMock.Destroy();
EXPECT_FALSE(textureMock.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) {
{
TextureMock* textureMock =
new TextureMock(&mDevice, TextureBase::TextureState::OwnedInternal);
EXPECT_CALL(*textureMock, DestroyImpl).Times(1);
{
TextureDescriptor desc = {};
Ref<TextureBase> texture;
EXPECT_CALL(mDevice, CreateTextureImpl)
.WillOnce(Return(ByMove(AcquireRef(textureMock))));
DAWN_ASSERT_AND_ASSIGN(texture, mDevice.CreateTexture(&desc));
EXPECT_TRUE(texture->IsAlive());
}
}
{
TextureMock* textureMock =
new TextureMock(&mDevice, TextureBase::TextureState::OwnedExternal);
EXPECT_CALL(*textureMock, DestroyImpl).Times(1);
{
TextureDescriptor desc = {};
Ref<TextureBase> texture;
EXPECT_CALL(mDevice, CreateTextureImpl)
.WillOnce(Return(ByMove(AcquireRef(textureMock))));
DAWN_ASSERT_AND_ASSIGN(texture, mDevice.CreateTexture(&desc));
EXPECT_TRUE(texture->IsAlive());
}
}
}
TEST_F(DestroyObjectTests, TextureViewExplicit) {
TextureViewMock textureViewMock(GetTexture().Get());
EXPECT_CALL(textureViewMock, DestroyImpl).Times(1);
EXPECT_TRUE(textureViewMock.IsAlive());
textureViewMock.Destroy();
EXPECT_FALSE(textureViewMock.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) {
TextureViewMock* textureViewMock = new TextureViewMock(GetTexture().Get());
EXPECT_CALL(*textureViewMock, DestroyImpl).Times(1);
{
TextureViewDescriptor desc = {};
Ref<TextureViewBase> textureView;
EXPECT_CALL(mDevice, CreateTextureViewImpl)
.WillOnce(Return(ByMove(AcquireRef(textureViewMock))));
DAWN_ASSERT_AND_ASSIGN(textureView, mDevice.CreateTextureView(GetTexture().Get(), &desc));
EXPECT_TRUE(textureView->IsAlive());
}
}
// Destroying the objects on the mDevice should result in all created objects being destroyed in
// order.
TEST_F(DestroyObjectTests, DestroyObjects) {
BindGroupMock* bindGroupMock = new BindGroupMock(&mDevice);
BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&mDevice);
BufferMock* bufferMock = new BufferMock(&mDevice, BufferBase::BufferState::Unmapped);
CommandBufferMock* commandBufferMock = new CommandBufferMock(&mDevice);
ComputePipelineMock* computePipelineMock = new ComputePipelineMock(&mDevice);
ExternalTextureMock* externalTextureMock = new ExternalTextureMock(&mDevice);
PipelineLayoutMock* pipelineLayoutMock = new PipelineLayoutMock(&mDevice);
QuerySetMock* querySetMock = new QuerySetMock(&mDevice);
RenderPipelineMock* renderPipelineMock = new RenderPipelineMock(&mDevice);
SamplerMock* samplerMock = new SamplerMock(&mDevice);
ShaderModuleMock* shaderModuleMock = new ShaderModuleMock(&mDevice);
SwapChainMock* swapChainMock = new SwapChainMock(&mDevice);
TextureMock* textureMock = new TextureMock(&mDevice, TextureBase::TextureState::OwnedInternal);
TextureViewMock* textureViewMock = new TextureViewMock(GetTexture().Get());
{
InSequence seq;
EXPECT_CALL(*commandBufferMock, DestroyImpl).Times(1);
EXPECT_CALL(*renderPipelineMock, DestroyImpl).Times(1);
EXPECT_CALL(*computePipelineMock, DestroyImpl).Times(1);
EXPECT_CALL(*pipelineLayoutMock, DestroyImpl).Times(1);
EXPECT_CALL(*swapChainMock, DestroyImpl).Times(1);
EXPECT_CALL(*bindGroupMock, DestroyImpl).Times(1);
EXPECT_CALL(*bindGroupLayoutMock, DestroyImpl).Times(1);
EXPECT_CALL(*shaderModuleMock, DestroyImpl).Times(1);
EXPECT_CALL(*externalTextureMock, DestroyImpl).Times(1);
EXPECT_CALL(*textureViewMock, DestroyImpl).Times(1);
EXPECT_CALL(*textureMock, DestroyImpl).Times(1);
EXPECT_CALL(*querySetMock, DestroyImpl).Times(1);
EXPECT_CALL(*samplerMock, DestroyImpl).Times(1);
EXPECT_CALL(*bufferMock, DestroyImpl).Times(1);
}
Ref<BindGroupBase> bindGroup;
{
BindGroupDescriptor desc = {};
EXPECT_CALL(mDevice, CreateBindGroupImpl)
.WillOnce(Return(ByMove(AcquireRef(bindGroupMock))));
DAWN_ASSERT_AND_ASSIGN(bindGroup, mDevice.CreateBindGroup(&desc));
EXPECT_TRUE(bindGroup->IsAlive());
}
Ref<BindGroupLayoutBase> bindGroupLayout;
{
BindGroupLayoutDescriptor desc = {};
EXPECT_CALL(mDevice, CreateBindGroupLayoutImpl)
.WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock))));
DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, mDevice.CreateBindGroupLayout(&desc));
EXPECT_TRUE(bindGroupLayout->IsAlive());
EXPECT_TRUE(bindGroupLayout->IsCachedReference());
}
Ref<BufferBase> buffer;
{
BufferDescriptor desc = {};
EXPECT_CALL(mDevice, CreateBufferImpl).WillOnce(Return(ByMove(AcquireRef(bufferMock))));
DAWN_ASSERT_AND_ASSIGN(buffer, mDevice.CreateBuffer(&desc));
EXPECT_TRUE(buffer->IsAlive());
}
Ref<CommandBufferBase> commandBuffer;
{
CommandBufferDescriptor desc = {};
EXPECT_CALL(mDevice, CreateCommandBuffer)
.WillOnce(Return(ByMove(AcquireRef(commandBufferMock))));
DAWN_ASSERT_AND_ASSIGN(commandBuffer, mDevice.CreateCommandBuffer(nullptr, &desc));
EXPECT_TRUE(commandBuffer->IsAlive());
}
Ref<ComputePipelineBase> computePipeline;
{
// Compute pipelines usually set their hash values at construction, but the mock does
// not, so we set it here.
constexpr size_t hash = 0x12345;
computePipelineMock->SetContentHash(hash);
ON_CALL(*computePipelineMock, ComputeContentHash).WillByDefault(Return(hash));
// Compute pipelines are initialized during their creation via the device.
EXPECT_CALL(*computePipelineMock, Initialize).Times(1);
ComputePipelineDescriptor desc = {};
desc.layout = GetPipelineLayout().Get();
desc.compute.module = GetComputeShaderModule().Get();
EXPECT_CALL(mDevice, CreateUninitializedComputePipelineImpl)
.WillOnce(Return(ByMove(AcquireRef(computePipelineMock))));
DAWN_ASSERT_AND_ASSIGN(computePipeline, mDevice.CreateComputePipeline(&desc));
EXPECT_TRUE(computePipeline->IsAlive());
EXPECT_TRUE(computePipeline->IsCachedReference());
}
Ref<ExternalTextureBase> externalTexture;
{
ExternalTextureDescriptor desc = {};
EXPECT_CALL(mDevice, CreateExternalTextureImpl)
.WillOnce(Return(ByMove(AcquireRef(externalTextureMock))));
DAWN_ASSERT_AND_ASSIGN(externalTexture, mDevice.CreateExternalTextureImpl(&desc));
EXPECT_TRUE(externalTexture->IsAlive());
}
Ref<PipelineLayoutBase> pipelineLayout;
{
PipelineLayoutDescriptor desc = {};
EXPECT_CALL(mDevice, CreatePipelineLayoutImpl)
.WillOnce(Return(ByMove(AcquireRef(pipelineLayoutMock))));
DAWN_ASSERT_AND_ASSIGN(pipelineLayout, mDevice.CreatePipelineLayout(&desc));
EXPECT_TRUE(pipelineLayout->IsAlive());
EXPECT_TRUE(pipelineLayout->IsCachedReference());
}
Ref<QuerySetBase> querySet;
{
QuerySetDescriptor desc = {};
EXPECT_CALL(mDevice, CreateQuerySetImpl).WillOnce(Return(ByMove(AcquireRef(querySetMock))));
DAWN_ASSERT_AND_ASSIGN(querySet, mDevice.CreateQuerySet(&desc));
EXPECT_TRUE(querySet->IsAlive());
}
Ref<RenderPipelineBase> renderPipeline;
{
// Render pipelines usually set their hash values at construction, but the mock does
// not, so we set it here.
constexpr size_t hash = 0x12345;
renderPipelineMock->SetContentHash(hash);
ON_CALL(*renderPipelineMock, ComputeContentHash).WillByDefault(Return(hash));
// Render pipelines are initialized during their creation via the device.
EXPECT_CALL(*renderPipelineMock, Initialize).Times(1);
RenderPipelineDescriptor desc = {};
desc.layout = GetPipelineLayout().Get();
desc.vertex.module = GetVertexShaderModule().Get();
EXPECT_CALL(mDevice, CreateUninitializedRenderPipelineImpl)
.WillOnce(Return(ByMove(AcquireRef(renderPipelineMock))));
DAWN_ASSERT_AND_ASSIGN(renderPipeline, mDevice.CreateRenderPipeline(&desc));
EXPECT_TRUE(renderPipeline->IsAlive());
EXPECT_TRUE(renderPipeline->IsCachedReference());
}
Ref<SamplerBase> sampler;
{
SamplerDescriptor desc = {};
EXPECT_CALL(mDevice, CreateSamplerImpl).WillOnce(Return(ByMove(AcquireRef(samplerMock))));
DAWN_ASSERT_AND_ASSIGN(sampler, mDevice.CreateSampler(&desc));
EXPECT_TRUE(sampler->IsAlive());
EXPECT_TRUE(sampler->IsCachedReference());
}
Ref<ShaderModuleBase> shaderModule;
{
ShaderModuleWGSLDescriptor wgslDesc;
wgslDesc.source = R"(
@stage(compute) @workgroup_size(1) fn main() {
}
)";
ShaderModuleDescriptor desc = {};
desc.nextInChain = &wgslDesc;
EXPECT_CALL(mDevice, CreateShaderModuleImpl)
.WillOnce(Return(ByMove(AcquireRef(shaderModuleMock))));
DAWN_ASSERT_AND_ASSIGN(shaderModule, mDevice.CreateShaderModule(&desc));
EXPECT_TRUE(shaderModule->IsAlive());
EXPECT_TRUE(shaderModule->IsCachedReference());
}
Ref<SwapChainBase> swapChain;
{
SwapChainDescriptor desc = {};
EXPECT_CALL(mDevice, CreateSwapChainImpl(_))
.WillOnce(Return(ByMove(AcquireRef(swapChainMock))));
DAWN_ASSERT_AND_ASSIGN(swapChain, mDevice.CreateSwapChain(nullptr, &desc));
EXPECT_TRUE(swapChain->IsAlive());
}
Ref<TextureBase> texture;
{
TextureDescriptor desc = {};
EXPECT_CALL(mDevice, CreateTextureImpl).WillOnce(Return(ByMove(AcquireRef(textureMock))));
DAWN_ASSERT_AND_ASSIGN(texture, mDevice.CreateTexture(&desc));
EXPECT_TRUE(texture->IsAlive());
}
Ref<TextureViewBase> textureView;
{
TextureViewDescriptor desc = {};
EXPECT_CALL(mDevice, CreateTextureViewImpl)
.WillOnce(Return(ByMove(AcquireRef(textureViewMock))));
DAWN_ASSERT_AND_ASSIGN(textureView, mDevice.CreateTextureView(GetTexture().Get(), &desc));
EXPECT_TRUE(textureView->IsAlive());
}
mDevice.DestroyObjects();
EXPECT_FALSE(bindGroup->IsAlive());
EXPECT_FALSE(bindGroupLayout->IsAlive());
EXPECT_FALSE(buffer->IsAlive());
EXPECT_FALSE(commandBuffer->IsAlive());
EXPECT_FALSE(computePipeline->IsAlive());
EXPECT_FALSE(externalTexture->IsAlive());
EXPECT_FALSE(pipelineLayout->IsAlive());
EXPECT_FALSE(querySet->IsAlive());
EXPECT_FALSE(renderPipeline->IsAlive());
EXPECT_FALSE(sampler->IsAlive());
EXPECT_FALSE(shaderModule->IsAlive());
EXPECT_FALSE(swapChain->IsAlive());
EXPECT_FALSE(texture->IsAlive());
EXPECT_FALSE(textureView->IsAlive());
}
static constexpr std::string_view kComputeShader = R"(
@stage(compute) @workgroup_size(1) fn main() {}
)";
static constexpr std::string_view kVertexShader = R"(
@stage(vertex) fn main() -> @builtin(position) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 0.0);
}
)";
static constexpr std::string_view kFragmentShader = R"(
@stage(fragment) fn main() {}
)";
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();
}
// TODO(https://crbug.com/dawn/1381) Remove when namespaces are not indented.
// NOLINTNEXTLINE(readability/namespace)
} // namespace
} // namespace dawn::native