From 8c5f64dbada07ebc1caa6165fe054a2344c1d8f3 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 3 May 2026 18:08:48 -0700 Subject: [PATCH 1/8] add capacity semantics to ManagedBuffer --- include/polyscope/render/engine.h | 4 + include/polyscope/render/managed_buffer.h | 55 +++++++ .../render/mock_opengl/mock_gl_engine.h | 1 + include/polyscope/render/opengl/gl_engine.h | 1 + src/render/managed_buffer.cpp | 138 +++++++++++++++++- src/render/mock_opengl/mock_gl_engine.cpp | 19 ++- src/render/opengl/gl_engine.cpp | 31 ++-- 7 files changed, 226 insertions(+), 23 deletions(-) diff --git a/include/polyscope/render/engine.h b/include/polyscope/render/engine.h index 38349f97..f0ba8959 100644 --- a/include/polyscope/render/engine.h +++ b/include/polyscope/render/engine.h @@ -96,6 +96,10 @@ class AttributeBuffer { virtual void setData(const std::vector>& data) = 0; virtual void setData(const std::vector>& data) = 0; + // Pre-allocate GPU memory for n elements without uploading any data. Subsequent setData() + // calls with size <= n will not need to reallocate the underlying buffer. + virtual void reserveCapacity(size_t n) = 0; + virtual uint32_t getNativeBufferID() = 0; // used to interop with external things, e.g. ImGui // == Getters diff --git a/include/polyscope/render/managed_buffer.h b/include/polyscope/render/managed_buffer.h index 26b97e63..d4b4e524 100644 --- a/include/polyscope/render/managed_buffer.h +++ b/include/polyscope/render/managed_buffer.h @@ -98,6 +98,56 @@ class ManagedBuffer : public virtual WeakReferrable { void setTextureSize(uint32_t sizeX, uint32_t sizeY, uint32_t sizeZ); std::array getTextureSize() const; + // == Resize / capacity API + + // The .size() of the buffer is the number of data elements it holds. + // + // The .capacity() of the buffer is the number of data elements it has capacity for without needing to reallocate + // (similar to std::vector). + // + // In a simple flow where we put data in a buffer once, the capacity is the same as the size and neither ever changes. + // But in settings where we e.g. incrementally add elements or change the number of data elements on each frame, we + // may want to allocate a larger capacity to avoid expensive re-allocation each time. + + // Resize the buffer to newSize elements. + // + // If newSize <= capacity(), this is a cheap constant-time operation which just updates metadata. + // + // If newSize > capacity(), this triggers a full reallocation. The new capacity is set to at least + // 2*oldCapacity (amortized doubling). Any data that was GPU-resident is first copied back to the + // host, then the GPU buffer is reallocated in-place (same buffer object, new backing memory). + // ShaderPrograms holding a reference to the GPU buffer remain valid. + // + // Returns true if a reallocation occurred, false if the resize stayed within capacity. + // + // Valid for attributes and 1D textures only; call the 2D/3D variants below for multidimensional + // textures. + bool resize(size_t newSize); + + // Resize a 2D texture. No-op (returns false) if dimensions are unchanged. Otherwise always + // triggers a reallocation (2D/3D textures have no capacity slack — capacity always equals size), + // reallocates the GPU buffer in-place, and returns true. + bool resizeTexture2D(uint32_t newSizeX, uint32_t newSizeY); + + // Resize a 3D texture. No-op (returns false) if dimensions are unchanged. Otherwise always + // triggers a reallocation (2D/3D textures have no capacity slack — capacity always equals size), + // reallocates the GPU buffer in-place, and returns true. + bool resizeTexture3D(uint32_t newSizeX, uint32_t newSizeY, uint32_t newSizeZ); + + // The maximum size the buffer can be resized to without triggering a reallocation. Always >= size(). + size_t capacity(); + + // Set the managed capacity to newCapacity. Unlike resize(), which grows capacity via amortized + // doubling, this sets the logical capacity to a precise value. Error if newCapacity < size(). + // Reallocates the GPU buffer in-place (same buffer object, new backing memory) if one exists. + // + // Note: data.capacity() is guaranteed to be >= managedCapacity, but may remain larger than + // newCapacity if the underlying vector already had more space allocated. + // + // Valid for attributes and 1D textures only; multidimensional textures always have capacity + // equal to their size, so use the 2D/3D resize() variants instead. + void setCapacity(size_t newCapacity); + // == Members for indexed data @@ -200,6 +250,11 @@ class ManagedBuffer : public virtual WeakReferrable { bool hostBufferIsPopulated; // true if the host buffer contains currently-valid data + // The buffer has capacity for at least this many elements. It is distinct from .size(), which is the actual number of + // elements currently stored in the buffer and may be smaller than the capacity. + // Any resize() operations that stay within the capacity are cheap, and do not trigger a full reallocation and copy. + size_t managedCapacity = 0; + std::shared_ptr renderAttributeBuffer; std::shared_ptr renderTextureBuffer; diff --git a/include/polyscope/render/mock_opengl/mock_gl_engine.h b/include/polyscope/render/mock_opengl/mock_gl_engine.h index cc4d2d49..b135564a 100644 --- a/include/polyscope/render/mock_opengl/mock_gl_engine.h +++ b/include/polyscope/render/mock_opengl/mock_gl_engine.h @@ -73,6 +73,7 @@ class GLAttributeBuffer : public AttributeBuffer { std::vector getDataRange_uvec4(size_t ind, size_t count) override; uint32_t getNativeBufferID() override; + void reserveCapacity(size_t n) override; protected: private: diff --git a/include/polyscope/render/opengl/gl_engine.h b/include/polyscope/render/opengl/gl_engine.h index 7e06d358..ef5ce764 100644 --- a/include/polyscope/render/opengl/gl_engine.h +++ b/include/polyscope/render/opengl/gl_engine.h @@ -101,6 +101,7 @@ class GLAttributeBuffer : public AttributeBuffer { std::vector getDataRange_uvec4(size_t ind, size_t count) override; uint32_t getNativeBufferID() override; + void reserveCapacity(size_t n) override; protected: VertexBufferHandle VBOLoc; diff --git a/src/render/managed_buffer.cpp b/src/render/managed_buffer.cpp index 67dbc7f2..14032ac6 100644 --- a/src/render/managed_buffer.cpp +++ b/src/render/managed_buffer.cpp @@ -18,7 +18,7 @@ namespace render { template ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::string& name_, std::vector& data_) : name(name_), uniqueID(internal::getNextUniqueID()), registry(registry_), data(data_), dataGetsComputed(false), - hostBufferIsPopulated(true) { + hostBufferIsPopulated(true), managedCapacity(data_.size()) { if (registry) { registry->addManagedBuffer(this); @@ -30,7 +30,7 @@ template ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::string& name_, std::vector& data_, std::function computeFunc_) : name(name_), uniqueID(internal::getNextUniqueID()), registry(registry_), data(data_), dataGetsComputed(true), - computeFunc(computeFunc_), hostBufferIsPopulated(false) { + computeFunc(computeFunc_), hostBufferIsPopulated(false), managedCapacity(0) { if (registry) { registry->addManagedBuffer(this); @@ -56,6 +56,8 @@ void ManagedBuffer::setTextureSize(uint32_t sizeX_) { deviceBufferType = DeviceBufferType::Texture1d; sizeX = sizeX_; + // Sync managedCapacity: data is expected to be populated before setTextureSize() is called. + if (data.size() > managedCapacity) managedCapacity = data.size(); } template @@ -66,6 +68,7 @@ void ManagedBuffer::setTextureSize(uint32_t sizeX_, uint32_t sizeY_) { deviceBufferType = DeviceBufferType::Texture2d; sizeX = sizeX_; sizeY = sizeY_; + if (data.size() > managedCapacity) managedCapacity = data.size(); } template @@ -77,6 +80,7 @@ void ManagedBuffer::setTextureSize(uint32_t sizeX_, uint32_t sizeY_, uint32_t sizeX = sizeX_; sizeY = sizeY_; sizeZ = sizeZ_; + if (data.size() > managedCapacity) managedCapacity = data.size(); } template @@ -85,6 +89,126 @@ std::array ManagedBuffer::getTextureSize() const { return std::array{sizeX, sizeY, sizeZ}; } +template +size_t ManagedBuffer::capacity() { + return managedCapacity; +} + +template +bool ManagedBuffer::resize(size_t newSize) { + if (deviceBufferType == DeviceBufferType::Texture2d || deviceBufferType == DeviceBufferType::Texture3d) + exception("resize() is not valid for 2D/3D texture buffers; use resizeTexture2D() or resizeTexture3D()"); + + if (newSize > managedCapacity) { + // Copy device-side data back to host BEFORE modifying data or invalidating the GPU buffer. + // ensureHostBufferPopulated() reads from renderAttributeBuffer into data; if we resize data + // first it would overwrite the resize with the old GPU contents. + ensureHostBufferPopulated(); + + // Reallocation needed: use amortized doubling + size_t newCapacity = std::max(newSize, 2 * managedCapacity); + data.reserve(newCapacity); + data.resize(newSize); + managedCapacity = newCapacity; + + // In-place reallocation: keep the same GPU buffer objects alive so ShaderPrograms + // holding shared_ptr references to them remain valid without needing to be reset. + if (renderAttributeBuffer) { + renderAttributeBuffer->reserveCapacity(managedCapacity); + } + if (renderTextureBuffer && deviceBufferType == DeviceBufferType::Texture1d) { + renderTextureBuffer->resize(static_cast(managedCapacity)); + } + hostBufferIsPopulated = true; + + if (deviceBufferType == DeviceBufferType::Texture1d) { + sizeX = static_cast(newSize); + } + + return true; + } else { + // No reallocation: just update size metadata + data.resize(newSize); + + if (deviceBufferType == DeviceBufferType::Texture1d) { + sizeX = static_cast(newSize); + } + + return false; + } +} + +template +void ManagedBuffer::setCapacity(size_t newCapacity) { + if (deviceBufferType == DeviceBufferType::Texture2d || deviceBufferType == DeviceBufferType::Texture3d) + exception("setCapacity() is not valid for 2D/3D texture buffers"); + + if (newCapacity < data.size()) + exception("setCapacity() cannot set capacity below current size (" + std::to_string(data.size()) + ")"); + + if (newCapacity == managedCapacity) return; // no-op + + // Before invalidating the GPU buffer, copy any device-side changes back to the host. + ensureHostBufferPopulated(); + + data.reserve(newCapacity); + managedCapacity = newCapacity; + + // In-place reallocation: keep the same GPU buffer objects alive. + if (renderAttributeBuffer) { + renderAttributeBuffer->reserveCapacity(managedCapacity); + } + if (renderTextureBuffer && deviceBufferType == DeviceBufferType::Texture1d) { + renderTextureBuffer->resize(static_cast(managedCapacity)); + } + hostBufferIsPopulated = true; +} + +template +bool ManagedBuffer::resizeTexture2D(uint32_t newSizeX, uint32_t newSizeY) { + checkDeviceBufferTypeIs(DeviceBufferType::Texture2d); + + if (newSizeX == sizeX && newSizeY == sizeY) return false; // no-op + + // Before invalidating the GPU buffer, copy any device-side changes back to the host. + ensureHostBufferPopulated(); + + sizeX = newSizeX; + sizeY = newSizeY; + managedCapacity = static_cast(sizeX) * sizeY; + data.resize(managedCapacity); + + if (renderTextureBuffer) { + renderTextureBuffer->resize(sizeX, sizeY); + } + hostBufferIsPopulated = true; + + return true; +} + +template +bool ManagedBuffer::resizeTexture3D(uint32_t newSizeX, uint32_t newSizeY, uint32_t newSizeZ) { + checkDeviceBufferTypeIs(DeviceBufferType::Texture3d); + + if (newSizeX == sizeX && newSizeY == sizeY && newSizeZ == sizeZ) return false; // no-op + + // Before invalidating the GPU buffer, copy any device-side changes back to the host. + ensureHostBufferPopulated(); + + sizeX = newSizeX; + sizeY = newSizeY; + sizeZ = newSizeZ; + managedCapacity = static_cast(sizeX) * sizeY * sizeZ; + data.resize(managedCapacity); + + if (renderTextureBuffer) { + renderTextureBuffer->resize(sizeX, sizeY, sizeZ); + } + hostBufferIsPopulated = true; + + return true; +} + template void ManagedBuffer::ensureHostBufferPopulated() { @@ -312,7 +436,11 @@ std::shared_ptr ManagedBuffer::getRenderAttributeBuf if (!renderAttributeBuffer) { ensureHostBufferPopulated(); // warning: the order of these matters because of how hostBufferPopulated works + // Sync managedCapacity on first GPU allocation: handles the case where data was populated + // externally before this call (e.g. class member ordering caused empty-vector-at-construction). + if (data.size() > managedCapacity) managedCapacity = data.size(); renderAttributeBuffer = generateAttributeBuffer(render::engine); + renderAttributeBuffer->reserveCapacity(managedCapacity); renderAttributeBuffer->setData(data); } return renderAttributeBuffer; @@ -324,6 +452,8 @@ std::shared_ptr ManagedBuffer::getRenderTextureBuffer( if (!renderTextureBuffer) { ensureHostBufferPopulated(); // warning: the order of these matters because of how hostBufferPopulated works + // Sync managedCapacity on first GPU allocation (same rationale as getRenderAttributeBuffer) + if (data.size() > managedCapacity) managedCapacity = data.size(); renderTextureBuffer = generateTextureBuffer(deviceBufferType, render::engine); @@ -333,7 +463,9 @@ std::shared_ptr ManagedBuffer::getRenderTextureBuffer( exception("bad call"); break; case DeviceBufferType::Texture1d: - renderTextureBuffer->resize(sizeX); + // Allocate GPU texture at the full managed capacity so future within-capacity resizes + // don't require a new GPU buffer. setData() below uploads only data.size() elements. + renderTextureBuffer->resize(static_cast(managedCapacity)); break; case DeviceBufferType::Texture2d: renderTextureBuffer->resize(sizeX, sizeY); diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index ef4877e2..a27f0ff8 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -73,19 +73,22 @@ void GLAttributeBuffer::checkArray(int testArrayCount) { } +void GLAttributeBuffer::reserveCapacity(size_t n) { + if (n <= bufferSize) return; + bufferSize = static_cast(n); + setFlag = true; +} + template void GLAttributeBuffer::setData_helper(const std::vector& data) { bind(); - // allocate if needed if (!isSet() || data.size() > bufferSize) { setFlag = true; - uint64_t newSize = data.size(); - newSize = std::max(newSize, 2 * bufferSize); // if we're expanding, at-least double + uint64_t newSize = static_cast(data.size()); bufferSize = newSize; } - // do the actual copy dataSize = data.size(); checkGLError(); @@ -411,7 +414,7 @@ void GLTextureBuffer::setData(const std::vector& data) { bind(); - if (data.size() != getTotalSize()) { + if (data.size() > getTotalSize()) { exception("OpenGL error: texture buffer data is not the right size."); } @@ -431,7 +434,7 @@ void GLTextureBuffer::setData(const std::vector& data) { bind(); - if (data.size() != getTotalSize()) { + if (data.size() > getTotalSize()) { exception("OpenGL error: texture buffer data is not the right size."); } @@ -450,7 +453,7 @@ void GLTextureBuffer::setData(const std::vector& data) { void GLTextureBuffer::setData(const std::vector& data) { bind(); - if (data.size() != getTotalSize()) { + if (data.size() > getTotalSize()) { exception("OpenGL error: texture buffer data is not the right size."); } @@ -469,7 +472,7 @@ void GLTextureBuffer::setData(const std::vector& data) { void GLTextureBuffer::setData(const std::vector& data) { bind(); - if (data.size() != getTotalSize()) { + if (data.size() > getTotalSize()) { exception("OpenGL error: texture buffer data is not the right size."); } diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index 608f0133..fe4d80dd 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -246,21 +246,26 @@ void GLAttributeBuffer::checkArray(int testArrayCount) { GLenum GLAttributeBuffer::getTarget() { return GL_ARRAY_BUFFER; } +void GLAttributeBuffer::reserveCapacity(size_t n) { + if (n <= bufferSize) return; + bind(); + glBufferData(getTarget(), n * sizeInBytes(dataType) * getArrayCount(), NULL, GL_STATIC_DRAW); + bufferSize = static_cast(n); + setFlag = true; + checkGLError(); +} template void GLAttributeBuffer::setData_helper(const std::vector& data) { bind(); - // allocate if needed if (!isSet() || data.size() > bufferSize) { setFlag = true; - uint64_t newSize = data.size(); - newSize = std::max(newSize, 2 * bufferSize); // if we're expanding, at-least double + uint64_t newSize = static_cast(data.size()); glBufferData(getTarget(), newSize * sizeof(T), NULL, GL_STATIC_DRAW); bufferSize = newSize; } - // do the actual copy dataSize = data.size(); glBufferSubData(getTarget(), 0, dataSize * sizeof(T), data.data()); @@ -614,13 +619,15 @@ void GLTextureBuffer::setData(const std::vector& data) { bind(); - if (data.size() != getTotalSize()) { + // For 1D textures, data.size() may be <= sizeX (partial upload into a capacity-allocated texture). + // For 2D/3D textures, capacity always equals size so the check is effectively ==. + if (data.size() > getTotalSize()) { exception("OpenGL error: texture buffer data is not the right size."); } switch (dim) { case 1: - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, sizeX, formatF(format), type(format), &data.front().x); + glTexSubImage1D(GL_TEXTURE_1D, 0, 0, static_cast(data.size()), formatF(format), type(format), &data.front().x); break; case 2: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, sizeX, sizeY, formatF(format), type(format), &data.front().x); @@ -637,13 +644,13 @@ void GLTextureBuffer::setData(const std::vector& data) { bind(); - if (data.size() != getTotalSize()) { + if (data.size() > getTotalSize()) { exception("OpenGL error: texture buffer data is not the right size."); } switch (dim) { case 1: - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, sizeX, formatF(format), type(format), &data.front().x); + glTexSubImage1D(GL_TEXTURE_1D, 0, 0, static_cast(data.size()), formatF(format), type(format), &data.front().x); break; case 2: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, sizeX, sizeY, formatF(format), type(format), &data.front().x); @@ -659,13 +666,13 @@ void GLTextureBuffer::setData(const std::vector& data) { void GLTextureBuffer::setData(const std::vector& data) { bind(); - if (data.size() != getTotalSize()) { + if (data.size() > getTotalSize()) { exception("OpenGL error: texture buffer data is not the right size."); } switch (dim) { case 1: - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, sizeX, formatF(format), type(format), &data.front()); + glTexSubImage1D(GL_TEXTURE_1D, 0, 0, static_cast(data.size()), formatF(format), type(format), &data.front()); break; case 2: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, sizeX, sizeY, formatF(format), type(format), &data.front()); @@ -689,13 +696,13 @@ void GLTextureBuffer::setData(const std::vector& data) { bind(); - if (data.size() != getTotalSize()) { + if (data.size() > getTotalSize()) { exception("OpenGL error: texture buffer data is not the right size."); } switch (dim) { case 1: - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, sizeX, formatF(format), type(format), &dataFloat.front()); + glTexSubImage1D(GL_TEXTURE_1D, 0, 0, static_cast(data.size()), formatF(format), type(format), &dataFloat.front()); break; case 2: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, sizeX, sizeY, formatF(format), type(format), &dataFloat.front()); From 735a44c8b804eb02f3f5346f6245ab639f79f428 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 3 May 2026 20:02:50 -0700 Subject: [PATCH 2/8] make ManagedBuffer own the .data buffer, rather than wrapping it --- include/polyscope/color_image_quantity.h | 2 - include/polyscope/color_quantity.h | 2 - include/polyscope/color_quantity.ipp | 2 +- .../polyscope/color_render_image_quantity.h | 3 - include/polyscope/curve_network.h | 7 -- .../polyscope/curve_network_color_quantity.h | 3 - .../polyscope/curve_network_scalar_quantity.h | 3 - include/polyscope/parameterization_quantity.h | 3 - .../polyscope/parameterization_quantity.ipp | 5 +- include/polyscope/point_cloud.h | 2 - .../raw_color_alpha_render_image_quantity.h | 3 - .../raw_color_render_image_quantity.h | 3 - include/polyscope/render/managed_buffer.h | 9 ++- .../polyscope/render_image_quantity_base.h | 4 -- include/polyscope/scalar_quantity.h | 1 - include/polyscope/scalar_quantity.ipp | 2 +- include/polyscope/simple_triangle_mesh.h | 4 -- include/polyscope/sparse_volume_grid.h | 5 -- include/polyscope/surface_mesh.h | 35 ---------- include/polyscope/vector_quantity.h | 5 -- include/polyscope/vector_quantity.ipp | 12 ++-- include/polyscope/volume_grid.h | 5 -- include/polyscope/volume_mesh.h | 21 ------ src/color_image_quantity.cpp | 5 +- src/color_render_image_quantity.cpp | 2 +- src/curve_network.cpp | 24 ++++--- src/curve_network_color_quantity.cpp | 2 +- src/curve_network_scalar_quantity.cpp | 2 +- src/point_cloud.cpp | 5 +- src/raw_color_alpha_render_image_quantity.cpp | 2 +- src/raw_color_render_image_quantity.cpp | 2 +- src/render/managed_buffer.cpp | 12 ++-- src/render_image_quantity_base.cpp | 7 +- src/simple_triangle_mesh.cpp | 6 +- src/sparse_volume_grid.cpp | 42 +++++------ src/surface_mesh.cpp | 70 +++++++++---------- src/volume_grid.cpp | 6 +- src/volume_mesh.cpp | 19 +++-- 38 files changed, 118 insertions(+), 229 deletions(-) diff --git a/include/polyscope/color_image_quantity.h b/include/polyscope/color_image_quantity.h index 4761f6f1..c249f582 100644 --- a/include/polyscope/color_image_quantity.h +++ b/include/polyscope/color_image_quantity.h @@ -34,8 +34,6 @@ class ColorImageQuantity : public ImageQuantity { protected: - std::vector colorsData; - PersistentValue isPremultiplied; // rendering internals diff --git a/include/polyscope/color_quantity.h b/include/polyscope/color_quantity.h index e880bb87..4a63e604 100644 --- a/include/polyscope/color_quantity.h +++ b/include/polyscope/color_quantity.h @@ -39,8 +39,6 @@ class ColorQuantity { // === ~DANGER~ experimental/unsupported functions protected: - std::vector colorsData; - // === Visualization parameters // Parameters diff --git a/include/polyscope/color_quantity.ipp b/include/polyscope/color_quantity.ipp index 3a14a9b3..8d7b248f 100644 --- a/include/polyscope/color_quantity.ipp +++ b/include/polyscope/color_quantity.ipp @@ -4,7 +4,7 @@ namespace polyscope { template ColorQuantity::ColorQuantity(QuantityT& quantity_, const std::vector& colors_) - : quantity(quantity_), colors(&quantity, quantity.uniquePrefix() + "colors", colorsData), colorsData(colors_) { + : quantity(quantity_), colors(&quantity, quantity.uniquePrefix() + "colors", std::vector(colors_)) { colors.checkInvalidValues(); } diff --git a/include/polyscope/color_render_image_quantity.h b/include/polyscope/color_render_image_quantity.h index 9edfe428..c2012460 100644 --- a/include/polyscope/color_render_image_quantity.h +++ b/include/polyscope/color_render_image_quantity.h @@ -37,9 +37,6 @@ class ColorRenderImageQuantity : public RenderImageQuantityBase { protected: // === Visualization parameters - // Store the raw data - std::vector colorsData; - // === Render data std::shared_ptr program; diff --git a/include/polyscope/curve_network.h b/include/polyscope/curve_network.h index 45fd818b..48dbf6db 100644 --- a/include/polyscope/curve_network.h +++ b/include/polyscope/curve_network.h @@ -162,13 +162,6 @@ class CurveNetwork : public Structure { private: - // Storage for the managed buffers above. You should generally interact with these through the managed buffers, not - // these members. - std::vector nodePositionsData; - std::vector edgeTailIndsData; - std::vector edgeTipIndsData; - std::vector edgeCentersData; - void computeEdgeCenters(); // === Visualization parameters diff --git a/include/polyscope/curve_network_color_quantity.h b/include/polyscope/curve_network_color_quantity.h index 717c0afd..348513be 100644 --- a/include/polyscope/curve_network_color_quantity.h +++ b/include/polyscope/curve_network_color_quantity.h @@ -58,9 +58,6 @@ class CurveNetworkEdgeColorQuantity : public CurveNetworkColorQuantity { render::ManagedBuffer nodeAverageColors; void updateNodeAverageColors(); - -private: - std::vector nodeAverageColorsData; }; } // namespace polyscope diff --git a/include/polyscope/curve_network_scalar_quantity.h b/include/polyscope/curve_network_scalar_quantity.h index 8885a9cc..7307f730 100644 --- a/include/polyscope/curve_network_scalar_quantity.h +++ b/include/polyscope/curve_network_scalar_quantity.h @@ -59,9 +59,6 @@ class CurveNetworkEdgeScalarQuantity : public CurveNetworkScalarQuantity { render::ManagedBuffer nodeAverageValues; void updateNodeAverageValues(); - -private: - std::vector nodeAverageValuesData; }; diff --git a/include/polyscope/parameterization_quantity.h b/include/polyscope/parameterization_quantity.h index 6504848a..7b2e4c3f 100644 --- a/include/polyscope/parameterization_quantity.h +++ b/include/polyscope/parameterization_quantity.h @@ -70,9 +70,6 @@ class ParameterizationQuantity { void setParameterizationUniforms(render::ShaderProgram& p); protected: - // Raw storage for the data. You should only interact with this via the managed buffer above - std::vector coordsData; - std::vector islandLabelsData; bool islandLabelsPopulated = false; // === Visualization parameters diff --git a/include/polyscope/parameterization_quantity.ipp b/include/polyscope/parameterization_quantity.ipp index 80f5028b..4db3408d 100644 --- a/include/polyscope/parameterization_quantity.ipp +++ b/include/polyscope/parameterization_quantity.ipp @@ -40,9 +40,8 @@ ParameterizationQuantity::ParameterizationQuantity(QuantityT& quantit : quantity(quantity_), // buffers - coords(&quantity, quantity.uniquePrefix() + "#coords", coordsData), - islandLabels(&quantity, quantity.uniquePrefix() + "#islandLabels", islandLabelsData), coordsType(type_), - coordsData(coords_), + coords(&quantity, quantity.uniquePrefix() + "#coords", std::vector(coords_)), + islandLabels(&quantity, quantity.uniquePrefix() + "#islandLabels", std::vector{}), coordsType(type_), // options checkerSize(quantity.uniquePrefix() + "#checkerSize", 0.02), diff --git a/include/polyscope/point_cloud.h b/include/polyscope/point_cloud.h index 40e8b6e7..346b8ddd 100644 --- a/include/polyscope/point_cloud.h +++ b/include/polyscope/point_cloud.h @@ -154,8 +154,6 @@ class PointCloud : public Structure { private: - // Storage for the managed buffers above. You should generally interact with this directly through them. - std::vector pointsData; // === Visualization parameters PersistentValue pointRenderMode; diff --git a/include/polyscope/raw_color_alpha_render_image_quantity.h b/include/polyscope/raw_color_alpha_render_image_quantity.h index 03fc0cf0..ce48e84f 100644 --- a/include/polyscope/raw_color_alpha_render_image_quantity.h +++ b/include/polyscope/raw_color_alpha_render_image_quantity.h @@ -35,9 +35,6 @@ class RawColorAlphaRenderImageQuantity : public RenderImageQuantityBase { bool getIsPremultiplied(); protected: - // Store the raw data - std::vector colorsData; - // === Visualization parameters PersistentValue isPremultiplied; diff --git a/include/polyscope/raw_color_render_image_quantity.h b/include/polyscope/raw_color_render_image_quantity.h index 73a46707..e9bdffa6 100644 --- a/include/polyscope/raw_color_render_image_quantity.h +++ b/include/polyscope/raw_color_render_image_quantity.h @@ -35,9 +35,6 @@ class RawColorRenderImageQuantity : public RenderImageQuantityBase { protected: // === Visualization parameters - // Store the raw data - std::vector colorsData; - // === Render data std::shared_ptr program; diff --git a/include/polyscope/render/managed_buffer.h b/include/polyscope/render/managed_buffer.h index d4b4e524..d22ba370 100644 --- a/include/polyscope/render/managed_buffer.h +++ b/include/polyscope/render/managed_buffer.h @@ -40,11 +40,11 @@ class ManagedBuffer : public virtual WeakReferrable { // (second variants are advanced versions which allow creation of multi-dimensional texture values) // Manage a buffer of data which is explicitly set externally. - ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, std::vector& data); + ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, std::vector data); // Manage a buffer of data which gets computed lazily - ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, std::vector& data, + ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, std::function computeFunc); @@ -63,8 +63,7 @@ class ManagedBuffer : public virtual WeakReferrable { ManagedBufferRegistry* registry; - // The raw underlying buffer which this class wraps that holds the data. - // It is assumed that it never changes length (although this class may clear it to empty). + // The raw underlying buffer which this class owns and holds the data. // // It is possible that data.size() == 0 if the data is lazily computed and has not been computed yet, or if this // host-side buffer is invalidated because it is being updated externally directly on the render device. @@ -75,7 +74,7 @@ class ManagedBuffer : public virtual WeakReferrable { // buff.markHostBufferUpdated(); // // - std::vector& data; + std::vector data; // == Members for computed data diff --git a/include/polyscope/render_image_quantity_base.h b/include/polyscope/render_image_quantity_base.h index f98dd2c0..b658be94 100644 --- a/include/polyscope/render_image_quantity_base.h +++ b/include/polyscope/render_image_quantity_base.h @@ -63,10 +63,6 @@ class RenderImageQuantityBase : public FloatingQuantity, public FullscreenArtist const bool hasNormals; ImageOrigin imageOrigin; - // Store the raw data - std::vector depthsData; - std::vector normalsData; - // === Visualization parameters PersistentValue material; PersistentValue transparency; diff --git a/include/polyscope/scalar_quantity.h b/include/polyscope/scalar_quantity.h index 037b20bd..d651602c 100644 --- a/include/polyscope/scalar_quantity.h +++ b/include/polyscope/scalar_quantity.h @@ -85,7 +85,6 @@ class ScalarQuantity { double getIsolineWidth(); protected: - std::vector valuesData; const DataType dataType; // === Visualization parameters diff --git a/include/polyscope/scalar_quantity.ipp b/include/polyscope/scalar_quantity.ipp index c286f740..04c3ca69 100644 --- a/include/polyscope/scalar_quantity.ipp +++ b/include/polyscope/scalar_quantity.ipp @@ -8,7 +8,7 @@ namespace polyscope { template ScalarQuantity::ScalarQuantity(QuantityT& quantity_, const std::vector& values_, DataType dataType_) - : quantity(quantity_), values(&quantity, quantity.uniquePrefix() + "values", valuesData), valuesData(values_), + : quantity(quantity_), values(&quantity, quantity.uniquePrefix() + "values", std::vector(values_)), dataType(dataType_), dataRange(robustMinMax(values.data, 1e-5)), vizRangeMin(quantity.uniquePrefix() + "vizRangeMin", -777.), // set later, vizRangeMax(quantity.uniquePrefix() + "vizRangeMax", -777.), // including clearing cache diff --git a/include/polyscope/simple_triangle_mesh.h b/include/polyscope/simple_triangle_mesh.h index 4b0c6041..181d1c1f 100644 --- a/include/polyscope/simple_triangle_mesh.h +++ b/include/polyscope/simple_triangle_mesh.h @@ -93,10 +93,6 @@ class SimpleTriangleMesh : public Structure { private: - // Storage for the managed buffers above. You should generally interact with this directly through them. - std::vector verticesData; - std::vector facesData; - // === Visualization parameters PersistentValue surfaceColor; PersistentValue material; diff --git a/include/polyscope/sparse_volume_grid.h b/include/polyscope/sparse_volume_grid.h index 7fbe2f9d..b64dd03e 100644 --- a/include/polyscope/sparse_volume_grid.h +++ b/include/polyscope/sparse_volume_grid.h @@ -165,17 +165,12 @@ class SparseVolumeGrid : public Structure { glm::vec3 origin; glm::vec3 gridCellWidth; - // === Storage for managed quantities - std::vector cellPositionsData; - std::vector cellIndicesData; - // User-facing occupied cell indices (signed) std::vector occupiedCellsData; // Canonical sorted node indices and corner index buffers (lazily computed) bool haveCornerNodeIndices = false; std::vector canonicalNodeIndsData; - std::vector cornerNodeIndsData[8]; // === Visualization parameters PersistentValue color; diff --git a/include/polyscope/surface_mesh.h b/include/polyscope/surface_mesh.h index bff5ef31..7df67a26 100644 --- a/include/polyscope/surface_mesh.h +++ b/include/polyscope/surface_mesh.h @@ -308,41 +308,6 @@ class SurfaceMesh : public Structure { private: - // == Mesh geometry buffers - // Storage for the managed buffers above. You should generally interact with these through the managed buffers, not - // these members. - - // = positions - std::vector vertexPositionsData; - - // = connectivity / indices - - // other derived indices, all defined per corner of the triangulated mesh - std::vector triangleVertexIndsData; // index of the corresponding vertex - std::vector triangleFaceIndsData; // index of the corresponding original face - std::vector triangleCornerIndsData; // index of the corresponding original corner - std::vector triangleAllVertexIndsData; // index of the corresponding original vertex - std::vector triangleAllEdgeIndsData; // index of the corresponding original edge - std::vector triangleAllHalfedgeIndsData; // index of the corresponding original halfedge - std::vector triangleAllCornerIndsData; // index of the corresponding original corner - - // internal triangle data for rendering, defined per corner of the triangulated mesh - std::vector baryCoordData; // always triangulated - std::vector edgeIsRealData; // always triangulated - - // other internally-computed geometry - std::vector faceNormalsData; - std::vector faceCentersData; - std::vector faceAreasData; - std::vector vertexNormalsData; - std::vector vertexAreasData; - // std::vector edgeLengthsData; - - // tangent spaces - std::vector defaultFaceTangentBasisXData; - std::vector defaultFaceTangentBasisYData; - - // Derived connectivity quantities bool halfedgesHaveBeenUsed = false; bool cornersHaveBeenUsed = false; diff --git a/include/polyscope/vector_quantity.h b/include/polyscope/vector_quantity.h index f5f82ac5..15738463 100644 --- a/include/polyscope/vector_quantity.h +++ b/include/polyscope/vector_quantity.h @@ -114,8 +114,6 @@ class VectorQuantity : public VectorQuantityBase { // helpers void createProgram(); void updateMaxLength(); - - std::vector vectorsData; }; @@ -158,9 +156,6 @@ class TangentVectorQuantity : public VectorQuantityBase { void createProgram(); void updateMaxLength(); - std::vector tangentVectorsData; - std::vector tangentBasisXData; - std::vector tangentBasisYData; int nSym; }; diff --git a/include/polyscope/vector_quantity.ipp b/include/polyscope/vector_quantity.ipp index 8819451c..9721dc13 100644 --- a/include/polyscope/vector_quantity.ipp +++ b/include/polyscope/vector_quantity.ipp @@ -122,8 +122,8 @@ template VectorQuantity::VectorQuantity(QuantityT& quantity_, const std::vector& vectors_, render::ManagedBuffer& vectorRoots_, VectorType vectorType_) : VectorQuantityBase(quantity_, vectorType_), - vectors(&quantity_, quantity_.uniquePrefix() + "#values", vectorsData), vectorRoots(vectorRoots_), - vectorsData(vectors_) { + vectors(&quantity_, quantity_.uniquePrefix() + "#values", std::vector(vectors_)), + vectorRoots(vectorRoots_) { vectors.checkInvalidValues(); this->updateMaxLength(); } @@ -232,10 +232,10 @@ TangentVectorQuantity::TangentVectorQuantity(QuantityT& quantity_, VectorType vectorType_) : VectorQuantityBase(quantity_, vectorType_), - tangentVectors(&quantity_, quantity_.uniquePrefix() + "#values", tangentVectorsData), - tangentBasisX(&quantity_, quantity_.uniquePrefix() + "#basisX", tangentBasisXData), - tangentBasisY(&quantity_, quantity_.uniquePrefix() + "#basisY", tangentBasisYData), vectorRoots(vectorRoots_), - tangentVectorsData(tangentVectors_), tangentBasisXData(tangentBasisX_), tangentBasisYData(tangentBasisY_), + tangentVectors(&quantity_, quantity_.uniquePrefix() + "#values", std::vector(tangentVectors_)), + tangentBasisX(&quantity_, quantity_.uniquePrefix() + "#basisX", std::vector(tangentBasisX_)), + tangentBasisY(&quantity_, quantity_.uniquePrefix() + "#basisY", std::vector(tangentBasisY_)), + vectorRoots(vectorRoots_), nSym(nSym_) { tangentVectors.checkInvalidValues(); tangentBasisX.checkInvalidValues(); diff --git a/include/polyscope/volume_grid.h b/include/polyscope/volume_grid.h index 8161af09..a8a9fc93 100644 --- a/include/polyscope/volume_grid.h +++ b/include/polyscope/volume_grid.h @@ -151,11 +151,6 @@ class VolumeGrid : public Structure { glm::uvec3 gridCellDim; glm::vec3 boundMin, boundMax; - // === Storage for managed quantities - std::vector gridPlaneReferencePositionsData; - std::vector gridPlaneReferenceNormalsData; - std::vector gridPlaneAxisIndsData; - // === Visualization parameters PersistentValue color; PersistentValue edgeColor; diff --git a/include/polyscope/volume_mesh.h b/include/polyscope/volume_mesh.h index ffb8eddc..ddd11484 100644 --- a/include/polyscope/volume_mesh.h +++ b/include/polyscope/volume_mesh.h @@ -186,27 +186,6 @@ class VolumeMesh : public Structure { private: - // == Mesh geometry buffers - // Storage for the managed buffers above. You should generally interact with these through the managed buffers, not - // these members. - - // positions - std::vector vertexPositionsData; - - // connectivity / indices - std::vector triangleVertexIndsData; // to the split, triangulated mesh - std::vector triangleFaceIndsData; // to the split, triangulated mesh - std::vector triangleCellIndsData; // to the split, triangulated mesh - - // internal triangle data for rendering - std::vector baryCoordData; - std::vector edgeIsRealData; - std::vector faceTypeData; - - // other internally-computed geometry - std::vector faceNormalsData; - std::vector cellCentersData; - // Visualization settings PersistentValue color; PersistentValue interiorColor; diff --git a/src/color_image_quantity.cpp b/src/color_image_quantity.cpp index 1046ce25..82482a90 100644 --- a/src/color_image_quantity.cpp +++ b/src/color_image_quantity.cpp @@ -13,8 +13,9 @@ namespace polyscope { ColorImageQuantity::ColorImageQuantity(Structure& parent_, std::string name, size_t dimX, size_t dimY, const std::vector& data_, ImageOrigin imageOrigin_) - : ImageQuantity(parent_, name, dimX, dimY, imageOrigin_), colors(this, uniquePrefix() + "colors", colorsData), - colorsData(data_), isPremultiplied(uniquePrefix() + "isPremultiplied", false) { + : ImageQuantity(parent_, name, dimX, dimY, imageOrigin_), + colors(this, uniquePrefix() + "colors", std::vector(data_)), + isPremultiplied(uniquePrefix() + "isPremultiplied", false) { colors.setTextureSize(dimX, dimY); } diff --git a/src/color_render_image_quantity.cpp b/src/color_render_image_quantity.cpp index 9a6b39fc..266d6254 100644 --- a/src/color_render_image_quantity.cpp +++ b/src/color_render_image_quantity.cpp @@ -16,7 +16,7 @@ ColorRenderImageQuantity::ColorRenderImageQuantity(Structure& parent_, std::stri const std::vector& normalData, const std::vector& colorsData_, ImageOrigin imageOrigin) : RenderImageQuantityBase(parent_, name, dimX, dimY, depthData, normalData, imageOrigin), - colors(this, uniquePrefix() + "colors", colorsData), colorsData(colorsData_) { + colors(this, uniquePrefix() + "colors", std::vector(colorsData_)) { colors.setTextureSize(dimX, dimY); } diff --git a/src/curve_network.cpp b/src/curve_network.cpp index fbae2a74..6a3ef2fd 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -20,13 +20,12 @@ const std::string CurveNetwork::structureTypeName = "Curve Network"; // Constructor CurveNetwork::CurveNetwork(std::string name, std::vector nodes_, std::vector> edges_) : // clang-format off - Structure(name, typeName()), - nodePositions(this, uniquePrefix() + "nodePositions", nodePositionsData), - edgeTailInds(this, uniquePrefix() + "edgeTailInds", edgeTailIndsData), - edgeTipInds(this, uniquePrefix() + "edgeTipInds", edgeTipIndsData), - edgeCenters(this, uniquePrefix() + "edgeCenters", edgeCentersData, std::bind(&CurveNetwork::computeEdgeCenters, this)), - nodePositionsData(std::move(nodes_)), - color(uniquePrefix() + "#color", getNextUniqueColor()), + Structure(name, typeName()), + nodePositions(this, uniquePrefix() + "nodePositions", std::move(nodes_)), + edgeTailInds(this, uniquePrefix() + "edgeTailInds", std::vector{}), + edgeTipInds(this, uniquePrefix() + "edgeTipInds", std::vector{}), + edgeCenters(this, uniquePrefix() + "edgeCenters", std::bind(&CurveNetwork::computeEdgeCenters, this)), + color(uniquePrefix() + "#color", getNextUniqueColor()), radius(uniquePrefix() + "#radius", relativeValue(0.005)), material(uniquePrefix() + "#material", "clay") // clang-format on @@ -34,8 +33,8 @@ CurveNetwork::CurveNetwork(std::string name, std::vector nodes_, std: nodePositions.checkInvalidValues(); // Copy interleaved data in to tip and tails buffers below - edgeTailIndsData.resize(edges_.size()); - edgeTipIndsData.resize(edges_.size()); + edgeTailInds.data.resize(edges_.size()); + edgeTipInds.data.resize(edges_.size()); // Compute node degrees; some quantities want them for visualizations nodeDegrees = std::vector(nNodes(), 0); @@ -46,8 +45,8 @@ CurveNetwork::CurveNetwork(std::string name, std::vector nodes_, std: size_t nA = std::get<0>(edge); size_t nB = std::get<1>(edge); - edgeTailIndsData[iE] = nA; - edgeTipIndsData[iE] = nB; + edgeTailInds.data[iE] = nA; + edgeTipInds.data[iE] = nB; // Make sure there are no out of bounds indices if (nA >= maxInd || nB >= maxInd) { @@ -61,6 +60,9 @@ CurveNetwork::CurveNetwork(std::string name, std::vector nodes_, std: nodeDegrees[nB]++; } + edgeTailInds.markHostBufferUpdated(); + edgeTipInds.markHostBufferUpdated(); + updateObjectSpaceBounds(); } diff --git a/src/curve_network_color_quantity.cpp b/src/curve_network_color_quantity.cpp index 9b65fcd2..22486ee6 100644 --- a/src/curve_network_color_quantity.cpp +++ b/src/curve_network_color_quantity.cpp @@ -110,7 +110,7 @@ void CurveNetworkColorQuantity::refresh() { CurveNetworkEdgeColorQuantity::CurveNetworkEdgeColorQuantity(std::string name, std::vector values_, CurveNetwork& network_) : CurveNetworkColorQuantity(name, network_, "edge", values_), - nodeAverageColors(this, uniquePrefix() + "#nodeAverageColors", nodeAverageColorsData) {} + nodeAverageColors(this, uniquePrefix() + "#nodeAverageColors", std::vector{}) {} void CurveNetworkEdgeColorQuantity::createProgram() { diff --git a/src/curve_network_scalar_quantity.cpp b/src/curve_network_scalar_quantity.cpp index 65fcf6c0..fd70deba 100644 --- a/src/curve_network_scalar_quantity.cpp +++ b/src/curve_network_scalar_quantity.cpp @@ -130,7 +130,7 @@ void CurveNetworkNodeScalarQuantity::buildNodeInfoGUI(size_t nInd) { CurveNetworkEdgeScalarQuantity::CurveNetworkEdgeScalarQuantity(std::string name, const std::vector& values_, CurveNetwork& network_, DataType dataType_) : CurveNetworkScalarQuantity(name, network_, "edge", values_, dataType_), - nodeAverageValues(this, uniquePrefix() + "#nodeAverageValues", nodeAverageValuesData) {} + nodeAverageValues(this, uniquePrefix() + "#nodeAverageValues", std::vector{}) {} void CurveNetworkEdgeScalarQuantity::createProgram() { // Create the program to draw this quantity diff --git a/src/point_cloud.cpp b/src/point_cloud.cpp index edd7a60b..caa92dfc 100644 --- a/src/point_cloud.cpp +++ b/src/point_cloud.cpp @@ -24,9 +24,8 @@ const std::string PointCloud::structureTypeName = "Point Cloud"; // Constructor PointCloud::PointCloud(std::string name, std::vector points_) : // clang-format off - Structure(name, structureTypeName), - points(this, uniquePrefix() + "points", pointsData), - pointsData(std::move(points_)), + Structure(name, structureTypeName), + points(this, uniquePrefix() + "points", std::move(points_)), pointRenderMode(uniquePrefix() + "pointRenderMode", "sphere"), pointColor(uniquePrefix() + "pointColor", getNextUniqueColor()), pointRadius(uniquePrefix() + "pointRadius", relativeValue(0.005)), diff --git a/src/raw_color_alpha_render_image_quantity.cpp b/src/raw_color_alpha_render_image_quantity.cpp index 89190ab6..b4ab180e 100644 --- a/src/raw_color_alpha_render_image_quantity.cpp +++ b/src/raw_color_alpha_render_image_quantity.cpp @@ -16,7 +16,7 @@ RawColorAlphaRenderImageQuantity::RawColorAlphaRenderImageQuantity(Structure& pa const std::vector& colorsData_, ImageOrigin imageOrigin) : RenderImageQuantityBase(parent_, name, dimX, dimY, depthData, std::vector(), imageOrigin), - colors(this, uniquePrefix() + "colors", colorsData), colorsData(colorsData_), + colors(this, uniquePrefix() + "colors", std::vector(colorsData_)), isPremultiplied(uniquePrefix() + "isPremultiplied", false) { colors.setTextureSize(dimX, dimY); } diff --git a/src/raw_color_render_image_quantity.cpp b/src/raw_color_render_image_quantity.cpp index a3b32b0b..5ba8cd52 100644 --- a/src/raw_color_render_image_quantity.cpp +++ b/src/raw_color_render_image_quantity.cpp @@ -16,7 +16,7 @@ RawColorRenderImageQuantity::RawColorRenderImageQuantity(Structure& parent_, std const std::vector& colorsData_, ImageOrigin imageOrigin) : RenderImageQuantityBase(parent_, name, dimX, dimY, depthData, std::vector(), imageOrigin), - colors(this, uniquePrefix() + "colors", colorsData), colorsData(colorsData_) { + colors(this, uniquePrefix() + "colors", std::vector(colorsData_)) { colors.setTextureSize(dimX, dimY); } diff --git a/src/render/managed_buffer.cpp b/src/render/managed_buffer.cpp index 14032ac6..65a54df6 100644 --- a/src/render/managed_buffer.cpp +++ b/src/render/managed_buffer.cpp @@ -16,9 +16,11 @@ namespace polyscope { namespace render { template -ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::string& name_, std::vector& data_) - : name(name_), uniqueID(internal::getNextUniqueID()), registry(registry_), data(data_), dataGetsComputed(false), - hostBufferIsPopulated(true), managedCapacity(data_.size()) { +ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::string& name_, std::vector data_) + : name(name_), uniqueID(internal::getNextUniqueID()), registry(registry_), data(std::move(data_)), + dataGetsComputed(false), hostBufferIsPopulated(true) { + + managedCapacity = data.size(); if (registry) { registry->addManagedBuffer(this); @@ -27,9 +29,9 @@ ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::str template -ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::string& name_, std::vector& data_, +ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::string& name_, std::function computeFunc_) - : name(name_), uniqueID(internal::getNextUniqueID()), registry(registry_), data(data_), dataGetsComputed(true), + : name(name_), uniqueID(internal::getNextUniqueID()), registry(registry_), dataGetsComputed(true), computeFunc(computeFunc_), hostBufferIsPopulated(false), managedCapacity(0) { if (registry) { diff --git a/src/render_image_quantity_base.cpp b/src/render_image_quantity_base.cpp index 356356dd..9c8295f8 100644 --- a/src/render_image_quantity_base.cpp +++ b/src/render_image_quantity_base.cpp @@ -12,9 +12,10 @@ namespace polyscope { RenderImageQuantityBase::RenderImageQuantityBase(Structure& parent_, std::string name, size_t dimX_, size_t dimY_, const std::vector& depthData_, const std::vector& normalData_, ImageOrigin imageOrigin_) - : FloatingQuantity(name, parent_), depths(this, uniquePrefix() + "depths", depthsData), - normals(this, uniquePrefix() + "normals", normalsData), dimX(dimX_), dimY(dimY_), - hasNormals(normalData_.size() > 0), imageOrigin(imageOrigin_), depthsData(depthData_), normalsData(normalData_), + : FloatingQuantity(name, parent_), + depths(this, uniquePrefix() + "depths", std::vector(depthData_)), + normals(this, uniquePrefix() + "normals", std::vector(normalData_)), dimX(dimX_), dimY(dimY_), + hasNormals(normalData_.size() > 0), imageOrigin(imageOrigin_), material(uniquePrefix() + "material", "clay"), transparency(uniquePrefix() + "transparency", 1.0), allowFullscreenCompositing(uniquePrefix() + "allowFullscreenCompositing", false) { depths.setTextureSize(dimX, dimY); diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index 549a2ce4..6dfb0103 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -21,10 +21,8 @@ SimpleTriangleMesh::SimpleTriangleMesh(std::string name, std::vector std::vector faces_) : // clang-format off Structure(name, structureTypeName), - vertices(this, uniquePrefix() + "vertices", verticesData), - faces(this, uniquePrefix() + "faces", facesData), - verticesData(std::move(vertices_)), - facesData(std::move(faces_)), + vertices(this, uniquePrefix() + "vertices", std::move(vertices_)), + faces(this, uniquePrefix() + "faces", std::move(faces_)), surfaceColor(uniquePrefix() + "surfaceColor", getNextUniqueColor()), material(uniquePrefix() + "material", "clay"), backFacePolicy(uniquePrefix() + "backFacePolicy", BackFacePolicy::Different), diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index a5436a5e..0c6ec1f3 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -33,17 +33,17 @@ SparseVolumeGrid::SparseVolumeGrid(std::string name, glm::vec3 origin_, glm::vec : Structure(name, typeName()), // clang-format off // == managed quantities - cellPositions(this, uniquePrefix() + "#cellPositions", cellPositionsData, std::bind(&SparseVolumeGrid::computeCellPositions, this)), - cellIndices(this, uniquePrefix() + "#cellIndices", cellIndicesData, [](){/* do nothing, gets handled by computeCellPositions */}), + cellPositions(this, uniquePrefix() + "#cellPositions", std::bind(&SparseVolumeGrid::computeCellPositions, this)), + cellIndices(this, uniquePrefix() + "#cellIndices", [](){/* do nothing, gets handled by computeCellPositions */}), cornerNodeInds{ - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds0", cornerNodeIndsData[0]), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds1", cornerNodeIndsData[1]), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds2", cornerNodeIndsData[2]), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds3", cornerNodeIndsData[3]), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds4", cornerNodeIndsData[4]), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds5", cornerNodeIndsData[5]), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds6", cornerNodeIndsData[6]), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds7", cornerNodeIndsData[7]), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds0", std::vector{}), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds1", std::vector{}), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds2", std::vector{}), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds3", std::vector{}), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds4", std::vector{}), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds5", std::vector{}), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds6", std::vector{}), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds7", std::vector{}), }, origin(origin_), gridCellWidth(gridCellWidth_), @@ -88,13 +88,13 @@ void SparseVolumeGrid::checkForDuplicateCells() { void SparseVolumeGrid::computeCellPositions() { size_t n = occupiedCellsData.size(); - cellPositionsData.resize(n); - cellIndicesData.resize(n); + cellPositions.data.resize(n); + cellIndices.data.resize(n); for (size_t i = 0; i < n; i++) { glm::ivec3 ijk = occupiedCellsData[i]; - cellPositionsData[i] = origin + (glm::vec3(ijk) + 0.5f) * gridCellWidth; - cellIndicesData[i] = ijk; + cellPositions.data[i] = origin + (glm::vec3(ijk) + 0.5f) * gridCellWidth; + cellIndices.data[i] = ijk; } cellPositions.markHostBufferUpdated(); @@ -142,7 +142,7 @@ void SparseVolumeGrid::computeCornerNodeIndices() { // Build corner index buffers using hashmap lookup for (int c = 0; c < 8; c++) { - cornerNodeIndsData[c].resize(n); + cornerNodeInds[c].data.resize(n); } for (size_t i = 0; i < n; i++) { @@ -152,7 +152,7 @@ void SparseVolumeGrid::computeCornerNodeIndices() { for (int dz = 0; dz < 2; dz++) { int c = dx * 4 + dy * 2 + dz; glm::ivec3 nodeIjk(ci.x + dx, ci.y + dy, ci.z + dz); - cornerNodeIndsData[c][i] = nodeToIndex[nodeIjk]; + cornerNodeInds[c].data[i] = nodeToIndex[nodeIjk]; } } } @@ -505,16 +505,16 @@ void SparseVolumeGrid::ensurePickProgramPrepared() { void SparseVolumeGrid::updateObjectSpaceBounds() { - if (cellPositionsData.empty()) { + if (cellPositions.data.empty()) { // no cells, degenerate bounds at origin objectSpaceBoundingBox = std::make_tuple(origin, origin); objectSpaceLengthScale = glm::length(gridCellWidth); return; } - glm::vec3 bboxMin = cellPositionsData[0]; - glm::vec3 bboxMax = cellPositionsData[0]; - for (const glm::vec3& p : cellPositionsData) { + glm::vec3 bboxMin = cellPositions.data[0]; + glm::vec3 bboxMax = cellPositions.data[0]; + for (const glm::vec3& p : cellPositions.data) { bboxMin = glm::min(bboxMin, p); bboxMax = glm::max(bboxMax, p); } @@ -557,7 +557,7 @@ SparseVolumeGridPickResult SparseVolumeGrid::interpretPickResult(const PickResul glm::vec3 localPos = (rawResult.position - origin) / gridCellWidth; // Find the cell index - glm::ivec3 cellInd3 = cellIndicesData[rawResult.localIndex]; + glm::ivec3 cellInd3 = cellIndices.data[rawResult.localIndex]; // Fractional position within cell [0,1] glm::vec3 fractional = localPos - glm::vec3(cellInd3); diff --git a/src/surface_mesh.cpp b/src/surface_mesh.cpp index a8fa78a1..986cec15 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -28,32 +28,32 @@ SurfaceMesh::SurfaceMesh(std::string name_) // == managed quantities // positions -vertexPositions( this, uniquePrefix() + "vertexPositions", vertexPositionsData), +vertexPositions( this, uniquePrefix() + "vertexPositions", std::vector{}), // connectivity / indices // (triangle and face inds are always computed initially when we triangulate the mesh) -triangleVertexInds( this, uniquePrefix() + "triangleVertexInds", triangleVertexIndsData), -triangleFaceInds( this, uniquePrefix() + "triangleFaceInds", triangleFaceIndsData), -triangleCornerInds( this, uniquePrefix() + "triangleCornerInds", triangleCornerIndsData, std::bind(&SurfaceMesh::computeTriangleCornerInds, this)), -triangleAllVertexInds( this, uniquePrefix() + "triangleAllVertexInds", triangleAllVertexIndsData, std::bind(&SurfaceMesh::computeTriangleAllVertexInds, this)), -triangleAllEdgeInds( this, uniquePrefix() + "triangleAllEdgeInds", triangleAllEdgeIndsData, std::bind(&SurfaceMesh::computeTriangleAllEdgeInds, this)), -triangleAllHalfedgeInds( this, uniquePrefix() + "triangleHalfedgeInds", triangleAllHalfedgeIndsData, std::bind(&SurfaceMesh::computeTriangleAllHalfedgeInds, this)), -triangleAllCornerInds( this, uniquePrefix() + "triangleAllCornerInds", triangleAllCornerIndsData, std::bind(&SurfaceMesh::computeTriangleAllCornerInds, this)), +triangleVertexInds( this, uniquePrefix() + "triangleVertexInds", std::vector{}), +triangleFaceInds( this, uniquePrefix() + "triangleFaceInds", std::vector{}), +triangleCornerInds( this, uniquePrefix() + "triangleCornerInds", std::bind(&SurfaceMesh::computeTriangleCornerInds, this)), +triangleAllVertexInds( this, uniquePrefix() + "triangleAllVertexInds", std::bind(&SurfaceMesh::computeTriangleAllVertexInds, this)), +triangleAllEdgeInds( this, uniquePrefix() + "triangleAllEdgeInds", std::bind(&SurfaceMesh::computeTriangleAllEdgeInds, this)), +triangleAllHalfedgeInds( this, uniquePrefix() + "triangleHalfedgeInds", std::bind(&SurfaceMesh::computeTriangleAllHalfedgeInds, this)), +triangleAllCornerInds( this, uniquePrefix() + "triangleAllCornerInds", std::bind(&SurfaceMesh::computeTriangleAllCornerInds, this)), // internal triangle data for rendering -baryCoord( this, uniquePrefix() + "baryCoord", baryCoordData), -edgeIsReal( this, uniquePrefix() + "edgeIsReal", edgeIsRealData), +baryCoord( this, uniquePrefix() + "baryCoord", std::vector{}), +edgeIsReal( this, uniquePrefix() + "edgeIsReal", std::vector{}), // other internally-computed geometry -faceNormals( this, uniquePrefix() + "faceNormals", faceNormalsData, std::bind(&SurfaceMesh::computeFaceNormals, this)), -faceCenters( this, uniquePrefix() + "faceCenters", faceCentersData, std::bind(&SurfaceMesh::computeFaceCenters, this)), -faceAreas( this, uniquePrefix() + "faceAreas", faceAreasData, std::bind(&SurfaceMesh::computeFaceAreas, this)), -vertexNormals( this, uniquePrefix() + "vertexNormals", vertexNormalsData, std::bind(&SurfaceMesh::computeVertexNormals, this)), -vertexAreas( this, uniquePrefix() + "vertexAreas", vertexAreasData, std::bind(&SurfaceMesh::computeVertexAreas, this)), +faceNormals( this, uniquePrefix() + "faceNormals", std::bind(&SurfaceMesh::computeFaceNormals, this)), +faceCenters( this, uniquePrefix() + "faceCenters", std::bind(&SurfaceMesh::computeFaceCenters, this)), +faceAreas( this, uniquePrefix() + "faceAreas", std::bind(&SurfaceMesh::computeFaceAreas, this)), +vertexNormals( this, uniquePrefix() + "vertexNormals", std::bind(&SurfaceMesh::computeVertexNormals, this)), +vertexAreas( this, uniquePrefix() + "vertexAreas", std::bind(&SurfaceMesh::computeVertexAreas, this)), // tangent spaces -defaultFaceTangentBasisX( this, uniquePrefix() + "defaultFaceTangentBasisX", defaultFaceTangentBasisXData, std::bind(&SurfaceMesh::computeDefaultFaceTangentBasisX, this)), -defaultFaceTangentBasisY( this, uniquePrefix() + "defaultFaceTangentBasisY", defaultFaceTangentBasisYData, std::bind(&SurfaceMesh::computeDefaultFaceTangentBasisY, this)), +defaultFaceTangentBasisX( this, uniquePrefix() + "defaultFaceTangentBasisX", std::bind(&SurfaceMesh::computeDefaultFaceTangentBasisX, this)), +defaultFaceTangentBasisY( this, uniquePrefix() + "defaultFaceTangentBasisY", std::bind(&SurfaceMesh::computeDefaultFaceTangentBasisY, this)), // == persistent options surfaceColor( uniquePrefix() + "surfaceColor", getNextUniqueColor()), @@ -71,7 +71,7 @@ SurfaceMesh::SurfaceMesh(std::string name_, const std::vector& vertex const std::vector& faceIndsEntries_, const std::vector& faceIndsStart_) : SurfaceMesh(name_) { - vertexPositionsData = vertexPositions_; + vertexPositions.data = vertexPositions_; faceIndsEntries = faceIndsEntries_; faceIndsStart = faceIndsStart_; @@ -84,7 +84,7 @@ SurfaceMesh::SurfaceMesh(std::string name_, const std::vector& vertex const std::vector>& facesIn) : SurfaceMesh(name_) { - vertexPositionsData = vertexPositions_; + vertexPositions.data = vertexPositions_; nestedFacesToFlat(facesIn); vertexPositions.checkInvalidValues(); @@ -114,14 +114,14 @@ void SurfaceMesh::computeConnectivityData() { nFacesTriangulationCount = nCornersCount - 2 * numFaces; // fill out these buffers as we construct the triangulation - triangleVertexIndsData.clear(); - triangleVertexIndsData.resize(3 * nFacesTriangulationCount); - triangleFaceIndsData.clear(); - triangleFaceIndsData.resize(3 * nFacesTriangulationCount); - baryCoordData.clear(); - baryCoordData.resize(3 * nFacesTriangulationCount); - edgeIsRealData.clear(); - edgeIsRealData.resize(3 * nFacesTriangulationCount); + triangleVertexInds.data.clear(); + triangleVertexInds.data.resize(3 * nFacesTriangulationCount); + triangleFaceInds.data.clear(); + triangleFaceInds.data.resize(3 * nFacesTriangulationCount); + baryCoord.data.clear(); + baryCoord.data.resize(3 * nFacesTriangulationCount); + edgeIsReal.data.clear(); + edgeIsReal.data.resize(3 * nFacesTriangulationCount); // validate the face-vertex indices for (size_t iV : faceIndsEntries) { @@ -144,17 +144,17 @@ void SurfaceMesh::computeConnectivityData() { uint32_t vC = faceIndsEntries[iStart + ((j + 1) % D)]; // triangle vertex indices - triangleVertexIndsData[3 * iTriFace + 0] = vRoot; - triangleVertexIndsData[3 * iTriFace + 1] = vB; - triangleVertexIndsData[3 * iTriFace + 2] = vC; + triangleVertexInds.data[3 * iTriFace + 0] = vRoot; + triangleVertexInds.data[3 * iTriFace + 1] = vB; + triangleVertexInds.data[3 * iTriFace + 2] = vC; // triangle face indices - for (size_t k = 0; k < 3; k++) triangleFaceIndsData[3 * iTriFace + k] = iF; + for (size_t k = 0; k < 3; k++) triangleFaceInds.data[3 * iTriFace + k] = iF; // barycentric coordinates - baryCoordData[3 * iTriFace + 0] = glm::vec3{1., 0., 0.}; - baryCoordData[3 * iTriFace + 1] = glm::vec3{0., 1., 0.}; - baryCoordData[3 * iTriFace + 2] = glm::vec3{0., 0., 1.}; + baryCoord.data[3 * iTriFace + 0] = glm::vec3{1., 0., 0.}; + baryCoord.data[3 * iTriFace + 1] = glm::vec3{0., 1., 0.}; + baryCoord.data[3 * iTriFace + 2] = glm::vec3{0., 0., 1.}; // internal edges for triangulated polygons glm::vec3 edgeRealV{0., 1., 0.}; @@ -164,7 +164,7 @@ void SurfaceMesh::computeConnectivityData() { if (j + 2 == D) { edgeRealV.z = 1.; } - for (size_t k = 0; k < 3; k++) edgeIsRealData[3 * iTriFace + k] = edgeRealV; + for (size_t k = 0; k < 3; k++) edgeIsReal.data[3 * iTriFace + k] = edgeRealV; iTriFace++; } diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index 15529f70..ae31a314 100644 --- a/src/volume_grid.cpp +++ b/src/volume_grid.cpp @@ -16,9 +16,9 @@ VolumeGrid::VolumeGrid(std::string name, glm::uvec3 gridNodeDim_, glm::vec3 boun // clang-format off // == managed quantities - gridPlaneReferencePositions(this, uniquePrefix() + "#gridPlaneReferencePositions", gridPlaneReferencePositionsData, std::bind(&VolumeGrid::computeGridPlaneReferenceGeometry, this)), - gridPlaneReferenceNormals(this, uniquePrefix() + "#gridPlaneReferenceNormals", gridPlaneReferenceNormalsData, [](){/* do nothing, gets handled by position func */} ), - gridPlaneAxisInds(this, uniquePrefix() + "#gridPlaneAxisInds", gridPlaneAxisIndsData, [](){/* do nothing, gets handled by position func */} ), + gridPlaneReferencePositions(this, uniquePrefix() + "#gridPlaneReferencePositions", std::bind(&VolumeGrid::computeGridPlaneReferenceGeometry, this)), + gridPlaneReferenceNormals(this, uniquePrefix() + "#gridPlaneReferenceNormals", [](){/* do nothing, gets handled by position func */} ), + gridPlaneAxisInds(this, uniquePrefix() + "#gridPlaneAxisInds", [](){/* do nothing, gets handled by position func */} ), gridNodeDim(gridNodeDim_), gridCellDim(gridNodeDim_ - 1u), boundMin(boundMin_), boundMax(boundMax_), diff --git a/src/volume_mesh.cpp b/src/volume_mesh.cpp index dcd6bc1d..3b8907fa 100644 --- a/src/volume_mesh.cpp +++ b/src/volume_mesh.cpp @@ -347,26 +347,25 @@ VolumeMesh::VolumeMesh(std::string name, const std::vector& vertexPos // == managed quantities // positions -vertexPositions( this, uniquePrefix() + "vertexPositions", vertexPositionsData), +vertexPositions( this, uniquePrefix() + "vertexPositions", std::vector(vertexPositions_)), // connectivity / indices -triangleVertexInds( this, uniquePrefix() + "triangleVertexInds", triangleVertexIndsData), -triangleFaceInds( this, uniquePrefix() + "triangleFaceInds", triangleFaceIndsData), -triangleCellInds( this, uniquePrefix() + "triangleCellInds", triangleCellIndsData), +triangleVertexInds( this, uniquePrefix() + "triangleVertexInds", std::vector{}), +triangleFaceInds( this, uniquePrefix() + "triangleFaceInds", std::vector{}), +triangleCellInds( this, uniquePrefix() + "triangleCellInds", std::vector{}), // internal triangle data for rendering -baryCoord( this, uniquePrefix() + "baryCoord", baryCoordData), -edgeIsReal( this, uniquePrefix() + "edgeIsReal", edgeIsRealData), -faceType( this, uniquePrefix() + "faceType", faceTypeData), +baryCoord( this, uniquePrefix() + "baryCoord", std::vector{}), +edgeIsReal( this, uniquePrefix() + "edgeIsReal", std::vector{}), +faceType( this, uniquePrefix() + "faceType", std::vector{}), // other internally-computed geometry -faceNormals( this, uniquePrefix() + "faceNormals", faceNormalsData, std::bind(&VolumeMesh::computeFaceNormals, this)), -cellCenters( this, uniquePrefix() + "cellCenters", cellCentersData, std::bind(&VolumeMesh::computeCellCenters, this)), +faceNormals( this, uniquePrefix() + "faceNormals", std::bind(&VolumeMesh::computeFaceNormals, this)), +cellCenters( this, uniquePrefix() + "cellCenters", std::bind(&VolumeMesh::computeCellCenters, this)), // == core input data cells(cellIndices_), -vertexPositionsData(vertexPositions_), // == persistent options color(uniquePrefix() + "color", getNextUniqueColor()), From e2a8ebc666385c17038d4e244959b65c32dc58b7 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 8 May 2026 23:29:32 -0700 Subject: [PATCH 3/8] ManagedBuffer now owns data arrays, rather than wrapping --- include/polyscope/color_quantity.ipp | 4 +- .../polyscope/color_render_image_quantity.h | 2 +- include/polyscope/curve_network.ipp | 4 +- .../polyscope/parameterization_quantity.ipp | 4 +- include/polyscope/point_cloud.ipp | 4 +- .../raw_color_alpha_render_image_quantity.h | 2 +- .../raw_color_render_image_quantity.h | 2 +- include/polyscope/render/managed_buffer.h | 121 +++++----- include/polyscope/scalar_quantity.ipp | 8 +- .../polyscope/scalar_render_image_quantity.h | 2 +- include/polyscope/simple_triangle_mesh.ipp | 17 +- include/polyscope/surface_mesh.ipp | 4 +- .../surface_parameterization_quantity.h | 3 +- include/polyscope/vector_quantity.ipp | 18 +- include/polyscope/volume_mesh.ipp | 4 +- src/curve_network.cpp | 27 ++- src/curve_network_color_quantity.cpp | 18 +- src/curve_network_scalar_quantity.cpp | 28 ++- src/point_cloud.cpp | 4 +- src/render/managed_buffer.cpp | 143 +++++++++--- src/render_image_quantity_base.cpp | 6 +- src/scalar_render_image_quantity.cpp | 6 +- src/simple_triangle_mesh.cpp | 4 +- src/slice_plane.cpp | 2 + src/sparse_volume_grid.cpp | 27 ++- src/surface_mesh.cpp | 216 +++++++++--------- src/surface_parameterization_quantity.cpp | 17 +- src/surface_vector_quantity.cpp | 31 ++- src/volume_grid.cpp | 37 +-- src/volume_grid_scalar_quantity.cpp | 5 +- src/volume_mesh.cpp | 66 +++--- src/volume_mesh_color_quantity.cpp | 9 +- src/volume_mesh_scalar_quantity.cpp | 27 ++- test/src/volume_grid_test.cpp | 2 +- 34 files changed, 504 insertions(+), 370 deletions(-) diff --git a/include/polyscope/color_quantity.ipp b/include/polyscope/color_quantity.ipp index 8d7b248f..e487c70f 100644 --- a/include/polyscope/color_quantity.ipp +++ b/include/polyscope/color_quantity.ipp @@ -27,7 +27,9 @@ template template void ColorQuantity::updateData(const V& newColors) { validateSize(newColors, colors.size(), "color quantity"); - colors.data = standardizeVectorArray(newColors); + auto d = standardizeVectorArray(newColors); + colors.resize(d.size()); + colors.setDataHost(d); colors.markHostBufferUpdated(); } diff --git a/include/polyscope/color_render_image_quantity.h b/include/polyscope/color_render_image_quantity.h index c2012460..74e578f2 100644 --- a/include/polyscope/color_render_image_quantity.h +++ b/include/polyscope/color_render_image_quantity.h @@ -57,7 +57,7 @@ void ColorRenderImageQuantity::updateBuffers(const T1& depthData, const T2& norm std::vector standardNormal(standardizeVectorArray(normalData)); std::vector standardColor(standardizeVectorArray(colorsData)); - colors.data = standardColor; + colors.setDataHost(standardColor); colors.markHostBufferUpdated(); updateBaseBuffers(standardDepth, standardNormal); diff --git a/include/polyscope/curve_network.ipp b/include/polyscope/curve_network.ipp index dc0dc906..1ff5573a 100644 --- a/include/polyscope/curve_network.ipp +++ b/include/polyscope/curve_network.ipp @@ -170,7 +170,9 @@ CurveNetwork* registerCurveNetworkLoop2D(std::string name, const P& nodes) { template void CurveNetwork::updateNodePositions(const V& newPositions) { validateSize(newPositions, nNodes(), "newPositions"); - nodePositions.data = standardizeVectorArray(newPositions); + auto d = standardizeVectorArray(newPositions); + nodePositions.resize(d.size()); + nodePositions.setDataHost(d); nodePositions.markHostBufferUpdated(); recomputeGeometryIfPopulated(); } diff --git a/include/polyscope/parameterization_quantity.ipp b/include/polyscope/parameterization_quantity.ipp index 4db3408d..86df18b2 100644 --- a/include/polyscope/parameterization_quantity.ipp +++ b/include/polyscope/parameterization_quantity.ipp @@ -226,7 +226,9 @@ template template void ParameterizationQuantity::updateCoords(const V& newCoords) { validateSize(newCoords, coords.size(), "parameterization quantity " + quantity.name); - coords.data = standardizeVectorArray(newCoords); + auto d = standardizeVectorArray(newCoords); + coords.resize(d.size()); + coords.setDataHost(d); coords.markHostBufferUpdated(); } diff --git a/include/polyscope/point_cloud.ipp b/include/polyscope/point_cloud.ipp index dd705aa6..f102aa96 100644 --- a/include/polyscope/point_cloud.ipp +++ b/include/polyscope/point_cloud.ipp @@ -36,7 +36,9 @@ PointCloud* registerPointCloud2D(std::string name, const T& points) { template void PointCloud::updatePointPositions(const V& newPositions) { validateSize(newPositions, nPoints(), "point cloud updated positions " + name); - points.data = standardizeVectorArray(newPositions); + auto d = standardizeVectorArray(newPositions); + points.resize(d.size()); + points.setDataHost(d); points.markHostBufferUpdated(); } diff --git a/include/polyscope/raw_color_alpha_render_image_quantity.h b/include/polyscope/raw_color_alpha_render_image_quantity.h index ce48e84f..4c801d3f 100644 --- a/include/polyscope/raw_color_alpha_render_image_quantity.h +++ b/include/polyscope/raw_color_alpha_render_image_quantity.h @@ -56,7 +56,7 @@ void RawColorAlphaRenderImageQuantity::updateBuffers(const T1& depthData, const std::vector standardNormal; std::vector standardColor(standardizeVectorArray(colorsData)); - colors.data = standardColor; + colors.setDataHost(standardColor); colors.markHostBufferUpdated(); updateBaseBuffers(standardDepth, standardNormal); diff --git a/include/polyscope/raw_color_render_image_quantity.h b/include/polyscope/raw_color_render_image_quantity.h index e9bdffa6..74f96563 100644 --- a/include/polyscope/raw_color_render_image_quantity.h +++ b/include/polyscope/raw_color_render_image_quantity.h @@ -53,7 +53,7 @@ void RawColorRenderImageQuantity::updateBuffers(const T1& depthData, const T2& c std::vector standardNormal; std::vector standardColor(standardizeVectorArray(colorsData)); - colors.data = standardColor; + colors.setDataHost(standardColor); colors.markHostBufferUpdated(); updateBaseBuffers(standardDepth, standardNormal); diff --git a/include/polyscope/render/managed_buffer.h b/include/polyscope/render/managed_buffer.h index d22ba370..7049890a 100644 --- a/include/polyscope/render/managed_buffer.h +++ b/include/polyscope/render/managed_buffer.h @@ -18,20 +18,18 @@ namespace render { class ManagedBufferRegistry; /* - * This class is a wrapper which sits on top of data buffers in Polyscope, and handles common data-management concerns - * of: + * This class owns and manages a typed data buffer in Polyscope, handling common data-management concerns of: * * (a) mirroring the buffer to the GPU/rendering framework * (b) allowing external users to update data either on the CPU- or GPU-side, and updating mirrored copies * appropriately - * (c) managed _indexed_ data, which gts expanded according to some index set for access at rendering time. + * (c) managing _indexed_ data, which gets expanded according to some index set for access at rendering time. * - * Most often this class is used to wrap structure/quantity data passed in by the user, such as a scalar quantity, but - * it is also sometimes wraps automatically-computed values within Polyscope, such as a vertex normal buffer for + * Most often this class is used to hold structure/quantity data passed in by the user, such as a scalar quantity, but + * it also sometimes holds automatically-computed values within Polyscope, such as a vertex normal buffer for * rendering. * - * This class offers functions and accessors which can (and generally, MUST) be used to interact with the underlying - * data buffer. + * Use the public accessor API (getHostValue, setHostValue, setDataHost, begin/end, etc.) to interact with the buffer. */ template class ManagedBuffer : public virtual WeakReferrable { @@ -63,31 +61,6 @@ class ManagedBuffer : public virtual WeakReferrable { ManagedBufferRegistry* registry; - // The raw underlying buffer which this class owns and holds the data. - // - // It is possible that data.size() == 0 if the data is lazily computed and has not been computed yet, or if this - // host-side buffer is invalidated because it is being updated externally directly on the render device. - // - // External users can write directly for this buffer. The required order of operations for writing to this buffer is: - // buff.ensureHostBufferAllocated(); - // buff.data = // fill .data with your values - // buff.markHostBufferUpdated(); - // - // - std::vector data; - - // == Members for computed data - - // This class somewhat weirdly tracks data from two separate sources: - // A) Values directly stored in `data` by an external source - // B) Values that get computed lazily by some function when needed - // - // When `dataGetsComputed = true`, we are in Case B, and computeFunc() must be set to a callback that does the - // lazy computing. - - bool dataGetsComputed; // if true, the value gets computed on-demand by calling computeFunc() - std::function computeFunc; // (optional) callback which populates the `data` buffer - // sanity check helper void checkInvalidValues(); @@ -108,7 +81,7 @@ class ManagedBuffer : public virtual WeakReferrable { // But in settings where we e.g. incrementally add elements or change the number of data elements on each frame, we // may want to allocate a larger capacity to avoid expensive re-allocation each time. - // Resize the buffer to newSize elements. + // Resize the buffer to newSize elements. Sets hostBufferIsPopulated = true. // // If newSize <= capacity(), this is a cheap constant-time operation which just updates metadata. // @@ -140,33 +113,46 @@ class ManagedBuffer : public virtual WeakReferrable { // doubling, this sets the logical capacity to a precise value. Error if newCapacity < size(). // Reallocates the GPU buffer in-place (same buffer object, new backing memory) if one exists. // - // Note: data.capacity() is guaranteed to be >= managedCapacity, but may remain larger than - // newCapacity if the underlying vector already had more space allocated. - // // Valid for attributes and 1D textures only; multidimensional textures always have capacity // equal to their size, so use the 2D/3D resize() variants instead. void setCapacity(size_t newCapacity); - // == Members for indexed data - // == Basic interactions - // Ensure that the `data` member vector reference is populated with the current values. In the common-case where - // the user sets data and it never changes, then this function will do nothing. However, if e.g. the value is - // being updated directly from GPU memory, this will mirror the updates to the cpu-side vector. Also, if the value - // is lazily computed by computeFunc(), it ensures that that function has been called. + // Ensure that the host buffer is populated with the current values. In the common case where the user sets data + // and it never changes, this does nothing. However, if the value is being updated directly from GPU memory, this + // mirrors the updates to the host-side buffer. Also, if the value is lazily computed by computeFunc(), it ensures + // that function has been called. void ensureHostBufferPopulated(); - // Ensure that the `data` member has the proper size. This does _not_ populate the buffer with any particular data, - // just ensures it is allocated. It is useful for when an external wants to fill the buffer with data. - void ensureHostBufferAllocated(); - - // Combines calling ensureHostBufferPopulated() and returning a reference to the `data` member - std::vector& getPopulatedHostBufferRef(); - - // If the contents of `data` are updated, this function MUST be called. It internally handles concerns like - // reflecting updates to the render buffer. + // Copy newData into the host buffer. Errors if newData.size() > capacity() — call resize() or + // setCapacity() first if needed. Does not change capacity. Automatically syncs to any allocated + // device buffers and triggers a redraw. Prefer this over setHostValue() for bulk writes. + void setDataHost(const std::vector& newData); + + // Iterator support. Caller must call ensureHostBufferPopulated() before using these. + // A debug-mode check will fire if this precondition is violated. + const T* begin() const; + const T* end() const; + + // Single-element read. Caller MUST call ensureHostBufferPopulated() before calling this + // (especially in loops — call it once before the loop, not per element). + // A debug-mode check will fire if this precondition is violated. + T getHostValue(size_t ind) const; + T getHostValue(size_t indX, size_t indY) const; // only valid for 2d texture data + T getHostValue(size_t indX, size_t indY, size_t indZ) const; // only valid for 3d texture data + + // Single-element write. Caller MUST call ensureHostBufferPopulated() (or resize()) before + // calling this. Automatically syncs to any allocated device buffers and triggers a redraw. + // For bulk writes, prefer setDataHost() to avoid redundant GPU uploads. + // A debug-mode check will fire if the precondition is violated. + void setHostValue(size_t ind, T val); + void setHostValue(size_t indX, size_t indY, T val); // only valid for 2d texture data + void setHostValue(size_t indX, size_t indY, size_t indZ, T val); // only valid for 3d texture data + + // If the contents of the host buffer have been updated externally, this function MUST be called. + // It internally handles concerns like reflecting updates to the render buffer. void markHostBufferUpdated(); // Get the value at index `i`. It may be dynamically fetched from either the cpu-side `data` member or the render @@ -247,6 +233,17 @@ class ManagedBuffer : public virtual WeakReferrable { protected: // == Internal members + // This class tracks data from two separate sources: + // A) Values directly stored in the buffer by an external source (dataGetsComputed == false) + // B) Values that get computed lazily by some function when needed (dataGetsComputed == true) + // + // When dataGetsComputed is true, computeFunc() must be set to a callback that populates the buffer. + // The callback should call resize() then setHostValue() to populate the buffer. + // The callback does NOT need to call markHostBufferUpdated() — that is handled by the ManagedBuffer + // infrastructure after the callback returns. + bool dataGetsComputed; // if true, the value gets computed on-demand by calling computeFunc() + std::function computeFunc; // (optional) callback which populates the buffer + bool hostBufferIsPopulated; // true if the host buffer contains currently-valid data // The buffer has capacity for at least this many elements. It is distinct from .size(), which is the actual number of @@ -278,17 +275,27 @@ class ManagedBuffer : public virtual WeakReferrable { // == Internal helper functions void invalidateHostBuffer(); - bool deviceBufferTypeIsTexture(); - void checkDeviceBufferTypeIs(DeviceBufferType targetType); - void checkDeviceBufferTypeIsTexture(); + bool deviceBufferTypeIsTexture() const; + void checkDeviceBufferTypeIs(DeviceBufferType targetType) const; + void checkDeviceBufferTypeIsTexture() const; enum class CanonicalDataSource { HostData = 0, NeedsCompute, RenderBuffer }; CanonicalDataSource currentCanonicalDataSource(); - // Manage the program which copies indexed data from the renderBuffer to the indexed views - void ensureHaveBufferIndexCopyProgram(); - void invokeBufferIndexCopyProgram(); + // TODO: add direct GPU-side indexed copy support using a transform-feedback/compute shader program. std::shared_ptr bufferIndexCopyProgram; + +private: + template friend class ManagedBuffer; + + // The raw underlying buffer which this class owns and holds the data. + // + // It is possible that data.size() == 0 if the data is lazily computed and has not been computed yet, or if this + // host-side buffer is invalidated because it is being updated externally directly on the render device. + // + // Use the accessor API (getHostValue, setHostValue, setDataHost, begin/end) to interact + // with this buffer rather than accessing it directly. + std::vector data; }; diff --git a/include/polyscope/scalar_quantity.ipp b/include/polyscope/scalar_quantity.ipp index 04c3ca69..83af60f8 100644 --- a/include/polyscope/scalar_quantity.ipp +++ b/include/polyscope/scalar_quantity.ipp @@ -9,7 +9,7 @@ namespace polyscope { template ScalarQuantity::ScalarQuantity(QuantityT& quantity_, const std::vector& values_, DataType dataType_) : quantity(quantity_), values(&quantity, quantity.uniquePrefix() + "values", std::vector(values_)), - dataType(dataType_), dataRange(robustMinMax(values.data, 1e-5)), + dataType(dataType_), dataRange(robustMinMax(values_, 1e-5)), vizRangeMin(quantity.uniquePrefix() + "vizRangeMin", -777.), // set later, vizRangeMax(quantity.uniquePrefix() + "vizRangeMax", -777.), // including clearing cache colorBar(quantity), cMap(quantity.uniquePrefix() + "cmap", defaultColorMap(dataType)), @@ -23,7 +23,7 @@ ScalarQuantity::ScalarQuantity(QuantityT& quantity_, const std::vecto { values.checkInvalidValues(); colorBar.updateColormap(cMap.get()); - colorBar.buildHistogram(values.data, dataType); + colorBar.buildHistogram(values_, dataType); // TODO: I think we might be building the histogram ^^^ twice for many quantities if (vizRangeMin.holdsDefaultValue()) { // min and max should always have same cache state @@ -312,7 +312,9 @@ template template void ScalarQuantity::updateData(const V& newValues) { validateSize(newValues, values.size(), "scalar quantity " + quantity.name); - values.data = standardizeArray(newValues); + auto d = standardizeArray(newValues); + values.resize(d.size()); + values.setDataHost(d); values.markHostBufferUpdated(); } diff --git a/include/polyscope/scalar_render_image_quantity.h b/include/polyscope/scalar_render_image_quantity.h index 50851e7e..fed2fba0 100644 --- a/include/polyscope/scalar_render_image_quantity.h +++ b/include/polyscope/scalar_render_image_quantity.h @@ -53,7 +53,7 @@ void ScalarRenderImageQuantity::updateBuffers(const T1& depthData, const T2& nor std::vector standardNormal(standardizeVectorArray(normalData)); std::vector standardScalar(standardizeArray(scalarData)); - values.data = standardScalar; + values.setDataHost(standardScalar); values.markHostBufferUpdated(); updateBaseBuffers(standardDepth, standardNormal); diff --git a/include/polyscope/simple_triangle_mesh.ipp b/include/polyscope/simple_triangle_mesh.ipp index dc284bef..ca10bc2c 100644 --- a/include/polyscope/simple_triangle_mesh.ipp +++ b/include/polyscope/simple_triangle_mesh.ipp @@ -26,17 +26,26 @@ SimpleTriangleMesh* registerSimpleTriangleMesh(std::string name, const V& vertex template void SimpleTriangleMesh::updateVertices(const V& newPositions) { validateSize(newPositions, vertices.size(), "newPositions"); - vertices.data = standardizeVectorArray(newPositions); + auto d = standardizeVectorArray(newPositions); + vertices.resize(d.size()); + vertices.setDataHost(d); vertices.markHostBufferUpdated(); } template void SimpleTriangleMesh::update(const V& newPositions, const F& newFaces) { - - vertices.data = standardizeVectorArray(newPositions); + { + auto d = standardizeVectorArray(newPositions); + vertices.resize(d.size()); + vertices.setDataHost(d); + } vertices.markHostBufferUpdated(); - faces.data = standardizeVectorArray(newFaces); + { + auto d = standardizeVectorArray(newFaces); + faces.resize(d.size()); + faces.setDataHost(d); + } faces.markHostBufferUpdated(); } diff --git a/include/polyscope/surface_mesh.ipp b/include/polyscope/surface_mesh.ipp index 7d35e279..cc42bfa1 100644 --- a/include/polyscope/surface_mesh.ipp +++ b/include/polyscope/surface_mesh.ipp @@ -63,7 +63,9 @@ SurfaceMesh* registerSurfaceMesh(std::string name, const V& vertexPositions, con template void SurfaceMesh::updateVertexPositions(const V& newPositions) { validateSize(newPositions, vertexDataSize, "newPositions"); - vertexPositions.data = standardizeVectorArray(newPositions); + auto d = standardizeVectorArray(newPositions); + vertexPositions.resize(d.size()); + vertexPositions.setDataHost(d); vertexPositions.markHostBufferUpdated(); recomputeGeometryIfPopulated(); } diff --git a/include/polyscope/surface_parameterization_quantity.h b/include/polyscope/surface_parameterization_quantity.h index 656c5a9d..40c3d15c 100644 --- a/include/polyscope/surface_parameterization_quantity.h +++ b/include/polyscope/surface_parameterization_quantity.h @@ -94,7 +94,8 @@ class SurfaceVertexParameterizationQuantity : public SurfaceParameterizationQuan template void SurfaceParameterizationQuantity::setIslandLabels(const V& newIslandLabels) { validateSize(newIslandLabels, this->nFaces(), "scalar quantity " + quantity.name); - islandLabels.data = standardizeArray(newIslandLabels); + islandLabels.resize(this->nFaces()); + islandLabels.setDataHost(standardizeArray(newIslandLabels)); islandLabels.markHostBufferUpdated(); islandLabelsPopulated = true; } diff --git a/include/polyscope/vector_quantity.ipp b/include/polyscope/vector_quantity.ipp index 9721dc13..d3db2c3c 100644 --- a/include/polyscope/vector_quantity.ipp +++ b/include/polyscope/vector_quantity.ipp @@ -187,7 +187,7 @@ void VectorQuantity::updateMaxLength() { vectors.ensureHostBufferPopulated(); float maxLength = 0.; - for (const glm::vec3& vec : vectors.data) { + for (const glm::vec3& vec : vectors) { maxLength = std::max(maxLength, glm::length(vec)); } this->vectorLengthRange = maxLength; @@ -202,7 +202,9 @@ template template void VectorQuantity::updateData(const T& newVectors) { validateSize(newVectors, this->vectors.size(), "vector quantity " + this->quantity.name); - this->vectors.data = standardizeVectorArray(newVectors); + auto d = standardizeVectorArray(newVectors); + this->vectors.resize(d.size()); + this->vectors.setDataHost(d); this->vectors.markHostBufferUpdated(); this->updateMaxLength(); } @@ -211,10 +213,12 @@ template template void VectorQuantity::updateData2D(const T& newVectors) { validateSize(newVectors, this->vectors.size(), "vector quantity " + this->quantity.name); - this->vectors.data = standardizeVectorArray(newVectors); - for (auto& v : this->vectors.data) { + auto vectors3D = standardizeVectorArray(newVectors); + for (auto& v : vectors3D) { v.z = 0.; } + this->vectors.resize(vectors3D.size()); + this->vectors.setDataHost(vectors3D); this->vectors.markHostBufferUpdated(); this->updateMaxLength(); } @@ -309,7 +313,7 @@ void TangentVectorQuantity::updateMaxLength() { tangentVectors.ensureHostBufferPopulated(); float maxLength = 0.; - for (const glm::vec2& vec : tangentVectors.data) { + for (const glm::vec2& vec : tangentVectors) { maxLength = std::max(maxLength, glm::length(vec)); } this->vectorLengthRange = maxLength; @@ -324,7 +328,9 @@ template template void TangentVectorQuantity::updateData(const T& newVectors) { validateSize(newVectors, this->tangentVectors.size(), "tangent vector quantity " + this->quantity.name); - this->tangentVectors.data = standardizeVectorArray(newVectors); + auto d = standardizeVectorArray(newVectors); + this->tangentVectors.resize(d.size()); + this->tangentVectors.setDataHost(d); this->tangentVectors.markHostBufferUpdated(); this->updateMaxLength(); } diff --git a/include/polyscope/volume_mesh.ipp b/include/polyscope/volume_mesh.ipp index 3bff6e18..60bf5264 100644 --- a/include/polyscope/volume_mesh.ipp +++ b/include/polyscope/volume_mesh.ipp @@ -87,7 +87,9 @@ VolumeMesh* registerTetHexMesh(std::string name, const V& vertexPositions, const template void VolumeMesh::updateVertexPositions(const V& newPositions) { validateSize(newPositions, nVertices(), "newPositions"); - vertexPositions.data = standardizeVectorArray(newPositions); + auto d = standardizeVectorArray(newPositions); + vertexPositions.resize(d.size()); + vertexPositions.setDataHost(d); vertexPositions.markHostBufferUpdated(); geometryChanged(); } diff --git a/src/curve_network.cpp b/src/curve_network.cpp index 6a3ef2fd..a6a1b580 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -33,8 +33,8 @@ CurveNetwork::CurveNetwork(std::string name, std::vector nodes_, std: nodePositions.checkInvalidValues(); // Copy interleaved data in to tip and tails buffers below - edgeTailInds.data.resize(edges_.size()); - edgeTipInds.data.resize(edges_.size()); + edgeTailInds.resize(edges_.size()); + edgeTipInds.resize(edges_.size()); // Compute node degrees; some quantities want them for visualizations nodeDegrees = std::vector(nNodes(), 0); @@ -45,8 +45,8 @@ CurveNetwork::CurveNetwork(std::string name, std::vector nodes_, std: size_t nA = std::get<0>(edge); size_t nB = std::get<1>(edge); - edgeTailInds.data[iE] = nA; - edgeTipInds.data[iE] = nB; + edgeTailInds.setHostValue(iE, nA); + edgeTipInds.setHostValue(iE, nB); // Make sure there are no out of bounds indices if (nA >= maxInd || nB >= maxInd) { @@ -328,8 +328,8 @@ void CurveNetwork::preparePick() { // Fill posiiton and pick index buffers for (size_t iE = 0; iE < nEdges(); iE++) { - size_t eTail = edgeTailInds.data[iE]; - size_t eTip = edgeTipInds.data[iE]; + size_t eTail = edgeTailInds.getHostValue(iE); + size_t eTip = edgeTipInds.getHostValue(iE); glm::vec3 colorValTail = pick::indToVec(pickStart + eTail); glm::vec3 colorValTip = pick::indToVec(pickStart + eTip); @@ -389,16 +389,15 @@ void CurveNetwork::computeEdgeCenters() { edgeTailInds.ensureHostBufferPopulated(); edgeTipInds.ensureHostBufferPopulated(); - edgeCenters.data.resize(nEdges()); + edgeCenters.resize(nEdges()); for (size_t iE = 0; iE < nEdges(); iE++) { - size_t eTail = edgeTailInds.data[iE]; - size_t eTip = edgeTipInds.data[iE]; - glm::vec3 p = 0.5f * (nodePositions.data[eTail] + nodePositions.data[eTip]); - edgeCenters.data[iE] = p; + size_t eTail = edgeTailInds.getHostValue(iE); + size_t eTip = edgeTipInds.getHostValue(iE); + glm::vec3 p = 0.5f * (nodePositions.getHostValue(eTail) + nodePositions.getHostValue(eTip)); + edgeCenters.setHostValue(iE, p); } - edgeCenters.markHostBufferUpdated(); } void CurveNetwork::refresh() { @@ -539,7 +538,7 @@ void CurveNetwork::updateObjectSpaceBounds() { // bounding box glm::vec3 min = glm::vec3{1, 1, 1} * std::numeric_limits::infinity(); glm::vec3 max = -glm::vec3{1, 1, 1} * std::numeric_limits::infinity(); - for (const glm::vec3& p : nodePositions.data) { + for (const glm::vec3& p : nodePositions) { min = componentwiseMin(min, p); max = componentwiseMax(max, p); } @@ -548,7 +547,7 @@ void CurveNetwork::updateObjectSpaceBounds() { // length scale, as twice the radius from the center of the bounding box glm::vec3 center = 0.5f * (min + max); float lengthScale = 0.0; - for (const glm::vec3& p : nodePositions.data) { + for (const glm::vec3& p : nodePositions) { lengthScale = std::max(lengthScale, glm::length2(p - center)); } objectSpaceLengthScale = 2 * std::sqrt(lengthScale); diff --git a/src/curve_network_color_quantity.cpp b/src/curve_network_color_quantity.cpp index 22486ee6..3ec89cb2 100644 --- a/src/curve_network_color_quantity.cpp +++ b/src/curve_network_color_quantity.cpp @@ -157,20 +157,24 @@ void CurveNetworkEdgeColorQuantity::updateNodeAverageColors() { parent.edgeTailInds.ensureHostBufferPopulated(); parent.edgeTipInds.ensureHostBufferPopulated(); colors.ensureHostBufferPopulated(); - nodeAverageColors.data.resize(parent.nNodes()); + nodeAverageColors.resize(parent.nNodes()); + nodeAverageColors.ensureHostBufferPopulated(); + + // initialize to zero before accumulation + for (size_t iN = 0; iN < parent.nNodes(); iN++) nodeAverageColors.setHostValue(iN, glm::vec3{0., 0., 0.}); for (size_t iE = 0; iE < parent.nEdges(); iE++) { - size_t eTail = parent.edgeTailInds.data[iE]; - size_t eTip = parent.edgeTipInds.data[iE]; + size_t eTail = parent.edgeTailInds.getHostValue(iE); + size_t eTip = parent.edgeTipInds.getHostValue(iE); - nodeAverageColors.data[eTail] += colors.data[iE]; - nodeAverageColors.data[eTip] += colors.data[iE]; + nodeAverageColors.setHostValue(eTail, nodeAverageColors.getHostValue(eTail) + colors.getHostValue(iE)); + nodeAverageColors.setHostValue(eTip, nodeAverageColors.getHostValue(eTip) + colors.getHostValue(iE)); } for (size_t iN = 0; iN < parent.nNodes(); iN++) { - nodeAverageColors.data[iN] /= parent.nodeDegrees[iN]; + nodeAverageColors.setHostValue(iN, nodeAverageColors.getHostValue(iN) / (float)parent.nodeDegrees[iN]); if (parent.nodeDegrees[iN] == 0) { - nodeAverageColors.data[iN] = glm::vec3{0., 0., 0.}; + nodeAverageColors.setHostValue(iN, glm::vec3{0., 0., 0.}); } } diff --git a/src/curve_network_scalar_quantity.cpp b/src/curve_network_scalar_quantity.cpp index fd70deba..7d8bc141 100644 --- a/src/curve_network_scalar_quantity.cpp +++ b/src/curve_network_scalar_quantity.cpp @@ -182,7 +182,8 @@ void CurveNetworkEdgeScalarQuantity::updateNodeAverageValues() { parent.edgeTailInds.ensureHostBufferPopulated(); parent.edgeTipInds.ensureHostBufferPopulated(); values.ensureHostBufferPopulated(); - nodeAverageValues.data.resize(parent.nNodes()); + nodeAverageValues.resize(parent.nNodes()); + nodeAverageValues.ensureHostBufferPopulated(); if (dataType == DataType::CATEGORICAL) { // uncommon case: take the mode of adjacent values @@ -199,11 +200,11 @@ void CurveNetworkEdgeScalarQuantity::updateNodeAverageValues() { }; for (size_t iE = 0; iE < parent.nEdges(); iE++) { - size_t eTail = parent.edgeTailInds.data[iE]; - size_t eTip = parent.edgeTipInds.data[iE]; + size_t eTail = parent.edgeTailInds.getHostValue(iE); + size_t eTip = parent.edgeTipInds.getHostValue(iE); - incrementValueCount(eTail, values.data[iE]); - incrementValueCount(eTip, values.data[iE]); + incrementValueCount(eTail, values.getHostValue(iE)); + incrementValueCount(eTip, values.getHostValue(iE)); } for (size_t iN = 0; iN < parent.nNodes(); iN++) { @@ -216,24 +217,27 @@ void CurveNetworkEdgeScalarQuantity::updateNodeAverageValues() { maxVal = entry.first; } } - nodeAverageValues.data[iN] = maxVal; + nodeAverageValues.setHostValue(iN, maxVal); } } else { // common case: take the mean of adjacent values + // initialize to zero before accumulation + for (size_t iN = 0; iN < parent.nNodes(); iN++) nodeAverageValues.setHostValue(iN, 0.f); + // mean reduction for (size_t iE = 0; iE < parent.nEdges(); iE++) { - size_t eTail = parent.edgeTailInds.data[iE]; - size_t eTip = parent.edgeTipInds.data[iE]; + size_t eTail = parent.edgeTailInds.getHostValue(iE); + size_t eTip = parent.edgeTipInds.getHostValue(iE); - nodeAverageValues.data[eTail] += values.data[iE]; - nodeAverageValues.data[eTip] += values.data[iE]; + nodeAverageValues.setHostValue(eTail, nodeAverageValues.getHostValue(eTail) + values.getHostValue(iE)); + nodeAverageValues.setHostValue(eTip, nodeAverageValues.getHostValue(eTip) + values.getHostValue(iE)); } for (size_t iN = 0; iN < parent.nNodes(); iN++) { - nodeAverageValues.data[iN] /= parent.nodeDegrees[iN]; + nodeAverageValues.setHostValue(iN, nodeAverageValues.getHostValue(iN) / parent.nodeDegrees[iN]); if (parent.nodeDegrees[iN] == 0) { - nodeAverageValues.data[iN] = 0.; + nodeAverageValues.setHostValue(iN, 0.f); } } } diff --git a/src/point_cloud.cpp b/src/point_cloud.cpp index caa92dfc..85251730 100644 --- a/src/point_cloud.cpp +++ b/src/point_cloud.cpp @@ -385,7 +385,7 @@ void PointCloud::updateObjectSpaceBounds() { // bounding box glm::vec3 min = glm::vec3{1, 1, 1} * std::numeric_limits::infinity(); glm::vec3 max = -glm::vec3{1, 1, 1} * std::numeric_limits::infinity(); - for (const glm::vec3& p : points.data) { + for (const glm::vec3& p : points) { min = componentwiseMin(min, p); max = componentwiseMax(max, p); } @@ -394,7 +394,7 @@ void PointCloud::updateObjectSpaceBounds() { // length scale, as twice the radius from the center of the bounding box glm::vec3 center = 0.5f * (min + max); float lengthScale = 0.0; - for (const glm::vec3& p : points.data) { + for (const glm::vec3& p : points) { lengthScale = std::max(lengthScale, glm::length2(p - center)); } objectSpaceLengthScale = 2 * std::sqrt(lengthScale); diff --git a/src/render/managed_buffer.cpp b/src/render/managed_buffer.cpp index 65a54df6..793354bf 100644 --- a/src/render/managed_buffer.cpp +++ b/src/render/managed_buffer.cpp @@ -105,7 +105,12 @@ bool ManagedBuffer::resize(size_t newSize) { // Copy device-side data back to host BEFORE modifying data or invalidating the GPU buffer. // ensureHostBufferPopulated() reads from renderAttributeBuffer into data; if we resize data // first it would overwrite the resize with the old GPU contents. - ensureHostBufferPopulated(); + // Only copy existing data to host if there is data to preserve. + // If state is NeedsCompute, there is no existing data — calling ensureHostBufferPopulated() + // would recursively invoke the compute function that is currently running. + if (currentCanonicalDataSource() != CanonicalDataSource::NeedsCompute) { + ensureHostBufferPopulated(); + } // Reallocation needed: use amortized doubling size_t newCapacity = std::max(newSize, 2 * managedCapacity); @@ -131,6 +136,7 @@ bool ManagedBuffer::resize(size_t newSize) { } else { // No reallocation: just update size metadata data.resize(newSize); + hostBufferIsPopulated = true; if (deviceBufferType == DeviceBufferType::Texture1d) { sizeX = static_cast(newSize); @@ -151,7 +157,10 @@ void ManagedBuffer::setCapacity(size_t newCapacity) { if (newCapacity == managedCapacity) return; // no-op // Before invalidating the GPU buffer, copy any device-side changes back to the host. - ensureHostBufferPopulated(); + // Skip if state is NeedsCompute — there's no existing data to preserve. + if (currentCanonicalDataSource() != CanonicalDataSource::NeedsCompute) { + ensureHostBufferPopulated(); + } data.reserve(newCapacity); managedCapacity = newCapacity; @@ -247,31 +256,115 @@ void ManagedBuffer::ensureHostBufferPopulated() { } template -void ManagedBuffer::ensureHostBufferAllocated() { - data.resize(size()); +const T* ManagedBuffer::begin() const { +#ifndef NDEBUG + if (!hostBufferIsPopulated) + exception("ManagedBuffer " + name + " begin() called without ensureHostBufferPopulated()"); +#endif + return data.data(); } template -std::vector& ManagedBuffer::getPopulatedHostBufferRef() { - ensureHostBufferPopulated(); - return data; +const T* ManagedBuffer::end() const { +#ifndef NDEBUG + if (!hostBufferIsPopulated) + exception("ManagedBuffer " + name + " end() called without ensureHostBufferPopulated()"); +#endif + return data.data() + data.size(); } template -void ManagedBuffer::markHostBufferUpdated() { +void ManagedBuffer::setDataHost(const std::vector& newData) { + if (newData.size() > managedCapacity) + exception("ManagedBuffer " + name + " setDataHost() called with data that exceeds capacity (" + + std::to_string(newData.size()) + " > " + std::to_string(managedCapacity) + "). Call resize() or setCapacity() first."); + data.assign(newData.begin(), newData.end()); hostBufferIsPopulated = true; - // If the data is stored in the device-side buffers, update it as needed + // Sync to any already-allocated device buffers and update indexed views. if (renderAttributeBuffer) { renderAttributeBuffer->setData(data); requestRedraw(); } + if (renderTextureBuffer) { + renderTextureBuffer->setData(data); + requestRedraw(); + } + if (deviceBufferType == DeviceBufferType::Attribute) { + updateIndexedViews(); + requestRedraw(); + } +} +template +T ManagedBuffer::getHostValue(size_t ind) const { +#ifndef NDEBUG + if (!hostBufferIsPopulated) + exception("ManagedBuffer " + name + " getHostValue() called without ensureHostBufferPopulated()"); +#endif + return data[ind]; +} + +template +T ManagedBuffer::getHostValue(size_t indX, size_t indY) const { + checkDeviceBufferTypeIs(DeviceBufferType::Texture2d); + return getHostValue(sizeY * indX + indY); +} + +template +T ManagedBuffer::getHostValue(size_t indX, size_t indY, size_t indZ) const { + checkDeviceBufferTypeIs(DeviceBufferType::Texture3d); + return getHostValue(sizeZ * sizeY * indX + sizeZ * indY + indZ); +} + +template +void ManagedBuffer::setHostValue(size_t ind, T val) { +#ifndef NDEBUG + if (!hostBufferIsPopulated) + exception("ManagedBuffer " + name + " setHostValue() called without ensureHostBufferPopulated()"); +#endif + data[ind] = val; + + // Sync to any already-allocated device buffers. Note: this re-uploads the entire buffer; for + // bulk writes, prefer setDataHost() to avoid repeated GPU uploads. + if (renderAttributeBuffer) { + renderAttributeBuffer->setData(data); + requestRedraw(); + } if (renderTextureBuffer) { renderTextureBuffer->setData(data); requestRedraw(); } + if (deviceBufferType == DeviceBufferType::Attribute) { + updateIndexedViews(); + requestRedraw(); + } +} + +template +void ManagedBuffer::setHostValue(size_t indX, size_t indY, T val) { + checkDeviceBufferTypeIs(DeviceBufferType::Texture2d); + setHostValue(sizeY * indX + indY, val); +} + +template +void ManagedBuffer::setHostValue(size_t indX, size_t indY, size_t indZ, T val) { + checkDeviceBufferTypeIs(DeviceBufferType::Texture3d); + setHostValue(sizeZ * sizeY * indX + sizeZ * indY + indZ, val); +} +template +void ManagedBuffer::markHostBufferUpdated() { + hostBufferIsPopulated = true; + + if (renderAttributeBuffer) { + renderAttributeBuffer->setData(data); + requestRedraw(); + } + if (renderTextureBuffer) { + renderTextureBuffer->setData(data); + requestRedraw(); + } if (deviceBufferType == DeviceBufferType::Attribute) { updateIndexedViews(); requestRedraw(); @@ -291,14 +384,12 @@ T ManagedBuffer::getValue(size_t ind) { if (ind >= data.size()) exception("out of bounds access in ManagedBuffer " + name + " getValue(" + std::to_string(ind) + ")"); return data[ind]; - break; case CanonicalDataSource::NeedsCompute: computeFunc(); if (ind >= data.size()) exception("out of bounds access in ManagedBuffer " + name + " getValue(" + std::to_string(ind) + ")"); return data[ind]; - break; case CanonicalDataSource::RenderBuffer: @@ -309,9 +400,7 @@ T ManagedBuffer::getValue(size_t ind) { if (static_cast(ind) >= renderAttributeBuffer->getDataSize()) exception("out of bounds access in ManagedBuffer " + name + " getValue(" + std::to_string(ind) + ")"); - T val = getAttributeBufferData(*renderAttributeBuffer, ind); - return val; - break; + return getAttributeBufferData(*renderAttributeBuffer, ind); }; return T(); // dummy return @@ -563,8 +652,8 @@ void ManagedBuffer::updateIndexedViews() { std::vector expandData = gather(data, indices.data); viewBuffer.setData(expandData); - // TODO fornow, only CPU-side updating is supported. Add direct GPU-side support using the bufferIndexCopyProgram - // below. + // TODO: add direct GPU-side indexed copy support (without round-tripping through the host) using a + // transform-feedback/compute shader program, to avoid the CPU gather cost for GPU-resident buffers. } requestRedraw(); @@ -591,13 +680,13 @@ void ManagedBuffer::invalidateHostBuffer() { } template -bool ManagedBuffer::deviceBufferTypeIsTexture() { +bool ManagedBuffer::deviceBufferTypeIsTexture() const { return ((deviceBufferType == DeviceBufferType::Texture1d) || (deviceBufferType == DeviceBufferType::Texture2d) || (deviceBufferType == DeviceBufferType::Texture3d)); } template -void ManagedBuffer::checkDeviceBufferTypeIs(DeviceBufferType targetType) { +void ManagedBuffer::checkDeviceBufferTypeIs(DeviceBufferType targetType) const { if (targetType != deviceBufferType) { exception("ManagedBuffer " + name + " has wrong type for this operation. Expected " + deviceBufferTypeName(targetType) + " but is " + deviceBufferTypeName(deviceBufferType)); @@ -605,7 +694,7 @@ void ManagedBuffer::checkDeviceBufferTypeIs(DeviceBufferType targetType) { } template -void ManagedBuffer::checkDeviceBufferTypeIsTexture() { +void ManagedBuffer::checkDeviceBufferTypeIsTexture() const { if (!deviceBufferTypeIsTexture()) { exception("ManagedBuffer " + name + " has wrong type for this operation. Expected a Texture1d/2d/3d but is " + deviceBufferTypeName(deviceBufferType)); @@ -636,22 +725,6 @@ typename ManagedBuffer::CanonicalDataSource ManagedBuffer::currentCanonica } -template -void ManagedBuffer::ensureHaveBufferIndexCopyProgram() { - if (bufferIndexCopyProgram) return; - - // sanity check - if (!renderAttributeBuffer) exception("ManagedBuffer " + name + " asked to copy indices, but has no buffers"); - - // TODO allocate the transform feedback program -} - -template -void ManagedBuffer::invokeBufferIndexCopyProgram() { - ensureHaveBufferIndexCopyProgram(); - bufferIndexCopyProgram->draw(); -} - // === Interact with the buffer registry std::tuple ManagedBufferRegistry::hasManagedBufferType(std::string name) { diff --git a/src/render_image_quantity_base.cpp b/src/render_image_quantity_base.cpp index 9c8295f8..6793142c 100644 --- a/src/render_image_quantity_base.cpp +++ b/src/render_image_quantity_base.cpp @@ -53,14 +53,12 @@ void RenderImageQuantityBase::addOptionsPopupEntries() { void RenderImageQuantityBase::updateBaseBuffers(const std::vector& newDepthData, const std::vector& newNormalData) { if (!newDepthData.empty()) { - depths.ensureHostBufferAllocated(); - depths.data = newDepthData; + depths.setDataHost(newDepthData); depths.markHostBufferUpdated(); } if (!newNormalData.empty()) { - normals.ensureHostBufferAllocated(); - normals.data = newNormalData; + normals.setDataHost(newNormalData); normals.markHostBufferUpdated(); } diff --git a/src/scalar_render_image_quantity.cpp b/src/scalar_render_image_quantity.cpp index 2ed82796..19cc195e 100644 --- a/src/scalar_render_image_quantity.cpp +++ b/src/scalar_render_image_quantity.cpp @@ -65,9 +65,9 @@ void ScalarRenderImageQuantity::prepare() { // push the color data to the buffer values.ensureHostBufferPopulated(); - std::vector floatData(values.data.size()); - for (size_t i = 0; i < values.data.size(); i++) { - floatData[i] = static_cast(values.data[i]); + std::vector floatData(values.size()); + for (size_t i = 0; i < values.size(); i++) { + floatData[i] = static_cast(values.getHostValue(i)); } // Create the sourceProgram diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index 6dfb0103..696b55f9 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -272,7 +272,7 @@ void SimpleTriangleMesh::updateObjectSpaceBounds() { // bounding box glm::vec3 min = glm::vec3{1, 1, 1} * std::numeric_limits::infinity(); glm::vec3 max = -glm::vec3{1, 1, 1} * std::numeric_limits::infinity(); - for (const glm::vec3& p : vertices.data) { + for (const glm::vec3& p : vertices) { min = componentwiseMin(min, p); max = componentwiseMax(max, p); } @@ -281,7 +281,7 @@ void SimpleTriangleMesh::updateObjectSpaceBounds() { // length scale, as twice the radius from the center of the bounding box glm::vec3 center = 0.5f * (min + max); float lengthScale = 0.0; - for (const glm::vec3& p : vertices.data) { + for (const glm::vec3& p : vertices) { lengthScale = std::max(lengthScale, glm::length2(p - center)); } objectSpaceLengthScale = 2 * std::sqrt(lengthScale); diff --git a/src/slice_plane.cpp b/src/slice_plane.cpp index f84f4185..9b988f93 100644 --- a/src/slice_plane.cpp +++ b/src/slice_plane.cpp @@ -245,6 +245,8 @@ void SlicePlane::setSliceAttributes(render::ShaderProgram& p) { } for (int i = 0; i < 4; i++) { + sliceBufferArr[i].resize(sliceBufferDataArr[i].size()); + sliceBufferArr[i].setDataHost(sliceBufferDataArr[i]); sliceBufferArr[i].markHostBufferUpdated(); } diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index 0c6ec1f3..1be789e1 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -88,16 +88,17 @@ void SparseVolumeGrid::checkForDuplicateCells() { void SparseVolumeGrid::computeCellPositions() { size_t n = occupiedCellsData.size(); - cellPositions.data.resize(n); - cellIndices.data.resize(n); + cellPositions.resize(n); + cellIndices.resize(n); for (size_t i = 0; i < n; i++) { glm::ivec3 ijk = occupiedCellsData[i]; - cellPositions.data[i] = origin + (glm::vec3(ijk) + 0.5f) * gridCellWidth; - cellIndices.data[i] = ijk; + cellPositions.setHostValue(i, origin + (glm::vec3(ijk) + 0.5f) * gridCellWidth); + cellIndices.setHostValue(i, ijk); } - cellPositions.markHostBufferUpdated(); + // cellPositions.markHostBufferUpdated() is called by recomputeIfPopulated() after this callback. + // cellIndices is a side-effect buffer, so mark it updated explicitly. cellIndices.markHostBufferUpdated(); } @@ -142,7 +143,7 @@ void SparseVolumeGrid::computeCornerNodeIndices() { // Build corner index buffers using hashmap lookup for (int c = 0; c < 8; c++) { - cornerNodeInds[c].data.resize(n); + cornerNodeInds[c].resize(n); } for (size_t i = 0; i < n; i++) { @@ -152,7 +153,7 @@ void SparseVolumeGrid::computeCornerNodeIndices() { for (int dz = 0; dz < 2; dz++) { int c = dx * 4 + dy * 2 + dz; glm::ivec3 nodeIjk(ci.x + dx, ci.y + dy, ci.z + dz); - cornerNodeInds[c].data[i] = nodeToIndex[nodeIjk]; + cornerNodeInds[c].setHostValue(i, nodeToIndex[nodeIjk]); } } } @@ -505,16 +506,17 @@ void SparseVolumeGrid::ensurePickProgramPrepared() { void SparseVolumeGrid::updateObjectSpaceBounds() { - if (cellPositions.data.empty()) { + if (cellPositions.size() == 0) { // no cells, degenerate bounds at origin objectSpaceBoundingBox = std::make_tuple(origin, origin); objectSpaceLengthScale = glm::length(gridCellWidth); return; } - glm::vec3 bboxMin = cellPositions.data[0]; - glm::vec3 bboxMax = cellPositions.data[0]; - for (const glm::vec3& p : cellPositions.data) { + cellPositions.ensureHostBufferPopulated(); + glm::vec3 bboxMin = cellPositions.getHostValue(0); + glm::vec3 bboxMax = cellPositions.getHostValue(0); + for (const glm::vec3& p : cellPositions) { bboxMin = glm::min(bboxMin, p); bboxMax = glm::max(bboxMax, p); } @@ -557,7 +559,8 @@ SparseVolumeGridPickResult SparseVolumeGrid::interpretPickResult(const PickResul glm::vec3 localPos = (rawResult.position - origin) / gridCellWidth; // Find the cell index - glm::ivec3 cellInd3 = cellIndices.data[rawResult.localIndex]; + cellIndices.ensureHostBufferPopulated(); + glm::ivec3 cellInd3 = cellIndices.getHostValue(rawResult.localIndex); // Fractional position within cell [0,1] glm::vec3 fractional = localPos - glm::vec3(cellInd3); diff --git a/src/surface_mesh.cpp b/src/surface_mesh.cpp index 986cec15..21e3cdc6 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -71,7 +71,8 @@ SurfaceMesh::SurfaceMesh(std::string name_, const std::vector& vertex const std::vector& faceIndsEntries_, const std::vector& faceIndsStart_) : SurfaceMesh(name_) { - vertexPositions.data = vertexPositions_; + vertexPositions.resize(vertexPositions_.size()); + vertexPositions.setDataHost(vertexPositions_); faceIndsEntries = faceIndsEntries_; faceIndsStart = faceIndsStart_; @@ -84,7 +85,8 @@ SurfaceMesh::SurfaceMesh(std::string name_, const std::vector& vertex const std::vector>& facesIn) : SurfaceMesh(name_) { - vertexPositions.data = vertexPositions_; + vertexPositions.resize(vertexPositions_.size()); + vertexPositions.setDataHost(vertexPositions_); nestedFacesToFlat(facesIn); vertexPositions.checkInvalidValues(); @@ -114,14 +116,10 @@ void SurfaceMesh::computeConnectivityData() { nFacesTriangulationCount = nCornersCount - 2 * numFaces; // fill out these buffers as we construct the triangulation - triangleVertexInds.data.clear(); - triangleVertexInds.data.resize(3 * nFacesTriangulationCount); - triangleFaceInds.data.clear(); - triangleFaceInds.data.resize(3 * nFacesTriangulationCount); - baryCoord.data.clear(); - baryCoord.data.resize(3 * nFacesTriangulationCount); - edgeIsReal.data.clear(); - edgeIsReal.data.resize(3 * nFacesTriangulationCount); + triangleVertexInds.resize(3 * nFacesTriangulationCount); + triangleFaceInds.resize(3 * nFacesTriangulationCount); + baryCoord.resize(3 * nFacesTriangulationCount); + edgeIsReal.resize(3 * nFacesTriangulationCount); // validate the face-vertex indices for (size_t iV : faceIndsEntries) { @@ -144,17 +142,17 @@ void SurfaceMesh::computeConnectivityData() { uint32_t vC = faceIndsEntries[iStart + ((j + 1) % D)]; // triangle vertex indices - triangleVertexInds.data[3 * iTriFace + 0] = vRoot; - triangleVertexInds.data[3 * iTriFace + 1] = vB; - triangleVertexInds.data[3 * iTriFace + 2] = vC; + triangleVertexInds.setHostValue(3 * iTriFace + 0, vRoot); + triangleVertexInds.setHostValue(3 * iTriFace + 1, vB); + triangleVertexInds.setHostValue(3 * iTriFace + 2, vC); // triangle face indices - for (size_t k = 0; k < 3; k++) triangleFaceInds.data[3 * iTriFace + k] = iF; + for (size_t k = 0; k < 3; k++) triangleFaceInds.setHostValue(3 * iTriFace + k, static_cast(iF)); // barycentric coordinates - baryCoord.data[3 * iTriFace + 0] = glm::vec3{1., 0., 0.}; - baryCoord.data[3 * iTriFace + 1] = glm::vec3{0., 1., 0.}; - baryCoord.data[3 * iTriFace + 2] = glm::vec3{0., 0., 1.}; + baryCoord.setHostValue(3 * iTriFace + 0, glm::vec3{1., 0., 0.}); + baryCoord.setHostValue(3 * iTriFace + 1, glm::vec3{0., 1., 0.}); + baryCoord.setHostValue(3 * iTriFace + 2, glm::vec3{0., 0., 1.}); // internal edges for triangulated polygons glm::vec3 edgeRealV{0., 1., 0.}; @@ -164,7 +162,7 @@ void SurfaceMesh::computeConnectivityData() { if (j + 2 == D) { edgeRealV.z = 1.; } - for (size_t k = 0; k < 3; k++) edgeIsReal.data[3 * iTriFace + k] = edgeRealV; + for (size_t k = 0; k < 3; k++) edgeIsReal.setHostValue(3 * iTriFace + k, edgeRealV); iTriFace++; } @@ -196,7 +194,7 @@ void SurfaceMesh::computeTriangleAllEdgeInds() { "Call setEdgePermutation()."); triangleVertexInds.ensureHostBufferPopulated(); - triangleAllEdgeInds.data.resize(3 * 3 * nFacesTriangulation()); + triangleAllEdgeInds.resize(3 * 3 * nFacesTriangulation()); halfedgeEdgeCorrespondence.resize(nHalfedges()); // used to loop over edges @@ -222,8 +220,8 @@ void SurfaceMesh::computeTriangleAllEdgeInds() { glm::uvec3 thisTriInds{0, 0, 0}; for (size_t j = 0; j < 3; j++) { - size_t vA = triangleVertexInds.data[3 * iF + j]; - size_t vB = triangleVertexInds.data[3 * iF + ((j + 1) % 3)]; + size_t vA = triangleVertexInds.getHostValue(3 * iF + j); + size_t vB = triangleVertexInds.getHostValue(3 * iF + ((j + 1) % 3)); std::pair key = createEdgeKey(vA, vB); @@ -248,13 +246,12 @@ void SurfaceMesh::computeTriangleAllEdgeInds() { for (size_t j = 0; j < 3; j++) { for (size_t k = 0; k < 3; k++) { - triangleAllEdgeInds.data[9 * iF + 3 * j + k] = thisTriInds[k]; + triangleAllEdgeInds.setHostValue(9 * iF + 3 * j + k, thisTriInds[k]); } } } nEdgesCount = psEdgeInd; - triangleAllEdgeInds.markHostBufferUpdated(); } void SurfaceMesh::countEdges() { @@ -269,6 +266,7 @@ void SurfaceMesh::countEdges() { return std::make_pair(std::min(a, b), std::max(a, b)); }; + triangleVertexInds.ensureHostBufferPopulated(); size_t psEdgeInd = 0; // polyscope's edge index, iterated according to Polyscope's canonical ordering for (size_t iF = 0; iF < nFaces(); iF++) { size_t start = faceIndsStart[iF]; @@ -281,8 +279,8 @@ void SurfaceMesh::countEdges() { } for (size_t j = 0; j < 3; j++) { - size_t vA = triangleVertexInds.data[3 * iF + j]; - size_t vB = triangleVertexInds.data[3 * iF + ((j + 1) % 3)]; + size_t vA = triangleVertexInds.getHostValue(3 * iF + j); + size_t vB = triangleVertexInds.getHostValue(3 * iF + ((j + 1) % 3)); std::pair key = createEdgeKey(vA, vB); @@ -304,8 +302,9 @@ size_t SurfaceMesh::nEdges() { void SurfaceMesh::computeTriangleCornerInds() { - triangleCornerInds.data.clear(); - triangleCornerInds.data.reserve(3 * nFacesTriangulation()); + size_t totalCount = 3 * nFacesTriangulation(); + triangleCornerInds.resize(totalCount); + size_t idx = 0; for (size_t iF = 0; iF < nFaces(); iF++) { size_t iStart = faceIndsStart[iF]; @@ -317,19 +316,19 @@ void SurfaceMesh::computeTriangleCornerInds() { uint32_t c1 = iStart + j; uint32_t c2 = iStart + j + 1; - triangleCornerInds.data.push_back(c0); - triangleCornerInds.data.push_back(c1); - triangleCornerInds.data.push_back(c2); + triangleCornerInds.setHostValue(idx++, c0); + triangleCornerInds.setHostValue(idx++, c1); + triangleCornerInds.setHostValue(idx++, c2); } } - triangleCornerInds.markHostBufferUpdated(); } void SurfaceMesh::computeTriangleAllVertexInds() { - triangleAllVertexInds.data.clear(); - triangleAllVertexInds.data.reserve(3 * 3 * nFacesTriangulation()); + size_t totalCount = 3 * 3 * nFacesTriangulation(); + triangleAllVertexInds.resize(totalCount); + size_t idx = 0; for (size_t iF = 0; iF < nFaces(); iF++) { size_t iStart = faceIndsStart[iF]; @@ -343,20 +342,20 @@ void SurfaceMesh::computeTriangleAllVertexInds() { // triangle vertex indices, all three values-each for (size_t k = 0; k < 3; k++) { - triangleAllVertexInds.data.push_back(vRoot); - triangleAllVertexInds.data.push_back(vB); - triangleAllVertexInds.data.push_back(vC); + triangleAllVertexInds.setHostValue(idx++, vRoot); + triangleAllVertexInds.setHostValue(idx++, vB); + triangleAllVertexInds.setHostValue(idx++, vC); } } } - triangleAllVertexInds.markHostBufferUpdated(); } void SurfaceMesh::computeTriangleAllHalfedgeInds() { - triangleAllHalfedgeInds.data.clear(); - triangleAllHalfedgeInds.data.reserve(3 * 3 * nFacesTriangulation()); + size_t totalCount = 3 * 3 * nFacesTriangulation(); + triangleAllHalfedgeInds.resize(totalCount); + size_t idx = 0; bool haveCustomIndex = !halfedgePerm.empty(); @@ -384,20 +383,20 @@ void SurfaceMesh::computeTriangleAllHalfedgeInds() { } for (size_t k = 0; k < 3; k++) { - triangleAllHalfedgeInds.data.push_back(he0); - triangleAllHalfedgeInds.data.push_back(he1); - triangleAllHalfedgeInds.data.push_back(he2); + triangleAllHalfedgeInds.setHostValue(idx++, he0); + triangleAllHalfedgeInds.setHostValue(idx++, he1); + triangleAllHalfedgeInds.setHostValue(idx++, he2); } } } - triangleAllHalfedgeInds.markHostBufferUpdated(); } void SurfaceMesh::computeTriangleAllCornerInds() { - triangleAllCornerInds.data.clear(); - triangleAllCornerInds.data.reserve(3 * nFacesTriangulation()); + size_t totalCount = 3 * 3 * nFacesTriangulation(); + triangleAllCornerInds.resize(totalCount); + size_t idx = 0; bool haveCustomIndex = !cornerPerm.empty(); @@ -418,14 +417,13 @@ void SurfaceMesh::computeTriangleAllCornerInds() { } for (size_t k = 0; k < 3; k++) { - triangleAllCornerInds.data.push_back(c0); - triangleAllCornerInds.data.push_back(c1); - triangleAllCornerInds.data.push_back(c2); + triangleAllCornerInds.setHostValue(idx++, c0); + triangleAllCornerInds.setHostValue(idx++, c1); + triangleAllCornerInds.setHostValue(idx++, c2); } } } - triangleAllCornerInds.markHostBufferUpdated(); } @@ -440,7 +438,7 @@ void SurfaceMesh::computeFaceNormals() { vertexPositions.ensureHostBufferPopulated(); - faceNormals.data.resize(nFaces()); + faceNormals.resize(nFaces()); for (size_t iF = 0; iF < nFaces(); iF++) { size_t iStart = faceIndsStart[iF]; @@ -448,51 +446,49 @@ void SurfaceMesh::computeFaceNormals() { glm::vec3 fN{0., 0., 0.}; if (D == 3) { - glm::vec3 pA = vertexPositions.data[faceIndsEntries[iStart + 0]]; - glm::vec3 pB = vertexPositions.data[faceIndsEntries[iStart + 1]]; - glm::vec3 pC = vertexPositions.data[faceIndsEntries[iStart + 2]]; + glm::vec3 pA = vertexPositions.getHostValue(faceIndsEntries[iStart + 0]); + glm::vec3 pB = vertexPositions.getHostValue(faceIndsEntries[iStart + 1]); + glm::vec3 pC = vertexPositions.getHostValue(faceIndsEntries[iStart + 2]); fN = glm::cross(pB - pA, pC - pA); } else { for (size_t j = 0; j < D; j++) { - glm::vec3 pA = vertexPositions.data[faceIndsEntries[iStart + j]]; - glm::vec3 pB = vertexPositions.data[faceIndsEntries[iStart + (j + 1) % D]]; - glm::vec3 pC = vertexPositions.data[faceIndsEntries[iStart + (j + 2) % D]]; + glm::vec3 pA = vertexPositions.getHostValue(faceIndsEntries[iStart + j]); + glm::vec3 pB = vertexPositions.getHostValue(faceIndsEntries[iStart + (j + 1) % D]); + glm::vec3 pC = vertexPositions.getHostValue(faceIndsEntries[iStart + (j + 2) % D]); fN += glm::cross(pC - pB, pA - pB); } } fN = glm::normalize(fN); - faceNormals.data[iF] = fN; + faceNormals.setHostValue(iF, fN); } - faceNormals.markHostBufferUpdated(); } void SurfaceMesh::computeFaceCenters() { vertexPositions.ensureHostBufferPopulated(); - faceCenters.data.resize(nFaces()); + faceCenters.resize(nFaces()); for (size_t iF = 0; iF < nFaces(); iF++) { size_t start = faceIndsStart[iF]; size_t D = faceIndsStart[iF + 1] - start; glm::vec3 faceCenter{0., 0., 0.}; for (size_t j = 0; j < D; j++) { - glm::vec3 pA = vertexPositions.data[faceIndsEntries[start + j]]; + glm::vec3 pA = vertexPositions.getHostValue(faceIndsEntries[start + j]); faceCenter += pA; } faceCenter /= D; - faceCenters.data[iF] = faceCenter; + faceCenters.setHostValue(iF, faceCenter); } - faceCenters.markHostBufferUpdated(); } void SurfaceMesh::computeFaceAreas() { vertexPositions.ensureHostBufferPopulated(); - faceAreas.data.resize(nFaces()); + faceAreas.resize(nFaces()); // Loop over faces to compute face-valued quantities for (size_t iF = 0; iF < nFaces(); iF++) { @@ -502,25 +498,24 @@ void SurfaceMesh::computeFaceAreas() { // Compute a face normal double fA; if (D == 3) { - glm::vec3 pA = vertexPositions.data[faceIndsEntries[start + 0]]; - glm::vec3 pB = vertexPositions.data[faceIndsEntries[start + 1]]; - glm::vec3 pC = vertexPositions.data[faceIndsEntries[start + 2]]; + glm::vec3 pA = vertexPositions.getHostValue(faceIndsEntries[start + 0]); + glm::vec3 pB = vertexPositions.getHostValue(faceIndsEntries[start + 1]); + glm::vec3 pC = vertexPositions.getHostValue(faceIndsEntries[start + 2]); glm::vec3 fN = glm::cross(pB - pA, pC - pA); fA = 0.5 * glm::length(fN); } else { fA = 0; - glm::vec3 pRoot = vertexPositions.data[faceIndsEntries[start]]; + glm::vec3 pRoot = vertexPositions.getHostValue(faceIndsEntries[start]); for (size_t j = 1; j + 1 < D; j++) { - glm::vec3 pA = vertexPositions.data[faceIndsEntries[start + j]]; - glm::vec3 pB = vertexPositions.data[faceIndsEntries[start + j + 1]]; + glm::vec3 pA = vertexPositions.getHostValue(faceIndsEntries[start + j]); + glm::vec3 pB = vertexPositions.getHostValue(faceIndsEntries[start + j + 1]); fA += 0.5 * glm::length(glm::cross(pA - pRoot, pB - pRoot)); } } - faceAreas.data[iF] = fA; + faceAreas.setHostValue(iF, fA); } - faceAreas.markHostBufferUpdated(); } @@ -529,11 +524,12 @@ void SurfaceMesh::computeVertexNormals() { faceNormals.ensureHostBufferPopulated(); faceAreas.ensureHostBufferPopulated(); - vertexNormals.data.resize(nVertices()); + vertexNormals.resize(nVertices()); + vertexNormals.ensureHostBufferPopulated(); const glm::vec3 zero{0., 0., 0.}; - std::fill(vertexNormals.data.begin(), vertexNormals.data.end(), zero); + for (size_t iV = 0; iV < nVertices(); iV++) vertexNormals.setHostValue(iV, zero); // Accumulate quantities from each face for (size_t iF = 0; iF < nFaces(); iF++) { @@ -541,24 +537,25 @@ void SurfaceMesh::computeVertexNormals() { size_t D = faceIndsStart[iF + 1] - start; for (size_t j = 0; j < D; j++) { size_t iV = faceIndsEntries[start + j]; - vertexNormals.data[iV] += faceNormals.data[iF] * static_cast(faceAreas.data[iF]); + vertexNormals.setHostValue(iV, vertexNormals.getHostValue(iV) + faceNormals.getHostValue(iF) * static_cast(faceAreas.getHostValue(iF))); } } // Normalize for (size_t iV = 0; iV < nVertices(); iV++) { - vertexNormals.data[iV] = glm::normalize(vertexNormals.data[iV]); + vertexNormals.setHostValue(iV, glm::normalize(vertexNormals.getHostValue(iV))); } - vertexNormals.markHostBufferUpdated(); } void SurfaceMesh::computeVertexAreas() { faceAreas.ensureHostBufferPopulated(); - vertexAreas.data.resize(nVertices()); - std::fill(vertexAreas.data.begin(), vertexAreas.data.end(), 0.); + vertexAreas.resize(nVertices()); + vertexAreas.ensureHostBufferPopulated(); + + for (size_t iV = 0; iV < nVertices(); iV++) vertexAreas.setHostValue(iV, 0.); // Accumulate quantities from each face for (size_t iF = 0; iF < nFaces(); iF++) { @@ -566,11 +563,10 @@ void SurfaceMesh::computeVertexAreas() { size_t start = faceIndsStart[iF]; for (size_t j = 0; j < D; j++) { size_t iV = faceIndsEntries[start + j]; - vertexAreas.data[iV] += faceAreas.data[iF] / D; + vertexAreas.setHostValue(iV, vertexAreas.getHostValue(iV) + faceAreas.getHostValue(iF) / D); } } - vertexAreas.markHostBufferUpdated(); } void SurfaceMesh::computeDefaultFaceTangentBasisX() { @@ -581,7 +577,7 @@ void SurfaceMesh::computeDefaultFaceTangentBasisX() { vertexPositions.ensureHostBufferPopulated(); faceNormals.ensureHostBufferPopulated(); - defaultFaceTangentBasisX.data.resize(nFaces()); + defaultFaceTangentBasisX.resize(nFaces()); for (size_t iF = 0; iF < nFaces(); iF++) { size_t D = faceIndsStart[iF + 1] - faceIndsStart[iF]; @@ -589,19 +585,18 @@ void SurfaceMesh::computeDefaultFaceTangentBasisX() { size_t start = faceIndsStart[iF]; - glm::vec3 pA = vertexPositions.data[faceIndsEntries[start + 0]]; - glm::vec3 pB = vertexPositions.data[faceIndsEntries[start + 1]]; - glm::vec3 N = faceNormals.data[iF]; + glm::vec3 pA = vertexPositions.getHostValue(faceIndsEntries[start + 0]); + glm::vec3 pB = vertexPositions.getHostValue(faceIndsEntries[start + 1]); + glm::vec3 N = faceNormals.getHostValue(iF); glm::vec3 basisX = pB - pA; basisX = glm::normalize(basisX - N * glm::dot(N, basisX)); glm::vec3 basisY = glm::normalize(-glm::cross(basisX, N)); - defaultFaceTangentBasisX.data[iF] = basisX; + defaultFaceTangentBasisX.setHostValue(iF, basisX); } - defaultFaceTangentBasisX.markHostBufferUpdated(); } void SurfaceMesh::computeDefaultFaceTangentBasisY() { @@ -612,7 +607,7 @@ void SurfaceMesh::computeDefaultFaceTangentBasisY() { vertexPositions.ensureHostBufferPopulated(); faceNormals.ensureHostBufferPopulated(); - defaultFaceTangentBasisY.data.resize(nFaces()); + defaultFaceTangentBasisY.resize(nFaces()); for (size_t iF = 0; iF < nFaces(); iF++) { size_t D = faceIndsStart[iF + 1] - faceIndsStart[iF]; @@ -620,19 +615,18 @@ void SurfaceMesh::computeDefaultFaceTangentBasisY() { size_t start = faceIndsStart[iF]; - glm::vec3 pA = vertexPositions.data[faceIndsEntries[start + 0]]; - glm::vec3 pB = vertexPositions.data[faceIndsEntries[start + 1]]; - glm::vec3 N = faceNormals.data[iF]; + glm::vec3 pA = vertexPositions.getHostValue(faceIndsEntries[start + 0]); + glm::vec3 pB = vertexPositions.getHostValue(faceIndsEntries[start + 1]); + glm::vec3 N = faceNormals.getHostValue(iF); glm::vec3 basisX = pB - pA; basisX = glm::normalize(basisX - N * glm::dot(N, basisX)); glm::vec3 basisY = glm::normalize(-glm::cross(basisX, N)); - defaultFaceTangentBasisY.data[iF] = basisY; + defaultFaceTangentBasisY.setHostValue(iF, basisY); } - defaultFaceTangentBasisY.markHostBufferUpdated(); } // === Edge Lengths === @@ -680,8 +674,8 @@ void SurfaceMesh::ensureHaveManifoldConnectivity() { // Fill out faceForHalfedge and populate edge lookup map for (size_t iF = 0; iF < nFacesTriangulation(); iF++) { for (size_t j = 0; j < 3; j++) { - size_t iV = triangleVertexInds.data[3 * iF + j]; - size_t iVNext = triangleVertexInds.data[3 * iF + ((j + 1) % 3)]; + size_t iV = triangleVertexInds.getHostValue(3 * iF + j); + size_t iVNext = triangleVertexInds.getHostValue(3 * iF + ((j + 1) % 3)); size_t iHe = 3 * iF + j; @@ -701,8 +695,8 @@ void SurfaceMesh::ensureHaveManifoldConnectivity() { // Second walk through, setting twins for (size_t iF = 0; iF < nFacesTriangulation(); iF++) { for (size_t j = 0; j < 3; j++) { - size_t iV = triangleVertexInds.data[3 * iF + j]; - size_t iVNext = triangleVertexInds.data[3 * iF + ((j + 1) % 3)]; + size_t iV = triangleVertexInds.getHostValue(3 * iF + j); + size_t iVNext = triangleVertexInds.getHostValue(3 * iF + ((j + 1) % 3)); size_t iHe = 3 * iF + j; std::pair edgeKey(std::min(iV, iVNext), std::max(iV, iVNext)); @@ -969,9 +963,9 @@ void SurfaceMesh::setMeshPickAttributes(render::ShaderProgram& p) { // clang-format off std::array vColor = { - pick::indToVec(triangleVertexInds.data[3*iFTri + 0] + vertexGlobalPickIndStart), - pick::indToVec(triangleVertexInds.data[3*iFTri + 1] + vertexGlobalPickIndStart), - pick::indToVec(triangleVertexInds.data[3*iFTri + 2] + vertexGlobalPickIndStart), + pick::indToVec(triangleVertexInds.getHostValue(3*iFTri + 0) + vertexGlobalPickIndStart), + pick::indToVec(triangleVertexInds.getHostValue(3*iFTri + 1) + vertexGlobalPickIndStart), + pick::indToVec(triangleVertexInds.getHostValue(3*iFTri + 2) + vertexGlobalPickIndStart), }; // clang-format on @@ -996,15 +990,17 @@ void SurfaceMesh::setMeshPickAttributes(render::ShaderProgram& p) { if (!usingSimplePick) { if (edgesHaveBeenUsed || halfedgesHaveBeenUsed) { - const std::vector& eDataVec = - (edgesHaveBeenUsed && !halfedgesHaveBeenUsed) ? triangleAllEdgeInds.data : triangleAllHalfedgeInds.data; + triangleAllEdgeInds.ensureHostBufferPopulated(); + triangleAllHalfedgeInds.ensureHostBufferPopulated(); + const uint32_t* eDataVec = + (edgesHaveBeenUsed && !halfedgesHaveBeenUsed) ? triangleAllEdgeInds.begin() : triangleAllHalfedgeInds.begin(); size_t offset = (edgesHaveBeenUsed && !halfedgesHaveBeenUsed) ? edgeGlobalPickIndStart : halfedgeGlobalPickIndStart; // clang-format off - std::array eColor = { - fColor, - pick::indToVec(eDataVec[9*iFTri + 1] + offset), + std::array eColor = { + fColor, + pick::indToVec(eDataVec[9*iFTri + 1] + offset), fColor }; // clang-format on @@ -1022,10 +1018,10 @@ void SurfaceMesh::setMeshPickAttributes(render::ShaderProgram& p) { if (!usingSimplePick) { if (cornersHaveBeenUsed) { // clang-format off - std::array cColor = { - pick::indToVec(triangleCornerInds.data[3*iFTri + 0] + cornerGlobalPickIndStart), - pick::indToVec(triangleCornerInds.data[3*iFTri + 1] + cornerGlobalPickIndStart), - pick::indToVec(triangleCornerInds.data[3*iFTri + 2] + cornerGlobalPickIndStart), + std::array cColor = { + pick::indToVec(triangleCornerInds.getHostValue(3*iFTri + 0) + cornerGlobalPickIndStart), + pick::indToVec(triangleCornerInds.getHostValue(3*iFTri + 1) + cornerGlobalPickIndStart), + pick::indToVec(triangleCornerInds.getHostValue(3*iFTri + 2) + cornerGlobalPickIndStart), }; // clang-format on for (int j = 0; j < 3; j++) cornerColors.push_back(cColor); @@ -1485,7 +1481,7 @@ void SurfaceMesh::updateObjectSpaceBounds() { // bounding box glm::vec3 min = glm::vec3{1, 1, 1} * std::numeric_limits::infinity(); glm::vec3 max = -glm::vec3{1, 1, 1} * std::numeric_limits::infinity(); - for (const glm::vec3& p : vertexPositions.data) { + for (const glm::vec3& p : vertexPositions) { min = componentwiseMin(min, p); max = componentwiseMax(max, p); } @@ -1494,7 +1490,7 @@ void SurfaceMesh::updateObjectSpaceBounds() { // length scale, as twice the radius from the center of the bounding box glm::vec3 center = 0.5f * (min + max); float lengthScale = 0.0; - for (const glm::vec3& p : vertexPositions.data) { + for (const glm::vec3& p : vertexPositions) { lengthScale = std::max(lengthScale, glm::length2(p - center)); } objectSpaceLengthScale = 2 * std::sqrt(lengthScale); diff --git a/src/surface_parameterization_quantity.cpp b/src/surface_parameterization_quantity.cpp index 11945be6..56342e0e 100644 --- a/src/surface_parameterization_quantity.cpp +++ b/src/surface_parameterization_quantity.cpp @@ -133,15 +133,18 @@ CurveNetwork* SurfaceParameterizationQuantity::createCurveNetworkFromSeams(std:: std::set> seamEdges; // loop over all edges + parent.edgeIsReal.ensureHostBufferPopulated(); + parent.triangleVertexInds.ensureHostBufferPopulated(); + parent.triangleCornerInds.ensureHostBufferPopulated(); for(size_t iT = 0; iT < parent.nFacesTriangulation(); iT++) { for(size_t k = 0; k < 3; k++) { - if(parent.edgeIsReal.data[3*iT][k] == 0.) continue; // skip internal tesselation edges + if(parent.edgeIsReal.getHostValue(3*iT)[k] == 0.) continue; // skip internal tesselation edges // gather data for the edge - int32_t iV_tail = parent.triangleVertexInds.data[3*iT + (k+0)%3]; - int32_t iV_tip = parent.triangleVertexInds.data[3*iT + (k+1)%3]; - int32_t iC_tail = parent.triangleCornerInds.data[3*iT + (k+0)%3]; - int32_t iC_tip = parent.triangleCornerInds.data[3*iT + (k+1)%3]; + int32_t iV_tail = parent.triangleVertexInds.getHostValue(3*iT + (k+0)%3); + int32_t iV_tip = parent.triangleVertexInds.getHostValue(3*iT + (k+1)%3); + int32_t iC_tail = parent.triangleCornerInds.getHostValue(3*iT + (k+0)%3); + int32_t iC_tip = parent.triangleCornerInds.getHostValue(3*iT + (k+1)%3); std::pair eInd (iV_tail, iV_tip); std::pair eC (cornerCoords[iC_tail], cornerCoords[iC_tip]); canonicalizeEdge(eInd, eC); // make sure ordering is consistent @@ -184,12 +187,12 @@ CurveNetwork* SurfaceParameterizationQuantity::createCurveNetworkFromSeams(std:: // get unique vertices for the edges if(vertexIndToDense.find(vA) == vertexIndToDense.end()) { vertexIndToDense[vA] = seamEdgeNodes.size(); - seamEdgeNodes.push_back(parent.vertexPositions.data[vA]); + seamEdgeNodes.push_back(parent.vertexPositions.getHostValue(vA)); } int32_t nA = vertexIndToDense[vA]; if(vertexIndToDense.find(vB) == vertexIndToDense.end()) { vertexIndToDense[vB] = seamEdgeNodes.size(); - seamEdgeNodes.push_back(parent.vertexPositions.data[vB]); + seamEdgeNodes.push_back(parent.vertexPositions.getHostValue(vB)); } int32_t nB = vertexIndToDense[vB]; diff --git a/src/surface_vector_quantity.cpp b/src/surface_vector_quantity.cpp index d6de8550..bbffc054 100644 --- a/src/surface_vector_quantity.cpp +++ b/src/surface_vector_quantity.cpp @@ -212,6 +212,16 @@ std::vector oneFormToFaceTangentVectors(SurfaceMesh& mesh, const std: mesh.defaultFaceTangentBasisX.ensureHostBufferPopulated(); mesh.defaultFaceTangentBasisY.ensureHostBufferPopulated(); mesh.triangleAllEdgeInds.ensureHostBufferPopulated(); + mesh.triangleVertexInds.ensureHostBufferPopulated(); + + // TODO why is this duplicated?! + mesh.vertexPositions.ensureHostBufferPopulated(); + mesh.faceAreas.ensureHostBufferPopulated(); + mesh.faceNormals.ensureHostBufferPopulated(); + mesh.defaultFaceTangentBasisX.ensureHostBufferPopulated(); + mesh.defaultFaceTangentBasisY.ensureHostBufferPopulated(); + mesh.triangleAllEdgeInds.ensureHostBufferPopulated(); + mesh.triangleVertexInds.ensureHostBufferPopulated(); std::vector mappedVectorField(mesh.nFaces()); @@ -220,17 +230,17 @@ std::vector oneFormToFaceTangentVectors(SurfaceMesh& mesh, const std: std::array formValues; std::array vecValues; for (size_t j = 0; j < 3; j++) { - size_t vA = mesh.triangleVertexInds.data[3 * iF + j]; - size_t vB = mesh.triangleVertexInds.data[3 * iF + ((j + 1) % 3)]; - size_t iE = mesh.triangleAllEdgeInds.data[9 * iF + j]; + size_t vA = mesh.triangleVertexInds.getHostValue(3 * iF + j); + size_t vB = mesh.triangleVertexInds.getHostValue(3 * iF + ((j + 1) % 3)); + size_t iE = mesh.triangleAllEdgeInds.getHostValue(9 * iF + j); bool isCanonicalOriented = (vB > vA) != (canonicalOrientation[iE]); // TODO double check convention float orientationSign = isCanonicalOriented ? 1.f : -1.f; formValues[j] = orientationSign * oneForm[iE]; - glm::vec3 heVec = mesh.vertexPositions.data[vB] - mesh.vertexPositions.data[vA]; - vecValues[j] = glm::cross(heVec, mesh.faceNormals.data[iF]); + glm::vec3 heVec = mesh.vertexPositions.getHostValue(vB) - mesh.vertexPositions.getHostValue(vA); + vecValues[j] = glm::cross(heVec, mesh.faceNormals.getHostValue(iF)); } // Whitney interpolation at center @@ -238,10 +248,10 @@ std::vector oneFormToFaceTangentVectors(SurfaceMesh& mesh, const std: for (int j = 0; j < 3; j++) { result += (formValues[(j + 1) % 3] - formValues[(j + 2) % 3]) * vecValues[j]; } - result /= static_cast(6. * mesh.faceAreas.data[iF]); + result /= static_cast(6. * mesh.faceAreas.getHostValue(iF)); - glm::vec2 approxVec{glm::dot(result, mesh.defaultFaceTangentBasisX.data[iF]), - glm::dot(result, mesh.defaultFaceTangentBasisY.data[iF])}; + glm::vec2 approxVec{glm::dot(result, mesh.defaultFaceTangentBasisX.getHostValue(iF)), + glm::dot(result, mesh.defaultFaceTangentBasisY.getHostValue(iF))}; mappedVectorField[iF] = approxVec; } @@ -256,8 +266,9 @@ SurfaceOneFormTangentVectorQuantity::SurfaceOneFormTangentVectorQuantity(std::st : SurfaceVectorQuantity(name, mesh_, MeshElement::FACE), TangentVectorQuantity( *this, oneFormToFaceTangentVectors(mesh_, oneForm_, canonicalOrientation_), - mesh_.defaultFaceTangentBasisX.getPopulatedHostBufferRef(), - mesh_.defaultFaceTangentBasisY.getPopulatedHostBufferRef(), parent.faceCenters, 1, VectorType::STANDARD), + std::vector(mesh_.defaultFaceTangentBasisX.begin(), mesh_.defaultFaceTangentBasisX.end()), + std::vector(mesh_.defaultFaceTangentBasisY.begin(), mesh_.defaultFaceTangentBasisY.end()), + parent.faceCenters, 1, VectorType::STANDARD), oneForm(oneForm_), canonicalOrientation(canonicalOrientation_) {} void SurfaceOneFormTangentVectorQuantity::refresh() { diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index ae31a314..fded29a7 100644 --- a/src/volume_grid.cpp +++ b/src/volume_grid.cpp @@ -312,28 +312,28 @@ void VolumeGrid::computeGridPlaneReferenceGeometry() { // Geometry is defined in the reference [0,1] cube - gridPlaneReferencePositions.data.clear(); - gridPlaneReferenceNormals.data.clear(); - gridPlaneAxisInds.data.clear(); + std::vector positionsVec; + std::vector normalsVec; + std::vector axisIndsVec; auto addPlane = [&](std::array corners, glm::vec3 normal, uint32_t axInd) { // first triangle - gridPlaneReferencePositions.data.push_back(corners[0]); - gridPlaneReferencePositions.data.push_back(corners[1]); - gridPlaneReferencePositions.data.push_back(corners[2]); - for (int32_t j = 0; j < 3; j++) gridPlaneReferenceNormals.data.push_back(normal); - for (int32_t j = 0; j < 3; j++) gridPlaneAxisInds.data.push_back(axInd); + positionsVec.push_back(corners[0]); + positionsVec.push_back(corners[1]); + positionsVec.push_back(corners[2]); + for (int32_t j = 0; j < 3; j++) normalsVec.push_back(normal); + for (int32_t j = 0; j < 3; j++) axisIndsVec.push_back((int32_t)axInd); // second triangle - gridPlaneReferencePositions.data.push_back(corners[1]); - gridPlaneReferencePositions.data.push_back(corners[3]); - gridPlaneReferencePositions.data.push_back(corners[2]); - for (int32_t j = 0; j < 3; j++) gridPlaneReferenceNormals.data.push_back(normal); - for (int32_t j = 0; j < 3; j++) gridPlaneAxisInds.data.push_back(axInd); + positionsVec.push_back(corners[1]); + positionsVec.push_back(corners[3]); + positionsVec.push_back(corners[2]); + for (int32_t j = 0; j < 3; j++) normalsVec.push_back(normal); + for (int32_t j = 0; j < 3; j++) axisIndsVec.push_back((int32_t)axInd); }; // The planes are intentionally added in order such that the outermost planes come first, and we don't massively - // overshade from back to front. Note that fthe first look runs backwards. + // overshade from back to front. Note that the first loop runs backwards. // Forward facing planes for (uint32_t d = 0; d < 3; d++) { // x/y/z dimension (plane is perpendicular) @@ -374,7 +374,14 @@ void VolumeGrid::computeGridPlaneReferenceGeometry() { } - gridPlaneReferencePositions.markHostBufferUpdated(); + gridPlaneReferencePositions.resize(positionsVec.size()); + gridPlaneReferencePositions.setDataHost(positionsVec); + gridPlaneReferenceNormals.resize(normalsVec.size()); + gridPlaneReferenceNormals.setDataHost(normalsVec); + gridPlaneAxisInds.resize(axisIndsVec.size()); + gridPlaneAxisInds.setDataHost(axisIndsVec); + // gridPlaneReferencePositions.markHostBufferUpdated() is called by recomputeIfPopulated() after this callback. + // The side-effect buffers must be marked updated explicitly since they are not the primary compute target. gridPlaneReferenceNormals.markHostBufferUpdated(); gridPlaneAxisInds.markHostBufferUpdated(); } diff --git a/src/volume_grid_scalar_quantity.cpp b/src/volume_grid_scalar_quantity.cpp index 769a1957..1010e3d4 100644 --- a/src/volume_grid_scalar_quantity.cpp +++ b/src/volume_grid_scalar_quantity.cpp @@ -165,7 +165,7 @@ void VolumeGridNodeScalarQuantity::createIsosurfaceProgram() { // Extract the isosurface from the level set of the scalar field MC::mcMesh isosurfaceMesh; - MC::marching_cube(&values.data.front(), isosurfaceLevel.get(), parent.getGridNodeDim().z, parent.getGridNodeDim().y, + MC::marching_cube(const_cast(values.begin()), isosurfaceLevel.get(), parent.getGridNodeDim().z, parent.getGridNodeDim().y, parent.getGridNodeDim().x, isosurfaceMesh); // Transform the result to be aligned with our volume's spatial layout @@ -215,7 +215,8 @@ SurfaceMesh* VolumeGridNodeScalarQuantity::registerIsosurfaceAsMesh(std::string // extract the mesh MC::mcMesh isosurfaceMesh; - MC::marching_cube(&values.data.front(), isosurfaceLevel.get(), parent.getGridNodeDim().z, parent.getGridNodeDim().y, + values.ensureHostBufferPopulated(); + MC::marching_cube(const_cast(values.begin()), isosurfaceLevel.get(), parent.getGridNodeDim().z, parent.getGridNodeDim().y, parent.getGridNodeDim().x, isosurfaceMesh); glm::vec3 scale = parent.gridSpacing(); for (auto& p : isosurfaceMesh.vertices) { diff --git a/src/volume_mesh.cpp b/src/volume_mesh.cpp index 3b8907fa..4fa610e6 100644 --- a/src/volume_mesh.cpp +++ b/src/volume_mesh.cpp @@ -533,11 +533,12 @@ void VolumeMesh::fillSliceGeometryBuffers(render::ShaderProgram& program) { point2.resize(tetCount); point3.resize(tetCount); point4.resize(tetCount); + vertexPositions.ensureHostBufferPopulated(); for (size_t tetIdx = 0; tetIdx < tets.size(); tetIdx++) { - point1[tetIdx] = vertexPositions.data[tets[tetIdx][0]]; - point2[tetIdx] = vertexPositions.data[tets[tetIdx][1]]; - point3[tetIdx] = vertexPositions.data[tets[tetIdx][2]]; - point4[tetIdx] = vertexPositions.data[tets[tetIdx][3]]; + point1[tetIdx] = vertexPositions.getHostValue(tets[tetIdx][0]); + point2[tetIdx] = vertexPositions.getHostValue(tets[tetIdx][1]); + point3[tetIdx] = vertexPositions.getHostValue(tets[tetIdx][2]); + point4[tetIdx] = vertexPositions.getHostValue(tets[tetIdx][3]); } program.setAttribute("a_point_1", point1); @@ -818,18 +819,12 @@ void VolumeMesh::computeConnectivityData() { // that exterior faces always win depth ties. This doesn't totally eliminate the problem, but greatly improves the // most egregious cases. // == Allocate buffers - triangleVertexInds.data.clear(); - triangleVertexInds.data.resize(3 * nFacesTriangulation()); - triangleFaceInds.data.clear(); - triangleFaceInds.data.resize(3 * nFacesTriangulation()); - triangleCellInds.data.clear(); - triangleCellInds.data.resize(3 * nFacesTriangulation()); - baryCoord.data.clear(); - baryCoord.data.resize(3 * nFacesTriangulation()); - edgeIsReal.data.clear(); - edgeIsReal.data.resize(3 * nFacesTriangulation()); - faceType.data.clear(); - faceType.data.resize(nFaces()); + triangleVertexInds.resize(3 * nFacesTriangulation()); + triangleFaceInds.resize(3 * nFacesTriangulation()); + triangleCellInds.resize(3 * nFacesTriangulation()); + baryCoord.resize(3 * nFacesTriangulation()); + edgeIsReal.resize(3 * nFacesTriangulation()); + faceType.resize(nFaces()); size_t iF = 0; // face counter size_t iFront = 0; @@ -862,28 +857,27 @@ void VolumeMesh::computeConnectivityData() { // Store triangle vertices for (size_t k = 0; k < 3; k++) { - triangleVertexInds.data[3 * iData + k] = cell[tri[k]]; + triangleVertexInds.setHostValue(3 * iData + k, cell[tri[k]]); } - // Face & cell indices - for (size_t k = 0; k < 3; k++) triangleFaceInds.data[3 * iData + k] = iF; - for (size_t k = 0; k < 3; k++) triangleCellInds.data[3 * iData + k] = iC; + for (size_t k = 0; k < 3; k++) triangleFaceInds.setHostValue(3 * iData + k, (uint32_t)iF); + for (size_t k = 0; k < 3; k++) triangleCellInds.setHostValue(3 * iData + k, (uint32_t)iC); // Barycentric coords - baryCoord.data[3 * iData + 0] = glm::vec3{1., 0., 0.}; - baryCoord.data[3 * iData + 1] = glm::vec3{0., 1., 0.}; - baryCoord.data[3 * iData + 2] = glm::vec3{0., 0., 1.}; + baryCoord.setHostValue(3 * iData + 0, glm::vec3{1., 0., 0.}); + baryCoord.setHostValue(3 * iData + 1, glm::vec3{0., 1., 0.}); + baryCoord.setHostValue(3 * iData + 2, glm::vec3{0., 0., 1.}); // Mark edges as real or not for (int k = 0; k < 3; k++) { for (int c = 0; c < 3; c++) { - edgeIsReal.data[3 * iData + k][c] = faceRealEdges[f][j][c] ? 1.0f : 0.0f; + edgeIsReal.setHostValue(3 * iData + k, c, faceRealEdges[f][j][c] ? 1.0f : 0.0f); } } } // Face type: 1 for interior, 0 for exterior - faceType.data[iF] = faceIsInterior[iF] ? 1.f : 0.f; + faceType.setHostValue(iF, faceIsInterior[iF] ? 1.f : 0.f); iF++; } @@ -948,7 +942,7 @@ void VolumeMesh::computeFaceNormals() { vertexPositions.ensureHostBufferPopulated(); - faceNormals.data.resize(nFaces()); + faceNormals.resize(nFaces()); size_t iF = 0; for (size_t iC = 0; iC < nCells(); iC++) { @@ -960,19 +954,18 @@ void VolumeMesh::computeFaceNormals() { // Do a first pass to compute a normal glm::vec3 normal{0., 0., 0.}; for (const std::array& tri : face) { - glm::vec3 pA = vertexPositions.data[cell[tri[0]]]; - glm::vec3 pB = vertexPositions.data[cell[tri[1]]]; - glm::vec3 pC = vertexPositions.data[cell[tri[2]]]; + glm::vec3 pA = vertexPositions.getHostValue(cell[tri[0]]); + glm::vec3 pB = vertexPositions.getHostValue(cell[tri[1]]); + glm::vec3 pC = vertexPositions.getHostValue(cell[tri[2]]); normal += glm::cross(pC - pB, pA - pB); } normal = glm::normalize(normal); - faceNormals.data[iF] = normal; + faceNormals.setHostValue(iF, normal); iF++; } } - faceNormals.markHostBufferUpdated(); } @@ -980,7 +973,7 @@ void VolumeMesh::computeCellCenters() { vertexPositions.ensureHostBufferPopulated(); - cellCenters.data.resize(nCells()); + cellCenters.resize(nCells()); for (size_t iC = 0; iC < nCells(); iC++) { @@ -990,16 +983,15 @@ void VolumeMesh::computeCellCenters() { const std::array& cell = cells[iC]; for (int j = 0; j < 8; j++) { if (cell[j] < INVALID_IND_32) { - center += vertexPositions.data[cell[j]]; + center += vertexPositions.getHostValue(cell[j]); count++; } } center /= count; - cellCenters.data[iC] = center; + cellCenters.setHostValue(iC, center); } - cellCenters.markHostBufferUpdated(); } @@ -1218,7 +1210,7 @@ void VolumeMesh::updateObjectSpaceBounds() { // bounding box glm::vec3 min = glm::vec3{1, 1, 1} * std::numeric_limits::infinity(); glm::vec3 max = -glm::vec3{1, 1, 1} * std::numeric_limits::infinity(); - for (const glm::vec3& p : vertexPositions.data) { + for (const glm::vec3& p : vertexPositions) { min = componentwiseMin(min, p); max = componentwiseMax(max, p); } @@ -1227,7 +1219,7 @@ void VolumeMesh::updateObjectSpaceBounds() { // length scale, as twice the radius from the center of the bounding box glm::vec3 center = 0.5f * (min + max); float lengthScale = 0.0; - for (const glm::vec3& p : vertexPositions.data) { + for (const glm::vec3& p : vertexPositions) { lengthScale = std::max(lengthScale, glm::length2(p - center)); } objectSpaceLengthScale = 2 * std::sqrt(lengthScale); diff --git a/src/volume_mesh_color_quantity.cpp b/src/volume_mesh_color_quantity.cpp index b4d314e1..712c7c36 100644 --- a/src/volume_mesh_color_quantity.cpp +++ b/src/volume_mesh_color_quantity.cpp @@ -92,11 +92,12 @@ void VolumeMeshVertexColorQuantity::fillSliceColorBuffers(render::ShaderProgram& colorval_3.resize(tetCount); colorval_4.resize(tetCount); + colors.ensureHostBufferPopulated(); for (size_t iT = 0; iT < parent.tets.size(); iT++) { - colorval_1[iT] = colors.data[parent.tets[iT][0]]; - colorval_2[iT] = colors.data[parent.tets[iT][1]]; - colorval_3[iT] = colors.data[parent.tets[iT][2]]; - colorval_4[iT] = colors.data[parent.tets[iT][3]]; + colorval_1[iT] = colors.getHostValue(parent.tets[iT][0]); + colorval_2[iT] = colors.getHostValue(parent.tets[iT][1]); + colorval_3[iT] = colors.getHostValue(parent.tets[iT][2]); + colorval_4[iT] = colors.getHostValue(parent.tets[iT][3]); } // Store data in buffers diff --git a/src/volume_mesh_scalar_quantity.cpp b/src/volume_mesh_scalar_quantity.cpp index 67085a30..946bfc7b 100644 --- a/src/volume_mesh_scalar_quantity.cpp +++ b/src/volume_mesh_scalar_quantity.cpp @@ -91,15 +91,17 @@ void VolumeMeshVertexScalarQuantity::fillLevelSetData(render::ShaderProgram& p) point2.resize(tetCount); point3.resize(tetCount); point4.resize(tetCount); + parent.vertexPositions.ensureHostBufferPopulated(); + values.ensureHostBufferPopulated(); for (size_t i = 0; i < parent.nTets(); i++) { - point1[i] = parent.vertexPositions.data[parent.tets[i][0]]; - point2[i] = parent.vertexPositions.data[parent.tets[i][1]]; - point3[i] = parent.vertexPositions.data[parent.tets[i][2]]; - point4[i] = parent.vertexPositions.data[parent.tets[i][3]]; - slice1[i] = glm::vec3(values.data[parent.tets[i][0]], 0, 0); - slice2[i] = glm::vec3(values.data[parent.tets[i][1]], 0, 0); - slice3[i] = glm::vec3(values.data[parent.tets[i][2]], 0, 0); - slice4[i] = glm::vec3(values.data[parent.tets[i][3]], 0, 0); + point1[i] = parent.vertexPositions.getHostValue(parent.tets[i][0]); + point2[i] = parent.vertexPositions.getHostValue(parent.tets[i][1]); + point3[i] = parent.vertexPositions.getHostValue(parent.tets[i][2]); + point4[i] = parent.vertexPositions.getHostValue(parent.tets[i][3]); + slice1[i] = glm::vec3(values.getHostValue(parent.tets[i][0]), 0, 0); + slice2[i] = glm::vec3(values.getHostValue(parent.tets[i][1]), 0, 0); + slice3[i] = glm::vec3(values.getHostValue(parent.tets[i][2]), 0, 0); + slice4[i] = glm::vec3(values.getHostValue(parent.tets[i][3]), 0, 0); } p.setAttribute("a_point_1", point1); p.setAttribute("a_point_2", point2); @@ -299,11 +301,12 @@ void VolumeMeshVertexScalarQuantity::fillSliceColorBuffers(render::ShaderProgram colorval_3.resize(tetCount); colorval_4.resize(tetCount); + values.ensureHostBufferPopulated(); for (size_t iT = 0; iT < parent.tets.size(); iT++) { - colorval_1[iT] = values.data[parent.tets[iT][0]]; - colorval_2[iT] = values.data[parent.tets[iT][1]]; - colorval_3[iT] = values.data[parent.tets[iT][2]]; - colorval_4[iT] = values.data[parent.tets[iT][3]]; + colorval_1[iT] = values.getHostValue(parent.tets[iT][0]); + colorval_2[iT] = values.getHostValue(parent.tets[iT][1]); + colorval_3[iT] = values.getHostValue(parent.tets[iT][2]); + colorval_4[iT] = values.getHostValue(parent.tets[iT][3]); } // Store data in buffers diff --git a/test/src/volume_grid_test.cpp b/test/src/volume_grid_test.cpp index ee499f12..40e5636a 100644 --- a/test/src/volume_grid_test.cpp +++ b/test/src/volume_grid_test.cpp @@ -232,7 +232,7 @@ TEST_F(PolyscopeTest, VolumeGridScalarIsosurfaceIndexing) { // get the vertices from the is surface polyscope::SurfaceMesh* isoTest = polyscope::getSurfaceMesh("iso test"); isoTest->vertexPositions.ensureHostBufferPopulated(); - std::vector isoverts = isoTest->vertexPositions.data; + std::vector isoverts(isoTest->vertexPositions.begin(), isoTest->vertexPositions.end()); // test that all vertices are within the bounds float EPS = 0.0001; From a759ef6fab9e5d8039fb5fa560c20ae5b288837a Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 15:18:57 -0700 Subject: [PATCH 4/8] pass managed buffers directly to shaders, change lazy update strategy --- include/polyscope/render/engine.h | 15 +- include/polyscope/render/managed_buffer.h | 156 ++++--- .../render/mock_opengl/mock_gl_engine.h | 9 +- include/polyscope/render/opengl/gl_engine.h | 9 +- include/polyscope/vector_quantity.ipp | 12 +- src/color_image_quantity.cpp | 4 +- src/color_render_image_quantity.cpp | 6 +- src/curve_network.cpp | 22 +- src/curve_network_color_quantity.cpp | 10 +- src/curve_network_scalar_quantity.cpp | 12 +- src/depth_render_image_quantity.cpp | 4 +- src/point_cloud.cpp | 6 +- src/point_cloud_color_quantity.cpp | 2 +- src/point_cloud_parameterization_quantity.cpp | 2 +- src/point_cloud_scalar_quantity.cpp | 2 +- src/raw_color_alpha_render_image_quantity.cpp | 4 +- src/raw_color_render_image_quantity.cpp | 4 +- src/render/managed_buffer.cpp | 416 +++++++++--------- src/render/mock_opengl/mock_gl_engine.cpp | 21 +- src/render/opengl/gl_engine.cpp | 21 +- src/render_image_quantity_base.cpp | 2 +- src/scalar_image_quantity.cpp | 4 +- src/scalar_render_image_quantity.cpp | 6 +- src/simple_triangle_mesh.cpp | 2 +- src/slice_plane.cpp | 8 +- src/sparse_volume_grid.cpp | 28 +- src/sparse_volume_grid_color_quantity.cpp | 4 +- src/sparse_volume_grid_scalar_quantity.cpp | 4 +- src/surface_color_quantity.cpp | 10 +- src/surface_mesh.cpp | 24 +- src/surface_parameterization_quantity.cpp | 6 +- src/surface_scalar_quantity.cpp | 20 +- src/surface_vector_quantity.cpp | 4 +- src/volume_grid.cpp | 16 +- src/volume_grid_scalar_quantity.cpp | 20 +- src/volume_mesh.cpp | 24 +- src/volume_mesh_color_quantity.cpp | 4 +- src/volume_mesh_scalar_quantity.cpp | 4 +- 38 files changed, 523 insertions(+), 404 deletions(-) diff --git a/include/polyscope/render/engine.h b/include/polyscope/render/engine.h index f0ba8959..ecba5d7c 100644 --- a/include/polyscope/render/engine.h +++ b/include/polyscope/render/engine.h @@ -17,6 +17,12 @@ namespace polyscope { +// Forward declarations for ManagedBuffer integration +namespace render { + class ManagedBufferBase; + template class ManagedBuffer; +} // namespace render + // == A few enums that control behavior // public enums are in the outer namespace to keep the typing burden down @@ -395,7 +401,7 @@ class ShaderProgram { virtual bool hasAttribute(std::string name) = 0; virtual bool attributeIsSet(std::string name) = 0; virtual std::shared_ptr getAttributeBuffer(std::string name) = 0; - virtual void setAttribute(std::string name, std::shared_ptr externalBuffer) = 0; + virtual void setAttribute(std::string name, std::shared_ptr externalBuffer, ManagedBufferBase* source = nullptr) = 0; virtual void setAttribute(std::string name, const std::vector& data) = 0; virtual void setAttribute(std::string name, const std::vector& data) = 0; virtual void setAttribute(std::string name, const std::vector& data) = 0; @@ -418,8 +424,13 @@ class ShaderProgram { bool withAlpha = true, bool useMipMap = false, bool repeat = false) = 0; virtual void setTextureFromColormap(std::string name, const std::string& colorMap, bool allowUpdate = false) = 0; // TODO make this one take a shared pointer and have the same semantics as the attribute version - virtual void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer) = 0; + virtual void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer, ManagedBufferBase* source = nullptr) = 0; + + // Convenience overloads for ManagedBuffer — set the buffer AND record the source for lazy sync. + // Defined in managed_buffer.h after ManagedBuffer is fully declared. + template void setAttribute(std::string name, ManagedBuffer& buf); + template void setTextureFromBuffer(std::string name, ManagedBuffer& buf); // Indices virtual void setIndex(std::shared_ptr externalBuffer) = 0; diff --git a/include/polyscope/render/managed_buffer.h b/include/polyscope/render/managed_buffer.h index 7049890a..9247a092 100644 --- a/include/polyscope/render/managed_buffer.h +++ b/include/polyscope/render/managed_buffer.h @@ -17,6 +17,69 @@ namespace render { // forward declaration class ManagedBufferRegistry; +/* + * Non-templated base class for ManagedBuffer. + * Contains all type-independent members and provides the syncToDeviceIfNeeded() virtual interface + * used by ShaderProgram::draw() to lazily push host data to the GPU before issuing a draw call. + */ +class ManagedBufferBase { +public: + virtual ~ManagedBufferBase() = default; + + // Called by ShaderProgram::draw() before issuing the GPU draw call. + // Pushes host data to device if deviceBufferValid is false and hostBufferValid is true. + virtual void syncToDeviceIfNeeded() = 0; + + const std::string name; + const uint64_t uniqueID; + +protected: + // protected constructors — only ManagedBuffer should construct this + ManagedBufferBase(ManagedBufferRegistry* registry_, const std::string& name_, bool dataGetsComputed_, + bool hostBufferValid_); + + // Internal helpers that don't depend on T + void invalidateHostBuffer(); + bool deviceBufferTypeIsTexture() const; + void checkDeviceBufferTypeIs(DeviceBufferType targetType) const; + void checkDeviceBufferTypeIsTexture() const; + + // True when the buffer has no valid data on host or device and is waiting to be computed. + bool isInNeedsComputeState() const { return !hostBufferValid && !deviceBufferValid && dataGetsComputed; } + + ManagedBufferRegistry* registry; + + bool dataGetsComputed; // if true, the value gets computed on-demand by calling computeFunc() + std::function computeFunc; // (optional) callback which populates the buffer + + bool hostBufferValid; // true if host-side data is current + bool deviceBufferValid; // true if device-side buffer matches current host data + + // True if all registered indexed views are consistent with the current source data. + // Set to false by markRenderAttributeBufferUpdated() when the GPU source is written externally + // and host data is no longer available to re-gather from. Will be set back to true once + // updateIndexedViews() runs successfully (which requires hostBufferValid). + // Future work: an on-device gather pass will be able to update indexed views even when + // !hostBufferValid, at which point this flag drives that path. + bool indexedViewsValid = true; + + // Explicit size and capacity. + // Invariant: when hostBufferValid, data.size() == managedCapacity (the vector is always kept at full + // capacity — data.capacity() (std::vector internal) is never relied on for anything). + // currentSize <= managedCapacity is the number of logically valid elements. + size_t managedCapacity = 0; + size_t currentSize = 0; + + std::shared_ptr renderAttributeBuffer; + std::shared_ptr renderTextureBuffer; + + DeviceBufferType deviceBufferType = DeviceBufferType::Attribute; + uint32_t sizeX = 0; + uint32_t sizeY = 0; + uint32_t sizeZ = 0; +}; + + /* * This class owns and manages a typed data buffer in Polyscope, handling common data-management concerns of: * @@ -32,15 +95,16 @@ class ManagedBufferRegistry; * Use the public accessor API (getHostValue, setHostValue, setDataHost, begin/end, etc.) to interact with the buffer. */ template -class ManagedBuffer : public virtual WeakReferrable { +class ManagedBuffer : public ManagedBufferBase, public virtual WeakReferrable { public: // === Constructors - // (second variants are advanced versions which allow creation of multi-dimensional texture values) + + // Create an empty buffer with no data. Use resize()+setHostValue() or setDataHost() to populate. + ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name); // Manage a buffer of data which is explicitly set externally. ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, std::vector data); - // Manage a buffer of data which gets computed lazily ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, std::function computeFunc); @@ -49,16 +113,7 @@ class ManagedBuffer : public virtual WeakReferrable { ~ManagedBuffer(); - // === Core members - - // A meaningful name for the buffer - std::string name; - - // A globally-unique ID - const uint64_t uniqueID; - - // The registry in which it is tracked (can be null) - ManagedBufferRegistry* registry; + // === Core members (note: name, uniqueID, registry, dataGetsComputed, computeFunc live in ManagedBufferBase) // sanity check helper @@ -81,7 +136,7 @@ class ManagedBuffer : public virtual WeakReferrable { // But in settings where we e.g. incrementally add elements or change the number of data elements on each frame, we // may want to allocate a larger capacity to avoid expensive re-allocation each time. - // Resize the buffer to newSize elements. Sets hostBufferIsPopulated = true. + // Resize the buffer to newSize elements. Sets hostBufferValid = true. // // If newSize <= capacity(), this is a cheap constant-time operation which just updates metadata. // @@ -131,7 +186,10 @@ class ManagedBuffer : public virtual WeakReferrable { // device buffers and triggers a redraw. Prefer this over setHostValue() for bulk writes. void setDataHost(const std::vector& newData); - // Iterator support. Caller must call ensureHostBufferPopulated() before using these. + // Returns a full copy of the host data, populating it from device if needed. + std::vector getDataCopy(); + + // Raw pointer iteration support. Caller must call ensureHostBufferPopulated() before using these. // A debug-mode check will fire if this precondition is violated. const T* begin() const; const T* end() const; @@ -182,8 +240,10 @@ class ManagedBuffer : public virtual WeakReferrable { // NOTE: this is only for attribute-accessed buffers (DeviceBufferType::Attribute). See the variants below for // textures. - // NOTE: This class follows the policy that once the render buffer is allocated, it is always immediately kept - // updated to reflect any external changes. + // NOTE: This class follows a lazy-sync policy: once the render buffer is allocated, it is kept up to date + // with host-side changes lazily — the actual GPU upload happens in syncToDeviceIfNeeded(), which is called + // by ShaderProgram::draw() just before the draw call. External writes to the GPU buffer (via + // markRenderAttributeBufferUpdated()) invalidate the host copy until ensureHostBufferPopulated() is called. // Get a reference to the underlying GPU-side attribute buffer // 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 { void markRenderTextureBufferUpdated(); + // Sync host data to the device buffer if needed. Called by ShaderProgram::draw() before drawing. + void syncToDeviceIfNeeded() override; + protected: // == Internal members + // Note: dataGetsComputed, computeFunc, hostBufferValid, deviceBufferValid, managedCapacity, + // renderAttributeBuffer, renderTextureBuffer, deviceBufferType, sizeX/Y/Z all live in ManagedBufferBase. - // This class tracks data from two separate sources: - // A) Values directly stored in the buffer by an external source (dataGetsComputed == false) - // B) Values that get computed lazily by some function when needed (dataGetsComputed == true) - // - // When dataGetsComputed is true, computeFunc() must be set to a callback that populates the buffer. - // The callback should call resize() then setHostValue() to populate the buffer. - // The callback does NOT need to call markHostBufferUpdated() — that is handled by the ManagedBuffer - // infrastructure after the callback returns. - bool dataGetsComputed; // if true, the value gets computed on-demand by calling computeFunc() - std::function computeFunc; // (optional) callback which populates the buffer - - bool hostBufferIsPopulated; // true if the host buffer contains currently-valid data - - // The buffer has capacity for at least this many elements. It is distinct from .size(), which is the actual number of - // elements currently stored in the buffer and may be smaller than the capacity. - // Any resize() operations that stay within the capacity are cheap, and do not trigger a full reallocation and copy. - size_t managedCapacity = 0; - - std::shared_ptr renderAttributeBuffer; - std::shared_ptr renderTextureBuffer; - - // For storing as textures - - // For data that can be interpreted as a 1/2/3 dimensional texture - DeviceBufferType deviceBufferType = DeviceBufferType::Attribute; // this gets set when you call setTextureSize - uint32_t sizeX = 0; - uint32_t sizeY = 0; // holds 0 if texture dim < 2 - uint32_t sizeZ = 0; // holds 0 if texture dim < 3 - + // Override invalidateHostBuffer to also clear the typed data vector. + void invalidateHostBuffer(); // == Internal representation of indexed views // 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 { existingIndexedViews; void updateIndexedViews(); void removeDeletedIndexedViews(); - - // == Internal helper functions - - void invalidateHostBuffer(); - bool deviceBufferTypeIsTexture() const; - void checkDeviceBufferTypeIs(DeviceBufferType targetType) const; - void checkDeviceBufferTypeIsTexture() const; + void invalidateIndexedViews(); enum class CanonicalDataSource { HostData = 0, NeedsCompute, RenderBuffer }; CanonicalDataSource currentCanonicalDataSource(); @@ -373,3 +405,21 @@ std::string typeName(ManagedBufferType type); } // namespace polyscope #include "polyscope/render/managed_buffer.ipp" + +// Implementations of ShaderProgram's ManagedBuffer convenience overloads +// (defined here because they need the full ManagedBuffer definition) +namespace polyscope { +namespace render { + +template +void ShaderProgram::setAttribute(std::string name, ManagedBuffer& buf) { + setAttribute(name, buf.getRenderAttributeBuffer(), &buf); +} + +template +void ShaderProgram::setTextureFromBuffer(std::string name, ManagedBuffer& buf) { + setTextureFromBuffer(name, buf.getRenderTextureBuffer().get(), &buf); +} + +} // namespace render +} // namespace polyscope diff --git a/include/polyscope/render/mock_opengl/mock_gl_engine.h b/include/polyscope/render/mock_opengl/mock_gl_engine.h index b135564a..ef0e1ad5 100644 --- a/include/polyscope/render/mock_opengl/mock_gl_engine.h +++ b/include/polyscope/render/mock_opengl/mock_gl_engine.h @@ -212,6 +212,7 @@ struct GLShaderAttribute { RenderDataType type; int arrayCount; std::shared_ptr buff; // the buffer that we will actually use + ManagedBufferBase* sourceManagedBuffer; // might be empty if not from a managed buffer. for indexed views, it's the original data source }; struct GLShaderTexture { @@ -221,6 +222,7 @@ struct GLShaderTexture { bool isSet; GLTextureBuffer* textureBuffer; std::shared_ptr textureBufferOwned; // might be empty, if texture isn't owned + ManagedBufferBase* sourceManagedBuffer; // might be empty if not from a managed buffer. for indexed views, it's the original data source }; // A thin wrapper around a program handle. @@ -282,7 +284,7 @@ class GLShaderProgram : public ShaderProgram { bool hasAttribute(std::string name) override; bool attributeIsSet(std::string name) override; std::shared_ptr getAttributeBuffer(std::string name) override; - void setAttribute(std::string name, std::shared_ptr externalBuffer) override; + void setAttribute(std::string name, std::shared_ptr externalBuffer, ManagedBufferBase* source = nullptr) override; void setAttribute(std::string name, const std::vector& data) override; void setAttribute(std::string name, const std::vector& data) override; void setAttribute(std::string name, const std::vector& data) override; @@ -310,11 +312,14 @@ class GLShaderProgram : public ShaderProgram { void setTexture2D(std::string name, unsigned char* texData, unsigned int width, unsigned int height, bool withAlpha = true, bool useMipMap = false, bool repeat = false) override; void setTextureFromColormap(std::string name, const std::string& colorMap, bool allowUpdate = false) override; - void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer) override; + void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer, ManagedBufferBase* source = nullptr) override; + + // ManagedBuffer source registration // Draw! void draw() override; void validateData() override; + void syncBuffersToDeviceIfNeeded(); protected: // Lists of attributes and uniforms that need to be set diff --git a/include/polyscope/render/opengl/gl_engine.h b/include/polyscope/render/opengl/gl_engine.h index ef5ce764..9f6c4d3a 100644 --- a/include/polyscope/render/opengl/gl_engine.h +++ b/include/polyscope/render/opengl/gl_engine.h @@ -252,6 +252,7 @@ struct GLShaderAttribute { int arrayCount; AttributeLocation location; // -1 means "no location", usually because it was optimized out std::shared_ptr buff; // the buffer that we will actually use + ManagedBufferBase* sourceManagedBuffer; // might be empty if not from a managed buffer. for indexed views, it's the original data source }; struct GLShaderTexture { @@ -262,6 +263,7 @@ struct GLShaderTexture { GLTextureBuffer* textureBuffer; std::shared_ptr textureBufferOwned; // might be empty, if texture isn't owned TextureLocation location; // -1 means "no location", usually because it was optimized out + ManagedBufferBase* sourceManagedBuffer; // might be empty if not from a managed buffer. for indexed views, it's the original data source }; // A thin wrapper around a program handle. @@ -325,7 +327,7 @@ class GLShaderProgram : public ShaderProgram { bool hasAttribute(std::string name) override; bool attributeIsSet(std::string name) override; std::shared_ptr getAttributeBuffer(std::string name) override; - void setAttribute(std::string name, std::shared_ptr externalBuffer) override; + void setAttribute(std::string name, std::shared_ptr externalBuffer, ManagedBufferBase* source = nullptr) override; void setAttribute(std::string name, const std::vector& data) override; void setAttribute(std::string name, const std::vector& data) override; void setAttribute(std::string name, const std::vector& data) override; @@ -354,11 +356,14 @@ class GLShaderProgram : public ShaderProgram { void setTexture2D(std::string name, unsigned char* texData, unsigned int width, unsigned int height, bool withAlpha = true, bool useMipMap = false, bool repeat = false) override; void setTextureFromColormap(std::string name, const std::string& colorMap, bool allowUpdate = false) override; - void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer) override; + void setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer, ManagedBufferBase* source = nullptr) override; + + // ManagedBuffer source registration // Draw! void draw() override; void validateData() override; + void syncBuffersToDeviceIfNeeded(); protected: // Lists of attributes and uniforms that need to be set diff --git a/include/polyscope/vector_quantity.ipp b/include/polyscope/vector_quantity.ipp index d3db2c3c..0f4a5bba 100644 --- a/include/polyscope/vector_quantity.ipp +++ b/include/polyscope/vector_quantity.ipp @@ -175,8 +175,8 @@ void VectorQuantity::createProgram() { ); // clang-format on - this->vectorProgram->setAttribute("a_vector", vectors.getRenderAttributeBuffer()); - this->vectorProgram->setAttribute("a_position", vectorRoots.getRenderAttributeBuffer()); + this->vectorProgram->setAttribute("a_vector", vectors); + this->vectorProgram->setAttribute("a_position", vectorRoots); render::engine->setMaterial(*(this->vectorProgram), this->material.get()); } @@ -299,10 +299,10 @@ void TangentVectorQuantity::createProgram() { ); // clang-format on - this->vectorProgram->setAttribute("a_tangentVector", tangentVectors.getRenderAttributeBuffer()); - this->vectorProgram->setAttribute("a_basisVectorX", tangentBasisX.getRenderAttributeBuffer()); - this->vectorProgram->setAttribute("a_basisVectorY", tangentBasisY.getRenderAttributeBuffer()); - this->vectorProgram->setAttribute("a_position", vectorRoots.getRenderAttributeBuffer()); + this->vectorProgram->setAttribute("a_tangentVector", tangentVectors); + this->vectorProgram->setAttribute("a_basisVectorX", tangentBasisX); + this->vectorProgram->setAttribute("a_basisVectorY", tangentBasisY); + this->vectorProgram->setAttribute("a_position", vectorRoots); render::engine->setMaterial(*(this->vectorProgram), this->material.get()); } diff --git a/src/color_image_quantity.cpp b/src/color_image_quantity.cpp index 82482a90..72971a84 100644 --- a/src/color_image_quantity.cpp +++ b/src/color_image_quantity.cpp @@ -56,7 +56,7 @@ void ColorImageQuantity::prepareFullscreen() { fullscreenProgram->setAttribute("a_position", render::engine->screenTrianglesCoords()); // TODO throughout polyscope we discard the shared pointer when adding textures/attributes to programs... should we // just track the shared pointer? - fullscreenProgram->setTextureFromBuffer("t_image", colors.getRenderTextureBuffer().get()); + fullscreenProgram->setTextureFromBuffer("t_image", colors); } void ColorImageQuantity::prepareBillboard() { @@ -75,7 +75,7 @@ void ColorImageQuantity::prepareBillboard() { render::ShaderReplacementDefaults::Process); // clang-format on billboardProgram->setAttribute("a_position", render::engine->screenTrianglesCoords()); - billboardProgram->setTextureFromBuffer("t_image", colors.getRenderTextureBuffer().get()); + billboardProgram->setTextureFromBuffer("t_image", colors); } void ColorImageQuantity::showFullscreen() { diff --git a/src/color_render_image_quantity.cpp b/src/color_render_image_quantity.cpp index 266d6254..5e0f3e52 100644 --- a/src/color_render_image_quantity.cpp +++ b/src/color_render_image_quantity.cpp @@ -72,11 +72,11 @@ void ColorRenderImageQuantity::prepare() { program = render::engine->requestShader("TEXTURE_DRAW_RENDERIMAGE_PLAIN", rules); program->setAttribute("a_position", render::engine->screenTrianglesCoords()); - program->setTextureFromBuffer("t_depth", depths.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_depth", depths); if (hasNormals) { - program->setTextureFromBuffer("t_normal", normals.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_normal", normals); } - program->setTextureFromBuffer("t_color", colors.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_color", colors); render::engine->setMaterial(*program, material.get()); } diff --git a/src/curve_network.cpp b/src/curve_network.cpp index a6a1b580..4d7b0b63 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -22,8 +22,8 @@ CurveNetwork::CurveNetwork(std::string name, std::vector nodes_, std: : // clang-format off Structure(name, typeName()), nodePositions(this, uniquePrefix() + "nodePositions", std::move(nodes_)), - edgeTailInds(this, uniquePrefix() + "edgeTailInds", std::vector{}), - edgeTipInds(this, uniquePrefix() + "edgeTipInds", std::vector{}), + edgeTailInds(this, uniquePrefix() + "edgeTailInds"), + edgeTipInds(this, uniquePrefix() + "edgeTipInds"), edgeCenters(this, uniquePrefix() + "edgeCenters", std::bind(&CurveNetwork::computeEdgeCenters, this)), color(uniquePrefix() + "#color", getNextUniqueColor()), radius(uniquePrefix() + "#radius", relativeValue(0.005)), @@ -347,7 +347,7 @@ void CurveNetwork::preparePick() { } void CurveNetwork::fillNodeGeometryBuffers(render::ShaderProgram& program) { - program.setAttribute("a_position", nodePositions.getRenderAttributeBuffer()); + program.setAttribute("a_position", nodePositions); bool haveNodeRadiusQuantity = (nodeRadiusQuantityName != ""); bool haveEdgeRadiusQuantity = (edgeRadiusQuantityName != ""); @@ -355,18 +355,18 @@ void CurveNetwork::fillNodeGeometryBuffers(render::ShaderProgram& program) { if (haveNodeRadiusQuantity) { // have just node, or have both CurveNetworkNodeScalarQuantity& nodeRadQ = resolveNodeRadiusQuantity(); - program.setAttribute("a_pointRadius", nodeRadQ.values.getRenderAttributeBuffer()); + program.setAttribute("a_pointRadius", nodeRadQ.values); } else if (haveEdgeRadiusQuantity) { // have just edge CurveNetworkEdgeScalarQuantity& edgeRadQ = resolveEdgeRadiusQuantity(); edgeRadQ.updateNodeAverageValues(); - program.setAttribute("a_pointRadius", edgeRadQ.nodeAverageValues.getRenderAttributeBuffer()); + program.setAttribute("a_pointRadius", edgeRadQ.nodeAverageValues); } } void CurveNetwork::fillEdgeGeometryBuffers(render::ShaderProgram& program) { - program.setAttribute("a_position_tail", nodePositions.getIndexedRenderAttributeBuffer(edgeTailInds)); - program.setAttribute("a_position_tip", nodePositions.getIndexedRenderAttributeBuffer(edgeTipInds)); + program.setAttribute("a_position_tail", nodePositions.getIndexedRenderAttributeBuffer(edgeTailInds), &nodePositions); + program.setAttribute("a_position_tip", nodePositions.getIndexedRenderAttributeBuffer(edgeTipInds), &nodePositions); bool haveNodeRadiusQuantity = (nodeRadiusQuantityName != ""); bool haveEdgeRadiusQuantity = (edgeRadiusQuantityName != ""); @@ -374,13 +374,13 @@ void CurveNetwork::fillEdgeGeometryBuffers(render::ShaderProgram& program) { if (haveEdgeRadiusQuantity) { // have just edge or have both CurveNetworkEdgeScalarQuantity& edgeRadQ = resolveEdgeRadiusQuantity(); - program.setAttribute("a_tailRadius", edgeRadQ.values.getRenderAttributeBuffer()); - program.setAttribute("a_tipRadius", edgeRadQ.values.getRenderAttributeBuffer()); + program.setAttribute("a_tailRadius", edgeRadQ.values); + program.setAttribute("a_tipRadius", edgeRadQ.values); } else if (haveNodeRadiusQuantity) { // have just node CurveNetworkNodeScalarQuantity& nodeRadQ = resolveNodeRadiusQuantity(); - program.setAttribute("a_tailRadius", nodeRadQ.values.getIndexedRenderAttributeBuffer(edgeTailInds)); - program.setAttribute("a_tipRadius", nodeRadQ.values.getIndexedRenderAttributeBuffer(edgeTipInds)); + program.setAttribute("a_tailRadius", nodeRadQ.values.getIndexedRenderAttributeBuffer(edgeTailInds), &nodeRadQ.values); + program.setAttribute("a_tipRadius", nodeRadQ.values.getIndexedRenderAttributeBuffer(edgeTipInds), &nodeRadQ.values); } } diff --git a/src/curve_network_color_quantity.cpp b/src/curve_network_color_quantity.cpp index 3ec89cb2..4e29c342 100644 --- a/src/curve_network_color_quantity.cpp +++ b/src/curve_network_color_quantity.cpp @@ -70,12 +70,12 @@ void CurveNetworkNodeColorQuantity::createProgram() { parent.fillNodeGeometryBuffers(*nodeProgram); { // Fill node color buffers - nodeProgram->setAttribute("a_color", colors.getRenderAttributeBuffer()); + nodeProgram->setAttribute("a_color", colors); } { // Fill edge color buffers - edgeProgram->setAttribute("a_color_tail", colors.getIndexedRenderAttributeBuffer(parent.edgeTailInds)); - edgeProgram->setAttribute("a_color_tip", colors.getIndexedRenderAttributeBuffer(parent.edgeTipInds)); + edgeProgram->setAttribute("a_color_tail", colors.getIndexedRenderAttributeBuffer(parent.edgeTailInds), &colors); + edgeProgram->setAttribute("a_color_tip", colors.getIndexedRenderAttributeBuffer(parent.edgeTipInds), &colors); } render::engine->setMaterial(*nodeProgram, parent.getMaterial()); @@ -142,11 +142,11 @@ void CurveNetworkEdgeColorQuantity::createProgram() { { // Fill node color buffers // Compute an average color at each node updateNodeAverageColors(); - nodeProgram->setAttribute("a_color", nodeAverageColors.getRenderAttributeBuffer()); + nodeProgram->setAttribute("a_color", nodeAverageColors); } { // Fill edge color buffers - edgeProgram->setAttribute("a_color", colors.getRenderAttributeBuffer()); + edgeProgram->setAttribute("a_color", colors); } render::engine->setMaterial(*nodeProgram, parent.getMaterial()); diff --git a/src/curve_network_scalar_quantity.cpp b/src/curve_network_scalar_quantity.cpp index 7d8bc141..04857a84 100644 --- a/src/curve_network_scalar_quantity.cpp +++ b/src/curve_network_scalar_quantity.cpp @@ -100,12 +100,12 @@ void CurveNetworkNodeScalarQuantity::createProgram() { parent.fillEdgeGeometryBuffers(*edgeProgram); { // Fill node color buffers - nodeProgram->setAttribute("a_value", values.getRenderAttributeBuffer()); + nodeProgram->setAttribute("a_value", values); } { // Fill edge color buffers - edgeProgram->setAttribute("a_value_tail", values.getIndexedRenderAttributeBuffer(parent.edgeTailInds)); - edgeProgram->setAttribute("a_value_tip", values.getIndexedRenderAttributeBuffer(parent.edgeTipInds)); + edgeProgram->setAttribute("a_value_tail", values.getIndexedRenderAttributeBuffer(parent.edgeTailInds), &values); + edgeProgram->setAttribute("a_value_tip", values.getIndexedRenderAttributeBuffer(parent.edgeTipInds), &values); } edgeProgram->setTextureFromColormap("t_colormap", cMap.get()); @@ -130,7 +130,7 @@ void CurveNetworkNodeScalarQuantity::buildNodeInfoGUI(size_t nInd) { CurveNetworkEdgeScalarQuantity::CurveNetworkEdgeScalarQuantity(std::string name, const std::vector& values_, CurveNetwork& network_, DataType dataType_) : CurveNetworkScalarQuantity(name, network_, "edge", values_, dataType_), - nodeAverageValues(this, uniquePrefix() + "#nodeAverageValues", std::vector{}) {} + nodeAverageValues(this, uniquePrefix() + "#nodeAverageValues") {} void CurveNetworkEdgeScalarQuantity::createProgram() { // Create the program to draw this quantity @@ -162,11 +162,11 @@ void CurveNetworkEdgeScalarQuantity::createProgram() { { // Fill node color buffers updateNodeAverageValues(); - nodeProgram->setAttribute("a_value", nodeAverageValues.getRenderAttributeBuffer()); + nodeProgram->setAttribute("a_value", nodeAverageValues); } { // Fill edge color buffers - edgeProgram->setAttribute("a_value", values.getRenderAttributeBuffer()); + edgeProgram->setAttribute("a_value", values); } edgeProgram->setTextureFromColormap("t_colormap", cMap.get()); diff --git a/src/depth_render_image_quantity.cpp b/src/depth_render_image_quantity.cpp index b6d0d947..a5890b20 100644 --- a/src/depth_render_image_quantity.cpp +++ b/src/depth_render_image_quantity.cpp @@ -79,9 +79,9 @@ void DepthRenderImageQuantity::prepare() { // clang-format on program->setAttribute("a_position", render::engine->screenTrianglesCoords()); - program->setTextureFromBuffer("t_depth", depths.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_depth", depths); if (hasNormals) { - program->setTextureFromBuffer("t_normal", normals.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_normal", normals); } render::engine->setMaterial(*program, material.get()); } diff --git a/src/point_cloud.cpp b/src/point_cloud.cpp index 85251730..9837770b 100644 --- a/src/point_cloud.cpp +++ b/src/point_cloud.cpp @@ -199,14 +199,14 @@ void PointCloud::ensurePickProgramPrepared() { } void PointCloud::setPointProgramGeometryAttributes(render::ShaderProgram& p) { - p.setAttribute("a_position", points.getRenderAttributeBuffer()); + p.setAttribute("a_position", points); if (pointRadiusQuantityName != "") { PointCloudScalarQuantity& radQ = resolvePointRadiusQuantity(); - p.setAttribute("a_pointRadius", radQ.values.getRenderAttributeBuffer()); + p.setAttribute("a_pointRadius", radQ.values); } if (transparencyQuantityName != "") { PointCloudScalarQuantity& transparencyQ = resolveTransparencyQuantity(); - p.setAttribute("a_valueAlpha", transparencyQ.values.getRenderAttributeBuffer()); + p.setAttribute("a_valueAlpha", transparencyQ.values); } } diff --git a/src/point_cloud_color_quantity.cpp b/src/point_cloud_color_quantity.cpp index 687487e0..d6a28066 100644 --- a/src/point_cloud_color_quantity.cpp +++ b/src/point_cloud_color_quantity.cpp @@ -47,7 +47,7 @@ void PointCloudColorQuantity::createPointProgram() { // clang-format on parent.setPointProgramGeometryAttributes(*pointProgram); - pointProgram->setAttribute("a_color", colors.getRenderAttributeBuffer()); + pointProgram->setAttribute("a_color", colors); // Fill buffers render::engine->setMaterial(*pointProgram, parent.getMaterial()); diff --git a/src/point_cloud_parameterization_quantity.cpp b/src/point_cloud_parameterization_quantity.cpp index d0dfc08b..3c7784e2 100644 --- a/src/point_cloud_parameterization_quantity.cpp +++ b/src/point_cloud_parameterization_quantity.cpp @@ -56,7 +56,7 @@ void PointCloudParameterizationQuantity::createProgram() { } void PointCloudParameterizationQuantity::fillCoordBuffers(render::ShaderProgram& p) { - p.setAttribute("a_value2", coords.getRenderAttributeBuffer()); + p.setAttribute("a_value2", coords); } void PointCloudParameterizationQuantity::buildCustomUI() { diff --git a/src/point_cloud_scalar_quantity.cpp b/src/point_cloud_scalar_quantity.cpp index 045ff44e..4cb11ff2 100644 --- a/src/point_cloud_scalar_quantity.cpp +++ b/src/point_cloud_scalar_quantity.cpp @@ -66,7 +66,7 @@ void PointCloudScalarQuantity::createProgram() { // clang-format on parent.setPointProgramGeometryAttributes(*pointProgram); - pointProgram->setAttribute("a_value", values.getRenderAttributeBuffer()); + pointProgram->setAttribute("a_value", values); // Fill buffers pointProgram->setTextureFromColormap("t_colormap", cMap.get()); diff --git a/src/raw_color_alpha_render_image_quantity.cpp b/src/raw_color_alpha_render_image_quantity.cpp index b4ab180e..fba8750a 100644 --- a/src/raw_color_alpha_render_image_quantity.cpp +++ b/src/raw_color_alpha_render_image_quantity.cpp @@ -75,8 +75,8 @@ void RawColorAlphaRenderImageQuantity::prepare() { // clang-format on program->setAttribute("a_position", render::engine->screenTrianglesCoords()); - program->setTextureFromBuffer("t_depth", depths.getRenderTextureBuffer().get()); - program->setTextureFromBuffer("t_color", colors.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_depth", depths); + program->setTextureFromBuffer("t_color", colors); } diff --git a/src/raw_color_render_image_quantity.cpp b/src/raw_color_render_image_quantity.cpp index 5ba8cd52..4d18ce92 100644 --- a/src/raw_color_render_image_quantity.cpp +++ b/src/raw_color_render_image_quantity.cpp @@ -69,8 +69,8 @@ void RawColorRenderImageQuantity::prepare() { // clang-format on program->setAttribute("a_position", render::engine->screenTrianglesCoords()); - program->setTextureFromBuffer("t_depth", depths.getRenderTextureBuffer().get()); - program->setTextureFromBuffer("t_color", colors.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_depth", depths); + program->setTextureFromBuffer("t_color", colors); } diff --git a/src/render/managed_buffer.cpp b/src/render/managed_buffer.cpp index 793354bf..2a60a2ca 100644 --- a/src/render/managed_buffer.cpp +++ b/src/render/managed_buffer.cpp @@ -15,12 +15,60 @@ namespace polyscope { namespace render { +// === ManagedBufferBase implementations === + +ManagedBufferBase::ManagedBufferBase(ManagedBufferRegistry* registry_, const std::string& name_, bool dataGetsComputed_, + bool hostBufferValid_) + : name(name_), uniqueID(internal::getNextUniqueID()), registry(registry_), dataGetsComputed(dataGetsComputed_), + hostBufferValid(hostBufferValid_), deviceBufferValid(false) {} + +void ManagedBufferBase::invalidateHostBuffer() { + hostBufferValid = false; + // Note: data.clear() is handled by ManagedBuffer which overrides this conceptually by + // calling the base version and then clearing its own data vector. +} + +bool ManagedBufferBase::deviceBufferTypeIsTexture() const { + return ((deviceBufferType == DeviceBufferType::Texture1d) || (deviceBufferType == DeviceBufferType::Texture2d) || + (deviceBufferType == DeviceBufferType::Texture3d)); +} + +void ManagedBufferBase::checkDeviceBufferTypeIs(DeviceBufferType targetType) const { + if (targetType != deviceBufferType) { + exception("ManagedBuffer " + name + " has wrong type for this operation. Expected " + + deviceBufferTypeName(targetType) + " but is " + deviceBufferTypeName(deviceBufferType)); + } +} + +void ManagedBufferBase::checkDeviceBufferTypeIsTexture() const { + if (!deviceBufferTypeIsTexture()) { + exception("ManagedBuffer " + name + " has wrong type for this operation. Expected a Texture1d/2d/3d but is " + + deviceBufferTypeName(deviceBufferType)); + } +} + + +// === ManagedBuffer implementations === + +template +ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::string& name_) + : ManagedBufferBase(registry_, name_, /*dataGetsComputed=*/false, /*hostBufferValid=*/true) { + + managedCapacity = 0; + currentSize = 0; + + if (registry) { + registry->addManagedBuffer(this); + } +} + template ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::string& name_, std::vector data_) - : name(name_), uniqueID(internal::getNextUniqueID()), registry(registry_), data(std::move(data_)), - dataGetsComputed(false), hostBufferIsPopulated(true) { + : ManagedBufferBase(registry_, name_, /*dataGetsComputed=*/false, /*hostBufferValid=*/true), + data(std::move(data_)) { managedCapacity = data.size(); + currentSize = data.size(); if (registry) { registry->addManagedBuffer(this); @@ -31,8 +79,11 @@ ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::str template ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::string& name_, std::function computeFunc_) - : name(name_), uniqueID(internal::getNextUniqueID()), registry(registry_), dataGetsComputed(true), - computeFunc(computeFunc_), hostBufferIsPopulated(false), managedCapacity(0) { + : ManagedBufferBase(registry_, name_, /*dataGetsComputed=*/true, /*hostBufferValid=*/false) { + + computeFunc = computeFunc_; + managedCapacity = 0; + currentSize = 0; if (registry) { registry->addManagedBuffer(this); @@ -48,7 +99,8 @@ ManagedBuffer::~ManagedBuffer() { template void ManagedBuffer::checkInvalidValues() { - polyscope::checkInvalidValues(name, data); + // Only check the logically-valid elements; slack in [currentSize, managedCapacity) is uninitialized. + polyscope::checkInvalidValues(name, std::vector(data.begin(), data.begin() + currentSize)); } template @@ -58,8 +110,7 @@ void ManagedBuffer::setTextureSize(uint32_t sizeX_) { deviceBufferType = DeviceBufferType::Texture1d; sizeX = sizeX_; - // Sync managedCapacity: data is expected to be populated before setTextureSize() is called. - if (data.size() > managedCapacity) managedCapacity = data.size(); + // managedCapacity and currentSize are set correctly from construction; no fixup needed. } template @@ -70,7 +121,6 @@ void ManagedBuffer::setTextureSize(uint32_t sizeX_, uint32_t sizeY_) { deviceBufferType = DeviceBufferType::Texture2d; sizeX = sizeX_; sizeY = sizeY_; - if (data.size() > managedCapacity) managedCapacity = data.size(); } template @@ -82,7 +132,6 @@ void ManagedBuffer::setTextureSize(uint32_t sizeX_, uint32_t sizeY_, uint32_t sizeX = sizeX_; sizeY = sizeY_; sizeZ = sizeZ_; - if (data.size() > managedCapacity) managedCapacity = data.size(); } template @@ -106,27 +155,23 @@ bool ManagedBuffer::resize(size_t newSize) { // ensureHostBufferPopulated() reads from renderAttributeBuffer into data; if we resize data // first it would overwrite the resize with the old GPU contents. // Only copy existing data to host if there is data to preserve. - // If state is NeedsCompute, there is no existing data — calling ensureHostBufferPopulated() - // would recursively invoke the compute function that is currently running. - if (currentCanonicalDataSource() != CanonicalDataSource::NeedsCompute) { + if (isInNeedsComputeState()) { + // If state is NeedsCompute, there is no existing data — calling ensureHostBufferPopulated() + // would recursively invoke the compute function that is currently running. + } else { + // Common case ensureHostBufferPopulated(); } - // Reallocation needed: use amortized doubling + // Reallocation needed: use amortized doubling. + // data is always kept at data.size() == managedCapacity (the invariant), so we resize (not reserve). size_t newCapacity = std::max(newSize, 2 * managedCapacity); - data.reserve(newCapacity); - data.resize(newSize); + data.resize(newCapacity); managedCapacity = newCapacity; + currentSize = newSize; - // In-place reallocation: keep the same GPU buffer objects alive so ShaderPrograms - // holding shared_ptr references to them remain valid without needing to be reset. - if (renderAttributeBuffer) { - renderAttributeBuffer->reserveCapacity(managedCapacity); - } - if (renderTextureBuffer && deviceBufferType == DeviceBufferType::Texture1d) { - renderTextureBuffer->resize(static_cast(managedCapacity)); - } - hostBufferIsPopulated = true; + hostBufferValid = true; + deviceBufferValid = false; if (deviceBufferType == DeviceBufferType::Texture1d) { sizeX = static_cast(newSize); @@ -134,9 +179,12 @@ bool ManagedBuffer::resize(size_t newSize) { return true; } else { - // No reallocation: just update size metadata - data.resize(newSize); - hostBufferIsPopulated = true; + // No reallocation: just update currentSize. + // data is already at managedCapacity; restore it if it was cleared by invalidateHostBuffer(). + if (data.size() != managedCapacity) data.resize(managedCapacity); + currentSize = newSize; + hostBufferValid = true; + deviceBufferValid = false; if (deviceBufferType == DeviceBufferType::Texture1d) { sizeX = static_cast(newSize); @@ -151,28 +199,23 @@ void ManagedBuffer::setCapacity(size_t newCapacity) { if (deviceBufferType == DeviceBufferType::Texture2d || deviceBufferType == DeviceBufferType::Texture3d) exception("setCapacity() is not valid for 2D/3D texture buffers"); - if (newCapacity < data.size()) - exception("setCapacity() cannot set capacity below current size (" + std::to_string(data.size()) + ")"); + if (newCapacity < currentSize) + exception("setCapacity() cannot set capacity below current size (" + std::to_string(currentSize) + ")"); if (newCapacity == managedCapacity) return; // no-op // Before invalidating the GPU buffer, copy any device-side changes back to the host. // Skip if state is NeedsCompute — there's no existing data to preserve. - if (currentCanonicalDataSource() != CanonicalDataSource::NeedsCompute) { + if (!isInNeedsComputeState()) { ensureHostBufferPopulated(); } - data.reserve(newCapacity); + // Resize data to the new capacity (maintaining the invariant data.size() == managedCapacity). + data.resize(newCapacity); managedCapacity = newCapacity; - // In-place reallocation: keep the same GPU buffer objects alive. - if (renderAttributeBuffer) { - renderAttributeBuffer->reserveCapacity(managedCapacity); - } - if (renderTextureBuffer && deviceBufferType == DeviceBufferType::Texture1d) { - renderTextureBuffer->resize(static_cast(managedCapacity)); - } - hostBufferIsPopulated = true; + hostBufferValid = true; + deviceBufferValid = false; } template @@ -187,12 +230,14 @@ bool ManagedBuffer::resizeTexture2D(uint32_t newSizeX, uint32_t newSizeY) { sizeX = newSizeX; sizeY = newSizeY; managedCapacity = static_cast(sizeX) * sizeY; + currentSize = managedCapacity; data.resize(managedCapacity); if (renderTextureBuffer) { renderTextureBuffer->resize(sizeX, sizeY); } - hostBufferIsPopulated = true; + hostBufferValid = true; + deviceBufferValid = false; return true; } @@ -210,12 +255,14 @@ bool ManagedBuffer::resizeTexture3D(uint32_t newSizeX, uint32_t newSizeY, uin sizeY = newSizeY; sizeZ = newSizeZ; managedCapacity = static_cast(sizeX) * sizeY * sizeZ; + currentSize = managedCapacity; data.resize(managedCapacity); if (renderTextureBuffer) { renderTextureBuffer->resize(sizeX, sizeY, sizeZ); } - hostBufferIsPopulated = true; + hostBufferValid = true; + deviceBufferValid = false; return true; } @@ -223,20 +270,13 @@ bool ManagedBuffer::resizeTexture3D(uint32_t newSizeX, uint32_t newSizeY, uin template void ManagedBuffer::ensureHostBufferPopulated() { - switch (currentCanonicalDataSource()) { - case CanonicalDataSource::HostData: + if (hostBufferValid) { // good to go, nothing needs to be done - break; - - case CanonicalDataSource::NeedsCompute: - - // compute it - computeFunc(); - - break; - - case CanonicalDataSource::RenderBuffer: + return; + } + if (!hostBufferValid && deviceBufferValid) { + // RenderBuffer case: copy data back from device to host if (deviceBufferTypeIsTexture()) { if (!renderTextureBuffer) exception("render buffer should be allocated but isn't"); @@ -248,18 +288,55 @@ void ManagedBuffer::ensureHostBufferPopulated() { if (!renderAttributeBuffer) exception("render buffer should be allocated but isn't"); // copy the data back from the renderBuffer - data = getAttributeBufferDataRange(*renderAttributeBuffer, 0, renderAttributeBuffer->getDataSize()); + // The GPU buffer is always managedCapacity-sized; only currentSize elements are valid. + // currentSize is already correct (set at the last resize/setDataHost); don't overwrite it. + std::vector readback = getAttributeBufferDataRange(*renderAttributeBuffer, 0, currentSize); + data.resize(managedCapacity); // maintain invariant: data.size() == managedCapacity + std::copy(readback.begin(), readback.end(), data.begin()); + hostBufferValid = true; } + return; + } - break; - }; + if (isInNeedsComputeState()) { + // NeedsCompute case: run the compute function + computeFunc(); + // Note: computeFunc is expected to call resize()+setHostValue() or setDataHost(), which sets hostBufferValid=true + return; + } + + // error! should always be one of the above + exception("ManagedBuffer " + name + " does not have data in either host or device buffers, nor a compute function."); +} + + +template +void ManagedBuffer::setDataHost(const std::vector& newData) { + if (newData.size() > managedCapacity) + exception("ManagedBuffer " + name + " setDataHost() called with data that exceeds capacity (" + + std::to_string(newData.size()) + " > " + std::to_string(managedCapacity) + + "). Call resize() or setCapacity() first."); + // Restore the invariant (data may have been cleared by invalidateHostBuffer()). + if (data.size() != managedCapacity) data.resize(managedCapacity); + std::copy(newData.begin(), newData.end(), data.begin()); + currentSize = newData.size(); + hostBufferValid = true; + deviceBufferValid = false; + invalidateIndexedViews(); + requestRedraw(); +} + +template +std::vector ManagedBuffer::getDataCopy() { + ensureHostBufferPopulated(); + // Return only the logically-valid elements; data may have capacity slack past currentSize. + return std::vector(data.begin(), data.begin() + currentSize); } template const T* ManagedBuffer::begin() const { #ifndef NDEBUG - if (!hostBufferIsPopulated) - exception("ManagedBuffer " + name + " begin() called without ensureHostBufferPopulated()"); + if (!hostBufferValid) exception("ManagedBuffer " + name + " begin() called without ensureHostBufferPopulated()"); #endif return data.data(); } @@ -267,39 +344,15 @@ const T* ManagedBuffer::begin() const { template const T* ManagedBuffer::end() const { #ifndef NDEBUG - if (!hostBufferIsPopulated) - exception("ManagedBuffer " + name + " end() called without ensureHostBufferPopulated()"); + if (!hostBufferValid) exception("ManagedBuffer " + name + " end() called without ensureHostBufferPopulated()"); #endif - return data.data() + data.size(); -} - -template -void ManagedBuffer::setDataHost(const std::vector& newData) { - if (newData.size() > managedCapacity) - exception("ManagedBuffer " + name + " setDataHost() called with data that exceeds capacity (" + - std::to_string(newData.size()) + " > " + std::to_string(managedCapacity) + "). Call resize() or setCapacity() first."); - data.assign(newData.begin(), newData.end()); - hostBufferIsPopulated = true; - - // Sync to any already-allocated device buffers and update indexed views. - if (renderAttributeBuffer) { - renderAttributeBuffer->setData(data); - requestRedraw(); - } - if (renderTextureBuffer) { - renderTextureBuffer->setData(data); - requestRedraw(); - } - if (deviceBufferType == DeviceBufferType::Attribute) { - updateIndexedViews(); - requestRedraw(); - } + return data.data() + currentSize; } template T ManagedBuffer::getHostValue(size_t ind) const { #ifndef NDEBUG - if (!hostBufferIsPopulated) + if (!hostBufferValid) exception("ManagedBuffer " + name + " getHostValue() called without ensureHostBufferPopulated()"); #endif return data[ind]; @@ -320,25 +373,13 @@ T ManagedBuffer::getHostValue(size_t indX, size_t indY, size_t indZ) const { template void ManagedBuffer::setHostValue(size_t ind, T val) { #ifndef NDEBUG - if (!hostBufferIsPopulated) + if (!hostBufferValid) exception("ManagedBuffer " + name + " setHostValue() called without ensureHostBufferPopulated()"); #endif data[ind] = val; - - // Sync to any already-allocated device buffers. Note: this re-uploads the entire buffer; for - // bulk writes, prefer setDataHost() to avoid repeated GPU uploads. - if (renderAttributeBuffer) { - renderAttributeBuffer->setData(data); - requestRedraw(); - } - if (renderTextureBuffer) { - renderTextureBuffer->setData(data); - requestRedraw(); - } - if (deviceBufferType == DeviceBufferType::Attribute) { - updateIndexedViews(); - requestRedraw(); - } + deviceBufferValid = false; + invalidateIndexedViews(); + requestRedraw(); } template @@ -355,19 +396,47 @@ void ManagedBuffer::setHostValue(size_t indX, size_t indY, size_t indZ, T val template void ManagedBuffer::markHostBufferUpdated() { - hostBufferIsPopulated = true; + hostBufferValid = true; + deviceBufferValid = false; + invalidateIndexedViews(); + requestRedraw(); +} - if (renderAttributeBuffer) { - renderAttributeBuffer->setData(data); - requestRedraw(); +template +void ManagedBuffer::syncToDeviceIfNeeded() { + // Quick exit: device data is current and all indexed views are up to date. + if (deviceBufferValid && indexedViewsValid) return; + + if (!deviceBufferValid && !hostBufferValid) { + // Neither host nor device has valid data at draw time. This is a bug: either the buffer was + // never populated, or getRenderAttributeBuffer() was not called before draw (which would have + // triggered the compute function and set deviceBufferValid). + exception("ManagedBuffer " + name + " has no valid data on host or device at draw time"); } - if (renderTextureBuffer) { - renderTextureBuffer->setData(data); - requestRedraw(); + + // Host data is valid. Upload to device if not already current. + if (!deviceBufferValid) { + if (renderAttributeBuffer) { + renderAttributeBuffer->setData(data); + } + if (renderTextureBuffer) { + renderTextureBuffer->setData(data); + } + deviceBufferValid = true; } - if (deviceBufferType == DeviceBufferType::Attribute) { - updateIndexedViews(); - requestRedraw(); + + + // Update indexed views + if (!indexedViewsValid) { + + if(deviceBufferValid) { // always true now at this point + + // TODO do an on-device update of indexed views + + // On-device update not implemented yet, copy it to host and do the update from the host + ensureHostBufferPopulated(); + updateIndexedViews(); // sets indexedViewsValid = true + } } } @@ -379,20 +448,20 @@ T ManagedBuffer::getValue(size_t ind) { ensureHostBufferPopulated(); } - switch (currentCanonicalDataSource()) { - case CanonicalDataSource::HostData: - if (ind >= data.size()) + if (hostBufferValid) { + if (ind >= currentSize) exception("out of bounds access in ManagedBuffer " + name + " getValue(" + std::to_string(ind) + ")"); return data[ind]; + } - case CanonicalDataSource::NeedsCompute: + if (isInNeedsComputeState()) { computeFunc(); - if (ind >= data.size()) + if (ind >= currentSize) exception("out of bounds access in ManagedBuffer " + name + " getValue(" + std::to_string(ind) + ")"); return data[ind]; + } - case CanonicalDataSource::RenderBuffer: - + if (!hostBufferValid && deviceBufferValid) { // NOTE: right now this case should never happen unless deviceBufferType == DeviceBufferType::Attribute. // In the texture case, we cannot get a single pixel from the backend anyway, so we always // call ensureHostBufferPopulated() above and do the host access. @@ -401,7 +470,7 @@ T ManagedBuffer::getValue(size_t ind) { exception("out of bounds access in ManagedBuffer " + name + " getValue(" + std::to_string(ind) + ")"); return getAttributeBufferData(*renderAttributeBuffer, ind); - }; + } return T(); // dummy return } @@ -424,40 +493,14 @@ T ManagedBuffer::getValue(size_t indX, size_t indY, size_t indZ) { template size_t ManagedBuffer::size() { - - switch (currentCanonicalDataSource()) { - case CanonicalDataSource::HostData: - return data.size(); - break; - - case CanonicalDataSource::NeedsCompute: - return 0; - break; - - case CanonicalDataSource::RenderBuffer: - if (deviceBufferType == DeviceBufferType::Attribute) { - return renderAttributeBuffer->getDataSize(); - } else { - size_t s = 1; - if (sizeX > 0) s *= sizeX; - if (sizeY > 0) s *= sizeY; - if (sizeZ > 0) s *= sizeZ; - return s; - } - break; - }; - - return INVALID_IND; + return currentSize; } template bool ManagedBuffer::hasData() { - if (hostBufferIsPopulated) return true; - if (deviceBufferType == DeviceBufferType::Attribute && renderAttributeBuffer) return true; - if (deviceBufferType == DeviceBufferType::Texture1d && renderTextureBuffer) return true; - if (deviceBufferType == DeviceBufferType::Texture2d && renderTextureBuffer) return true; - if (deviceBufferType == DeviceBufferType::Texture3d && renderTextureBuffer) return true; + if (hostBufferValid) return true; + if (deviceBufferValid) return true; return false; } @@ -472,20 +515,12 @@ std::string ManagedBuffer::summaryString() { std::string str = ""; str += "[" + name + "]"; - str += " status: "; - switch (currentCanonicalDataSource()) { - case CanonicalDataSource::HostData: - str += "HostData"; - break; - case CanonicalDataSource::NeedsCompute: - str += "NeedsCompute"; - break; - case CanonicalDataSource::RenderBuffer: - str += "Renderbuffer"; - break; - }; - str += " size: " + std::to_string(size()); - str += " device type: "; + str += " hostValid: " + std::string(hostBufferValid ? "T" : "F"); + str += " deviceValid: " + std::string(deviceBufferValid ? "T" : "F"); + str += " indexedViewsValid: " + std::string(indexedViewsValid ? "T" : "F"); + str += " hasComputeFunc: " + std::string(dataGetsComputed ? "T" : "F"); + str += " size: " + std::to_string(currentSize) + " / " + std::to_string(managedCapacity); + str += " device type: "; switch (deviceBufferType) { case DeviceBufferType::Attribute: str += "Attribute"; @@ -511,8 +546,8 @@ void ManagedBuffer::recomputeIfPopulated() { exception("called recomputeIfPopulated() on buffer which does not get computed"); } - // if not populated, quick out - if (currentCanonicalDataSource() == CanonicalDataSource::NeedsCompute) { + // if not populated (NeedsCompute state), quick out + if (!hostBufferValid && !deviceBufferValid) { return; } @@ -526,13 +561,11 @@ std::shared_ptr ManagedBuffer::getRenderAttributeBuf checkDeviceBufferTypeIs(DeviceBufferType::Attribute); if (!renderAttributeBuffer) { - ensureHostBufferPopulated(); // warning: the order of these matters because of how hostBufferPopulated works - // Sync managedCapacity on first GPU allocation: handles the case where data was populated - // externally before this call (e.g. class member ordering caused empty-vector-at-construction). - if (data.size() > managedCapacity) managedCapacity = data.size(); + ensureHostBufferPopulated(); renderAttributeBuffer = generateAttributeBuffer(render::engine); renderAttributeBuffer->reserveCapacity(managedCapacity); renderAttributeBuffer->setData(data); + deviceBufferValid = true; } return renderAttributeBuffer; } @@ -542,10 +575,7 @@ std::shared_ptr ManagedBuffer::getRenderTextureBuffer( checkDeviceBufferTypeIsTexture(); if (!renderTextureBuffer) { - ensureHostBufferPopulated(); // warning: the order of these matters because of how hostBufferPopulated works - // Sync managedCapacity on first GPU allocation (same rationale as getRenderAttributeBuffer) - if (data.size() > managedCapacity) managedCapacity = data.size(); - + ensureHostBufferPopulated(); renderTextureBuffer = generateTextureBuffer(deviceBufferType, render::engine); // templatize this? @@ -567,6 +597,7 @@ std::shared_ptr ManagedBuffer::getRenderTextureBuffer( } renderTextureBuffer->setData(data); + deviceBufferValid = true; } return renderTextureBuffer; } @@ -575,8 +606,9 @@ template void ManagedBuffer::markRenderAttributeBufferUpdated() { checkDeviceBufferTypeIs(DeviceBufferType::Attribute); - invalidateHostBuffer(); - updateIndexedViews(); + invalidateHostBuffer(); // also clears data + deviceBufferValid = true; + invalidateIndexedViews(); requestRedraw(); } @@ -584,7 +616,9 @@ template void ManagedBuffer::markRenderTextureBufferUpdated() { checkDeviceBufferTypeIsTexture(); - invalidateHostBuffer(); + invalidateHostBuffer(); // also clears data + deviceBufferValid = true; + invalidateIndexedViews(); requestRedraw(); } @@ -652,10 +686,15 @@ void ManagedBuffer::updateIndexedViews() { std::vector expandData = gather(data, indices.data); viewBuffer.setData(expandData); + // NOTE: this updates ALL registered indexed views of this buffer, even if only one of them is + // actually used in the current draw call. This is overly eager but correct; a lazier design + // would require indexed views to be first-class ManagedBuffers with their own NeedsCompute state. + // TODO: add direct GPU-side indexed copy support (without round-tripping through the host) using a // transform-feedback/compute shader program, to avoid the CPU gather cost for GPU-resident buffers. } + indexedViewsValid = true; requestRedraw(); } @@ -675,42 +714,25 @@ void ManagedBuffer::removeDeletedIndexedViews() { template void ManagedBuffer::invalidateHostBuffer() { - hostBufferIsPopulated = false; + ManagedBufferBase::invalidateHostBuffer(); data.clear(); } template -bool ManagedBuffer::deviceBufferTypeIsTexture() const { - return ((deviceBufferType == DeviceBufferType::Texture1d) || (deviceBufferType == DeviceBufferType::Texture2d) || - (deviceBufferType == DeviceBufferType::Texture3d)); -} - -template -void ManagedBuffer::checkDeviceBufferTypeIs(DeviceBufferType targetType) const { - if (targetType != deviceBufferType) { - exception("ManagedBuffer " + name + " has wrong type for this operation. Expected " + - deviceBufferTypeName(targetType) + " but is " + deviceBufferTypeName(deviceBufferType)); - } -} - -template -void ManagedBuffer::checkDeviceBufferTypeIsTexture() const { - if (!deviceBufferTypeIsTexture()) { - exception("ManagedBuffer " + name + " has wrong type for this operation. Expected a Texture1d/2d/3d but is " + - deviceBufferTypeName(deviceBufferType)); - } +void ManagedBuffer::invalidateIndexedViews() { + if (existingIndexedViews.size() > 0) indexedViewsValid = false; } template typename ManagedBuffer::CanonicalDataSource ManagedBuffer::currentCanonicalDataSource() { // Always prefer the host data if it is up to date - if (hostBufferIsPopulated) { + if (hostBufferValid) { return CanonicalDataSource::HostData; } // Check if the render buffer contains the canonical data - if (renderAttributeBuffer || renderTextureBuffer) { + if (deviceBufferValid) { return CanonicalDataSource::RenderBuffer; } diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index a27f0ff8..b4ce8ce9 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -2,6 +2,7 @@ #ifdef POLYSCOPE_BACKEND_OPENGL_MOCK_ENABLED #include "polyscope/render/mock_opengl/mock_gl_engine.h" +#include "polyscope/render/managed_buffer.h" #include "polyscope/imgui_config.h" #include "polyscope/messages.h" @@ -779,7 +780,7 @@ void GLCompiledProgram::addUniqueAttribute(ShaderSpecAttribute newAttribute) { return; } } - attributes.push_back(GLShaderAttribute{newAttribute.name, newAttribute.type, newAttribute.arrayCount, nullptr}); + attributes.push_back(GLShaderAttribute{newAttribute.name, newAttribute.type, newAttribute.arrayCount, nullptr, nullptr}); } void GLCompiledProgram::addUniqueUniform(ShaderSpecUniform newUniform) { @@ -806,7 +807,7 @@ void GLCompiledProgram::addUniqueTexture(ShaderSpecTexture newTexture) { return; } } - textures.push_back(GLShaderTexture{newTexture.name, newTexture.dim, 777, false, nullptr, nullptr}); + textures.push_back(GLShaderTexture{newTexture.name, newTexture.dim, 777, false, nullptr, nullptr, nullptr}); } @@ -838,7 +839,7 @@ void GLShaderProgram::createBuffers() { } } -void GLShaderProgram::setAttribute(std::string name, std::shared_ptr externalBuffer) { +void GLShaderProgram::setAttribute(std::string name, std::shared_ptr externalBuffer, ManagedBufferBase* source) { bindVAO(); checkGLError(); @@ -860,6 +861,7 @@ void GLShaderProgram::setAttribute(std::string name, std::shared_ptrbind(); @@ -1459,7 +1461,7 @@ void GLShaderProgram::setTexture2D(std::string name, unsigned char* texData, uns throw std::invalid_argument("No texture with name " + name); } -void GLShaderProgram::setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer) { +void GLShaderProgram::setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer, ManagedBufferBase* source) { // Find the right texture for (GLShaderTexture& t : textures) { @@ -1474,6 +1476,7 @@ void GLShaderProgram::setTextureFromBuffer(std::string name, TextureBuffer* text throw std::invalid_argument("Bad texture in setTextureFromBuffer()"); } + t.sourceManagedBuffer = source; t.isSet = true; return; } @@ -1659,8 +1662,18 @@ void GLShaderProgram::activateTextures() { } } +void GLShaderProgram::syncBuffersToDeviceIfNeeded() { + for (auto& attr : attributes) { + if (attr.sourceManagedBuffer) attr.sourceManagedBuffer->syncToDeviceIfNeeded(); + } + for (auto& tex : textures) { + if (tex.sourceManagedBuffer) tex.sourceManagedBuffer->syncToDeviceIfNeeded(); + } +} + void GLShaderProgram::draw() { validateData(); + syncBuffersToDeviceIfNeeded(); if (usePrimitiveRestart) { } diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index fe4d80dd..53fb729a 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -4,6 +4,7 @@ #ifdef POLYSCOPE_BACKEND_OPENGL3_ENABLED #include "polyscope/render/opengl/gl_engine.h" +#include "polyscope/render/managed_buffer.h" #include "polyscope/messages.h" #include "polyscope/options.h" @@ -1217,7 +1218,7 @@ void GLCompiledProgram::addUniqueAttribute(ShaderSpecAttribute newAttribute) { return; } } - attributes.push_back(GLShaderAttribute{newAttribute.name, newAttribute.type, newAttribute.arrayCount, -1, nullptr}); + attributes.push_back(GLShaderAttribute{newAttribute.name, newAttribute.type, newAttribute.arrayCount, -1, nullptr, nullptr}); } void GLCompiledProgram::addUniqueUniform(ShaderSpecUniform newUniform) { @@ -1244,7 +1245,7 @@ void GLCompiledProgram::addUniqueTexture(ShaderSpecTexture newTexture) { return; } } - textures.push_back(GLShaderTexture{newTexture.name, newTexture.dim, 777, false, nullptr, nullptr, 777}); + textures.push_back(GLShaderTexture{newTexture.name, newTexture.dim, 777, false, nullptr, nullptr, 777, nullptr}); } @@ -1288,7 +1289,7 @@ void GLShaderProgram::createBuffers() { checkGLError(); } -void GLShaderProgram::setAttribute(std::string name, std::shared_ptr externalBuffer) { +void GLShaderProgram::setAttribute(std::string name, std::shared_ptr externalBuffer, ManagedBufferBase* source) { bindVAO(); checkGLError(); @@ -1312,6 +1313,7 @@ void GLShaderProgram::setAttribute(std::string name, std::shared_ptrbind(); @@ -2008,7 +2010,7 @@ void GLShaderProgram::setTexture2D(std::string name, unsigned char* texData, uns throw std::invalid_argument("No texture with name " + name); } -void GLShaderProgram::setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer) { +void GLShaderProgram::setTextureFromBuffer(std::string name, TextureBuffer* textureBuffer, ManagedBufferBase* source) { glUseProgram(compiledProgram->getHandle()); // Find the right texture @@ -2024,6 +2026,7 @@ void GLShaderProgram::setTextureFromBuffer(std::string name, TextureBuffer* text throw std::invalid_argument("Bad texture in setTextureFromBuffer()"); } + t.sourceManagedBuffer = source; t.isSet = true; return; } @@ -2211,8 +2214,18 @@ void GLShaderProgram::activateTextures() { } } +void GLShaderProgram::syncBuffersToDeviceIfNeeded() { + for (auto& attr : attributes) { + if (attr.sourceManagedBuffer) attr.sourceManagedBuffer->syncToDeviceIfNeeded(); + } + for (auto& tex : textures) { + if (tex.sourceManagedBuffer) tex.sourceManagedBuffer->syncToDeviceIfNeeded(); + } +} + void GLShaderProgram::draw() { validateData(); + syncBuffersToDeviceIfNeeded(); glUseProgram(compiledProgram->getHandle()); glBindVertexArray(vaoHandle); diff --git a/src/render_image_quantity_base.cpp b/src/render_image_quantity_base.cpp index 6793142c..0420fa3d 100644 --- a/src/render_image_quantity_base.cpp +++ b/src/render_image_quantity_base.cpp @@ -126,7 +126,7 @@ void RenderImageQuantityBase::preparePick() { // clang-format on pickProgram->setAttribute("a_position", render::engine->screenTrianglesCoords()); - pickProgram->setTextureFromBuffer("t_depth", depths.getRenderTextureBuffer().get()); + pickProgram->setTextureFromBuffer("t_depth", depths); } void RenderImageQuantityBase::refresh() { diff --git a/src/scalar_image_quantity.cpp b/src/scalar_image_quantity.cpp index e7f9abf0..4ad7d558 100644 --- a/src/scalar_image_quantity.cpp +++ b/src/scalar_image_quantity.cpp @@ -52,7 +52,7 @@ void ScalarImageQuantity::prepareFullscreen() { this->addScalarRules({getImageOriginRule(imageOrigin), "TEXTURE_SET_TRANSPARENCY", "TEXTURE_PREMULTIPLY_OUT"}), render::ShaderReplacementDefaults::Process); fullscreenProgram->setAttribute("a_position", render::engine->screenTrianglesCoords()); - fullscreenProgram->setTextureFromBuffer("t_scalar", values.getRenderTextureBuffer().get()); + fullscreenProgram->setTextureFromBuffer("t_scalar", values); fullscreenProgram->setTextureFromColormap("t_colormap", this->cMap.get()); } @@ -65,7 +65,7 @@ void ScalarImageQuantity::prepareBillboard() { "TEXTURE_BILLBOARD_FROM_UNIFORMS"}), render::ShaderReplacementDefaults::Process); billboardProgram->setAttribute("a_position", render::engine->screenTrianglesCoords()); - billboardProgram->setTextureFromBuffer("t_scalar", values.getRenderTextureBuffer().get()); + billboardProgram->setTextureFromBuffer("t_scalar", values); billboardProgram->setTextureFromColormap("t_colormap", this->cMap.get()); } diff --git a/src/scalar_render_image_quantity.cpp b/src/scalar_render_image_quantity.cpp index 19cc195e..1008cfa3 100644 --- a/src/scalar_render_image_quantity.cpp +++ b/src/scalar_render_image_quantity.cpp @@ -87,11 +87,11 @@ void ScalarRenderImageQuantity::prepare() { // clang-format on program->setAttribute("a_position", render::engine->screenTrianglesCoords()); - program->setTextureFromBuffer("t_depth", depths.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_depth", depths); if (hasNormals) { - program->setTextureFromBuffer("t_normal", normals.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_normal", normals); } - program->setTextureFromBuffer("t_scalar", values.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_scalar", values); render::engine->setMaterial(*program, material.get()); program->setTextureFromColormap("t_colormap", cMap.get()); } diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index 696b55f9..6151c395 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -253,7 +253,7 @@ std::vector SimpleTriangleMesh::addSimpleTriangleMeshRules(std::vec } void SimpleTriangleMesh::setSimpleTriangleMeshProgramGeometryAttributes(render::ShaderProgram& p) { - p.setAttribute("a_vertexPositions", vertices.getRenderAttributeBuffer()); + p.setAttribute("a_vertexPositions", vertices); p.setIndex(faces.getRenderAttributeBuffer()); } diff --git a/src/slice_plane.cpp b/src/slice_plane.cpp index 9b988f93..bc56d2d8 100644 --- a/src/slice_plane.cpp +++ b/src/slice_plane.cpp @@ -250,10 +250,10 @@ void SlicePlane::setSliceAttributes(render::ShaderProgram& p) { sliceBufferArr[i].markHostBufferUpdated(); } - p.setAttribute("a_slice_1", meshToInspect->vertexPositions.getIndexedRenderAttributeBuffer(sliceBufferArr[0])); - p.setAttribute("a_slice_2", meshToInspect->vertexPositions.getIndexedRenderAttributeBuffer(sliceBufferArr[1])); - p.setAttribute("a_slice_3", meshToInspect->vertexPositions.getIndexedRenderAttributeBuffer(sliceBufferArr[2])); - p.setAttribute("a_slice_4", meshToInspect->vertexPositions.getIndexedRenderAttributeBuffer(sliceBufferArr[3])); + p.setAttribute("a_slice_1", meshToInspect->vertexPositions.getIndexedRenderAttributeBuffer(sliceBufferArr[0]), &meshToInspect->vertexPositions); + p.setAttribute("a_slice_2", meshToInspect->vertexPositions.getIndexedRenderAttributeBuffer(sliceBufferArr[1]), &meshToInspect->vertexPositions); + p.setAttribute("a_slice_3", meshToInspect->vertexPositions.getIndexedRenderAttributeBuffer(sliceBufferArr[2]), &meshToInspect->vertexPositions); + p.setAttribute("a_slice_4", meshToInspect->vertexPositions.getIndexedRenderAttributeBuffer(sliceBufferArr[3]), &meshToInspect->vertexPositions); } void SlicePlane::drawGeometry() { diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index 1be789e1..51282de3 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -36,14 +36,14 @@ SparseVolumeGrid::SparseVolumeGrid(std::string name, glm::vec3 origin_, glm::vec cellPositions(this, uniquePrefix() + "#cellPositions", std::bind(&SparseVolumeGrid::computeCellPositions, this)), cellIndices(this, uniquePrefix() + "#cellIndices", [](){/* do nothing, gets handled by computeCellPositions */}), cornerNodeInds{ - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds0", std::vector{}), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds1", std::vector{}), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds2", std::vector{}), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds3", std::vector{}), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds4", std::vector{}), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds5", std::vector{}), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds6", std::vector{}), - render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds7", std::vector{}), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds0"), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds1"), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds2"), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds3"), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds4"), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds5"), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds6"), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds7"), }, origin(origin_), gridCellWidth(gridCellWidth_), @@ -367,8 +367,8 @@ void SparseVolumeGrid::ensureRenderProgramPrepared() { ); // clang-format on - program->setAttribute("a_cellPosition", cellPositions.getRenderAttributeBuffer()); - program->setAttribute("a_cellInd", cellIndices.getRenderAttributeBuffer()); + program->setAttribute("a_cellPosition", cellPositions); + program->setAttribute("a_cellInd", cellIndices); render::engine->setMaterial(*program, material.get()); } @@ -499,8 +499,8 @@ void SparseVolumeGrid::ensurePickProgramPrepared() { pickProgram->setAttribute("a_color", pickColors); - pickProgram->setAttribute("a_cellPosition", cellPositions.getRenderAttributeBuffer()); - pickProgram->setAttribute("a_cellInd", cellIndices.getRenderAttributeBuffer()); + pickProgram->setAttribute("a_cellPosition", cellPositions); + pickProgram->setAttribute("a_cellInd", cellIndices); } @@ -792,8 +792,8 @@ glm::vec3 SparseVolumeGrid::getWireframeColor() { return wireframeColor.get(); } void SparseVolumeGrid::setCellGeometryAttributes(render::ShaderProgram& p) { - p.setAttribute("a_cellPosition", cellPositions.getRenderAttributeBuffer()); - p.setAttribute("a_cellInd", cellIndices.getRenderAttributeBuffer()); + p.setAttribute("a_cellPosition", cellPositions); + p.setAttribute("a_cellInd", cellIndices); } std::vector SparseVolumeGrid::addSparseGridShaderRules(std::vector initRules, bool pickOnly) { diff --git a/src/sparse_volume_grid_color_quantity.cpp b/src/sparse_volume_grid_color_quantity.cpp index fcd493f6..247a7d65 100644 --- a/src/sparse_volume_grid_color_quantity.cpp +++ b/src/sparse_volume_grid_color_quantity.cpp @@ -61,7 +61,7 @@ void SparseVolumeGridCellColorQuantity::createProgram() { // clang-format on parent.setCellGeometryAttributes(*program); - program->setAttribute("a_color", colors.getRenderAttributeBuffer()); + program->setAttribute("a_color", colors); render::engine->setMaterial(*program, parent.getMaterial()); } @@ -108,7 +108,7 @@ void SparseVolumeGridNodeColorQuantity::createProgram() { parent.setCellGeometryAttributes(*program); for (int c = 0; c < 8; c++) { program->setAttribute("a_nodeColor" + std::to_string(c), - colors.getIndexedRenderAttributeBuffer(parent.cornerNodeInds[c])); + colors.getIndexedRenderAttributeBuffer(parent.cornerNodeInds[c]), &colors); } render::engine->setMaterial(*program, parent.getMaterial()); } diff --git a/src/sparse_volume_grid_scalar_quantity.cpp b/src/sparse_volume_grid_scalar_quantity.cpp index 125061c7..088b65a2 100644 --- a/src/sparse_volume_grid_scalar_quantity.cpp +++ b/src/sparse_volume_grid_scalar_quantity.cpp @@ -76,7 +76,7 @@ void SparseVolumeGridCellScalarQuantity::createProgram() { // clang-format on parent.setCellGeometryAttributes(*program); - program->setAttribute("a_value", values.getRenderAttributeBuffer()); + program->setAttribute("a_value", values); program->setTextureFromColormap("t_colormap", cMap.get()); render::engine->setMaterial(*program, parent.getMaterial()); } @@ -121,7 +121,7 @@ void SparseVolumeGridNodeScalarQuantity::createProgram() { parent.setCellGeometryAttributes(*program); for (int c = 0; c < 8; c++) { program->setAttribute("a_nodeValue" + std::to_string(c), - values.getIndexedRenderAttributeBuffer(parent.cornerNodeInds[c])); + values.getIndexedRenderAttributeBuffer(parent.cornerNodeInds[c]), &values); } program->setTextureFromColormap("t_colormap", cMap.get()); render::engine->setMaterial(*program, parent.getMaterial()); diff --git a/src/surface_color_quantity.cpp b/src/surface_color_quantity.cpp index 4a6f1ef3..7955542d 100644 --- a/src/surface_color_quantity.cpp +++ b/src/surface_color_quantity.cpp @@ -70,7 +70,7 @@ void SurfaceVertexColorQuantity::createProgram() { // clang-format on parent.setMeshGeometryAttributes(*program); - program->setAttribute("a_color", colors.getIndexedRenderAttributeBuffer(parent.triangleVertexInds)); + program->setAttribute("a_color", colors.getIndexedRenderAttributeBuffer(parent.triangleVertexInds), &colors); render::engine->setMaterial(*program, parent.getMaterial()); } @@ -123,7 +123,7 @@ void SurfaceFaceColorQuantity::createProgram() { // clang-format on parent.setMeshGeometryAttributes(*program); - program->setAttribute("a_color", colors.getIndexedRenderAttributeBuffer(parent.triangleFaceInds)); + program->setAttribute("a_color", colors.getIndexedRenderAttributeBuffer(parent.triangleFaceInds), &colors); render::engine->setMaterial(*program, parent.getMaterial()); } @@ -177,17 +177,17 @@ void SurfaceTextureColorQuantity::createProgram() { // the indexing into the parameterization varies based on whether it is a corner or vertex quantity switch (param.definedOn) { case MeshElement::VERTEX: - program->setAttribute("a_tCoord", param.coords.getIndexedRenderAttributeBuffer(parent.triangleVertexInds)); + program->setAttribute("a_tCoord", param.coords.getIndexedRenderAttributeBuffer(parent.triangleVertexInds), ¶m.coords); break; case MeshElement::CORNER: - program->setAttribute("a_tCoord", param.coords.getIndexedRenderAttributeBuffer(parent.triangleCornerInds)); + program->setAttribute("a_tCoord", param.coords.getIndexedRenderAttributeBuffer(parent.triangleCornerInds), ¶m.coords); break; default: // nothing break; } - program->setTextureFromBuffer("t_color", colors.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_color", colors); render::engine->setMaterial(*program, parent.getMaterial()); colors.getRenderTextureBuffer()->setFilterMode(filterMode.get()); diff --git a/src/surface_mesh.cpp b/src/surface_mesh.cpp index 21e3cdc6..4a64cbf1 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -28,12 +28,12 @@ SurfaceMesh::SurfaceMesh(std::string name_) // == managed quantities // positions -vertexPositions( this, uniquePrefix() + "vertexPositions", std::vector{}), +vertexPositions( this, uniquePrefix() + "vertexPositions"), // connectivity / indices // (triangle and face inds are always computed initially when we triangulate the mesh) -triangleVertexInds( this, uniquePrefix() + "triangleVertexInds", std::vector{}), -triangleFaceInds( this, uniquePrefix() + "triangleFaceInds", std::vector{}), +triangleVertexInds( this, uniquePrefix() + "triangleVertexInds"), +triangleFaceInds( this, uniquePrefix() + "triangleFaceInds"), triangleCornerInds( this, uniquePrefix() + "triangleCornerInds", std::bind(&SurfaceMesh::computeTriangleCornerInds, this)), triangleAllVertexInds( this, uniquePrefix() + "triangleAllVertexInds", std::bind(&SurfaceMesh::computeTriangleAllVertexInds, this)), triangleAllEdgeInds( this, uniquePrefix() + "triangleAllEdgeInds", std::bind(&SurfaceMesh::computeTriangleAllEdgeInds, this)), @@ -41,8 +41,8 @@ triangleAllHalfedgeInds( this, uniquePrefix() + "triangleHalfedgeInds", triangleAllCornerInds( this, uniquePrefix() + "triangleAllCornerInds", std::bind(&SurfaceMesh::computeTriangleAllCornerInds, this)), // internal triangle data for rendering -baryCoord( this, uniquePrefix() + "baryCoord", std::vector{}), -edgeIsReal( this, uniquePrefix() + "edgeIsReal", std::vector{}), +baryCoord( this, uniquePrefix() + "baryCoord"), +edgeIsReal( this, uniquePrefix() + "edgeIsReal"), // other internally-computed geometry faceNormals( this, uniquePrefix() + "faceNormals", std::bind(&SurfaceMesh::computeFaceNormals, this)), @@ -872,29 +872,29 @@ void SurfaceMesh::preparePick() { void SurfaceMesh::setMeshGeometryAttributes(render::ShaderProgram& p) { if (p.hasAttribute("a_vertexPositions")) { - p.setAttribute("a_vertexPositions", vertexPositions.getIndexedRenderAttributeBuffer(triangleVertexInds)); + p.setAttribute("a_vertexPositions", vertexPositions.getIndexedRenderAttributeBuffer(triangleVertexInds), &vertexPositions); } if (p.hasAttribute("a_vertexNormals")) { if (getShadeStyle() == MeshShadeStyle::Smooth) { - p.setAttribute("a_vertexNormals", vertexNormals.getIndexedRenderAttributeBuffer(triangleVertexInds)); + p.setAttribute("a_vertexNormals", vertexNormals.getIndexedRenderAttributeBuffer(triangleVertexInds), &vertexNormals); } else { // these aren't actually used in in the automatically-generated case, but the shader is set up in a lazy way so // it is still needed - p.setAttribute("a_vertexNormals", faceNormals.getIndexedRenderAttributeBuffer(triangleFaceInds)); + p.setAttribute("a_vertexNormals", faceNormals.getIndexedRenderAttributeBuffer(triangleFaceInds), &faceNormals); } } if (p.hasAttribute("a_normal")) { - p.setAttribute("a_normal", faceNormals.getIndexedRenderAttributeBuffer(triangleFaceInds)); + p.setAttribute("a_normal", faceNormals.getIndexedRenderAttributeBuffer(triangleFaceInds), &faceNormals); } if (p.hasAttribute("a_barycoord")) { - p.setAttribute("a_barycoord", baryCoord.getRenderAttributeBuffer()); + p.setAttribute("a_barycoord", baryCoord); } if (p.hasAttribute("a_edgeIsReal")) { - p.setAttribute("a_edgeIsReal", edgeIsReal.getRenderAttributeBuffer()); + p.setAttribute("a_edgeIsReal", edgeIsReal); } if (wantsCullPosition()) { - p.setAttribute("a_cullPos", faceCenters.getIndexedRenderAttributeBuffer(triangleFaceInds)); + p.setAttribute("a_cullPos", faceCenters.getIndexedRenderAttributeBuffer(triangleFaceInds), &faceCenters); } if (transparencyQuantityName != "") { diff --git a/src/surface_parameterization_quantity.cpp b/src/surface_parameterization_quantity.cpp index 56342e0e..be773f8b 100644 --- a/src/surface_parameterization_quantity.cpp +++ b/src/surface_parameterization_quantity.cpp @@ -76,7 +76,7 @@ void SurfaceParameterizationQuantity::createProgram() { parent.setMeshGeometryAttributes(*program); if(getStyle() == ParamVizStyle::CHECKER_ISLANDS) { - program->setAttribute("a_value", islandLabels.getIndexedRenderAttributeBuffer(parent.triangleFaceInds)); + program->setAttribute("a_value", islandLabels.getIndexedRenderAttributeBuffer(parent.triangleFaceInds), &islandLabels); } render::engine->setMaterial(*program, parent.getMaterial()); @@ -228,7 +228,7 @@ std::string SurfaceCornerParameterizationQuantity::niceName() { return name + " void SurfaceCornerParameterizationQuantity::fillCoordBuffers(render::ShaderProgram& p) { - p.setAttribute("a_value2", coords.getIndexedRenderAttributeBuffer(parent.triangleCornerInds)); + p.setAttribute("a_value2", coords.getIndexedRenderAttributeBuffer(parent.triangleCornerInds), &coords); } @@ -260,7 +260,7 @@ SurfaceVertexParameterizationQuantity::SurfaceVertexParameterizationQuantity(std std::string SurfaceVertexParameterizationQuantity::niceName() { return name + " (vertex parameterization)"; } void SurfaceVertexParameterizationQuantity::fillCoordBuffers(render::ShaderProgram& p) { - p.setAttribute("a_value2", coords.getIndexedRenderAttributeBuffer(parent.triangleVertexInds)); + p.setAttribute("a_value2", coords.getIndexedRenderAttributeBuffer(parent.triangleVertexInds), &coords); } std::vector SurfaceVertexParameterizationQuantity::getCornerCoords() { diff --git a/src/surface_scalar_quantity.cpp b/src/surface_scalar_quantity.cpp index 894c5f57..f11b866d 100644 --- a/src/surface_scalar_quantity.cpp +++ b/src/surface_scalar_quantity.cpp @@ -85,7 +85,7 @@ void SurfaceVertexScalarQuantity::createProgram() { ); // clang-format on - program->setAttribute("a_value3", values.getIndexedRenderAttributeBuffer(parent.triangleAllVertexInds)); + program->setAttribute("a_value3", values.getIndexedRenderAttributeBuffer(parent.triangleAllVertexInds), &values); } else { // common case: linear interpolation within each triangle @@ -102,7 +102,7 @@ void SurfaceVertexScalarQuantity::createProgram() { ); // clang-format on - program->setAttribute("a_value", values.getIndexedRenderAttributeBuffer(parent.triangleVertexInds)); + program->setAttribute("a_value", values.getIndexedRenderAttributeBuffer(parent.triangleVertexInds), &values); } parent.setMeshGeometryAttributes(*program); @@ -146,7 +146,7 @@ void SurfaceFaceScalarQuantity::createProgram() { ); // clang-format on - program->setAttribute("a_value", values.getIndexedRenderAttributeBuffer(parent.triangleFaceInds)); + program->setAttribute("a_value", values.getIndexedRenderAttributeBuffer(parent.triangleFaceInds), &values); parent.setMeshGeometryAttributes(*program); render::engine->setMaterial(*program, parent.getMaterial()); program->setTextureFromColormap("t_colormap", cMap.get()); @@ -192,7 +192,7 @@ void SurfaceEdgeScalarQuantity::createProgram() { ); // clang-format on - program->setAttribute("a_value3", values.getIndexedRenderAttributeBuffer(parent.triangleAllEdgeInds)); + program->setAttribute("a_value3", values.getIndexedRenderAttributeBuffer(parent.triangleAllEdgeInds), &values); parent.setMeshGeometryAttributes(*program); render::engine->setMaterial(*program, parent.getMaterial()); program->setTextureFromColormap("t_colormap", cMap.get()); @@ -234,7 +234,7 @@ void SurfaceHalfedgeScalarQuantity::createProgram() { ); // clang-format on - program->setAttribute("a_value3", values.getIndexedRenderAttributeBuffer(parent.triangleAllHalfedgeInds)); + program->setAttribute("a_value3", values.getIndexedRenderAttributeBuffer(parent.triangleAllHalfedgeInds), &values); parent.setMeshGeometryAttributes(*program); render::engine->setMaterial(*program, parent.getMaterial()); program->setTextureFromColormap("t_colormap", cMap.get()); @@ -280,7 +280,7 @@ void SurfaceCornerScalarQuantity::createProgram() { ); // clang-format on - program->setAttribute("a_value3", values.getIndexedRenderAttributeBuffer(parent.triangleAllCornerInds)); + program->setAttribute("a_value3", values.getIndexedRenderAttributeBuffer(parent.triangleAllCornerInds), &values); } else { @@ -296,7 +296,7 @@ void SurfaceCornerScalarQuantity::createProgram() { ); // clang-format on - program->setAttribute("a_value", values.getIndexedRenderAttributeBuffer(parent.triangleCornerInds)); + program->setAttribute("a_value", values.getIndexedRenderAttributeBuffer(parent.triangleCornerInds), &values); } parent.setMeshGeometryAttributes(*program); @@ -353,17 +353,17 @@ void SurfaceTextureScalarQuantity::createProgram() { // the indexing into the parameterization varies based on whether it is a corner or vertex quantity switch (param.definedOn) { case MeshElement::VERTEX: - program->setAttribute("a_tCoord", param.coords.getIndexedRenderAttributeBuffer(parent.triangleVertexInds)); + program->setAttribute("a_tCoord", param.coords.getIndexedRenderAttributeBuffer(parent.triangleVertexInds), ¶m.coords); break; case MeshElement::CORNER: - program->setAttribute("a_tCoord", param.coords.getIndexedRenderAttributeBuffer(parent.triangleCornerInds)); + program->setAttribute("a_tCoord", param.coords.getIndexedRenderAttributeBuffer(parent.triangleCornerInds), ¶m.coords); break; default: // nothing break; } - program->setTextureFromBuffer("t_scalar", values.getRenderTextureBuffer().get()); + program->setTextureFromBuffer("t_scalar", values); render::engine->setMaterial(*program, parent.getMaterial()); program->setTextureFromColormap("t_colormap", cMap.get()); diff --git a/src/surface_vector_quantity.cpp b/src/surface_vector_quantity.cpp index bbffc054..7d31b51d 100644 --- a/src/surface_vector_quantity.cpp +++ b/src/surface_vector_quantity.cpp @@ -266,8 +266,8 @@ SurfaceOneFormTangentVectorQuantity::SurfaceOneFormTangentVectorQuantity(std::st : SurfaceVectorQuantity(name, mesh_, MeshElement::FACE), TangentVectorQuantity( *this, oneFormToFaceTangentVectors(mesh_, oneForm_, canonicalOrientation_), - std::vector(mesh_.defaultFaceTangentBasisX.begin(), mesh_.defaultFaceTangentBasisX.end()), - std::vector(mesh_.defaultFaceTangentBasisY.begin(), mesh_.defaultFaceTangentBasisY.end()), + mesh_.defaultFaceTangentBasisX.getDataCopy(), + mesh_.defaultFaceTangentBasisY.getDataCopy(), parent.faceCenters, 1, VectorType::STANDARD), oneForm(oneForm_), canonicalOrientation(canonicalOrientation_) {} diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index fded29a7..c6327611 100644 --- a/src/volume_grid.cpp +++ b/src/volume_grid.cpp @@ -242,9 +242,9 @@ void VolumeGrid::ensureGridCubeRenderProgramPrepared() { ); // clang-format on - program->setAttribute("a_referencePosition", gridPlaneReferencePositions.getRenderAttributeBuffer()); - program->setAttribute("a_referenceNormal", gridPlaneReferenceNormals.getRenderAttributeBuffer()); - program->setAttribute("a_axisInd", gridPlaneAxisInds.getRenderAttributeBuffer()); + program->setAttribute("a_referencePosition", gridPlaneReferencePositions); + program->setAttribute("a_referenceNormal", gridPlaneReferenceNormals); + program->setAttribute("a_axisInd", gridPlaneAxisInds); render::engine->setMaterial(*program, material.get()); } @@ -256,16 +256,16 @@ void VolumeGrid::ensureGridCubePickProgramPrepared() { // clang-format off pickProgram = render::engine->requestShader( - "GRIDCUBE_PLANE", - addGridCubeRules({"GRIDCUBE_CONSTANT_PICK"}, false), + "GRIDCUBE_PLANE", + addGridCubeRules({"GRIDCUBE_CONSTANT_PICK"}, false), render::ShaderReplacementDefaults::Pick ); // clang-format on - pickProgram->setAttribute("a_referencePosition", gridPlaneReferencePositions.getRenderAttributeBuffer()); - pickProgram->setAttribute("a_referenceNormal", gridPlaneReferenceNormals.getRenderAttributeBuffer()); - pickProgram->setAttribute("a_axisInd", gridPlaneAxisInds.getRenderAttributeBuffer()); + pickProgram->setAttribute("a_referencePosition", gridPlaneReferencePositions); + pickProgram->setAttribute("a_referenceNormal", gridPlaneReferenceNormals); + pickProgram->setAttribute("a_axisInd", gridPlaneAxisInds); if (globalPickConstant == INVALID_IND_64) { diff --git a/src/volume_grid_scalar_quantity.cpp b/src/volume_grid_scalar_quantity.cpp index 1010e3d4..33645e30 100644 --- a/src/volume_grid_scalar_quantity.cpp +++ b/src/volume_grid_scalar_quantity.cpp @@ -148,15 +148,15 @@ void VolumeGridNodeScalarQuantity::createGridcubeProgram() { ); // clang-format on - gridcubeProgram->setAttribute("a_referencePosition", parent.gridPlaneReferencePositions.getRenderAttributeBuffer()); - gridcubeProgram->setAttribute("a_referenceNormal", parent.gridPlaneReferenceNormals.getRenderAttributeBuffer()); - gridcubeProgram->setAttribute("a_axisInd", parent.gridPlaneAxisInds.getRenderAttributeBuffer()); + gridcubeProgram->setAttribute("a_referencePosition", parent.gridPlaneReferencePositions); + gridcubeProgram->setAttribute("a_referenceNormal", parent.gridPlaneReferenceNormals); + gridcubeProgram->setAttribute("a_axisInd", parent.gridPlaneAxisInds); gridcubeProgram->setTextureFromColormap("t_colormap", cMap.get()); render::engine->setMaterial(*gridcubeProgram, parent.getMaterial()); - gridcubeProgram->setTextureFromBuffer("t_value", values.getRenderTextureBuffer().get()); - values.getRenderTextureBuffer().get()->setFilterMode(FilterMode::Linear); + gridcubeProgram->setTextureFromBuffer("t_value", values); + values.getRenderTextureBuffer()->setFilterMode(FilterMode::Linear); } void VolumeGridNodeScalarQuantity::createIsosurfaceProgram() { @@ -360,15 +360,15 @@ void VolumeGridCellScalarQuantity::createGridcubeProgram() { ); // clang-format on - gridcubeProgram->setAttribute("a_referencePosition", parent.gridPlaneReferencePositions.getRenderAttributeBuffer()); - gridcubeProgram->setAttribute("a_referenceNormal", parent.gridPlaneReferenceNormals.getRenderAttributeBuffer()); - gridcubeProgram->setAttribute("a_axisInd", parent.gridPlaneAxisInds.getRenderAttributeBuffer()); + gridcubeProgram->setAttribute("a_referencePosition", parent.gridPlaneReferencePositions); + gridcubeProgram->setAttribute("a_referenceNormal", parent.gridPlaneReferenceNormals); + gridcubeProgram->setAttribute("a_axisInd", parent.gridPlaneAxisInds); gridcubeProgram->setTextureFromColormap("t_colormap", cMap.get()); render::engine->setMaterial(*gridcubeProgram, parent.getMaterial()); - gridcubeProgram->setTextureFromBuffer("t_value", values.getRenderTextureBuffer().get()); - values.getRenderTextureBuffer().get()->setFilterMode(FilterMode::Linear); + gridcubeProgram->setTextureFromBuffer("t_value", values); + values.getRenderTextureBuffer()->setFilterMode(FilterMode::Linear); } void VolumeGridCellScalarQuantity::buildCellInfoGUI(size_t ind) { diff --git a/src/volume_mesh.cpp b/src/volume_mesh.cpp index 4fa610e6..cc87f77d 100644 --- a/src/volume_mesh.cpp +++ b/src/volume_mesh.cpp @@ -350,14 +350,14 @@ VolumeMesh::VolumeMesh(std::string name, const std::vector& vertexPos vertexPositions( this, uniquePrefix() + "vertexPositions", std::vector(vertexPositions_)), // connectivity / indices -triangleVertexInds( this, uniquePrefix() + "triangleVertexInds", std::vector{}), -triangleFaceInds( this, uniquePrefix() + "triangleFaceInds", std::vector{}), -triangleCellInds( this, uniquePrefix() + "triangleCellInds", std::vector{}), +triangleVertexInds( this, uniquePrefix() + "triangleVertexInds"), +triangleFaceInds( this, uniquePrefix() + "triangleFaceInds"), +triangleCellInds( this, uniquePrefix() + "triangleCellInds"), // internal triangle data for rendering -baryCoord( this, uniquePrefix() + "baryCoord", std::vector{}), -edgeIsReal( this, uniquePrefix() + "edgeIsReal", std::vector{}), -faceType( this, uniquePrefix() + "faceType", std::vector{}), +baryCoord( this, uniquePrefix() + "baryCoord"), +edgeIsReal( this, uniquePrefix() + "edgeIsReal"), +faceType( this, uniquePrefix() + "faceType"), // other internally-computed geometry faceNormals( this, uniquePrefix() + "faceNormals", std::bind(&VolumeMesh::computeFaceNormals, this)), @@ -787,9 +787,9 @@ void VolumeMesh::setVolumeMeshUniforms(render::ShaderProgram& p) { void VolumeMesh::fillGeometryBuffers(render::ShaderProgram& p) { - p.setAttribute("a_vertexPositions", vertexPositions.getIndexedRenderAttributeBuffer(triangleVertexInds)); + p.setAttribute("a_vertexPositions", vertexPositions.getIndexedRenderAttributeBuffer(triangleVertexInds), &vertexPositions); - p.setAttribute("a_vertexNormals", faceNormals.getIndexedRenderAttributeBuffer(triangleFaceInds)); + p.setAttribute("a_vertexNormals", faceNormals.getIndexedRenderAttributeBuffer(triangleFaceInds), &faceNormals); bool wantsBary = p.hasAttribute("a_barycoord"); bool wantsEdge = (getEdgeWidth() > 0); @@ -797,16 +797,16 @@ void VolumeMesh::fillGeometryBuffers(render::ShaderProgram& p) { bool wantsFaceType = p.hasAttribute("a_faceColorType"); if (wantsBary) { - p.setAttribute("a_barycoord", baryCoord.getRenderAttributeBuffer()); + p.setAttribute("a_barycoord", baryCoord); } if (wantsEdge) { - p.setAttribute("a_edgeIsReal", edgeIsReal.getRenderAttributeBuffer()); + p.setAttribute("a_edgeIsReal", edgeIsReal); } if (wantsAttrCullPosition) { - p.setAttribute("a_cullPos", cellCenters.getIndexedRenderAttributeBuffer(triangleCellInds)); + p.setAttribute("a_cullPos", cellCenters.getIndexedRenderAttributeBuffer(triangleCellInds), &cellCenters); } if (wantsFaceType) { - p.setAttribute("a_faceColorType", faceType.getIndexedRenderAttributeBuffer(triangleFaceInds)); + p.setAttribute("a_faceColorType", faceType.getIndexedRenderAttributeBuffer(triangleFaceInds), &faceType); } } diff --git a/src/volume_mesh_color_quantity.cpp b/src/volume_mesh_color_quantity.cpp index 712c7c36..e4708a9c 100644 --- a/src/volume_mesh_color_quantity.cpp +++ b/src/volume_mesh_color_quantity.cpp @@ -123,7 +123,7 @@ void VolumeMeshVertexColorQuantity::createProgram() { // Fill color buffers parent.fillGeometryBuffers(*program); - program->setAttribute("a_color", colors.getIndexedRenderAttributeBuffer(parent.triangleVertexInds)); + program->setAttribute("a_color", colors.getIndexedRenderAttributeBuffer(parent.triangleVertexInds), &colors); render::engine->setMaterial(*program, parent.getMaterial()); } @@ -176,7 +176,7 @@ void VolumeMeshCellColorQuantity::createProgram() { // Fill color buffers parent.fillGeometryBuffers(*program); - program->setAttribute("a_color", colors.getIndexedRenderAttributeBuffer(parent.triangleCellInds)); + program->setAttribute("a_color", colors.getIndexedRenderAttributeBuffer(parent.triangleCellInds), &colors); render::engine->setMaterial(*program, parent.getMaterial()); } diff --git a/src/volume_mesh_scalar_quantity.cpp b/src/volume_mesh_scalar_quantity.cpp index 946bfc7b..6a963851 100644 --- a/src/volume_mesh_scalar_quantity.cpp +++ b/src/volume_mesh_scalar_quantity.cpp @@ -259,7 +259,7 @@ void VolumeMeshVertexScalarQuantity::createProgram() { // Fill color buffers parent.fillGeometryBuffers(*program); - program->setAttribute("a_value", values.getIndexedRenderAttributeBuffer(parent.triangleVertexInds)); + program->setAttribute("a_value", values.getIndexedRenderAttributeBuffer(parent.triangleVertexInds), &values); program->setTextureFromColormap("t_colormap", cMap.get()); render::engine->setMaterial(*program, parent.getMaterial()); } @@ -351,7 +351,7 @@ void VolumeMeshCellScalarQuantity::createProgram() { // Fill color buffers parent.fillGeometryBuffers(*program); - program->setAttribute("a_value", values.getIndexedRenderAttributeBuffer(parent.triangleCellInds)); + program->setAttribute("a_value", values.getIndexedRenderAttributeBuffer(parent.triangleCellInds), &values); program->setTextureFromColormap("t_colormap", cMap.get()); render::engine->setMaterial(*program, parent.getMaterial()); } From 20eb64374c42ea84f1773608ffc389406a22e8b2 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 15:54:52 -0700 Subject: [PATCH 5/8] bugfix with new setter --- src/volume_mesh.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/volume_mesh.cpp b/src/volume_mesh.cpp index cc87f77d..9ca27b8f 100644 --- a/src/volume_mesh.cpp +++ b/src/volume_mesh.cpp @@ -869,10 +869,11 @@ void VolumeMesh::computeConnectivityData() { baryCoord.setHostValue(3 * iData + 2, glm::vec3{0., 0., 1.}); // Mark edges as real or not + glm::vec3 eReal{faceRealEdges[f][j][0] ? 1.0f : 0.0f, + faceRealEdges[f][j][1] ? 1.0f : 0.0f, + faceRealEdges[f][j][2] ? 1.0f : 0.0f}; for (int k = 0; k < 3; k++) { - for (int c = 0; c < 3; c++) { - edgeIsReal.setHostValue(3 * iData + k, c, faceRealEdges[f][j][c] ? 1.0f : 0.0f); - } + edgeIsReal.setHostValue(3 * iData + k, eReal); } } From 7e9995e88de17202170de0ceca6f09d0dcadf844 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 16:01:38 -0700 Subject: [PATCH 6/8] remove duplicated lines --- src/surface_vector_quantity.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/surface_vector_quantity.cpp b/src/surface_vector_quantity.cpp index 7d31b51d..3c30be73 100644 --- a/src/surface_vector_quantity.cpp +++ b/src/surface_vector_quantity.cpp @@ -214,15 +214,6 @@ std::vector oneFormToFaceTangentVectors(SurfaceMesh& mesh, const std: mesh.triangleAllEdgeInds.ensureHostBufferPopulated(); mesh.triangleVertexInds.ensureHostBufferPopulated(); - // TODO why is this duplicated?! - mesh.vertexPositions.ensureHostBufferPopulated(); - mesh.faceAreas.ensureHostBufferPopulated(); - mesh.faceNormals.ensureHostBufferPopulated(); - mesh.defaultFaceTangentBasisX.ensureHostBufferPopulated(); - mesh.defaultFaceTangentBasisY.ensureHostBufferPopulated(); - mesh.triangleAllEdgeInds.ensureHostBufferPopulated(); - mesh.triangleVertexInds.ensureHostBufferPopulated(); - std::vector mappedVectorField(mesh.nFaces()); for (size_t iF = 0; iF < mesh.nFaces(); iF++) { From 4b967f803a688364fdd6e0b27a0c3140361732b1 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 16:04:12 -0700 Subject: [PATCH 7/8] avoid duplicate mark --- src/volume_grid.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index c6327611..5fb666c4 100644 --- a/src/volume_grid.cpp +++ b/src/volume_grid.cpp @@ -380,10 +380,6 @@ void VolumeGrid::computeGridPlaneReferenceGeometry() { gridPlaneReferenceNormals.setDataHost(normalsVec); gridPlaneAxisInds.resize(axisIndsVec.size()); gridPlaneAxisInds.setDataHost(axisIndsVec); - // gridPlaneReferencePositions.markHostBufferUpdated() is called by recomputeIfPopulated() after this callback. - // The side-effect buffers must be marked updated explicitly since they are not the primary compute target. - gridPlaneReferenceNormals.markHostBufferUpdated(); - gridPlaneAxisInds.markHostBufferUpdated(); } // === Option getters and setters From 89e14cf7d86e172cc8fef56b4c64f937a6e51107 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 16:56:44 -0700 Subject: [PATCH 8/8] additional cleanup of ManagedBuffer api --- include/polyscope/render/managed_buffer.h | 228 +++++++++--------- src/color_image_quantity.cpp | 1 + src/color_render_image_quantity.cpp | 1 + src/curve_network.cpp | 1 + src/raw_color_alpha_render_image_quantity.cpp | 1 + src/raw_color_render_image_quantity.cpp | 1 + src/render/managed_buffer.cpp | 133 ++++------ src/render_image_quantity_base.cpp | 2 + src/scalar_image_quantity.cpp | 1 + src/scalar_render_image_quantity.cpp | 1 + src/sparse_volume_grid.cpp | 3 +- src/surface_color_quantity.cpp | 1 + src/surface_mesh.cpp | 12 + src/surface_scalar_quantity.cpp | 1 + src/volume_grid_scalar_quantity.cpp | 2 + src/volume_mesh.cpp | 2 + 16 files changed, 196 insertions(+), 195 deletions(-) diff --git a/include/polyscope/render/managed_buffer.h b/include/polyscope/render/managed_buffer.h index 9247a092..499d9dd2 100644 --- a/include/polyscope/render/managed_buffer.h +++ b/include/polyscope/render/managed_buffer.h @@ -19,8 +19,8 @@ class ManagedBufferRegistry; /* * Non-templated base class for ManagedBuffer. - * Contains all type-independent members and provides the syncToDeviceIfNeeded() virtual interface - * used by ShaderProgram::draw() to lazily push host data to the GPU before issuing a draw call. + * Contains all type-independent members, in particular the syncToDeviceIfNeeded() function which needs + * to be called without the type known. */ class ManagedBufferBase { public: @@ -56,11 +56,7 @@ class ManagedBufferBase { bool deviceBufferValid; // true if device-side buffer matches current host data // True if all registered indexed views are consistent with the current source data. - // Set to false by markRenderAttributeBufferUpdated() when the GPU source is written externally - // and host data is no longer available to re-gather from. Will be set back to true once - // updateIndexedViews() runs successfully (which requires hostBufferValid). - // Future work: an on-device gather pass will be able to update indexed views even when - // !hostBufferValid, at which point this flag drives that path. + // Gets marked as false whenever the source data changes on the host or device, until the indexed views are updated bool indexedViewsValid = true; // Explicit size and capacity. @@ -74,9 +70,10 @@ class ManagedBufferBase { std::shared_ptr renderTextureBuffer; DeviceBufferType deviceBufferType = DeviceBufferType::Attribute; - uint32_t sizeX = 0; - uint32_t sizeY = 0; - uint32_t sizeZ = 0; + + uint32_t sizeX = 0; // only populated if texture + uint32_t sizeY = 0; // only populated if texture + uint32_t sizeZ = 0; // only populated if texture }; @@ -97,7 +94,9 @@ class ManagedBufferBase { template class ManagedBuffer : public ManagedBufferBase, public virtual WeakReferrable { public: - // === Constructors + // ======================================================================== + // == Constructors + // ======================================================================== // Create an empty buffer with no data. Use resize()+setHostValue() or setDataHost() to populate. ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name); @@ -106,26 +105,14 @@ class ManagedBuffer : public ManagedBufferBase, public virtual WeakReferrable { ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, std::vector data); // Manage a buffer of data which gets computed lazily - ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, - std::function computeFunc); - + ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, std::function computeFunc); ~ManagedBuffer(); - // === Core members (note: name, uniqueID, registry, dataGetsComputed, computeFunc live in ManagedBufferBase) - - - // sanity check helper - void checkInvalidValues(); - - // mark as texture, set size - void setTextureSize(uint32_t sizeX); - void setTextureSize(uint32_t sizeX, uint32_t sizeY); - void setTextureSize(uint32_t sizeX, uint32_t sizeY, uint32_t sizeZ); - std::array getTextureSize() const; - - // == Resize / capacity API + // ======================================================================== + // == Size, capacity, and type management + // ======================================================================== // The .size() of the buffer is the number of data elements it holds. // @@ -136,6 +123,39 @@ class ManagedBuffer : public ManagedBufferBase, public virtual WeakReferrable { // But in settings where we e.g. incrementally add elements or change the number of data elements on each frame, we // may want to allocate a larger capacity to avoid expensive re-allocation each time. + // Get the size of the data in the buffer. Always <= capacity(). + size_t size() const; + + // Get the capacity of the buffer. The maximum size the buffer can be resized to without triggering a reallocation. + // Always >= size(). + size_t capacity() const; + + // Set the managed capacity to newCapacity. Unlike resize(), which grows capacity via amortized + // doubling, this sets the logical capacity to a precise value. Error if newCapacity < size(). + // Reallocates the GPU buffer in-place (same buffer object, new backing memory) if one exists. + // + // Valid for attributes and 1D textures only; multidimensional textures always have capacity + // equal to their size, so use setTextureSize() instead. + void setCapacity(size_t newCapacity); + + // Set the device buffer type. Call once at construction time before setTextureSize(). + // All buffers are Attribute by default, and can be switch to texture exactly once. + void setAsType(DeviceBufferType type); + + // Is it an attribute, texture1d, texture2d, etc? + DeviceBufferType getDeviceBufferType() const; + + // Set or update the texture dimensions. On first call (managedCapacity == 0): just stores the + // dimensions. On subsequent calls: no-op if unchanged; otherwise copies any device-side data + // back to host and reallocates the GPU buffer in-place. + // Requires setAsType() to have been called first with the matching texture type. + // For 1D textures, resize() may be used instead. + void setTextureSize(uint32_t sizeX); + void setTextureSize(uint32_t sizeX, uint32_t sizeY); + void setTextureSize(uint32_t sizeX, uint32_t sizeY, uint32_t sizeZ); + std::array getTextureSize() const; + + // Resize the buffer to newSize elements. Sets hostBufferValid = true. // // If newSize <= capacity(), this is a cheap constant-time operation which just updates metadata. @@ -147,39 +167,25 @@ class ManagedBuffer : public ManagedBufferBase, public virtual WeakReferrable { // // Returns true if a reallocation occurred, false if the resize stayed within capacity. // - // Valid for attributes and 1D textures only; call the 2D/3D variants below for multidimensional - // textures. + // Valid for attributes and 1D textures only; call setTextureSize() for multidimensional textures. bool resize(size_t newSize); - - // Resize a 2D texture. No-op (returns false) if dimensions are unchanged. Otherwise always - // triggers a reallocation (2D/3D textures have no capacity slack — capacity always equals size), - // reallocates the GPU buffer in-place, and returns true. - bool resizeTexture2D(uint32_t newSizeX, uint32_t newSizeY); - - // Resize a 3D texture. No-op (returns false) if dimensions are unchanged. Otherwise always - // triggers a reallocation (2D/3D textures have no capacity slack — capacity always equals size), - // reallocates the GPU buffer in-place, and returns true. - bool resizeTexture3D(uint32_t newSizeX, uint32_t newSizeY, uint32_t newSizeZ); - // The maximum size the buffer can be resized to without triggering a reallocation. Always >= size(). - size_t capacity(); - // Set the managed capacity to newCapacity. Unlike resize(), which grows capacity via amortized - // doubling, this sets the logical capacity to a precise value. Error if newCapacity < size(). - // Reallocates the GPU buffer in-place (same buffer object, new backing memory) if one exists. - // - // Valid for attributes and 1D textures only; multidimensional textures always have capacity - // equal to their size, so use the 2D/3D resize() variants instead. - void setCapacity(size_t newCapacity); + // ======================================================================== + // == Basic data access + // ======================================================================== + // The functions are always valid to call, but might be expensive to call in a type loop or if the data resides + // only on the device. - // == Basic interactions + // Get the value at index `i`. It may be dynamically fetched from either the cpu-side `data` member or the render + // buffer, depending on where the data currently lives. + // If the data lives only on the device-side render buffer, this function is expensive, so don't call it in a + // loop. + T getValue(size_t ind); + T getValue(size_t indX, size_t indY); // only valid for 2d texture data + T getValue(size_t indX, size_t indY, size_t indZ); // only valid for 3d texture data - // Ensure that the host buffer is populated with the current values. In the common case where the user sets data - // and it never changes, this does nothing. However, if the value is being updated directly from GPU memory, this - // mirrors the updates to the host-side buffer. Also, if the value is lazily computed by computeFunc(), it ensures - // that function has been called. - void ensureHostBufferPopulated(); // Copy newData into the host buffer. Errors if newData.size() > capacity() — call resize() or // setCapacity() first if needed. Does not change capacity. Automatically syncs to any allocated @@ -189,71 +195,56 @@ class ManagedBuffer : public ManagedBufferBase, public virtual WeakReferrable { // Returns a full copy of the host data, populating it from device if needed. std::vector getDataCopy(); - // Raw pointer iteration support. Caller must call ensureHostBufferPopulated() before using these. - // A debug-mode check will fire if this precondition is violated. - const T* begin() const; - const T* end() const; - // Single-element read. Caller MUST call ensureHostBufferPopulated() before calling this - // (especially in loops — call it once before the loop, not per element). - // A debug-mode check will fire if this precondition is violated. + // ======================================================================== + // == Low-level data access + // ======================================================================== + + // These functions provide direct access to the underlying host side buffer. + // Callers are generally _required_ to call ensureHostBufferPopulated() before using these, and + // markHostBufferUpdated() after making any changes, to maintain the validity of the host/device mirroring. + + // Ensure that the host buffer is populated with the current values. In the common case where the user sets data + // and it never changes, this does nothing. However, if the value is being updated directly from GPU memory, this + // mirrors the updates to the host-side buffer. Also, if the value is lazily computed by computeFunc(), it ensures + // that function has been called. + void ensureHostBufferPopulated(); + + // If the contents of the host buffer have been updated via the functions below, this must be called once updates are + // finished. It internally handles concerns like reflecting updates to the render buffer. + void markHostBufferUpdated(); + + // Single-element read. Caller MUST call ensureHostBufferPopulated() first. T getHostValue(size_t ind) const; T getHostValue(size_t indX, size_t indY) const; // only valid for 2d texture data T getHostValue(size_t indX, size_t indY, size_t indZ) const; // only valid for 3d texture data - // Single-element write. Caller MUST call ensureHostBufferPopulated() (or resize()) before - // calling this. Automatically syncs to any allocated device buffers and triggers a redraw. - // For bulk writes, prefer setDataHost() to avoid redundant GPU uploads. - // A debug-mode check will fire if the precondition is violated. + // Single-element write. Caller MUST call ensureHostBufferPopulated() first, and MUST call markHostBufferUpdated() + // after all writes are complete. void setHostValue(size_t ind, T val); void setHostValue(size_t indX, size_t indY, T val); // only valid for 2d texture data void setHostValue(size_t indX, size_t indY, size_t indZ, T val); // only valid for 3d texture data - // If the contents of the host buffer have been updated externally, this function MUST be called. - // It internally handles concerns like reflecting updates to the render buffer. - void markHostBufferUpdated(); + // Raw pointer iteration support. Caller MUST call ensureHostBufferPopulated() before using these. + const T* begin() const; + const T* end() const; - // Get the value at index `i`. It may be dynamically fetched from either the cpu-side `data` member or the render - // buffer, depending on where the data currently lives. - // If the data lives only on the device-side render buffer, this function is expensive, so don't call it in a - // loop. - T getValue(size_t ind); - T getValue(size_t indX, size_t indY); // only valid for 2d texture data - T getValue(size_t indX, size_t indY, size_t indZ); // only valid for 3d texture data + // ======================================================================== + // == Misc meta functions and data management + // ======================================================================== // If computeFunc() has already been called to populate the stored data, call it again to recompute the data, and // re-fill the buffer if necessary. This function is only meaningful in the case where `dataGetsComputed = true`. void recomputeIfPopulated(); - bool hasData(); // true if there is valid data on either the host or device - size_t size(); // size of the data (number of entries) - - // Is it an attribute, texture1d, texture2d, etc? - DeviceBufferType getDeviceBufferType(); - - std::string summaryString(); // for debugging - - // ======================================================================== - // == Direct access to the GPU (device-side) render attribute buffer - // ======================================================================== - - // NOTE: this is only for attribute-accessed buffers (DeviceBufferType::Attribute). See the variants below for - // textures. - - // NOTE: This class follows a lazy-sync policy: once the render buffer is allocated, it is kept up to date - // with host-side changes lazily — the actual GPU upload happens in syncToDeviceIfNeeded(), which is called - // by ShaderProgram::draw() just before the draw call. External writes to the GPU buffer (via - // markRenderAttributeBufferUpdated()) invalidate the host copy until ensureHostBufferPopulated() is called. + // Sync host data to the device buffer if not up to date + void syncToDeviceIfNeeded() override; - // Get a reference to the underlying GPU-side attribute buffer - // Once this reference is created, it will always be immediately updated to reflect any external changes to the - // data. (note that if you write to this buffer externally, you MUST call markRenderAttributeBufferUpdated() - // below) - std::shared_ptr getRenderAttributeBuffer(); + // Get an info string for debugging + std::string summaryString() const; - // Tell Polyscope that you wrote updated data into the render buffer. This MUST be called after externally writing - // to the buffer from getRenderBuffer() above. - void markRenderAttributeBufferUpdated(); + // Throw exception if the buffer contains any values which are NaN or Inf as in polyscope::isInvalidValue(). + void checkInvalidValues(); // ======================================================================== // == Indexed views @@ -279,24 +270,36 @@ class ManagedBuffer : public ManagedBufferBase, public virtual WeakReferrable { // copy (which is not cached). std::vector getIndexedView(ManagedBuffer& indices); + // ======================================================================== - // == Direct access to the GPU (device-side) render texture buffer + // == Direct access to the GPU (device-side) render buffers // ======================================================================== - // + + // NOTE: this is only for attribute-accessed buffers (DeviceBufferType::Attribute). See the variants below for + // textures. + + // NOTE: This class follows a lazy-sync policy: once the render buffer is allocated, it is kept up to date + // with host-side changes lazily — the actual GPU upload happens in syncToDeviceIfNeeded(), which is called + // by ShaderProgram::draw() just before the draw call. External writes to the GPU buffer (via + // markRenderAttributeBufferUpdated()) invalidate the host copy until ensureHostBufferPopulated() is called. + + // Get a reference to the underlying GPU-side attribute buffer + // Once this reference is created, it will always be immediately updated to reflect any external changes to the + // data. (note that if you write to this buffer externally, you MUST call markRenderAttributeBufferUpdated() + // below) + std::shared_ptr getRenderAttributeBuffer(); + + // Tell Polyscope that you wrote updated data into the render buffer. This MUST be called after externally writing + // to the buffer from getRenderBuffer() above. + void markRenderAttributeBufferUpdated(); + // NOTE: these follow the same semantics as the attribute version above, but these apply when the buffer is a texture // (DeviceBufferType::Texture1d, etc). - std::shared_ptr getRenderTextureBuffer(); void markRenderTextureBufferUpdated(); - // Sync host data to the device buffer if needed. Called by ShaderProgram::draw() before drawing. - void syncToDeviceIfNeeded() override; - protected: - // == Internal members - // Note: dataGetsComputed, computeFunc, hostBufferValid, deviceBufferValid, managedCapacity, - // renderAttributeBuffer, renderTextureBuffer, deviceBufferType, sizeX/Y/Z all live in ManagedBufferBase. // Override invalidateHostBuffer to also clear the typed data vector. void invalidateHostBuffer(); @@ -318,7 +321,8 @@ class ManagedBuffer : public ManagedBufferBase, public virtual WeakReferrable { std::shared_ptr bufferIndexCopyProgram; private: - template friend class ManagedBuffer; + template + friend class ManagedBuffer; // The raw underlying buffer which this class owns and holds the data. // diff --git a/src/color_image_quantity.cpp b/src/color_image_quantity.cpp index 72971a84..9652a720 100644 --- a/src/color_image_quantity.cpp +++ b/src/color_image_quantity.cpp @@ -16,6 +16,7 @@ ColorImageQuantity::ColorImageQuantity(Structure& parent_, std::string name, siz : ImageQuantity(parent_, name, dimX, dimY, imageOrigin_), colors(this, uniquePrefix() + "colors", std::vector(data_)), isPremultiplied(uniquePrefix() + "isPremultiplied", false) { + colors.setAsType(DeviceBufferType::Texture2d); colors.setTextureSize(dimX, dimY); } diff --git a/src/color_render_image_quantity.cpp b/src/color_render_image_quantity.cpp index 5e0f3e52..2d69bd80 100644 --- a/src/color_render_image_quantity.cpp +++ b/src/color_render_image_quantity.cpp @@ -17,6 +17,7 @@ ColorRenderImageQuantity::ColorRenderImageQuantity(Structure& parent_, std::stri const std::vector& colorsData_, ImageOrigin imageOrigin) : RenderImageQuantityBase(parent_, name, dimX, dimY, depthData, normalData, imageOrigin), colors(this, uniquePrefix() + "colors", std::vector(colorsData_)) { + colors.setAsType(DeviceBufferType::Texture2d); colors.setTextureSize(dimX, dimY); } diff --git a/src/curve_network.cpp b/src/curve_network.cpp index 4d7b0b63..0c45a231 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -398,6 +398,7 @@ void CurveNetwork::computeEdgeCenters() { edgeCenters.setHostValue(iE, p); } + edgeCenters.markHostBufferUpdated(); } void CurveNetwork::refresh() { diff --git a/src/raw_color_alpha_render_image_quantity.cpp b/src/raw_color_alpha_render_image_quantity.cpp index fba8750a..f278e1c5 100644 --- a/src/raw_color_alpha_render_image_quantity.cpp +++ b/src/raw_color_alpha_render_image_quantity.cpp @@ -18,6 +18,7 @@ RawColorAlphaRenderImageQuantity::RawColorAlphaRenderImageQuantity(Structure& pa : RenderImageQuantityBase(parent_, name, dimX, dimY, depthData, std::vector(), imageOrigin), colors(this, uniquePrefix() + "colors", std::vector(colorsData_)), isPremultiplied(uniquePrefix() + "isPremultiplied", false) { + colors.setAsType(DeviceBufferType::Texture2d); colors.setTextureSize(dimX, dimY); } diff --git a/src/raw_color_render_image_quantity.cpp b/src/raw_color_render_image_quantity.cpp index 4d18ce92..b09238a3 100644 --- a/src/raw_color_render_image_quantity.cpp +++ b/src/raw_color_render_image_quantity.cpp @@ -17,6 +17,7 @@ RawColorRenderImageQuantity::RawColorRenderImageQuantity(Structure& parent_, std ImageOrigin imageOrigin) : RenderImageQuantityBase(parent_, name, dimX, dimY, depthData, std::vector(), imageOrigin), colors(this, uniquePrefix() + "colors", std::vector(colorsData_)) { + colors.setAsType(DeviceBufferType::Texture2d); colors.setTextureSize(dimX, dimY); } diff --git a/src/render/managed_buffer.cpp b/src/render/managed_buffer.cpp index 2a60a2ca..0cadf77e 100644 --- a/src/render/managed_buffer.cpp +++ b/src/render/managed_buffer.cpp @@ -104,31 +104,50 @@ void ManagedBuffer::checkInvalidValues() { } template -void ManagedBuffer::setTextureSize(uint32_t sizeX_) { +void ManagedBuffer::setAsType(DeviceBufferType type) { if (deviceBufferType != DeviceBufferType::Attribute) - exception("managed buffer cannot be resized, texture size can only be set once"); + exception("ManagedBuffer " + name + " setAsType(): type has already been set"); + deviceBufferType = type; +} - deviceBufferType = DeviceBufferType::Texture1d; - sizeX = sizeX_; - // managedCapacity and currentSize are set correctly from construction; no fixup needed. +template +void ManagedBuffer::setTextureSize(uint32_t sizeX_) { + checkDeviceBufferTypeIs(DeviceBufferType::Texture1d); + resize(static_cast(sizeX_)); } template void ManagedBuffer::setTextureSize(uint32_t sizeX_, uint32_t sizeY_) { - if (deviceBufferType != DeviceBufferType::Attribute) - exception("managed buffer cannot be resized, texture size can only be set once"); - - deviceBufferType = DeviceBufferType::Texture2d; + checkDeviceBufferTypeIs(DeviceBufferType::Texture2d); + if (sizeX_ == sizeX && sizeY_ == sizeY) return; // no-op + if (managedCapacity > 0) { + // Resize: copy device-side data back to host before touching dimensions or the GPU buffer. + ensureHostBufferPopulated(); + managedCapacity = static_cast(sizeX_) * sizeY_; + currentSize = managedCapacity; + data.resize(managedCapacity); + if (renderTextureBuffer) renderTextureBuffer->resize(sizeX_, sizeY_); + hostBufferValid = true; + deviceBufferValid = false; + } sizeX = sizeX_; sizeY = sizeY_; } template void ManagedBuffer::setTextureSize(uint32_t sizeX_, uint32_t sizeY_, uint32_t sizeZ_) { - if (deviceBufferType != DeviceBufferType::Attribute) - exception("managed buffer cannot be resized, texture size can only be set once"); - - deviceBufferType = DeviceBufferType::Texture3d; + checkDeviceBufferTypeIs(DeviceBufferType::Texture3d); + if (sizeX_ == sizeX && sizeY_ == sizeY && sizeZ_ == sizeZ) return; // no-op + if (managedCapacity > 0) { + // Resize: copy device-side data back to host before touching dimensions or the GPU buffer. + ensureHostBufferPopulated(); + managedCapacity = static_cast(sizeX_) * sizeY_ * sizeZ_; + currentSize = managedCapacity; + data.resize(managedCapacity); + if (renderTextureBuffer) renderTextureBuffer->resize(sizeX_, sizeY_, sizeZ_); + hostBufferValid = true; + deviceBufferValid = false; + } sizeX = sizeX_; sizeY = sizeY_; sizeZ = sizeZ_; @@ -141,14 +160,14 @@ std::array ManagedBuffer::getTextureSize() const { } template -size_t ManagedBuffer::capacity() { +size_t ManagedBuffer::capacity() const { return managedCapacity; } template bool ManagedBuffer::resize(size_t newSize) { if (deviceBufferType == DeviceBufferType::Texture2d || deviceBufferType == DeviceBufferType::Texture3d) - exception("resize() is not valid for 2D/3D texture buffers; use resizeTexture2D() or resizeTexture3D()"); + exception("resize() is not valid for 2D/3D texture buffers; use setTextureSize() instead"); if (newSize > managedCapacity) { // Copy device-side data back to host BEFORE modifying data or invalidating the GPU buffer. @@ -218,54 +237,6 @@ void ManagedBuffer::setCapacity(size_t newCapacity) { deviceBufferValid = false; } -template -bool ManagedBuffer::resizeTexture2D(uint32_t newSizeX, uint32_t newSizeY) { - checkDeviceBufferTypeIs(DeviceBufferType::Texture2d); - - if (newSizeX == sizeX && newSizeY == sizeY) return false; // no-op - - // Before invalidating the GPU buffer, copy any device-side changes back to the host. - ensureHostBufferPopulated(); - - sizeX = newSizeX; - sizeY = newSizeY; - managedCapacity = static_cast(sizeX) * sizeY; - currentSize = managedCapacity; - data.resize(managedCapacity); - - if (renderTextureBuffer) { - renderTextureBuffer->resize(sizeX, sizeY); - } - hostBufferValid = true; - deviceBufferValid = false; - - return true; -} - -template -bool ManagedBuffer::resizeTexture3D(uint32_t newSizeX, uint32_t newSizeY, uint32_t newSizeZ) { - checkDeviceBufferTypeIs(DeviceBufferType::Texture3d); - - if (newSizeX == sizeX && newSizeY == sizeY && newSizeZ == sizeZ) return false; // no-op - - // Before invalidating the GPU buffer, copy any device-side changes back to the host. - ensureHostBufferPopulated(); - - sizeX = newSizeX; - sizeY = newSizeY; - sizeZ = newSizeZ; - managedCapacity = static_cast(sizeX) * sizeY * sizeZ; - currentSize = managedCapacity; - data.resize(managedCapacity); - - if (renderTextureBuffer) { - renderTextureBuffer->resize(sizeX, sizeY, sizeZ); - } - hostBufferValid = true; - deviceBufferValid = false; - - return true; -} template void ManagedBuffer::ensureHostBufferPopulated() { @@ -352,45 +323,53 @@ const T* ManagedBuffer::end() const { template T ManagedBuffer::getHostValue(size_t ind) const { #ifndef NDEBUG - if (!hostBufferValid) - exception("ManagedBuffer " + name + " getHostValue() called without ensureHostBufferPopulated()"); + if (!hostBufferValid) exception("ManagedBuffer " + name + " getHostValue() called without ensureHostBufferPopulated()"); #endif return data[ind]; } template T ManagedBuffer::getHostValue(size_t indX, size_t indY) const { +#ifndef NDEBUG + if (!hostBufferValid) exception("ManagedBuffer " + name + " getHostValue() called without ensureHostBufferPopulated()"); checkDeviceBufferTypeIs(DeviceBufferType::Texture2d); +#endif return getHostValue(sizeY * indX + indY); } template T ManagedBuffer::getHostValue(size_t indX, size_t indY, size_t indZ) const { +#ifndef NDEBUG + if (!hostBufferValid) exception("ManagedBuffer " + name + " getHostValue() called without ensureHostBufferPopulated()"); checkDeviceBufferTypeIs(DeviceBufferType::Texture3d); +#endif return getHostValue(sizeZ * sizeY * indX + sizeZ * indY + indZ); } template void ManagedBuffer::setHostValue(size_t ind, T val) { #ifndef NDEBUG - if (!hostBufferValid) - exception("ManagedBuffer " + name + " setHostValue() called without ensureHostBufferPopulated()"); + if (!hostBufferValid) exception("ManagedBuffer " + name + " setHostValue() called without ensureHostBufferPopulated()"); #endif data[ind] = val; - deviceBufferValid = false; - invalidateIndexedViews(); - requestRedraw(); + // NOTE: intentionally no side-effects here. Caller must call markHostBufferUpdated() after all writes are done. } template void ManagedBuffer::setHostValue(size_t indX, size_t indY, T val) { +#ifndef NDEBUG + if (!hostBufferValid) exception("ManagedBuffer " + name + " setHostValue() called without ensureHostBufferPopulated()"); checkDeviceBufferTypeIs(DeviceBufferType::Texture2d); +#endif setHostValue(sizeY * indX + indY, val); } template void ManagedBuffer::setHostValue(size_t indX, size_t indY, size_t indZ, T val) { +#ifndef NDEBUG + if (!hostBufferValid) exception("ManagedBuffer " + name + " setHostValue() called without ensureHostBufferPopulated()"); checkDeviceBufferTypeIs(DeviceBufferType::Texture3d); +#endif setHostValue(sizeZ * sizeY * indX + sizeZ * indY + indZ, val); } @@ -492,25 +471,17 @@ T ManagedBuffer::getValue(size_t indX, size_t indY, size_t indZ) { } template -size_t ManagedBuffer::size() { +size_t ManagedBuffer::size() const { return currentSize; } template -bool ManagedBuffer::hasData() { - - if (hostBufferValid) return true; - if (deviceBufferValid) return true; - return false; -} - -template -DeviceBufferType ManagedBuffer::getDeviceBufferType() { +DeviceBufferType ManagedBuffer::getDeviceBufferType() const { return deviceBufferType; } template -std::string ManagedBuffer::summaryString() { +std::string ManagedBuffer::summaryString() const { std::string str = ""; diff --git a/src/render_image_quantity_base.cpp b/src/render_image_quantity_base.cpp index 0420fa3d..fb57174b 100644 --- a/src/render_image_quantity_base.cpp +++ b/src/render_image_quantity_base.cpp @@ -18,8 +18,10 @@ RenderImageQuantityBase::RenderImageQuantityBase(Structure& parent_, std::string hasNormals(normalData_.size() > 0), imageOrigin(imageOrigin_), material(uniquePrefix() + "material", "clay"), transparency(uniquePrefix() + "transparency", 1.0), allowFullscreenCompositing(uniquePrefix() + "allowFullscreenCompositing", false) { + depths.setAsType(DeviceBufferType::Texture2d); depths.setTextureSize(dimX, dimY); if (hasNormals) { + normals.setAsType(DeviceBufferType::Texture2d); normals.setTextureSize(dimX, dimY); } diff --git a/src/scalar_image_quantity.cpp b/src/scalar_image_quantity.cpp index 4ad7d558..90028cde 100644 --- a/src/scalar_image_quantity.cpp +++ b/src/scalar_image_quantity.cpp @@ -13,6 +13,7 @@ namespace polyscope { ScalarImageQuantity::ScalarImageQuantity(Structure& parent_, std::string name, size_t dimX, size_t dimY, const std::vector& data_, ImageOrigin imageOrigin_, DataType dataType_) : ImageQuantity(parent_, name, dimX, dimY, imageOrigin_), ScalarQuantity(*this, data_, dataType_) { + values.setAsType(DeviceBufferType::Texture2d); values.setTextureSize(dimX, dimY); } diff --git a/src/scalar_render_image_quantity.cpp b/src/scalar_render_image_quantity.cpp index 1008cfa3..eb843b96 100644 --- a/src/scalar_render_image_quantity.cpp +++ b/src/scalar_render_image_quantity.cpp @@ -17,6 +17,7 @@ ScalarRenderImageQuantity::ScalarRenderImageQuantity(Structure& parent_, std::st DataType dataType_) : RenderImageQuantityBase(parent_, name, dimX, dimY, depthData, normalData, imageOrigin), ScalarQuantity(*this, scalarData_, dataType_) { + values.setAsType(DeviceBufferType::Texture2d); values.setTextureSize(dimX, dimY); } diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index 51282de3..b0467136 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -97,8 +97,7 @@ void SparseVolumeGrid::computeCellPositions() { cellIndices.setHostValue(i, ijk); } - // cellPositions.markHostBufferUpdated() is called by recomputeIfPopulated() after this callback. - // cellIndices is a side-effect buffer, so mark it updated explicitly. + cellPositions.markHostBufferUpdated(); cellIndices.markHostBufferUpdated(); } diff --git a/src/surface_color_quantity.cpp b/src/surface_color_quantity.cpp index 7955542d..4c5336e1 100644 --- a/src/surface_color_quantity.cpp +++ b/src/surface_color_quantity.cpp @@ -155,6 +155,7 @@ SurfaceTextureColorQuantity::SurfaceTextureColorQuantity(std::string name, Surfa ImageOrigin origin_) : SurfaceColorQuantity(name, mesh_, "texture", colorValues_), TextureMapQuantity(*this, dimX_, dimY_, origin_), param(param_) { + colors.setAsType(DeviceBufferType::Texture2d); colors.setTextureSize(dimX, dimY); } diff --git a/src/surface_mesh.cpp b/src/surface_mesh.cpp index 4a64cbf1..6954b5a5 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -251,6 +251,7 @@ void SurfaceMesh::computeTriangleAllEdgeInds() { } } + triangleAllEdgeInds.markHostBufferUpdated(); nEdgesCount = psEdgeInd; } @@ -322,6 +323,7 @@ void SurfaceMesh::computeTriangleCornerInds() { } } + triangleCornerInds.markHostBufferUpdated(); } void SurfaceMesh::computeTriangleAllVertexInds() { @@ -349,6 +351,7 @@ void SurfaceMesh::computeTriangleAllVertexInds() { } } + triangleAllVertexInds.markHostBufferUpdated(); } void SurfaceMesh::computeTriangleAllHalfedgeInds() { @@ -390,6 +393,7 @@ void SurfaceMesh::computeTriangleAllHalfedgeInds() { } } + triangleAllHalfedgeInds.markHostBufferUpdated(); } void SurfaceMesh::computeTriangleAllCornerInds() { @@ -424,6 +428,7 @@ void SurfaceMesh::computeTriangleAllCornerInds() { } } + triangleAllCornerInds.markHostBufferUpdated(); } @@ -462,6 +467,7 @@ void SurfaceMesh::computeFaceNormals() { faceNormals.setHostValue(iF, fN); } + faceNormals.markHostBufferUpdated(); } void SurfaceMesh::computeFaceCenters() { @@ -482,6 +488,7 @@ void SurfaceMesh::computeFaceCenters() { faceCenters.setHostValue(iF, faceCenter); } + faceCenters.markHostBufferUpdated(); } void SurfaceMesh::computeFaceAreas() { @@ -516,6 +523,7 @@ void SurfaceMesh::computeFaceAreas() { faceAreas.setHostValue(iF, fA); } + faceAreas.markHostBufferUpdated(); } @@ -546,6 +554,7 @@ void SurfaceMesh::computeVertexNormals() { vertexNormals.setHostValue(iV, glm::normalize(vertexNormals.getHostValue(iV))); } + vertexNormals.markHostBufferUpdated(); } void SurfaceMesh::computeVertexAreas() { @@ -567,6 +576,7 @@ void SurfaceMesh::computeVertexAreas() { } } + vertexAreas.markHostBufferUpdated(); } void SurfaceMesh::computeDefaultFaceTangentBasisX() { @@ -597,6 +607,7 @@ void SurfaceMesh::computeDefaultFaceTangentBasisX() { defaultFaceTangentBasisX.setHostValue(iF, basisX); } + defaultFaceTangentBasisX.markHostBufferUpdated(); } void SurfaceMesh::computeDefaultFaceTangentBasisY() { @@ -627,6 +638,7 @@ void SurfaceMesh::computeDefaultFaceTangentBasisY() { defaultFaceTangentBasisY.setHostValue(iF, basisY); } + defaultFaceTangentBasisY.markHostBufferUpdated(); } // === Edge Lengths === diff --git a/src/surface_scalar_quantity.cpp b/src/surface_scalar_quantity.cpp index f11b866d..12ff80cd 100644 --- a/src/surface_scalar_quantity.cpp +++ b/src/surface_scalar_quantity.cpp @@ -325,6 +325,7 @@ SurfaceTextureScalarQuantity::SurfaceTextureScalarQuantity(std::string name, Sur ImageOrigin origin_, DataType dataType_) : SurfaceScalarQuantity(name, mesh_, "vertex", values_, dataType_), TextureMapQuantity(*this, dimX_, dimY_, origin_), param(param_) { + values.setAsType(DeviceBufferType::Texture2d); values.setTextureSize(dimX, dimY); if (dataType == DataType::CATEGORICAL) { diff --git a/src/volume_grid_scalar_quantity.cpp b/src/volume_grid_scalar_quantity.cpp index 33645e30..1da4eabe 100644 --- a/src/volume_grid_scalar_quantity.cpp +++ b/src/volume_grid_scalar_quantity.cpp @@ -19,6 +19,7 @@ VolumeGridNodeScalarQuantity::VolumeGridNodeScalarQuantity(std::string name, Vol isosurfaceColor(uniquePrefix() + "isosurfaceColor", getNextUniqueColor()), slicePlanesAffectIsosurface(uniquePrefix() + "slicePlanesAffectIsosurface", false) { + values.setAsType(DeviceBufferType::Texture3d); values.setTextureSize(parent.getGridNodeDim().x, parent.getGridNodeDim().y, parent.getGridNodeDim().z); } @@ -283,6 +284,7 @@ VolumeGridCellScalarQuantity::VolumeGridCellScalarQuantity(std::string name, Vol : VolumeGridQuantity(name, grid_, true), ScalarQuantity(*this, values_, dataType_), gridcubeVizEnabled(parent.uniquePrefix() + "#" + name + "#gridcubeVizEnabled", true) { + values.setAsType(DeviceBufferType::Texture3d); values.setTextureSize(parent.getGridCellDim().x, parent.getGridCellDim().y, parent.getGridCellDim().z); } diff --git a/src/volume_mesh.cpp b/src/volume_mesh.cpp index 9ca27b8f..73792565 100644 --- a/src/volume_mesh.cpp +++ b/src/volume_mesh.cpp @@ -967,6 +967,7 @@ void VolumeMesh::computeFaceNormals() { } } + faceNormals.markHostBufferUpdated(); } @@ -993,6 +994,7 @@ void VolumeMesh::computeCellCenters() { cellCenters.setHostValue(iC, center); } + cellCenters.markHostBufferUpdated(); }