diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index f144979544..af99d4e2be 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -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; + + /** + * 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 diff --git a/filament/backend/src/Platform.cpp b/filament/backend/src/Platform.cpp index db2fd0eafd..77c8512941 100644 --- a/filament/backend/src/Platform.cpp +++ b/filament/backend/src/Platform.cpp @@ -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 diff --git a/filament/backend/src/metal/MetalBuffer.h b/filament/backend/src/metal/MetalBuffer.h index 4baccffc2c..579975d0d6 100644 --- a/filament/backend/src/metal/MetalBuffer.h +++ b/filament/backend/src/metal/MetalBuffer.h @@ -18,7 +18,6 @@ #define TNT_FILAMENT_DRIVER_METALBUFFER_H #include "MetalContext.h" -#include "MetalBufferPool.h" #include @@ -28,9 +27,50 @@ #include #include +#include namespace filament::backend { +class TrackedMetalBuffer { +public: + TrackedMetalBuffer() noexcept : mBuffer(nil) {} + TrackedMetalBuffer(id 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 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 temp = mBuffer; + mBuffer = other.mBuffer; + other.mBuffer = temp; + } + + id mBuffer; + + static std::atomic aliveBuffers; +}; + class MetalBuffer { public: @@ -82,7 +122,7 @@ public: private: - id 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, 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 mDevice; - id mBuffer; - id mAuxBuffer; + TrackedMetalBuffer mBuffer; + TrackedMetalBuffer mAuxBuffer; MTLResourceOptions mBufferOptions; diff --git a/filament/backend/src/metal/MetalBuffer.mm b/filament/backend/src/metal/MetalBuffer.mm index 4b04e4d5c8..af46027e20 100644 --- a/filament/backend/src/metal/MetalBuffer.mm +++ b/filament/backend/src/metal/MetalBuffer.mm @@ -15,12 +15,15 @@ */ #include "MetalBuffer.h" +#include "MetalBufferPool.h" #include "MetalContext.h" namespace filament { namespace backend { +std::atomic 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 cmdBuffer = getPendingCommandBuffer(&mContext); id 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 MetalBuffer::getGpuBufferForDraw(id cmdBuffer) n return nil; } assert_invariant(mBuffer); - return mBuffer; + return mBuffer.get(); } void MetalBuffer::bindBuffers(id cmdBuffer, id encoder, diff --git a/filament/backend/src/metal/MetalBufferPool.h b/filament/backend/src/metal/MetalBufferPool.h index 68e056ed40..03688ab3c4 100644 --- a/filament/backend/src/metal/MetalBufferPool.h +++ b/filament/backend/src/metal/MetalBufferPool.h @@ -19,6 +19,8 @@ #include +#include "MetalBuffer.h" + #include #include #include @@ -30,7 +32,7 @@ struct MetalContext; // Immutable POD representing a shared CPU-GPU buffer. struct MetalBufferPoolEntry { - id buffer; + TrackedMetalBuffer buffer; size_t capacity; mutable uint64_t lastAccessed; mutable uint32_t referenceCount; diff --git a/filament/backend/src/metal/MetalBufferPool.mm b/filament/backend/src/metal/MetalBufferPool.mm index 29915d4aac..3b75c8e85d 100644 --- a/filament/backend/src/metal/MetalBufferPool.mm +++ b/filament/backend/src/metal/MetalBufferPool.mm @@ -45,12 +45,12 @@ MetalBufferPoolEntry const* MetalBufferPool::acquireBuffer(size_t numBytes) { id 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; diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 1a87b31771..f0e23ccb03 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -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 #include +#include #include @@ -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 sch, diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 18d8311825..0d9976211d 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -19,6 +19,7 @@ #include "MetalBlitter.h" #include "MetalEnums.h" #include "MetalUtils.h" +#include "MetalBufferPool.h" #include @@ -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(data.buffer) + shape.sourceOffset, stagingBufferSize); id blitCommandBuffer = getPendingCommandBuffer(&context); id 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 diff --git a/libs/filamentapp/src/FilamentApp.cpp b/libs/filamentapp/src/FilamentApp.cpp index 25f7126f83..82eba7a231 100644 --- a/libs/filamentapp/src/FilamentApp.cpp +++ b/libs/filamentapp/src/FilamentApp.cpp @@ -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 diff --git a/libs/utils/include/utils/ostream.h b/libs/utils/include/utils/ostream.h index 623cf9c08e..cde8e75bd2 100644 --- a/libs/utils/include/utils/ostream.h +++ b/libs/utils/include/utils/ostream.h @@ -92,6 +92,7 @@ protected: std::pair 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; diff --git a/libs/utils/include/utils/sstream.h b/libs/utils/include/utils/sstream.h index 8b0c4b4a2a..605b678e6d 100644 --- a/libs/utils/include/utils/sstream.h +++ b/libs/utils/include/utils/sstream.h @@ -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 diff --git a/libs/utils/src/ostream.cpp b/libs/utils/src/ostream.cpp index 579dc9e36e..35ffab8434 100644 --- a/libs/utils/src/ostream.cpp +++ b/libs/utils/src/ostream.cpp @@ -267,6 +267,10 @@ void ostream::Buffer::reset() noexcept { size = capacity; } +size_t ostream::Buffer::length() const noexcept { + return curr - buffer; +} + std::pair ostream::Buffer::grow(size_t s) noexcept { if (UTILS_UNLIKELY(size < s)) { size_t const used = curr - buffer; diff --git a/libs/utils/src/sstream.cpp b/libs/utils/src/sstream.cpp index 1c02fd862b..a3cc80a3a7 100644 --- a/libs/utils/src/sstream.cpp +++ b/libs/utils/src/sstream.cpp @@ -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 diff --git a/libs/utils/test/test_sstream.cpp b/libs/utils/test/test_sstream.cpp index fbac624279..9a832faf2e 100644 --- a/libs/utils/test/test_sstream.cpp +++ b/libs/utils/test/test_sstream.cpp @@ -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()); +}