diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index afe26351..8a2cae4c 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); @@ -186,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/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..e487c70f 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(); } @@ -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 9edfe428..74e578f2 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; @@ -60,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.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.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/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..86df18b2 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), @@ -227,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/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/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/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/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/raw_color_alpha_render_image_quantity.h b/include/polyscope/raw_color_alpha_render_image_quantity.h index 03fc0cf0..4c801d3f 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; @@ -59,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 73a46707..74f96563 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; @@ -56,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/engine.h b/include/polyscope/render/engine.h index 38349f97..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 @@ -96,6 +102,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 @@ -391,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; @@ -414,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 26b97e63..499d9dd2 100644 --- a/include/polyscope/render/managed_buffer.h +++ b/include/polyscope/render/managed_buffer.h @@ -18,107 +18,165 @@ 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: + * Non-templated base class for ManagedBuffer. + * Contains all type-independent members, in particular the syncToDeviceIfNeeded() function which needs + * to be called without the type known. + */ +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. + // 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. + // 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; // only populated if texture + uint32_t sizeY = 0; // only populated if texture + uint32_t sizeZ = 0; // only populated if texture +}; + + +/* + * 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 { +class ManagedBuffer : public ManagedBufferBase, public virtual WeakReferrable { public: - // === Constructors - // (second variants are advanced versions which allow creation of multi-dimensional texture values) + // ======================================================================== + // == Constructors + // ======================================================================== - // Manage a buffer of data which is explicitly set externally. - ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, std::vector& data); + // 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::vector& data, - std::function computeFunc); - + ManagedBuffer(ManagedBufferRegistry* registry, const std::string& name, std::function computeFunc); ~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; - + // ======================================================================== + // == Size, capacity, and type management + // ======================================================================== - // 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). - // - // 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. + // The .size() of the buffer is the number of data elements it holds. // - // 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(); + // The .capacity() of the buffer is the number of data elements it has capacity for without needing to reallocate + // (similar to std::vector). // - // - std::vector& data; + // 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. + + // Get the size of the data in the buffer. Always <= capacity(). + size_t size() const; - // == Members for computed data + // 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; - // 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 + // 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. // - // When `dataGetsComputed = true`, we are in Case B, and computeFunc() must be set to a callback that does the - // lazy computing. + // 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); - bool dataGetsComputed; // if true, the value gets computed on-demand by calling computeFunc() - std::function computeFunc; // (optional) callback which populates the `data` buffer + // 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); - // sanity check helper - void checkInvalidValues(); + // Is it an attribute, texture1d, texture2d, etc? + DeviceBufferType getDeviceBufferType() const; - // mark as texture, set size + // 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; - // == 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. - void ensureHostBufferPopulated(); + // Resize the buffer to newSize elements. Sets hostBufferValid = true. + // + // 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 setTextureSize() for multidimensional textures. + bool resize(size_t newSize); - // 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(); + // ======================================================================== + // == Basic data access + // ======================================================================== - // If the contents of `data` are updated, this function MUST be called. It internally handles concerns like - // reflecting updates to the render buffer. - void markHostBufferUpdated(); + // 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. // 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. @@ -128,37 +186,65 @@ class ManagedBuffer : public virtual WeakReferrable { 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 - // 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) + // 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); - // Is it an attribute, texture1d, texture2d, etc? - DeviceBufferType getDeviceBufferType(); + // Returns a full copy of the host data, populating it from device if needed. + std::vector getDataCopy(); - std::string summaryString(); // for debugging // ======================================================================== - // == Direct access to the GPU (device-side) render attribute buffer + // == Low-level data access // ======================================================================== - // NOTE: this is only for attribute-accessed buffers (DeviceBufferType::Attribute). See the variants below for - // textures. + // 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. - // NOTE: This class follows the policy that once the render buffer is allocated, it is always immediately kept - // updated to reflect any external changes. + // 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(); - // 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(); + // 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(); - // 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(); + // 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() 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 + + // Raw pointer iteration support. Caller MUST call ensureHostBufferPopulated() before using these. + const T* begin() const; + const T* end() const; + + // ======================================================================== + // == 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(); + + // Sync host data to the device buffer if not up to date + void syncToDeviceIfNeeded() override; + + // Get an info string for debugging + std::string summaryString() const; + + // Throw exception if the buffer contains any values which are NaN or Inf as in polyscope::isInvalidValue(). + void checkInvalidValues(); // ======================================================================== // == Indexed views @@ -184,33 +270,39 @@ class ManagedBuffer : 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: 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(); + // 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. -protected: - // == Internal members + // 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(); - bool hostBufferIsPopulated; // true if the host buffer contains currently-valid data + // 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(); - std::shared_ptr renderAttributeBuffer; - std::shared_ptr renderTextureBuffer; + // 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(); - // 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 +protected: + // 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 @@ -220,21 +312,26 @@ class ManagedBuffer : public virtual WeakReferrable { existingIndexedViews; void updateIndexedViews(); void removeDeletedIndexedViews(); - - // == Internal helper functions - - void invalidateHostBuffer(); - bool deviceBufferTypeIsTexture(); - void checkDeviceBufferTypeIs(DeviceBufferType targetType); - void checkDeviceBufferTypeIsTexture(); + void invalidateIndexedViews(); 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; }; @@ -312,3 +409,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 cc4d2d49..ef0e1ad5 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: @@ -211,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 { @@ -220,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. @@ -281,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; @@ -309,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 7e06d358..9f6c4d3a 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; @@ -251,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 { @@ -261,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. @@ -324,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; @@ -353,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/render/opengl/shaders/surface_mesh_shaders.h b/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h index d89d3433..9185b6c1 100644 --- a/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h +++ b/include/polyscope/render/opengl/shaders/surface_mesh_shaders.h @@ -36,6 +36,9 @@ 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; +extern const ShaderReplacementRule SIMPLE_MESH_PROPAGATE_FACE_PICK; } // namespace backend_openGL3 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..83af60f8 100644 --- a/include/polyscope/scalar_quantity.ipp +++ b/include/polyscope/scalar_quantity.ipp @@ -8,8 +8,8 @@ namespace polyscope { template ScalarQuantity::ScalarQuantity(QuantityT& quantity_, const std::vector& values_, DataType dataType_) - : quantity(quantity_), values(&quantity, quantity.uniquePrefix() + "values", valuesData), valuesData(values_), - dataType(dataType_), dataRange(robustMinMax(values.data, 1e-5)), + : quantity(quantity_), values(&quantity, quantity.uniquePrefix() + "values", std::vector(values_)), + 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.h b/include/polyscope/simple_triangle_mesh.h index 4b0c6041..b17e3787 100644 --- a/include/polyscope/simple_triangle_mesh.h +++ b/include/polyscope/simple_triangle_mesh.h @@ -8,7 +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/types.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 @@ -18,9 +24,16 @@ namespace polyscope { 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 + 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 { @@ -50,15 +63,39 @@ 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 + 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 + SimpleTriangleMeshVertexColorQuantity* addVertexColorQuantity(std::string name, const T& values); + + template + SimpleTriangleMeshFaceColorQuantity* addFaceColorQuantity(std::string name, const T& values); + // === Mutate + // update only positions (vertex count must stay the same). template - void updateVertices(const V& newPositions); + void updateVertexPositions(const V& newPositions); + // update both vertices and faces; vertex/face counts may change template - void update(const V& newVertices, const F& newFaces); + void updateMesh(const V& newVertices, const F& newFaces); + + // optionally pre-allocates capacity to avoid reallocations on future updateMesh() calls. + void reserveMeshCapacity(size_t nVerts, size_t nFaces); // Misc data static const std::string structureTypeName; @@ -84,6 +121,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); @@ -93,15 +134,12 @@ 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; PersistentValue backFacePolicy; PersistentValue backFaceColor; + PersistentValue selectionMode; // Drawing related things // if nullptr, prepare() (resp. preparePick()) needs to be called @@ -112,13 +150,19 @@ 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, + 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; - glm::vec3 pickColor; + size_t pickStart = 0; }; diff --git a/include/polyscope/simple_triangle_mesh.ipp b/include/polyscope/simple_triangle_mesh.ipp index dc284bef..14b0aa93 100644 --- a/include/polyscope/simple_triangle_mesh.ipp +++ b/include/polyscope/simple_triangle_mesh.ipp @@ -24,22 +24,53 @@ SimpleTriangleMesh* registerSimpleTriangleMesh(std::string name, const V& vertex template -void SimpleTriangleMesh::updateVertices(const V& newPositions) { - validateSize(newPositions, vertices.size(), "newPositions"); - vertices.data = standardizeVectorArray(newPositions); - vertices.markHostBufferUpdated(); +void SimpleTriangleMesh::updateVertexPositions(const V& newPositions) { + validateSize(newPositions, nVertices(), "newPositions"); + vertices.setDataHost(standardizeVectorArray(newPositions)); } template -void SimpleTriangleMesh::update(const V& newPositions, const F& newFaces) { +void SimpleTriangleMesh::updateMesh(const V& newVerts, const F& newFaces) { + auto vertsStd = standardizeVectorArray(newVerts); + vertices.resize(vertsStd.size()); // amortized doubling handled by ManagedBuffer + vertices.setDataHost(vertsStd); - vertices.data = standardizeVectorArray(newPositions); - vertices.markHostBufferUpdated(); + auto facesStd = standardizeVectorArray(newFaces); + faces.resize(facesStd.size()); + faces.setDataHost(facesStd); +} + +// ===================================================== +// ============== Quantities +// ===================================================== - faces.data = standardizeVectorArray(newFaces); - faces.markHostBufferUpdated(); +template +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 +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) { 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..669301fe --- /dev/null +++ b/include/polyscope/simple_triangle_mesh_color_quantity.h @@ -0,0 +1,74 @@ +// 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 "polyscope/standardize_data_array.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 buildColorOptionsUI() override; + 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) { + auto newData = standardizeVectorArray(newColors); + colors.resize(newData.size()); // amortized doubling handled by ManagedBuffer + colors.setDataHost(newData); + } + + const std::string definedOn; + +protected: + 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; + virtual void buildVertexInfoGUI(size_t vInd) override; +}; + + +// ======================================================== +// ========== Face Color ========== +// ======================================================== + +class SimpleTriangleMeshFaceColorQuantity : public SimpleTriangleMeshColorQuantity { +public: + SimpleTriangleMeshFaceColorQuantity(std::string name, const std::vector& colors, + SimpleTriangleMesh& mesh); + + virtual void createProgram() override; + virtual void buildFaceInfoGUI(size_t fInd) override; +}; + + +} // 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..ab8a0116 --- /dev/null +++ b/include/polyscope/simple_triangle_mesh_quantity.h @@ -0,0 +1,25 @@ +// 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 + + // 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 new file mode 100644 index 00000000..63f70f17 --- /dev/null +++ b/include/polyscope/simple_triangle_mesh_scalar_quantity.h @@ -0,0 +1,75 @@ +// 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 "polyscope/standardize_data_array.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; + + // 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) { + auto newData = standardizeArray(newValues); + values.resize(newData.size()); // amortized doubling handled by ManagedBuffer + values.setDataHost(newData); + } + + const std::string definedOn; + +protected: + std::shared_ptr program; + + 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; + virtual void buildVertexInfoGUI(size_t vInd) 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; + virtual void buildFaceInfoGUI(size_t fInd) override; +}; + + +} // namespace polyscope 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/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/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/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.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..0f4a5bba 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(); } @@ -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()); } @@ -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(); } @@ -232,10 +236,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(); @@ -295,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()); } @@ -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_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/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/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/color_image_quantity.cpp b/src/color_image_quantity.cpp index 1046ce25..9652a720 100644 --- a/src/color_image_quantity.cpp +++ b/src/color_image_quantity.cpp @@ -13,8 +13,10 @@ 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.setAsType(DeviceBufferType::Texture2d); colors.setTextureSize(dimX, dimY); } @@ -55,7 +57,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() { @@ -74,7 +76,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 9a6b39fc..2d69bd80 100644 --- a/src/color_render_image_quantity.cpp +++ b/src/color_render_image_quantity.cpp @@ -16,7 +16,8 @@ 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.setAsType(DeviceBufferType::Texture2d); colors.setTextureSize(dimX, dimY); } @@ -72,11 +73,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 fbae2a74..0c45a231 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"), + edgeTipInds(this, uniquePrefix() + "edgeTipInds"), + 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.resize(edges_.size()); + edgeTipInds.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.setHostValue(iE, nA); + edgeTipInds.setHostValue(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(); } @@ -326,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); @@ -345,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 != ""); @@ -353,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 != ""); @@ -372,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); } } @@ -387,13 +389,13 @@ 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(); @@ -537,7 +539,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); } @@ -546,7 +548,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 9b65fcd2..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()); @@ -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() { @@ -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()); @@ -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 65fcf6c0..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", nodeAverageValuesData) {} + 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()); @@ -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/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 edd7a60b..9837770b 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)), @@ -200,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); } } @@ -386,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); } @@ -395,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/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/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 diff --git a/src/raw_color_alpha_render_image_quantity.cpp b/src/raw_color_alpha_render_image_quantity.cpp index 89190ab6..f278e1c5 100644 --- a/src/raw_color_alpha_render_image_quantity.cpp +++ b/src/raw_color_alpha_render_image_quantity.cpp @@ -16,8 +16,9 @@ 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.setAsType(DeviceBufferType::Texture2d); colors.setTextureSize(dimX, dimY); } @@ -75,8 +76,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 a3b32b0b..b09238a3 100644 --- a/src/raw_color_render_image_quantity.cpp +++ b/src/raw_color_render_image_quantity.cpp @@ -16,7 +16,8 @@ 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.setAsType(DeviceBufferType::Texture2d); colors.setTextureSize(dimX, dimY); } @@ -69,8 +70,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 67dbc7f2..0cadf77e 100644 --- a/src/render/managed_buffer.cpp +++ b/src/render/managed_buffer.cpp @@ -15,10 +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(data_), dataGetsComputed(false), - hostBufferIsPopulated(true) { +ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::string& name_, std::vector data_) + : ManagedBufferBase(registry_, name_, /*dataGetsComputed=*/false, /*hostBufferValid=*/true), + data(std::move(data_)) { + + managedCapacity = data.size(); + currentSize = data.size(); if (registry) { registry->addManagedBuffer(this); @@ -27,10 +77,13 @@ 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), - computeFunc(computeFunc_), hostBufferIsPopulated(false) { + : ManagedBufferBase(registry_, name_, /*dataGetsComputed=*/true, /*hostBufferValid=*/false) { + + computeFunc = computeFunc_; + managedCapacity = 0; + currentSize = 0; if (registry) { registry->addManagedBuffer(this); @@ -46,34 +99,55 @@ 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 -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_; +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_; @@ -86,22 +160,94 @@ std::array ManagedBuffer::getTextureSize() const { } template -void ManagedBuffer::ensureHostBufferPopulated() { +size_t ManagedBuffer::capacity() const { + return managedCapacity; +} - switch (currentCanonicalDataSource()) { - case CanonicalDataSource::HostData: - // good to go, nothing needs to be done - break; +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 setTextureSize() instead"); - case CanonicalDataSource::NeedsCompute: + 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. + // Only copy existing data to host if there is data to preserve. + 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(); + } - // compute it - computeFunc(); + // 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.resize(newCapacity); + managedCapacity = newCapacity; + currentSize = newSize; - break; + hostBufferValid = true; + deviceBufferValid = false; + + if (deviceBufferType == DeviceBufferType::Texture1d) { + sizeX = static_cast(newSize); + } + + return true; + } else { + // 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); + } + + 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 < 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 (!isInNeedsComputeState()) { + ensureHostBufferPopulated(); + } + + // Resize data to the new capacity (maintaining the invariant data.size() == managedCapacity). + data.resize(newCapacity); + managedCapacity = newCapacity; + + hostBufferValid = true; + deviceBufferValid = false; +} + + +template +void ManagedBuffer::ensureHostBufferPopulated() { - case CanonicalDataSource::RenderBuffer: + if (hostBufferValid) { + // good to go, nothing needs to be done + 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"); @@ -113,42 +259,163 @@ 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::ensureHostBufferAllocated() { - data.resize(size()); +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::getPopulatedHostBufferRef() { +std::vector ManagedBuffer::getDataCopy() { ensureHostBufferPopulated(); - return data; + // 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 (!hostBufferValid) exception("ManagedBuffer " + name + " begin() called without ensureHostBufferPopulated()"); +#endif + return data.data(); +} + +template +const T* ManagedBuffer::end() const { +#ifndef NDEBUG + if (!hostBufferValid) exception("ManagedBuffer " + name + " end() called without ensureHostBufferPopulated()"); +#endif + return data.data() + currentSize; +} + +template +T ManagedBuffer::getHostValue(size_t ind) const { +#ifndef NDEBUG + 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()"); +#endif + data[ind] = val; + // 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); } template void ManagedBuffer::markHostBufferUpdated() { - hostBufferIsPopulated = true; + hostBufferValid = true; + deviceBufferValid = false; + invalidateIndexedViews(); + requestRedraw(); +} - // If the data is stored in the device-side buffers, update it as needed - 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; } + + + // Update indexed views + if (!indexedViewsValid) { + + if(deviceBufferValid) { // always true now at this point + + // TODO do an on-device update of indexed views - if (deviceBufferType == DeviceBufferType::Attribute) { - updateIndexedViews(); - requestRedraw(); + // On-device update not implemented yet, copy it to host and do the update from the host + ensureHostBufferPopulated(); + updateIndexedViews(); // sets indexedViewsValid = true + } } } @@ -160,22 +427,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]; - break; + } - 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]; - break; - - 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. @@ -183,10 +448,8 @@ 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 } @@ -208,69 +471,27 @@ 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; +size_t ManagedBuffer::size() const { + 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; - 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 = ""; 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"; @@ -296,8 +517,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; } @@ -311,9 +532,11 @@ std::shared_ptr ManagedBuffer::getRenderAttributeBuf checkDeviceBufferTypeIs(DeviceBufferType::Attribute); if (!renderAttributeBuffer) { - ensureHostBufferPopulated(); // warning: the order of these matters because of how hostBufferPopulated works + ensureHostBufferPopulated(); renderAttributeBuffer = generateAttributeBuffer(render::engine); + renderAttributeBuffer->reserveCapacity(managedCapacity); renderAttributeBuffer->setData(data); + deviceBufferValid = true; } return renderAttributeBuffer; } @@ -323,8 +546,7 @@ std::shared_ptr ManagedBuffer::getRenderTextureBuffer( checkDeviceBufferTypeIsTexture(); if (!renderTextureBuffer) { - ensureHostBufferPopulated(); // warning: the order of these matters because of how hostBufferPopulated works - + ensureHostBufferPopulated(); renderTextureBuffer = generateTextureBuffer(deviceBufferType, render::engine); // templatize this? @@ -333,7 +555,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); @@ -344,6 +568,7 @@ std::shared_ptr ManagedBuffer::getRenderTextureBuffer( } renderTextureBuffer->setData(data); + deviceBufferValid = true; } return renderTextureBuffer; } @@ -352,8 +577,9 @@ template void ManagedBuffer::markRenderAttributeBufferUpdated() { checkDeviceBufferTypeIs(DeviceBufferType::Attribute); - invalidateHostBuffer(); - updateIndexedViews(); + invalidateHostBuffer(); // also clears data + deviceBufferValid = true; + invalidateIndexedViews(); requestRedraw(); } @@ -361,7 +587,9 @@ template void ManagedBuffer::markRenderTextureBufferUpdated() { checkDeviceBufferTypeIsTexture(); - invalidateHostBuffer(); + invalidateHostBuffer(); // also clears data + deviceBufferValid = true; + invalidateIndexedViews(); requestRedraw(); } @@ -429,10 +657,15 @@ 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. + // 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(); } @@ -452,42 +685,25 @@ void ManagedBuffer::removeDeletedIndexedViews() { template void ManagedBuffer::invalidateHostBuffer() { - hostBufferIsPopulated = false; + ManagedBufferBase::invalidateHostBuffer(); data.clear(); } template -bool ManagedBuffer::deviceBufferTypeIsTexture() { - return ((deviceBufferType == DeviceBufferType::Texture1d) || (deviceBufferType == DeviceBufferType::Texture2d) || - (deviceBufferType == DeviceBufferType::Texture3d)); -} - -template -void ManagedBuffer::checkDeviceBufferTypeIs(DeviceBufferType targetType) { - if (targetType != deviceBufferType) { - exception("ManagedBuffer " + name + " has wrong type for this operation. Expected " + - deviceBufferTypeName(targetType) + " but is " + deviceBufferTypeName(deviceBufferType)); - } -} - -template -void ManagedBuffer::checkDeviceBufferTypeIsTexture() { - 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; } @@ -502,22 +718,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/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index ef4877e2..3f068b04 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" @@ -73,19 +74,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 +415,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 +435,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 +454,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 +473,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."); } @@ -776,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) { @@ -803,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}); } @@ -835,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(); @@ -857,6 +861,7 @@ void GLShaderProgram::setAttribute(std::string name, std::shared_ptrbind(); @@ -1456,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) { @@ -1471,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; } @@ -1656,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) { } @@ -2233,7 +2249,10 @@ 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); + 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 608f0133..88cdf48a 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" @@ -246,21 +247,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 +620,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 +645,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 +667,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 +697,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()); @@ -1210,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) { @@ -1237,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}); } @@ -1281,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(); @@ -1305,6 +1313,7 @@ void GLShaderProgram::setAttribute(std::string name, std::shared_ptrbind(); @@ -2001,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 @@ -2017,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; } @@ -2204,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); @@ -2716,6 +2736,9 @@ 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); + 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 860cb2fc..6b842c6a 100644 --- a/src/render/opengl/shaders/surface_mesh_shaders.cpp +++ b/src/render/opengl/shaders/surface_mesh_shaders.cpp @@ -731,6 +731,62 @@ 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}} +); + +// 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/render_image_quantity_base.cpp b/src/render_image_quantity_base.cpp index 356356dd..fb57174b 100644 --- a/src/render_image_quantity_base.cpp +++ b/src/render_image_quantity_base.cpp @@ -12,13 +12,16 @@ 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.setAsType(DeviceBufferType::Texture2d); depths.setTextureSize(dimX, dimY); if (hasNormals) { + normals.setAsType(DeviceBufferType::Texture2d); normals.setTextureSize(dimX, dimY); } @@ -52,14 +55,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(); } @@ -127,7 +128,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..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); } @@ -52,7 +53,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 +66,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 2ed82796..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); } @@ -65,9 +66,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 @@ -87,11 +88,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 549a2ce4..0691022f 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -5,6 +5,9 @@ #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 "polyscope/view.h" #include "imgui.h" @@ -21,14 +24,13 @@ 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), - 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(); + } } @@ -187,7 +200,7 @@ void SimpleTriangleMesh::ensureRenderProgramPrepared() { render::engine->addMaterialRules(getMaterial(), addSimpleTriangleMeshRules( { - "SHADE_BASECOLOR", "COMPUTE_SHADE_NORMAL_FROM_POSITION", "PROJ_AND_INV_PROJ_MAT" + "SHADE_BASECOLOR" } ) ) @@ -205,29 +218,33 @@ void SimpleTriangleMesh::ensurePickProgramPrepared() { if (pickProgram) return; // clang-format off - pickProgram = render::engine->requestShader("SIMPLE_MESH", - addSimpleTriangleMeshRules( - { - "SHADECOLOR_FROM_UNIFORM", "COMPUTE_SHADE_NORMAL_FROM_POSITION", "PROJ_AND_INV_PROJ_MAT" - } - , 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) { 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 @@ -255,7 +272,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()); } @@ -274,7 +291,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); } @@ -283,23 +300,100 @@ 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); } +void SimpleTriangleMesh::reserveMeshCapacity(size_t nVerts, size_t nFaces) { + vertices.setCapacity(nVerts); + faces.setCapacity(nFaces); +} + 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; } @@ -307,7 +401,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); + } } @@ -347,5 +469,47 @@ 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 + +SimpleTriangleMeshVertexScalarQuantity* +SimpleTriangleMesh::addVertexScalarQuantityImpl(std::string name, const std::vector& data, DataType type) { + checkForQuantityWithNameAndDeleteOrError(name); + SimpleTriangleMeshVertexScalarQuantity* q = new SimpleTriangleMeshVertexScalarQuantity(name, data, *this, type); + addQuantity(q); + return q; +} + +SimpleTriangleMeshFaceScalarQuantity* +SimpleTriangleMesh::addFaceScalarQuantityImpl(std::string name, const std::vector& data, DataType type) { + checkForQuantityWithNameAndDeleteOrError(name); + 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; +} + } // 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..64baf8c2 --- /dev/null +++ b/src/simple_triangle_mesh_color_quantity.cpp @@ -0,0 +1,147 @@ +// 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 { + +// ======================================================== +// ========== Color Quantity Base ========== +// ======================================================== + +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; + + checkQuantitySizeMatchesParentStructure(); + + if (program == nullptr) createProgram(); + + parent.setStructureUniforms(*program); + parent.setSimpleTriangleMeshUniforms(*program); + setColorUniforms(*program); + render::engine->setMaterialUniforms(*program, parent.getMaterial()); + + 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)"); +} + +void SimpleTriangleMeshColorQuantity::buildCustomUI() { + ImGui::SameLine(); + if (ImGui::Button("Options")) { + ImGui::OpenPopup("OptionsPopup"); + } + if (ImGui::BeginPopup("OptionsPopup")) { + buildColorOptionsUI(); + ImGui::EndPopup(); + } + buildColorUI(); +} + +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::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", + render::engine->addMaterialRules(parent.getMaterial(), + addColorRules( + parent.addSimpleTriangleMeshRules( + {"MESH_PROPAGATE_COLOR", "SHADE_COLOR"} + ) + ) + ) + ); + // clang-format on + + parent.setSimpleTriangleMeshProgramGeometryAttributes(*program); + program->setAttribute("a_color", colors.getRenderAttributeBuffer()); + render::engine->setMaterial(*program, parent.getMaterial()); +} + + +// ======================================================== +// ========== Face Color ========== +// ======================================================== + +SimpleTriangleMeshFaceColorQuantity::SimpleTriangleMeshFaceColorQuantity(std::string name, + const std::vector& colors_, + SimpleTriangleMesh& mesh_) + : SimpleTriangleMeshColorQuantity(name, colors_, "face", mesh_) { + colors.setAsType(DeviceBufferType::Texture1d); + 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", + 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_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..c7a06e0b --- /dev/null +++ b/src/simple_triangle_mesh_scalar_quantity.cpp @@ -0,0 +1,140 @@ +// 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 { + +// ======================================================== +// ========== 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_) {} + +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); + 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::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::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", + render::engine->addMaterialRules(parent.getMaterial(), + parent.addSimpleTriangleMeshRules( + addScalarRules( + {"MESH_PROPAGATE_VALUE"} + ) + ) + ) + ); + // clang-format on + + parent.setSimpleTriangleMeshProgramGeometryAttributes(*program); + program->setAttribute("a_value", values.getRenderAttributeBuffer()); + program->setTextureFromColormap("t_colormap", cMap.get()); + render::engine->setMaterial(*program, parent.getMaterial()); +} + + +// ======================================================== +// ========== Face Scalar ========== +// ======================================================== + +SimpleTriangleMeshFaceScalarQuantity::SimpleTriangleMeshFaceScalarQuantity(std::string name, + const std::vector& values_, + SimpleTriangleMesh& mesh_, + DataType dataType_) + : SimpleTriangleMeshScalarQuantity(name, values_, "face", mesh_, dataType_) { + values.setAsType(DeviceBufferType::Texture1d); + 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", + 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/src/slice_plane.cpp b/src/slice_plane.cpp index f84f4185..bc56d2d8 100644 --- a/src/slice_plane.cpp +++ b/src/slice_plane.cpp @@ -245,13 +245,15 @@ 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(); } - 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 a5436a5e..b0467136 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"), + 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_), @@ -88,13 +88,13 @@ void SparseVolumeGrid::checkForDuplicateCells() { void SparseVolumeGrid::computeCellPositions() { size_t n = occupiedCellsData.size(); - cellPositionsData.resize(n); - cellIndicesData.resize(n); + cellPositions.resize(n); + cellIndices.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.setHostValue(i, origin + (glm::vec3(ijk) + 0.5f) * gridCellWidth); + cellIndices.setHostValue(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].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].setHostValue(i, nodeToIndex[nodeIjk]); } } } @@ -366,8 +366,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()); } @@ -498,23 +498,24 @@ 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); } void SparseVolumeGrid::updateObjectSpaceBounds() { - if (cellPositionsData.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 = cellPositionsData[0]; - glm::vec3 bboxMax = cellPositionsData[0]; - for (const glm::vec3& p : cellPositionsData) { + 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 +558,8 @@ SparseVolumeGridPickResult SparseVolumeGrid::interpretPickResult(const PickResul glm::vec3 localPos = (rawResult.position - origin) / gridCellWidth; // Find the cell index - glm::ivec3 cellInd3 = cellIndicesData[rawResult.localIndex]; + cellIndices.ensureHostBufferPopulated(); + glm::ivec3 cellInd3 = cellIndices.getHostValue(rawResult.localIndex); // Fractional position within cell [0,1] glm::vec3 fractional = localPos - glm::vec3(cellInd3); @@ -789,8 +791,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..4c5336e1 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()); } @@ -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); } @@ -177,17 +178,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 a8fa78a1..6954b5a5 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"), // 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"), +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)), +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"), +edgeIsReal( this, uniquePrefix() + "edgeIsReal"), // 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,8 @@ SurfaceMesh::SurfaceMesh(std::string name_, const std::vector& vertex const std::vector& faceIndsEntries_, const std::vector& faceIndsStart_) : SurfaceMesh(name_) { - vertexPositionsData = 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_) { - vertexPositionsData = 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 - 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.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 - triangleVertexIndsData[3 * iTriFace + 0] = vRoot; - triangleVertexIndsData[3 * iTriFace + 1] = vB; - triangleVertexIndsData[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++) triangleFaceIndsData[3 * iTriFace + k] = iF; + for (size_t k = 0; k < 3; k++) triangleFaceInds.setHostValue(3 * iTriFace + k, static_cast(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.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++) edgeIsRealData[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,13 @@ 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(); + nEdgesCount = psEdgeInd; } void SurfaceMesh::countEdges() { @@ -269,6 +267,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 +280,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 +303,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,9 +317,9 @@ 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); } } @@ -328,8 +328,9 @@ void SurfaceMesh::computeTriangleCornerInds() { 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,9 +344,9 @@ 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); } } } @@ -355,8 +356,9 @@ void SurfaceMesh::computeTriangleAllVertexInds() { 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,9 +386,9 @@ 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); } } } @@ -396,8 +398,9 @@ void SurfaceMesh::computeTriangleAllHalfedgeInds() { 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,9 +421,9 @@ 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); } } } @@ -440,7 +443,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,20 +451,20 @@ 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(); @@ -471,18 +474,18 @@ 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(); @@ -492,7 +495,7 @@ 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,22 +505,22 @@ 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 +532,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,13 +545,13 @@ 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(); @@ -557,8 +561,10 @@ 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,7 +572,7 @@ 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); } } @@ -581,7 +587,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,16 +595,16 @@ 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(); @@ -612,7 +618,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,16 +626,16 @@ 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(); @@ -680,8 +686,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 +707,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)); @@ -878,29 +884,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 != "") { @@ -969,9 +975,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 +1002,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 +1030,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 +1493,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 +1502,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..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()); @@ -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]; @@ -225,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); } @@ -257,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..12ff80cd 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); @@ -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) { @@ -353,17 +354,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 d6de8550..3c30be73 100644 --- a/src/surface_vector_quantity.cpp +++ b/src/surface_vector_quantity.cpp @@ -212,6 +212,7 @@ std::vector oneFormToFaceTangentVectors(SurfaceMesh& mesh, const std: mesh.defaultFaceTangentBasisX.ensureHostBufferPopulated(); mesh.defaultFaceTangentBasisY.ensureHostBufferPopulated(); mesh.triangleAllEdgeInds.ensureHostBufferPopulated(); + mesh.triangleVertexInds.ensureHostBufferPopulated(); std::vector mappedVectorField(mesh.nFaces()); @@ -220,17 +221,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 +239,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 +257,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), + mesh_.defaultFaceTangentBasisX.getDataCopy(), + mesh_.defaultFaceTangentBasisY.getDataCopy(), + 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 15529f70..5fb666c4 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_), @@ -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) { @@ -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,9 +374,12 @@ void VolumeGrid::computeGridPlaneReferenceGeometry() { } - gridPlaneReferencePositions.markHostBufferUpdated(); - gridPlaneReferenceNormals.markHostBufferUpdated(); - gridPlaneAxisInds.markHostBufferUpdated(); + gridPlaneReferencePositions.resize(positionsVec.size()); + gridPlaneReferencePositions.setDataHost(positionsVec); + gridPlaneReferenceNormals.resize(normalsVec.size()); + gridPlaneReferenceNormals.setDataHost(normalsVec); + gridPlaneAxisInds.resize(axisIndsVec.size()); + gridPlaneAxisInds.setDataHost(axisIndsVec); } // === Option getters and setters diff --git a/src/volume_grid_scalar_quantity.cpp b/src/volume_grid_scalar_quantity.cpp index 769a1957..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); } @@ -148,15 +149,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() { @@ -165,7 +166,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 +216,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) { @@ -282,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); } @@ -359,15 +362,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 dcd6bc1d..73792565 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"), +triangleFaceInds( this, uniquePrefix() + "triangleFaceInds"), +triangleCellInds( this, uniquePrefix() + "triangleCellInds"), // internal triangle data for rendering -baryCoord( this, uniquePrefix() + "baryCoord", baryCoordData), -edgeIsReal( this, uniquePrefix() + "edgeIsReal", edgeIsRealData), -faceType( this, uniquePrefix() + "faceType", faceTypeData), +baryCoord( this, uniquePrefix() + "baryCoord"), +edgeIsReal( this, uniquePrefix() + "edgeIsReal"), +faceType( this, uniquePrefix() + "faceType"), // 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()), @@ -534,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); @@ -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); } } @@ -819,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; @@ -863,28 +857,28 @@ 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 + 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.data[3 * iData + k][c] = faceRealEdges[f][j][c] ? 1.0f : 0.0f; - } + edgeIsReal.setHostValue(3 * iData + k, eReal); } } // 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++; } @@ -949,7 +943,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++) { @@ -961,14 +955,14 @@ 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++; } } @@ -981,7 +975,7 @@ void VolumeMesh::computeCellCenters() { vertexPositions.ensureHostBufferPopulated(); - cellCenters.data.resize(nCells()); + cellCenters.resize(nCells()); for (size_t iC = 0; iC < nCells(); iC++) { @@ -991,13 +985,13 @@ 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(); @@ -1219,7 +1213,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); } @@ -1228,7 +1222,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..e4708a9c 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 @@ -122,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()); } @@ -175,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 67085a30..6a963851 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); @@ -257,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()); } @@ -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 @@ -348,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()); } 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 diff --git a/test/src/surface_mesh_test.cpp b/test/src/surface_mesh_test.cpp index 16b2ca21..f5cfbb31 100644 --- a/test/src/surface_mesh_test.cpp +++ b/test/src/surface_mesh_test.cpp @@ -643,24 +643,148 @@ 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(); +} + +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(); +} + +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(); +} + +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(); 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;