Chromium memory instrumentation support in Dawn
Expose an interface to integrate with Chromium memory instrumentation,
and implement memory dumps for a few important object types like buffers
and textures. These implementations are simplistic e.g. using estimated
byte size for textures, but they work cross-platform and per-platform
implementations can be added later e.g. to dump resource heap usage.
Bug: chromium:330806170
Change-Id: I2b7b5f96268b69100d13b635e26bed5ccbcd9792
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/180180
Commit-Queue: Austin Eng <enga@chromium.org>
Auto-Submit: Sunny Sachanandani <sunnyps@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/include/dawn/native/DawnNative.h b/include/dawn/native/DawnNative.h
index 4b4c147..74749b1 100644
--- a/include/dawn/native/DawnNative.h
+++ b/include/dawn/native/DawnNative.h
@@ -28,6 +28,7 @@
#ifndef INCLUDE_DAWN_NATIVE_DAWNNATIVE_H_
#define INCLUDE_DAWN_NATIVE_DAWNNATIVE_H_
+#include <string>
#include <vector>
#include "dawn/dawn_proc_table.h"
@@ -283,6 +284,30 @@
// name of an feature supported in Dawn.
DAWN_NATIVE_EXPORT const FeatureInfo* GetFeatureInfo(wgpu::FeatureName feature);
+class DAWN_NATIVE_EXPORT MemoryDump {
+ public:
+ // Standard attribute |name|s for the AddScalar() and AddString() methods.
+ // These match the expected names in Chromium memory-infra instrumentation.
+ static const char kNameSize[]; // To represent allocated space.
+ static const char kNameObjectCount[]; // To represent number of objects.
+
+ // Standard attribute |unit|s for the AddScalar() and AddString() methods.
+ // These match the expected names in Chromium memory-infra instrumentation.
+ static const char kUnitsBytes[]; // Unit name to represent bytes.
+ static const char kUnitsObjects[]; // Unit name to represent #objects.
+
+ virtual void AddScalar(const char* name,
+ const char* key,
+ const char* units,
+ uint64_t value) = 0;
+
+ virtual void AddString(const char* name, const char* key, const std::string& value) = 0;
+
+ protected:
+ virtual ~MemoryDump() = default;
+};
+DAWN_NATIVE_EXPORT void DumpMemoryStatistics(WGPUDevice device, MemoryDump* dump);
+
} // namespace dawn::native
#endif // INCLUDE_DAWN_NATIVE_DAWNNATIVE_H_
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index 3efce35..a2d5471 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -892,4 +892,16 @@
return offset == 0 && size == GetSize();
}
+void BufferBase::DumpMemoryStatistics(MemoryDump* dump, const char* prefix) const {
+ // Do not emit for destroyed buffers.
+ if (!IsAlive()) {
+ return;
+ }
+ std::string name = absl::StrFormat("%s/buffer_%p", prefix, static_cast<const void*>(this));
+ dump->AddScalar(name.c_str(), MemoryDump::kNameSize, MemoryDump::kUnitsBytes,
+ GetAllocatedSize());
+ dump->AddString(name.c_str(), "label", GetLabel());
+ dump->AddString(name.c_str(), "usage", absl::StrFormat("%s", GetUsage()));
+}
+
} // namespace dawn::native
diff --git a/src/dawn/native/Buffer.h b/src/dawn/native/Buffer.h
index 6204fff..e87ee17 100644
--- a/src/dawn/native/Buffer.h
+++ b/src/dawn/native/Buffer.h
@@ -114,6 +114,8 @@
void* GetMappedRange(size_t offset, size_t size, bool writable = true);
MaybeError Unmap();
+ void DumpMemoryStatistics(dawn::native::MemoryDump* dump, const char* prefix) const;
+
// Dawn API
void APIMapAsync(wgpu::MapMode mode,
size_t offset,
diff --git a/src/dawn/native/DawnNative.cpp b/src/dawn/native/DawnNative.cpp
index 0ba5011..9ac41fa 100644
--- a/src/dawn/native/DawnNative.cpp
+++ b/src/dawn/native/DawnNative.cpp
@@ -42,6 +42,11 @@
namespace dawn::native {
+const char MemoryDump::kNameSize[] = "size";
+const char MemoryDump::kNameObjectCount[] = "object_count";
+const char MemoryDump::kUnitsBytes[] = "bytes";
+const char MemoryDump::kUnitsObjects[] = "objects";
+
const DawnProcTable& GetProcsAutogen();
const DawnProcTable& GetProcs() {
@@ -303,4 +308,8 @@
return &kFeatureNameAndInfoList[FromAPI(feature)];
}
+void DumpMemoryStatistics(WGPUDevice device, MemoryDump* dump) {
+ FromAPI(device)->DumpMemoryStatistics(dump);
+}
+
} // namespace dawn::native
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 76ab25b..9f7eda1 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -947,6 +947,10 @@
return &mObjectLists[type];
}
+const ApiObjectList* DeviceBase::GetObjectTrackingList(ObjectType type) const {
+ return &mObjectLists[type];
+}
+
InstanceBase* DeviceBase::GetInstance() const {
return mAdapter->GetPhysicalDevice()->GetInstance();
}
@@ -2335,6 +2339,11 @@
mToggles.ForceSet(toggle, isEnabled);
}
+void DeviceBase::ForceEnableFeatureForTesting(Feature feature) {
+ mEnabledFeatures.EnableFeature(feature);
+ mFormatTable = BuildFormatTable(this);
+}
+
void DeviceBase::FlushCallbackTaskQueue() {
// Callbacks might cause re-entrances. Mutex shouldn't be locked. So we expect there is no
// locked mutex before entering this method.
@@ -2468,6 +2477,16 @@
return mMutex == nullptr || mMutex->IsLockedByCurrentThread();
}
+void DeviceBase::DumpMemoryStatistics(dawn::native::MemoryDump* dump) const {
+ std::string prefix = absl::StrFormat("device_%p", static_cast<const void*>(this));
+ GetObjectTrackingList(ObjectType::Texture)->ForEach([&](const ApiObjectBase* texture) {
+ static_cast<const TextureBase*>(texture)->DumpMemoryStatistics(dump, prefix.c_str());
+ });
+ GetObjectTrackingList(ObjectType::Buffer)->ForEach([&](const ApiObjectBase* buffer) {
+ static_cast<const BufferBase*>(buffer)->DumpMemoryStatistics(dump, prefix.c_str());
+ });
+}
+
IgnoreLazyClearCountScope::IgnoreLazyClearCountScope(DeviceBase* device)
: mDevice(device), mLazyClearCountForTesting(device->mLazyClearCountForTesting) {}
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 5d2d0d7..c74fe8f 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -345,6 +345,7 @@
State GetState() const;
bool IsLost() const;
ApiObjectList* GetObjectTrackingList(ObjectType type);
+ const ApiObjectList* GetObjectTrackingList(ObjectType type) const;
std::vector<const char*> GetTogglesUsed() const;
const tint::wgsl::AllowedFeatures& GetWGSLAllowedFeatures() const;
@@ -446,11 +447,14 @@
Ref<ComputePipelineBase> computePipeline);
Ref<RenderPipelineBase> AddOrGetCachedRenderPipeline(Ref<RenderPipelineBase> renderPipeline);
+ void DumpMemoryStatistics(dawn::native::MemoryDump* dump) const;
+
protected:
// Constructor used only for mocking and testing.
DeviceBase();
void ForceSetToggleForTesting(Toggle toggle, bool isEnabled);
+ void ForceEnableFeatureForTesting(Feature feature);
MaybeError Initialize(Ref<QueueBase> defaultQueue);
void DestroyObjects();
diff --git a/src/dawn/native/ObjectBase.cpp b/src/dawn/native/ObjectBase.cpp
index 8d89660..d4a89e3 100644
--- a/src/dawn/native/ObjectBase.cpp
+++ b/src/dawn/native/ObjectBase.cpp
@@ -59,27 +59,23 @@
}
void ApiObjectList::Track(ApiObjectBase* object) {
- if (mMarkedDestroyed) {
+ if (mMarkedDestroyed.load(std::memory_order_acquire)) {
object->DestroyImpl();
return;
}
- std::lock_guard<std::mutex> lock(mMutex);
- mObjects.Prepend(object);
+ mObjects.Use([&object](auto lockedObjects) { lockedObjects->Prepend(object); });
}
bool ApiObjectList::Untrack(ApiObjectBase* object) {
- std::lock_guard<std::mutex> lock(mMutex);
- return object->RemoveFromList();
+ return mObjects.Use([&object](auto lockedObjects) { return object->RemoveFromList(); });
}
void ApiObjectList::Destroy() {
LinkedList<ApiObjectBase> objects;
- {
- std::lock_guard<std::mutex> lock(mMutex);
- mMarkedDestroyed = true;
- mObjects.MoveInto(&objects);
- }
-
+ mObjects.Use([&objects, this](auto lockedObjects) {
+ mMarkedDestroyed.store(true, std::memory_order_release);
+ lockedObjects->MoveInto(&objects);
+ });
while (!objects.empty()) {
auto* head = objects.head();
bool removed = head->RemoveFromList();
diff --git a/src/dawn/native/ObjectBase.h b/src/dawn/native/ObjectBase.h
index 6ac6ac9..a2bfbd2 100644
--- a/src/dawn/native/ObjectBase.h
+++ b/src/dawn/native/ObjectBase.h
@@ -32,6 +32,7 @@
#include <string>
#include "dawn/common/LinkedList.h"
+#include "dawn/common/MutexProtected.h"
#include "dawn/common/Ref.h"
#include "dawn/common/RefCounted.h"
#include "dawn/native/Forward.h"
@@ -98,12 +99,21 @@
// Destroys and removes all the objects tracked in the list.
void Destroy();
+ template <typename F>
+ void ForEach(F fn) const {
+ mObjects.Use([&fn](const auto lockedObjects) {
+ for (const auto* node = lockedObjects->head(); node != lockedObjects->end();
+ node = node->next()) {
+ fn(node->value());
+ }
+ });
+ }
+
private:
// Boolean used to mark the list so that on subsequent calls to Untrack, we don't need to
- // reaquire the lock, and Track on new objects immediately destroys them.
- bool mMarkedDestroyed = false;
- std::mutex mMutex;
- LinkedList<ApiObjectBase> mObjects;
+ // reacquire the lock, and Track on new objects immediately destroys them.
+ std::atomic<bool> mMarkedDestroyed{false};
+ MutexProtected<LinkedList<ApiObjectBase>> mObjects;
};
class ApiObjectBase : public ObjectBase, public LinkNode<ApiObjectBase> {
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index b15bbe8..2c6e6e3 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -1175,6 +1175,41 @@
return (GetUsage() & wgpu::TextureUsage::TextureBinding) != 0;
}
+void TextureBase::SetSharedResourceMemoryContentsForTesting(
+ Ref<SharedResourceMemoryContents> contents) {
+ mSharedResourceMemoryContents = std::move(contents);
+}
+
+void TextureBase::DumpMemoryStatistics(dawn::native::MemoryDump* dump, const char* prefix) const {
+ // Do not emit for destroyed textures or textures that wrap external shared texture memory.
+ if (!IsAlive() || GetSharedResourceMemoryContents() != nullptr) {
+ return;
+ }
+ std::string name = absl::StrFormat("%s/texture_%p", prefix, static_cast<const void*>(this));
+ dump->AddScalar(name.c_str(), MemoryDump::kNameSize, MemoryDump::kUnitsBytes,
+ ComputeEstimatedByteSize());
+ dump->AddString(name.c_str(), "label", GetLabel());
+ dump->AddString(name.c_str(), "dimensions", GetSizeLabel());
+ dump->AddString(name.c_str(), "format", absl::StrFormat("%s", GetFormat().format));
+}
+
+uint64_t TextureBase::ComputeEstimatedByteSize() const {
+ DAWN_ASSERT(!IsError());
+ uint64_t byteSize = 0;
+ for (Aspect aspect : IterateEnumMask(SelectFormatAspects(*mFormat, wgpu::TextureAspect::All))) {
+ const AspectInfo& info = mFormat->GetAspectInfo(aspect);
+ for (uint32_t i = 0; i < mMipLevelCount; i++) {
+ Extent3D mipSize = GetMipLevelSingleSubresourcePhysicalSize(i, aspect);
+ byteSize += (mipSize.width / info.block.width) * (mipSize.height / info.block.height) *
+ info.block.byteSize * mSampleCount;
+ }
+ }
+ if (mDimension == wgpu::TextureDimension::e2D) {
+ byteSize *= mBaseSize.depthOrArrayLayers;
+ }
+ return byteSize;
+}
+
void TextureBase::APIDestroy() {
Destroy();
}
diff --git a/src/dawn/native/Texture.h b/src/dawn/native/Texture.h
index 3180200..ad1f9f9 100644
--- a/src/dawn/native/Texture.h
+++ b/src/dawn/native/Texture.h
@@ -153,6 +153,8 @@
bool IsImplicitMSAARenderTextureViewSupported() const;
+ void DumpMemoryStatistics(dawn::native::MemoryDump* dump, const char* prefix) const;
+
// Dawn API
TextureViewBase* APICreateView(const TextureViewDescriptor* descriptor = nullptr);
TextureViewBase* APICreateErrorView(const TextureViewDescriptor* descriptor = nullptr);
@@ -172,6 +174,7 @@
void DestroyImpl() override;
void AddInternalUsage(wgpu::TextureUsage usage);
+ void SetSharedResourceMemoryContentsForTesting(Ref<SharedResourceMemoryContents> contents);
private:
struct TextureState {
@@ -187,6 +190,8 @@
std::string GetSizeLabel() const;
+ uint64_t ComputeEstimatedByteSize() const;
+
wgpu::TextureDimension mDimension;
wgpu::TextureViewDimension
mCompatibilityTextureBindingViewDimension; // only used for compatibility mode
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 036ade7..abb1118 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -372,6 +372,7 @@
"unittests/native/DeviceAsyncTaskTests.cpp",
"unittests/native/DeviceCreationTests.cpp",
"unittests/native/LimitsTests.cpp",
+ "unittests/native/MemoryInstrumentationTests.cpp",
"unittests/native/ObjectContentHasherTests.cpp",
"unittests/native/StreamTests.cpp",
"unittests/validation/BindGroupValidationTests.cpp",
diff --git a/src/dawn/tests/unittests/native/MemoryInstrumentationTests.cpp b/src/dawn/tests/unittests/native/MemoryInstrumentationTests.cpp
new file mode 100644
index 0000000..8f32282
--- /dev/null
+++ b/src/dawn/tests/unittests/native/MemoryInstrumentationTests.cpp
@@ -0,0 +1,183 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <string>
+#include <utility>
+
+#include "dawn/native/SharedResourceMemory.h"
+#include "dawn/tests/unittests/native/mocks/BufferMock.h"
+#include "dawn/tests/unittests/native/mocks/DawnMockTest.h"
+#include "dawn/tests/unittests/native/mocks/TextureMock.h"
+
+namespace dawn::native {
+namespace {
+
+using ::testing::ByMove;
+using ::testing::NiceMock;
+using ::testing::Return;
+using ::testing::StrEq;
+
+class MemoryDumpMock : public MemoryDump {
+ public:
+ MemoryDumpMock() {
+ ON_CALL(*this, AddScalar)
+ .WillByDefault(
+ [this](const char* name, const char* key, const char* units, uint64_t value) {
+ if (key == MemoryDump::kNameSize && units == MemoryDump::kUnitsBytes) {
+ mTotalSize += value;
+ }
+ });
+ }
+
+ MOCK_METHOD(void,
+ AddScalar,
+ (const char* name, const char* key, const char* units, uint64_t value),
+ (override));
+
+ void AddString(const char* name, const char* key, const std::string& value) override {}
+
+ uint64_t GetTotalSize() const { return mTotalSize; }
+
+ private:
+ uint64_t mTotalSize = 0;
+};
+
+using MemoryInstrumentationTest = DawnMockTest;
+
+TEST_F(MemoryInstrumentationTest, DumpMemoryStatistics) {
+ MemoryDumpMock memoryDumpMock;
+
+ auto textureLabel = [this](const wgpu::Texture& texture) {
+ return absl::StrFormat("device_%p/texture_%p", device.Get(), texture.Get());
+ };
+
+ auto bufferLabel = [this](const wgpu::Buffer& buffer) {
+ return absl::StrFormat("device_%p/buffer_%p", device.Get(), buffer.Get());
+ };
+
+ // Create a buffer and destroy it and check that its size is not counted.
+ constexpr uint64_t kBufferSize = 31;
+ constexpr wgpu::BufferDescriptor kBufferDesc = {
+ .usage = wgpu::BufferUsage::Uniform,
+ .size = kBufferSize,
+ };
+ wgpu::Buffer destroyedBuffer = device.CreateBuffer(&kBufferDesc);
+ EXPECT_TRUE(destroyedBuffer);
+ destroyedBuffer.Destroy();
+
+ // Create a buffer whose allocated size is larger than requested size and check that allocated
+ // size is counted.
+ constexpr uint64_t kBufferAllocatedSize = 32;
+ Ref<BufferMock> bufferMock = AcquireRef(
+ new NiceMock<BufferMock>(mDeviceMock, FromCppAPI(&kBufferDesc), kBufferAllocatedSize));
+ EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(ByMove(std::move(bufferMock))));
+ wgpu::Buffer buffer = device.CreateBuffer(&kBufferDesc);
+
+ EXPECT_CALL(memoryDumpMock, AddScalar(StrEq(bufferLabel(buffer)), MemoryDump::kNameSize,
+ MemoryDump::kUnitsBytes, kBufferAllocatedSize));
+
+ // Create a mip-mapped texture and check that all mip level sizes are counted.
+ constexpr wgpu::TextureFormat kRG8UnormTextureFormat = wgpu::TextureFormat::RG8Unorm;
+ const wgpu::TextureDescriptor kMipmappedTextureDesc = {
+ .usage = wgpu::TextureUsage::RenderAttachment,
+ .size = {.width = 30, .height = 20, .depthOrArrayLayers = 10},
+ .format = kRG8UnormTextureFormat,
+ .mipLevelCount = 5,
+ .viewFormatCount = 1,
+ .viewFormats = &kRG8UnormTextureFormat,
+ };
+ wgpu::Texture mipmappedTexture = device.CreateTexture(&kMipmappedTextureDesc);
+
+ // Byte size of entire mip chain =
+ // ((level0 width * level0 height) + ... + (levelN width * levelN height)) * bpp * array layers.
+ constexpr uint64_t kMipmappedTextureSize =
+ (((30 * 20) + (15 * 10) + (7 * 5) + (3 * 2) + (1 * 1)) * 2) * 10; // 15840
+ EXPECT_CALL(memoryDumpMock,
+ AddScalar(StrEq(textureLabel(mipmappedTexture)), MemoryDump::kNameSize,
+ MemoryDump::kUnitsBytes, kMipmappedTextureSize));
+
+ // Create a multi-sampled texture and check that sample count is taken into account.
+ const wgpu::TextureDescriptor kMultisampleTextureDesc = {
+ .usage = wgpu::TextureUsage::RenderAttachment,
+ .size = {.width = 30, .height = 20},
+ .format = kRG8UnormTextureFormat,
+ .sampleCount = 4,
+ .viewFormatCount = 1,
+ .viewFormats = &kRG8UnormTextureFormat,
+ };
+ wgpu::Texture multisampleTexture = device.CreateTexture(&kMultisampleTextureDesc);
+ // Expected size = width(30) * height(20) * bytes per pixel(2) * sample count(4).
+ constexpr uint64_t kMultisampleTextureSize = 30 * 20 * 2 * 4;
+ EXPECT_CALL(memoryDumpMock,
+ AddScalar(StrEq(textureLabel(multisampleTexture)), MemoryDump::kNameSize,
+ MemoryDump::kUnitsBytes, kMultisampleTextureSize));
+
+ // Create a compressed texture and check that counted size is correct.
+ mDeviceMock->ForceEnableFeatureForTesting(Feature::TextureCompressionETC2);
+ constexpr wgpu::TextureFormat kETC2TextureFormat = wgpu::TextureFormat::ETC2RGB8Unorm;
+ const wgpu::TextureDescriptor kETC2TextureDesc = {
+ .usage = wgpu::TextureUsage::CopySrc,
+ .size = {.width = 32, .height = 32},
+ .format = kETC2TextureFormat,
+ .viewFormatCount = 1,
+ .viewFormats = &kETC2TextureFormat,
+ };
+ wgpu::Texture etc2Texture = device.CreateTexture(&kETC2TextureDesc);
+ // Expected size = (width / block width) * (height / block height) * bytes per block.
+ constexpr uint64_t kETC2TextureSize = (32 / 4) * (32 / 4) * 8;
+ EXPECT_CALL(memoryDumpMock, AddScalar(StrEq(textureLabel(etc2Texture)), MemoryDump::kNameSize,
+ MemoryDump::kUnitsBytes, kETC2TextureSize));
+
+ // Create a texture and destroy it and check that its size is not counted.
+ wgpu::Texture destroyedTexture = device.CreateTexture(&kMipmappedTextureDesc);
+ EXPECT_TRUE(destroyedTexture);
+ destroyedTexture.Destroy();
+
+ // Create a shared resourc memory texture and check that its size is not counted.
+ constexpr wgpu::TextureFormat kRGBA8UnormTextureFormat = wgpu::TextureFormat::RGBA8Unorm;
+ const wgpu::TextureDescriptor kSharedTextureDesc = {
+ .usage = wgpu::TextureUsage::TextureBinding,
+ .size = {.width = 30, .height = 20},
+ .format = kRGBA8UnormTextureFormat,
+ .viewFormatCount = 1,
+ .viewFormats = &kRGBA8UnormTextureFormat,
+ };
+ Ref<TextureMock> textureMock =
+ AcquireRef(new NiceMock<TextureMock>(mDeviceMock, FromCppAPI(&kSharedTextureDesc)));
+ textureMock->SetSharedResourceMemoryContentsForTesting(
+ AcquireRef(new SharedResourceMemoryContents(nullptr)));
+ EXPECT_CALL(*mDeviceMock, CreateTextureImpl).WillOnce(Return(ByMove(std::move(textureMock))));
+ wgpu::Texture sharedTexture = device.CreateTexture(&kSharedTextureDesc);
+
+ DumpMemoryStatistics(device.Get(), &memoryDumpMock);
+
+ EXPECT_EQ(memoryDumpMock.GetTotalSize(), kBufferAllocatedSize + kMipmappedTextureSize +
+ kMultisampleTextureSize + kETC2TextureSize);
+}
+
+} // namespace
+} // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/BufferMock.cpp b/src/dawn/tests/unittests/native/mocks/BufferMock.cpp
index c1f646f..9c8d325 100644
--- a/src/dawn/tests/unittests/native/mocks/BufferMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/BufferMock.cpp
@@ -33,10 +33,13 @@
using ::testing::Return;
-BufferMock::BufferMock(DeviceMock* device, const UnpackedPtr<BufferDescriptor>& descriptor)
+BufferMock::BufferMock(DeviceMock* device,
+ const UnpackedPtr<BufferDescriptor>& descriptor,
+ std::optional<uint64_t> allocatedSize)
: BufferBase(device, descriptor) {
- mBackingData = std::unique_ptr<uint8_t[]>(new uint8_t[GetSize()]);
- mAllocatedSize = GetSize();
+ mAllocatedSize = allocatedSize.value_or(GetSize());
+ DAWN_ASSERT(mAllocatedSize >= GetSize());
+ mBackingData = std::unique_ptr<uint8_t[]>(new uint8_t[mAllocatedSize]);
ON_CALL(*this, DestroyImpl).WillByDefault([this] { this->BufferBase::DestroyImpl(); });
ON_CALL(*this, GetMappedPointer).WillByDefault(Return(mBackingData.get()));
@@ -45,8 +48,10 @@
});
}
-BufferMock::BufferMock(DeviceMock* device, const BufferDescriptor* descriptor)
- : BufferMock(device, Unpack(descriptor)) {}
+BufferMock::BufferMock(DeviceMock* device,
+ const BufferDescriptor* descriptor,
+ std::optional<uint64_t> allocatedSize)
+ : BufferMock(device, Unpack(descriptor), allocatedSize) {}
BufferMock::~BufferMock() = default;
diff --git a/src/dawn/tests/unittests/native/mocks/BufferMock.h b/src/dawn/tests/unittests/native/mocks/BufferMock.h
index 60bcf69..7c6740d 100644
--- a/src/dawn/tests/unittests/native/mocks/BufferMock.h
+++ b/src/dawn/tests/unittests/native/mocks/BufferMock.h
@@ -39,8 +39,12 @@
class BufferMock : public BufferBase {
public:
- BufferMock(DeviceMock* device, const UnpackedPtr<BufferDescriptor>& descriptor);
- BufferMock(DeviceMock* device, const BufferDescriptor* descriptor);
+ BufferMock(DeviceMock* device,
+ const UnpackedPtr<BufferDescriptor>& descriptor,
+ std::optional<uint64_t> allocatedSize = std::nullopt);
+ BufferMock(DeviceMock* device,
+ const BufferDescriptor* descriptor,
+ std::optional<uint64_t> allocatedSize = std::nullopt);
~BufferMock() override;
MOCK_METHOD(void, DestroyImpl, (), (override));
diff --git a/src/dawn/tests/unittests/native/mocks/DeviceMock.h b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
index 6a6a50c..87c41ee 100644
--- a/src/dawn/tests/unittests/native/mocks/DeviceMock.h
+++ b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
@@ -45,6 +45,7 @@
public:
// Exposes some protected functions for testing purposes.
using DeviceBase::DestroyObjects;
+ using DeviceBase::ForceEnableFeatureForTesting;
using DeviceBase::ForceSetToggleForTesting;
// TODO(lokokung): Use real DeviceBase constructor instead of mock specific one.
diff --git a/src/dawn/tests/unittests/native/mocks/TextureMock.h b/src/dawn/tests/unittests/native/mocks/TextureMock.h
index e85c129..5592357 100644
--- a/src/dawn/tests/unittests/native/mocks/TextureMock.h
+++ b/src/dawn/tests/unittests/native/mocks/TextureMock.h
@@ -41,6 +41,8 @@
TextureMock(DeviceMock* device, const TextureDescriptor* descriptor);
~TextureMock() override;
+ using TextureBase::SetSharedResourceMemoryContentsForTesting;
+
MOCK_METHOD(void, DestroyImpl, (), (override));
};