From 9901c710d1f834fd3e98356f0842553e1ac4afb2 Mon Sep 17 00:00:00 2001 From: Loko Kung Date: Thu, 28 Oct 2021 17:33:25 +0000 Subject: [PATCH] Adds destroy handling for simple objects without new backend changes yet. Simple objects are defined here as objects that do not already have a destroy or destroy-like API available. They include: - BindGroups - ComputePipelines - PipelineLayouts - RenderPipelines - Samplers - ShaderModules - SwapChains Changes include: - Adds necessary constructors for testing and caching - Adding mock objects, mock constructors, and tests Bug: dawn:628 Change-Id: I26a5e37bc5580b9064db299a75ef1243521b266a Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/65864 Commit-Queue: Loko Kung Reviewed-by: Austin Eng --- src/dawn_native/BindGroup.cpp | 8 +- src/dawn_native/BindGroup.h | 4 +- src/dawn_native/BindGroupLayout.cpp | 10 +- src/dawn_native/BindGroupLayout.h | 1 + src/dawn_native/ComputePipeline.cpp | 15 +- src/dawn_native/ComputePipeline.h | 5 + src/dawn_native/Device.cpp | 19 +- src/dawn_native/Device.h | 1 - src/dawn_native/ObjectBase.h | 2 +- src/dawn_native/Pipeline.cpp | 5 + src/dawn_native/Pipeline.h | 5 + src/dawn_native/PipelineLayout.cpp | 24 +- src/dawn_native/PipelineLayout.h | 6 + src/dawn_native/RenderPipeline.cpp | 120 ++--- src/dawn_native/RenderPipeline.h | 5 + src/dawn_native/Sampler.cpp | 22 +- src/dawn_native/Sampler.h | 8 + src/dawn_native/ShaderModule.cpp | 23 +- src/dawn_native/ShaderModule.h | 7 + src/dawn_native/SwapChain.cpp | 1 + src/tests/BUILD.gn | 11 + .../unittests/native/DestroyObjectTests.cpp | 447 ++++++++++++++++-- .../unittests/native/mocks/BindGroupMock.h | 36 ++ .../native/mocks/ComputePipelineMock.h | 38 ++ .../native/mocks/PipelineLayoutMock.h | 36 ++ .../native/mocks/RenderPipelineMock.h | 38 ++ .../unittests/native/mocks/SamplerMock.h | 36 ++ .../native/mocks/ShaderModuleMock.cpp | 34 ++ .../unittests/native/mocks/ShaderModuleMock.h | 42 ++ .../unittests/native/mocks/SwapChainMock.h | 43 ++ 30 files changed, 935 insertions(+), 117 deletions(-) create mode 100644 src/tests/unittests/native/mocks/BindGroupMock.h create mode 100644 src/tests/unittests/native/mocks/ComputePipelineMock.h create mode 100644 src/tests/unittests/native/mocks/PipelineLayoutMock.h create mode 100644 src/tests/unittests/native/mocks/RenderPipelineMock.h create mode 100644 src/tests/unittests/native/mocks/SamplerMock.h create mode 100644 src/tests/unittests/native/mocks/ShaderModuleMock.cpp create mode 100644 src/tests/unittests/native/mocks/ShaderModuleMock.h create mode 100644 src/tests/unittests/native/mocks/SwapChainMock.h diff --git a/src/dawn_native/BindGroup.cpp b/src/dawn_native/BindGroup.cpp index 361ca82618..874c369c76 100644 --- a/src/dawn_native/BindGroup.cpp +++ b/src/dawn_native/BindGroup.cpp @@ -391,6 +391,12 @@ namespace dawn_native { ++packedIdx; } } + + TrackInDevice(); + } + + BindGroupBase::BindGroupBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); } BindGroupBase::~BindGroupBase() { @@ -407,7 +413,7 @@ namespace dawn_native { // is destroyed after the bind group. The bind group is slab-allocated inside // memory owned by the layout (except for the null backend). Ref layout = mLayout; - RefCounted::DeleteThis(); + ApiObjectBase::DeleteThis(); } BindGroupBase::BindGroupBase(DeviceBase* device, ObjectBase::ErrorTag tag) diff --git a/src/dawn_native/BindGroup.h b/src/dawn_native/BindGroup.h index 1ce4b9fe43..6792d619b9 100644 --- a/src/dawn_native/BindGroup.h +++ b/src/dawn_native/BindGroup.h @@ -73,7 +73,9 @@ namespace dawn_native { static_assert(std::is_base_of::value, ""); } - protected: + // Constructor used only for mocking and testing. + BindGroupBase(DeviceBase* device); + ~BindGroupBase() override; private: diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp index c0097ccd0d..aea7c40e54 100644 --- a/src/dawn_native/BindGroupLayout.cpp +++ b/src/dawn_native/BindGroupLayout.cpp @@ -416,13 +416,13 @@ namespace dawn_native { TrackInDevice(); } + BindGroupLayoutBase::~BindGroupLayoutBase() = default; + bool BindGroupLayoutBase::DestroyApiObject() { bool wasDestroyed = ApiObjectBase::DestroyApiObject(); - if (wasDestroyed) { - // Do not uncache the actual cached object if we are a blueprint - if (IsCachedReference()) { - GetDevice()->UncacheBindGroupLayout(this); - } + if (wasDestroyed && IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint or already destroyed. + GetDevice()->UncacheBindGroupLayout(this); } return wasDestroyed; } diff --git a/src/dawn_native/BindGroupLayout.h b/src/dawn_native/BindGroupLayout.h index 61b811f990..1c2c4e3647 100644 --- a/src/dawn_native/BindGroupLayout.h +++ b/src/dawn_native/BindGroupLayout.h @@ -49,6 +49,7 @@ namespace dawn_native { BindGroupLayoutBase(DeviceBase* device, const BindGroupLayoutDescriptor* descriptor, PipelineCompatibilityToken pipelineCompatibilityToken); + ~BindGroupLayoutBase() override; static BindGroupLayoutBase* MakeError(DeviceBase* device); diff --git a/src/dawn_native/ComputePipeline.cpp b/src/dawn_native/ComputePipeline.cpp index 72addc4797..9eca55c987 100644 --- a/src/dawn_native/ComputePipeline.cpp +++ b/src/dawn_native/ComputePipeline.cpp @@ -47,17 +47,26 @@ namespace dawn_native { descriptor->compute.entryPoint, descriptor->compute.constantCount, descriptor->compute.constants}}) { SetContentHash(ComputeContentHash()); + TrackInDevice(); + } + + ComputePipelineBase::ComputePipelineBase(DeviceBase* device) : PipelineBase(device) { + TrackInDevice(); } ComputePipelineBase::ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag) : PipelineBase(device, tag) { } - ComputePipelineBase::~ComputePipelineBase() { - // Do not uncache the actual cached object if we are a blueprint - if (IsCachedReference()) { + ComputePipelineBase::~ComputePipelineBase() = default; + + bool ComputePipelineBase::DestroyApiObject() { + bool wasDestroyed = ApiObjectBase::DestroyApiObject(); + if (wasDestroyed && IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint or already destroyed. GetDevice()->UncacheComputePipeline(this); } + return wasDestroyed; } // static diff --git a/src/dawn_native/ComputePipeline.h b/src/dawn_native/ComputePipeline.h index 6352c1999b..18729237ce 100644 --- a/src/dawn_native/ComputePipeline.h +++ b/src/dawn_native/ComputePipeline.h @@ -34,6 +34,7 @@ namespace dawn_native { static ComputePipelineBase* MakeError(DeviceBase* device); + bool DestroyApiObject() override; ObjectType GetType() const override; // Functors necessary for the unordered_set-based cache. @@ -41,6 +42,10 @@ namespace dawn_native { bool operator()(const ComputePipelineBase* a, const ComputePipelineBase* b) const; }; + protected: + // Constructor used only for mocking and testing. + ComputePipelineBase(DeviceBase* device); + private: ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag); }; diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp index baaa7a99e6..b30a2f89a8 100644 --- a/src/dawn_native/Device.cpp +++ b/src/dawn_native/Device.cpp @@ -37,6 +37,7 @@ #include "dawn_native/QuerySet.h" #include "dawn_native/Queue.h" #include "dawn_native/RenderBundleEncoder.h" +#include "dawn_native/RenderPipeline.h" #include "dawn_native/Sampler.h" #include "dawn_native/Surface.h" #include "dawn_native/SwapChain.h" @@ -274,9 +275,19 @@ namespace dawn_native { // that this only considers the immediate frontend dependencies, while backend objects could // add complications and extra dependencies. // TODO(dawn/628) Add types into the array as they are implemented. - static constexpr std::array kObjectTypeDependencyOrder = { + + // clang-format off + static constexpr std::array kObjectTypeDependencyOrder = { + ObjectType::RenderPipeline, + ObjectType::ComputePipeline, + ObjectType::PipelineLayout, + ObjectType::SwapChain, + ObjectType::BindGroup, ObjectType::BindGroupLayout, + ObjectType::ShaderModule, + ObjectType::Sampler, }; + // clang-format on // We first move all objects out from the tracking list into a separate list so that we can // avoid locking the same mutex twice. We can then iterate across the separate list to call @@ -723,7 +734,7 @@ namespace dawn_native { ResultOrError> DeviceBase::GetOrCreatePipelineLayout( const PipelineLayoutDescriptor* descriptor) { - PipelineLayoutBase blueprint(this, descriptor); + PipelineLayoutBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice); const size_t blueprintHash = blueprint.ComputeContentHash(); blueprint.SetContentHash(blueprintHash); @@ -756,7 +767,7 @@ namespace dawn_native { ResultOrError> DeviceBase::GetOrCreateSampler( const SamplerDescriptor* descriptor) { - SamplerBase blueprint(this, descriptor); + SamplerBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice); const size_t blueprintHash = blueprint.ComputeContentHash(); blueprint.SetContentHash(blueprintHash); @@ -787,7 +798,7 @@ namespace dawn_native { OwnedCompilationMessages* compilationMessages) { ASSERT(parseResult != nullptr); - ShaderModuleBase blueprint(this, descriptor); + ShaderModuleBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice); const size_t blueprintHash = blueprint.ComputeContentHash(); blueprint.SetContentHash(blueprintHash); diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h index 7852c6afe7..6d0130229a 100644 --- a/src/dawn_native/Device.h +++ b/src/dawn_native/Device.h @@ -24,7 +24,6 @@ #include "dawn_native/Limits.h" #include "dawn_native/ObjectBase.h" #include "dawn_native/ObjectType_autogen.h" -#include "dawn_native/RenderPipeline.h" #include "dawn_native/StagingBuffer.h" #include "dawn_native/Toggles.h" diff --git a/src/dawn_native/ObjectBase.h b/src/dawn_native/ObjectBase.h index d05a56e0e6..291e806613 100644 --- a/src/dawn_native/ObjectBase.h +++ b/src/dawn_native/ObjectBase.h @@ -51,7 +51,7 @@ namespace dawn_native { ApiObjectBase(DeviceBase* device, LabelNotImplementedTag tag); ApiObjectBase(DeviceBase* device, const char* label); ApiObjectBase(DeviceBase* device, ErrorTag tag); - virtual ~ApiObjectBase() override; + ~ApiObjectBase() override; virtual ObjectType GetType() const = 0; const std::string& GetLabel() const; diff --git a/src/dawn_native/Pipeline.cpp b/src/dawn_native/Pipeline.cpp index ab232c3566..bb846fdb15 100644 --- a/src/dawn_native/Pipeline.cpp +++ b/src/dawn_native/Pipeline.cpp @@ -142,10 +142,15 @@ namespace dawn_native { } } + PipelineBase::PipelineBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { + } + PipelineBase::PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag) : ApiObjectBase(device, tag) { } + PipelineBase::~PipelineBase() = default; + PipelineLayoutBase* PipelineBase::GetLayout() { ASSERT(!IsError()); return mLayout.Get(); diff --git a/src/dawn_native/Pipeline.h b/src/dawn_native/Pipeline.h index 74442930b4..7a1e09a595 100644 --- a/src/dawn_native/Pipeline.h +++ b/src/dawn_native/Pipeline.h @@ -50,6 +50,8 @@ namespace dawn_native { class PipelineBase : public ApiObjectBase, public CachedObject { public: + ~PipelineBase() override; + PipelineLayoutBase* GetLayout(); const PipelineLayoutBase* GetLayout() const; const RequiredBufferSizes& GetMinBufferSizes() const; @@ -76,6 +78,9 @@ namespace dawn_native { std::vector stages); PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag); + // Constructor used only for mocking and testing. + PipelineBase(DeviceBase* device); + private: MaybeError ValidateGetBindGroupLayout(uint32_t group); diff --git a/src/dawn_native/PipelineLayout.cpp b/src/dawn_native/PipelineLayout.cpp index 9b1b707b69..eb23756465 100644 --- a/src/dawn_native/PipelineLayout.cpp +++ b/src/dawn_native/PipelineLayout.cpp @@ -57,7 +57,8 @@ namespace dawn_native { // PipelineLayoutBase PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device, - const PipelineLayoutDescriptor* descriptor) + const PipelineLayoutDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag) : ApiObjectBase(device, kLabelNotImplemented) { ASSERT(descriptor->bindGroupLayoutCount <= kMaxBindGroups); for (BindGroupIndex group(0); group < BindGroupIndex(descriptor->bindGroupLayoutCount); @@ -67,15 +68,30 @@ namespace dawn_native { } } + PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device, + const PipelineLayoutDescriptor* descriptor) + : PipelineLayoutBase(device, descriptor, kUntrackedByDevice) { + TrackInDevice(); + } + + PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device) + : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag) : ApiObjectBase(device, tag) { } - PipelineLayoutBase::~PipelineLayoutBase() { - // Do not uncache the actual cached object if we are a blueprint - if (IsCachedReference()) { + PipelineLayoutBase::~PipelineLayoutBase() = default; + + bool PipelineLayoutBase::DestroyApiObject() { + bool wasDestroyed = ApiObjectBase::DestroyApiObject(); + if (wasDestroyed && IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint GetDevice()->UncachePipelineLayout(this); } + return wasDestroyed; } // static diff --git a/src/dawn_native/PipelineLayout.h b/src/dawn_native/PipelineLayout.h index 7371dab46d..597de45886 100644 --- a/src/dawn_native/PipelineLayout.h +++ b/src/dawn_native/PipelineLayout.h @@ -50,6 +50,9 @@ namespace dawn_native { class PipelineLayoutBase : public ApiObjectBase, public CachedObject { public: + PipelineLayoutBase(DeviceBase* device, + const PipelineLayoutDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag); PipelineLayoutBase(DeviceBase* device, const PipelineLayoutDescriptor* descriptor); ~PipelineLayoutBase() override; @@ -58,6 +61,7 @@ namespace dawn_native { DeviceBase* device, std::vector stages); + bool DestroyApiObject() override; ObjectType GetType() const override; const BindGroupLayoutBase* GetBindGroupLayout(BindGroupIndex group) const; @@ -80,6 +84,8 @@ namespace dawn_native { }; protected: + // Constructor used only for mocking and testing. + PipelineLayoutBase(DeviceBase* device); PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag); BindGroupLayoutArray mBindGroupLayouts; diff --git a/src/dawn_native/RenderPipeline.cpp b/src/dawn_native/RenderPipeline.cpp index 1c16bbad6c..d1305132a2 100644 --- a/src/dawn_native/RenderPipeline.cpp +++ b/src/dawn_native/RenderPipeline.cpp @@ -670,12 +670,28 @@ namespace dawn_native { } SetContentHash(ComputeContentHash()); + TrackInDevice(); + } + + RenderPipelineBase::RenderPipelineBase(DeviceBase* device) : PipelineBase(device) { + TrackInDevice(); } RenderPipelineBase::RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag) : PipelineBase(device, tag) { } + RenderPipelineBase::~RenderPipelineBase() = default; + + bool RenderPipelineBase::DestroyApiObject() { + bool wasDestroyed = ApiObjectBase::DestroyApiObject(); + if (wasDestroyed && IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint or already destroyed. + GetDevice()->UncacheRenderPipeline(this); + } + return wasDestroyed; + } + // static RenderPipelineBase* RenderPipelineBase::MakeError(DeviceBase* device) { class ErrorRenderPipeline final : public RenderPipelineBase { @@ -697,12 +713,6 @@ namespace dawn_native { return ObjectType::RenderPipeline; } - RenderPipelineBase::~RenderPipelineBase() { - if (IsCachedReference()) { - GetDevice()->UncacheRenderPipeline(this); - } - } - const ityp::bitset& RenderPipelineBase::GetAttributeLocationsUsed() const { ASSERT(!IsError()); @@ -928,62 +938,64 @@ namespace dawn_native { return false; } - for (ColorAttachmentIndex i : - IterateBitSet(a->mAttachmentState->GetColorAttachmentsMask())) { - const ColorTargetState& descA = *a->GetColorTargetState(i); - const ColorTargetState& descB = *b->GetColorTargetState(i); - if (descA.writeMask != descB.writeMask) { - return false; - } - if ((descA.blend == nullptr) != (descB.blend == nullptr)) { - return false; - } - if (descA.blend != nullptr) { - if (descA.blend->color.operation != descB.blend->color.operation || - descA.blend->color.srcFactor != descB.blend->color.srcFactor || - descA.blend->color.dstFactor != descB.blend->color.dstFactor) { + if (a->mAttachmentState.Get() != nullptr) { + for (ColorAttachmentIndex i : + IterateBitSet(a->mAttachmentState->GetColorAttachmentsMask())) { + const ColorTargetState& descA = *a->GetColorTargetState(i); + const ColorTargetState& descB = *b->GetColorTargetState(i); + if (descA.writeMask != descB.writeMask) { return false; } - if (descA.blend->alpha.operation != descB.blend->alpha.operation || - descA.blend->alpha.srcFactor != descB.blend->alpha.srcFactor || - descA.blend->alpha.dstFactor != descB.blend->alpha.dstFactor) { + if ((descA.blend == nullptr) != (descB.blend == nullptr)) { return false; } + if (descA.blend != nullptr) { + if (descA.blend->color.operation != descB.blend->color.operation || + descA.blend->color.srcFactor != descB.blend->color.srcFactor || + descA.blend->color.dstFactor != descB.blend->color.dstFactor) { + return false; + } + if (descA.blend->alpha.operation != descB.blend->alpha.operation || + descA.blend->alpha.srcFactor != descB.blend->alpha.srcFactor || + descA.blend->alpha.dstFactor != descB.blend->alpha.dstFactor) { + return false; + } + } } - } - // Check depth/stencil state - if (a->mAttachmentState->HasDepthStencilAttachment()) { - const DepthStencilState& stateA = a->mDepthStencil; - const DepthStencilState& stateB = b->mDepthStencil; + // Check depth/stencil state + if (a->mAttachmentState->HasDepthStencilAttachment()) { + const DepthStencilState& stateA = a->mDepthStencil; + const DepthStencilState& stateB = b->mDepthStencil; - ASSERT(!std::isnan(stateA.depthBiasSlopeScale)); - ASSERT(!std::isnan(stateB.depthBiasSlopeScale)); - ASSERT(!std::isnan(stateA.depthBiasClamp)); - ASSERT(!std::isnan(stateB.depthBiasClamp)); + ASSERT(!std::isnan(stateA.depthBiasSlopeScale)); + ASSERT(!std::isnan(stateB.depthBiasSlopeScale)); + ASSERT(!std::isnan(stateA.depthBiasClamp)); + ASSERT(!std::isnan(stateB.depthBiasClamp)); - if (stateA.depthWriteEnabled != stateB.depthWriteEnabled || - stateA.depthCompare != stateB.depthCompare || - stateA.depthBias != stateB.depthBias || - stateA.depthBiasSlopeScale != stateB.depthBiasSlopeScale || - stateA.depthBiasClamp != stateB.depthBiasClamp) { - return false; - } - if (stateA.stencilFront.compare != stateB.stencilFront.compare || - stateA.stencilFront.failOp != stateB.stencilFront.failOp || - stateA.stencilFront.depthFailOp != stateB.stencilFront.depthFailOp || - stateA.stencilFront.passOp != stateB.stencilFront.passOp) { - return false; - } - if (stateA.stencilBack.compare != stateB.stencilBack.compare || - stateA.stencilBack.failOp != stateB.stencilBack.failOp || - stateA.stencilBack.depthFailOp != stateB.stencilBack.depthFailOp || - stateA.stencilBack.passOp != stateB.stencilBack.passOp) { - return false; - } - if (stateA.stencilReadMask != stateB.stencilReadMask || - stateA.stencilWriteMask != stateB.stencilWriteMask) { - return false; + if (stateA.depthWriteEnabled != stateB.depthWriteEnabled || + stateA.depthCompare != stateB.depthCompare || + stateA.depthBias != stateB.depthBias || + stateA.depthBiasSlopeScale != stateB.depthBiasSlopeScale || + stateA.depthBiasClamp != stateB.depthBiasClamp) { + return false; + } + if (stateA.stencilFront.compare != stateB.stencilFront.compare || + stateA.stencilFront.failOp != stateB.stencilFront.failOp || + stateA.stencilFront.depthFailOp != stateB.stencilFront.depthFailOp || + stateA.stencilFront.passOp != stateB.stencilFront.passOp) { + return false; + } + if (stateA.stencilBack.compare != stateB.stencilBack.compare || + stateA.stencilBack.failOp != stateB.stencilBack.failOp || + stateA.stencilBack.depthFailOp != stateB.stencilBack.depthFailOp || + stateA.stencilBack.passOp != stateB.stencilBack.passOp) { + return false; + } + if (stateA.stencilReadMask != stateB.stencilReadMask || + stateA.stencilWriteMask != stateB.stencilWriteMask) { + return false; + } } } diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h index bd354ab5de..b69b6ed712 100644 --- a/src/dawn_native/RenderPipeline.h +++ b/src/dawn_native/RenderPipeline.h @@ -63,6 +63,7 @@ namespace dawn_native { static RenderPipelineBase* MakeError(DeviceBase* device); + bool DestroyApiObject() override; ObjectType GetType() const override; const ityp::bitset& @@ -107,6 +108,10 @@ namespace dawn_native { bool operator()(const RenderPipelineBase* a, const RenderPipelineBase* b) const; }; + protected: + // Constructor used only for mocking and testing. + RenderPipelineBase(DeviceBase* device); + private: RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag); diff --git a/src/dawn_native/Sampler.cpp b/src/dawn_native/Sampler.cpp index ec3266c50c..94c26dde53 100644 --- a/src/dawn_native/Sampler.cpp +++ b/src/dawn_native/Sampler.cpp @@ -69,7 +69,9 @@ namespace dawn_native { // SamplerBase - SamplerBase::SamplerBase(DeviceBase* device, const SamplerDescriptor* descriptor) + SamplerBase::SamplerBase(DeviceBase* device, + const SamplerDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag) : ApiObjectBase(device, kLabelNotImplemented), mAddressModeU(descriptor->addressModeU), mAddressModeV(descriptor->addressModeV), @@ -83,14 +85,28 @@ namespace dawn_native { mMaxAnisotropy(descriptor->maxAnisotropy) { } + SamplerBase::SamplerBase(DeviceBase* device, const SamplerDescriptor* descriptor) + : SamplerBase(device, descriptor, kUntrackedByDevice) { + TrackInDevice(); + } + + SamplerBase::SamplerBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + SamplerBase::SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag) : ApiObjectBase(device, tag) { } - SamplerBase::~SamplerBase() { - if (IsCachedReference()) { + SamplerBase::~SamplerBase() = default; + + bool SamplerBase::DestroyApiObject() { + bool wasDestroyed = ApiObjectBase::DestroyApiObject(); + if (wasDestroyed && IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint or already destroyed. GetDevice()->UncacheSampler(this); } + return wasDestroyed; } // static diff --git a/src/dawn_native/Sampler.h b/src/dawn_native/Sampler.h index 3e7d1fbae4..6d9e945bb2 100644 --- a/src/dawn_native/Sampler.h +++ b/src/dawn_native/Sampler.h @@ -30,11 +30,15 @@ namespace dawn_native { class SamplerBase : public ApiObjectBase, public CachedObject { public: + SamplerBase(DeviceBase* device, + const SamplerDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag); SamplerBase(DeviceBase* device, const SamplerDescriptor* descriptor); ~SamplerBase() override; static SamplerBase* MakeError(DeviceBase* device); + bool DestroyApiObject() override; ObjectType GetType() const override; bool IsComparison() const; @@ -51,6 +55,10 @@ namespace dawn_native { return mMaxAnisotropy; } + protected: + // Constructor used only for mocking and testing. + SamplerBase(DeviceBase* device); + private: SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag); diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp index 058f886e48..3a8c26ca76 100644 --- a/src/dawn_native/ShaderModule.cpp +++ b/src/dawn_native/ShaderModule.cpp @@ -1145,7 +1145,9 @@ namespace dawn_native { // ShaderModuleBase - ShaderModuleBase::ShaderModuleBase(DeviceBase* device, const ShaderModuleDescriptor* descriptor) + ShaderModuleBase::ShaderModuleBase(DeviceBase* device, + const ShaderModuleDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag) : ApiObjectBase(device, descriptor->label), mType(Type::Undefined) { ASSERT(descriptor->nextInChain != nullptr); const ShaderModuleSPIRVDescriptor* spirvDesc = nullptr; @@ -1163,14 +1165,29 @@ namespace dawn_native { } } + ShaderModuleBase::ShaderModuleBase(DeviceBase* device, const ShaderModuleDescriptor* descriptor) + : ShaderModuleBase(device, descriptor, kUntrackedByDevice) { + TrackInDevice(); + } + + ShaderModuleBase::ShaderModuleBase(DeviceBase* device) + : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); + } + ShaderModuleBase::ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag) : ApiObjectBase(device, tag), mType(Type::Undefined) { } - ShaderModuleBase::~ShaderModuleBase() { - if (IsCachedReference()) { + ShaderModuleBase::~ShaderModuleBase() = default; + + bool ShaderModuleBase::DestroyApiObject() { + bool wasDestroyed = ApiObjectBase::DestroyApiObject(); + if (wasDestroyed && IsCachedReference()) { + // Do not uncache the actual cached object if we are a blueprint or already destroyed. GetDevice()->UncacheShaderModule(this); } + return wasDestroyed; } // static diff --git a/src/dawn_native/ShaderModule.h b/src/dawn_native/ShaderModule.h index 09bede54cd..30f32946bb 100644 --- a/src/dawn_native/ShaderModule.h +++ b/src/dawn_native/ShaderModule.h @@ -221,11 +221,15 @@ namespace dawn_native { class ShaderModuleBase : public ApiObjectBase, public CachedObject { public: + ShaderModuleBase(DeviceBase* device, + const ShaderModuleDescriptor* descriptor, + ApiObjectBase::UntrackedByDeviceTag tag); ShaderModuleBase(DeviceBase* device, const ShaderModuleDescriptor* descriptor); ~ShaderModuleBase() override; static Ref MakeError(DeviceBase* device); + bool DestroyApiObject() override; ObjectType GetType() const override; // Return true iff the program has an entrypoint called `entryPoint`. @@ -252,6 +256,9 @@ namespace dawn_native { OwnedCompilationMessages* GetCompilationMessages() const; protected: + // Constructor used only for mocking and testing. + ShaderModuleBase(DeviceBase* device); + MaybeError InitializeBase(ShaderModuleParseResult* parseResult); private: diff --git a/src/dawn_native/SwapChain.cpp b/src/dawn_native/SwapChain.cpp index 7bba116c10..26617b1a51 100644 --- a/src/dawn_native/SwapChain.cpp +++ b/src/dawn_native/SwapChain.cpp @@ -115,6 +115,7 @@ namespace dawn_native { // SwapChainBase SwapChainBase::SwapChainBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { + TrackInDevice(); } SwapChainBase::SwapChainBase(DeviceBase* device, ObjectBase::ErrorTag tag) diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index e1e658a590..77b4099ae6 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -138,9 +138,20 @@ source_set("dawn_native_mocks_sources") { "${dawn_root}/src/utils:dawn_utils", ] + # Add internal dawn_native config for internal unittests. + configs += [ "${dawn_root}/src/dawn_native:dawn_native_internal" ] + sources = [ "unittests/native/mocks/BindGroupLayoutMock.h", + "unittests/native/mocks/BindGroupMock.h", + "unittests/native/mocks/ComputePipelineMock.h", "unittests/native/mocks/DeviceMock.h", + "unittests/native/mocks/PipelineLayoutMock.h", + "unittests/native/mocks/RenderPipelineMock.h", + "unittests/native/mocks/SamplerMock.h", + "unittests/native/mocks/ShaderModuleMock.cpp", + "unittests/native/mocks/ShaderModuleMock.h", + "unittests/native/mocks/SwapChainMock.h", ] } diff --git a/src/tests/unittests/native/DestroyObjectTests.cpp b/src/tests/unittests/native/DestroyObjectTests.cpp index 6e9c2835a3..a82a7f4aa9 100644 --- a/src/tests/unittests/native/DestroyObjectTests.cpp +++ b/src/tests/unittests/native/DestroyObjectTests.cpp @@ -16,80 +16,453 @@ #include "dawn_native/Toggles.h" #include "mocks/BindGroupLayoutMock.h" +#include "mocks/BindGroupMock.h" +#include "mocks/ComputePipelineMock.h" #include "mocks/DeviceMock.h" +#include "mocks/PipelineLayoutMock.h" +#include "mocks/RenderPipelineMock.h" +#include "mocks/SamplerMock.h" +#include "mocks/ShaderModuleMock.h" +#include "mocks/SwapChainMock.h" #include "tests/DawnNativeTest.h" +#include "utils/ComboRenderPipelineDescriptor.h" namespace dawn_native { namespace { + using ::testing::_; using ::testing::ByMove; using ::testing::InSequence; using ::testing::Return; + using ::testing::Test; - TEST(DestroyObjectTests, BindGroupLayoutExplicit) { - // Skipping validation on descriptors as coverage for validation is already present. - DeviceMock device; - device.SetToggle(Toggle::SkipValidation, true); + class DestroyObjectTests : public Test { + public: + DestroyObjectTests() : Test() { + // Skipping validation on descriptors as coverage for validation is already present. + mDevice.SetToggle(Toggle::SkipValidation, true); + } - BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&device); - EXPECT_CALL(*bindGroupLayoutMock, DestroyApiObjectImpl).Times(1); + Ref GetPipelineLayout() { + if (mPipelineLayout != nullptr) { + return mPipelineLayout; + } + mPipelineLayout = AcquireRef(new PipelineLayoutMock(&mDevice)); + EXPECT_CALL(*mPipelineLayout.Get(), DestroyApiObjectImpl).Times(1); + return mPipelineLayout; + } - BindGroupLayoutDescriptor desc = {}; - Ref bindGroupLayout; - EXPECT_CALL(device, CreateBindGroupLayoutImpl) - .WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock)))); - DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, device.CreateBindGroupLayout(&desc)); + Ref GetVertexShaderModule() { + if (mVsModule != nullptr) { + return mVsModule; + } + DAWN_TRY_ASSIGN_WITH_CLEANUP( + mVsModule, ShaderModuleMock::Create(&mDevice, R"( + [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4 { + return vec4(0.0, 0.0, 0.0, 1.0); + })"), + { ASSERT(false); }, mVsModule); + EXPECT_CALL(*mVsModule.Get(), DestroyApiObjectImpl).Times(1); + return mVsModule; + } - EXPECT_TRUE(bindGroupLayout->IsAlive()); - EXPECT_TRUE(bindGroupLayout->IsCachedReference()); + Ref 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(), DestroyApiObjectImpl).Times(1); + return mCsModule; + } - bindGroupLayout->DestroyApiObject(); - EXPECT_FALSE(bindGroupLayout->IsAlive()); + protected: + DeviceMock mDevice; + + // The following lazy-initialized objects are used to facilitate creation of dependent + // objects under test. + Ref mPipelineLayout; + Ref mVsModule; + Ref mCsModule; + }; + + TEST_F(DestroyObjectTests, BindGroupExplicit) { + BindGroupMock bindGroupMock(&mDevice); + EXPECT_CALL(bindGroupMock, DestroyApiObjectImpl).Times(1); + + EXPECT_TRUE(bindGroupMock.IsAlive()); + bindGroupMock.DestroyApiObject(); + 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(DestroyObjectTests, BindGroupLayoutImplicit) { - // Skipping validation on descriptors as coverage for validation is already present. - DeviceMock device; - device.SetToggle(Toggle::SkipValidation, true); + TEST_F(DestroyObjectTests, BindGroupImplicit) { + BindGroupMock* bindGroupMock = new BindGroupMock(&mDevice); + EXPECT_CALL(*bindGroupMock, DestroyApiObjectImpl).Times(1); + { + BindGroupDescriptor desc = {}; + Ref bindGroup; + EXPECT_CALL(mDevice, CreateBindGroupImpl) + .WillOnce(Return(ByMove(AcquireRef(bindGroupMock)))); + DAWN_ASSERT_AND_ASSIGN(bindGroup, mDevice.CreateBindGroup(&desc)); - BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&device); + EXPECT_TRUE(bindGroup->IsAlive()); + } + } + + TEST_F(DestroyObjectTests, BindGroupLayoutExplicit) { + BindGroupLayoutMock bindGroupLayoutMock(&mDevice); + EXPECT_CALL(bindGroupLayoutMock, DestroyApiObjectImpl).Times(1); + + EXPECT_TRUE(bindGroupLayoutMock.IsAlive()); + bindGroupLayoutMock.DestroyApiObject(); + 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, DestroyApiObjectImpl).Times(1); - { BindGroupLayoutDescriptor desc = {}; Ref bindGroupLayout; - EXPECT_CALL(device, CreateBindGroupLayoutImpl) + EXPECT_CALL(mDevice, CreateBindGroupLayoutImpl) .WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock)))); - DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, device.CreateBindGroupLayout(&desc)); + DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, mDevice.CreateBindGroupLayout(&desc)); EXPECT_TRUE(bindGroupLayout->IsAlive()); EXPECT_TRUE(bindGroupLayout->IsCachedReference()); } } - // Destroying the objects on the device should result in all created objects being destroyed in - // order. - TEST(DestroyObjectTests, DestroyObjects) { - DeviceMock device; - device.SetToggle(Toggle::SkipValidation, true); + TEST_F(DestroyObjectTests, ComputePipelineExplicit) { + ComputePipelineMock computePipelineMock(&mDevice); + EXPECT_CALL(computePipelineMock, DestroyApiObjectImpl).Times(1); - BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&device); + EXPECT_TRUE(computePipelineMock.IsAlive()); + computePipelineMock.DestroyApiObject(); + 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, DestroyApiObjectImpl).Times(1); + + { + ComputePipelineDescriptor desc = {}; + desc.layout = GetPipelineLayout().Get(); + desc.compute.module = GetComputeShaderModule().Get(); + + Ref 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, PipelineLayoutExplicit) { + PipelineLayoutMock pipelineLayoutMock(&mDevice); + EXPECT_CALL(pipelineLayoutMock, DestroyApiObjectImpl).Times(1); + + EXPECT_TRUE(pipelineLayoutMock.IsAlive()); + pipelineLayoutMock.DestroyApiObject(); + 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, DestroyApiObjectImpl).Times(1); + { + PipelineLayoutDescriptor desc = {}; + Ref 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, RenderPipelineExplicit) { + RenderPipelineMock renderPipelineMock(&mDevice); + EXPECT_CALL(renderPipelineMock, DestroyApiObjectImpl).Times(1); + + EXPECT_TRUE(renderPipelineMock.IsAlive()); + renderPipelineMock.DestroyApiObject(); + 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, DestroyApiObjectImpl).Times(1); + + { + RenderPipelineDescriptor desc = {}; + desc.layout = GetPipelineLayout().Get(); + desc.vertex.module = GetVertexShaderModule().Get(); + + Ref 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, DestroyApiObjectImpl).Times(1); + + EXPECT_TRUE(samplerMock.IsAlive()); + samplerMock.DestroyApiObject(); + 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, DestroyApiObjectImpl).Times(1); + { + SamplerDescriptor desc = {}; + Ref 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, DestroyApiObjectImpl).Times(1); + + EXPECT_TRUE(shaderModuleMock.IsAlive()); + shaderModuleMock.DestroyApiObject(); + 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, DestroyApiObjectImpl).Times(1); + { + ShaderModuleWGSLDescriptor wgslDesc; + wgslDesc.source = R"( + [[stage(compute), workgroup_size(1)]] fn main() { + } + )"; + ShaderModuleDescriptor desc = {}; + desc.nextInChain = &wgslDesc; + Ref 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, DestroyApiObjectImpl).Times(1); + + EXPECT_TRUE(swapChainMock.IsAlive()); + swapChainMock.DestroyApiObject(); + 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, DestroyApiObjectImpl).Times(1); + { + SwapChainDescriptor desc = {}; + Ref swapChain; + EXPECT_CALL(mDevice, CreateSwapChainImpl(_)) + .WillOnce(Return(ByMove(AcquireRef(swapChainMock)))); + DAWN_ASSERT_AND_ASSIGN(swapChain, mDevice.CreateSwapChain(nullptr, &desc)); + + EXPECT_TRUE(swapChain->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); + ComputePipelineMock* computePipelineMock = new ComputePipelineMock(&mDevice); + PipelineLayoutMock* pipelineLayoutMock = new PipelineLayoutMock(&mDevice); + RenderPipelineMock* renderPipelineMock = new RenderPipelineMock(&mDevice); + SamplerMock* samplerMock = new SamplerMock(&mDevice); + ShaderModuleMock* shaderModuleMock = new ShaderModuleMock(&mDevice); + SwapChainMock* swapChainMock = new SwapChainMock(&mDevice); { InSequence seq; + EXPECT_CALL(*renderPipelineMock, DestroyApiObjectImpl).Times(1); + EXPECT_CALL(*computePipelineMock, DestroyApiObjectImpl).Times(1); + EXPECT_CALL(*pipelineLayoutMock, DestroyApiObjectImpl).Times(1); + EXPECT_CALL(*swapChainMock, DestroyApiObjectImpl).Times(1); + EXPECT_CALL(*bindGroupMock, DestroyApiObjectImpl).Times(1); EXPECT_CALL(*bindGroupLayoutMock, DestroyApiObjectImpl).Times(1); + EXPECT_CALL(*shaderModuleMock, DestroyApiObjectImpl).Times(1); + EXPECT_CALL(*samplerMock, DestroyApiObjectImpl).Times(1); } - BindGroupLayoutDescriptor desc = {}; - Ref bindGroupLayout; - EXPECT_CALL(device, CreateBindGroupLayoutImpl) - .WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock)))); - DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, device.CreateBindGroupLayout(&desc)); - EXPECT_TRUE(bindGroupLayout->IsAlive()); - EXPECT_TRUE(bindGroupLayout->IsCachedReference()); + Ref bindGroup; + { + BindGroupDescriptor desc = {}; + EXPECT_CALL(mDevice, CreateBindGroupImpl) + .WillOnce(Return(ByMove(AcquireRef(bindGroupMock)))); + DAWN_ASSERT_AND_ASSIGN(bindGroup, mDevice.CreateBindGroup(&desc)); + EXPECT_TRUE(bindGroup->IsAlive()); + } - device.DestroyObjects(); + Ref 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 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 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 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 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 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 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()); + } + + mDevice.DestroyObjects(); + EXPECT_FALSE(bindGroup->IsAlive()); EXPECT_FALSE(bindGroupLayout->IsAlive()); + EXPECT_FALSE(computePipeline->IsAlive()); + EXPECT_FALSE(pipelineLayout->IsAlive()); + EXPECT_FALSE(renderPipeline->IsAlive()); + EXPECT_FALSE(sampler->IsAlive()); + EXPECT_FALSE(shaderModule->IsAlive()); + EXPECT_FALSE(swapChain->IsAlive()); } }} // namespace dawn_native:: diff --git a/src/tests/unittests/native/mocks/BindGroupMock.h b/src/tests/unittests/native/mocks/BindGroupMock.h new file mode 100644 index 0000000000..e36dc8457c --- /dev/null +++ b/src/tests/unittests/native/mocks/BindGroupMock.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef TESTS_UNITTESTS_NATIVE_MOCKS_BINDGROUP_MOCK_H_ +#define TESTS_UNITTESTS_NATIVE_MOCKS_BINDGROUP_MOCK_H_ + +#include "dawn_native/BindGroup.h" +#include "dawn_native/Device.h" + +#include + +namespace dawn_native { + + class BindGroupMock : public BindGroupBase { + public: + BindGroupMock(DeviceBase* device) : BindGroupBase(device) { + } + ~BindGroupMock() override = default; + + MOCK_METHOD(void, DestroyApiObjectImpl, (), (override)); + }; + +} // namespace dawn_native + +#endif // TESTS_UNITTESTS_NATIVE_MOCKS_BINDGROUP_MOCK_H_ diff --git a/src/tests/unittests/native/mocks/ComputePipelineMock.h b/src/tests/unittests/native/mocks/ComputePipelineMock.h new file mode 100644 index 0000000000..6289b56a08 --- /dev/null +++ b/src/tests/unittests/native/mocks/ComputePipelineMock.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef TESTS_UNITTESTS_NATIVE_MOCKS_COMPUTEPIPELINE_MOCK_H_ +#define TESTS_UNITTESTS_NATIVE_MOCKS_COMPUTEPIPELINE_MOCK_H_ + +#include "dawn_native/ComputePipeline.h" +#include "dawn_native/Device.h" + +#include + +namespace dawn_native { + + class ComputePipelineMock : public ComputePipelineBase { + public: + ComputePipelineMock(DeviceBase* device) : ComputePipelineBase(device) { + } + ~ComputePipelineMock() override = default; + + MOCK_METHOD(MaybeError, Initialize, (), (override)); + MOCK_METHOD(size_t, ComputeContentHash, (), (override)); + MOCK_METHOD(void, DestroyApiObjectImpl, (), (override)); + }; + +} // namespace dawn_native + +#endif // TESTS_UNITTESTS_NATIVE_MOCKS_COMPUTEPIPELINE_MOCK_H_ diff --git a/src/tests/unittests/native/mocks/PipelineLayoutMock.h b/src/tests/unittests/native/mocks/PipelineLayoutMock.h new file mode 100644 index 0000000000..4b9201a2cf --- /dev/null +++ b/src/tests/unittests/native/mocks/PipelineLayoutMock.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef TESTS_UNITTESTS_NATIVE_MOCKS_PIPELINELAYOUT_MOCK_H_ +#define TESTS_UNITTESTS_NATIVE_MOCKS_PIPELINELAYOUT_MOCK_H_ + +#include "dawn_native/Device.h" +#include "dawn_native/PipelineLayout.h" + +#include + +namespace dawn_native { + + class PipelineLayoutMock : public PipelineLayoutBase { + public: + PipelineLayoutMock(DeviceBase* device) : PipelineLayoutBase(device) { + } + ~PipelineLayoutMock() override = default; + + MOCK_METHOD(void, DestroyApiObjectImpl, (), (override)); + }; + +} // namespace dawn_native + +#endif // TESTS_UNITTESTS_NATIVE_MOCKS_PIPELINELAYOUT_MOCK_H_ diff --git a/src/tests/unittests/native/mocks/RenderPipelineMock.h b/src/tests/unittests/native/mocks/RenderPipelineMock.h new file mode 100644 index 0000000000..fa82ab5d1e --- /dev/null +++ b/src/tests/unittests/native/mocks/RenderPipelineMock.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef TESTS_UNITTESTS_NATIVE_MOCKS_RENDERPIPELINE_MOCK_H_ +#define TESTS_UNITTESTS_NATIVE_MOCKS_RENDERPIPELINE_MOCK_H_ + +#include "dawn_native/Device.h" +#include "dawn_native/RenderPipeline.h" + +#include + +namespace dawn_native { + + class RenderPipelineMock : public RenderPipelineBase { + public: + RenderPipelineMock(DeviceBase* device) : RenderPipelineBase(device) { + } + ~RenderPipelineMock() override = default; + + MOCK_METHOD(MaybeError, Initialize, (), (override)); + MOCK_METHOD(size_t, ComputeContentHash, (), (override)); + MOCK_METHOD(void, DestroyApiObjectImpl, (), (override)); + }; + +} // namespace dawn_native + +#endif // TESTS_UNITTESTS_NATIVE_MOCKS_RENDERPIPELINE_MOCK_H_ diff --git a/src/tests/unittests/native/mocks/SamplerMock.h b/src/tests/unittests/native/mocks/SamplerMock.h new file mode 100644 index 0000000000..ca9a6b00e8 --- /dev/null +++ b/src/tests/unittests/native/mocks/SamplerMock.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef TESTS_UNITTESTS_NATIVE_MOCKS_SAMPLER_MOCK_H_ +#define TESTS_UNITTESTS_NATIVE_MOCKS_SAMPLER_MOCK_H_ + +#include "dawn_native/Device.h" +#include "dawn_native/Sampler.h" + +#include + +namespace dawn_native { + + class SamplerMock : public SamplerBase { + public: + SamplerMock(DeviceBase* device) : SamplerBase(device) { + } + ~SamplerMock() override = default; + + MOCK_METHOD(void, DestroyApiObjectImpl, (), (override)); + }; + +} // namespace dawn_native + +#endif // TESTS_UNITTESTS_NATIVE_MOCKS_SAMPLER_MOCK_H_ diff --git a/src/tests/unittests/native/mocks/ShaderModuleMock.cpp b/src/tests/unittests/native/mocks/ShaderModuleMock.cpp new file mode 100644 index 0000000000..4d0f0f2d20 --- /dev/null +++ b/src/tests/unittests/native/mocks/ShaderModuleMock.cpp @@ -0,0 +1,34 @@ +// 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 "ShaderModuleMock.h" + +namespace dawn_native { + + ResultOrError> ShaderModuleMock::Create(DeviceBase* device, + const char* source) { + ShaderModuleMock* mock = new ShaderModuleMock(device); + + ShaderModuleWGSLDescriptor wgslDesc; + wgslDesc.source = source; + ShaderModuleDescriptor desc; + desc.nextInChain = &wgslDesc; + + ShaderModuleParseResult parseResult; + DAWN_TRY(ValidateShaderModuleDescriptor(device, &desc, &parseResult, nullptr)); + DAWN_TRY(mock->InitializeBase(&parseResult)); + return AcquireRef(mock); + } + +} // namespace dawn_native diff --git a/src/tests/unittests/native/mocks/ShaderModuleMock.h b/src/tests/unittests/native/mocks/ShaderModuleMock.h new file mode 100644 index 0000000000..ebe2357c6e --- /dev/null +++ b/src/tests/unittests/native/mocks/ShaderModuleMock.h @@ -0,0 +1,42 @@ +// 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. + +#ifndef TESTS_UNITTESTS_NATIVE_MOCKS_SHADERMODULE_MOCK_H_ +#define TESTS_UNITTESTS_NATIVE_MOCKS_SHADERMODULE_MOCK_H_ + +#include "dawn_native/Error.h" +#include "dawn_native/ShaderModule.h" +#include "src/dawn_native/Device.h" + +#include + +#include + +namespace dawn_native { + + class ShaderModuleMock : public ShaderModuleBase { + public: + ShaderModuleMock(DeviceBase* device) : ShaderModuleBase(device) { + } + ~ShaderModuleMock() override = default; + + MOCK_METHOD(void, DestroyApiObjectImpl, (), (override)); + + // Creates a shader module mock based on the wgsl source. + static ResultOrError> Create(DeviceBase* device, const char* source); + }; + +} // namespace dawn_native + +#endif // TESTS_UNITTESTS_NATIVE_MOCKS_SHADERMODULE_MOCK_H_ diff --git a/src/tests/unittests/native/mocks/SwapChainMock.h b/src/tests/unittests/native/mocks/SwapChainMock.h new file mode 100644 index 0000000000..cee332b445 --- /dev/null +++ b/src/tests/unittests/native/mocks/SwapChainMock.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef TESTS_UNITTESTS_NATIVE_MOCKS_SWAPCHAIN_MOCK_H_ +#define TESTS_UNITTESTS_NATIVE_MOCKS_SWAPCHAIN_MOCK_H_ + +#include "dawn_native/Device.h" +#include "dawn_native/SwapChain.h" + +#include + +namespace dawn_native { + + class SwapChainMock : public SwapChainBase { + public: + SwapChainMock(DeviceBase* device) : SwapChainBase(device) { + } + ~SwapChainMock() override = default; + + MOCK_METHOD(void, DestroyApiObjectImpl, (), (override)); + + MOCK_METHOD(void, + APIConfigure, + (wgpu::TextureFormat, wgpu::TextureUsage, uint32_t, uint32_t), + (override)); + MOCK_METHOD(TextureViewBase*, APIGetCurrentTextureView, (), (override)); + MOCK_METHOD(void, APIPresent, (), (override)); + }; + +} // namespace dawn_native + +#endif // TESTS_UNITTESTS_NATIVE_MOCKS_SWAPCHAIN_MOCK_H_