From 3fcaf4525e387697a47af214d0e7aa4fb0a70e45 Mon Sep 17 00:00:00 2001 From: Lumina Wang Date: Tue, 26 Aug 2025 23:38:01 -0400 Subject: [PATCH 1/9] pageable buffer v0 --- include/hvt/pageableBuffer/pageFileManager.h | 99 +++ include/hvt/pageableBuffer/pageableBuffer.h | 201 +++++ .../pageableBuffer/pageableBufferManager.h | 705 ++++++++++++++++++ include/hvt/pageableBuffer/pageableConcepts.h | 210 ++++++ .../hvt/pageableBuffer/pageableDataSource.h | 292 ++++++++ .../pageableBuffer/pageableMemoryMonitor.h | 70 ++ .../hvt/pageableBuffer/pageableStrategies.h | 248 ++++++ source/CMakeLists.txt | 2 + source/pageableBuffer/CMakeLists.txt | 77 ++ source/pageableBuffer/README.md | 329 ++++++++ source/pageableBuffer/pageFileManager.cpp | 329 ++++++++ source/pageableBuffer/pageableBuffer.cpp | 287 +++++++ .../pageableBuffer/pageableBufferManager.cpp | 29 + source/pageableBuffer/pageableDataSource.cpp | 400 ++++++++++ .../pageableBuffer/pageableMemoryMonitor.cpp | 105 +++ source/pageableBuffer/pageableStrategies.cpp | 131 ++++ test/CMakeLists.txt | 5 + test/tests/testPageableBuffer.cpp | 365 +++++++++ 18 files changed, 3884 insertions(+) create mode 100644 include/hvt/pageableBuffer/pageFileManager.h create mode 100644 include/hvt/pageableBuffer/pageableBuffer.h create mode 100644 include/hvt/pageableBuffer/pageableBufferManager.h create mode 100644 include/hvt/pageableBuffer/pageableConcepts.h create mode 100644 include/hvt/pageableBuffer/pageableDataSource.h create mode 100644 include/hvt/pageableBuffer/pageableMemoryMonitor.h create mode 100644 include/hvt/pageableBuffer/pageableStrategies.h create mode 100644 source/pageableBuffer/CMakeLists.txt create mode 100644 source/pageableBuffer/README.md create mode 100644 source/pageableBuffer/pageFileManager.cpp create mode 100644 source/pageableBuffer/pageableBuffer.cpp create mode 100644 source/pageableBuffer/pageableBufferManager.cpp create mode 100644 source/pageableBuffer/pageableDataSource.cpp create mode 100644 source/pageableBuffer/pageableMemoryMonitor.cpp create mode 100644 source/pageableBuffer/pageableStrategies.cpp create mode 100644 test/tests/testPageableBuffer.cpp diff --git a/include/hvt/pageableBuffer/pageFileManager.h b/include/hvt/pageableBuffer/pageFileManager.h new file mode 100644 index 00000000..65994311 --- /dev/null +++ b/include/hvt/pageableBuffer/pageFileManager.h @@ -0,0 +1,99 @@ +// Copyright 2025 Autodesk, Inc. +// +// 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. +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +namespace HVT_NS +{ + +class HdBufferPageHandle; + +struct HVT_API HdFreeListEntry { + std::ptrdiff_t offset = 0; + size_t size = 0; + + HdFreeListEntry() = default; + HdFreeListEntry(std::ptrdiff_t offset, size_t size) : offset(offset), size(size) {} +}; + +class HVT_API HdPageFileEntry { +public: + HdPageFileEntry(const std::string& filename, size_t pageId); + ~HdPageFileEntry(); + + std::ptrdiff_t FindPageFileGap(size_t size); + size_t NextOffset() const { return mNextOffset; } + bool SetNextOffset(std::ptrdiff_t offset); + void AddFreeListEntry(std::ptrdiff_t offset, size_t size); + void ConsolidateFreeList(); + + size_t PageFileId() const { return mPageId; } + size_t SizeLimit() const { return mSizeLimit; } + const std::string& FileName() const { return mFileName; } + + bool WriteData(std::ptrdiff_t offset, const void* data, size_t size); + bool ReadData(std::ptrdiff_t offset, void* data, size_t size); + +private: + const std::string mFileName; + const size_t mPageId; + const size_t mSizeLimit; + std::ptrdiff_t mNextOffset = 0; + std::vector mFreeList; + bool mFreeListConsolidated = true; + mutable std::mutex mFileMutex; +}; + +class HVT_API HdPageFileManager { +public: + ~HdPageFileManager(); + + std::unique_ptr CreatePageHandle(const void* data, size_t size); + bool LoadPage(const HdBufferPageHandle& handle, void* data); + bool UpdatePage(const HdBufferPageHandle& handle, const void* data); + void DeletePage(const HdBufferPageHandle& handle); + + size_t GetTotalDiskUsage() const; + void PrintPagerStats() const; + + static constexpr size_t MAX_PAGE_FILE_SIZE = static_cast(1.8 * 1024 * 1024 * 1024); // 1.8GB + +private: + // By design, only HdPageableBufferManager can create and hold it. + HdPageFileManager(std::filesystem::path pageFileDirectory); + + // Disable copy and move + HdPageFileManager(const HdPageFileManager&) = delete; + HdPageFileManager(HdPageFileManager&&) = delete; + + HdPageFileEntry* GetCurrentPageFileEntry() const; + bool CreatePageFile(); + + std::vector> mPageFileEntries; + mutable std::mutex mSyncMutex; + + std::filesystem::path mPageFileDirectory = std::filesystem::temp_directory_path() / "temp_pages"; + + template friend class HdPageableBufferManager; +}; + +} // namespace HVT_NS \ No newline at end of file diff --git a/include/hvt/pageableBuffer/pageableBuffer.h b/include/hvt/pageableBuffer/pageableBuffer.h new file mode 100644 index 00000000..393e7d78 --- /dev/null +++ b/include/hvt/pageableBuffer/pageableBuffer.h @@ -0,0 +1,201 @@ +// Copyright 2025 Autodesk, Inc. +// +// 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. +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace HVT_NS +{ + +// Forward declarations +class HdPageFileManager; +class HdMemoryMonitor; + +template< +#if defined(__cpp_concepts) +HdPagingConcepts::PagingStrategyLike PagingStrategyType, +HdPagingConcepts::BufferSelectionStrategyLike BufferSelectionStrategyType +#else +typename PagingStrategyType, +typename BufferSelectionStrategyType +#endif +> +class HdPageableBufferManager; + +enum class HVT_API HdBufferState { + Unknown = 0, ///< Initial state + SceneBuffer = 1 << 0, ///< Data in the scene + RendererBuffer = 1 << 1, ///< Data in renderer + DiskBuffer = 1 << 2, ///< Data in disk +}; + +enum class HVT_API HdBufferUsage { + Static, ///< Immutable data, will be paged if possible + Dynamic ///< Mutable data, will be paged if necessary +}; + +class HVT_API HdBufferPageHandle { +public: + constexpr HdBufferPageHandle(size_t pageId, size_t size, std::ptrdiff_t offset) noexcept + : mPageId(pageId), mSize(size), mOffset(offset) {} + + constexpr size_t PageId() const noexcept { return mPageId; } + constexpr size_t Size() const noexcept { return mSize; } + constexpr std::ptrdiff_t Offset() const noexcept { return mOffset; } + constexpr bool IsValid() const noexcept { return mOffset != static_cast(-1); } + + // Comparison operators + bool operator!=(const HdBufferPageHandle& other) const noexcept { + return mPageId != other.mPageId || + mOffset != other.mOffset || + mSize != other.mSize; + } + + bool operator==(const HdBufferPageHandle& other) const noexcept { + return !(*this != other); + } + + bool operator<(const HdBufferPageHandle& other) const noexcept { + if (mPageId != other.mPageId) return mPageId < other.mPageId; + if (mOffset != other.mOffset) return mOffset < other.mOffset; + return mSize < other.mSize; + } + + bool operator>(const HdBufferPageHandle& other) const noexcept { + return other < *this; + } + + bool operator<=(const HdBufferPageHandle& other) const noexcept { + return !(other < *this); + } + + bool operator>=(const HdBufferPageHandle& other) const noexcept { + return !(*this < other); + } + +private: + const size_t mPageId; + const size_t mSize; + const std::ptrdiff_t mOffset; +}; + +// NOTE: Implementation should maintain data consistency. +// For example, once data is swapped out to disk, it's immutable. And if user want to READ/WRITE the data in any case, the buffer should be paged back. +class HVT_API HdPageableBufferBase { +public: + using DestructionCallback = std::function; + virtual ~HdPageableBufferBase(); + + // Resource management between Scene, Renderer and disk. ////////////////// + // Page: Create new buffer and fill data. Keep the source buffer. + [[nodiscard]] virtual bool PageToSceneMemory(bool force = false); + [[nodiscard]] virtual bool PageToRendererMemory(bool force = false); + [[nodiscard]] virtual bool PageToDisk(bool force = false); + + // Swap: Create new buffer and fill data. Release the source buffer. + [[nodiscard]] virtual bool SwapSceneToDisk(bool force = false); + [[nodiscard]] virtual bool SwapRendererToDisk(bool force = false); + [[nodiscard]] virtual bool SwapToSceneMemory(bool force = false, HdBufferState releaseBuffer + = static_cast(static_cast(HdBufferState::RendererBuffer) | static_cast(HdBufferState::DiskBuffer))); + [[nodiscard]] virtual bool SwapToRendererMemory(bool force = false, HdBufferState releaseBuffer + = static_cast(static_cast(HdBufferState::SceneBuffer) | static_cast(HdBufferState::DiskBuffer))); + + // Core operation sets: Release. ////////////////////////////////////////// + // Release: Release the source buffer and update the state. + virtual void ReleaseSceneBuffer() noexcept; + virtual void ReleaseRendererBuffer() noexcept; + virtual void ReleaseDiskPage() noexcept; + + // Get memory as spans for safe access + [[nodiscard]] virtual PXR_NS::TfSpan GetSceneMemorySpan() const noexcept; + [[nodiscard]] virtual PXR_NS::TfSpan GetSceneMemorySpan() noexcept; + [[nodiscard]] virtual PXR_NS::TfSpan GetRendererMemorySpan() const noexcept; + [[nodiscard]] virtual PXR_NS::TfSpan GetRendererMemorySpan() noexcept; + + // Properties + [[nodiscard]] constexpr const PXR_NS::SdfPath& Path() const noexcept { return mPath; } + [[nodiscard]] constexpr size_t Size() const noexcept { return mSize; } + void SetSize(size_t size) noexcept { mSize = size; } + [[nodiscard]] constexpr HdBufferUsage Usage() const noexcept { return mUsage; } + [[nodiscard]] constexpr HdBufferState GetBufferState() const noexcept { return mBufferState; } + + [[nodiscard]] constexpr int FrameStamp() const noexcept { return mFrameStamp; } + void UpdateFrameStamp(int frame) noexcept { mFrameStamp = frame; } + + // Status + [[nodiscard]] constexpr bool IsOverAge(int currentFrame, int ageLimit) const noexcept { + return (currentFrame - mFrameStamp) > ageLimit; + } + [[nodiscard]] constexpr bool HasValidDiskBuffer() const noexcept { + return mPageHandle && mPageHandle->IsValid(); + } + [[nodiscard]] constexpr bool HasSceneBuffer() const noexcept { + return (static_cast(mBufferState) & static_cast(HdBufferState::SceneBuffer)); + } + [[nodiscard]] constexpr bool HasRendererBuffer() const noexcept { + return (static_cast(mBufferState) & static_cast(HdBufferState::RendererBuffer)); + } + [[nodiscard]] constexpr bool HasDiskBuffer() const noexcept { + return (static_cast(mBufferState) & static_cast(HdBufferState::DiskBuffer)); + } + +protected: + // By design, only HdPageableBufferManager can create buffers. + HdPageableBufferBase(const PXR_NS::SdfPath& path, size_t size, HdBufferUsage usage, + const std::unique_ptr& pageFileManager, + const std::unique_ptr& memoryMonitor, + DestructionCallback destructionCallback); + + // Core operation sets: Creation. ///////////////////////////////////////// + // Create: Create a new buffer and update the state. No data is copied. + virtual void CreateSceneBuffer(); + virtual void CreateRendererBuffer(); + + // Helper to create aligned memory span + template + [[nodiscard]] constexpr PXR_NS::TfSpan MakeSpan(std::unique_ptr& ptr, size_t size) const noexcept { + return ptr ? PXR_NS::TfSpan(ptr.get(), size) : PXR_NS::TfSpan{}; + } + + template friend class HdPageableBufferManager; + + const PXR_NS::SdfPath mPath; // TODO: really need to hold??? + const HdBufferUsage mUsage; + + size_t mSize = 0; + HdBufferState mBufferState = HdBufferState::Unknown; + int mFrameStamp = 0; // Frame stamp for age tracking + + // Page handle for disk storage + std::unique_ptr mPageHandle; + + // Destruction callback to notify BufferManager (and avoid cycle ref) + DestructionCallback mDestructionCallback; + + // Accessor to PageFileManager & MemoryMonitor + std::unique_ptr& mPageFileManager; + std::unique_ptr& mMemoryMonitor; +}; + +} // namespace HVT_NS diff --git a/include/hvt/pageableBuffer/pageableBufferManager.h b/include/hvt/pageableBuffer/pageableBufferManager.h new file mode 100644 index 00000000..7e4faba3 --- /dev/null +++ b/include/hvt/pageableBuffer/pageableBufferManager.h @@ -0,0 +1,705 @@ +// Copyright 2025 Autodesk, Inc. +// +// 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. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace HVT_NS +{ + +class HdPageableBufferBase; + +template< +#if defined(__cpp_concepts) + HdPagingConcepts::PagingStrategyLike PagingStrategyType, + HdPagingConcepts::BufferSelectionStrategyLike BufferSelectionStrategyType +#else + typename PagingStrategyType, + typename BufferSelectionStrategyType +#endif +> +class HVT_API HdPageableBufferManager +{ +public: +#if !defined(__cpp_concepts) + using BufferSelectionStrategyIterator = tbb::concurrent_unordered_map, PXR_NS::SdfPath::Hash>::iterator; + static_assert(HdPagingConcepts::PagingStrategyLikeValue, "PagingStrategyType does not meet the requirements of a paging strategy"); + static_assert(HdPagingConcepts::BufferSelectionStrategyLikeValue, "BufferSelectionStrategyType does not meet the requirements of a buffer selection strategy"); +#endif + + struct InitializeDesc{ + std::filesystem::path pageFileDirectory = std::filesystem::temp_directory_path() / "temp_pages"; + int ageLimit = 20 /* frame */; + size_t sceneMemoryLimit = 2ULL * 1024 * 1024 * 1024 /* 2GB */; + size_t rendererMemoryLimit = 1ULL * 1024 * 1024 * 1024 /* 1GB */; + size_t numThreads = 2; + }; + // Constructor and destructor are now public for direct instantiation + HdPageableBufferManager(InitializeDesc desc) + : mAgeLimit(desc.ageLimit) + , mPageFileManager(std::unique_ptr(new HdPageFileManager(desc.pageFileDirectory))) + , mMemoryMonitor(std::unique_ptr(new HdMemoryMonitor(desc.sceneMemoryLimit, desc.rendererMemoryLimit))) + , mThreadPool(desc.numThreads > 0 ? desc.numThreads : std::thread::hardware_concurrency()) + { + // TODO: numThreads <= 0 means no async operations??? + } + ~HdPageableBufferManager() = default; + + // Frame stamp management + void AdvanceFrame(uint advanceCount = 1) noexcept { mCurrentFrame += advanceCount; } + [[nodiscard]] uint GetCurrentFrame() const noexcept { return mCurrentFrame; } + + // Strategy access (no runtime changing allowed) + [[nodiscard]] constexpr PagingStrategyType GetPagingStrategy() const noexcept { return mPagingStrategy; } + [[nodiscard]] constexpr BufferSelectionStrategyType GetBufferSelectionStrategy() const noexcept { return mBufferSelectionStrategy; } + constexpr int GetAgeLimit() const noexcept { return mAgeLimit; } + + // Accessors to internal managers + [[nodiscard]] std::unique_ptr& GetPageFileManager() { return mPageFileManager; } + [[nodiscard]] std::unique_ptr& GetMemoryMonitor() { return mMemoryMonitor; } + + // Buffer operations ////////////////////////////////////////////////////// + + // Buffer lifecycle management + [[nodiscard]] std::shared_ptr CreateBuffer( + const PXR_NS::SdfPath& path, size_t size = 0, HdBufferUsage usage = HdBufferUsage::Static); + bool AddBuffer(const PXR_NS::SdfPath& path, std::shared_ptr buffer); + void RemoveBuffer(const PXR_NS::SdfPath& path); + [[nodiscard]] std::shared_ptr FindBuffer(const PXR_NS::SdfPath& path); + + // Paging trigger + static constexpr size_t kMinimalCheckCount = 10; + void FreeCrawl(float percentage = 10.0f); + + // Async buffer operations + [[nodiscard]] std::future PageToSceneMemoryAsync(std::shared_ptr buffer, bool force = false); + [[nodiscard]] std::future PageToRendererMemoryAsync(std::shared_ptr buffer, bool force = false); + [[nodiscard]] std::future PageToDiskAsync(std::shared_ptr buffer, bool force = false); + + [[nodiscard]] std::future SwapSceneToDiskAsync(std::shared_ptr buffer, bool force = false); + [[nodiscard]] std::future SwapRendererToDiskAsync(std::shared_ptr buffer, bool force = false); + [[nodiscard]] std::future SwapToSceneMemoryAsync(std::shared_ptr buffer, bool force = false, HdBufferState releaeBuffer + = static_cast(static_cast(HdBufferState::RendererBuffer) | static_cast(HdBufferState::DiskBuffer))); + [[nodiscard]] std::future SwapToRendererMemoryAsync(std::shared_ptr buffer, bool force = false, HdBufferState releaeBuffer + = static_cast(static_cast(HdBufferState::SceneBuffer) | static_cast(HdBufferState::DiskBuffer))); + + [[nodiscard]] std::future ReleaseSceneBufferAsync(std::shared_ptr buffer) noexcept; + [[nodiscard]] std::future ReleaseRendererBufferAsync(std::shared_ptr buffer) noexcept; + [[nodiscard]] std::future ReleaseDiskPageAsync(std::shared_ptr buffer) noexcept; + + // Async paging trigger. + std::vector> FreeCrawlAsync(float percentage = 10.0f); + + // Async operation status + size_t GetPendingOperations() const; + void WaitForAllOperations(); + + // Statistics + // NOTE: These APIs may severely slow down the system and should be used for development only. + [[nodiscard]] size_t GetBufferCount() const; + void PrintCacheStats() const; + +private: + // Disable copy and move + HdPageableBufferManager(const HdPageableBufferManager&) = delete; + HdPageableBufferManager(HdPageableBufferManager&&) = delete; + + // HdPageableBufferBase destruction callback + void OnBufferDestroyed(const PXR_NS::SdfPath& path); + + // Helper method: dispose old buffer using configurable strategy + bool DisposeOldBuffer(HdPageableBufferBase& buffer, int currentFrame, int ageLimit, float scenePressure, float rendererPressure); + + // Execute paging decision on buffer (synchronous) + bool ExecutePagingDecision(HdPageableBufferBase& buffer, const HdPagingDecision& decision); + + // Execute paging decision on buffer (asynchronous) + std::future ExecutePagingDecisionAsync(std::shared_ptr buffer, const HdPagingDecision& decision); + + tbb::concurrent_unordered_map, PXR_NS::SdfPath::Hash> mBuffers; + + std::atomic mCurrentFrame{0}; + const int mAgeLimit{20}; // TODO: move to strategies??? + + // Compile-time strategy instances (no runtime changing) + PagingStrategyType mPagingStrategy{}; + BufferSelectionStrategyType mBufferSelectionStrategy{}; + + std::unique_ptr mPageFileManager; + std::unique_ptr mMemoryMonitor; + + // Thread pool for async buffer operations + // TODO: Replace with a tbb job system. + class ThreadPool; + ThreadPool mThreadPool; +}; + +// Thread Pool //////////////////////////////////////////////////////////////// +template< +#if defined(__cpp_concepts) + HdPagingConcepts::PagingStrategyLike PagingStrategyType, + HdPagingConcepts::BufferSelectionStrategyLike BufferSelectionStrategyType +#else + typename PagingStrategyType, + typename BufferSelectionStrategyType +#endif +> +class HdPageableBufferManager::ThreadPool +{ +public: + // Constructor creates the specified number of worker threads + explicit ThreadPool(size_t numThreads = std::thread::hardware_concurrency()) + { + // Create worker threads + for (size_t i = 0; i < numThreads; ++i) + { + mWorkers.emplace_back([this] + { workerThread(); }); + } + } + + // Destructor waits for all tasks to complete and joins all threads + ~ThreadPool() + { + // Signal all threads to stop + { + std::unique_lock lock(mQueueMutex); + mStop = true; + } + + // Wake up all threads and wait for them to finish + mCondition.notify_all(); + for (std::thread &worker : mWorkers) + { + if (worker.joinable()) + { + worker.join(); + } + } + } + + // Submit a task and get a future for the result + template + auto enqueue(F &&f, Args &&...args) -> std::future> + { + using return_type = typename std::invoke_result_t; + + auto task = std::make_shared>( + std::bind(std::forward(f), std::forward(args)...)); + + std::future result = task->get_future(); + + { + std::unique_lock lock(mQueueMutex); + + // Don't allow enqueueing after stopping the pool + if (mStop) + { + throw std::runtime_error("ThreadPool: enqueue on stopped pool"); + } + + mTasks.emplace([task]() + { (*task)(); }); + } + + mCondition.notify_one(); + return result; + } + + // Get the number of worker threads + size_t size() const noexcept { return mWorkers.size(); } + + // Get the number of pending tasks + size_t pending() const + { + std::unique_lock lock(mQueueMutex); + return mTasks.size(); + } + + void waitAll() + { + if (pending() > 0) { + std::unique_lock lock(mQueueMutex); + { + // Yield the thread until all tasks are completed. + while (!mTasks.empty()) { + std::this_thread::yield(); + } + } + } + } + +private: + // Worker threads + std::vector mWorkers; + + // Task queue + std::queue> mTasks; + + // Synchronization + mutable std::mutex mQueueMutex; + std::condition_variable mCondition; + std::atomic mStop{false}; + + // Worker thread function + void workerThread() + { + for (;;) + { + std::function task; + { + std::unique_lock lock(mQueueMutex); + + // Wait for a task or stop signal + mCondition.wait(lock, [this] + { return mStop || !mTasks.empty(); }); + + // Exit if we're stopping and no tasks remain + if (mStop && mTasks.empty()) + { + return; + } + + // Get the next task + task = std::move(mTasks.front()); + mTasks.pop(); + } + + // Execute the task + task(); + } + } +}; + +template +size_t HdPageableBufferManager::GetPendingOperations() const { + return mThreadPool.pending(); +} + +template +void HdPageableBufferManager::WaitForAllOperations() { + return mThreadPool.waitAll(); +} + +// Template Methods Implementations /////////////////////////////////////////// + +template +std::shared_ptr HdPageableBufferManager::CreateBuffer(const PXR_NS::SdfPath& path, size_t size, HdBufferUsage usage) { + // Check if buffer with this path already exists + { + auto const it = mBuffers.find(path); + if (it != mBuffers.end()) { + using namespace PXR_NS; + TF_WARN("HdPageableBufferBase '%s' already exists, returning existing buffer\n", path.GetText()); + return it->second; + } + } + + // Create destruction callback that will remove buffer from the list. + auto destructionCallback = [this](const PXR_NS::SdfPath& path) { + this->OnBufferDestroyed(path); + }; + + // Create new buffer and insert into the managed list. + auto buffer = std::shared_ptr(new HdPageableBufferBase(path, size, usage, this->mPageFileManager, this->mMemoryMonitor, destructionCallback)); + { + mBuffers.emplace(path, buffer); + } + return buffer; +} + +template +bool HdPageableBufferManager::AddBuffer(const PXR_NS::SdfPath& path, std::shared_ptr buffer) { + auto it = mBuffers.emplace(path, buffer); + if (it.second) { + return true; + } + return false; +} + +template +void HdPageableBufferManager::OnBufferDestroyed(const PXR_NS::SdfPath& path) { + this->RemoveBuffer(path); +} + +template +void HdPageableBufferManager::RemoveBuffer(const PXR_NS::SdfPath& path) { + auto it = mBuffers.find(path); + if (it != mBuffers.end()) { + mBuffers.unsafe_erase(it); + } +} + +template +std::shared_ptr HdPageableBufferManager::FindBuffer(const PXR_NS::SdfPath& path) { + { + auto const it = mBuffers.find(path); + if (it != mBuffers.end()) { + return it->second; + } + } + return nullptr; +} + +template +bool HdPageableBufferManager::DisposeOldBuffer(HdPageableBufferBase& buffer, int currentFrame, int ageLimit, float scenePressure, float rendererPressure) { + // Create paging context + HdPagingContext context; + context.currentFrame = currentFrame; + context.bufferAge = currentFrame - buffer.FrameStamp(); + context.ageLimit = ageLimit; + context.scenePressure = scenePressure; + context.rendererPressure = rendererPressure; + context.isOverAge = buffer.IsOverAge(currentFrame, ageLimit); + context.bufferUsage = buffer.Usage(); + context.bufferState = buffer.GetBufferState(); + + // Use configured strategy + HdPagingDecision decision = mPagingStrategy(buffer, context); + return ExecutePagingDecision(buffer, decision); +} + +template +bool HdPageableBufferManager::ExecutePagingDecision(HdPageableBufferBase& buffer, const HdPagingDecision& decision) { + if (!decision.shouldPage) { + return false; + } + + bool disposed = false; + + switch (decision.action) { + case HdPagingDecision::Action::SwapSceneToDisk: + disposed = buffer.SwapSceneToDisk(decision.forceOperation); + break; + + case HdPagingDecision::Action::SwapRendererToDisk: + disposed = buffer.SwapRendererToDisk(decision.forceOperation); + break; + + case HdPagingDecision::Action::SwapToSceneMemory: + disposed = buffer.SwapToSceneMemory(decision.forceOperation); + break; + + case HdPagingDecision::Action::ReleaseRendererBuffer: + buffer.ReleaseRendererBuffer(); + disposed = true; + break; + + case HdPagingDecision::Action::None: + default: + break; + } + + return disposed; +} + +template +std::future HdPageableBufferManager::ExecutePagingDecisionAsync(std::shared_ptr buffer, const HdPagingDecision& decision) { + if (!decision.shouldPage) { + // Return a future that immediately resolves to false + std::promise promise; + promise.set_value(false); + return promise.get_future(); + } + + switch (decision.action) { + case HdPagingDecision::Action::SwapSceneToDisk: + return SwapSceneToDiskAsync(buffer, decision.forceOperation); + + case HdPagingDecision::Action::SwapRendererToDisk: + return SwapRendererToDiskAsync(buffer, decision.forceOperation); + + case HdPagingDecision::Action::SwapToSceneMemory: + return SwapToSceneMemoryAsync(buffer, decision.forceOperation); + + case HdPagingDecision::Action::ReleaseRendererBuffer: + // Convert void future to bool future + return mThreadPool.enqueue([buffer]() -> bool { + buffer->ReleaseRendererBuffer(); + return true; + }); + + case HdPagingDecision::Action::None: + default: + // Return a future that immediately resolves to false + std::promise promise; + promise.set_value(false); + return promise.get_future(); + } +} + +template +void HdPageableBufferManager::FreeCrawl(float percentage) { + float scenePressure = mMemoryMonitor->GetSceneMemoryPressure(); + float rendererPressure = mMemoryMonitor->GetRendererMemoryPressure(); + + // Only crawl if we're under memory pressure + if (scenePressure < HdMemoryMonitor::LOW_MEMORY_THRESHOLD && + rendererPressure < HdMemoryMonitor::LOW_MEMORY_THRESHOLD) { + return; + } + + // Calculate number of buffers to check + size_t numToCheck = static_cast(mBuffers.size() * (percentage / 100.0f)); + numToCheck = std::max(numToCheck, kMinimalCheckCount); + numToCheck = std::min(numToCheck, mBuffers.size()); + + for (auto it = mBuffers.begin(); it != mBuffers.end();) { + if (!it->second) { + it = mBuffers.unsafe_erase(it); + } else { + ++it; + } + } + + // Create selection context + HdSelectionContext selectionContext; + selectionContext.currentFrame = mCurrentFrame; + selectionContext.scenePressure = scenePressure; + selectionContext.rendererPressure = rendererPressure; + selectionContext.requestedCount = numToCheck; + selectionContext.totalBufferCount = mBuffers.size(); + + // Use configurable buffer selection strategy + std::vector> selectedBuffers = mBufferSelectionStrategy(mBuffers.begin(), mBuffers.end(), selectionContext); + + [[maybe_unused]] size_t checkedCount = 0; + for (auto& buffer : selectedBuffers) { + if (buffer) { + buffer->UpdateFrameStamp(mCurrentFrame); + DisposeOldBuffer(*buffer, mCurrentFrame, mAgeLimit, scenePressure, rendererPressure); + ++checkedCount; + } + } +} + +template +std::vector> HdPageableBufferManager::FreeCrawlAsync(float percentage) { + std::vector> futures; + + float scenePressure = mMemoryMonitor->GetSceneMemoryPressure(); + float rendererPressure = mMemoryMonitor->GetRendererMemoryPressure(); + + // Only crawl if we're under memory pressure + if (scenePressure < HdMemoryMonitor::LOW_MEMORY_THRESHOLD && + rendererPressure < HdMemoryMonitor::LOW_MEMORY_THRESHOLD) { + return futures; + } + // Calculate number of buffers to check + size_t numToCheck = static_cast(mBuffers.size() * (percentage / 100.0f)); + numToCheck = std::max(numToCheck, kMinimalCheckCount); + numToCheck = std::min(numToCheck, mBuffers.size()); + + { + // Remove null buffers first + for (auto it = mBuffers.begin(); it != mBuffers.end();) { + if (!it->second) { + it = mBuffers.unsafe_erase(it); + } else { + ++it; + } + } + + // Create selection context + HdSelectionContext selectionContext; + selectionContext.currentFrame = mCurrentFrame; + selectionContext.scenePressure = scenePressure; + selectionContext.rendererPressure = rendererPressure; + selectionContext.requestedCount = numToCheck; + selectionContext.totalBufferCount = mBuffers.size(); + + // Use configurable buffer selection strategy + std::vector> selectedBuffers = mBufferSelectionStrategy(mBuffers.begin(), mBuffers.end(), selectionContext); + + // Start async operations for each selected buffer + for (auto& buffer : selectedBuffers) { + if (buffer) { + buffer->UpdateFrameStamp(mCurrentFrame); + + // Check if buffer should be disposed based on age and pressure + int age = mCurrentFrame - buffer->FrameStamp(); + if (age >= mAgeLimit) { + // Create paging context and get decision + HdPagingContext context; + context.currentFrame = mCurrentFrame; + context.ageLimit = mAgeLimit; + context.scenePressure = scenePressure; + context.rendererPressure = rendererPressure; + + HdPagingDecision decision = mPagingStrategy(*buffer, context); + + // Start async operation + if (decision.shouldPage) { + futures.push_back(ExecutePagingDecisionAsync(buffer, decision)); + } + } + } + } + } // Release lock + + return futures; +} + +template +size_t HdPageableBufferManager::GetBufferCount() const { +#ifdef DEBUG + const auto nonEmptyBuffer = std::count_if(mBuffers.begin(), mBuffers.end(), [](const auto& buffer) { return buffer != nullptr; }); + if (nonEmptyBuffer != mBuffers.size()) { + using namespace PXR_NS; + TF_STATUS("HdPageableBufferManager::GetBufferCount find %zu empty buffers.\n", mBuffer.size() - nonEmptyBuffer); + } +#endif + return mBuffers.size(); +} + +template +void HdPageableBufferManager::PrintCacheStats() const { + using namespace PXR_NS; + size_t sceneBuffers = 0; + size_t rendererBuffers = 0; + size_t diskBuffers = 0; + for(auto const& it : mBuffers) { + if(!it.second) continue; + + if (it.second->HasSceneBuffer()) ++sceneBuffers; + if (it.second->HasRendererBuffer()) ++rendererBuffers; + if (it.second->HasDiskBuffer()) ++diskBuffers; + } + + TF_STATUS("=== Cache Statistics ===\n" + "Total Buffers: %zu\n" + "Scene Buffers: %zu\n" + "Renderer Buffers: %zu\n" + "Disk Buffers: %zu\n" + "Current Frame: %u\n" + "Age Limit: %d frames\n" + "========================\n", + mBuffers.size(), + sceneBuffers, + rendererBuffers, + diskBuffers, + mCurrentFrame.load(), + mAgeLimit); +} + +// Async Buffer Operations //////////////////////////////////////////////////// + +template +std::future HdPageableBufferManager::PageToSceneMemoryAsync(std::shared_ptr buffer, bool force) { + return mThreadPool.enqueue([buffer, force]() -> bool { + return buffer->PageToSceneMemory(force); + }); +} + +template +std::future HdPageableBufferManager::PageToRendererMemoryAsync(std::shared_ptr buffer, bool force) { + return mThreadPool.enqueue([buffer, force]() -> bool { + return buffer->PageToRendererMemory(force); + }); +} + +template +std::future HdPageableBufferManager::PageToDiskAsync(std::shared_ptr buffer, bool force) { + return mThreadPool.enqueue([buffer, force]() -> bool { + return buffer->PageToDisk(force); + }); +} + +template +std::future HdPageableBufferManager::SwapSceneToDiskAsync(std::shared_ptr buffer, bool force) { + return mThreadPool.enqueue([buffer, force]() -> bool { + return buffer->SwapSceneToDisk(force); + }); +} + +template +std::future HdPageableBufferManager::SwapRendererToDiskAsync(std::shared_ptr buffer, bool force) { + return mThreadPool.enqueue([buffer, force]() -> bool { + return buffer->SwapRendererToDisk(force); + }); +} + +template +std::future HdPageableBufferManager::SwapToSceneMemoryAsync(std::shared_ptr buffer, bool force, HdBufferState releaeBuffer) { + return mThreadPool.enqueue([buffer, force, releaeBuffer]() -> bool { + return buffer->SwapToSceneMemory(force, releaeBuffer); + }); +} + +template +std::future HdPageableBufferManager::SwapToRendererMemoryAsync(std::shared_ptr buffer, bool force, HdBufferState releaeBuffer) { + return mThreadPool.enqueue([buffer, force, releaeBuffer]() -> bool { + return buffer->SwapToRendererMemory(force, releaeBuffer); + }); +} + +template +std::future HdPageableBufferManager::ReleaseSceneBufferAsync(std::shared_ptr buffer) noexcept { + return mThreadPool.enqueue([buffer]() -> void { + buffer->ReleaseSceneBuffer(); + }); +} + +template +std::future HdPageableBufferManager::ReleaseRendererBufferAsync(std::shared_ptr buffer) noexcept { + return mThreadPool.enqueue([buffer]() -> void { + buffer->ReleaseRendererBuffer(); + }); +} + +template +std::future HdPageableBufferManager::ReleaseDiskPageAsync(std::shared_ptr buffer) noexcept { + return mThreadPool.enqueue([buffer]() -> void { + buffer->ReleaseDiskPage(); + }); +} + +// Built-in BufferManager Aliases ///////////////////////////////////////////// + +// Default HdPageableBufferManager (also the one offered in HdMemoryManager) +using DefaultBufferManager = HdPageableBufferManager; + +// Memory-focused combinations +using PressureBasedLargestBufferManager = HdPageableBufferManager; +using PressureBasedLRUBufferManager = HdPageableBufferManager; + +// Performance-focused combinations +using ConservativeFIFOBufferManager = HdPageableBufferManager; +using ConservativeOldestBufferManager = HdPageableBufferManager; + +// Strategy-specific combinations +using AgeBasedBufferManager = HdPageableBufferManager; +using FIFOBufferManager = HdPageableBufferManager; + +} // namespace HVT_NS diff --git a/include/hvt/pageableBuffer/pageableConcepts.h b/include/hvt/pageableBuffer/pageableConcepts.h new file mode 100644 index 00000000..3fe5c993 --- /dev/null +++ b/include/hvt/pageableBuffer/pageableConcepts.h @@ -0,0 +1,210 @@ +// Copyright 2025 Autodesk, Inc. +// +// 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. +#pragma once + +#include + +#include +#include + +#include +#include +#if defined(__cpp_concepts) +#include +#else +#include +#include +#endif + +namespace HVT_NS +{ + +// Forward declarations +class HdPageableBufferBase; +struct HdPagingContext; +struct HdPagingDecision; +struct HdSelectionContext; + +namespace HdPagingConcepts { + +#if defined(__cpp_concepts) + +// Concept for objects that have an id (path) +template +concept Pathed = requires(T t) { + { t.Path() } -> std::convertible_to; +}; + +// Concept for objects that have a size +template +concept Sized = requires(T t) { + { t.Size() } -> std::convertible_to; +}; + +// Concept for memory-managed objects +template +concept MemoryManaged = requires(T t) { + { t.PageToSceneMemory() } -> std::convertible_to; + { t.PageToRendererMemory() } -> std::convertible_to; + { t.PageToDisk() } -> std::convertible_to; +}; + +// Concept for aged resources +template +concept Aged = requires(T t, int frame) { + { t.FrameStamp() } -> std::convertible_to; + { t.UpdateFrameStamp(frame) } -> std::same_as; + { t.IsOverAge(frame, frame) } -> std::convertible_to; +}; + +// Combined concept for complete buffer-like objects +template +concept BufferLike = Pathed && Sized && MemoryManaged && Aged; + +// Concept for pageable buffer managers +template +concept BufferManagerLike = requires(T t, std::shared_ptr buffer, PXR_NS::SdfPath path, size_t size) { + requires BufferLike; + { t.CreateBuffer(path, size) } -> std::same_as>; + { t.RemoveBuffer(path) } -> std::same_as; + { t.FindBuffer(path) } -> std::convertible_to>; + { t.FreeCrawl() } -> std::same_as; +}; + +// Concept for paging strategies +template +concept PagingStrategyLike = requires(T t, const HdPageableBufferBase& buffer, const HdPagingContext& context) { + { t(buffer, context) } -> std::convertible_to; +} || requires(T t, const HdPageableBufferBase& buffer, const HdPagingContext& context) { + { t.operator()(buffer, context) } -> std::convertible_to; +}; + +// Concept for buffer selection strategies +template +concept BufferSelectionStrategyLike = requires(T t, InputIterator first, InputIterator last, const HdSelectionContext& context) { + { t(first, last, context) } -> std::convertible_to>>; +} || requires(T t, InputIterator first, InputIterator last, const HdSelectionContext& context) { + { t.operator()(first, last, context) } -> std::convertible_to>>; +}; + +#else // !defined(__cpp_concepts) + +// SFINAE-based detection helpers for pre-C++20 compilers + +template +struct Pathed : std::false_type {}; +template +struct Pathed().Path())>> + : std::is_convertible().Path()), PXR_NS::SdfPath> {}; + + +template +struct Sized : std::false_type {}; +template +struct Sized().Size())>> + : std::is_convertible().Size()), std::size_t> {}; + + +template +struct MemoryManaged : std::false_type {}; +template +struct MemoryManaged().PageToSceneMemory()), + decltype(std::declval().PageToRendererMemory()), + decltype(std::declval().PageToDisk())>> +{ + static constexpr bool value = + std::is_convertible().PageToSceneMemory()), bool>::value && + std::is_convertible().PageToRendererMemory()), bool>::value && + std::is_convertible().PageToDisk()), bool>::value; +}; + +template +struct Aged : std::false_type {}; +template +struct Aged().FrameStamp()), + decltype(std::declval().UpdateFrameStamp(0)), + decltype(std::declval().IsOverAge(0,0))>> +{ + static constexpr bool value = + std::is_convertible().FrameStamp()), int>::value && + std::is_same().UpdateFrameStamp(0)), void>::value && + std::is_convertible().IsOverAge(0,0)), bool>::value; +}; + +// Traits that resemble pageable concepts + +template +struct BufferLike : std::integral_constant::value && + Sized::value && + MemoryManaged::value && + Aged::value> {}; +template +inline constexpr bool BufferLikeValue = BufferLike::value; + +template +struct _BufferManagerLike : std::false_type {}; +template +struct _BufferManagerLike().CreateBuffer(std::declval(), std::declval())), + decltype(std::declval().RemoveBuffer(std::declval())), + decltype(std::declval().FindBuffer(std::declval())), + decltype(std::declval().FreeCrawl())>> +{ + static constexpr bool value = + BufferLikeValue && + std::is_same().CreateBuffer(std::declval(), std::declval())), std::shared_ptr>::value && + std::is_same().RemoveBuffer(std::declval())), void>::value && + std::is_convertible().FindBuffer(std::declval())), std::shared_ptr>::value && + std::is_same().FreeCrawl()), void>::value; +}; + +template +struct BufferManagerLike : std::integral_constant::value> {}; +template +inline constexpr bool BufferManagerLikeValue = BufferManagerLike::value; + +template +struct _PagingStrategyLike : std::false_type {}; +template +struct _PagingStrategyLike()(std::declval(), std::declval()))>> +{ + static constexpr bool value = std::is_convertible()(std::declval(), std::declval())), HdPagingDecision>::value; +}; + +template +struct PagingStrategyLike : std::integral_constant::value> {}; +template +inline constexpr bool PagingStrategyLikeValue = PagingStrategyLike::value; + +template +struct _BufferSelectionStrategyLike : std::false_type {}; +template +struct _BufferSelectionStrategyLike()(std::declval(), std::declval(), std::declval()))>> +{ + static constexpr bool value = std::is_convertible()(std::declval(), std::declval(), std::declval())), std::vector>>::value; +}; + +template +struct BufferSelectionStrategyLike : std::integral_constant::value> {}; +template +inline constexpr bool BufferSelectionStrategyLikeValue = BufferSelectionStrategyLike::value; + +#endif // __cpp_concepts + +} // namespace HdPagingConcepts + +} // namespace HVT_NS diff --git a/include/hvt/pageableBuffer/pageableDataSource.h b/include/hvt/pageableBuffer/pageableDataSource.h new file mode 100644 index 00000000..c70c3b5f --- /dev/null +++ b/include/hvt/pageableBuffer/pageableDataSource.h @@ -0,0 +1,292 @@ +// Copyright 2025 Autodesk, Inc. +// +// 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. +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace HVT_NS +{ + +/// Forward declarations +class HdMemoryManager; + +/// Memory-aware VtValue. +// TODO: We should either forbid users from modifying the source VtArray once it is paged out. +// or, remove the HdPageableValue design but make it more higher level and applicable to data source only. +class HVT_API HdPageableValue : public HdPageableBufferBase +{ +public: + HdPageableValue(const PXR_NS::SdfPath& path, + size_t estimatedSize, + HdBufferUsage usage, + const std::unique_ptr& pageFileManager, + const std::unique_ptr& memoryMonitor, + DestructionCallback destructionCallback, + const PXR_NS::VtValue& data, + const PXR_NS::TfToken& dataType); + + /// Get the original VtValue data (triggers load if needed) + PXR_NS::VtValue GetValue(); + + /// Get data type for Hydra consumption + PXR_NS::TfToken GetDataType() const { return mDataType; } + + /// Check if data is immediately available + bool IsDataResident() const { return HdPageableBufferBase::HasSceneBuffer(); } + + /// HdPageableBufferBase methods ////////////////////////////////////////// + + bool SwapSceneToDisk(bool force = false) override; + bool SwapToSceneMemory(bool force = false, HdBufferState releaseBuffer = HdBufferState::DiskBuffer) override; + + [[nodiscard]] PXR_NS::TfSpan GetSceneMemorySpan() const noexcept override; + [[nodiscard]] PXR_NS::TfSpan GetSceneMemorySpan() noexcept override; + + /// Utilities + static size_t EstimateMemoryUsage(const PXR_NS::VtValue& value) noexcept; + std::vector SerializeVtValue(const PXR_NS::VtValue& value) const noexcept; + PXR_NS::VtValue DeserializeVtValue(const std::vector& data) noexcept; + +private: + mutable PXR_NS::VtValue mSourceValue; + PXR_NS::TfToken mDataType; +}; + +struct HVT_API HdContainerPageEntry { + std::type_index type; + size_t offset; + size_t size; +}; + +/// Memory-managed container data source. +class HVT_API HdPageableContainerDataSource : public PXR_NS::HdContainerDataSource, public HdPageableBufferBase { +public: + HD_DECLARE_DATASOURCE_ABSTRACT(HdPageableContainerDataSource); + + static Handle New(const PXR_NS::SdfPath& primPath, + const std::unique_ptr& pageFileManager, + const std::unique_ptr& memoryMonitor, + DestructionCallback destructionCallback, + HdBufferUsage usage = HdBufferUsage::Static); + + virtual std::map GetMemoryBreakdown() const; + +private: + HdPageableContainerDataSource(const PXR_NS::SdfPath& primPath, + const std::unique_ptr& pageFileManager, + const std::unique_ptr& memoryMonitor, + DestructionCallback destructionCallback, + HdBufferUsage usage = HdBufferUsage::Static); + + std::map mContainerPageEntries; +}; +HD_DECLARE_DATASOURCE_HANDLES(HdPageableContainerDataSource); + +/// Memory-managed vector data source. +class HVT_API HdPageableVectorDataSource : public PXR_NS::HdVectorDataSource, public HdPageableBufferBase { +public: + HD_DECLARE_DATASOURCE_ABSTRACT(HdPageableVectorDataSource); + + static Handle New(const PXR_NS::SdfPath& primPath, + const std::unique_ptr& pageFileManager, + const std::unique_ptr& memoryMonitor, + DestructionCallback destructionCallback, + HdBufferUsage usage = HdBufferUsage::Static); + + virtual std::vector GetMemoryBreakdown() const; + +private: + HdPageableVectorDataSource(const PXR_NS::SdfPath& primPath, + const std::unique_ptr& pageFileManager, + const std::unique_ptr& memoryMonitor, + DestructionCallback destructionCallback, + HdBufferUsage usage = HdBufferUsage::Static); + + std::vector mElements; +}; +HD_DECLARE_DATASOURCE_HANDLES(HdPageableVectorDataSource); + +// TODO ???? +/// Memory-managed sampled data source for time-sampled values. +class HVT_API HdPageableSampledDataSource : public PXR_NS::HdSampledDataSource, public HdPageableBufferBase { +public: + HD_DECLARE_DATASOURCE_ABSTRACT(HdPageableSampledDataSource); + + /// Create with memory management + static Handle New(const PXR_NS::VtValue& value, + const PXR_NS::SdfPath& primPath, + const PXR_NS::TfToken& attributeName, + const std::unique_ptr& pageFileManager, + const std::unique_ptr& memoryMonitor, + DestructionCallback destructionCallback, + HdBufferUsage usage = HdBufferUsage::Static); + + /// Create time-sampled with memory management + static Handle New(const std::map& samples, + const PXR_NS::SdfPath& primPath, + const PXR_NS::TfToken& attributeName, + const std::unique_ptr& pageFileManager, + const std::unique_ptr& memoryMonitor, + DestructionCallback destructionCallback, + HdBufferUsage usage = HdBufferUsage::Static); + + /// HdSampledDataSource interface + PXR_NS::VtValue GetValue(Time shutterOffset) override; + bool GetContributingSampleTimesForInterval(Time startTime, Time endTime, + std::vector