Reland "Adds destroy handling for simple objects without new backend changes yet."

This is a reland of 9901c710d1

Original change's description:
> 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 <lokokung@google.com>
> Reviewed-by: Austin Eng <enga@chromium.org>

Bug: dawn:628
Change-Id: I19492ad6b1660e205d123b2d1fdf53c772564cfe
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/67961
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
This commit is contained in:
Loko Kung 2021-11-01 18:14:21 +00:00 committed by Dawn LUCI CQ
parent 1722f9e78e
commit e1e9fd0722
30 changed files with 935 additions and 117 deletions

View File

@ -391,6 +391,12 @@ namespace dawn_native {
++packedIdx; ++packedIdx;
} }
} }
TrackInDevice();
}
BindGroupBase::BindGroupBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) {
TrackInDevice();
} }
BindGroupBase::~BindGroupBase() { BindGroupBase::~BindGroupBase() {
@ -407,7 +413,7 @@ namespace dawn_native {
// is destroyed after the bind group. The bind group is slab-allocated inside // is destroyed after the bind group. The bind group is slab-allocated inside
// memory owned by the layout (except for the null backend). // memory owned by the layout (except for the null backend).
Ref<BindGroupLayoutBase> layout = mLayout; Ref<BindGroupLayoutBase> layout = mLayout;
RefCounted::DeleteThis(); ApiObjectBase::DeleteThis();
} }
BindGroupBase::BindGroupBase(DeviceBase* device, ObjectBase::ErrorTag tag) BindGroupBase::BindGroupBase(DeviceBase* device, ObjectBase::ErrorTag tag)

View File

@ -73,7 +73,9 @@ namespace dawn_native {
static_assert(std::is_base_of<BindGroupBase, Derived>::value, ""); static_assert(std::is_base_of<BindGroupBase, Derived>::value, "");
} }
protected: // Constructor used only for mocking and testing.
BindGroupBase(DeviceBase* device);
~BindGroupBase() override; ~BindGroupBase() override;
private: private:

View File

@ -416,13 +416,13 @@ namespace dawn_native {
TrackInDevice(); TrackInDevice();
} }
BindGroupLayoutBase::~BindGroupLayoutBase() = default;
bool BindGroupLayoutBase::DestroyApiObject() { bool BindGroupLayoutBase::DestroyApiObject() {
bool wasDestroyed = ApiObjectBase::DestroyApiObject(); bool wasDestroyed = ApiObjectBase::DestroyApiObject();
if (wasDestroyed) { if (wasDestroyed && IsCachedReference()) {
// Do not uncache the actual cached object if we are a blueprint // Do not uncache the actual cached object if we are a blueprint or already destroyed.
if (IsCachedReference()) { GetDevice()->UncacheBindGroupLayout(this);
GetDevice()->UncacheBindGroupLayout(this);
}
} }
return wasDestroyed; return wasDestroyed;
} }

View File

@ -49,6 +49,7 @@ namespace dawn_native {
BindGroupLayoutBase(DeviceBase* device, BindGroupLayoutBase(DeviceBase* device,
const BindGroupLayoutDescriptor* descriptor, const BindGroupLayoutDescriptor* descriptor,
PipelineCompatibilityToken pipelineCompatibilityToken); PipelineCompatibilityToken pipelineCompatibilityToken);
~BindGroupLayoutBase() override;
static BindGroupLayoutBase* MakeError(DeviceBase* device); static BindGroupLayoutBase* MakeError(DeviceBase* device);

View File

@ -47,17 +47,26 @@ namespace dawn_native {
descriptor->compute.entryPoint, descriptor->compute.constantCount, descriptor->compute.entryPoint, descriptor->compute.constantCount,
descriptor->compute.constants}}) { descriptor->compute.constants}}) {
SetContentHash(ComputeContentHash()); SetContentHash(ComputeContentHash());
TrackInDevice();
}
ComputePipelineBase::ComputePipelineBase(DeviceBase* device) : PipelineBase(device) {
TrackInDevice();
} }
ComputePipelineBase::ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag) ComputePipelineBase::ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag)
: PipelineBase(device, tag) { : PipelineBase(device, tag) {
} }
ComputePipelineBase::~ComputePipelineBase() { ComputePipelineBase::~ComputePipelineBase() = default;
// Do not uncache the actual cached object if we are a blueprint
if (IsCachedReference()) { 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); GetDevice()->UncacheComputePipeline(this);
} }
return wasDestroyed;
} }
// static // static

