Metal: track buffer allocations (#7556)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user