@@ -17,6 +17,69 @@ namespace render {
1717// forward declaration
1818class ManagedBufferRegistry ;
1919
20+ /*
21+ * Non-templated base class for ManagedBuffer<T>.
22+ * Contains all type-independent members and provides the syncToDeviceIfNeeded() virtual interface
23+ * used by ShaderProgram::draw() to lazily push host data to the GPU before issuing a draw call.
24+ */
25+ class ManagedBufferBase {
26+ public:
27+ virtual ~ManagedBufferBase () = default ;
28+
29+ // Called by ShaderProgram::draw() before issuing the GPU draw call.
30+ // Pushes host data to device if deviceBufferValid is false and hostBufferValid is true.
31+ virtual void syncToDeviceIfNeeded () = 0;
32+
33+ const std::string name;
34+ const uint64_t uniqueID;
35+
36+ protected:
37+ // protected constructors — only ManagedBuffer<T> should construct this
38+ ManagedBufferBase (ManagedBufferRegistry* registry_, const std::string& name_, bool dataGetsComputed_,
39+ bool hostBufferValid_);
40+
41+ // Internal helpers that don't depend on T
42+ void invalidateHostBuffer ();
43+ bool deviceBufferTypeIsTexture () const ;
44+ void checkDeviceBufferTypeIs (DeviceBufferType targetType) const ;
45+ void checkDeviceBufferTypeIsTexture () const ;
46+
47+ // True when the buffer has no valid data on host or device and is waiting to be computed.
48+ bool isInNeedsComputeState () const { return !hostBufferValid && !deviceBufferValid && dataGetsComputed; }
49+
50+ ManagedBufferRegistry* registry;
51+
52+ bool dataGetsComputed; // if true, the value gets computed on-demand by calling computeFunc()
53+ std::function<void ()> computeFunc; // (optional) callback which populates the buffer
54+
55+ bool hostBufferValid; // true if host-side data is current
56+ bool deviceBufferValid; // true if device-side buffer matches current host data
57+
58+ // True if all registered indexed views are consistent with the current source data.
59+ // Set to false by markRenderAttributeBufferUpdated() when the GPU source is written externally
60+ // and host data is no longer available to re-gather from. Will be set back to true once
61+ // updateIndexedViews() runs successfully (which requires hostBufferValid).
62+ // Future work: an on-device gather pass will be able to update indexed views even when
63+ // !hostBufferValid, at which point this flag drives that path.
64+ bool indexedViewsValid = true ;
65+
66+ // Explicit size and capacity.
67+ // Invariant: when hostBufferValid, data.size() == managedCapacity (the vector is always kept at full
68+ // capacity — data.capacity() (std::vector internal) is never relied on for anything).
69+ // currentSize <= managedCapacity is the number of logically valid elements.
70+ size_t managedCapacity = 0 ;
71+ size_t currentSize = 0 ;
72+
73+ std::shared_ptr<render::AttributeBuffer> renderAttributeBuffer;
74+ std::shared_ptr<render::TextureBuffer> renderTextureBuffer;
75+
76+ DeviceBufferType deviceBufferType = DeviceBufferType::Attribute;
77+ uint32_t sizeX = 0 ;
78+ uint32_t sizeY = 0 ;
79+ uint32_t sizeZ = 0 ;
80+ };
81+
82+
2083/*
2184 * This class owns and manages a typed data buffer in Polyscope, handling common data-management concerns of:
2285 *
@@ -32,15 +95,16 @@ class ManagedBufferRegistry;
3295 * Use the public accessor API (getHostValue, setHostValue, setDataHost, begin/end, etc.) to interact with the buffer.
3396 */
3497template <typename T>
35- class ManagedBuffer : public virtual WeakReferrable {
98+ class ManagedBuffer : public ManagedBufferBase , public virtual WeakReferrable {
3699public:
37100 // === Constructors
38- // (second variants are advanced versions which allow creation of multi-dimensional texture values)
101+
102+ // Create an empty buffer with no data. Use resize()+setHostValue() or setDataHost() to populate.
103+ ManagedBuffer (ManagedBufferRegistry* registry, const std::string& name);
39104
40105 // Manage a buffer of data which is explicitly set externally.
41106 ManagedBuffer (ManagedBufferRegistry* registry, const std::string& name, std::vector<T> data);
42107
43-
44108 // Manage a buffer of data which gets computed lazily
45109 ManagedBuffer (ManagedBufferRegistry* registry, const std::string& name,
46110 std::function<void ()> computeFunc);
@@ -49,16 +113,7 @@ class ManagedBuffer : public virtual WeakReferrable {
49113 ~ManagedBuffer ();
50114
51115
52- // === Core members
53-
54- // A meaningful name for the buffer
55- std::string name;
56-
57- // A globally-unique ID
58- const uint64_t uniqueID;
59-
60- // The registry in which it is tracked (can be null)
61- ManagedBufferRegistry* registry;
116+ // === Core members (note: name, uniqueID, registry, dataGetsComputed, computeFunc live in ManagedBufferBase)
62117
63118
64119 // sanity check helper
@@ -81,7 +136,7 @@ class ManagedBuffer : public virtual WeakReferrable {
81136 // But in settings where we e.g. incrementally add elements or change the number of data elements on each frame, we
82137 // may want to allocate a larger capacity to avoid expensive re-allocation each time.
83138
84- // Resize the buffer to newSize elements. Sets hostBufferIsPopulated = true.
139+ // Resize the buffer to newSize elements. Sets hostBufferValid = true.
85140 //
86141 // If newSize <= capacity(), this is a cheap constant-time operation which just updates metadata.
87142 //
@@ -131,7 +186,10 @@ class ManagedBuffer : public virtual WeakReferrable {
131186 // device buffers and triggers a redraw. Prefer this over setHostValue() for bulk writes.
132187 void setDataHost (const std::vector<T>& newData);
133188
134- // Iterator support. Caller must call ensureHostBufferPopulated() before using these.
189+ // Returns a full copy of the host data, populating it from device if needed.
190+ std::vector<T> getDataCopy ();
191+
192+ // Raw pointer iteration support. Caller must call ensureHostBufferPopulated() before using these.
135193 // A debug-mode check will fire if this precondition is violated.
136194 const T* begin () const ;
137195 const T* end () const ;
@@ -182,8 +240,10 @@ class ManagedBuffer : public virtual WeakReferrable {
182240 // NOTE: this is only for attribute-accessed buffers (DeviceBufferType::Attribute). See the variants below for
183241 // textures.
184242
185- // NOTE: This class follows the policy that once the render buffer is allocated, it is always immediately kept
186- // updated to reflect any external changes.
243+ // NOTE: This class follows a lazy-sync policy: once the render buffer is allocated, it is kept up to date
244+ // with host-side changes lazily — the actual GPU upload happens in syncToDeviceIfNeeded(), which is called
245+ // by ShaderProgram::draw() just before the draw call. External writes to the GPU buffer (via
246+ // markRenderAttributeBufferUpdated()) invalidate the host copy until ensureHostBufferPopulated() is called.
187247
188248 // Get a reference to the underlying GPU-side attribute buffer
189249 // Once this reference is created, it will always be immediately updated to reflect any external changes to the
@@ -230,38 +290,16 @@ class ManagedBuffer : public virtual WeakReferrable {
230290 void markRenderTextureBufferUpdated ();
231291
232292
293+ // Sync host data to the device buffer if needed. Called by ShaderProgram::draw() before drawing.
294+ void syncToDeviceIfNeeded () override ;
295+
233296protected:
234297 // == Internal members
298+ // Note: dataGetsComputed, computeFunc, hostBufferValid, deviceBufferValid, managedCapacity,
299+ // renderAttributeBuffer, renderTextureBuffer, deviceBufferType, sizeX/Y/Z all live in ManagedBufferBase.
235300
236- // This class tracks data from two separate sources:
237- // A) Values directly stored in the buffer by an external source (dataGetsComputed == false)
238- // B) Values that get computed lazily by some function when needed (dataGetsComputed == true)
239- //
240- // When dataGetsComputed is true, computeFunc() must be set to a callback that populates the buffer.
241- // The callback should call resize() then setHostValue() to populate the buffer.
242- // The callback does NOT need to call markHostBufferUpdated() — that is handled by the ManagedBuffer
243- // infrastructure after the callback returns.
244- bool dataGetsComputed; // if true, the value gets computed on-demand by calling computeFunc()
245- std::function<void ()> computeFunc; // (optional) callback which populates the buffer
246-
247- bool hostBufferIsPopulated; // true if the host buffer contains currently-valid data
248-
249- // The buffer has capacity for at least this many elements. It is distinct from .size(), which is the actual number of
250- // elements currently stored in the buffer and may be smaller than the capacity.
251- // Any resize() operations that stay within the capacity are cheap, and do not trigger a full reallocation and copy.
252- size_t managedCapacity = 0 ;
253-
254- std::shared_ptr<render::AttributeBuffer> renderAttributeBuffer;
255- std::shared_ptr<render::TextureBuffer> renderTextureBuffer;
256-
257- // For storing as textures
258-
259- // For data that can be interpreted as a 1/2/3 dimensional texture
260- DeviceBufferType deviceBufferType = DeviceBufferType::Attribute; // this gets set when you call setTextureSize
261- uint32_t sizeX = 0 ;
262- uint32_t sizeY = 0 ; // holds 0 if texture dim < 2
263- uint32_t sizeZ = 0 ; // holds 0 if texture dim < 3
264-
301+ // Override invalidateHostBuffer to also clear the typed data vector.
302+ void invalidateHostBuffer ();
265303
266304 // == Internal representation of indexed views
267305 // NOTE: this seems like a problem, we are storing pointers as keys in a cache. Here, it works out because if the
@@ -271,13 +309,7 @@ class ManagedBuffer : public virtual WeakReferrable {
271309 existingIndexedViews;
272310 void updateIndexedViews ();
273311 void removeDeletedIndexedViews ();
274-
275- // == Internal helper functions
276-
277- void invalidateHostBuffer ();
278- bool deviceBufferTypeIsTexture () const ;
279- void checkDeviceBufferTypeIs (DeviceBufferType targetType) const ;
280- void checkDeviceBufferTypeIsTexture () const ;
312+ void invalidateIndexedViews ();
281313
282314 enum class CanonicalDataSource { HostData = 0 , NeedsCompute, RenderBuffer };
283315 CanonicalDataSource currentCanonicalDataSource ();
@@ -373,3 +405,21 @@ std::string typeName(ManagedBufferType type);
373405} // namespace polyscope
374406
375407#include " polyscope/render/managed_buffer.ipp"
408+
409+ // Implementations of ShaderProgram's ManagedBuffer convenience overloads
410+ // (defined here because they need the full ManagedBuffer<T> definition)
411+ namespace polyscope {
412+ namespace render {
413+
414+ template <typename T>
415+ void ShaderProgram::setAttribute (std::string name, ManagedBuffer<T>& buf) {
416+ setAttribute (name, buf.getRenderAttributeBuffer (), &buf);
417+ }
418+
419+ template <typename T>
420+ void ShaderProgram::setTextureFromBuffer (std::string name, ManagedBuffer<T>& buf) {
421+ setTextureFromBuffer (name, buf.getRenderTextureBuffer ().get (), &buf);
422+ }
423+
424+ } // namespace render
425+ } // namespace polyscope
0 commit comments