View File

@ -34,6 +34,7 @@ namespace dawn_native {
static ComputePipelineBase* MakeError(DeviceBase* device); static ComputePipelineBase* MakeError(DeviceBase* device);
bool DestroyApiObject() override;
ObjectType GetType() const override; ObjectType GetType() const override;
// Functors necessary for the unordered_set<ComputePipelineBase*>-based cache. // Functors necessary for the unordered_set<ComputePipelineBase*>-based cache.
@ -41,6 +42,10 @@ namespace dawn_native {
bool operator()(const ComputePipelineBase* a, const ComputePipelineBase* b) const; bool operator()(const ComputePipelineBase* a, const ComputePipelineBase* b) const;
}; };
protected:
// Constructor used only for mocking and testing.
ComputePipelineBase(DeviceBase* device);
private: private:
ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag); ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
}; };

View File

@ -37,6 +37,7 @@
#include "dawn_native/QuerySet.h" #include "dawn_native/QuerySet.h"
#include "dawn_native/Queue.h" #include "dawn_native/Queue.h"
#include "dawn_native/RenderBundleEncoder.h" #include "dawn_native/RenderBundleEncoder.h"
#include "dawn_native/RenderPipeline.h"
#include "dawn_native/Sampler.h" #include "dawn_native/Sampler.h"
#include "dawn_native/Surface.h" #include "dawn_native/Surface.h"
#include "dawn_native/SwapChain.h" #include "dawn_native/SwapChain.h"
@ -265,9 +266,19 @@ namespace dawn_native {
// that this only considers the immediate frontend dependencies, while backend objects could // that this only considers the immediate frontend dependencies, while backend objects could
// add complications and extra dependencies. // add complications and extra dependencies.
// TODO(dawn/628) Add types into the array as they are implemented. // TODO(dawn/628) Add types into the array as they are implemented.
static constexpr std::array<ObjectType, 1> kObjectTypeDependencyOrder = {
// clang-format off
static constexpr std::array<ObjectType, 8> kObjectTypeDependencyOrder = {
ObjectType::RenderPipeline,
ObjectType::ComputePipeline,
ObjectType::PipelineLayout,
ObjectType::SwapChain,
ObjectType::BindGroup,
ObjectType::BindGroupLayout, 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 // 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 // avoid locking the same mutex twice. We can then iterate across the separate list to call
@ -714,7 +725,7 @@ namespace dawn_native {
ResultOrError<Ref<PipelineLayoutBase>> DeviceBase::GetOrCreatePipelineLayout( ResultOrError<Ref<PipelineLayoutBase>> DeviceBase::GetOrCreatePipelineLayout(
const PipelineLayoutDescriptor* descriptor) { const PipelineLayoutDescriptor* descriptor) {
PipelineLayoutBase blueprint(this, descriptor); PipelineLayoutBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice);
const size_t blueprintHash = blueprint.ComputeContentHash(); const size_t blueprintHash = blueprint.ComputeContentHash();
blueprint.SetContentHash(blueprintHash); blueprint.SetContentHash(blueprintHash);
@ -747,7 +758,7 @@ namespace dawn_native {
ResultOrError<Ref<SamplerBase>> DeviceBase::GetOrCreateSampler( ResultOrError<Ref<SamplerBase>> DeviceBase::GetOrCreateSampler(
const SamplerDescriptor* descriptor) { const SamplerDescriptor* descriptor) {
SamplerBase blueprint(this, descriptor); SamplerBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice);
const size_t blueprintHash = blueprint.ComputeContentHash(); const size_t blueprintHash = blueprint.ComputeContentHash();
blueprint.SetContentHash(blueprintHash); blueprint.SetContentHash(blueprintHash);
@ -778,7 +789,7 @@ namespace dawn_native {
OwnedCompilationMessages* compilationMessages) { OwnedCompilationMessages* compilationMessages) {
ASSERT(parseResult != nullptr); ASSERT(parseResult != nullptr);
ShaderModuleBase blueprint(this, descriptor); ShaderModuleBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice);
const size_t blueprintHash = blueprint.ComputeContentHash(); const size_t blueprintHash = blueprint.ComputeContentHash();
blueprint.SetContentHash(blueprintHash); blueprint.SetContentHash(blueprintHash);

View File

@ -24,7 +24,6 @@
#include "dawn_native/Limits.h" #include "dawn_native/Limits.h"
#include "dawn_native/ObjectBase.h" #include "dawn_native/ObjectBase.h"
#include "dawn_native/ObjectType_autogen.h" #include "dawn_native/ObjectType_autogen.h"
#include "dawn_native/RenderPipeline.h"
#include "dawn_native/StagingBuffer.h" #include "dawn_native/StagingBuffer.h"
#include "dawn_native/Toggles.h" #include "dawn_native/Toggles.h"

View File

@ -51,7 +51,7 @@ namespace dawn_native {
ApiObjectBase(DeviceBase* device, LabelNotImplementedTag tag); ApiObjectBase(DeviceBase* device, LabelNotImplementedTag tag);
ApiObjectBase(DeviceBase* device, const char* label); ApiObjectBase(DeviceBase* device, const char* label);
ApiObjectBase(DeviceBase* device, ErrorTag tag); ApiObjectBase(DeviceBase* device, ErrorTag tag);
virtual ~ApiObjectBase() override; ~ApiObjectBase() override;
virtual ObjectType GetType() const = 0; virtual ObjectType GetType() const = 0;
const std::string& GetLabel() const; const std::string& GetLabel() const;

View File

@ -142,10 +142,15 @@ namespace dawn_native {
} }
} }
PipelineBase::PipelineBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) {
}
PipelineBase::PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag) PipelineBase::PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag)
: ApiObjectBase(device, tag) { : ApiObjectBase(device, tag) {
} }
PipelineBase::~PipelineBase() = default;
PipelineLayoutBase* PipelineBase::GetLayout() { PipelineLayoutBase* PipelineBase::GetLayout() {
ASSERT(!IsError()); ASSERT(!IsError());
return mLayout.Get(); return mLayout.Get();

View File

@ -50,6 +50,8 @@ namespace dawn_native {
class PipelineBase : public ApiObjectBase, public CachedObject { class PipelineBase : public ApiObjectBase, public CachedObject {
public: public:
~PipelineBase() override;
PipelineLayoutBase* GetLayout(); PipelineLayoutBase* GetLayout();
const PipelineLayoutBase* GetLayout() const; const PipelineLayoutBase* GetLayout() const;
const RequiredBufferSizes& GetMinBufferSizes() const; const RequiredBufferSizes& GetMinBufferSizes() const;
@ -76,6 +78,9 @@ namespace dawn_native {
std::vector<StageAndDescriptor> stages); std::vector<StageAndDescriptor> stages);
PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag); PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
// Constructor used only for mocking and testing.
PipelineBase(DeviceBase* device);
private: private:
MaybeError ValidateGetBindGroupLayout(uint32_t group); MaybeError ValidateGetBindGroupLayout(uint32_t group);

View File

@ -57,7 +57,8 @@ namespace dawn_native {
// PipelineLayoutBase // PipelineLayoutBase
PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device, PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device,
const PipelineLayoutDescriptor* descriptor) const PipelineLayoutDescriptor* descriptor,
ApiObjectBase::UntrackedByDeviceTag tag)
: ApiObjectBase(device, kLabelNotImplemented) { : ApiObjectBase(device, kLabelNotImplemented) {
ASSERT(descriptor->bindGroupLayoutCount <= kMaxBindGroups); ASSERT(descriptor->bindGroupLayoutCount <= kMaxBindGroups);
for (BindGroupIndex group(0); group < BindGroupIndex(descriptor->bindGroupLayoutCount); 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) PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag)
: ApiObjectBase(device, tag) { : ApiObjectBase(device, tag) {
} }
PipelineLayoutBase::~PipelineLayoutBase() { PipelineLayoutBase::~PipelineLayoutBase() = default;
// Do not uncache the actual cached object if we are a blueprint
if (IsCachedReference()) { 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); GetDevice()->UncachePipelineLayout(this);
} }
return wasDestroyed;
} }
// static // static

View File

@ -50,6 +50,9 @@ namespace dawn_native {
class PipelineLayoutBase : public ApiObjectBase, public CachedObject { class PipelineLayoutBase : public ApiObjectBase, public CachedObject {
public: public:
PipelineLayoutBase(DeviceBase* device,
const PipelineLayoutDescriptor* descriptor,
ApiObjectBase::UntrackedByDeviceTag tag);
PipelineLayoutBase(DeviceBase* device, const PipelineLayoutDescriptor* descriptor); PipelineLayoutBase(DeviceBase* device, const PipelineLayoutDescriptor* descriptor);
~PipelineLayoutBase() override; ~PipelineLayoutBase() override;
@ -58,6 +61,7 @@ namespace dawn_native {
DeviceBase* device, DeviceBase* device,
std::vector<StageAndDescriptor> stages); std::vector<StageAndDescriptor> stages);
bool DestroyApiObject() override;
ObjectType GetType() const override; ObjectType GetType() const override;
const BindGroupLayoutBase* GetBindGroupLayout(BindGroupIndex group) const; const BindGroupLayoutBase* GetBindGroupLayout(BindGroupIndex group) const;
@ -80,6 +84,8 @@ namespace dawn_native {
}; };
protected: protected:
// Constructor used only for mocking and testing.
PipelineLayoutBase(DeviceBase* device);
PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag); PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag);
BindGroupLayoutArray mBindGroupLayouts; BindGroupLayoutArray mBindGroupLayouts;

View File

@ -670,12 +670,28 @@ namespace dawn_native {
} }
SetContentHash(ComputeContentHash()); SetContentHash(ComputeContentHash());
TrackInDevice();
}
RenderPipelineBase::RenderPipelineBase(DeviceBase* device) : PipelineBase(device) {
TrackInDevice();
} }
RenderPipelineBase::RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag) RenderPipelineBase::RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag)
: PipelineBase(device, 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 // static
RenderPipelineBase* RenderPipelineBase::MakeError(DeviceBase* device) { RenderPipelineBase* RenderPipelineBase::MakeError(DeviceBase* device) {
class ErrorRenderPipeline final : public RenderPipelineBase { class ErrorRenderPipeline final : public RenderPipelineBase {
@ -697,12 +713,6 @@ namespace dawn_native {
return ObjectType::RenderPipeline; return ObjectType::RenderPipeline;
} }
RenderPipelineBase::~RenderPipelineBase() {
if (IsCachedReference()) {
GetDevice()->UncacheRenderPipeline(this);
}
}
const ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>& const ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>&
RenderPipelineBase::GetAttributeLocationsUsed() const { RenderPipelineBase::GetAttributeLocationsUsed() const {
ASSERT(!IsError()); ASSERT(!IsError());
@ -928,62 +938,64 @@ namespace dawn_native {
return false; return false;
} }
for (ColorAttachmentIndex i : if (a->mAttachmentState.Get() != nullptr) {
IterateBitSet(a->mAttachmentState->GetColorAttachmentsMask())) { for (ColorAttachmentIndex i :
const ColorTargetState& descA = *a->GetColorTargetState(i); IterateBitSet(a->mAttachmentState->GetColorAttachmentsMask())) {
const ColorTargetState& descB = *b->GetColorTargetState(i); const ColorTargetState& descA = *a->GetColorTargetState(i);
if (descA.writeMask != descB.writeMask) { const ColorTargetState& descB = *b->GetColorTargetState(i);
return false; if (descA.writeMask != descB.writeMask) {
}
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; return false;
} }
if (descA.blend->alpha.operation != descB.blend->alpha.operation || if ((descA.blend == nullptr) != (descB.blend == nullptr)) {
descA.blend->alpha.srcFactor != descB.blend->alpha.srcFactor ||
descA.blend->alpha.dstFactor != descB.blend->alpha.dstFactor) {
return false; 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 // Check depth/stencil state
if (a->mAttachmentState->HasDepthStencilAttachment()) { if (a->mAttachmentState->HasDepthStencilAttachment()) {
const DepthStencilState& stateA = a->mDepthStencil; const DepthStencilState& stateA = a->mDepthStencil;
const DepthStencilState& stateB = b->mDepthStencil; const DepthStencilState& stateB = b->mDepthStencil;
ASSERT(!std::isnan(stateA.depthBiasSlopeScale)); ASSERT(!std::isnan(stateA.depthBiasSlopeScale));
ASSERT(!std::isnan(stateB.depthBiasSlopeScale)); ASSERT(!std::isnan(stateB.depthBiasSlopeScale));
ASSERT(!std::isnan(stateA.depthBiasClamp)); ASSERT(!std::isnan(stateA.depthBiasClamp));
ASSERT(!std::isnan(stateB.depthBiasClamp)); ASSERT(!std::isnan(stateB.depthBiasClamp));
if (stateA.depthWriteEnabled != stateB.depthWriteEnabled || if (stateA.depthWriteEnabled != stateB.depthWriteEnabled ||
stateA.depthCompare != stateB.depthCompare || stateA.depthCompare != stateB.depthCompare ||
stateA.depthBias != stateB.depthBias || stateA.depthBias != stateB.depthBias ||
stateA.depthBiasSlopeScale != stateB.depthBiasSlopeScale || stateA.depthBiasSlopeScale != stateB.depthBiasSlopeScale ||
stateA.depthBiasClamp != stateB.depthBiasClamp) { stateA.depthBiasClamp != stateB.depthBiasClamp) {
return false; return false;
} }
if (stateA.stencilFront.compare != stateB.stencilFront.compare || if (stateA.stencilFront.compare != stateB.stencilFront.compare ||
stateA.stencilFront.failOp != stateB.stencilFront.failOp || stateA.stencilFront.failOp != stateB.stencilFront.failOp ||
stateA.stencilFront.depthFailOp != stateB.stencilFront.depthFailOp || stateA.stencilFront.depthFailOp != stateB.stencilFront.depthFailOp ||
stateA.stencilFront.passOp != stateB.stencilFront.passOp) { stateA.stencilFront.passOp != stateB.stencilFront.passOp) {
return false; return false;
} }
if (stateA.stencilBack.compare != stateB.stencilBack.compare || if (stateA.stencilBack.compare != stateB.stencilBack.compare ||
stateA.stencilBack.failOp != stateB.stencilBack.failOp || stateA.stencilBack.failOp != stateB.stencilBack.failOp ||
stateA.stencilBack.depthFailOp != stateB.stencilBack.depthFailOp || stateA.stencilBack.depthFailOp != stateB.stencilBack.depthFailOp ||
stateA.stencilBack.passOp != stateB.stencilBack.passOp) { stateA.stencilBack.passOp != stateB.stencilBack.passOp) {
return false; return false;
} }
if (stateA.stencilReadMask != stateB.stencilReadMask || if (stateA.stencilReadMask != stateB.stencilReadMask ||
stateA.stencilWriteMask != stateB.stencilWriteMask) { stateA.stencilWriteMask != stateB.stencilWriteMask) {
return false; return false;
}
} }
} }

View File

@ -63,6 +63,7 @@ namespace dawn_native {
static RenderPipelineBase* MakeError(DeviceBase* device); static RenderPipelineBase* MakeError(DeviceBase* device);
bool DestroyApiObject() override;
ObjectType GetType() const override; ObjectType GetType() const override;
const ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>& const ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes>&
@ -107,6 +108,10 @@ namespace dawn_native {
bool operator()(const RenderPipelineBase* a, const RenderPipelineBase* b) const; bool operator()(const RenderPipelineBase* a, const RenderPipelineBase* b) const;
}; };
protected:
// Constructor used only for mocking and testing.
RenderPipelineBase(DeviceBase* device);
private: private:
RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag); RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);

View File

@ -69,7 +69,9 @@ namespace dawn_native {
// SamplerBase // SamplerBase
SamplerBase::SamplerBase(DeviceBase* device, const SamplerDescriptor* descriptor) SamplerBase::SamplerBase(DeviceBase* device,
const SamplerDescriptor* descriptor,
ApiObjectBase::UntrackedByDeviceTag tag)
: ApiObjectBase(device, kLabelNotImplemented), : ApiObjectBase(device, kLabelNotImplemented),
mAddressModeU(descriptor->addressModeU), mAddressModeU(descriptor->addressModeU),
mAddressModeV(descriptor->addressModeV), mAddressModeV(descriptor->addressModeV),
@ -83,14 +85,28 @@ namespace dawn_native {
mMaxAnisotropy(descriptor->maxAnisotropy) { 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) SamplerBase::SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag)
: ApiObjectBase(device, tag) { : ApiObjectBase(device, tag) {
} }
SamplerBase::~SamplerBase() { SamplerBase::~SamplerBase() = default;
if (IsCachedReference()) {
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); GetDevice()->UncacheSampler(this);
} }
return wasDestroyed;
} }
// static // static

View File

@ -30,11 +30,15 @@ namespace dawn_native {
class SamplerBase : public ApiObjectBase, public CachedObject { class SamplerBase : public ApiObjectBase, public CachedObject {
public: public:
SamplerBase(DeviceBase* device,
const SamplerDescriptor* descriptor,
ApiObjectBase::UntrackedByDeviceTag tag);
SamplerBase(DeviceBase* device, const SamplerDescriptor* descriptor); SamplerBase(DeviceBase* device, const SamplerDescriptor* descriptor);
~SamplerBase() override; ~SamplerBase() override;
static SamplerBase* MakeError(DeviceBase* device); static SamplerBase* MakeError(DeviceBase* device);
bool DestroyApiObject() override;
ObjectType GetType() const override; ObjectType GetType() const override;
bool IsComparison() const; bool IsComparison() const;
@ -51,6 +55,10 @@ namespace dawn_native {
return mMaxAnisotropy; return mMaxAnisotropy;
} }
protected:
// Constructor used only for mocking and testing.
SamplerBase(DeviceBase* device);
private: private:
SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag); SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag);

View File

@ -1145,7 +1145,9 @@ namespace dawn_native {
// ShaderModuleBase // 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) { : ApiObjectBase(device, descriptor->label), mType(Type::Undefined) {
ASSERT(descriptor->nextInChain != nullptr); ASSERT(descriptor->nextInChain != nullptr);
const ShaderModuleSPIRVDescriptor* spirvDesc = 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) ShaderModuleBase::ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag)
: ApiObjectBase(device, tag), mType(Type::Undefined) { : ApiObjectBase(device, tag), mType(Type::Undefined) {
} }
ShaderModuleBase::~ShaderModuleBase() { ShaderModuleBase::~ShaderModuleBase() = default;
if (IsCachedReference()) {
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); GetDevice()->UncacheShaderModule(this);
} }
return wasDestroyed;
} }
// static // static

