Skip to content

Commit a759ef6

Browse files
committed
pass managed buffers directly to shaders, change lazy update strategy
1 parent e2a8ebc commit a759ef6

38 files changed

Lines changed: 523 additions & 404 deletions

include/polyscope/render/engine.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717

1818
namespace polyscope {
1919

20+
// Forward declarations for ManagedBuffer integration
21+
namespace render {
22+
class ManagedBufferBase;
23+
template <typename T> class ManagedBuffer;
24+
} // namespace render
25+
2026
// == A few enums that control behavior
2127
// public enums are in the outer namespace to keep the typing burden down
2228

@@ -395,7 +401,7 @@ class ShaderProgram {
395401
virtual bool hasAttribute(std::string name) = 0;
396402
virtual bool attributeIsSet(std::string name) = 0;
397403
virtual std::shared_ptr<AttributeBuffer> getAttributeBuffer(std::string name) = 0;
398-
virtual void setAttribute(std::string name, std::shared_ptr<AttributeBuffer> externalBuffer) = 0;
404+
virtual void setAttribute(std::string name, std::shared_ptr<AttributeBuffer> externalBuffer, ManagedBufferBase* source = nullptr) = 0;
399405
virtual void setAttribute(std::string name, const std::vector<glm::vec2>& data) = 0;
400406
virtual void setAttribute(std::string name, const std::vector<glm::vec3>& data) = 0;
401407
virtual void setAttribute(std::string name, const std::vector<glm::vec4>& data) = 0;
@@ -418,8 +424,13 @@ class ShaderProgram {
418424
bool withAlpha = true, bool useMipMap = false, bool repeat = false) = 0;
419425
virtual void setTextureFromColormap(std::string name, const std::string& colorMap, bool allowUpdate = false) = 0;
420426
// TODO make this one take a shared pointer and have the same semantics as the attribute version
421-
virtual void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer) = 0;
427+
virtual void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer, ManagedBufferBase* source = nullptr) = 0;
428+
422429

430+
// Convenience overloads for ManagedBuffer — set the buffer AND record the source for lazy sync.
431+
// Defined in managed_buffer.h after ManagedBuffer<T> is fully declared.
432+
template <typename T> void setAttribute(std::string name, ManagedBuffer<T>& buf);
433+
template <typename T> void setTextureFromBuffer(std::string name, ManagedBuffer<T>& buf);
423434

424435
// Indices
425436
virtual void setIndex(std::shared_ptr<AttributeBuffer> externalBuffer) = 0;

include/polyscope/render/managed_buffer.h

