From 8c5f64dbada07ebc1caa6165fe054a2344c1d8f3 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 3 May 2026 18:08:48 -0700 Subject: [PATCH 01/17] 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 02/17] 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 03/17] 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 04/17] 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 05/17] 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 06/17] 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 07/17] 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 08/17] 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(); } From bf6b983036b49b372c8d629b449038d5e6363fa2 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 25 Mar 2026 16:54:06 -0500 Subject: [PATCH 09/17] initial work on simple triangle vertex and face quantities --- examples/demo-app/demo_app.cpp | 6 +- include/polyscope/simple_triangle_mesh.h | 23 +++++++ include/polyscope/simple_triangle_mesh.ipp | 18 +++++ .../simple_triangle_mesh_color_quantity.h | 30 ++++++++ .../polyscope/simple_triangle_mesh_quantity.h | 20 ++++++ .../simple_triangle_mesh_scalar_quantity.h | 33 +++++++++ src/CMakeLists.txt | 6 ++ src/simple_triangle_mesh.cpp | 22 ++++++ src/simple_triangle_mesh_color_quantity.cpp | 68 ++++++++++++++++++ src/simple_triangle_mesh_quantity.cpp | 13 ++++ src/simple_triangle_mesh_scalar_quantity.cpp | 69 +++++++++++++++++++ test/src/surface_mesh_test.cpp | 36 ++++++++++ 12 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 include/polyscope/simple_triangle_mesh_color_quantity.h create mode 100644 include/polyscope/simple_triangle_mesh_quantity.h create mode 100644 include/polyscope/simple_triangle_mesh_scalar_quantity.h create mode 100644 src/simple_triangle_mesh_color_quantity.cpp create mode 100644 src/simple_triangle_mesh_quantity.cpp create mode 100644 src/simple_triangle_mesh_scalar_quantity.cpp diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index afe26351..566abcc2 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -121,7 +121,7 @@ void processFileOBJ(std::string filename) { } auto psMesh = polyscope::registerSurfaceMesh(niceName, vertexPositionsGLM, faceIndices); - auto psSimpleMesh = polyscope::registerSimpleTriangleMesh(niceName, vertexPositionsGLM, faceIndices); + auto psSimpleMesh = polyscope::registerSimpleTriangleMesh(niceName + " (simple)", vertexPositionsGLM, faceIndices); psSimpleMesh->setEnabled(false); // Useful data @@ -154,6 +154,10 @@ void processFileOBJ(std::string filename) { polyscope::getSurfaceMesh(niceName)->addVertexScalarQuantity("categorical vert", valCat, polyscope::DataType::CATEGORICAL); + // Simple triangle mesh quantities + psSimpleMesh->addVertexScalarQuantity("cY", valY); + psSimpleMesh->addVertexColorQuantity("vColor", randColor); + polyscope::getSurfaceMesh(niceName)->addVertexDistanceQuantity("cY_dist", valY); polyscope::getSurfaceMesh(niceName)->addVertexSignedDistanceQuantity("cY_signeddist", valY); diff --git a/include/polyscope/simple_triangle_mesh.h b/include/polyscope/simple_triangle_mesh.h index 181d1c1f..ad600f7c 100644 --- a/include/polyscope/simple_triangle_mesh.h +++ b/include/polyscope/simple_triangle_mesh.h @@ -8,8 +8,13 @@ #include "polyscope/render/engine.h" #include "polyscope/render/managed_buffer.h" #include "polyscope/scaled_value.h" +#include "polyscope/standardize_data_array.h" #include "polyscope/structure.h" +#include "polyscope/simple_triangle_mesh_color_quantity.h" +#include "polyscope/simple_triangle_mesh_quantity.h" +#include "polyscope/simple_triangle_mesh_scalar_quantity.h" + #include namespace polyscope { @@ -18,6 +23,8 @@ namespace polyscope { class SimpleTriangleMesh; // Forward declare quantity types +class SimpleTriangleMeshScalarQuantity; +class SimpleTriangleMeshColorQuantity; struct SimpleTriangleMeshPickResult { // this does nothing for now, just matching pattern from other structures @@ -50,8 +57,20 @@ class SimpleTriangleMesh : public Structure { render::ManagedBuffer vertices; render::ManagedBuffer faces; + size_t nVertices() { return vertices.size(); } + size_t nFaces() { return faces.size(); } + // === Quantities + // Scalars + template + SimpleTriangleMeshScalarQuantity* addVertexScalarQuantity(std::string name, const T& values, + DataType type = DataType::STANDARD); + + // Colors + template + SimpleTriangleMeshColorQuantity* addVertexColorQuantity(std::string name, const T& values); + // === Mutate template @@ -111,6 +130,10 @@ class SimpleTriangleMesh : public Structure { void setPickUniforms(render::ShaderProgram& p); // === Quantity adder implementations + SimpleTriangleMeshScalarQuantity* addVertexScalarQuantityImpl(std::string name, const std::vector& data, + DataType type); + SimpleTriangleMeshColorQuantity* addVertexColorQuantityImpl(std::string name, + const std::vector& colors); // == Picking related things size_t pickStart; diff --git a/include/polyscope/simple_triangle_mesh.ipp b/include/polyscope/simple_triangle_mesh.ipp index ca10bc2c..6b49b02f 100644 --- a/include/polyscope/simple_triangle_mesh.ipp +++ b/include/polyscope/simple_triangle_mesh.ipp @@ -49,6 +49,24 @@ void SimpleTriangleMesh::update(const V& newPositions, const F& newFaces) { faces.markHostBufferUpdated(); } +// ===================================================== +// ============== Quantities +// ===================================================== + +template +SimpleTriangleMeshScalarQuantity* SimpleTriangleMesh::addVertexScalarQuantity(std::string name, const T& values, + DataType type) { + validateSize(values, nVertices(), "vertex scalar quantity " + name); + return addVertexScalarQuantityImpl(name, standardizeArray(values), type); +} + +template +SimpleTriangleMeshColorQuantity* SimpleTriangleMesh::addVertexColorQuantity(std::string name, const T& values) { + validateSize(values, nVertices(), "vertex color quantity " + name); + return addVertexColorQuantityImpl(name, standardizeVectorArray(values)); +} + + // Shorthand to get a mesh from polyscope inline SimpleTriangleMesh* getSimpleTriangleMesh(std::string name) { return dynamic_cast(getStructure(SimpleTriangleMesh::structureTypeName, name)); diff --git a/include/polyscope/simple_triangle_mesh_color_quantity.h b/include/polyscope/simple_triangle_mesh_color_quantity.h new file mode 100644 index 00000000..2383511e --- /dev/null +++ b/include/polyscope/simple_triangle_mesh_color_quantity.h @@ -0,0 +1,30 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include "polyscope/color_quantity.h" +#include "polyscope/simple_triangle_mesh_quantity.h" + +#include + +namespace polyscope { + +class SimpleTriangleMeshColorQuantity : public SimpleTriangleMeshQuantity, + public ColorQuantity { +public: + SimpleTriangleMeshColorQuantity(std::string name, const std::vector& colors, std::string definedOn, + SimpleTriangleMesh& mesh); + + virtual void draw() override; + virtual void buildCustomUI() override; + virtual void refresh() override; + virtual std::string niceName() override; + + std::string definedOn; + +protected: + void createProgram(); + std::shared_ptr program; +}; + +} // namespace polyscope diff --git a/include/polyscope/simple_triangle_mesh_quantity.h b/include/polyscope/simple_triangle_mesh_quantity.h new file mode 100644 index 00000000..6df4e0e4 --- /dev/null +++ b/include/polyscope/simple_triangle_mesh_quantity.h @@ -0,0 +1,20 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include "polyscope/quantity.h" +#include "polyscope/structure.h" + +namespace polyscope { + +class SimpleTriangleMesh; + +class SimpleTriangleMeshQuantity : public Quantity { +public: + SimpleTriangleMeshQuantity(std::string name, SimpleTriangleMesh& parentStructure, bool dominates = false); + virtual ~SimpleTriangleMeshQuantity(){}; + + SimpleTriangleMesh& parent; // shadows and hides the generic member in Quantity +}; + +} // namespace polyscope diff --git a/include/polyscope/simple_triangle_mesh_scalar_quantity.h b/include/polyscope/simple_triangle_mesh_scalar_quantity.h new file mode 100644 index 00000000..729b24a7 --- /dev/null +++ b/include/polyscope/simple_triangle_mesh_scalar_quantity.h @@ -0,0 +1,33 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include "polyscope/affine_remapper.h" +#include "polyscope/render/color_maps.h" +#include "polyscope/scalar_quantity.h" +#include "polyscope/simple_triangle_mesh_quantity.h" + +#include + +namespace polyscope { + +class SimpleTriangleMeshScalarQuantity : public SimpleTriangleMeshQuantity, + public ScalarQuantity { +public: + SimpleTriangleMeshScalarQuantity(std::string name, const std::vector& values, std::string definedOn, + SimpleTriangleMesh& mesh, DataType dataType); + + virtual void draw() override; + virtual void buildCustomUI() override; + virtual void refresh() override; + virtual std::string niceName() override; + + +protected: + std::string definedOn; + std::shared_ptr program; + + void createProgram(); +}; + +} // namespace polyscope diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8b4eca3b..da32e3a5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -235,6 +235,9 @@ SET(SRCS # Simple triangle mesh simple_triangle_mesh.cpp + simple_triangle_mesh_quantity.cpp + simple_triangle_mesh_scalar_quantity.cpp + simple_triangle_mesh_color_quantity.cpp # Floating quantities floating_quantity_structure.cpp @@ -337,6 +340,9 @@ SET(HEADERS ${INCLUDE_ROOT}/screenshot.h ${INCLUDE_ROOT}/simple_triangle_mesh.h ${INCLUDE_ROOT}/simple_triangle_mesh.ipp + ${INCLUDE_ROOT}/simple_triangle_mesh_quantity.h + ${INCLUDE_ROOT}/simple_triangle_mesh_scalar_quantity.h + ${INCLUDE_ROOT}/simple_triangle_mesh_color_quantity.h ${INCLUDE_ROOT}/slice_plane.h ${INCLUDE_ROOT}/standardize_data_array.h ${INCLUDE_ROOT}/structure.h diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index 6151c395..683e2631 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -5,6 +5,8 @@ #include "polyscope/pick.h" #include "polyscope/polyscope.h" #include "polyscope/render/engine.h" +#include "polyscope/simple_triangle_mesh_color_quantity.h" +#include "polyscope/simple_triangle_mesh_scalar_quantity.h" #include "imgui.h" @@ -346,4 +348,24 @@ SimpleTriangleMesh* SimpleTriangleMesh::setBackFaceColor(glm::vec3 val) { glm::vec3 SimpleTriangleMesh::getBackFaceColor() { return backFaceColor.get(); } +// === Quantity adder implementations + +SimpleTriangleMeshScalarQuantity* SimpleTriangleMesh::addVertexScalarQuantityImpl(std::string name, + const std::vector& data, + DataType type) { + checkForQuantityWithNameAndDeleteOrError(name); + SimpleTriangleMeshScalarQuantity* q = new SimpleTriangleMeshScalarQuantity(name, data, "vertex", *this, type); + addQuantity(q); + return q; +} + +SimpleTriangleMeshColorQuantity* SimpleTriangleMesh::addVertexColorQuantityImpl(std::string name, + const std::vector& colors) { + checkForQuantityWithNameAndDeleteOrError(name); + SimpleTriangleMeshColorQuantity* q = new SimpleTriangleMeshColorQuantity(name, colors, "vertex", *this); + addQuantity(q); + return q; +} + + } // namespace polyscope diff --git a/src/simple_triangle_mesh_color_quantity.cpp b/src/simple_triangle_mesh_color_quantity.cpp new file mode 100644 index 00000000..3e6430ef --- /dev/null +++ b/src/simple_triangle_mesh_color_quantity.cpp @@ -0,0 +1,68 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#include "polyscope/simple_triangle_mesh_color_quantity.h" + +#include "polyscope/polyscope.h" +#include "polyscope/simple_triangle_mesh.h" + +#include "imgui.h" + +namespace polyscope { + +SimpleTriangleMeshColorQuantity::SimpleTriangleMeshColorQuantity(std::string name, + const std::vector& colors_, + std::string definedOn_, + SimpleTriangleMesh& mesh_) + : SimpleTriangleMeshQuantity(name, mesh_, true), ColorQuantity(*this, colors_), definedOn(definedOn_) {} + +void SimpleTriangleMeshColorQuantity::draw() { + if (!isEnabled()) return; + + if (program == nullptr) createProgram(); + + parent.setStructureUniforms(*program); + parent.setSimpleTriangleMeshUniforms(*program); + setColorUniforms(*program); + render::engine->setMaterialUniforms(*program, parent.getMaterial()); + + program->draw(); +} + +void SimpleTriangleMeshColorQuantity::buildCustomUI() { + ImGui::SameLine(); + if (ImGui::Button("Options")) { + ImGui::OpenPopup("OptionsPopup"); + } + if (ImGui::BeginPopup("OptionsPopup")) { + buildColorOptionsUI(); + ImGui::EndPopup(); + } + buildColorUI(); +} + +void SimpleTriangleMeshColorQuantity::createProgram() { + // clang-format off + program = render::engine->requestShader("SIMPLE_MESH", + render::engine->addMaterialRules(parent.getMaterial(), + addColorRules( + parent.addSimpleTriangleMeshRules( + {"MESH_PROPAGATE_COLOR", "SHADE_COLOR", "COMPUTE_SHADE_NORMAL_FROM_POSITION", "PROJ_AND_INV_PROJ_MAT"} + ) + ) + ) + ); + // clang-format on + + parent.setSimpleTriangleMeshProgramGeometryAttributes(*program); + program->setAttribute("a_color", colors.getRenderAttributeBuffer()); + render::engine->setMaterial(*program, parent.getMaterial()); +} + +void SimpleTriangleMeshColorQuantity::refresh() { + program.reset(); + Quantity::refresh(); +} + +std::string SimpleTriangleMeshColorQuantity::niceName() { return name + " (" + definedOn + " color)"; } + +} // namespace polyscope diff --git a/src/simple_triangle_mesh_quantity.cpp b/src/simple_triangle_mesh_quantity.cpp new file mode 100644 index 00000000..aa917224 --- /dev/null +++ b/src/simple_triangle_mesh_quantity.cpp @@ -0,0 +1,13 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#include "polyscope/simple_triangle_mesh_quantity.h" + +#include "polyscope/simple_triangle_mesh.h" + +namespace polyscope { + +SimpleTriangleMeshQuantity::SimpleTriangleMeshQuantity(std::string name, SimpleTriangleMesh& parentStructure, + bool dominates) + : Quantity(name, parentStructure, dominates), parent(parentStructure) {} + +} // namespace polyscope diff --git a/src/simple_triangle_mesh_scalar_quantity.cpp b/src/simple_triangle_mesh_scalar_quantity.cpp new file mode 100644 index 00000000..1e48ce04 --- /dev/null +++ b/src/simple_triangle_mesh_scalar_quantity.cpp @@ -0,0 +1,69 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#include "polyscope/simple_triangle_mesh_scalar_quantity.h" + +#include "polyscope/polyscope.h" +#include "polyscope/simple_triangle_mesh.h" + +#include "imgui.h" + +namespace polyscope { + +SimpleTriangleMeshScalarQuantity::SimpleTriangleMeshScalarQuantity(std::string name, const std::vector& values_, + std::string definedOn_, + SimpleTriangleMesh& mesh_, DataType dataType_) + : SimpleTriangleMeshQuantity(name, mesh_, true), ScalarQuantity(*this, values_, dataType_), + definedOn(definedOn_) {} + +void SimpleTriangleMeshScalarQuantity::draw() { + if (!isEnabled()) return; + + if (program == nullptr) createProgram(); + + parent.setStructureUniforms(*program); + parent.setSimpleTriangleMeshUniforms(*program); + setScalarUniforms(*program); + render::engine->setMaterialUniforms(*program, parent.getMaterial()); + + program->draw(); +} + +void SimpleTriangleMeshScalarQuantity::buildCustomUI() { + ImGui::SameLine(); + if (ImGui::Button("Options")) { + ImGui::OpenPopup("OptionsPopup"); + } + if (ImGui::BeginPopup("OptionsPopup")) { + buildScalarOptionsUI(); + ImGui::EndPopup(); + } + buildScalarUI(); +} + +void SimpleTriangleMeshScalarQuantity::createProgram() { + // clang-format off + program = render::engine->requestShader("SIMPLE_MESH", + render::engine->addMaterialRules(parent.getMaterial(), + parent.addSimpleTriangleMeshRules( + addScalarRules( + {"MESH_PROPAGATE_VALUE", "COMPUTE_SHADE_NORMAL_FROM_POSITION", "PROJ_AND_INV_PROJ_MAT"} + ) + ) + ) + ); + // clang-format on + + parent.setSimpleTriangleMeshProgramGeometryAttributes(*program); + program->setAttribute("a_value", values.getRenderAttributeBuffer()); + program->setTextureFromColormap("t_colormap", cMap.get()); + render::engine->setMaterial(*program, parent.getMaterial()); +} + +void SimpleTriangleMeshScalarQuantity::refresh() { + program.reset(); + Quantity::refresh(); +} + +std::string SimpleTriangleMeshScalarQuantity::niceName() { return name + " (" + definedOn + " scalar)"; } + +} // namespace polyscope diff --git a/test/src/surface_mesh_test.cpp b/test/src/surface_mesh_test.cpp index 16b2ca21..bbef8570 100644 --- a/test/src/surface_mesh_test.cpp +++ b/test/src/surface_mesh_test.cpp @@ -665,3 +665,39 @@ TEST_F(PolyscopeTest, SimpleTriangleMesUpdate) { polyscope::removeAllStructures(); } + +TEST_F(PolyscopeTest, SimpleTriangleMeshVertexScalar) { + auto psMesh = registerSimpleTriangleMesh(); + std::vector vScalar(psMesh->nVertices(), 7.); + auto q1 = psMesh->addVertexScalarQuantity("vScalar", vScalar); + q1->setEnabled(true); + polyscope::show(3); + + q1->updateData(vScalar); + polyscope::show(3); + + polyscope::removeAllStructures(); +} + +TEST_F(PolyscopeTest, SimpleTriangleMeshVertexScalarCategorical) { + auto psMesh = registerSimpleTriangleMesh(); + std::vector vScalar(psMesh->nVertices(), 2.); + auto q1 = psMesh->addVertexScalarQuantity("vScalar", vScalar, polyscope::DataType::CATEGORICAL); + q1->setEnabled(true); + polyscope::show(3); + + polyscope::removeAllStructures(); +} + +TEST_F(PolyscopeTest, SimpleTriangleMeshVertexColor) { + auto psMesh = registerSimpleTriangleMesh(); + std::vector vColors(psMesh->nVertices(), glm::vec3{.2, .3, .4}); + auto q1 = psMesh->addVertexColorQuantity("vColor", vColors); + q1->setEnabled(true); + polyscope::show(3); + + q1->updateData(vColors); + polyscope::show(3); + + polyscope::removeAllStructures(); +} From 07b8471cf34290087ab4d9568f0ba506266a7752 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Wed, 25 Mar 2026 16:59:41 -0500 Subject: [PATCH 10/17] clean up unfiorms --- src/simple_triangle_mesh.cpp | 7 +++++-- src/simple_triangle_mesh_color_quantity.cpp | 2 +- src/simple_triangle_mesh_scalar_quantity.cpp | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index 683e2631..3b4d2491 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -187,7 +187,7 @@ void SimpleTriangleMesh::ensureRenderProgramPrepared() { render::engine->addMaterialRules(getMaterial(), addSimpleTriangleMeshRules( { - "SHADE_BASECOLOR", "COMPUTE_SHADE_NORMAL_FROM_POSITION", "PROJ_AND_INV_PROJ_MAT" + "SHADE_BASECOLOR" } ) ) @@ -208,7 +208,7 @@ void SimpleTriangleMesh::ensurePickProgramPrepared() { pickProgram = render::engine->requestShader("SIMPLE_MESH", addSimpleTriangleMeshRules( { - "SHADECOLOR_FROM_UNIFORM", "COMPUTE_SHADE_NORMAL_FROM_POSITION", "PROJ_AND_INV_PROJ_MAT" + "SHADECOLOR_FROM_UNIFORM" } , false), render::ShaderReplacementDefaults::Pick ); @@ -228,6 +228,9 @@ std::vector SimpleTriangleMesh::addSimpleTriangleMeshRules(std::vec initRules = addStructureRules(initRules); + initRules.push_back("COMPUTE_SHADE_NORMAL_FROM_POSITION"); + initRules.push_back("PROJ_AND_INV_PROJ_MAT"); + if (withSurfaceShade) { // rules that only get used when we're shading the surface of the mesh diff --git a/src/simple_triangle_mesh_color_quantity.cpp b/src/simple_triangle_mesh_color_quantity.cpp index 3e6430ef..2342c204 100644 --- a/src/simple_triangle_mesh_color_quantity.cpp +++ b/src/simple_triangle_mesh_color_quantity.cpp @@ -46,7 +46,7 @@ void SimpleTriangleMeshColorQuantity::createProgram() { render::engine->addMaterialRules(parent.getMaterial(), addColorRules( parent.addSimpleTriangleMeshRules( - {"MESH_PROPAGATE_COLOR", "SHADE_COLOR", "COMPUTE_SHADE_NORMAL_FROM_POSITION", "PROJ_AND_INV_PROJ_MAT"} + {"MESH_PROPAGATE_COLOR", "SHADE_COLOR"} ) ) ) diff --git a/src/simple_triangle_mesh_scalar_quantity.cpp b/src/simple_triangle_mesh_scalar_quantity.cpp index 1e48ce04..bc3026d8 100644 --- a/src/simple_triangle_mesh_scalar_quantity.cpp +++ b/src/simple_triangle_mesh_scalar_quantity.cpp @@ -46,7 +46,7 @@ void SimpleTriangleMeshScalarQuantity::createProgram() { render::engine->addMaterialRules(parent.getMaterial(), parent.addSimpleTriangleMeshRules( addScalarRules( - {"MESH_PROPAGATE_VALUE", "COMPUTE_SHADE_NORMAL_FROM_POSITION", "PROJ_AND_INV_PROJ_MAT"} + {"MESH_PROPAGATE_VALUE"} ) ) ) From d8541b618af6857022b1d2b7fa64f8e92a5baf55 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 5 Apr 2026 09:43:48 -0700 Subject: [PATCH 11/17] quantities on simple triangle meshes --- examples/demo-app/demo_app.cpp | 3 + .../opengl/shaders/surface_mesh_shaders.h | 2 + include/polyscope/simple_triangle_mesh.h | 28 ++++++-- include/polyscope/simple_triangle_mesh.ipp | 19 +++++- .../simple_triangle_mesh_color_quantity.h | 32 +++++++++- .../simple_triangle_mesh_scalar_quantity.h | 31 ++++++++- src/render/mock_opengl/mock_gl_engine.cpp | 2 + src/render/opengl/gl_engine.cpp | 2 + .../opengl/shaders/surface_mesh_shaders.cpp | 34 ++++++++++ src/simple_triangle_mesh.cpp | 29 +++++++-- src/simple_triangle_mesh_color_quantity.cpp | 54 ++++++++++++++-- src/simple_triangle_mesh_scalar_quantity.cpp | 64 ++++++++++++++++--- test/src/surface_mesh_test.cpp | 26 ++++++++ 13 files changed, 291 insertions(+), 35 deletions(-) diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index 566abcc2..8a2cae4c 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -190,6 +190,9 @@ void processFileOBJ(std::string filename) { polyscope::getSurfaceMesh(niceName)->addFaceScalarQuantity("categorical face", fCat, polyscope::DataType::CATEGORICAL); + psSimpleMesh->addFaceScalarQuantity("face area", fArea, polyscope::DataType::MAGNITUDE); + psSimpleMesh->addFaceColorQuantity("fColor", fColor); + // size_t nEdges = psMesh->nEdges(); diff --git a/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h b/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h index d89d3433..b5e9484b 100644 --- a/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h +++ b/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h @@ -36,6 +36,8 @@ extern const ShaderReplacementRule MESH_PROPAGATE_CULLPOS; extern const ShaderReplacementRule MESH_PROPAGATE_PICK; extern const ShaderReplacementRule MESH_PROPAGATE_PICK_SIMPLE; extern const ShaderReplacementRule MESH_PROPAGATE_TYPE_AND_BASECOLOR2_SHADE; +extern const ShaderReplacementRule SIMPLE_MESH_PROPAGATE_FACE_VALUE; +extern const ShaderReplacementRule SIMPLE_MESH_PROPAGATE_FACE_COLOR; } // namespace backend_openGL3 diff --git a/include/polyscope/simple_triangle_mesh.h b/include/polyscope/simple_triangle_mesh.h index ad600f7c..b4c79a32 100644 --- a/include/polyscope/simple_triangle_mesh.h +++ b/include/polyscope/simple_triangle_mesh.h @@ -24,7 +24,11 @@ class SimpleTriangleMesh; // Forward declare quantity types class SimpleTriangleMeshScalarQuantity; +class SimpleTriangleMeshVertexScalarQuantity; +class SimpleTriangleMeshFaceScalarQuantity; class SimpleTriangleMeshColorQuantity; +class SimpleTriangleMeshVertexColorQuantity; +class SimpleTriangleMeshFaceColorQuantity; struct SimpleTriangleMeshPickResult { // this does nothing for now, just matching pattern from other structures @@ -64,12 +68,19 @@ class SimpleTriangleMesh : public Structure { // Scalars template - SimpleTriangleMeshScalarQuantity* addVertexScalarQuantity(std::string name, const T& values, - DataType type = DataType::STANDARD); + SimpleTriangleMeshVertexScalarQuantity* addVertexScalarQuantity(std::string name, const T& values, + DataType type = DataType::STANDARD); + + template + SimpleTriangleMeshFaceScalarQuantity* addFaceScalarQuantity(std::string name, const T& values, + DataType type = DataType::STANDARD); // Colors template - SimpleTriangleMeshColorQuantity* addVertexColorQuantity(std::string name, const T& values); + SimpleTriangleMeshVertexColorQuantity* addVertexColorQuantity(std::string name, const T& values); + + template + SimpleTriangleMeshFaceColorQuantity* addFaceColorQuantity(std::string name, const T& values); // === Mutate @@ -130,10 +141,13 @@ class SimpleTriangleMesh : public Structure { void setPickUniforms(render::ShaderProgram& p); // === Quantity adder implementations - SimpleTriangleMeshScalarQuantity* addVertexScalarQuantityImpl(std::string name, const std::vector& data, - DataType type); - SimpleTriangleMeshColorQuantity* addVertexColorQuantityImpl(std::string name, - const std::vector& colors); + SimpleTriangleMeshVertexScalarQuantity* addVertexScalarQuantityImpl(std::string name, const std::vector& data, + DataType type); + SimpleTriangleMeshFaceScalarQuantity* addFaceScalarQuantityImpl(std::string name, const std::vector& data, + DataType type); + SimpleTriangleMeshVertexColorQuantity* addVertexColorQuantityImpl(std::string name, + const std::vector& colors); + SimpleTriangleMeshFaceColorQuantity* addFaceColorQuantityImpl(std::string name, const std::vector& colors); // == Picking related things size_t pickStart; diff --git a/include/polyscope/simple_triangle_mesh.ipp b/include/polyscope/simple_triangle_mesh.ipp index 6b49b02f..05cb1b5a 100644 --- a/include/polyscope/simple_triangle_mesh.ipp +++ b/include/polyscope/simple_triangle_mesh.ipp @@ -54,18 +54,31 @@ void SimpleTriangleMesh::update(const V& newPositions, const F& newFaces) { // ===================================================== template -SimpleTriangleMeshScalarQuantity* SimpleTriangleMesh::addVertexScalarQuantity(std::string name, const T& values, - DataType type) { +SimpleTriangleMeshVertexScalarQuantity* SimpleTriangleMesh::addVertexScalarQuantity(std::string name, const T& values, + DataType type) { validateSize(values, nVertices(), "vertex scalar quantity " + name); return addVertexScalarQuantityImpl(name, standardizeArray(values), type); } template -SimpleTriangleMeshColorQuantity* SimpleTriangleMesh::addVertexColorQuantity(std::string name, const T& values) { +SimpleTriangleMeshFaceScalarQuantity* SimpleTriangleMesh::addFaceScalarQuantity(std::string name, const T& values, + DataType type) { + validateSize(values, nFaces(), "face scalar quantity " + name); + return addFaceScalarQuantityImpl(name, standardizeArray(values), type); +} + +template +SimpleTriangleMeshVertexColorQuantity* SimpleTriangleMesh::addVertexColorQuantity(std::string name, const T& values) { validateSize(values, nVertices(), "vertex color quantity " + name); return addVertexColorQuantityImpl(name, standardizeVectorArray(values)); } +template +SimpleTriangleMeshFaceColorQuantity* SimpleTriangleMesh::addFaceColorQuantity(std::string name, const T& values) { + validateSize(values, nFaces(), "face color quantity " + name); + return addFaceColorQuantityImpl(name, standardizeVectorArray(values)); +} + // Shorthand to get a mesh from polyscope inline SimpleTriangleMesh* getSimpleTriangleMesh(std::string name) { diff --git a/include/polyscope/simple_triangle_mesh_color_quantity.h b/include/polyscope/simple_triangle_mesh_color_quantity.h index 2383511e..7e8bce09 100644 --- a/include/polyscope/simple_triangle_mesh_color_quantity.h +++ b/include/polyscope/simple_triangle_mesh_color_quantity.h @@ -20,11 +20,39 @@ class SimpleTriangleMeshColorQuantity : public SimpleTriangleMeshQuantity, virtual void refresh() override; virtual std::string niceName() override; - std::string definedOn; + const std::string definedOn; protected: - void createProgram(); std::shared_ptr program; + + virtual void createProgram() = 0; +}; + + +// ======================================================== +// ========== Vertex Color ========== +// ======================================================== + +class SimpleTriangleMeshVertexColorQuantity : public SimpleTriangleMeshColorQuantity { +public: + SimpleTriangleMeshVertexColorQuantity(std::string name, const std::vector& colors, + SimpleTriangleMesh& mesh); + + virtual void createProgram() override; }; + +// ======================================================== +// ========== Face Color ========== +// ======================================================== + +class SimpleTriangleMeshFaceColorQuantity : public SimpleTriangleMeshColorQuantity { +public: + SimpleTriangleMeshFaceColorQuantity(std::string name, const std::vector& colors, + SimpleTriangleMesh& mesh); + + virtual void createProgram() override; +}; + + } // namespace polyscope diff --git a/include/polyscope/simple_triangle_mesh_scalar_quantity.h b/include/polyscope/simple_triangle_mesh_scalar_quantity.h index 729b24a7..cae5277d 100644 --- a/include/polyscope/simple_triangle_mesh_scalar_quantity.h +++ b/include/polyscope/simple_triangle_mesh_scalar_quantity.h @@ -22,12 +22,39 @@ class SimpleTriangleMeshScalarQuantity : public SimpleTriangleMeshQuantity, virtual void refresh() override; virtual std::string niceName() override; + const std::string definedOn; protected: - std::string definedOn; std::shared_ptr program; - void createProgram(); + virtual void createProgram() = 0; }; + +// ======================================================== +// ========== Vertex Scalar ========== +// ======================================================== + +class SimpleTriangleMeshVertexScalarQuantity : public SimpleTriangleMeshScalarQuantity { +public: + SimpleTriangleMeshVertexScalarQuantity(std::string name, const std::vector& values, SimpleTriangleMesh& mesh, + DataType dataType = DataType::STANDARD); + + virtual void createProgram() override; +}; + + +// ======================================================== +// ========== Face Scalar ========== +// ======================================================== + +class SimpleTriangleMeshFaceScalarQuantity : public SimpleTriangleMeshScalarQuantity { +public: + SimpleTriangleMeshFaceScalarQuantity(std::string name, const std::vector& values, SimpleTriangleMesh& mesh, + DataType dataType = DataType::STANDARD); + + virtual void createProgram() override; +}; + + } // namespace polyscope diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index b4ce8ce9..162324c1 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -2249,6 +2249,8 @@ void MockGLEngine::populateDefaultShadersAndRules() { registerShaderRule("MESH_PROPAGATE_TYPE_AND_BASECOLOR2_SHADE", MESH_PROPAGATE_TYPE_AND_BASECOLOR2_SHADE); registerShaderRule("MESH_PROPAGATE_PICK", MESH_PROPAGATE_PICK); registerShaderRule("MESH_PROPAGATE_PICK_SIMPLE", MESH_PROPAGATE_PICK_SIMPLE); + registerShaderRule("SIMPLE_MESH_PROPAGATE_FACE_VALUE", SIMPLE_MESH_PROPAGATE_FACE_VALUE); + registerShaderRule("SIMPLE_MESH_PROPAGATE_FACE_COLOR", SIMPLE_MESH_PROPAGATE_FACE_COLOR); // volume gridcube things registerShaderRule("GRIDCUBE_PROPAGATE_NODE_VALUE", GRIDCUBE_PROPAGATE_NODE_VALUE); diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index 53fb729a..38cde890 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -2736,6 +2736,8 @@ void GLEngine::populateDefaultShadersAndRules() { registerShaderRule("MESH_PROPAGATE_TYPE_AND_BASECOLOR2_SHADE", MESH_PROPAGATE_TYPE_AND_BASECOLOR2_SHADE); registerShaderRule("MESH_PROPAGATE_PICK", MESH_PROPAGATE_PICK); registerShaderRule("MESH_PROPAGATE_PICK_SIMPLE", MESH_PROPAGATE_PICK_SIMPLE); + registerShaderRule("SIMPLE_MESH_PROPAGATE_FACE_VALUE", SIMPLE_MESH_PROPAGATE_FACE_VALUE); + registerShaderRule("SIMPLE_MESH_PROPAGATE_FACE_COLOR", SIMPLE_MESH_PROPAGATE_FACE_COLOR); // volume gridcube things registerShaderRule("GRIDCUBE_PROPAGATE_NODE_VALUE", GRIDCUBE_PROPAGATE_NODE_VALUE); diff --git a/src/render/opengl/shaders/surface_mesh_shaders.cpp b/src/render/opengl/shaders/surface_mesh_shaders.cpp index 860cb2fc..fae61087 100644 --- a/src/render/opengl/shaders/surface_mesh_shaders.cpp +++ b/src/render/opengl/shaders/surface_mesh_shaders.cpp @@ -731,6 +731,40 @@ const ShaderReplacementRule MESH_PROPAGATE_PICK_SIMPLE ( // this one does faces ); +// Uses gl_PrimitiveID to look up per-face scalar data from a 1D texture. +// Requires indexed triangle drawing (DrawMode::IndexedTriangles) where primitive index == face index. +const ShaderReplacementRule SIMPLE_MESH_PROPAGATE_FACE_VALUE( + /* rule name */ "SIMPLE_MESH_PROPAGATE_FACE_VALUE", + { /* replacement sources */ + {"FRAG_DECLARATIONS", R"( + uniform sampler1D t_faceValues; + )"}, + {"GENERATE_SHADE_VALUE", R"( + float shadeValue = texelFetch(t_faceValues, gl_PrimitiveID, 0).r; + )"}, + }, + /* uniforms */ {}, + /* attributes */ {}, + /* textures */ {{"t_faceValues", 1}} +); + +// Uses gl_PrimitiveID to look up per-face color data from a 1D texture. +// Requires indexed triangle drawing (DrawMode::IndexedTriangles) where primitive index == face index. +const ShaderReplacementRule SIMPLE_MESH_PROPAGATE_FACE_COLOR( + /* rule name */ "SIMPLE_MESH_PROPAGATE_FACE_COLOR", + { /* replacement sources */ + {"FRAG_DECLARATIONS", R"( + uniform sampler1D t_faceColors; + )"}, + {"GENERATE_SHADE_VALUE", R"( + vec3 shadeColor = texelFetch(t_faceColors, gl_PrimitiveID, 0).rgb; + )"}, + }, + /* uniforms */ {}, + /* attributes */ {}, + /* textures */ {{"t_faceColors", 1}} +); + // clang-format on } // namespace backend_openGL3 diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index 3b4d2491..cdbc4445 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -353,19 +353,34 @@ glm::vec3 SimpleTriangleMesh::getBackFaceColor() { return backFaceColor.get(); } // === Quantity adder implementations -SimpleTriangleMeshScalarQuantity* SimpleTriangleMesh::addVertexScalarQuantityImpl(std::string name, - const std::vector& data, - DataType type) { +SimpleTriangleMeshVertexScalarQuantity* +SimpleTriangleMesh::addVertexScalarQuantityImpl(std::string name, const std::vector& data, DataType type) { checkForQuantityWithNameAndDeleteOrError(name); - SimpleTriangleMeshScalarQuantity* q = new SimpleTriangleMeshScalarQuantity(name, data, "vertex", *this, type); + SimpleTriangleMeshVertexScalarQuantity* q = new SimpleTriangleMeshVertexScalarQuantity(name, data, *this, type); addQuantity(q); return q; } -SimpleTriangleMeshColorQuantity* SimpleTriangleMesh::addVertexColorQuantityImpl(std::string name, - const std::vector& colors) { +SimpleTriangleMeshFaceScalarQuantity* +SimpleTriangleMesh::addFaceScalarQuantityImpl(std::string name, const std::vector& data, DataType type) { checkForQuantityWithNameAndDeleteOrError(name); - SimpleTriangleMeshColorQuantity* q = new SimpleTriangleMeshColorQuantity(name, colors, "vertex", *this); + SimpleTriangleMeshFaceScalarQuantity* q = new SimpleTriangleMeshFaceScalarQuantity(name, data, *this, type); + addQuantity(q); + return q; +} + +SimpleTriangleMeshVertexColorQuantity* +SimpleTriangleMesh::addVertexColorQuantityImpl(std::string name, const std::vector& colors) { + checkForQuantityWithNameAndDeleteOrError(name); + SimpleTriangleMeshVertexColorQuantity* q = new SimpleTriangleMeshVertexColorQuantity(name, colors, *this); + addQuantity(q); + return q; +} + +SimpleTriangleMeshFaceColorQuantity* +SimpleTriangleMesh::addFaceColorQuantityImpl(std::string name, const std::vector& colors) { + checkForQuantityWithNameAndDeleteOrError(name); + SimpleTriangleMeshFaceColorQuantity* q = new SimpleTriangleMeshFaceColorQuantity(name, colors, *this); addQuantity(q); return q; } diff --git a/src/simple_triangle_mesh_color_quantity.cpp b/src/simple_triangle_mesh_color_quantity.cpp index 2342c204..8d40bba4 100644 --- a/src/simple_triangle_mesh_color_quantity.cpp +++ b/src/simple_triangle_mesh_color_quantity.cpp @@ -9,6 +9,10 @@ namespace polyscope { +// ======================================================== +// ========== Color Quantity Base ========== +// ======================================================== + SimpleTriangleMeshColorQuantity::SimpleTriangleMeshColorQuantity(std::string name, const std::vector& colors_, std::string definedOn_, @@ -40,7 +44,24 @@ void SimpleTriangleMeshColorQuantity::buildCustomUI() { buildColorUI(); } -void SimpleTriangleMeshColorQuantity::createProgram() { +void SimpleTriangleMeshColorQuantity::refresh() { + program.reset(); + Quantity::refresh(); +} + +std::string SimpleTriangleMeshColorQuantity::niceName() { return name + " (" + definedOn + " color)"; } + + +// ======================================================== +// ========== Vertex Color ========== +// ======================================================== + +SimpleTriangleMeshVertexColorQuantity::SimpleTriangleMeshVertexColorQuantity(std::string name, + const std::vector& colors_, + SimpleTriangleMesh& mesh_) + : SimpleTriangleMeshColorQuantity(name, colors_, "vertex", mesh_) {} + +void SimpleTriangleMeshVertexColorQuantity::createProgram() { // clang-format off program = render::engine->requestShader("SIMPLE_MESH", render::engine->addMaterialRules(parent.getMaterial(), @@ -58,11 +79,34 @@ void SimpleTriangleMeshColorQuantity::createProgram() { render::engine->setMaterial(*program, parent.getMaterial()); } -void SimpleTriangleMeshColorQuantity::refresh() { - program.reset(); - Quantity::refresh(); + +// ======================================================== +// ========== Face Color ========== +// ======================================================== + +SimpleTriangleMeshFaceColorQuantity::SimpleTriangleMeshFaceColorQuantity(std::string name, + const std::vector& colors_, + SimpleTriangleMesh& mesh_) + : SimpleTriangleMeshColorQuantity(name, colors_, "face", mesh_) { + colors.setTextureSize(parent.nFaces()); } -std::string SimpleTriangleMeshColorQuantity::niceName() { return name + " (" + definedOn + " color)"; } +void SimpleTriangleMeshFaceColorQuantity::createProgram() { + // clang-format off + program = render::engine->requestShader("SIMPLE_MESH", + render::engine->addMaterialRules(parent.getMaterial(), + addColorRules( + parent.addSimpleTriangleMeshRules( + {"SIMPLE_MESH_PROPAGATE_FACE_COLOR", "SHADE_COLOR"} + ) + ) + ) + ); + // clang-format on + + parent.setSimpleTriangleMeshProgramGeometryAttributes(*program); + program->setTextureFromBuffer("t_faceColors", colors.getRenderTextureBuffer().get()); + render::engine->setMaterial(*program, parent.getMaterial()); +} } // namespace polyscope diff --git a/src/simple_triangle_mesh_scalar_quantity.cpp b/src/simple_triangle_mesh_scalar_quantity.cpp index bc3026d8..e13a2c94 100644 --- a/src/simple_triangle_mesh_scalar_quantity.cpp +++ b/src/simple_triangle_mesh_scalar_quantity.cpp @@ -9,11 +9,14 @@ namespace polyscope { +// ======================================================== +// ========== Scalar Quantity Base ========== +// ======================================================== + SimpleTriangleMeshScalarQuantity::SimpleTriangleMeshScalarQuantity(std::string name, const std::vector& values_, - std::string definedOn_, - SimpleTriangleMesh& mesh_, DataType dataType_) - : SimpleTriangleMeshQuantity(name, mesh_, true), ScalarQuantity(*this, values_, dataType_), - definedOn(definedOn_) {} + std::string definedOn_, SimpleTriangleMesh& mesh_, + DataType dataType_) + : SimpleTriangleMeshQuantity(name, mesh_, true), ScalarQuantity(*this, values_, dataType_), definedOn(definedOn_) {} void SimpleTriangleMeshScalarQuantity::draw() { if (!isEnabled()) return; @@ -40,7 +43,25 @@ void SimpleTriangleMeshScalarQuantity::buildCustomUI() { buildScalarUI(); } -void SimpleTriangleMeshScalarQuantity::createProgram() { +void SimpleTriangleMeshScalarQuantity::refresh() { + program.reset(); + Quantity::refresh(); +} + +std::string SimpleTriangleMeshScalarQuantity::niceName() { return name + " (" + definedOn + " scalar)"; } + + +// ======================================================== +// ========== Vertex Scalar ========== +// ======================================================== + +SimpleTriangleMeshVertexScalarQuantity::SimpleTriangleMeshVertexScalarQuantity(std::string name, + const std::vector& values_, + SimpleTriangleMesh& mesh_, + DataType dataType_) + : SimpleTriangleMeshScalarQuantity(name, values_, "vertex", mesh_, dataType_) {} + +void SimpleTriangleMeshVertexScalarQuantity::createProgram() { // clang-format off program = render::engine->requestShader("SIMPLE_MESH", render::engine->addMaterialRules(parent.getMaterial(), @@ -59,11 +80,36 @@ void SimpleTriangleMeshScalarQuantity::createProgram() { render::engine->setMaterial(*program, parent.getMaterial()); } -void SimpleTriangleMeshScalarQuantity::refresh() { - program.reset(); - Quantity::refresh(); + +// ======================================================== +// ========== Face Scalar ========== +// ======================================================== + +SimpleTriangleMeshFaceScalarQuantity::SimpleTriangleMeshFaceScalarQuantity(std::string name, + const std::vector& values_, + SimpleTriangleMesh& mesh_, + DataType dataType_) + : SimpleTriangleMeshScalarQuantity(name, values_, "face", mesh_, dataType_) { + values.setTextureSize(parent.nFaces()); } -std::string SimpleTriangleMeshScalarQuantity::niceName() { return name + " (" + definedOn + " scalar)"; } +void SimpleTriangleMeshFaceScalarQuantity::createProgram() { + // clang-format off + program = render::engine->requestShader("SIMPLE_MESH", + render::engine->addMaterialRules(parent.getMaterial(), + parent.addSimpleTriangleMeshRules( + addScalarRules( + {"SIMPLE_MESH_PROPAGATE_FACE_VALUE"} + ) + ) + ) + ); + // clang-format on + + parent.setSimpleTriangleMeshProgramGeometryAttributes(*program); + program->setTextureFromBuffer("t_faceValues", values.getRenderTextureBuffer().get()); + program->setTextureFromColormap("t_colormap", cMap.get()); + render::engine->setMaterial(*program, parent.getMaterial()); +} } // namespace polyscope diff --git a/test/src/surface_mesh_test.cpp b/test/src/surface_mesh_test.cpp index bbef8570..ed2d3402 100644 --- a/test/src/surface_mesh_test.cpp +++ b/test/src/surface_mesh_test.cpp @@ -701,3 +701,29 @@ TEST_F(PolyscopeTest, SimpleTriangleMeshVertexColor) { polyscope::removeAllStructures(); } + +TEST_F(PolyscopeTest, SimpleTriangleMeshFaceScalar) { + auto psMesh = registerSimpleTriangleMesh(); + std::vector fScalar(psMesh->nFaces(), 7.); + auto q1 = psMesh->addFaceScalarQuantity("fScalar", fScalar); + q1->setEnabled(true); + polyscope::show(3); + + q1->updateData(fScalar); + polyscope::show(3); + + polyscope::removeAllStructures(); +} + +TEST_F(PolyscopeTest, SimpleTriangleMeshFaceColor) { + auto psMesh = registerSimpleTriangleMesh(); + std::vector fColors(psMesh->nFaces(), glm::vec3{.2, .3, .4}); + auto q1 = psMesh->addFaceColorQuantity("fColor", fColors); + q1->setEnabled(true); + polyscope::show(3); + + q1->updateData(fColors); + polyscope::show(3); + + polyscope::removeAllStructures(); +} From 7794672e3d802dd97132ed8e28827b15b8a0c279 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 5 Apr 2026 21:28:10 -0700 Subject: [PATCH 12/17] clean up quantities, implement picking for SimpleTriangleMesh --- include/polyscope/pick.ipp | 50 ++++++ .../opengl/shaders/surface_mesh_shaders.h | 1 + include/polyscope/simple_triangle_mesh.h | 14 +- .../simple_triangle_mesh_color_quantity.h | 3 + .../polyscope/simple_triangle_mesh_quantity.h | 7 +- .../simple_triangle_mesh_scalar_quantity.h | 2 + src/render/mock_opengl/mock_gl_engine.cpp | 3 +- src/render/opengl/gl_engine.cpp | 1 + src/render/opengl/shaders/common.cpp | 13 +- .../opengl/shaders/surface_mesh_shaders.cpp | 22 +++ src/simple_triangle_mesh.cpp | 153 ++++++++++++++++-- src/simple_triangle_mesh_color_quantity.cpp | 28 +++- src/simple_triangle_mesh_scalar_quantity.cpp | 14 ++ test/src/misc_test.cpp | 37 +++++ 14 files changed, 323 insertions(+), 25 deletions(-) diff --git a/include/polyscope/pick.ipp b/include/polyscope/pick.ipp index 9d4a3476..da4b3299 100644 --- a/include/polyscope/pick.ipp +++ b/include/polyscope/pick.ipp @@ -13,6 +13,8 @@ const uint64_t bitsForPickPacking = 22; inline glm::vec3 indToVec(size_t globalInd) { + // NOTE: there is a duplicate version of this logic in a macro below, which must be kept in sync. + // Can comfortably fit a 22 bit integer exactly in a single precision float uint64_t factor = 1 << bitsForPickPacking; uint64_t mask = factor - 1; @@ -48,5 +50,53 @@ inline uint64_t vecToInd(glm::vec3 vec) { return ind; } + +// == Weird alternate implementation of indToVec() +// This is here because sometimes we want to evaluate the indToVec() bit-bashing logic in a shader, and get exactly +// the same result as the C++ version above. A GLSL implementation is not too bad, but it's hard to test to ensure +// it really matches. +// +// The solution here is a funky macro'd implementation, that compiles as C++ or GLSL. We compile it as C++ in the tests +// and verify it matches the usual indToVec() implementation, then compile it as GLSL in shaders. +// +// All of the macro stuff you see below is just boilerplate to make that possible. + +#define POLYSCOPE_PICK_STR_H(...) #__VA_ARGS__ +#define POLYSCOPE_PICK_STR(...) POLYSCOPE_PICK_STR_H(__VA_ARGS__) + +// See note above. This is logic that is meant to be identical to indToVec(). +// clang-format off +#define POLYSCOPE_PICK_INDEX_COLOR_BODY \ + POLYSCOPE_PICK_UINT idxLow = pickStartLow + primID; \ + POLYSCOPE_PICK_UINT carry = (idxLow < pickStartLow) ? 1u : 0u; \ + POLYSCOPE_PICK_UINT idxHigh = pickStartHigh + carry; \ + POLYSCOPE_PICK_UINT low22 = idxLow & 0x3FFFFFu; \ + POLYSCOPE_PICK_UINT med22 = ((idxLow >> 22u) | (idxHigh << 10u)) & 0x3FFFFFu; \ + POLYSCOPE_PICK_UINT high22 = (idxHigh >> 12u) & 0x3FFFFFu; \ + return POLYSCOPE_PICK_VEC3(float(low22), float(med22), float(high22)) / 4194304.0f; +// clang-format on + +// C++ version: compile the body with C++ types. +#define POLYSCOPE_PICK_UINT uint32_t +#define POLYSCOPE_PICK_VEC3 glm::vec3 +inline glm::vec3 pickIndexToColorImpl(uint32_t pickStartLow, uint32_t pickStartHigh, uint32_t primID) { + POLYSCOPE_PICK_INDEX_COLOR_BODY +} +#undef POLYSCOPE_PICK_UINT +#undef POLYSCOPE_PICK_VEC3 + +// Convenience wrapper for C++ callers that have a combined uint64_t pickStart. +inline glm::vec3 pickIndexToColor(uint64_t pickStart, uint32_t primID) { + return pickIndexToColorImpl(static_cast(pickStart & 0xFFFFFFFFull), static_cast(pickStart >> 32), + primID); +} + +// GLSL string macro. Stringifies POLYSCOPE_PICK_INDEX_COLOR_BODY using GLSL type names. +// REQUIRES: POLYSCOPE_PICK_UINT must equal "uint" and POLYSCOPE_PICK_VEC3 must equal "vec3" +// at the point of expansion (see common.cpp for the usage pattern). +#define POLYSCOPE_PICK_INDEX_TO_COLOR_GLSL \ + "vec3 pickIndexToColor(uint pickStartLow, uint pickStartHigh, uint primID) { " POLYSCOPE_PICK_STR( \ + POLYSCOPE_PICK_INDEX_COLOR_BODY) " }" + } // namespace pick } // namespace polyscope diff --git a/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h b/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h index b5e9484b..9185b6c1 100644 --- a/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h +++ b/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h @@ -38,6 +38,7 @@ extern const ShaderReplacementRule MESH_PROPAGATE_PICK_SIMPLE; extern const ShaderReplacementRule MESH_PROPAGATE_TYPE_AND_BASECOLOR2_SHADE; extern const ShaderReplacementRule SIMPLE_MESH_PROPAGATE_FACE_VALUE; extern const ShaderReplacementRule SIMPLE_MESH_PROPAGATE_FACE_COLOR; +extern const ShaderReplacementRule SIMPLE_MESH_PROPAGATE_FACE_PICK; } // namespace backend_openGL3 diff --git a/include/polyscope/simple_triangle_mesh.h b/include/polyscope/simple_triangle_mesh.h index b4c79a32..e7232013 100644 --- a/include/polyscope/simple_triangle_mesh.h +++ b/include/polyscope/simple_triangle_mesh.h @@ -10,6 +10,7 @@ #include "polyscope/scaled_value.h" #include "polyscope/standardize_data_array.h" #include "polyscope/structure.h" +#include "polyscope/types.h" #include "polyscope/simple_triangle_mesh_color_quantity.h" #include "polyscope/simple_triangle_mesh_quantity.h" @@ -31,7 +32,8 @@ class SimpleTriangleMeshVertexColorQuantity; class SimpleTriangleMeshFaceColorQuantity; struct SimpleTriangleMeshPickResult { - // this does nothing for now, just matching pattern from other structures + MeshElement elementType = MeshElement::FACE; // which kind of element was clicked + int64_t index = -1; // index of the clicked element (vertex or face) }; class SimpleTriangleMesh : public Structure { @@ -114,6 +116,10 @@ class SimpleTriangleMesh : public Structure { SimpleTriangleMesh* setBackFacePolicy(BackFacePolicy newPolicy); BackFacePolicy getBackFacePolicy(); + // Selection mode (controls vertex vs face pick threshold) + SimpleTriangleMesh* setSelectionMode(MeshSelectionMode newMode); + MeshSelectionMode getSelectionMode(); + // Rendering helpers used by quantities void setSimpleTriangleMeshUniforms(render::ShaderProgram& p, bool withSurfaceShade = true); void setSimpleTriangleMeshProgramGeometryAttributes(render::ShaderProgram& p); @@ -128,6 +134,7 @@ class SimpleTriangleMesh : public Structure { PersistentValue material; PersistentValue backFacePolicy; PersistentValue backFaceColor; + PersistentValue selectionMode; // Drawing related things // if nullptr, prepare() (resp. preparePick()) needs to be called @@ -138,7 +145,7 @@ class SimpleTriangleMesh : public Structure { // Do setup work related to drawing, including allocating openGL data void ensureRenderProgramPrepared(); void ensurePickProgramPrepared(); - void setPickUniforms(render::ShaderProgram& p); + void setPickUniforms(render::ShaderProgram& p); // sets u_pickStartLow/High for SIMPLE_MESH_PROPAGATE_FACE_PICK // === Quantity adder implementations SimpleTriangleMeshVertexScalarQuantity* addVertexScalarQuantityImpl(std::string name, const std::vector& data, @@ -150,8 +157,7 @@ class SimpleTriangleMesh : public Structure { SimpleTriangleMeshFaceColorQuantity* addFaceColorQuantityImpl(std::string name, const std::vector& colors); // == Picking related things - size_t pickStart; - glm::vec3 pickColor; + size_t pickStart = 0; }; diff --git a/include/polyscope/simple_triangle_mesh_color_quantity.h b/include/polyscope/simple_triangle_mesh_color_quantity.h index 7e8bce09..d3b8d352 100644 --- a/include/polyscope/simple_triangle_mesh_color_quantity.h +++ b/include/polyscope/simple_triangle_mesh_color_quantity.h @@ -17,6 +17,7 @@ class SimpleTriangleMeshColorQuantity : public SimpleTriangleMeshQuantity, virtual void draw() override; virtual void buildCustomUI() override; + virtual void buildColorOptionsUI() override; virtual void refresh() override; virtual std::string niceName() override; @@ -39,6 +40,7 @@ class SimpleTriangleMeshVertexColorQuantity : public SimpleTriangleMeshColorQuan SimpleTriangleMesh& mesh); virtual void createProgram() override; + virtual void buildVertexInfoGUI(size_t vInd) override; }; @@ -52,6 +54,7 @@ class SimpleTriangleMeshFaceColorQuantity : public SimpleTriangleMeshColorQuanti SimpleTriangleMesh& mesh); virtual void createProgram() override; + virtual void buildFaceInfoGUI(size_t fInd) override; }; diff --git a/include/polyscope/simple_triangle_mesh_quantity.h b/include/polyscope/simple_triangle_mesh_quantity.h index 6df4e0e4..ab8a0116 100644 --- a/include/polyscope/simple_triangle_mesh_quantity.h +++ b/include/polyscope/simple_triangle_mesh_quantity.h @@ -12,9 +12,14 @@ class SimpleTriangleMesh; class SimpleTriangleMeshQuantity : public Quantity { public: SimpleTriangleMeshQuantity(std::string name, SimpleTriangleMesh& parentStructure, bool dominates = false); - virtual ~SimpleTriangleMeshQuantity(){}; + virtual ~SimpleTriangleMeshQuantity() {}; SimpleTriangleMesh& parent; // shadows and hides the generic member in Quantity + + // Called by buildPickUI() to display this quantity's value for the selected element. + // Override in vertex quantities (for vertex picks) and face quantities (for face picks). + virtual void buildVertexInfoGUI(size_t vInd) {} + virtual void buildFaceInfoGUI(size_t fInd) {} }; } // namespace polyscope diff --git a/include/polyscope/simple_triangle_mesh_scalar_quantity.h b/include/polyscope/simple_triangle_mesh_scalar_quantity.h index cae5277d..9a7623c0 100644 --- a/include/polyscope/simple_triangle_mesh_scalar_quantity.h +++ b/include/polyscope/simple_triangle_mesh_scalar_quantity.h @@ -41,6 +41,7 @@ class SimpleTriangleMeshVertexScalarQuantity : public SimpleTriangleMeshScalarQu DataType dataType = DataType::STANDARD); virtual void createProgram() override; + virtual void buildVertexInfoGUI(size_t vInd) override; }; @@ -54,6 +55,7 @@ class SimpleTriangleMeshFaceScalarQuantity : public SimpleTriangleMeshScalarQuan DataType dataType = DataType::STANDARD); virtual void createProgram() override; + virtual void buildFaceInfoGUI(size_t fInd) override; }; diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index 162324c1..3f068b04 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -2251,7 +2251,8 @@ void MockGLEngine::populateDefaultShadersAndRules() { registerShaderRule("MESH_PROPAGATE_PICK_SIMPLE", MESH_PROPAGATE_PICK_SIMPLE); registerShaderRule("SIMPLE_MESH_PROPAGATE_FACE_VALUE", SIMPLE_MESH_PROPAGATE_FACE_VALUE); registerShaderRule("SIMPLE_MESH_PROPAGATE_FACE_COLOR", SIMPLE_MESH_PROPAGATE_FACE_COLOR); - + registerShaderRule("SIMPLE_MESH_PROPAGATE_FACE_PICK", SIMPLE_MESH_PROPAGATE_FACE_PICK); + // volume gridcube things registerShaderRule("GRIDCUBE_PROPAGATE_NODE_VALUE", GRIDCUBE_PROPAGATE_NODE_VALUE); registerShaderRule("GRIDCUBE_PROPAGATE_CELL_VALUE", GRIDCUBE_PROPAGATE_CELL_VALUE); diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index 38cde890..88cdf48a 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -2738,6 +2738,7 @@ void GLEngine::populateDefaultShadersAndRules() { registerShaderRule("MESH_PROPAGATE_PICK_SIMPLE", MESH_PROPAGATE_PICK_SIMPLE); registerShaderRule("SIMPLE_MESH_PROPAGATE_FACE_VALUE", SIMPLE_MESH_PROPAGATE_FACE_VALUE); registerShaderRule("SIMPLE_MESH_PROPAGATE_FACE_COLOR", SIMPLE_MESH_PROPAGATE_FACE_COLOR); + registerShaderRule("SIMPLE_MESH_PROPAGATE_FACE_PICK", SIMPLE_MESH_PROPAGATE_FACE_PICK); // volume gridcube things registerShaderRule("GRIDCUBE_PROPAGATE_NODE_VALUE", GRIDCUBE_PROPAGATE_NODE_VALUE); diff --git a/src/render/opengl/shaders/common.cpp b/src/render/opengl/shaders/common.cpp index 58f98551..ac7a1aed 100644 --- a/src/render/opengl/shaders/common.cpp +++ b/src/render/opengl/shaders/common.cpp @@ -3,6 +3,8 @@ #include "polyscope/render/opengl/shaders/common.h" +#include "polyscope/pick.h" // for POLYSCOPE_PICK_INDEX_TO_COLOR_GLSL + namespace polyscope { namespace render { namespace backend_openGL3 { @@ -437,6 +439,15 @@ bool rayTaperedCylinderIntersection(vec3 rayStart, vec3 rayDir, vec3 cylTail, ve } +)" +// Define GLSL type names so POLYSCOPE_PICK_INDEX_COLOR_BODY stringifies as GLSL (see pick.ipp). +#define POLYSCOPE_PICK_UINT uint +#define POLYSCOPE_PICK_VEC3 vec3 + POLYSCOPE_PICK_INDEX_TO_COLOR_GLSL +#undef POLYSCOPE_PICK_UINT +#undef POLYSCOPE_PICK_VEC3 + R"( + bool rayConeIntersection(vec3 rayStart, vec3 rayDir, vec3 coneBase, vec3 coneTip, float coneRad, out float tHit, out vec3 pHit, out vec3 nHit) { rayDir = normalize(rayDir); @@ -510,6 +521,6 @@ bool rayConeIntersection(vec3 rayStart, vec3 rayDir, vec3 coneBase, vec3 coneTip )"; -} +} // namespace backend_openGL3 } // namespace render } // namespace polyscope diff --git a/src/render/opengl/shaders/surface_mesh_shaders.cpp b/src/render/opengl/shaders/surface_mesh_shaders.cpp index fae61087..6b842c6a 100644 --- a/src/render/opengl/shaders/surface_mesh_shaders.cpp +++ b/src/render/opengl/shaders/surface_mesh_shaders.cpp @@ -765,6 +765,28 @@ const ShaderReplacementRule SIMPLE_MESH_PROPAGATE_FACE_COLOR( /* textures */ {{"t_faceColors", 1}} ); +// Encodes the pick color for each face directly from gl_PrimitiveID + pickStart, with no texture lookup. +// Delegates encoding to pickIndexToColor() in common.cpp (see there for the bit layout). +const ShaderReplacementRule SIMPLE_MESH_PROPAGATE_FACE_PICK( + /* rule name */ "SIMPLE_MESH_PROPAGATE_FACE_PICK", + { /* replacement sources */ + {"FRAG_DECLARATIONS", R"( + uniform uint u_pickStartLow; // lower 32 bits of the global pick index for face 0 + uniform uint u_pickStartHigh; // upper 32 bits of the global pick index for face 0 + vec3 pickIndexToColor(uint pickStartLow, uint pickStartHigh, uint primID); // defined in common.cpp + )"}, + {"GENERATE_SHADE_VALUE", R"( + vec3 shadeColor = pickIndexToColor(u_pickStartLow, u_pickStartHigh, uint(gl_PrimitiveID)); + )"}, + }, + /* uniforms */ { + {"u_pickStartLow", RenderDataType::UInt}, + {"u_pickStartHigh", RenderDataType::UInt}, + }, + /* attributes */ {}, + /* textures */ {} +); + // clang-format on } // namespace backend_openGL3 diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index cdbc4445..1cace4b5 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -7,6 +7,7 @@ #include "polyscope/render/engine.h" #include "polyscope/simple_triangle_mesh_color_quantity.h" #include "polyscope/simple_triangle_mesh_scalar_quantity.h" +#include "polyscope/view.h" #include "imgui.h" @@ -28,7 +29,8 @@ SimpleTriangleMesh::SimpleTriangleMesh(std::string name, std::vector surfaceColor(uniquePrefix() + "surfaceColor", getNextUniqueColor()), material(uniquePrefix() + "material", "clay"), backFacePolicy(uniquePrefix() + "backFacePolicy", BackFacePolicy::Different), - backFaceColor(uniquePrefix() + "backFaceColor", glm::vec3(1.f - surfaceColor.get().r, 1.f - surfaceColor.get().g, 1.f - surfaceColor.get().b)) + backFaceColor(uniquePrefix() + "backFaceColor", glm::vec3(1.f - surfaceColor.get().r, 1.f - surfaceColor.get().g, 1.f - surfaceColor.get().b)), + selectionMode(uniquePrefix() + "selectionMode", MeshSelectionMode::Auto) // clang-format on { cullWholeElements.setPassive(false); @@ -74,6 +76,17 @@ void SimpleTriangleMesh::buildCustomOptionsUI() { setBackFacePolicy(BackFacePolicy::Cull); ImGui::EndMenu(); } + + // Selection mode + if (ImGui::BeginMenu("Selection Mode")) { + if (ImGui::MenuItem("auto", NULL, selectionMode.get() == MeshSelectionMode::Auto)) + setSelectionMode(MeshSelectionMode::Auto); + if (ImGui::MenuItem("vertices only", NULL, selectionMode.get() == MeshSelectionMode::VerticesOnly)) + setSelectionMode(MeshSelectionMode::VerticesOnly); + if (ImGui::MenuItem("faces only", NULL, selectionMode.get() == MeshSelectionMode::FacesOnly)) + setSelectionMode(MeshSelectionMode::FacesOnly); + ImGui::EndMenu(); + } } @@ -205,23 +218,24 @@ void SimpleTriangleMesh::ensurePickProgramPrepared() { if (pickProgram) return; // clang-format off - pickProgram = render::engine->requestShader("SIMPLE_MESH", - addSimpleTriangleMeshRules( - { - "SHADECOLOR_FROM_UNIFORM" - } - , false), render::ShaderReplacementDefaults::Pick - ); + pickProgram = render::engine->requestShader("SIMPLE_MESH", + addSimpleTriangleMeshRules({"SIMPLE_MESH_PROPAGATE_FACE_PICK"}, false), + render::ShaderReplacementDefaults::Pick); // clang-format on setSimpleTriangleMeshProgramGeometryAttributes(*pickProgram); - // Request pick indices - pickStart = pick::requestPickBufferRange(this, 1); - pickColor = pick::indToVec(pickStart); + // Allocate one pick index per face + pickStart = pick::requestPickBufferRange(this, nFaces()); } -void SimpleTriangleMesh::setPickUniforms(render::ShaderProgram& p) { p.setUniform("u_color", pickColor); } +void SimpleTriangleMesh::setPickUniforms(render::ShaderProgram& p) { + // Encode pickStart as two uint uniforms for the shader's 64-bit index arithmetic. + // NOTE: must stay in sync with SIMPLE_MESH_PROPAGATE_FACE_PICK in surface_mesh_shaders.cpp + // and with pick::indToVec() / pick::bitsForPickPacking in pick.ipp. + p.setUniform("u_pickStartLow", static_cast(pickStart & 0xFFFFFFFFull)); + p.setUniform("u_pickStartHigh", static_cast(pickStart >> 32)); +} std::vector SimpleTriangleMesh::addSimpleTriangleMeshRules(std::vector initRules, bool withSurfaceShade) { @@ -295,14 +309,86 @@ void SimpleTriangleMesh::updateObjectSpaceBounds() { SimpleTriangleMeshPickResult SimpleTriangleMesh::interpretPickResult(const PickResult& rawResult) { if (rawResult.structure != this) { - // caller must ensure that the PickResult belongs to this structure - // by checking the structure pointer or name exception("called interpretPickResult(), but the pick result is not from this structure"); } SimpleTriangleMeshPickResult result; - // currently nothing + size_t localInd = rawResult.localIndex; + if (localInd >= nFaces()) { + // Shouldn't happen, but guard against stale pick data + return result; + } + + // Recover the 3 vertex positions of the clicked face + glm::uvec3 triInds = faces.getValue(localInd); + glm::vec3 pA = vertices.getValue(triInds[0]); + glm::vec3 pB = vertices.getValue(triInds[1]); + glm::vec3 pC = vertices.getValue(triInds[2]); + + // Compute barycentric coordinates of the pick via sub-triangle areas. + // Each lambda_i is the area of the sub-triangle formed by the other two vertices and the click point P, + // divided by the total. Clamp each area to be nonneg and finite, then normalise. If the total is zero, fall back to + // (1/3, 1/3, 1/3). + glm::vec3 P = rawResult.position; + + auto subArea = [](glm::vec3 u, glm::vec3 v, glm::vec3 w) -> float { + float a = glm::length(glm::cross(v - u, w - u)); + return (std::isfinite(a) && a >= 0.f) ? a : 0.f; + }; + + float aA = subArea(P, pB, pC); + float aB = subArea(pA, P, pC); + float aC = subArea(pA, pB, P); + float total = aA + aB + aC; + + float lambdaA, lambdaB, lambdaC; + if (total == 0.f) { + lambdaA = lambdaB = lambdaC = 1.f / 3.f; + } else { + lambdaA = aA / total; + lambdaB = aB / total; + lambdaC = aC / total; + } + + // Find the dominant vertex + size_t nearestLocalIdx = 0; + float maxLambda = lambdaA; + if (lambdaB > maxLambda) { + maxLambda = lambdaB; + nearestLocalIdx = 1; + } + if (lambdaC > maxLambda) { + maxLambda = lambdaC; + nearestLocalIdx = 2; + } + size_t nearestVertexIdx = triInds[nearestLocalIdx]; + + // Threshold: lambda must exceed this to count as a vertex click. + // Matches SurfaceMesh: Auto uses 1 - 0.2 = 0.8, VerticesOnly always picks vertex, FacesOnly never does. + float vertexPickThreshold; + switch (selectionMode.get()) { + case MeshSelectionMode::Auto: + vertexPickThreshold = 0.8f; + break; + case MeshSelectionMode::VerticesOnly: + vertexPickThreshold = 0.0f; + break; + case MeshSelectionMode::FacesOnly: + vertexPickThreshold = 2.0f; + break; + default: + vertexPickThreshold = 0.8f; + break; + } + + if (maxLambda > vertexPickThreshold) { + result.elementType = MeshElement::VERTEX; + result.index = static_cast(nearestVertexIdx); + } else { + result.elementType = MeshElement::FACE; + result.index = static_cast(localInd); + } return result; } @@ -310,7 +396,35 @@ SimpleTriangleMeshPickResult SimpleTriangleMesh::interpretPickResult(const PickR void SimpleTriangleMesh::buildPickUI(const PickResult& rawResult) { SimpleTriangleMeshPickResult result = interpretPickResult(rawResult); - // Do nothing for now, we just pick a single constant for the whole structure + if (result.index < 0) return; + + if (result.elementType == MeshElement::VERTEX) { + ImGui::TextUnformatted(("Vertex #" + std::to_string(result.index)).c_str()); + + ImGui::Spacing(); + ImGui::Indent(20.f); + ImGui::Columns(2); + ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() / 3); + for (auto& x : quantities) { + SimpleTriangleMeshQuantity* q = static_cast(x.second.get()); + q->buildVertexInfoGUI(static_cast(result.index)); + } + ImGui::Columns(1); + ImGui::Indent(-20.f); + } else { + ImGui::TextUnformatted(("Face #" + std::to_string(result.index)).c_str()); + + ImGui::Spacing(); + ImGui::Indent(20.f); + ImGui::Columns(2); + ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() / 3); + for (auto& x : quantities) { + SimpleTriangleMeshQuantity* q = static_cast(x.second.get()); + q->buildFaceInfoGUI(static_cast(result.index)); + } + ImGui::Columns(1); + ImGui::Indent(-20.f); + } } @@ -350,6 +464,13 @@ SimpleTriangleMesh* SimpleTriangleMesh::setBackFaceColor(glm::vec3 val) { glm::vec3 SimpleTriangleMesh::getBackFaceColor() { return backFaceColor.get(); } +SimpleTriangleMesh* SimpleTriangleMesh::setSelectionMode(MeshSelectionMode newMode) { + selectionMode = newMode; + requestRedraw(); + return this; +} +MeshSelectionMode SimpleTriangleMesh::getSelectionMode() { return selectionMode.get(); } + // === Quantity adder implementations diff --git a/src/simple_triangle_mesh_color_quantity.cpp b/src/simple_triangle_mesh_color_quantity.cpp index 8d40bba4..3300d9b5 100644 --- a/src/simple_triangle_mesh_color_quantity.cpp +++ b/src/simple_triangle_mesh_color_quantity.cpp @@ -15,8 +15,7 @@ namespace polyscope { SimpleTriangleMeshColorQuantity::SimpleTriangleMeshColorQuantity(std::string name, const std::vector& colors_, - std::string definedOn_, - SimpleTriangleMesh& mesh_) + std::string definedOn_, SimpleTriangleMesh& mesh_) : SimpleTriangleMeshQuantity(name, mesh_, true), ColorQuantity(*this, colors_), definedOn(definedOn_) {} void SimpleTriangleMeshColorQuantity::draw() { @@ -32,6 +31,11 @@ void SimpleTriangleMeshColorQuantity::draw() { program->draw(); } +void SimpleTriangleMeshColorQuantity::buildColorOptionsUI() { + ColorQuantity::buildColorOptionsUI(); + ImGui::TextUnformatted("(no options available)"); +} + void SimpleTriangleMeshColorQuantity::buildCustomUI() { ImGui::SameLine(); if (ImGui::Button("Options")) { @@ -61,6 +65,16 @@ SimpleTriangleMeshVertexColorQuantity::SimpleTriangleMeshVertexColorQuantity(std SimpleTriangleMesh& mesh_) : SimpleTriangleMeshColorQuantity(name, colors_, "vertex", mesh_) {} +void SimpleTriangleMeshVertexColorQuantity::buildVertexInfoGUI(size_t vInd) { + ImGui::TextUnformatted(name.c_str()); + ImGui::NextColumn(); + glm::vec3 val = colors.getValue(vInd); + ImGui::ColorEdit3("", &val[0], ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoPicker); + ImGui::SameLine(); + ImGui::Text("%.3f, %.3f, %.3f", val.r, val.g, val.b); + ImGui::NextColumn(); +} + void SimpleTriangleMeshVertexColorQuantity::createProgram() { // clang-format off program = render::engine->requestShader("SIMPLE_MESH", @@ -91,6 +105,16 @@ SimpleTriangleMeshFaceColorQuantity::SimpleTriangleMeshFaceColorQuantity(std::st colors.setTextureSize(parent.nFaces()); } +void SimpleTriangleMeshFaceColorQuantity::buildFaceInfoGUI(size_t fInd) { + ImGui::TextUnformatted(name.c_str()); + ImGui::NextColumn(); + glm::vec3 val = colors.getValue(fInd); + ImGui::ColorEdit3("", &val[0], ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoPicker); + ImGui::SameLine(); + ImGui::Text("%.3f, %.3f, %.3f", val.r, val.g, val.b); + ImGui::NextColumn(); +} + void SimpleTriangleMeshFaceColorQuantity::createProgram() { // clang-format off program = render::engine->requestShader("SIMPLE_MESH", diff --git a/src/simple_triangle_mesh_scalar_quantity.cpp b/src/simple_triangle_mesh_scalar_quantity.cpp index e13a2c94..92de0748 100644 --- a/src/simple_triangle_mesh_scalar_quantity.cpp +++ b/src/simple_triangle_mesh_scalar_quantity.cpp @@ -61,6 +61,13 @@ SimpleTriangleMeshVertexScalarQuantity::SimpleTriangleMeshVertexScalarQuantity(s DataType dataType_) : SimpleTriangleMeshScalarQuantity(name, values_, "vertex", mesh_, dataType_) {} +void SimpleTriangleMeshVertexScalarQuantity::buildVertexInfoGUI(size_t vInd) { + ImGui::TextUnformatted(name.c_str()); + ImGui::NextColumn(); + ImGui::Text("%g", values.getValue(vInd)); + ImGui::NextColumn(); +} + void SimpleTriangleMeshVertexScalarQuantity::createProgram() { // clang-format off program = render::engine->requestShader("SIMPLE_MESH", @@ -93,6 +100,13 @@ SimpleTriangleMeshFaceScalarQuantity::SimpleTriangleMeshFaceScalarQuantity(std:: values.setTextureSize(parent.nFaces()); } +void SimpleTriangleMeshFaceScalarQuantity::buildFaceInfoGUI(size_t fInd) { + ImGui::TextUnformatted(name.c_str()); + ImGui::NextColumn(); + ImGui::Text("%g", values.getValue(fInd)); + ImGui::NextColumn(); +} + void SimpleTriangleMeshFaceScalarQuantity::createProgram() { // clang-format off program = render::engine->requestShader("SIMPLE_MESH", diff --git a/test/src/misc_test.cpp b/test/src/misc_test.cpp index c7b00c1c..60d7dea4 100644 --- a/test/src/misc_test.cpp +++ b/test/src/misc_test.cpp @@ -206,4 +206,41 @@ TEST_F(PolyscopeTest, TestSlicePlane) { polyscope::removeAllSlicePlanes(); polyscope::removeAllStructures(); +} + + +// ============================================================ +// =============== Pick index encoding +// ============================================================ + +TEST_F(PolyscopeTest, PickIndexEncoding) { + // See the note in pick.ipp. We have multiple implementations of the pick index decoding logic, + // and this verifies they produce identical output. + + // indToVec takes size_t; cast explicitly to avoid linker issues on platforms where + // size_t and uint64_t have different mangled names (e.g. arm64: ulong vs ulonglong). + auto check = [](uint64_t pickStart, uint32_t primID) { + glm::vec3 expected = polyscope::pick::indToVec(static_cast(pickStart + primID)); + glm::vec3 actual = polyscope::pick::pickIndexToColor(pickStart, primID); + EXPECT_FLOAT_EQ(actual.x, expected.x); + EXPECT_FLOAT_EQ(actual.y, expected.y); + EXPECT_FLOAT_EQ(actual.z, expected.z); + }; + + check(0, 0); + check(0, 1); + check(0, 42); + check(0, (1u << 22) - 1u); // max value in low22 word + check(0, (1u << 22)); // overflows into med22 + check(5'000'000, 0); // pickStart > 2^22 + check(0x0000'00FF'FF00'0000ull, 0); // high bits set + + // --- Carry cases: pickStartLow + primID overflows uint32 --- + // Carry from low32 into high32 (the `carry` variable in the body): + check(0xFFFF0000u, 0x10000u); // low: 0xFFFF0000 + 0x10000 wraps -> carry=1 + check(0xFFFFFFFFu, 1u); // extreme: low wraps to 0 exactly -> carry=1 + check(0xFFFFFFFFu, 0xFFFFFFFFu); // both max -> low=0xFFFFFFFE, carry=1 + // Carry with non-zero pickStartHigh (tests idxHigh = pickStartHigh + carry): + check(0x1'0000'0000ull, 0xFFFFFFFFu); // high=1, low wraps -> idxHigh = 1 + 1 = 2 + check(0x1'FFFF'0000ull, 0x10000u); // high=1 + carry from low wrap } \ No newline at end of file From b4b266a0e87071f121020e8d369a0123e5224e2b Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 3 May 2026 18:17:12 -0700 Subject: [PATCH 13/17] initial support for updates in SimpleTriangleMesh --- include/polyscope/simple_triangle_mesh.h | 13 ++++ include/polyscope/simple_triangle_mesh.ipp | 28 +++++++++ .../simple_triangle_mesh_color_quantity.h | 14 +++++ .../simple_triangle_mesh_scalar_quantity.h | 14 +++++ src/simple_triangle_mesh.cpp | 5 ++ src/simple_triangle_mesh_color_quantity.cpp | 10 +++ src/simple_triangle_mesh_scalar_quantity.cpp | 10 +++ test/src/surface_mesh_test.cpp | 62 +++++++++++++++++++ 8 files changed, 156 insertions(+) diff --git a/include/polyscope/simple_triangle_mesh.h b/include/polyscope/simple_triangle_mesh.h index e7232013..c246b820 100644 --- a/include/polyscope/simple_triangle_mesh.h +++ b/include/polyscope/simple_triangle_mesh.h @@ -92,6 +92,19 @@ class SimpleTriangleMesh : public Structure { template void update(const V& newVertices, const F& newFaces); + // CPU-side update functions. + + // update only positions (vertex count must stay the same). + template + void updateVertexPositions(const V& newPositions); + + // update both vertices and faces; vertex/face counts may change + template + void updateMesh(const V& newVertices, const F& newFaces); + + // optionally pre-allocates capacity to avoid reallocations on future updateMesh() calls. + void reserve(size_t nVerts, size_t nFaces); + // Misc data static const std::string structureTypeName; diff --git a/include/polyscope/simple_triangle_mesh.ipp b/include/polyscope/simple_triangle_mesh.ipp index 05cb1b5a..5b76204a 100644 --- a/include/polyscope/simple_triangle_mesh.ipp +++ b/include/polyscope/simple_triangle_mesh.ipp @@ -49,6 +49,34 @@ void SimpleTriangleMesh::update(const V& newPositions, const F& newFaces) { faces.markHostBufferUpdated(); } +template +void SimpleTriangleMesh::updateVertexPositions(const V& newPositions) { + validateSize(newPositions, nVertices(), "newPositions"); + verticesData = standardizeVectorArray(newPositions); + vertices.markHostBufferUpdated(); + updateObjectSpaceBounds(); +} + +template +void SimpleTriangleMesh::updateMesh(const V& newVerts, const F& newFaces) { + std::vector vertsStd = standardizeVectorArray(newVerts); + if (vertsStd.size() > verticesData.capacity()) { + // amortized doubling on the CPU-side backing vectors. + verticesData.reserve(std::max(vertsStd.size(), 2 * verticesData.capacity())); + } + verticesData = std::move(vertsStd); + vertices.markHostBufferUpdated(); + + std::vector facesStd = standardizeVectorArray(newFaces); + if (facesStd.size() > facesData.capacity()) { + facesData.reserve(std::max(facesStd.size(), 2 * facesData.capacity())); + } + facesData = std::move(facesStd); + faces.markHostBufferUpdated(); + + updateObjectSpaceBounds(); +} + // ===================================================== // ============== Quantities // ===================================================== diff --git a/include/polyscope/simple_triangle_mesh_color_quantity.h b/include/polyscope/simple_triangle_mesh_color_quantity.h index d3b8d352..8ba13982 100644 --- a/include/polyscope/simple_triangle_mesh_color_quantity.h +++ b/include/polyscope/simple_triangle_mesh_color_quantity.h @@ -4,6 +4,7 @@ #include "polyscope/color_quantity.h" #include "polyscope/simple_triangle_mesh_quantity.h" +#include "polyscope/standardize_data_array.h" #include @@ -21,6 +22,19 @@ class SimpleTriangleMeshColorQuantity : public SimpleTriangleMeshQuantity, virtual void refresh() override; virtual std::string niceName() override; + // Throws if the quantity buffer size doesn't match the parent mesh's vertex/face count. + void checkQuantitySizeMatchesParentStructure(); + + // Update the color values, allowing the count to change (e.g. after updateMesh()). + // For same-size updates, prefer ColorQuantity::updateData() which validates the size. + template + void updateData(const V& newColors) { + std::vector newData = standardizeVectorArray(newColors); + colors.resize(newData.size()); + colors.data.assign(newData.begin(), newData.end()); + colors.markHostBufferUpdated(); + } + const std::string definedOn; protected: diff --git a/include/polyscope/simple_triangle_mesh_scalar_quantity.h b/include/polyscope/simple_triangle_mesh_scalar_quantity.h index 9a7623c0..ab42b461 100644 --- a/include/polyscope/simple_triangle_mesh_scalar_quantity.h +++ b/include/polyscope/simple_triangle_mesh_scalar_quantity.h @@ -6,6 +6,7 @@ #include "polyscope/render/color_maps.h" #include "polyscope/scalar_quantity.h" #include "polyscope/simple_triangle_mesh_quantity.h" +#include "polyscope/standardize_data_array.h" #include @@ -22,6 +23,19 @@ class SimpleTriangleMeshScalarQuantity : public SimpleTriangleMeshQuantity, virtual void refresh() override; virtual std::string niceName() override; + // Throws if the quantity buffer size doesn't match the parent mesh's vertex/face count. + void checkQuantitySizeMatchesParentStructure(); + + // Update the scalar values, allowing the count to change (e.g. after updateMesh()). + // For same-size updates, prefer ScalarQuantity::updateData() which validates the size. + template + void updateData(const V& newValues) { + std::vector newData = standardizeArray(newValues); + values.resize(newData.size()); + values.data.assign(newData.begin(), newData.end()); + values.markHostBufferUpdated(); + } + const std::string definedOn; protected: diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index 1cace4b5..dd904100 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -306,6 +306,11 @@ void SimpleTriangleMesh::updateObjectSpaceBounds() { objectSpaceLengthScale = 2 * std::sqrt(lengthScale); } +void SimpleTriangleMesh::reserve(size_t nVerts, size_t nFaces) { + verticesData.reserve(nVerts); + facesData.reserve(nFaces); +} + SimpleTriangleMeshPickResult SimpleTriangleMesh::interpretPickResult(const PickResult& rawResult) { if (rawResult.structure != this) { diff --git a/src/simple_triangle_mesh_color_quantity.cpp b/src/simple_triangle_mesh_color_quantity.cpp index 3300d9b5..fb2b4d34 100644 --- a/src/simple_triangle_mesh_color_quantity.cpp +++ b/src/simple_triangle_mesh_color_quantity.cpp @@ -21,6 +21,8 @@ SimpleTriangleMeshColorQuantity::SimpleTriangleMeshColorQuantity(std::string nam void SimpleTriangleMeshColorQuantity::draw() { if (!isEnabled()) return; + checkQuantitySizeMatchesParentStructure(); + if (program == nullptr) createProgram(); parent.setStructureUniforms(*program); @@ -31,6 +33,14 @@ void SimpleTriangleMeshColorQuantity::draw() { program->draw(); } +void SimpleTriangleMeshColorQuantity::checkQuantitySizeMatchesParentStructure() { + size_t expected = (definedOn == "vertex") ? parent.nVertices() : parent.nFaces(); + if (colors.size() != expected) { + exception("SimpleTriangleMesh color quantity '" + name + "' has " + std::to_string(colors.size()) + + " colors but parent mesh has " + std::to_string(expected) + " " + definedOn + "s"); + } +} + void SimpleTriangleMeshColorQuantity::buildColorOptionsUI() { ColorQuantity::buildColorOptionsUI(); ImGui::TextUnformatted("(no options available)"); diff --git a/src/simple_triangle_mesh_scalar_quantity.cpp b/src/simple_triangle_mesh_scalar_quantity.cpp index 92de0748..6492afd7 100644 --- a/src/simple_triangle_mesh_scalar_quantity.cpp +++ b/src/simple_triangle_mesh_scalar_quantity.cpp @@ -18,9 +18,19 @@ SimpleTriangleMeshScalarQuantity::SimpleTriangleMeshScalarQuantity(std::string n DataType dataType_) : SimpleTriangleMeshQuantity(name, mesh_, true), ScalarQuantity(*this, values_, dataType_), definedOn(definedOn_) {} +void SimpleTriangleMeshScalarQuantity::checkQuantitySizeMatchesParentStructure() { + size_t expected = (definedOn == "vertex") ? parent.nVertices() : parent.nFaces(); + if (values.size() != expected) { + exception("SimpleTriangleMesh scalar quantity '" + name + "' has " + std::to_string(values.size()) + + " values but parent mesh has " + std::to_string(expected) + " " + definedOn + "s"); + } +} + void SimpleTriangleMeshScalarQuantity::draw() { if (!isEnabled()) return; + checkQuantitySizeMatchesParentStructure(); + if (program == nullptr) createProgram(); parent.setStructureUniforms(*program); diff --git a/test/src/surface_mesh_test.cpp b/test/src/surface_mesh_test.cpp index ed2d3402..48eba571 100644 --- a/test/src/surface_mesh_test.cpp +++ b/test/src/surface_mesh_test.cpp @@ -727,3 +727,65 @@ TEST_F(PolyscopeTest, SimpleTriangleMeshFaceColor) { polyscope::removeAllStructures(); } + +TEST_F(PolyscopeTest, SimpleTriangleMeshUpdateFunctions) { + auto psMesh = registerSimpleTriangleMesh(); + size_t nV = psMesh->nVertices(); + size_t nF = psMesh->nFaces(); + + // updateVertexPositions: same count, renders fine + psMesh->updateVertexPositions(std::vector(nV, glm::vec3{0.f, 0.f, 0.f})); + EXPECT_EQ(psMesh->nVertices(), nV); + EXPECT_EQ(psMesh->nFaces(), nF); + polyscope::show(3); + + // updateMesh: shrink + psMesh->updateMesh(std::vector{{0,0,0},{1,0,0},{0,1,0}}, + std::vector{{0,1,2}}); + EXPECT_EQ(psMesh->nVertices(), 3u); + EXPECT_EQ(psMesh->nFaces(), 1u); + polyscope::show(3); + + // updateMesh: grow beyond original size + std::vector bigVerts = {{0,0,0},{1,0,0},{0,1,0},{0,0,1},{1,1,0}}; + std::vector bigFaces = {{0,1,2},{1,3,4},{2,3,0}}; + psMesh->updateMesh(bigVerts, bigFaces); + EXPECT_EQ(psMesh->nVertices(), 5u); + EXPECT_EQ(psMesh->nFaces(), 3u); + polyscope::show(3); + + polyscope::removeAllStructures(); +} + +TEST_F(PolyscopeTest, SimpleTriangleMeshQuantityUpdateAfterResize) { + auto psMesh = registerSimpleTriangleMesh(); + + // Add quantities at original size and enable them + auto qVS = psMesh->addVertexScalarQuantity("vScalar", std::vector(psMesh->nVertices(), 1.f)); + auto qFS = psMesh->addFaceScalarQuantity("fScalar", std::vector(psMesh->nFaces(), 2.f)); + auto qVC = psMesh->addVertexColorQuantity("vColor", std::vector(psMesh->nVertices(), glm::vec3{1,0,0})); + auto qFC = psMesh->addFaceColorQuantity("fColor", std::vector(psMesh->nFaces(), glm::vec3{0,1,0})); + qVS->setEnabled(true); + polyscope::show(3); + + // Resize the mesh + std::vector newVerts = {{0,0,0},{1,0,0},{0,1,0},{0,0,1},{1,1,0}}; + std::vector newFaces = {{0,1,2},{1,3,4},{2,3,0}}; + psMesh->updateMesh(newVerts, newFaces); + + // Update all quantities to match new counts + qVS->updateData(std::vector(psMesh->nVertices(), 3.f)); + qFS->updateData(std::vector(psMesh->nFaces(), 4.f)); + qVC->updateData(std::vector(psMesh->nVertices(), glm::vec3{0,0,1})); + qFC->updateData(std::vector(psMesh->nFaces(), glm::vec3{1,1,0})); + + EXPECT_EQ(qVS->values.size(), psMesh->nVertices()); + EXPECT_EQ(qFS->values.size(), psMesh->nFaces()); + EXPECT_EQ(qVC->colors.size(), psMesh->nVertices()); + EXPECT_EQ(qFC->colors.size(), psMesh->nFaces()); + + qFS->setEnabled(true); + polyscope::show(3); + + polyscope::removeAllStructures(); +} From 07b06212ccedeb608a998e77eff76163879f56b0 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 17:21:11 -0700 Subject: [PATCH 14/17] update for new managed buffer setup --- include/polyscope/simple_triangle_mesh.ipp | 45 ++++++------------- .../simple_triangle_mesh_color_quantity.h | 7 ++- .../simple_triangle_mesh_scalar_quantity.h | 7 ++- src/simple_triangle_mesh.cpp | 4 +- src/simple_triangle_mesh_color_quantity.cpp | 1 + src/simple_triangle_mesh_scalar_quantity.cpp | 1 + 6 files changed, 24 insertions(+), 41 deletions(-) diff --git a/include/polyscope/simple_triangle_mesh.ipp b/include/polyscope/simple_triangle_mesh.ipp index 5b76204a..51132795 100644 --- a/include/polyscope/simple_triangle_mesh.ipp +++ b/include/polyscope/simple_triangle_mesh.ipp @@ -26,53 +26,36 @@ SimpleTriangleMesh* registerSimpleTriangleMesh(std::string name, const V& vertex template void SimpleTriangleMesh::updateVertices(const V& newPositions) { validateSize(newPositions, vertices.size(), "newPositions"); - auto d = standardizeVectorArray(newPositions); - vertices.resize(d.size()); - vertices.setDataHost(d); - vertices.markHostBufferUpdated(); + vertices.setDataHost(standardizeVectorArray(newPositions)); } template void SimpleTriangleMesh::update(const V& newPositions, const F& newFaces) { - { - auto d = standardizeVectorArray(newPositions); - vertices.resize(d.size()); - vertices.setDataHost(d); - } - vertices.markHostBufferUpdated(); + auto vertsStd = standardizeVectorArray(newPositions); + vertices.resize(vertsStd.size()); // ManagedBuffer internally does amortized doubling to make this efficient + vertices.setDataHost(vertsStd); - { - auto d = standardizeVectorArray(newFaces); - faces.resize(d.size()); - faces.setDataHost(d); - } - faces.markHostBufferUpdated(); + auto facesStd = standardizeVectorArray(newFaces); + faces.resize(facesStd.size()); + faces.setDataHost(facesStd); } template void SimpleTriangleMesh::updateVertexPositions(const V& newPositions) { validateSize(newPositions, nVertices(), "newPositions"); - verticesData = standardizeVectorArray(newPositions); - vertices.markHostBufferUpdated(); + vertices.setDataHost(standardizeVectorArray(newPositions)); updateObjectSpaceBounds(); } template void SimpleTriangleMesh::updateMesh(const V& newVerts, const F& newFaces) { - std::vector vertsStd = standardizeVectorArray(newVerts); - if (vertsStd.size() > verticesData.capacity()) { - // amortized doubling on the CPU-side backing vectors. - verticesData.reserve(std::max(vertsStd.size(), 2 * verticesData.capacity())); - } - verticesData = std::move(vertsStd); - vertices.markHostBufferUpdated(); + auto vertsStd = standardizeVectorArray(newVerts); + vertices.resize(vertsStd.size()); // amortized doubling handled by ManagedBuffer + vertices.setDataHost(vertsStd); - std::vector facesStd = standardizeVectorArray(newFaces); - if (facesStd.size() > facesData.capacity()) { - facesData.reserve(std::max(facesStd.size(), 2 * facesData.capacity())); - } - facesData = std::move(facesStd); - faces.markHostBufferUpdated(); + auto facesStd = standardizeVectorArray(newFaces); + faces.resize(facesStd.size()); + faces.setDataHost(facesStd); updateObjectSpaceBounds(); } diff --git a/include/polyscope/simple_triangle_mesh_color_quantity.h b/include/polyscope/simple_triangle_mesh_color_quantity.h index 8ba13982..669301fe 100644 --- a/include/polyscope/simple_triangle_mesh_color_quantity.h +++ b/include/polyscope/simple_triangle_mesh_color_quantity.h @@ -29,10 +29,9 @@ class SimpleTriangleMeshColorQuantity : public SimpleTriangleMeshQuantity, // For same-size updates, prefer ColorQuantity::updateData() which validates the size. template void updateData(const V& newColors) { - std::vector newData = standardizeVectorArray(newColors); - colors.resize(newData.size()); - colors.data.assign(newData.begin(), newData.end()); - colors.markHostBufferUpdated(); + auto newData = standardizeVectorArray(newColors); + colors.resize(newData.size()); // amortized doubling handled by ManagedBuffer + colors.setDataHost(newData); } const std::string definedOn; diff --git a/include/polyscope/simple_triangle_mesh_scalar_quantity.h b/include/polyscope/simple_triangle_mesh_scalar_quantity.h index ab42b461..63f70f17 100644 --- a/include/polyscope/simple_triangle_mesh_scalar_quantity.h +++ b/include/polyscope/simple_triangle_mesh_scalar_quantity.h @@ -30,10 +30,9 @@ class SimpleTriangleMeshScalarQuantity : public SimpleTriangleMeshQuantity, // For same-size updates, prefer ScalarQuantity::updateData() which validates the size. template void updateData(const V& newValues) { - std::vector newData = standardizeArray(newValues); - values.resize(newData.size()); - values.data.assign(newData.begin(), newData.end()); - values.markHostBufferUpdated(); + auto newData = standardizeArray(newValues); + values.resize(newData.size()); // amortized doubling handled by ManagedBuffer + values.setDataHost(newData); } const std::string definedOn; diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index dd904100..fd7f1a16 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -307,8 +307,8 @@ void SimpleTriangleMesh::updateObjectSpaceBounds() { } void SimpleTriangleMesh::reserve(size_t nVerts, size_t nFaces) { - verticesData.reserve(nVerts); - facesData.reserve(nFaces); + vertices.setCapacity(nVerts); + faces.setCapacity(nFaces); } SimpleTriangleMeshPickResult SimpleTriangleMesh::interpretPickResult(const PickResult& rawResult) { diff --git a/src/simple_triangle_mesh_color_quantity.cpp b/src/simple_triangle_mesh_color_quantity.cpp index fb2b4d34..64baf8c2 100644 --- a/src/simple_triangle_mesh_color_quantity.cpp +++ b/src/simple_triangle_mesh_color_quantity.cpp @@ -112,6 +112,7 @@ SimpleTriangleMeshFaceColorQuantity::SimpleTriangleMeshFaceColorQuantity(std::st const std::vector& colors_, SimpleTriangleMesh& mesh_) : SimpleTriangleMeshColorQuantity(name, colors_, "face", mesh_) { + colors.setAsType(DeviceBufferType::Texture1d); colors.setTextureSize(parent.nFaces()); } diff --git a/src/simple_triangle_mesh_scalar_quantity.cpp b/src/simple_triangle_mesh_scalar_quantity.cpp index 6492afd7..c7a06e0b 100644 --- a/src/simple_triangle_mesh_scalar_quantity.cpp +++ b/src/simple_triangle_mesh_scalar_quantity.cpp @@ -107,6 +107,7 @@ SimpleTriangleMeshFaceScalarQuantity::SimpleTriangleMeshFaceScalarQuantity(std:: SimpleTriangleMesh& mesh_, DataType dataType_) : SimpleTriangleMeshScalarQuantity(name, values_, "face", mesh_, dataType_) { + values.setAsType(DeviceBufferType::Texture1d); values.setTextureSize(parent.nFaces()); } From 206ff289cd13a2164edc56f3d4117875f05ff138 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 19:32:45 -0700 Subject: [PATCH 15/17] add forced update option for object bounds --- include/polyscope/polyscope.h | 1 + include/polyscope/structure.h | 3 ++- src/polyscope.cpp | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/include/polyscope/polyscope.h b/include/polyscope/polyscope.h index d228c316..6450229b 100644 --- a/include/polyscope/polyscope.h +++ b/include/polyscope/polyscope.h @@ -209,6 +209,7 @@ void drawStructuresDelayed(); // need to call this directly. void processLazyProperties(); void processLazyPropertiesOutsideOfImGui(); +void forceRecomputeAllStructureBounds(); } // namespace polyscope diff --git a/include/polyscope/structure.h b/include/polyscope/structure.h index 10d39ed5..475151a8 100644 --- a/include/polyscope/structure.h +++ b/include/polyscope/structure.h @@ -118,6 +118,8 @@ class Structure : public render::ManagedBufferRegistry, public virtual WeakRefer std::tuple boundingBox(); // get axis-aligned bounding box float lengthScale(); // get characteristic length virtual bool hasExtents(); // bounding box and length scale are only meaningful if true + virtual void updateObjectSpaceBounds() = 0; // force an update of bbox and length scale, only rarely needs to be + // called if certain kinds of updates are performed // ==================================================================== // ==== Enabling, Selection, and Groups =============================== @@ -281,7 +283,6 @@ class Structure : public render::ManagedBufferRegistry, public virtual WeakRefer // The STRUCTURE is responsible for making sure updateObjectSpaceBounds() gets called any time the geometry changes std::tuple objectSpaceBoundingBox; float objectSpaceLengthScale; - virtual void updateObjectSpaceBounds() = 0; }; diff --git a/src/polyscope.cpp b/src/polyscope.cpp index 0cd27c2f..3bf6cfb6 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -760,6 +760,8 @@ void buildPolyscopeGui() { ImGui::Begin("Polyscope ", nullptr); if (ImGui::Button(ICON_LC_HOUSE " Reset View")) { + forceRecomputeAllStructureBounds(); + updateStructureExtents(); view::flyToHomeView(); } ImGui::SameLine(); @@ -1607,6 +1609,14 @@ void updateStructureExtents() { requestRedraw(); } +void forceRecomputeAllStructureBounds() { + for (auto& cat : state::structures) { + for (auto& x : cat.second) { + x.second->updateObjectSpaceBounds(); + } + } +} + namespace state { glm::vec3 center() { return 0.5f * (std::get<0>(state::boundingBox) + std::get<1>(state::boundingBox)); } } // namespace state From 21bea0a8cd4b0d39d0f4a52bad8e10afc77b6e4a Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 19:34:34 -0700 Subject: [PATCH 16/17] remove redundant updaters --- include/polyscope/simple_triangle_mesh.h | 8 -------- include/polyscope/simple_triangle_mesh.ipp | 20 -------------------- 2 files changed, 28 deletions(-) diff --git a/include/polyscope/simple_triangle_mesh.h b/include/polyscope/simple_triangle_mesh.h index c246b820..fe8582e5 100644 --- a/include/polyscope/simple_triangle_mesh.h +++ b/include/polyscope/simple_triangle_mesh.h @@ -86,14 +86,6 @@ class SimpleTriangleMesh : public Structure { // === Mutate - template - void updateVertices(const V& newPositions); - - template - void update(const V& newVertices, const F& newFaces); - - // CPU-side update functions. - // update only positions (vertex count must stay the same). template void updateVertexPositions(const V& newPositions); diff --git a/include/polyscope/simple_triangle_mesh.ipp b/include/polyscope/simple_triangle_mesh.ipp index 51132795..14b0aa93 100644 --- a/include/polyscope/simple_triangle_mesh.ipp +++ b/include/polyscope/simple_triangle_mesh.ipp @@ -23,28 +23,10 @@ SimpleTriangleMesh* registerSimpleTriangleMesh(std::string name, const V& vertex } -template -void SimpleTriangleMesh::updateVertices(const V& newPositions) { - validateSize(newPositions, vertices.size(), "newPositions"); - vertices.setDataHost(standardizeVectorArray(newPositions)); -} - -template -void SimpleTriangleMesh::update(const V& newPositions, const F& newFaces) { - auto vertsStd = standardizeVectorArray(newPositions); - vertices.resize(vertsStd.size()); // ManagedBuffer internally does amortized doubling to make this efficient - vertices.setDataHost(vertsStd); - - auto facesStd = standardizeVectorArray(newFaces); - faces.resize(facesStd.size()); - faces.setDataHost(facesStd); -} - template void SimpleTriangleMesh::updateVertexPositions(const V& newPositions) { validateSize(newPositions, nVertices(), "newPositions"); vertices.setDataHost(standardizeVectorArray(newPositions)); - updateObjectSpaceBounds(); } template @@ -56,8 +38,6 @@ void SimpleTriangleMesh::updateMesh(const V& newVerts, const F& newFaces) { auto facesStd = standardizeVectorArray(newFaces); faces.resize(facesStd.size()); faces.setDataHost(facesStd); - - updateObjectSpaceBounds(); } // ===================================================== From b02f85d15a086bf14a62c6586af6b80806922894 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 9 May 2026 22:48:13 -0700 Subject: [PATCH 17/17] naming fixes --- include/polyscope/simple_triangle_mesh.h | 2 +- src/simple_triangle_mesh.cpp | 2 +- test/src/surface_mesh_test.cpp | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/polyscope/simple_triangle_mesh.h b/include/polyscope/simple_triangle_mesh.h index fe8582e5..b17e3787 100644 --- a/include/polyscope/simple_triangle_mesh.h +++ b/include/polyscope/simple_triangle_mesh.h @@ -95,7 +95,7 @@ class SimpleTriangleMesh : public Structure { void updateMesh(const V& newVertices, const F& newFaces); // optionally pre-allocates capacity to avoid reallocations on future updateMesh() calls. - void reserve(size_t nVerts, size_t nFaces); + void reserveMeshCapacity(size_t nVerts, size_t nFaces); // Misc data static const std::string structureTypeName; diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index fd7f1a16..0691022f 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -306,7 +306,7 @@ void SimpleTriangleMesh::updateObjectSpaceBounds() { objectSpaceLengthScale = 2 * std::sqrt(lengthScale); } -void SimpleTriangleMesh::reserve(size_t nVerts, size_t nFaces) { +void SimpleTriangleMesh::reserveMeshCapacity(size_t nVerts, size_t nFaces) { vertices.setCapacity(nVerts); faces.setCapacity(nFaces); } diff --git a/test/src/surface_mesh_test.cpp b/test/src/surface_mesh_test.cpp index 48eba571..f5cfbb31 100644 --- a/test/src/surface_mesh_test.cpp +++ b/test/src/surface_mesh_test.cpp @@ -643,24 +643,24 @@ TEST_F(PolyscopeTest, SimpleTriangleMesUpdate) { polyscope::show(3); // make sure everything is populated // update just the locations - psMesh->updateVertices(std::vector(4)); + psMesh->updateVertexPositions(std::vector(4)); polyscope::show(3); // update the locations and faces - psMesh->update(std::vector(4), std::vector(4, glm::uvec3(0, 1, 2))); + psMesh->updateMesh(std::vector(4), std::vector(4, glm::uvec3(0, 1, 2))); polyscope::show(3); // do a bunch of resizing - psMesh->update(std::vector(12), std::vector(4, glm::uvec3(0, 1, 2))); + psMesh->updateMesh(std::vector(12), std::vector(4, glm::uvec3(0, 1, 2))); polyscope::show(3); - psMesh->update(std::vector(3), std::vector(4, glm::uvec3(0, 1, 2))); + psMesh->updateMesh(std::vector(3), std::vector(4, glm::uvec3(0, 1, 2))); polyscope::show(3); - psMesh->update(std::vector(3), std::vector(14, glm::uvec3(0, 1, 2))); + psMesh->updateMesh(std::vector(3), std::vector(14, glm::uvec3(0, 1, 2))); polyscope::show(3); - psMesh->update(std::vector(3), std::vector(1, glm::uvec3(0, 1, 2))); + psMesh->updateMesh(std::vector(3), std::vector(1, glm::uvec3(0, 1, 2))); polyscope::show(3); polyscope::removeAllStructures();