View File

@ -221,11 +221,15 @@ namespace dawn_native {
class ShaderModuleBase : public ApiObjectBase, public CachedObject { class ShaderModuleBase : public ApiObjectBase, public CachedObject {
public: public:
ShaderModuleBase(DeviceBase* device,
const ShaderModuleDescriptor* descriptor,
ApiObjectBase::UntrackedByDeviceTag tag);
ShaderModuleBase(DeviceBase* device, const ShaderModuleDescriptor* descriptor); ShaderModuleBase(DeviceBase* device, const ShaderModuleDescriptor* descriptor);
~ShaderModuleBase() override; ~ShaderModuleBase() override;
static Ref<ShaderModuleBase> MakeError(DeviceBase* device); static Ref<ShaderModuleBase> MakeError(DeviceBase* device);
bool DestroyApiObject() override;
ObjectType GetType() const override; ObjectType GetType() const override;
// Return true iff the program has an entrypoint called `entryPoint`. // Return true iff the program has an entrypoint called `entryPoint`.
@ -252,6 +256,9 @@ namespace dawn_native {
OwnedCompilationMessages* GetCompilationMessages() const; OwnedCompilationMessages* GetCompilationMessages() const;
protected: protected:
// Constructor used only for mocking and testing.
ShaderModuleBase(DeviceBase* device);
MaybeError InitializeBase(ShaderModuleParseResult* parseResult); MaybeError InitializeBase(ShaderModuleParseResult* parseResult);
private: private:

View File

@ -115,6 +115,7 @@ namespace dawn_native {
// SwapChainBase // SwapChainBase
SwapChainBase::SwapChainBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { SwapChainBase::SwapChainBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) {
TrackInDevice();
} }
SwapChainBase::SwapChainBase(DeviceBase* device, ObjectBase::ErrorTag tag) SwapChainBase::SwapChainBase(DeviceBase* device, ObjectBase::ErrorTag tag)

View File

@ -138,9 +138,20 @@ source_set("dawn_native_mocks_sources") {
"${dawn_root}/src/utils:dawn_utils", "${dawn_root}/src/utils:dawn_utils",
] ]
# Add internal dawn_native config for internal unittests.
configs += [ "${dawn_root}/src/dawn_native:dawn_native_internal" ]
sources = [ sources = [
"unittests/native/mocks/BindGroupLayoutMock.h", "unittests/native/mocks/BindGroupLayoutMock.h",
"unittests/native/mocks/BindGroupMock.h",
"unittests/native/mocks/ComputePipelineMock.h",
"unittests/native/mocks/DeviceMock.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",
] ]
} }

View File

@ -16,80 +16,453 @@
#include "dawn_native/Toggles.h" #include "dawn_native/Toggles.h"
#include "mocks/BindGroupLayoutMock.h" #include "mocks/BindGroupLayoutMock.h"
#include "mocks/BindGroupMock.h"
#include "mocks/ComputePipelineMock.h"
#include "mocks/DeviceMock.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 "tests/DawnNativeTest.h"
#include "utils/ComboRenderPipelineDescriptor.h"
namespace dawn_native { namespace { namespace dawn_native { namespace {
using ::testing::_;
using ::testing::ByMove; using ::testing::ByMove;
using ::testing::InSequence; using ::testing::InSequence;
using ::testing::Return; using ::testing::Return;
using ::testing::Test;
TEST(DestroyObjectTests, BindGroupLayoutExplicit) { class DestroyObjectTests : public Test {
// Skipping validation on descriptors as coverage for validation is already present. public:
DeviceMock device; DestroyObjectTests() : Test() {
device.SetToggle(Toggle::SkipValidation, true); // Skipping validation on descriptors as coverage for validation is already present.
mDevice.SetToggle(Toggle::SkipValidation, true);
}
BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&device); Ref<PipelineLayoutMock> GetPipelineLayout() {
EXPECT_CALL(*bindGroupLayoutMock, DestroyApiObjectImpl).Times(1); if (mPipelineLayout != nullptr) {
return mPipelineLayout;
}
mPipelineLayout = AcquireRef(new PipelineLayoutMock(&mDevice));
EXPECT_CALL(*mPipelineLayout.Get(), DestroyApiObjectImpl).Times(1);
return mPipelineLayout;
}
BindGroupLayoutDescriptor desc = {}; Ref<ShaderModuleMock> GetVertexShaderModule() {
Ref<BindGroupLayoutBase> bindGroupLayout; if (mVsModule != nullptr) {
EXPECT_CALL(device, CreateBindGroupLayoutImpl) return mVsModule;
.WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock)))); }
DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, device.CreateBindGroupLayout(&desc)); 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(), DestroyApiObjectImpl).Times(1);
return mVsModule;
}
EXPECT_TRUE(bindGroupLayout->IsAlive()); Ref<ShaderModuleMock> GetComputeShaderModule() {
EXPECT_TRUE(bindGroupLayout->IsCachedReference()); 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(); protected:
EXPECT_FALSE(bindGroupLayout->IsAlive()); DeviceMock mDevice;
// The following lazy-initialized objects are used to facilitate creation of dependent
// objects under test.
Ref<PipelineLayoutMock> mPipelineLayout;
Ref<ShaderModuleMock> mVsModule;
Ref<ShaderModuleMock> 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 // 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. // will also complain if there is a memory leak.
TEST(DestroyObjectTests, BindGroupLayoutImplicit) { TEST_F(DestroyObjectTests, BindGroupImplicit) {
// Skipping validation on descriptors as coverage for validation is already present. BindGroupMock* bindGroupMock = new BindGroupMock(&mDevice);
DeviceMock device; EXPECT_CALL(*bindGroupMock, DestroyApiObjectImpl).Times(1);
device.SetToggle(Toggle::SkipValidation, true); {
BindGroupDescriptor desc = {};
Ref<BindGroupBase> 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); EXPECT_CALL(*bindGroupLayoutMock, DestroyApiObjectImpl).Times(1);
{ {
BindGroupLayoutDescriptor desc = {}; BindGroupLayoutDescriptor desc = {};
Ref<BindGroupLayoutBase> bindGroupLayout; Ref<BindGroupLayoutBase> bindGroupLayout;
EXPECT_CALL(device, CreateBindGroupLayoutImpl) EXPECT_CALL(mDevice, CreateBindGroupLayoutImpl)
.WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock)))); .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->IsAlive());
EXPECT_TRUE(bindGroupLayout->IsCachedReference()); EXPECT_TRUE(bindGroupLayout->IsCachedReference());
} }
} }
// Destroying the objects on the device should result in all created objects being destroyed in TEST_F(DestroyObjectTests, ComputePipelineExplicit) {
// order. ComputePipelineMock computePipelineMock(&mDevice);
TEST(DestroyObjectTests, DestroyObjects) { EXPECT_CALL(computePipelineMock, DestroyApiObjectImpl).Times(1);
DeviceMock device;
device.SetToggle(Toggle::SkipValidation, true);
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<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, 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<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, 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<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, 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<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, 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<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, 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<SwapChainBase> 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; 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(*bindGroupLayoutMock, DestroyApiObjectImpl).Times(1);
EXPECT_CALL(*shaderModuleMock, DestroyApiObjectImpl).Times(1);
EXPECT_CALL(*samplerMock, DestroyApiObjectImpl).Times(1);
} }
BindGroupLayoutDescriptor desc = {}; Ref<BindGroupBase> bindGroup;
Ref<BindGroupLayoutBase> bindGroupLayout; {
EXPECT_CALL(device, CreateBindGroupLayoutImpl) BindGroupDescriptor desc = {};
.WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock)))); EXPECT_CALL(mDevice, CreateBindGroupImpl)
DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, device.CreateBindGroupLayout(&desc)); .WillOnce(Return(ByMove(AcquireRef(bindGroupMock))));
EXPECT_TRUE(bindGroupLayout->IsAlive()); DAWN_ASSERT_AND_ASSIGN(bindGroup, mDevice.CreateBindGroup(&desc));
EXPECT_TRUE(bindGroupLayout->IsCachedReference()); EXPECT_TRUE(bindGroup->IsAlive());
}
device.DestroyObjects(); 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<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<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<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());
}
mDevice.DestroyObjects();
EXPECT_FALSE(bindGroup->IsAlive());
EXPECT_FALSE(bindGroupLayout->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:: }} // namespace dawn_native::

View File

@ -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 <gmock/gmock.h>
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_

View File

@ -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 <gmock/gmock.h>
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_

View File

@ -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 <gmock/gmock.h>
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_

View File

@ -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 <gmock/gmock.h>
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_

View File

@ -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 <gmock/gmock.h>
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_

View File

@ -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<Ref<ShaderModuleMock>> 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

View File

@ -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/Device.h"
#include "dawn_native/Error.h"
#include "dawn_native/ShaderModule.h"
#include <memory>
#include <gmock/gmock.h>
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<Ref<ShaderModuleMock>> Create(DeviceBase* device, const char* source);
};
} // namespace dawn_native
#endif // TESTS_UNITTESTS_NATIVE_MOCKS_SHADERMODULE_MOCK_H_

View File

@ -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 <gmock/gmock.h>
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_