Lines changed: 103 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,69 @@ namespace render {
1717
// forward declaration
1818
class 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
*/
3497
template <typename T>
35-
class ManagedBuffer : public virtual WeakReferrable {
98+
class ManagedBuffer : public ManagedBufferBase, public virtual WeakReferrable {
3699
public:
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+
233296
protected:
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

include/polyscope/render/mock_opengl/mock_gl_engine.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ struct GLShaderAttribute {
212212
RenderDataType type;
213213
int arrayCount;
214214
std::shared_ptr<GLAttributeBuffer> buff; // the buffer that we will actually use
215+
ManagedBufferBase* sourceManagedBuffer; // might be empty if not from a managed buffer. for indexed views, it's the original data source
215216
};
216217

217218
struct GLShaderTexture {
@@ -221,6 +222,7 @@ struct GLShaderTexture {
221222
bool isSet;
222223
GLTextureBuffer* textureBuffer;
223224
std::shared_ptr<GLTextureBuffer> textureBufferOwned; // might be empty, if texture isn't owned
225+
ManagedBufferBase* sourceManagedBuffer; // might be empty if not from a managed buffer. for indexed views, it's the original data source
224226
};
225227

226228
// A thin wrapper around a program handle.
@@ -282,7 +284,7 @@ class GLShaderProgram : public ShaderProgram {
282284
bool hasAttribute(std::string name) override;
283285
bool attributeIsSet(std::string name) override;
284286
std::shared_ptr<AttributeBuffer> getAttributeBuffer(std::string name) override;
285-
void setAttribute(std::string name, std::shared_ptr<AttributeBuffer> externalBuffer) override;
287+
void setAttribute(std::string name, std::shared_ptr<AttributeBuffer> externalBuffer, ManagedBufferBase* source = nullptr) override;
286288
void setAttribute(std::string name, const std::vector<glm::vec2>& data) override;
287289
void setAttribute(std::string name, const std::vector<glm::vec3>& data) override;
288290
void setAttribute(std::string name, const std::vector<glm::vec4>& data) override;
@@ -310,11 +312,14 @@ class GLShaderProgram : public ShaderProgram {
310312
void setTexture2D(std::string name, unsigned char* texData, unsigned int width, unsigned int height,
311313
bool withAlpha = true, bool useMipMap = false, bool repeat = false) override;
312314
void setTextureFromColormap(std::string name, const std::string& colorMap, bool allowUpdate = false) override;
313-
void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer) override;
315+
void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer, ManagedBufferBase* source = nullptr) override;
316+
317+
// ManagedBuffer source registration
314318

315319
// Draw!
316320
void draw() override;
317321
void validateData() override;
322+
void syncBuffersToDeviceIfNeeded();
318323

319324
protected:
320325
// Lists of attributes and uniforms that need to be set

include/polyscope/render/opengl/gl_engine.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ struct GLShaderAttribute {
252252
int arrayCount;
253253
AttributeLocation location; // -1 means "no location", usually because it was optimized out
254254
std::shared_ptr<GLAttributeBuffer> buff; // the buffer that we will actually use
255+
ManagedBufferBase* sourceManagedBuffer; // might be empty if not from a managed buffer. for indexed views, it's the original data source
255256
};
256257

257258
struct GLShaderTexture {
@@ -262,6 +263,7 @@ struct GLShaderTexture {
262263
GLTextureBuffer* textureBuffer;
263264
std::shared_ptr<GLTextureBuffer> textureBufferOwned; // might be empty, if texture isn't owned
264265
TextureLocation location; // -1 means "no location", usually because it was optimized out
266+
ManagedBufferBase* sourceManagedBuffer; // might be empty if not from a managed buffer. for indexed views, it's the original data source
265267
};
266268

267269
// A thin wrapper around a program handle.
@@ -325,7 +327,7 @@ class GLShaderProgram : public ShaderProgram {
325327
bool hasAttribute(std::string name) override;
326328
bool attributeIsSet(std::string name) override;
327329
std::shared_ptr<AttributeBuffer> getAttributeBuffer(std::string name) override;
328-
void setAttribute(std::string name, std::shared_ptr<AttributeBuffer> externalBuffer) override;
330+
void setAttribute(std::string name, std::shared_ptr<AttributeBuffer> externalBuffer, ManagedBufferBase* source = nullptr) override;
329331
void setAttribute(std::string name, const std::vector<glm::vec2>& data) override;
330332
void setAttribute(std::string name, const std::vector<glm::vec3>& data) override;
331333
void setAttribute(std::string name, const std::vector<glm::vec4>& data) override;
@@ -354,11 +356,14 @@ class GLShaderProgram : public ShaderProgram {
354356
void setTexture2D(std::string name, unsigned char* texData, unsigned int width, unsigned int height,
355357
bool withAlpha = true, bool useMipMap = false, bool repeat = false) override;
356358
void setTextureFromColormap(std::string name, const std::string& colorMap, bool allowUpdate = false) override;
357-
void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer) override;
359+
void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer, ManagedBufferBase* source = nullptr) override;
360+
361+
// ManagedBuffer source registration
358362

359363
// Draw!
360364
void draw() override;
361365
void validateData() override;
366+
void syncBuffersToDeviceIfNeeded();
362367

363368
protected:
364369
// Lists of attributes and uniforms that need to be set

include/polyscope/vector_quantity.ipp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ void VectorQuantity<QuantityT>::createProgram() {
175175
);
176176
// clang-format on
177177
178-
this->vectorProgram->setAttribute("a_vector", vectors.getRenderAttributeBuffer());
179-
this->vectorProgram->setAttribute("a_position", vectorRoots.getRenderAttributeBuffer());
178+
this->vectorProgram->setAttribute("a_vector", vectors);
179+
this->vectorProgram->setAttribute("a_position", vectorRoots);
180180
181181
render::engine->setMaterial(*(this->vectorProgram), this->material.get());
182182
}
@@ -299,10 +299,10 @@ void TangentVectorQuantity<QuantityT>::createProgram() {
299299
);
300300
// clang-format on
301301
302-
this->vectorProgram->setAttribute("a_tangentVector", tangentVectors.getRenderAttributeBuffer());
303-
this->vectorProgram->setAttribute("a_basisVectorX", tangentBasisX.getRenderAttributeBuffer());
304-
this->vectorProgram->setAttribute("a_basisVectorY", tangentBasisY.getRenderAttributeBuffer());
305-
this->vectorProgram->setAttribute("a_position", vectorRoots.getRenderAttributeBuffer());
302+
this->vectorProgram->setAttribute("a_tangentVector", tangentVectors);
303+
this->vectorProgram->setAttribute("a_basisVectorX", tangentBasisX);
304+
this->vectorProgram->setAttribute("a_basisVectorY", tangentBasisY);
305+
this->vectorProgram->setAttribute("a_position", vectorRoots);
306306
307307
render::engine->setMaterial(*(this->vectorProgram), this->material.get());
308308
}

src/color_image_quantity.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ void ColorImageQuantity::prepareFullscreen() {
5656
fullscreenProgram->setAttribute("a_position", render::engine->screenTrianglesCoords());
5757
// TODO throughout polyscope we discard the shared pointer when adding textures/attributes to programs... should we
5858
// just track the shared pointer?
59-
fullscreenProgram->setTextureFromBuffer("t_image", colors.getRenderTextureBuffer().get());
59+
fullscreenProgram->setTextureFromBuffer("t_image", colors);
6060
}
6161

6262
void ColorImageQuantity::prepareBillboard() {
@@ -75,7 +75,7 @@ void ColorImageQuantity::prepareBillboard() {
7575
render::ShaderReplacementDefaults::Process);
7676
// clang-format on
7777
billboardProgram->setAttribute("a_position", render::engine->screenTrianglesCoords());
78-
billboardProgram->setTextureFromBuffer("t_image", colors.getRenderTextureBuffer().get());
78+
billboardProgram->setTextureFromBuffer("t_image", colors);
7979
}
8080

8181
void ColorImageQuantity::showFullscreen() {

0 commit comments

Comments
 (0)