Metal: track buffer allocations (#7556)

This commit is contained in:
Ben Doherty
2024-03-22 12:47:56 -07:00
committed by GitHub
parent 9aaaad9271
commit 317c1bb7ea
14 changed files with 136 additions and 16 deletions
@@ -193,9 +193,40 @@ public:
size_t retrieveBlob(const void* UTILS_NONNULL key, size_t keySize,
void* UTILS_NONNULL value, size_t valueSize);
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key, uint64_t value)>;
/**
* Sets the callback function that the backend can use to update backend-specific statistics
* to aid with debugging. This callback is guaranteed to be called on the Filament driver
* thread.
*
* @param debugUpdateStat an Invocable that updates debug statistics
*/
void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept;
/**
* @return true if debugUpdateStat is valid.
*/
bool hasDebugUpdateStatFunc() const noexcept;
/**
* To track backend-specific statistics, the backend implementation can call the
* application-provided callback function debugUpdateStatFunc to associate or update a value
* with a given key. It is possible for this function to be called multiple times with the
* same key, in which case newer values should overwrite older values.
*
* This function is guaranteed to be called only on a single thread, the Filament driver
* thread.
*
* @param key a null-terminated C-string with the key of the debug statistic
* @param value the updated value of key
*/
void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t value);
private:
InsertBlobFunc mInsertBlob;
RetrieveBlobFunc mRetrieveBlob;
DebugUpdateStatFunc mDebugUpdateStat;
};
} // namespace filament
+14
View File
@@ -53,4 +53,18 @@ size_t Platform::retrieveBlob(void const* key, size_t keySize, void* value, size
return 0;
}
void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept {
mDebugUpdateStat = std::move(debugUpdateStat);
}
bool Platform::hasDebugUpdateStatFunc() const noexcept {
return bool(mDebugUpdateStat);
}
void Platform::debugUpdateStat(const char* key, uint64_t value) {
if (mDebugUpdateStat) {
mDebugUpdateStat(key, value);
}
}
} // namespace filament::backend
+47 -7
View File
@@ -18,7 +18,6 @@
#define TNT_FILAMENT_DRIVER_METALBUFFER_H
#include "MetalContext.h"
#include "MetalBufferPool.h"
#include <backend/DriverEnums.h>
@@ -28,9 +27,50 @@
#include <utility>
#include <memory>
#include <atomic>
namespace filament::backend {
class TrackedMetalBuffer {
public:
TrackedMetalBuffer() noexcept : mBuffer(nil) {}
TrackedMetalBuffer(id<MTLBuffer> buffer) noexcept : mBuffer(buffer) {
if (buffer) {
aliveBuffers++;
}
}
~TrackedMetalBuffer() {
if (mBuffer) {
aliveBuffers--;
}
}
TrackedMetalBuffer(TrackedMetalBuffer&&) = delete;
TrackedMetalBuffer(TrackedMetalBuffer const&) = delete;
TrackedMetalBuffer& operator=(TrackedMetalBuffer const&) = delete;
TrackedMetalBuffer& operator=(TrackedMetalBuffer&& rhs) noexcept {
swap(rhs);
return *this;
}
id<MTLBuffer> get() const noexcept { return mBuffer; }
operator bool() const noexcept { return bool(mBuffer); }
static uint64_t getAliveBuffers() { return aliveBuffers; }
private:
void swap(TrackedMetalBuffer& other) noexcept {
id<MTLBuffer> temp = mBuffer;
mBuffer = other.mBuffer;
other.mBuffer = temp;
}
id<MTLBuffer> mBuffer;
static std::atomic<uint64_t> aliveBuffers;
};
class MetalBuffer {
public:
@@ -82,7 +122,7 @@ public:
private:
id<MTLBuffer> mBuffer = nil;
TrackedMetalBuffer mBuffer;
size_t mBufferSize = 0;
void* mCpuBuffer = nullptr;
MetalContext& mContext;
@@ -151,7 +191,7 @@ public:
// finishes executing.
mAuxBuffer = [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions];
assert_invariant(mAuxBuffer);
return {mAuxBuffer, 0};
return {mAuxBuffer.get(), 0};
}
mCurrentSlot = (mCurrentSlot + 1) % mSlotCount;
mOccupiedSlots->fetch_add(1, std::memory_order_relaxed);
@@ -180,9 +220,9 @@ public:
*/
std::pair<id<MTLBuffer>, NSUInteger> getCurrentAllocation() const {
if (UTILS_UNLIKELY(mAuxBuffer)) {
return { mAuxBuffer, 0 };
return { mAuxBuffer.get(), 0 };
}
return { mBuffer, mCurrentSlot * mSlotSizeBytes };
return { mBuffer.get(), mCurrentSlot * mSlotSizeBytes };
}
bool canAccomodateLayout(MTLSizeAndAlign layout) const {
@@ -191,8 +231,8 @@ public:
private:
id<MTLDevice> mDevice;
id<MTLBuffer> mBuffer;
id<MTLBuffer> mAuxBuffer;
TrackedMetalBuffer mBuffer;
TrackedMetalBuffer mAuxBuffer;
MTLResourceOptions mBufferOptions;
+7 -4
View File
@@ -15,12 +15,15 @@
*/
#include "MetalBuffer.h"
#include "MetalBufferPool.h"
#include "MetalContext.h"
namespace filament {
namespace backend {
std::atomic<uint64_t> TrackedMetalBuffer::aliveBuffers = 0;
MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) {
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
@@ -61,7 +64,7 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
// Acquire a staging buffer to hold the contents of this update.
MetalBufferPool* bufferPool = mContext.bufferPool;
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
memcpy(staging->buffer.contents, src, size);
memcpy(staging->buffer.get().contents, src, size);
// The blit below requires that byteOffset be a multiple of 4.
ASSERT_PRECONDITION(!(byteOffset & 0x3u), "byteOffset must be a multiple of 4");
@@ -70,9 +73,9 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
blitEncoder.label = @"Buffer upload blit";
[blitEncoder copyFromBuffer:staging->buffer
[blitEncoder copyFromBuffer:staging->buffer.get()
sourceOffset:0
toBuffer:mBuffer
toBuffer:mBuffer.get()
destinationOffset:byteOffset
size:size];
[blitEncoder endEncoding];
@@ -93,7 +96,7 @@ id<MTLBuffer> MetalBuffer::getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) n
return nil;
}
assert_invariant(mBuffer);
return mBuffer;
return mBuffer.get();
}
void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncoder> encoder,
+3 -1
View File
@@ -19,6 +19,8 @@
#include <Metal/Metal.h>
#include "MetalBuffer.h"
#include <map>
#include <mutex>
#include <unordered_set>
@@ -30,7 +32,7 @@ struct MetalContext;
// Immutable POD representing a shared CPU-GPU buffer.
struct MetalBufferPoolEntry {
id<MTLBuffer> buffer;
TrackedMetalBuffer buffer;
size_t capacity;
mutable uint64_t lastAccessed;
mutable uint32_t referenceCount;
@@ -45,12 +45,12 @@ MetalBufferPoolEntry const* MetalBufferPool::acquireBuffer(size_t numBytes) {
id<MTLBuffer> buffer = [mContext.device newBufferWithLength:numBytes
options:MTLResourceStorageModeShared];
ASSERT_POSTCONDITION(buffer, "Could not allocate Metal staging buffer of size %zu.", numBytes);
MetalBufferPoolEntry* stage = new MetalBufferPoolEntry({
MetalBufferPoolEntry* stage = new MetalBufferPoolEntry {
.buffer = buffer,
.capacity = numBytes,
.lastAccessed = mCurrentFrame,
.referenceCount = 1
});
};
mUsedStages.insert(stage);
return stage;
@@ -20,6 +20,7 @@
#include "metal/MetalDriver.h"
#include "MetalBlitter.h"
#include "MetalBufferPool.h"
#include "MetalContext.h"
#include "MetalDriverFactory.h"
#include "MetalEnums.h"
@@ -36,6 +37,7 @@
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/sstream.h>
#include <algorithm>
@@ -214,6 +216,9 @@ void MetalDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
#if defined(FILAMENT_METAL_PROFILING)
os_signpost_interval_begin(mContext->log, mContext->signpostId, "Frame encoding", "%{public}d", frameId);
#endif
if (mPlatform.hasDebugUpdateStatFunc()) {
mPlatform.debugUpdateStat("filament.metal.alive_buffers", TrackedMetalBuffer::getAliveBuffers());
}
}
void MetalDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
+3 -2
View File
@@ -19,6 +19,7 @@
#include "MetalBlitter.h"
#include "MetalEnums.h"
#include "MetalUtils.h"
#include "MetalBufferPool.h"
#include <filament/SwapChain.h>
@@ -770,13 +771,13 @@ void MetalTexture::loadWithCopyBuffer(uint32_t level, uint32_t slice, MTLRegion
PixelBufferDescriptor const& data, const PixelBufferShape& shape) {
const size_t stagingBufferSize = shape.totalBytes;
auto entry = context.bufferPool->acquireBuffer(stagingBufferSize);
memcpy(entry->buffer.contents,
memcpy(entry->buffer.get().contents,
static_cast<uint8_t*>(data.buffer) + shape.sourceOffset,
stagingBufferSize);
id<MTLCommandBuffer> blitCommandBuffer = getPendingCommandBuffer(&context);
id<MTLBlitCommandEncoder> blitCommandEncoder = [blitCommandBuffer blitCommandEncoder];
blitCommandEncoder.label = @"Texture upload buffer blit";
[blitCommandEncoder copyFromBuffer:entry->buffer
[blitCommandEncoder copyFromBuffer:entry->buffer.get()
sourceOffset:0
sourceBytesPerRow:shape.bytesPerRow
sourceBytesPerImage:shape.bytesPerSlice
+1
View File
@@ -674,6 +674,7 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
mSwapChain = mFilamentApp->mEngine->createSwapChain(
nativeSwapChain, filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
}
mRenderer = mFilamentApp->mEngine->createRenderer();
// create cameras
+1
View File
@@ -92,6 +92,7 @@ protected:
std::pair<char*, size_t> grow(size_t s) noexcept;
void advance(ssize_t n) noexcept;
void reset() noexcept;
size_t length() const noexcept;
private:
void reserve(size_t newSize) noexcept;
+1
View File
@@ -25,6 +25,7 @@ class sstream : public ostream {
public:
ostream& flush() noexcept override;
const char* c_str() const noexcept;
size_t length() const noexcept;
};
} // namespace utils::io
+4
View File
@@ -267,6 +267,10 @@ void ostream::Buffer::reset() noexcept {
size = capacity;
}
size_t ostream::Buffer::length() const noexcept {
return curr - buffer;
}
std::pair<char*, size_t> ostream::Buffer::grow(size_t s) noexcept {
if (UTILS_UNLIKELY(size < s)) {
size_t const used = curr - buffer;
+4
View File
@@ -28,4 +28,8 @@ const char* sstream::c_str() const noexcept {
return buffer ? buffer : "";
}
size_t sstream::length() const noexcept {
return getBuffer().length();
}
} // namespace utils::io
+13
View File
@@ -144,6 +144,7 @@ TEST(sstream, LargeBuffer) {
}
EXPECT_EQ(1024 * 1024 * 16, strlen(ss.c_str()));
EXPECT_EQ(1024 * 1024 * 16, ss.length());
}
TEST(sstream, LargeString) {
@@ -158,6 +159,7 @@ TEST(sstream, LargeString) {
ss << filler;
EXPECT_EQ(size, strlen(ss.c_str()));
EXPECT_EQ(size, ss.length());
EXPECT_STREQ(filler, ss.c_str());
free(filler);
@@ -182,7 +184,18 @@ TEST(sstream, SeveralStrings) {
ss << fillerB;
EXPECT_EQ(sizeA + sizeB, strlen(ss.c_str()));
EXPECT_EQ(sizeA + sizeB, ss.length());
free(fillerA);
free(fillerB);
}
TEST(sstream, length) {
sstream ss;
EXPECT_EQ(0, ss.length());
ss << "Hello, world\n";
EXPECT_EQ(13, ss.length());
ss << "Foo bar\n";
EXPECT_EQ(13 + 8, ss.length());
}