From d49c18532c488b94883824bb6b81fb28ee7aa2f4 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 11 Dec 2025 16:32:26 +0000 Subject: [PATCH 01/46] Enables the wireframe mode, per-model --- examples/hexgrid.cpp | 5 +++-- mplot/VisualModelBase.h | 5 +++++ mplot/VisualModelImplMX.h | 9 +++++++++ mplot/VisualModelImplNoMX.h | 9 +++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/examples/hexgrid.cpp b/examples/hexgrid.cpp index 4153c741..223a1c10 100644 --- a/examples/hexgrid.cpp +++ b/examples/hexgrid.cpp @@ -41,14 +41,15 @@ int main() // Make some dummy data (a sine wave) to make an interesting surface std::vector data(hg.num(), 0.0f); - for (unsigned int ri=0; ri1 + for (unsigned int ri = 0; ri < hg.num(); ++ri) { + data[ri] = 0.05f + 0.05f * std::sin (20.0f * hg.d_x[ri]) * std::sin (10.0f * hg.d_y[ri]) ; // Range 0->1 } // Add a HexGridVisual to display the HexGrid within the sm::Visual scene sm::vec offset = { 0.0f, -0.05f, 0.0f }; auto hgv = std::make_unique>(&hg, offset); v.bindmodel (hgv); + hgv->wireframe (true); hgv->cm.setType (mplot::ColourMapType::Ice); hgv->setScalarData (&data); hgv->hexVisMode = mplot::HexVisMode::HexInterp; // Or sm::HexVisMode::Triangles for a smoother surface plot diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 4c262ecd..d5191689 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -64,6 +64,7 @@ namespace mplot postVertexInitRequired, twodimensional, // If true, then this VisualModel should always be viewed in a plane - it's a 2D model hide, // If true, then calls to VisualModel::render should return + wireframe, // If true, draw in GL's polygon mode show_bb, // If true, draw vertices/indices for the bounding box frame compute_bb // For some models, it's not useful to compute the bounding box (e.g. coordinate arrows) }; @@ -746,6 +747,7 @@ namespace mplot _flags.set (vm_bools::postVertexInitRequired, false); _flags.set (vm_bools::twodimensional, false); _flags.set (vm_bools::hide, false); + _flags.set (vm_bools::wireframe, false); _flags.set (vm_bools::show_bb, false); _flags.set (vm_bools::compute_bb, true); return _flags; @@ -765,6 +767,9 @@ namespace mplot void twodimensional (const bool val) { this->flags.set (vm_bools::twodimensional, val); } bool twodimensional() const { return this->flags.test (vm_bools::twodimensional); } + void wireframe (const bool val) { this->flags.set (vm_bools::wireframe, val); } + bool wireframe() const { return this->flags.test (vm_bools::wireframe); } + //! Getter for vertex positions (for mplot::NormalsVisual) std::vector getVertexPositions() { return this->vertexPositions; } //! Getter for vertex normals (for mplot::NormalsVisual) diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index b752d3f1..06bc2175 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -215,6 +215,14 @@ namespace mplot _glfn->UseProgram (this->get_gprog(this->parentVis)); if (!this->indices.empty()) { + + // Enable/disable wireframe mode per-model on each render call + if (this->flags.test (vm_bools::wireframe)) { + _glfn->PolygonMode (GL_FRONT_AND_BACK, GL_LINE); + } else { + _glfn->PolygonMode (GL_FRONT_AND_BACK, GL_FILL); + } + // It is only necessary to bind the vertex array object before rendering // (not the vertex buffer objects) _glfn->BindVertexArray (this->vao); @@ -252,6 +260,7 @@ namespace mplot mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); // Now render any VisualTextModels + _glfn->PolygonMode (GL_FRONT_AND_BACK, GL_FILL); auto ti = this->texts.begin(); while (ti != this->texts.end()) { (*ti)->render(); ti++; } diff --git a/mplot/VisualModelImplNoMX.h b/mplot/VisualModelImplNoMX.h index 70f236c3..749c0a7c 100644 --- a/mplot/VisualModelImplNoMX.h +++ b/mplot/VisualModelImplNoMX.h @@ -204,6 +204,14 @@ namespace mplot glUseProgram (this->get_gprog(this->parentVis)); if (!this->indices.empty()) { + + // Enable/disable wireframe mode per-model on each render call + if (this->flags.test (vm_bools::wireframe)) { + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + } else { + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + } + // It is only necessary to bind the vertex array object before rendering // (not the vertex buffer objects) glBindVertexArray (this->vao); @@ -241,6 +249,7 @@ namespace mplot mplot::gl::Util::checkError (__FILE__, __LINE__); // Now render any VisualTextModels + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); auto ti = this->texts.begin(); while (ti != this->texts.end()) { (*ti)->render(); ti++; } From b134b33ce3ad98eef77322fda921a484d2ddaaf5 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 11 Dec 2025 17:03:21 +0000 Subject: [PATCH 02/46] Skeleton. --- mplot/TrailVisual.h | 4 ++++ mplot/VisualModelBase.h | 1 + 2 files changed, 5 insertions(+) create mode 100644 mplot/TrailVisual.h diff --git a/mplot/TrailVisual.h b/mplot/TrailVisual.h new file mode 100644 index 00000000..843f6077 --- /dev/null +++ b/mplot/TrailVisual.h @@ -0,0 +1,4 @@ +/* + * A 'breadcrumb trail'. A bit like ScatterVisual, but with a fixed size of data container. Copies + * of a sphere are used for the crumbs - instanced rendering. Will need some changes in core mathplot for this + */ diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index d5191689..31088f08 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -65,6 +65,7 @@ namespace mplot twodimensional, // If true, then this VisualModel should always be viewed in a plane - it's a 2D model hide, // If true, then calls to VisualModel::render should return wireframe, // If true, draw in GL's polygon mode + instance_model, // If true, draw this as an array instance model show_bb, // If true, draw vertices/indices for the bounding box frame compute_bb // For some models, it's not useful to compute the bounding box (e.g. coordinate arrows) }; From 0c644e6d3a81c844227092acc4a2e0bd3c549ebc Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 12 Dec 2025 10:53:28 +0000 Subject: [PATCH 03/46] Refactors ssbo.h to ssbo_nomx.h --- examples/gl_compute/shader_naive_scan.cpp | 2 +- examples/gl_compute/shader_naive_scan_cli.cpp | 2 +- examples/gl_compute/shader_ssbo.cpp | 2 +- mplot/gl/{ssbo.h => ssbo_nomx.h} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename mplot/gl/{ssbo.h => ssbo_nomx.h} (100%) diff --git a/examples/gl_compute/shader_naive_scan.cpp b/examples/gl_compute/shader_naive_scan.cpp index df5666e9..59ca1fee 100644 --- a/examples/gl_compute/shader_naive_scan.cpp +++ b/examples/gl_compute/shader_naive_scan.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include diff --git a/examples/gl_compute/shader_naive_scan_cli.cpp b/examples/gl_compute/shader_naive_scan_cli.cpp index 593f30de..2dd4cecf 100644 --- a/examples/gl_compute/shader_naive_scan_cli.cpp +++ b/examples/gl_compute/shader_naive_scan_cli.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include namespace my { diff --git a/examples/gl_compute/shader_ssbo.cpp b/examples/gl_compute/shader_ssbo.cpp index 44e77c00..9f86d9ed 100644 --- a/examples/gl_compute/shader_ssbo.cpp +++ b/examples/gl_compute/shader_ssbo.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include namespace my { diff --git a/mplot/gl/ssbo.h b/mplot/gl/ssbo_nomx.h similarity index 100% rename from mplot/gl/ssbo.h rename to mplot/gl/ssbo_nomx.h From a89059b85fe7fb1db7ef7ccb0867152d7eb641d1 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 12 Dec 2025 10:55:19 +0000 Subject: [PATCH 04/46] Forgot to change this in last commit --- mplot/gl/CMakeLists.txt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/mplot/gl/CMakeLists.txt b/mplot/gl/CMakeLists.txt index da74c1eb..f939cd13 100644 --- a/mplot/gl/CMakeLists.txt +++ b/mplot/gl/CMakeLists.txt @@ -1,5 +1,16 @@ # Header installation install( - FILES compute_manager.h shaders.h loadshaders_nomx.h loadshaders_mx.h texture.h version.h compute_manager_cli.h compute_shaderprog.h ssbo.h util_nomx.h util_mx.h + FILES + compute_manager.h + shaders.h + texture.h + version.h + compute_manager_cli.h + compute_shaderprog.h + ssbo_nomx.h + loadshaders_nomx.h + loadshaders_mx.h + util_nomx.h + util_mx.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mplot/gl ) From 10c74c3c29e816f564d4ada14dddbf4b375ab7ff Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 12 Dec 2025 11:05:24 +0000 Subject: [PATCH 05/46] Adds an MX version of ssbo --- mplot/gl/CMakeLists.txt | 1 + mplot/gl/ssbo_mx.h | 188 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 mplot/gl/ssbo_mx.h diff --git a/mplot/gl/CMakeLists.txt b/mplot/gl/CMakeLists.txt index f939cd13..526c8387 100644 --- a/mplot/gl/CMakeLists.txt +++ b/mplot/gl/CMakeLists.txt @@ -8,6 +8,7 @@ install( compute_manager_cli.h compute_shaderprog.h ssbo_nomx.h + ssbo_mx.h loadshaders_nomx.h loadshaders_mx.h util_nomx.h diff --git a/mplot/gl/ssbo_mx.h b/mplot/gl/ssbo_mx.h new file mode 100644 index 00000000..ca00fb8f --- /dev/null +++ b/mplot/gl/ssbo_mx.h @@ -0,0 +1,188 @@ +#pragma once + +/* + * Common code for SSBO interactions in mplot programs + * + * Note: You have to include a header like gl3.h or glext.h etc for the GL types and + * functions BEFORE including this file. + * + * Author: Seb James. + */ + +#include +#include +#include +#include +#include + +namespace mplot::gl +{ + // An SSBO and its data + // @tparam index: The index of the buffer, used in the GLSL + // @tparam T: The type of the data in the SSBO + // @tparam N: The number of elements of type T in the SSBO + template // Could add version template params if necessary, to select correct gl function calls + struct ssbo + { + // The name of the buffer, generated with glGenBuffers() + unsigned int name = 0; + // The CPU-side data for the buffer + sm::vec data; + // The OpenGL function pointer + GladGLContext* glfn = nullptr; + + ssbo() {} + ~ssbo() {} + + // Init is not built into the constructor, as client code must ensure there is an OpenGL context available + void init (GladGLContext* _glfn) + { + this->glfn = _glfn; + this->glfn->GenBuffers (1, &this->name); + this->copy_to_gpu(); + } + + // Copy the data in the sm::vec data over to the GPU + void copy_to_gpu() + { + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), this->data.data(), GL_STATIC_DRAW); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + + // Map the GPU memory to CPU space, then copy the values into this->data. NB: it's a + // performance hit to *copy* to the mapped data to our sm::vec, because the data is + // *already in CPU accessible memory* after glMapBufferRange(). + // However, in case you need it, here it is. + void copy_from_gpu() + { + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N*sizeof(T), GL_MAP_READ_BIT)); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + for (unsigned int i = 0; i < N; ++i) { this->data[i] = cpuptr[i]; } + this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + + // Find the range of the data in the given Shader Storage Buffer Object + // + // ssbo_idx: The Index of the Shader Storage Buffer Object that we're reading from + // ssbo_name: The name (really a number) of the Shader Storage Buffer Object that we're reading from + // ssbo_num_elements: The number of elements of type T in the SSBO. + sm::range get_range() + { + sm::range r; + r.search_init(); + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N*sizeof(T), GL_MAP_READ_BIT)); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + for (unsigned int i = 0; i < N; ++i) { r.update (cpuptr[i]); } + this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + return r; + } + }; + + // Set up a Shader Storage Buffer Object (SSBO) and buffer data into it (from a sm::vvec) + template + void setup_ssbo (const GLuint target_index, unsigned int& ssbo_id, const sm::vvec& data) + { + this->glfn->GenBuffers (1, &ssbo_id); + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); + // Mutable, re-locatable storage: + this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, data.size() * sizeof(T), data.data(), GL_STATIC_DRAW); + // Immutable storage would be: + // void glBufferStorage(GLenum target​, GLsizeiptr size​, const GLvoid * data​, GLbitfield flags​); + //this->glfn->BufferStorage (GL_SHADER_STORAGE_BUFFER, data.size() * sizeof(T), data.data(), GL_CLIENT_STORAGE_BIT | GL_MAP_READ_BIT); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + + // Set up a Shader Storage Buffer Object (SSBO) and buffer data into it (sm::vec version) + template + void setup_ssbo (const GLuint target_index, unsigned int& ssbo_id, const sm::vec& data) + { + this->glfn->GenBuffers (1, &ssbo_id); + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); + this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + + // Copy data to an existing SSBO + template + void copy_vvec_to_ssbo (const GLuint target_index, const unsigned int ssbo_id, const sm::vvec& data) + { + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); + this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, data.size() * sizeof(T), data.data(), GL_STATIC_DRAW); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + + template + void copy_vvec_to_ssbo (const GLuint target_index, const unsigned int ssbo_id, const sm::vvec& data) + { + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); + this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + + // Map the SSBO to cpu space, then make a copy of the data into a passed-in vvec. + // + // ssbo_idx: The Index of the Shader Storage Buffer Object that we're reading from + // ssbo_name: The name (really a number) of the Shader Storage Buffer Object that we're reading from + // cpu_data: A vvec of the right size to receive the data in the SSBO into 'CPU accessible memory' + template + void ssbo_copy_to_vvec (const unsigned int ssbo_idx, const unsigned int ssbo_name, sm::vvec& cpu_side) + { + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, ssbo_idx, ssbo_name); + // Really, it's crazy to *copy* because the data is *already in CPU + // accessible memory* after glMapBufferRange. BUT here's the copy: + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, cpu_side.size()*sizeof(T), GL_MAP_READ_BIT)); + for (unsigned int i = 0; i < cpu_side.size(); ++i) { cpu_side[i] = cpuptr[i]; } + this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + + template + void ssbo_copy_to_vec (const unsigned int ssbo_idx, const unsigned int ssbo_name, sm::vec& cpu_side) + { + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, ssbo_idx, ssbo_name); + // Really, it's crazy to *copy* because the data is *already in CPU + // accessible memory* after glMapBufferRange. BUT here's the copy: + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N*sizeof(T), GL_MAP_READ_BIT)); + for (unsigned int i = 0; i < N; ++i) { cpu_side[i] = cpuptr[i]; } + this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + + // Find the range of the data in the given Shader Storage Buffer Object + // + // ssbo_idx: The Index of the Shader Storage Buffer Object that we're reading from + // ssbo_name: The name (really a number) of the Shader Storage Buffer Object that we're reading from + // ssbo_num_elements: The number of elements of type T in the SSBO. + template + sm::range ssbo_get_range (const unsigned int ssbo_idx, const unsigned int ssbo_name, const unsigned int ssbo_num_elements) + { + sm::range r; + r.search_init(); + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, ssbo_idx, ssbo_name); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, ssbo_num_elements*sizeof(T), GL_MAP_READ_BIT)); + for (unsigned int i = 0; i < ssbo_num_elements; ++i) { r.update (cpuptr[i]); } + this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + return r; + } + +} // mplot::gl From a8f36d8557d2876f03cf2d199af04b8749640478 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 12 Dec 2025 11:49:07 +0000 Subject: [PATCH 06/46] Removes some functions that pre-date the ssbo struct --- mplot/gl/ssbo_mx.h | 98 +------------------------------------------- mplot/gl/ssbo_nomx.h | 96 +------------------------------------------ 2 files changed, 3 insertions(+), 191 deletions(-) diff --git a/mplot/gl/ssbo_mx.h b/mplot/gl/ssbo_mx.h index ca00fb8f..38e073e2 100644 --- a/mplot/gl/ssbo_mx.h +++ b/mplot/gl/ssbo_mx.h @@ -21,7 +21,8 @@ namespace mplot::gl // @tparam index: The index of the buffer, used in the GLSL // @tparam T: The type of the data in the SSBO // @tparam N: The number of elements of type T in the SSBO - template // Could add version template params if necessary, to select correct gl function calls + // Could add version template params if necessary, to select correct gl function calls + template // T should be simple type? struct ssbo { // The name of the buffer, generated with glGenBuffers() @@ -90,99 +91,4 @@ namespace mplot::gl } }; - // Set up a Shader Storage Buffer Object (SSBO) and buffer data into it (from a sm::vvec) - template - void setup_ssbo (const GLuint target_index, unsigned int& ssbo_id, const sm::vvec& data) - { - this->glfn->GenBuffers (1, &ssbo_id); - this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); - // Mutable, re-locatable storage: - this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, data.size() * sizeof(T), data.data(), GL_STATIC_DRAW); - // Immutable storage would be: - // void glBufferStorage(GLenum target​, GLsizeiptr size​, const GLvoid * data​, GLbitfield flags​); - //this->glfn->BufferStorage (GL_SHADER_STORAGE_BUFFER, data.size() * sizeof(T), data.data(), GL_CLIENT_STORAGE_BIT | GL_MAP_READ_BIT); - this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - } - - // Set up a Shader Storage Buffer Object (SSBO) and buffer data into it (sm::vec version) - template - void setup_ssbo (const GLuint target_index, unsigned int& ssbo_id, const sm::vec& data) - { - this->glfn->GenBuffers (1, &ssbo_id); - this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); - this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW); - this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - } - - // Copy data to an existing SSBO - template - void copy_vvec_to_ssbo (const GLuint target_index, const unsigned int ssbo_id, const sm::vvec& data) - { - this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); - this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, data.size() * sizeof(T), data.data(), GL_STATIC_DRAW); - this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - } - - template - void copy_vvec_to_ssbo (const GLuint target_index, const unsigned int ssbo_id, const sm::vvec& data) - { - this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); - this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW); - this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - } - - // Map the SSBO to cpu space, then make a copy of the data into a passed-in vvec. - // - // ssbo_idx: The Index of the Shader Storage Buffer Object that we're reading from - // ssbo_name: The name (really a number) of the Shader Storage Buffer Object that we're reading from - // cpu_data: A vvec of the right size to receive the data in the SSBO into 'CPU accessible memory' - template - void ssbo_copy_to_vvec (const unsigned int ssbo_idx, const unsigned int ssbo_name, sm::vvec& cpu_side) - { - this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, ssbo_idx, ssbo_name); - // Really, it's crazy to *copy* because the data is *already in CPU - // accessible memory* after glMapBufferRange. BUT here's the copy: - T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, cpu_side.size()*sizeof(T), GL_MAP_READ_BIT)); - for (unsigned int i = 0; i < cpu_side.size(); ++i) { cpu_side[i] = cpuptr[i]; } - this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); - this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - } - - template - void ssbo_copy_to_vec (const unsigned int ssbo_idx, const unsigned int ssbo_name, sm::vec& cpu_side) - { - this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, ssbo_idx, ssbo_name); - // Really, it's crazy to *copy* because the data is *already in CPU - // accessible memory* after glMapBufferRange. BUT here's the copy: - T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N*sizeof(T), GL_MAP_READ_BIT)); - for (unsigned int i = 0; i < N; ++i) { cpu_side[i] = cpuptr[i]; } - this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); - this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - } - - // Find the range of the data in the given Shader Storage Buffer Object - // - // ssbo_idx: The Index of the Shader Storage Buffer Object that we're reading from - // ssbo_name: The name (really a number) of the Shader Storage Buffer Object that we're reading from - // ssbo_num_elements: The number of elements of type T in the SSBO. - template - sm::range ssbo_get_range (const unsigned int ssbo_idx, const unsigned int ssbo_name, const unsigned int ssbo_num_elements) - { - sm::range r; - r.search_init(); - this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, ssbo_idx, ssbo_name); - T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, ssbo_num_elements*sizeof(T), GL_MAP_READ_BIT)); - for (unsigned int i = 0; i < ssbo_num_elements; ++i) { r.update (cpuptr[i]); } - this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); - this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - return r; - } - } // mplot::gl diff --git a/mplot/gl/ssbo_nomx.h b/mplot/gl/ssbo_nomx.h index 6a7e83a9..2b64608b 100644 --- a/mplot/gl/ssbo_nomx.h +++ b/mplot/gl/ssbo_nomx.h @@ -60,6 +60,7 @@ namespace mplot::gl mplot::gl::Util::checkError (__FILE__, __LINE__); T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N*sizeof(T), GL_MAP_READ_BIT)); mplot::gl::Util::checkError (__FILE__, __LINE__); + //this->data.resize(N); for (unsigned int i = 0; i < N; ++i) { this->data[i] = cpuptr[i]; } glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); @@ -87,99 +88,4 @@ namespace mplot::gl } }; - // Set up a Shader Storage Buffer Object (SSBO) and buffer data into it (from a sm::vvec) - template - void setup_ssbo (const GLuint target_index, unsigned int& ssbo_id, const sm::vvec& data) - { - glGenBuffers (1, &ssbo_id); - glBindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); - // Mutable, re-locatable storage: - glBufferData (GL_SHADER_STORAGE_BUFFER, data.size() * sizeof(T), data.data(), GL_STATIC_DRAW); - // Immutable storage would be: - // void glBufferStorage(GLenum target​, GLsizeiptr size​, const GLvoid * data​, GLbitfield flags​); - //glBufferStorage (GL_SHADER_STORAGE_BUFFER, data.size() * sizeof(T), data.data(), GL_CLIENT_STORAGE_BIT | GL_MAP_READ_BIT); - glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__); - } - - // Set up a Shader Storage Buffer Object (SSBO) and buffer data into it (sm::vec version) - template - void setup_ssbo (const GLuint target_index, unsigned int& ssbo_id, const sm::vec& data) - { - glGenBuffers (1, &ssbo_id); - glBindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); - glBufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW); - glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__); - } - - // Copy data to an existing SSBO - template - void copy_vvec_to_ssbo (const GLuint target_index, const unsigned int ssbo_id, const sm::vvec& data) - { - glBindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); - glBufferData (GL_SHADER_STORAGE_BUFFER, data.size() * sizeof(T), data.data(), GL_STATIC_DRAW); - glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__); - } - - template - void copy_vvec_to_ssbo (const GLuint target_index, const unsigned int ssbo_id, const sm::vvec& data) - { - glBindBufferBase (GL_SHADER_STORAGE_BUFFER, target_index, ssbo_id); - glBufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW); - glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__); - } - - // Map the SSBO to cpu space, then make a copy of the data into a passed-in vvec. - // - // ssbo_idx: The Index of the Shader Storage Buffer Object that we're reading from - // ssbo_name: The name (really a number) of the Shader Storage Buffer Object that we're reading from - // cpu_data: A vvec of the right size to receive the data in the SSBO into 'CPU accessible memory' - template - void ssbo_copy_to_vvec (const unsigned int ssbo_idx, const unsigned int ssbo_name, sm::vvec& cpu_side) - { - glBindBufferBase (GL_SHADER_STORAGE_BUFFER, ssbo_idx, ssbo_name); - // Really, it's crazy to *copy* because the data is *already in CPU - // accessible memory* after glMapBufferRange. BUT here's the copy: - T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, cpu_side.size()*sizeof(T), GL_MAP_READ_BIT)); - for (unsigned int i = 0; i < cpu_side.size(); ++i) { cpu_side[i] = cpuptr[i]; } - glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); - glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__); - } - - template - void ssbo_copy_to_vec (const unsigned int ssbo_idx, const unsigned int ssbo_name, sm::vec& cpu_side) - { - glBindBufferBase (GL_SHADER_STORAGE_BUFFER, ssbo_idx, ssbo_name); - // Really, it's crazy to *copy* because the data is *already in CPU - // accessible memory* after glMapBufferRange. BUT here's the copy: - T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N*sizeof(T), GL_MAP_READ_BIT)); - for (unsigned int i = 0; i < N; ++i) { cpu_side[i] = cpuptr[i]; } - glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); - glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__); - } - - // Find the range of the data in the given Shader Storage Buffer Object - // - // ssbo_idx: The Index of the Shader Storage Buffer Object that we're reading from - // ssbo_name: The name (really a number) of the Shader Storage Buffer Object that we're reading from - // ssbo_num_elements: The number of elements of type T in the SSBO. - template - sm::range ssbo_get_range (const unsigned int ssbo_idx, const unsigned int ssbo_name, const unsigned int ssbo_num_elements) - { - sm::range r; - r.search_init(); - glBindBufferBase (GL_SHADER_STORAGE_BUFFER, ssbo_idx, ssbo_name); - T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, ssbo_num_elements*sizeof(T), GL_MAP_READ_BIT)); - for (unsigned int i = 0; i < ssbo_num_elements; ++i) { r.update (cpuptr[i]); } - glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); - glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__); - return r; - } - } // mplot::gl From d39511676f60edcdebe7a4e7fb779a96d0c7fd94 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 12 Dec 2025 16:45:42 +0000 Subject: [PATCH 07/46] Basic instanced rendering --- examples/CMakeLists.txt | 6 ++ examples/breadcrumbs.cpp | 58 +++++++++++++++++ examples/scatter_instanced.cpp | 55 +++++++++++++++++ mplot/InstancedScatterVisual.h | 110 +++++++++++++++++++++++++++++++++ mplot/VisualDefaultShaders.h | 104 ++++--------------------------- mplot/VisualModelBase.h | 15 ++++- mplot/VisualModelImplMX.h | 35 ++++++++++- mplot/VisualModelImplNoMX.h | 2 +- mplot/gl/version.h | 15 +++++ shaders/Visual.vert.glsl | 17 ++++- 10 files changed, 315 insertions(+), 102 deletions(-) create mode 100644 examples/breadcrumbs.cpp create mode 100644 examples/scatter_instanced.cpp create mode 100644 mplot/InstancedScatterVisual.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a3f30007..4e2d9cbe 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -247,6 +247,12 @@ if(NOT APPLE) add_executable(scatter_geodesic scatter_geodesic.cpp) target_link_libraries(scatter_geodesic OpenGL::GL glfw Freetype::Freetype) + + # Instancing also not available on Apple (limited to OpenGL 4.1) + add_executable(scatter_instanced scatter_instanced.cpp) + target_link_libraries(scatter_instanced OpenGL::GL glfw Freetype::Freetype) + add_executable(breadcrumbs breadcrumbs.cpp) + target_link_libraries(breadcrumbs OpenGL::GL glfw Freetype::Freetype) endif() add_executable(scatter_dynamic scatter_dynamic.cpp) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp new file mode 100644 index 00000000..eca9e755 --- /dev/null +++ b/examples/breadcrumbs.cpp @@ -0,0 +1,58 @@ +/* + * Visualize a test surface + */ +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +// Instanced rendering requires OpenGL 4.3 or higher (for the SSBO) +constexpr int glver = mplot::gl::version_4_3; + +int main() +{ + int rtn = -1; + + mplot::Visual v(1024, 768, "mplot::InstancedScatterVisual"); + v.lightingEffects(); + + std::deque> points(50); + std::deque data(50); + + for (int i = 0; i < 50; ++i) { + float x = i/50.0f; + float y = std::sqrt (1.0f - x*x); + // z is some function of x, y + float z = x * std::exp(-(x*x) - (y*y)); + points[i] = {x, y, z}; + // std::cout << "points[" << i << "] = " << points[i] << std::endl; + data[i] = z; + } + + auto isv = std::make_unique> (sm::vec<>{}); + v.bindmodel (isv); + // Place data in SSBO + isv->set_data (points, data); + isv->radiusFixed = 0.03f; + //isv->cm.setType (mplot::ColourMapType::Plasma); + isv->finalize(); + v.addVisualModel (isv); + + while (!v.readyToFinish()) { + v.render(); + v.wait (0.4); + points.push_back (points.back() + sm::vec<>::uy() * 0.04f); + points.pop_front(); + isv->set_data (points, data); + } + + return rtn; +} diff --git a/examples/scatter_instanced.cpp b/examples/scatter_instanced.cpp new file mode 100644 index 00000000..08cb3d1e --- /dev/null +++ b/examples/scatter_instanced.cpp @@ -0,0 +1,55 @@ +/* + * Visualize a test surface + */ +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +// Instanced rendering requires OpenGL 4.3 or higher (for the SSBO) +constexpr int glver = mplot::gl::version_4_3; + +int main() +{ + int rtn = -1; + + mplot::Visual v(1024, 768, "mplot::InstancedScatterVisual"); + v.lightingEffects(); + + sm::vvec> points(20*20); + sm::vvec data(20*20); + size_t k = 0; + + for (int i = -10; i < 10; ++i) { + for (int j = -10; j < 10; ++j) { + float x = 0.1*i; + float y = 0.1*j; + // z is some function of x, y + float z = x * std::exp(-(x*x) - (y*y)); + points[k] = {x, y, z}; + data[k] = z; + k++; + } + } + + auto isv = std::make_unique> (sm::vec<>{}); + v.bindmodel (isv); + // Place data in SSBO + isv->set_data (points, data); + isv->radiusFixed = 0.03f; + //isv->cm.setType (mplot::ColourMapType::Plasma); + isv->finalize(); + v.addVisualModel (isv); + + v.keepOpen(); + + return rtn; +} diff --git a/mplot/InstancedScatterVisual.h b/mplot/InstancedScatterVisual.h new file mode 100644 index 00000000..04e71102 --- /dev/null +++ b/mplot/InstancedScatterVisual.h @@ -0,0 +1,110 @@ +/*! + * An example of a scatter plot using instanced rendering + * + * \author Seb James + * \date 2025 + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mplot +{ + template + class InstancedScatterVisual : public VisualModel + { + public: + InstancedScatterVisual (const sm::vec _offset) + { + this->instanced (true); + this->viewmatrix.translate (_offset); + } + + void marker (const sm::vec coord, const std::array& clr, const float size) + { + if (this->markers == mplot::markerstyle::rod) { + // Draw a rod. markerdirn gives length and dirn. Radius from size + sm::vec hr = this->markerdirn * 0.5f; // half rod + sm::vec rs = coord + hr; + sm::vec re = coord - hr; + this->computeTube (rs, re, clr, clr, size, 12); + } else { + if constexpr (draw_spheres_as_geodesics) { + // Slower than regular computeSphere(). 2 iterations gives 320 faces + this->template computeSphereGeoFast (coord, clr, size); + } else { + // (16+2) * 20 gives 360 faces + this->computeSphere (coord, clr, size, 16, 20); + } + } + } + + void set_data (const std::deque>& points, const std::deque& data) + { + if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } + if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } + + size_t j = 0; + for (size_t i = 0; i < points.size(); ++i) { + sm::vec c = points[i]; + this->instance_data.data[j++] = c[0]; + this->instance_data.data[j++] = c[1]; + this->instance_data.data[j++] = c[2]; + this->instance_data.data[j++] = data[i]; + } + this->instance_count = points.size(); + } + + void set_data (const sm::vvec>& points, const sm::vvec& data) + { + if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } + if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } + + size_t j = 0; + for (size_t i = 0; i < points.size(); ++i) { + sm::vec c = points[i]; + this->instance_data.data[j++] = c[0]; + this->instance_data.data[j++] = c[1]; + this->instance_data.data[j++] = c[2]; + this->instance_data.data[j++] = data[i]; + } + this->instance_count = points.size(); + } + + //! Compute spheres for a scatter plot + void initializeVertices() + { + // Draw one marker. It will then be instanced as many using instanced rendering + this->marker (sm::vec<>{}, mplot::colour::grey50, this->radiusFixed); + } + + // The constexpr, unordered geodesic code is no slower than the regular + // VisualModel::computeSphere(), but leave this off for now (if true, C++-20 is + // required) + static constexpr bool draw_spheres_as_geodesics = false; + + //! Set this->radiusFixed, then re-compute vertices. + void setRadius (float fr) + { + this->radiusFixed = fr; + this->reinit(); + } + + // How to show the scatter points? + markerstyle markers = mplot::markerstyle::sphere; + + // Marker direction, if relevant. Used for length of rod markers + sm::vec markerdirn = sm::vec<>::uz(); + + //! Change this to get larger or smaller spheres. + float radiusFixed = 0.05f; + }; + +} // namespace mplot diff --git a/mplot/VisualDefaultShaders.h b/mplot/VisualDefaultShaders.h index e88c68d7..b0e1e92f 100644 --- a/mplot/VisualDefaultShaders.h +++ b/mplot/VisualDefaultShaders.h @@ -3,109 +3,23 @@ #pragma once -// Define a version string for the shaders -#ifdef __APPLE__ -// Mac support is fixed at OpenGL 4.1 in favour of their own graphics API. -#define OpenGL_VersionString "#version 410\n" -#else -/* - On other platforms ALSO specify OpenGL 4.1, because only relatively simple features - of OpenGL are in use. In future, may wish to change this, in case there's a need for - any of the following major features released since OpenGL 4.1 (this list from the - wikipedia OpenGL page): - -OpenGL 4.2 - - Release date: August 8, 2011 - - Support for shaders with atomic counters and load-store-atomic read-modify-write - operations to one level of a texture - - Drawing multiple instances of data captured from GPU vertex processing (including - tessellation), to enable complex objects to be efficiently repositioned and - replicated - - Support for modifying an arbitrary subset of a compressed texture, without having to - re-download the whole texture to the GPU for significant performance improvements - -OpenGL 4.3 - - Release date: August 6, 2012 - - Compute shaders leveraging GPU parallelism within the context of the graphics pipeline - - Shader storage buffer objects, allowing shaders to read and write buffer objects like - image load/store from 4.2, but through the language rather than function calls. - - Image format parameter queries - - ETC2/EAC texture compression as a standard feature - - Full compatibility with OpenGL ES 3.0 APIs - - Debug abilities to receive debugging messages during application development - - Texture views to interpret textures in different ways without data replication - - Increased memory security and multi-application robustness - -OpenGL 4.4 - - Release date: July 22, 2013 - - Enforced buffer object usage controls - - Asynchronous queries into buffer objects - - Expression of more layout controls of interface variables in shaders - - Efficient binding of multiple objects simultaneously - -OpenGL 4.5 - - Release date: August 11, 2014 - - Direct State Access (DSA) - object accessors enable state to be queried and modified - without binding objects to contexts, for increased application and middleware - efficiency and flexibility. - - Flush Control - applications can control flushing of pending commands before context - switching - enabling high-performance multithreaded applications; - - Robustness - providing a secure platform for applications such as WebGL browsers, - including preventing a GPU reset affecting any other running applications; - - OpenGL ES 3.1 API and shader compatibility - to enable the easy development and - execution of the latest OpenGL ES applications on desktop systems. - - OpenGL 4.6 - - Release date: July 31, 2017 - - more efficient, GPU-sided, geometry processing - more efficient shader execution (AZDO) - more information through statistics, overflow query and counters - higher performance through no error handling contexts - clamping of polygon offset function, solves a shadow rendering problem - SPIR-V shaders - Improved anisotropic filtering -*/ -#define OpenGL_VersionString "#version 310 es\n" -#endif - #include namespace mplot { // The default vertex shader. To study this GLSL, see Visual.vert.glsl, which has // some code comments. - const char* defaultVtxShader = "uniform mat4 m_matrix;\n" + const char* defaultVtxShader_part1 = + "uniform mat4 m_matrix;\n" "uniform mat4 v_matrix;\n" "uniform mat4 p_matrix;\n" "uniform float alpha;\n" + "uniform int instanced = 0;\n" "layout(location = 0) in vec4 position;\n" "layout(location = 1) in vec4 normalin;\n" - "layout(location = 2) in vec3 color;\n" + "layout(location = 2) in vec3 color;\n"; + + const char* defaultVtxShader_part2 = "out VERTEX\n" "{\n" " vec4 normal;\n" @@ -124,7 +38,11 @@ namespace mplot { std::string shdr; shdr += mplot::gl::version::shaderpreamble (glver); - shdr += defaultVtxShader; + shdr += defaultVtxShader_part1; + if (mplot::gl::version::has_ssbo (glver)) { + shdr += "layout (std430, binding = 1) buffer InputBlock { float instance_data[]; };\n"; + } + shdr += defaultVtxShader_part2; return shdr; } diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 31088f08..2f95705d 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -64,8 +64,8 @@ namespace mplot postVertexInitRequired, twodimensional, // If true, then this VisualModel should always be viewed in a plane - it's a 2D model hide, // If true, then calls to VisualModel::render should return - wireframe, // If true, draw in GL's polygon mode - instance_model, // If true, draw this as an array instance model + wireframe, // If true, draw in GL's polygon GL_LINES mode (instead of GL_FILL) + instanced, // If true, draw this VisualModel with 'instancing' 1 or more times show_bb, // If true, draw vertices/indices for the bounding box frame compute_bb // For some models, it's not useful to compute the bounding box (e.g. coordinate arrows) }; @@ -718,6 +718,9 @@ namespace mplot GLuint idx = 0u; GLuint idx_bb = 0u; + //! If drawing with instancing, how many instances? + unsigned int instance_count = 1; + /*! * A function that will be runtime defined to get_shaderprogs from a pointer to * Visual (saving a boilerplate argument and avoiding that killer circular @@ -771,6 +774,9 @@ namespace mplot void wireframe (const bool val) { this->flags.set (vm_bools::wireframe, val); } bool wireframe() const { return this->flags.test (vm_bools::wireframe); } + void instanced (const bool val) { this->flags.set (vm_bools::instanced, val); } + bool instanced() const { return this->flags.test (vm_bools::instanced); } + //! Getter for vertex positions (for mplot::NormalsVisual) std::vector getVertexPositions() { return this->vertexPositions; } //! Getter for vertex normals (for mplot::NormalsVisual) @@ -901,7 +907,10 @@ namespace mplot /** * START vertex/index computation code * - * ALL methods below this point are for computing vertices + * ALL methods below this point are for computing vertices. + * + * The metheds compute vertexPositions/Normals/Colors along with indices in a form suitable + * for use with GL's DrawElements (or DrawElementsInstanced) drawing call. */ /*! diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 06bc2175..b9060626 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -32,7 +33,7 @@ namespace mplot /*! * Multiple context safe implementation (mplot::gl::multicontext == 1) */ - template = true > + template requires (mx == 1) struct VisualModelImpl : public mplot::VisualModelBase { VisualModelImpl() : mplot::VisualModelBase::VisualModelBase() {} @@ -44,7 +45,7 @@ namespace mplot // Explicitly clear owned VisualTextModels this->texts.clear(); if (this->vbos != nullptr) { - GladGLContext* _glfn = this->get_glfn(this->parentVis); + GladGLContext* _glfn = this->get_glfn (this->parentVis); _glfn->DeleteBuffers (this->numVBO, this->vbos.get()); _glfn->DeleteVertexArrays (1, &this->vao); } @@ -105,6 +106,11 @@ namespace mplot _glfn->BindVertexArray(0); // carefully unbind and rebind mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); + if (this->flags.test (vm_bools::instanced)) { + // Ensure instance_data is over on the GPU + this->instance_data.copy_to_gpu(); + } + /* * Now do the same for the bounding box */ @@ -245,7 +251,15 @@ namespace mplot } // Draw the triangles - _glfn->DrawElements (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0); + GLint loc_i = _glfn->GetUniformLocation (this->get_gprog(this->parentVis), static_cast("instanced")); + if (this->flags.test (vm_bools::instanced)) { + if (loc_i != -1) { _glfn->Uniform1i (loc_i, 1); } + _glfn->DrawElementsInstanced (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0, + this->instance_count); + } else { + if (loc_i != -1) { _glfn->Uniform1i (loc_i, 0); } + _glfn->DrawElements (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0); + } // Unbind the VAO _glfn->BindVertexArray(0); @@ -399,6 +413,13 @@ namespace mplot //! Get the GladGLContext function pointer std::function*)> get_glfn; + //! Shader Storage Buffer Object for instanced rendering + static constexpr unsigned int instance_index = 1; // instance data stored in SSBO index 1 (must match GLSL code) + static constexpr unsigned int max_instances = 1024; // Each instance has: location (3 floats), scale (1 float) + static constexpr unsigned int max_instance_limit = 4 * max_instances; + mplot::gl::ssbo instance_data; + constexpr unsigned int max_instanced_items() { return max_instances; } + protected: //! A vector of pointers to text models that should be rendered. @@ -418,6 +439,14 @@ namespace mplot mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); _glfn->EnableVertexAttribArray (bufferAttribPosition); mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); + + if (this->flags.test (vm_bools::instanced)) { + if constexpr (mplot::gl::version::has_ssbo (glver) == true) { + this->instance_data.init (_glfn); + } else { + throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); + } + } } }; diff --git a/mplot/VisualModelImplNoMX.h b/mplot/VisualModelImplNoMX.h index 749c0a7c..c71d991a 100644 --- a/mplot/VisualModelImplNoMX.h +++ b/mplot/VisualModelImplNoMX.h @@ -31,7 +31,7 @@ namespace mplot /*! * Single context safe implementation (mplot::gl::multicontext == 0) */ - template = true > + template requires (mx == 0) struct VisualModelImpl : public mplot::VisualModelBase { VisualModelImpl() : mplot::VisualModelBase::VisualModelBase() {} diff --git a/mplot/gl/version.h b/mplot/gl/version.h index b8b61e62..4d5cb2ca 100644 --- a/mplot/gl/version.h +++ b/mplot/gl/version.h @@ -66,6 +66,21 @@ namespace mplot::gl { return (((gl_version_number >> 30) & 0x1) > 0x0) ? true : false; } + // True if this version suports shader storage buffer objects + static bool constexpr has_ssbo (const int gl_version_number) + { + if (mplot::gl::version::gles (gl_version_number) == true) { + // OpenGL ES 3.1 and up supports SSBO + return (mplot::gl::version::major (gl_version_number) > 3 + || (mplot::gl::version::major (gl_version_number) == 3 + && mplot::gl::version::minor (gl_version_number) >= 1)); + } else { + // OpenGL 4.3 and up supports SSBO + return (mplot::gl::version::major (gl_version_number) > 4 + || (mplot::gl::version::major (gl_version_number) == 4 + && mplot::gl::version::minor (gl_version_number) >= 3)); + } + } // Output a string describing the version number static inline std::string vstring (const int gl_version_number) { diff --git a/shaders/Visual.vert.glsl b/shaders/Visual.vert.glsl index 1586f954..7e8dbfdb 100644 --- a/shaders/Visual.vert.glsl +++ b/shaders/Visual.vert.glsl @@ -1,5 +1,8 @@ // The coded-in shaders tell non-Mac platforms that they use OpenGL 4.5, but Mac limited to 4.1 -#version 410 +#version 430 + +// SSBO to be added optionally + // ProjMatrix * RotnMatrix operation can be carried out on CPU with a single matrix //uniform mat4 mvp_matrix; @@ -10,11 +13,14 @@ uniform mat4 v_matrix; // scene view matrix uniform mat4 p_matrix; // projection matrix // alpha - to make a model see-through uniform float alpha; +uniform int instanced = 0; layout(location = 0) in vec4 position; // Attrib location 0 layout(location = 1) in vec4 normalin; // Attrib location 1 layout(location = 2) in vec3 color; // Attrib location 2 +layout (std430, binding = 1) buffer InputBlock { float instance_data[]; }; + out VERTEX { vec4 normal; @@ -24,7 +30,14 @@ out VERTEX void main (void) { - gl_Position = (p_matrix * v_matrix * m_matrix * position); + if (instanced != 0) { + vec4 offset = { instance_data[gl_InstanceID * 4], instance_data[gl_InstanceID * 4 + 1], instance_data[gl_InstanceID * 4 + 2], 1 }; + float dval = instance_data[gl_InstanceID * 4 + 3]; + // Make colour from dval? Or rather, pass colour as three floats. + gl_Position = (p_matrix * v_matrix * m_matrix * (position + offset)); + } else { + gl_Position = (p_matrix * v_matrix * m_matrix * position); + } vertex.color = vec4(color, alpha); vertex.fragpos = vec3(m_matrix * position); // Normals are all automatically computed, so there's no need for From e22ec85b947bdbca4e93623264de8d40fe645483 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 15 Dec 2025 13:08:39 +0000 Subject: [PATCH 08/46] Got the write-and-rewrite to SSBO to work. Now need to go back and see if I can reinstate the SSBO::data member --- examples/breadcrumbs.cpp | 54 +++++++++++++++++++---------- examples/gl_compute/shader_ssbo.cpp | 28 ++++++++++----- mplot/InstancedScatterVisual.h | 22 +++++++----- mplot/VisualModelImplMX.h | 19 +++++----- mplot/gl/ssbo_mx.h | 50 ++++++++++++++++++-------- mplot/gl/ssbo_nomx.h | 38 ++++++++++++++------ 6 files changed, 142 insertions(+), 69 deletions(-) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp index eca9e755..db17027e 100644 --- a/examples/breadcrumbs.cpp +++ b/examples/breadcrumbs.cpp @@ -17,6 +17,20 @@ // Instanced rendering requires OpenGL 4.3 or higher (for the SSBO) constexpr int glver = mplot::gl::version_4_3; +constexpr float st = sm::mathconst::two_pi / 360; +constexpr float z = 0.0f; +sm::vec f (int i) +{ + float phi = st * i; + float r = 1.0f + 0.1f * std::sin (phi * 10.0f); + sm::vec xyz = { + r * std::sin (phi), + r * std::cos (phi), + 0.0f + }; + return xyz; +} + int main() { int rtn = -1; @@ -24,34 +38,36 @@ int main() mplot::Visual v(1024, 768, "mplot::InstancedScatterVisual"); v.lightingEffects(); - std::deque> points(50); - std::deque data(50); - - for (int i = 0; i < 50; ++i) { - float x = i/50.0f; - float y = std::sqrt (1.0f - x*x); - // z is some function of x, y - float z = x * std::exp(-(x*x) - (y*y)); - points[i] = {x, y, z}; - // std::cout << "points[" << i << "] = " << points[i] << std::endl; - data[i] = z; + constexpr int dsz = 260; + std::deque> points(dsz); + std::deque data(dsz); + + int i = 0; + sm::vec xyz = {}; + for (i = 0; i < dsz; ++i) { + xyz = f (i); + points[i] = xyz; + data[i] = xyz[2]; } auto isv = std::make_unique> (sm::vec<>{}); v.bindmodel (isv); - // Place data in SSBO - isv->set_data (points, data); isv->radiusFixed = 0.03f; - //isv->cm.setType (mplot::ColourMapType::Plasma); + isv->finalize(); - v.addVisualModel (isv); + auto isvp = v.addVisualModel (isv); while (!v.readyToFinish()) { v.render(); - v.wait (0.4); - points.push_back (points.back() + sm::vec<>::uy() * 0.04f); - points.pop_front(); - isv->set_data (points, data); + v.wait (0.008); + + // Update all points/data + xyz = f (i); + points[i%dsz] = xyz; + data[i%dsz] = xyz[2]; + ++i; + // Place data in SSBO. first call of set_data should occur after first call to v.render() + isvp->set_data (points, data); } return rtn; diff --git a/examples/gl_compute/shader_ssbo.cpp b/examples/gl_compute/shader_ssbo.cpp index 9f86d9ed..37d2055a 100644 --- a/examples/gl_compute/shader_ssbo.cpp +++ b/examples/gl_compute/shader_ssbo.cpp @@ -86,9 +86,12 @@ namespace my { sm::vec _dims = mplot::loadpng ("../examples/gl_compute/bike.png", inputvv); if ((_dims - dims) > 0) { throw std::runtime_error ("Loaded image is not expected size"); } + this->input_ssbo.init (); // could take the image data as arg + // Set that data into the SSBO object (where it is stored in a vec<>) - std::copy (inputvv.begin(), inputvv.end(), this->input_ssbo.data.begin()); - this->input_ssbo.init(); + sm::vec data; + std::copy (inputvv.begin(), inputvv.end(), data.begin()); + this->input_ssbo.copy_to_gpu (data); } ~compute_manager() @@ -119,8 +122,9 @@ namespace my { this->compute_program.load_shaders (shaders); // We'll reuse the vertex/fragment shaders from the shadercompute example + std::string defVtxShdr = mplot::getDefaultVtxShader (mplot::gl::version_3_1_es); std::vector vtxshaders = { - {GL_VERTEX_SHADER, "../examples/gl_compute/shader_ssbo.vert.glsl", mplot::defaultVtxShader, 0 }, + {GL_VERTEX_SHADER, "../examples/gl_compute/shader_ssbo.vert.glsl", defVtxShdr.c_str(), 0 }, {GL_FRAGMENT_SHADER, "../examples/gl_compute/shader_ssbo.frag.glsl", mplot::defaultFragShader, 0 } }; this->vtxprog = mplot::gl::LoadShaders (vtxshaders); @@ -130,10 +134,12 @@ namespace my { // Override your one time/non-rendering compute function void compute() final { - // To copy updated data: - this->input_ssbo.data[0] = std::abs (std::sin (compstep)); // Make a pixel pulse + // To copy updated data. Would be nice to give a data subrange to update. + sm::vec data_update; + data_update[0] = std::abs (std::sin (compstep)); // Make a pixel pulse + data_update[1] = std::abs (std::cos (compstep)); compstep += 0.0001; - this->input_ssbo.copy_to_gpu(); + this->input_ssbo.copy_to_gpu (data_update, 2); this->measure_compute(); // optional this->compute_program.use(); @@ -142,8 +148,14 @@ namespace my { // To retreive data from the SSBO: // sm::range ssbo_range = this->input_ssbo.get_range(); // or - this->input_ssbo.copy_from_gpu(); - // and access input_ssbo.data. +#if 0 + sm::vec data_rtn; + this->input_ssbo.copy_from_gpu (data_rtn, 0); + std::cout << "data_rtn[0-5] = " + << data_rtn[0] << ", " << data_rtn[1] << ", " + << data_rtn[2] << ", " << data_rtn[3] << ", " + << data_rtn[4] << ", " << data_rtn[5] << std::endl; +#endif } // Override the render method to do whatever visualization you want diff --git a/mplot/InstancedScatterVisual.h b/mplot/InstancedScatterVisual.h index 04e71102..7ba8edee 100644 --- a/mplot/InstancedScatterVisual.h +++ b/mplot/InstancedScatterVisual.h @@ -48,34 +48,40 @@ namespace mplot void set_data (const std::deque>& points, const std::deque& data) { + if (this->instance_data.ready() == false) { throw std::runtime_error ("instance_data ssbo is not ready"); } if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } size_t j = 0; + + sm::vec::max_instance_limit> replacement_data; for (size_t i = 0; i < points.size(); ++i) { sm::vec c = points[i]; - this->instance_data.data[j++] = c[0]; - this->instance_data.data[j++] = c[1]; - this->instance_data.data[j++] = c[2]; - this->instance_data.data[j++] = data[i]; + replacement_data[j++] = c[0]; + replacement_data[j++] = c[1]; + replacement_data[j++] = c[2]; + replacement_data[j++] = data[i]; } this->instance_count = points.size(); + this->instance_data.copy_to_gpu (replacement_data); } void set_data (const sm::vvec>& points, const sm::vvec& data) { + if (this->instance_data.ready() == false) { throw std::runtime_error ("instance_data ssbo is not ready"); } if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } size_t j = 0; for (size_t i = 0; i < points.size(); ++i) { sm::vec c = points[i]; - this->instance_data.data[j++] = c[0]; - this->instance_data.data[j++] = c[1]; - this->instance_data.data[j++] = c[2]; - this->instance_data.data[j++] = data[i]; + this->idata[j++] = c[0]; + this->idata[j++] = c[1]; + this->idata[j++] = c[2]; + this->idata[j++] = data[i]; } this->instance_count = points.size(); + this->instance_data.copy_to_gpu (this->idata); } //! Compute spheres for a scatter plot diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index b9060626..5506c89f 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -106,9 +106,17 @@ namespace mplot _glfn->BindVertexArray(0); // carefully unbind and rebind mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); + if (this->flags.test (vm_bools::instanced)) { + if constexpr (mplot::gl::version::has_ssbo (glver) == true) { + this->instance_data.init (_glfn); + } else { + throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); + } + } + if (this->flags.test (vm_bools::instanced)) { // Ensure instance_data is over on the GPU - this->instance_data.copy_to_gpu(); + this->instance_data.copy_to_gpu (this->idata); } /* @@ -418,6 +426,7 @@ namespace mplot static constexpr unsigned int max_instances = 1024; // Each instance has: location (3 floats), scale (1 float) static constexpr unsigned int max_instance_limit = 4 * max_instances; mplot::gl::ssbo instance_data; + sm::vec idata = {}; constexpr unsigned int max_instanced_items() { return max_instances; } protected: @@ -439,14 +448,6 @@ namespace mplot mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); _glfn->EnableVertexAttribArray (bufferAttribPosition); mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); - - if (this->flags.test (vm_bools::instanced)) { - if constexpr (mplot::gl::version::has_ssbo (glver) == true) { - this->instance_data.init (_glfn); - } else { - throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); - } - } } }; diff --git a/mplot/gl/ssbo_mx.h b/mplot/gl/ssbo_mx.h index 38e073e2..82863c68 100644 --- a/mplot/gl/ssbo_mx.h +++ b/mplot/gl/ssbo_mx.h @@ -21,17 +21,16 @@ namespace mplot::gl // @tparam index: The index of the buffer, used in the GLSL // @tparam T: The type of the data in the SSBO // @tparam N: The number of elements of type T in the SSBO - // Could add version template params if necessary, to select correct gl function calls - template // T should be simple type? + template // requires std::is_arithmetic_v? struct ssbo { // The name of the buffer, generated with glGenBuffers() unsigned int name = 0; - // The CPU-side data for the buffer - sm::vec data; // The OpenGL function pointer GladGLContext* glfn = nullptr; + bool ready() const { return this->name != 0u; } + ssbo() {} ~ssbo() {} @@ -40,31 +39,52 @@ namespace mplot::gl { this->glfn = _glfn; this->glfn->GenBuffers (1, &this->name); - this->copy_to_gpu(); + this->init_buffer_object(); } - // Copy the data in the sm::vec data over to the GPU - void copy_to_gpu() + void init_buffer_object() { + sm::vec data = {}; this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), this->data.data(), GL_STATIC_DRAW); + this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); } - // Map the GPU memory to CPU space, then copy the values into this->data. NB: it's a - // performance hit to *copy* to the mapped data to our sm::vec, because the data is + // Copy data in the sm::vec data over to the GPU, once it has been initialized with init_buffer_object + // data may contain less than N elements, and may be copied at an offset + template + void copy_to_gpu (sm::vec& data, std::size_t offset = 0u) + { + static_assert (_N <= N); + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), N * sizeof(T), GL_MAP_WRITE_BIT)); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: + for (unsigned int i = 0; i < N; ++i) { cpuptr[i] = data[i]; } + this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + + // Map the GPU memory to CPU space, then copy the values into data. NB: it's a + // performance hit to *copy* to the mapped data to the sm::vec, because the data is // *already in CPU accessible memory* after glMapBufferRange(). - // However, in case you need it, here it is. - void copy_from_gpu() + // + // The fastest way to compute on the CPU side would be to add a method to this class (or an + // extension of it) and operate directly on the pointer cpuptr. + template + void copy_from_gpu (sm::vec& data, std::size_t offset = 0u) { + static_assert (_N <= N); this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N*sizeof(T), GL_MAP_READ_BIT)); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), N * sizeof(T), GL_MAP_READ_BIT)); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - for (unsigned int i = 0; i < N; ++i) { this->data[i] = cpuptr[i]; } + for (unsigned int i = 0; i < N; ++i) { data[i] = cpuptr[i]; } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); @@ -81,7 +101,7 @@ namespace mplot::gl r.search_init(); this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N*sizeof(T), GL_MAP_READ_BIT)); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_READ_BIT)); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); for (unsigned int i = 0; i < N; ++i) { r.update (cpuptr[i]); } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); diff --git a/mplot/gl/ssbo_nomx.h b/mplot/gl/ssbo_nomx.h index 2b64608b..42cdb060 100644 --- a/mplot/gl/ssbo_nomx.h +++ b/mplot/gl/ssbo_nomx.h @@ -27,7 +27,7 @@ namespace mplot::gl // The name of the buffer, generated with glGenBuffers() unsigned int name = 0; // The CPU-side data for the buffer - sm::vec data; + //sm::vec data; ssbo() {} ~ssbo() {} @@ -36,32 +36,50 @@ namespace mplot::gl void init() { glGenBuffers (1, &this->name); - this->copy_to_gpu(); + this->init_buffer_object(); } - // Copy the data in the sm::vec data over to the GPU - void copy_to_gpu() + void init_buffer_object() { + sm::vec data = {}; glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__); - glBufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), this->data.data(), GL_STATIC_DRAW); + glBufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW); mplot::gl::Util::checkError (__FILE__, __LINE__); glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__); } + // Copy data in the sm::vec data over to the GPU, once it has been initialized with init_buffer_object + // data may contain less than N elements, and may be copied at an offset + template + void copy_to_gpu (sm::vec& data, std::size_t offset = 0u) + { + static_assert (_N <= N); + glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); + mplot::gl::Util::checkError (__FILE__, __LINE__); + T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), _N * sizeof(T), GL_MAP_WRITE_BIT)); + mplot::gl::Util::checkError (__FILE__, __LINE__); + // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: + for (unsigned int i = 0; i < _N; ++i) { cpuptr[i] = data[i]; } + glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); + glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__); + } + // Map the GPU memory to CPU space, then copy the values into this->data. NB: it's a // performance hit to *copy* to the mapped data to our sm::vec, because the data is // *already in CPU accessible memory* after glMapBufferRange(). // However, in case you need it, here it is. - void copy_from_gpu() + template + void copy_from_gpu (sm::vec& data, std::size_t offset = 0u) { + static_assert (_N <= N); glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__); - T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N*sizeof(T), GL_MAP_READ_BIT)); + T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), _N * sizeof(T), GL_MAP_READ_BIT)); mplot::gl::Util::checkError (__FILE__, __LINE__); - //this->data.resize(N); - for (unsigned int i = 0; i < N; ++i) { this->data[i] = cpuptr[i]; } + for (unsigned int i = 0; i < _N; ++i) { data[i] = cpuptr[i]; } glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__); @@ -78,7 +96,7 @@ namespace mplot::gl r.search_init(); glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__); - T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N*sizeof(T), GL_MAP_READ_BIT)); + T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_READ_BIT)); mplot::gl::Util::checkError (__FILE__, __LINE__); for (unsigned int i = 0; i < N; ++i) { r.update (cpuptr[i]); } glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); From ec70c99e466fc3108be50f1b58316ad600518fc1 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 15 Dec 2025 14:22:11 +0000 Subject: [PATCH 09/46] Yes, we can use a vec inside an ssbo --- examples/breadcrumbs.cpp | 2 +- examples/scatter_instanced.cpp | 9 +++---- mplot/InstancedScatterVisual.h | 24 ++++++++----------- mplot/VisualModelImplMX.h | 26 ++++++++++---------- mplot/gl/ssbo_mx.h | 44 +++++++++++++++++++++++++++------- 5 files changed, 65 insertions(+), 40 deletions(-) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp index db17027e..56527f79 100644 --- a/examples/breadcrumbs.cpp +++ b/examples/breadcrumbs.cpp @@ -66,7 +66,7 @@ int main() points[i%dsz] = xyz; data[i%dsz] = xyz[2]; ++i; - // Place data in SSBO. first call of set_data should occur after first call to v.render() + // Place data in SSBO. first call of set_data must occur after first call to v.render() isvp->set_data (points, data); } diff --git a/examples/scatter_instanced.cpp b/examples/scatter_instanced.cpp index 08cb3d1e..6f8e7046 100644 --- a/examples/scatter_instanced.cpp +++ b/examples/scatter_instanced.cpp @@ -42,12 +42,13 @@ int main() auto isv = std::make_unique> (sm::vec<>{}); v.bindmodel (isv); - // Place data in SSBO - isv->set_data (points, data); isv->radiusFixed = 0.03f; - //isv->cm.setType (mplot::ColourMapType::Plasma); isv->finalize(); - v.addVisualModel (isv); + auto isvp = v.addVisualModel (isv); + + v.render(); + // Place data in SSBO. You have to call v.render() once first. + isvp->set_data (points, data); v.keepOpen(); diff --git a/mplot/InstancedScatterVisual.h b/mplot/InstancedScatterVisual.h index 7ba8edee..7915a0bf 100644 --- a/mplot/InstancedScatterVisual.h +++ b/mplot/InstancedScatterVisual.h @@ -48,40 +48,36 @@ namespace mplot void set_data (const std::deque>& points, const std::deque& data) { - if (this->instance_data.ready() == false) { throw std::runtime_error ("instance_data ssbo is not ready"); } if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } size_t j = 0; - - sm::vec::max_instance_limit> replacement_data; for (size_t i = 0; i < points.size(); ++i) { sm::vec c = points[i]; - replacement_data[j++] = c[0]; - replacement_data[j++] = c[1]; - replacement_data[j++] = c[2]; - replacement_data[j++] = data[i]; + this->instance_data.data[j++] = c[0]; + this->instance_data.data[j++] = c[1]; + this->instance_data.data[j++] = c[2]; + this->instance_data.data[j++] = data[i]; } this->instance_count = points.size(); - this->instance_data.copy_to_gpu (replacement_data); + this->instance_data.copy_to_gpu(); } void set_data (const sm::vvec>& points, const sm::vvec& data) { - if (this->instance_data.ready() == false) { throw std::runtime_error ("instance_data ssbo is not ready"); } if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } size_t j = 0; for (size_t i = 0; i < points.size(); ++i) { sm::vec c = points[i]; - this->idata[j++] = c[0]; - this->idata[j++] = c[1]; - this->idata[j++] = c[2]; - this->idata[j++] = data[i]; + this->instance_data.data[j++] = c[0]; + this->instance_data.data[j++] = c[1]; + this->instance_data.data[j++] = c[2]; + this->instance_data.data[j++] = data[i]; } this->instance_count = points.size(); - this->instance_data.copy_to_gpu (this->idata); + this->instance_data.copy_to_gpu(); } //! Compute spheres for a scatter plot diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 5506c89f..3789f0f3 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -71,6 +71,18 @@ namespace mplot model->releaseContext = &mplot::VisualBase::release_context; } + void init_instance_data() + { + if constexpr (mplot::gl::version::has_ssbo (glver) == true) { + if (this->instance_data.ready() == false) { + GladGLContext* _glfn = this->get_glfn (this->parentVis); + this->instance_data.init (_glfn); + } + } else { + throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); + } + } + //! Common code to call after the vertices have been set up. GL has to have been initialised. void postVertexInit() final { @@ -106,17 +118,8 @@ namespace mplot _glfn->BindVertexArray(0); // carefully unbind and rebind mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); - if (this->flags.test (vm_bools::instanced)) { - if constexpr (mplot::gl::version::has_ssbo (glver) == true) { - this->instance_data.init (_glfn); - } else { - throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); - } - } - - if (this->flags.test (vm_bools::instanced)) { - // Ensure instance_data is over on the GPU - this->instance_data.copy_to_gpu (this->idata); + if (this->flags.test (vm_bools::instanced) && this->instance_data.ready() == false) { + this->init_instance_data(); } /* @@ -426,7 +429,6 @@ namespace mplot static constexpr unsigned int max_instances = 1024; // Each instance has: location (3 floats), scale (1 float) static constexpr unsigned int max_instance_limit = 4 * max_instances; mplot::gl::ssbo instance_data; - sm::vec idata = {}; constexpr unsigned int max_instanced_items() { return max_instances; } protected: diff --git a/mplot/gl/ssbo_mx.h b/mplot/gl/ssbo_mx.h index 82863c68..3c99e87e 100644 --- a/mplot/gl/ssbo_mx.h +++ b/mplot/gl/ssbo_mx.h @@ -28,6 +28,8 @@ namespace mplot::gl unsigned int name = 0; // The OpenGL function pointer GladGLContext* glfn = nullptr; + // Our CPU side data buffer + sm::vec data = {}; bool ready() const { return this->name != 0u; } @@ -38,16 +40,16 @@ namespace mplot::gl void init (GladGLContext* _glfn) { this->glfn = _glfn; - this->glfn->GenBuffers (1, &this->name); + this->glfn->GenBuffers (1, &this->name); // has to happen after VBO buffers? this->init_buffer_object(); } void init_buffer_object() { - sm::vec data = {}; + sm::vec zd = {}; this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW); + this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), zd.data(), GL_STATIC_DRAW); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); @@ -56,20 +58,43 @@ namespace mplot::gl // Copy data in the sm::vec data over to the GPU, once it has been initialized with init_buffer_object // data may contain less than N elements, and may be copied at an offset template - void copy_to_gpu (sm::vec& data, std::size_t offset = 0u) + void copy_to_gpu (sm::vec& _data, std::size_t offset = 0u) { static_assert (_N <= N); + + // Update local cached version of data, a portion of which we're about to write to the SSBO + for (unsigned int i = 0; i < _N; ++i) { this->data[i + offset] = _data[i]; } + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), N * sizeof(T), GL_MAP_WRITE_BIT)); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, + offset * sizeof(T), _N * sizeof(T), GL_MAP_WRITE_BIT)); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: - for (unsigned int i = 0; i < N; ++i) { cpuptr[i] = data[i]; } + for (unsigned int i = 0; i < _N; ++i) { cpuptr[i] = _data[i]; } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); } + void copy_to_gpu() + { + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_WRITE_BIT)); + if (cpuptr == nullptr) { + // Error + std::cout << "ssbo::copy_to_gpu(): MapBufferRange error" << std::endl; + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } else { + // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: + for (unsigned int i = 0; i < N; ++i) { cpuptr[i] = this->data[i]; } + this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + } + // Map the GPU memory to CPU space, then copy the values into data. NB: it's a // performance hit to *copy* to the mapped data to the sm::vec, because the data is // *already in CPU accessible memory* after glMapBufferRange(). @@ -77,14 +102,15 @@ namespace mplot::gl // The fastest way to compute on the CPU side would be to add a method to this class (or an // extension of it) and operate directly on the pointer cpuptr. template - void copy_from_gpu (sm::vec& data, std::size_t offset = 0u) + void copy_from_gpu (std::size_t offset = 0u) { static_assert (_N <= N); this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), N * sizeof(T), GL_MAP_READ_BIT)); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, + offset * sizeof(T), _N * sizeof(T), GL_MAP_READ_BIT)); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - for (unsigned int i = 0; i < N; ++i) { data[i] = cpuptr[i]; } + for (unsigned int i = 0; i < _N; ++i) { this->data[i + offset] = cpuptr[i]; } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); From 412683de28d2872282a7fb976b46df62ed996314 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 15 Dec 2025 14:50:31 +0000 Subject: [PATCH 10/46] Think this sorts SSBOs --- examples/gl_compute/shader_ssbo.cpp | 10 +++--- examples/gl_compute/shadercompute.cpp | 10 +++--- mplot/gl/ssbo_mx.h | 33 +++++++++++------ mplot/gl/ssbo_nomx.h | 52 ++++++++++++++++++++++----- 4 files changed, 77 insertions(+), 28 deletions(-) diff --git a/examples/gl_compute/shader_ssbo.cpp b/examples/gl_compute/shader_ssbo.cpp index 37d2055a..532d51b0 100644 --- a/examples/gl_compute/shader_ssbo.cpp +++ b/examples/gl_compute/shader_ssbo.cpp @@ -4,7 +4,7 @@ */ // You have to include the GL headers manually so that you will be sure you have the -// right ones. THis is because for OpenGL version 4.3, you would include GL3/gl3.h and +// right ones. This is because for OpenGL version 4.3, you would include GL3/gl3.h and // GL/glext.h whereas if you are targeting OpenGL 3.1 ES, you want to include // GLES3/gl3[12].h (and maybe GLES3/gl3ext.h). #include @@ -18,8 +18,10 @@ namespace my { + static constexpr int glver = mplot::gl::version_3_1_es; + // Use OpenGL 3.1 ES here - struct compute_manager : public mplot::gl::compute_manager + struct compute_manager : public mplot::gl::compute_manager { static constexpr int dwidth = 256; static constexpr int dheight = 65; @@ -122,7 +124,7 @@ namespace my { this->compute_program.load_shaders (shaders); // We'll reuse the vertex/fragment shaders from the shadercompute example - std::string defVtxShdr = mplot::getDefaultVtxShader (mplot::gl::version_3_1_es); + std::string defVtxShdr = mplot::getDefaultVtxShader (glver); std::vector vtxshaders = { {GL_VERTEX_SHADER, "../examples/gl_compute/shader_ssbo.vert.glsl", defVtxShdr.c_str(), 0 }, {GL_FRAGMENT_SHADER, "../examples/gl_compute/shader_ssbo.frag.glsl", mplot::defaultFragShader, 0 } @@ -206,7 +208,7 @@ namespace my { // CPU side input data. This will be SSBO index 1. mplot::gl::ssbo<1, float, dsz> input_ssbo; // You will need at least one gl::compute_shaderprog - mplot::gl::compute_shaderprog compute_program; + mplot::gl::compute_shaderprog compute_program; }; } // namespace my diff --git a/examples/gl_compute/shadercompute.cpp b/examples/gl_compute/shadercompute.cpp index 11ffa136..0a26fc49 100644 --- a/examples/gl_compute/shadercompute.cpp +++ b/examples/gl_compute/shadercompute.cpp @@ -12,7 +12,6 @@ * https://learnopengl.com/Guest-Articles/2022/Compute-Shaders/Introduction */ - // You have to include the GL headers manually so that you will be sure you have the // right ones. This is because for OpenGL version 4.3, you would include GL3/gl3.h and // GL/glext.h whereas if you are targeting OpenGL 3.1 ES, you want to include @@ -25,8 +24,10 @@ namespace my { + static constexpr int glver = mplot::gl::version_4_5; + // Specify OpenGL version 4.5 (4.3 is min for compute) - struct compute_manager : public mplot::gl::compute_manager + struct compute_manager : public mplot::gl::compute_manager { // Call init in your constructor, ensuring *your* version of load_shaders() is called. compute_manager() @@ -80,8 +81,9 @@ namespace my { }; this->compute_program.load_shaders (shaders); + std::string defVtxShdr = mplot::getDefaultVtxShader (glver); std::vector vtxshaders = { - {GL_VERTEX_SHADER, "../examples/gl_compute/shadercompute.vert.glsl", mplot::defaultVtxShader, 0 }, + {GL_VERTEX_SHADER, "../examples/gl_compute/shadercompute.vert.glsl", defVtxShdr.c_str(), 0 }, {GL_FRAGMENT_SHADER, "../examples/gl_compute/shadercompute.frag.glsl", mplot::defaultFragShader, 0 } }; this->vtxprog = mplot::gl::LoadShaders (vtxshaders); @@ -137,7 +139,7 @@ namespace my { unsigned int vao = 0; unsigned int vbo = 0; // You will need at least one gl::compute_shaderprog - mplot::gl::compute_shaderprog compute_program; + mplot::gl::compute_shaderprog compute_program; }; } // namespace my diff --git a/mplot/gl/ssbo_mx.h b/mplot/gl/ssbo_mx.h index 3c99e87e..26dcdfc9 100644 --- a/mplot/gl/ssbo_mx.h +++ b/mplot/gl/ssbo_mx.h @@ -28,7 +28,7 @@ namespace mplot::gl unsigned int name = 0; // The OpenGL function pointer GladGLContext* glfn = nullptr; - // Our CPU side data buffer + // The CPU-side data for the buffer sm::vec data = {}; bool ready() const { return this->name != 0u; } @@ -69,7 +69,11 @@ namespace mplot::gl mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), _N * sizeof(T), GL_MAP_WRITE_BIT)); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + if (cpuptr == nullptr) { + std::cout << "ssbo::copy_to_gpu(sm::vec&): MapBufferRange error" << std::endl; + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + return; + } // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: for (unsigned int i = 0; i < _N; ++i) { cpuptr[i] = _data[i]; } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); @@ -83,16 +87,15 @@ namespace mplot::gl mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_WRITE_BIT)); if (cpuptr == nullptr) { - // Error std::cout << "ssbo::copy_to_gpu(): MapBufferRange error" << std::endl; mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - } else { - // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: - for (unsigned int i = 0; i < N; ++i) { cpuptr[i] = this->data[i]; } - this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); - this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + return; } + // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: + for (unsigned int i = 0; i < N; ++i) { cpuptr[i] = this->data[i]; } + this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); } // Map the GPU memory to CPU space, then copy the values into data. NB: it's a @@ -109,7 +112,11 @@ namespace mplot::gl mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), _N * sizeof(T), GL_MAP_READ_BIT)); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + if (cpuptr == nullptr) { + std::cout << "ssbo::copy_from_gpu(): MapBufferRange error" << std::endl; + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + return; + } for (unsigned int i = 0; i < _N; ++i) { this->data[i + offset] = cpuptr[i]; } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); @@ -128,7 +135,11 @@ namespace mplot::gl this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_READ_BIT)); - mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + if (cpuptr == nullptr) { + std::cout << "ssbo::get_range(): MapBufferRange error" << std::endl; + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + return r; + } for (unsigned int i = 0; i < N; ++i) { r.update (cpuptr[i]); } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); diff --git a/mplot/gl/ssbo_nomx.h b/mplot/gl/ssbo_nomx.h index 42cdb060..56a0b62c 100644 --- a/mplot/gl/ssbo_nomx.h +++ b/mplot/gl/ssbo_nomx.h @@ -27,7 +27,9 @@ namespace mplot::gl // The name of the buffer, generated with glGenBuffers() unsigned int name = 0; // The CPU-side data for the buffer - //sm::vec data; + sm::vec data = {}; + + bool ready() const { return this->name != 0u; } ssbo() {} ~ssbo() {} @@ -41,10 +43,9 @@ namespace mplot::gl void init_buffer_object() { - sm::vec data = {}; glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__); - glBufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW); + glBufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), this->data.data(), GL_STATIC_DRAW); mplot::gl::Util::checkError (__FILE__, __LINE__); glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__); @@ -53,15 +54,40 @@ namespace mplot::gl // Copy data in the sm::vec data over to the GPU, once it has been initialized with init_buffer_object // data may contain less than N elements, and may be copied at an offset template - void copy_to_gpu (sm::vec& data, std::size_t offset = 0u) + void copy_to_gpu (sm::vec& _data, std::size_t offset = 0u) { static_assert (_N <= N); + + // Update local cached version of data, a portion of which we're about to write to the SSBO + for (unsigned int i = 0; i < _N; ++i) { this->data[i + offset] = _data[i]; } + glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__); T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), _N * sizeof(T), GL_MAP_WRITE_BIT)); + if (cpuptr == nullptr) { + std::cout << "ssbo::copy_to_gpu(sm::vec&): MapBufferRange error" << std::endl; + mplot::gl::Util::checkError (__FILE__, __LINE__); + return; + } + // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: + for (unsigned int i = 0; i < _N; ++i) { cpuptr[i] = _data[i]; } + glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); + glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__); + } + + void copy_to_gpu () + { + glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); + mplot::gl::Util::checkError (__FILE__, __LINE__); + T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_WRITE_BIT)); + if (cpuptr == nullptr) { + std::cout << "ssbo::copy_to_gpu(): MapBufferRange error" << std::endl; + mplot::gl::Util::checkError (__FILE__, __LINE__); + return; + } // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: - for (unsigned int i = 0; i < _N; ++i) { cpuptr[i] = data[i]; } + for (unsigned int i = 0; i < N; ++i) { cpuptr[i] = this->data[i]; } glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__); @@ -72,14 +98,18 @@ namespace mplot::gl // *already in CPU accessible memory* after glMapBufferRange(). // However, in case you need it, here it is. template - void copy_from_gpu (sm::vec& data, std::size_t offset = 0u) + void copy_from_gpu (std::size_t offset = 0u) { static_assert (_N <= N); glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__); T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), _N * sizeof(T), GL_MAP_READ_BIT)); - mplot::gl::Util::checkError (__FILE__, __LINE__); - for (unsigned int i = 0; i < _N; ++i) { data[i] = cpuptr[i]; } + if (cpuptr == nullptr) { + std::cout << "ssbo::copy_from_gpu(): MapBufferRange error" << std::endl; + mplot::gl::Util::checkError (__FILE__, __LINE__); + return; + } + for (unsigned int i = 0; i < _N; ++i) { data[i + offset] = cpuptr[i]; } glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__); @@ -97,7 +127,11 @@ namespace mplot::gl glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__); T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_READ_BIT)); - mplot::gl::Util::checkError (__FILE__, __LINE__); + if (cpuptr == nullptr) { + std::cout << "ssbo::get_range(): MapBufferRange error" << std::endl; + mplot::gl::Util::checkError (__FILE__, __LINE__); + return r; + } for (unsigned int i = 0; i < N; ++i) { r.update (cpuptr[i]); } glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); From 1c02619957721ff01bef6d337eaf731ac423a16f Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 15 Dec 2025 14:59:14 +0000 Subject: [PATCH 11/46] Adds the forgotten vertex shader changes --- mplot/VisualDefaultShaders.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mplot/VisualDefaultShaders.h b/mplot/VisualDefaultShaders.h index b0e1e92f..4d86115a 100644 --- a/mplot/VisualDefaultShaders.h +++ b/mplot/VisualDefaultShaders.h @@ -27,8 +27,14 @@ namespace mplot " vec3 fragpos;\n" "} vertex;\n" "void main()\n" - "{\n" - " gl_Position = (p_matrix * v_matrix * m_matrix * position);\n" + " {\n" + " if (instanced != 0) {\n" + " vec4 offset = { instance_data[gl_InstanceID * 4], instance_data[gl_InstanceID * 4 + 1], instance_data[gl_InstanceID * 4 + 2], 1 };\n" + " float dval = instance_data[gl_InstanceID * 4 + 3];\n" + " gl_Position = (p_matrix * v_matrix * m_matrix * (position + offset));\n" + " } else {\n" + " gl_Position = (p_matrix * v_matrix * m_matrix * position);\n" + " }\n" " vertex.color = vec4(color, alpha);\n" " vertex.fragpos = vec3(m_matrix * position);\n" " vertex.normal = normalin;\n" From 55d0623f36f347ed38eb4e0a06ccba0571bc77d5 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 15 Dec 2025 17:01:48 +0000 Subject: [PATCH 12/46] Attempted to get the update to avoid a requirement to copy all the data --- examples/breadcrumbs.cpp | 41 ++++++++++++++++++++++++++-------- mplot/InstancedScatterVisual.h | 24 +++++++++++--------- mplot/VisualModelBase.h | 2 ++ mplot/VisualModelImplMX.h | 10 ++++++--- mplot/gl/ssbo_mx.h | 29 ++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 22 deletions(-) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp index 56527f79..017e2318 100644 --- a/examples/breadcrumbs.cpp +++ b/examples/breadcrumbs.cpp @@ -19,14 +19,14 @@ constexpr int glver = mplot::gl::version_4_3; constexpr float st = sm::mathconst::two_pi / 360; constexpr float z = 0.0f; -sm::vec f (int i) +sm::vec f (int i, float _z = 0.0f) { float phi = st * i; float r = 1.0f + 0.1f * std::sin (phi * 10.0f); sm::vec xyz = { r * std::sin (phi), r * std::cos (phi), - 0.0f + _z }; return xyz; } @@ -39,8 +39,8 @@ int main() v.lightingEffects(); constexpr int dsz = 260; - std::deque> points(dsz); - std::deque data(dsz); + sm::vvec> points(dsz); + sm::vvec data(dsz); int i = 0; sm::vec xyz = {}; @@ -57,17 +57,40 @@ int main() isv->finalize(); auto isvp = v.addVisualModel (isv); + v.render(); + isvp->set_data (points, data); + std::cout << "isvp->instance_count = " << isvp->instance_count << std::endl; + while (!v.readyToFinish()) { - v.render(); - v.wait (0.008); - // Update all points/data - xyz = f (i); + xyz = f (i%360); + //std::cout << "i = " << i << ", i%dsz = " << (i%dsz) << ", i%360 = " << (i%360) << " f(i%360) = " << xyz << " -> points[" << (i%dsz)<< "]" << std::endl; + points[i%dsz] = xyz; data[i%dsz] = xyz[2]; - ++i; + +#if 1 + // Update all points/data // Place data in SSBO. first call of set_data must occur after first call to v.render() isvp->set_data (points, data); + + v.render(); + v.waitevents (0.001); +#else + // update circularly, change isvp->instance_start each time + std::cout << "updating data with points[i%dsz] = " << points[i%dsz] << std::endl; + isvp->update_data (points, data, (i % dsz), (i % dsz)); + + std::cout << "Before render, isvp->instance_start is " << isvp->instance_start << std::endl; + + v.render(); + v.wait (0.4); + + isvp->instance_start += 1; + if (isvp->instance_start >= isvp->instance_count) { isvp->instance_start = 0; } + +#endif + ++i; } return rtn; diff --git a/mplot/InstancedScatterVisual.h b/mplot/InstancedScatterVisual.h index 7915a0bf..24a9d73f 100644 --- a/mplot/InstancedScatterVisual.h +++ b/mplot/InstancedScatterVisual.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -46,7 +45,7 @@ namespace mplot } } - void set_data (const std::deque>& points, const std::deque& data) + void set_data (const sm::vvec>& points, const sm::vvec& data) { if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } @@ -63,21 +62,26 @@ namespace mplot this->instance_data.copy_to_gpu(); } - void set_data (const sm::vvec>& points, const sm::vvec& data) + // Update the instance_data from points and data. Update the range of data starting at + // points/data index i_s and ending at i_e. + void update_data (const sm::vvec>& points, const sm::vvec& data, + std::size_t i_s, std::size_t i_e) { if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } + sm::vvec updated(4 * (1 + i_e - i_s)); size_t j = 0; - for (size_t i = 0; i < points.size(); ++i) { + for (size_t i = i_s; i <= i_e; ++i) { + //std::cout << "writing points/data["< c = points[i]; - this->instance_data.data[j++] = c[0]; - this->instance_data.data[j++] = c[1]; - this->instance_data.data[j++] = c[2]; - this->instance_data.data[j++] = data[i]; + updated[j++] = c[0]; + updated[j++] = c[1]; + updated[j++] = c[2]; + updated[j++] = data[i]; } - this->instance_count = points.size(); - this->instance_data.copy_to_gpu(); + //std::cout << "Copy updated, size " << updated.size() << " to gpu with offset " << i_s << "\n"; + this->instance_data.copy_to_gpu (updated, i_s); } //! Compute spheres for a scatter plot diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 2f95705d..da4c3f8c 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -720,6 +720,8 @@ namespace mplot //! If drawing with instancing, how many instances? unsigned int instance_count = 1; + //! Which datum in the instance buffer is the one to be drawn by the first thread? + unsigned int instance_start = 0; /*! * A function that will be runtime defined to get_shaderprogs from a pointer to diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 3789f0f3..b1066489 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -262,13 +262,17 @@ namespace mplot } // Draw the triangles - GLint loc_i = _glfn->GetUniformLocation (this->get_gprog(this->parentVis), static_cast("instanced")); + GLint loc_is = _glfn->GetUniformLocation (this->get_gprog(this->parentVis), static_cast("instance_start")); if (this->flags.test (vm_bools::instanced)) { - if (loc_i != -1) { _glfn->Uniform1i (loc_i, 1); } + if (loc_is != -1) { + _glfn->Uniform1i (loc_is, this->instance_start); + GLint loc_ic = _glfn->GetUniformLocation (this->get_gprog(this->parentVis), static_cast("instance_count")); + _glfn->Uniform1i (loc_ic, this->instance_count); + } _glfn->DrawElementsInstanced (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0, this->instance_count); } else { - if (loc_i != -1) { _glfn->Uniform1i (loc_i, 0); } + if (loc_is != -1) { _glfn->Uniform1i (loc_is, -1); } _glfn->DrawElements (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0); } diff --git a/mplot/gl/ssbo_mx.h b/mplot/gl/ssbo_mx.h index 26dcdfc9..ee48e172 100644 --- a/mplot/gl/ssbo_mx.h +++ b/mplot/gl/ssbo_mx.h @@ -81,6 +81,35 @@ namespace mplot::gl mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); } + void copy_to_gpu (sm::vvec& _data, std::size_t offset = 0u) + { + if (_data.size() > N) { throw std::runtime_error ("Too big"); } + + // Update local cached version of data, a portion of which we're about to write to the SSBO + //std::cout << "Copy " << _data.size() << " elements from _data[0] to this->data[" << offset << "]\n"; + for (unsigned int i = 0; i < _data.size(); ++i) { this->data[i + offset] = _data[i]; } + + //std::cout << "this->data is now\n\n" << this->data << std::endl; + + this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, + offset * sizeof(T), _data.size() * sizeof(T), GL_MAP_WRITE_BIT)); + if (cpuptr == nullptr) { + std::cout << "ssbo::copy_to_gpu(sm::vec&): MapBufferRange error" << std::endl; + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + return; + } + // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: + for (unsigned int i = 0; i < _data.size(); ++i) { + //std::cout << "Writing value " << " _data[i] = " << _data[i] << " to mapped range" << std::endl; + cpuptr[i] = _data[i]; + } + this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + } + void copy_to_gpu() { this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); From 349aff1912b040e7150ef38db912c34529a70a2a Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 15 Dec 2025 17:43:16 +0000 Subject: [PATCH 13/46] Minor changes --- mplot/gl/ssbo_mx.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mplot/gl/ssbo_mx.h b/mplot/gl/ssbo_mx.h index ee48e172..ea25f25e 100644 --- a/mplot/gl/ssbo_mx.h +++ b/mplot/gl/ssbo_mx.h @@ -93,20 +93,24 @@ namespace mplot::gl this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); + std::cout << "Mapping buffer range at offset " << offset << " floats = " << (offset * sizeof(T)) << " bytes" << std::endl; T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, - offset * sizeof(T), _data.size() * sizeof(T), GL_MAP_WRITE_BIT)); + offset * sizeof(T), _data.size() * sizeof(T), + GL_MAP_WRITE_BIT)); if (cpuptr == nullptr) { std::cout << "ssbo::copy_to_gpu(sm::vec&): MapBufferRange error" << std::endl; mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); return; } // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: - for (unsigned int i = 0; i < _data.size(); ++i) { - //std::cout << "Writing value " << " _data[i] = " << _data[i] << " to mapped range" << std::endl; + std::cout << "About to copy " << _data.size() << " things (prolly floats)...\n"; + for (unsigned int i = 0; i < _data.size(); ++i) { + std::cout << "Writing value " << " _data[i] = " << _data[i] << " into mapped range" << std::endl; cpuptr[i] = _data[i]; } - this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); - this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); + + this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); // necessary + this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); // optional mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); } @@ -121,6 +125,7 @@ namespace mplot::gl return; } // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: + //std::cout << "About to copy " << N << " things (prolly floats)...\n"; for (unsigned int i = 0; i < N; ++i) { cpuptr[i] = this->data[i]; } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); From 403e07d81e234cfdc77bd44e98ce7b2096b68d61 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 15 Dec 2025 17:43:39 +0000 Subject: [PATCH 14/46] Comment (confession) --- examples/breadcrumbs.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp index 017e2318..19b81f4b 100644 --- a/examples/breadcrumbs.cpp +++ b/examples/breadcrumbs.cpp @@ -76,7 +76,8 @@ int main() v.render(); v.waitevents (0.001); -#else + +#else // I haven't mastered updating a subrange of data in an SSBO // update circularly, change isvp->instance_start each time std::cout << "updating data with points[i%dsz] = " << points[i%dsz] << std::endl; isvp->update_data (points, data, (i % dsz), (i % dsz)); From 59d48c5295241926dae8ba4653f385aa5195f907 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 15 Dec 2025 17:44:21 +0000 Subject: [PATCH 15/46] Makes scene_to_visualmodels OpenGL-versionable --- mplot/compoundray/interop.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mplot/compoundray/interop.h b/mplot/compoundray/interop.h index 2b74f60e..0be3e4ac 100644 --- a/mplot/compoundray/interop.h +++ b/mplot/compoundray/interop.h @@ -51,7 +51,8 @@ namespace mplot::compoundray * This function finds the meshes in compound-ray's MulticamScene and creates corresponding * VisualModels in the mplot::Visual */ - void scene_to_visualmodels (MulticamScene* thescene, mplot::Visual<>* thevisual, bool make_navmeshes = false) + template + void scene_to_visualmodels (MulticamScene* thescene, mplot::Visual* thevisual, bool make_navmeshes = false) { static constexpr bool debug_meshload = false; std::vector> mymeshes = thescene->getMeshes(); @@ -138,7 +139,7 @@ namespace mplot::compoundray << ind.size() << " indices, " << posn.size() << " posns, " << norm.size() << " norms, " << colr.size() << " colours" << std::endl; } - auto vertvm = std::make_unique> (tfm, ind, posn, norm, colr); + auto vertvm = std::make_unique> (tfm, ind, posn, norm, colr); thevisual->bindmodel (vertvm); vertvm->name = mymeshes[mi]->name; if (make_navmeshes == true) { vertvm->make_navmesh(); } From c413b4ffa9a9364d13869a95fd681e5fb84e3a3b Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 11:59:37 +0000 Subject: [PATCH 16/46] More robustness --- mplot/VisualModelImplMX.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index b1066489..55777a10 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -263,14 +263,11 @@ namespace mplot // Draw the triangles GLint loc_is = _glfn->GetUniformLocation (this->get_gprog(this->parentVis), static_cast("instance_start")); + GLint loc_ic = _glfn->GetUniformLocation (this->get_gprog(this->parentVis), static_cast("instance_count")); if (this->flags.test (vm_bools::instanced)) { - if (loc_is != -1) { - _glfn->Uniform1i (loc_is, this->instance_start); - GLint loc_ic = _glfn->GetUniformLocation (this->get_gprog(this->parentVis), static_cast("instance_count")); - _glfn->Uniform1i (loc_ic, this->instance_count); - } - _glfn->DrawElementsInstanced (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0, - this->instance_count); + if (loc_is != -1) { _glfn->Uniform1i (loc_is, this->instance_start); } + if (loc_ic != -1) { _glfn->Uniform1i (loc_ic, this->instance_count); } + _glfn->DrawElementsInstanced (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0, this->instance_count); } else { if (loc_is != -1) { _glfn->Uniform1i (loc_is, -1); } _glfn->DrawElements (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0); From 2fca4000a969e5b1f45f477f2fc7779e9f6c7de9 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 12:01:17 +0000 Subject: [PATCH 17/46] Adds the necessary GLSL for breadcrumbs and scatter_instanced --- mplot/VisualDefaultShaders.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mplot/VisualDefaultShaders.h b/mplot/VisualDefaultShaders.h index 4d86115a..aec69251 100644 --- a/mplot/VisualDefaultShaders.h +++ b/mplot/VisualDefaultShaders.h @@ -14,7 +14,8 @@ namespace mplot "uniform mat4 v_matrix;\n" "uniform mat4 p_matrix;\n" "uniform float alpha;\n" - "uniform int instanced = 0;\n" + "uniform int instance_count = 0;\n" + "uniform int instance_start = -1;\n" "layout(location = 0) in vec4 position;\n" "layout(location = 1) in vec4 normalin;\n" "layout(location = 2) in vec3 color;\n"; @@ -28,7 +29,7 @@ namespace mplot "} vertex;\n" "void main()\n" " {\n" - " if (instanced != 0) {\n" + " if (instance_count > 0) {\n" " vec4 offset = { instance_data[gl_InstanceID * 4], instance_data[gl_InstanceID * 4 + 1], instance_data[gl_InstanceID * 4 + 2], 1 };\n" " float dval = instance_data[gl_InstanceID * 4 + 3];\n" " gl_Position = (p_matrix * v_matrix * m_matrix * (position + offset));\n" From 16bb73d913120ceab7d85fc60430292052b08351 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 13:25:47 +0000 Subject: [PATCH 18/46] Moves some attributes back a level --- mplot/VisualModelBase.h | 5 +++++ mplot/VisualModelImplMX.h | 7 ++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index da4c3f8c..07c1b115 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -718,6 +718,11 @@ namespace mplot GLuint idx = 0u; GLuint idx_bb = 0u; + //! Instanced rendering mode (SSBO access) + static constexpr unsigned int instance_index = 1; // instance data stored in SSBO index 1 (must match GLSL code) + static constexpr unsigned int max_instances = 1024; // Each instance has: location (3 floats), scale (1 float) + static constexpr unsigned int max_instance_limit = 4 * max_instances; + constexpr unsigned int max_instanced_items() { return max_instances; } //! If drawing with instancing, how many instances? unsigned int instance_count = 1; //! Which datum in the instance buffer is the one to be drawn by the first thread? diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 55777a10..db5433bb 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -426,11 +426,8 @@ namespace mplot std::function*)> get_glfn; //! Shader Storage Buffer Object for instanced rendering - static constexpr unsigned int instance_index = 1; // instance data stored in SSBO index 1 (must match GLSL code) - static constexpr unsigned int max_instances = 1024; // Each instance has: location (3 floats), scale (1 float) - static constexpr unsigned int max_instance_limit = 4 * max_instances; - mplot::gl::ssbo instance_data; - constexpr unsigned int max_instanced_items() { return max_instances; } + mplot::gl::ssbo::instance_index, + float, mplot::VisualModelBase::max_instance_limit> instance_data; protected: From 90fc9f81c562082d587699db3fa70a2e8d822ceb Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 13:26:13 +0000 Subject: [PATCH 19/46] Default for InstancedScatterVisual should be 4.3 --- mplot/InstancedScatterVisual.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mplot/InstancedScatterVisual.h b/mplot/InstancedScatterVisual.h index 24a9d73f..1bad4dea 100644 --- a/mplot/InstancedScatterVisual.h +++ b/mplot/InstancedScatterVisual.h @@ -16,7 +16,7 @@ namespace mplot { - template + template class InstancedScatterVisual : public VisualModel { public: From d38c6061cc2a978359292a23c706f7be1b3a8529 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 13:42:20 +0000 Subject: [PATCH 20/46] Fix to allow both instanced and non-instanced models in one scene --- examples/breadcrumbs.cpp | 16 +++++++++++++++- mplot/VisualModelBase.h | 2 +- mplot/VisualModelImplMX.h | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp index 19b81f4b..286560a5 100644 --- a/examples/breadcrumbs.cpp +++ b/examples/breadcrumbs.cpp @@ -13,6 +13,7 @@ #include #include #include +#include // Instanced rendering requires OpenGL 4.3 or higher (for the SSBO) constexpr int glver = mplot::gl::version_4_3; @@ -38,6 +39,19 @@ int main() mplot::Visual v(1024, 768, "mplot::InstancedScatterVisual"); v.lightingEffects(); + // A normal, non instanced model. A sphere to orbit around. + auto gv1 = std::make_unique> (sm::vec<>{}, 0.2f); + v.bindmodel (gv1); + gv1->iterations = 3; + gv1->cm.setType (mplot::ColourMapType::Tofino); + gv1->finalize(); + // re-colour after construction + auto gv1p = v.addVisualModel (gv1); + // sequential colouring: + size_t sz1 = gv1p->data.size(); + gv1p->data.linspace (0.0f, 1.0f, sz1); + gv1p->reinitColours(); + constexpr int dsz = 260; sm::vvec> points(dsz); sm::vvec data(dsz); @@ -75,7 +89,7 @@ int main() isvp->set_data (points, data); v.render(); - v.waitevents (0.001); + v.waitevents (0.03); #else // I haven't mastered updating a subrange of data in an SSBO // update circularly, change isvp->instance_start each time diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 07c1b115..d47025e4 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -724,7 +724,7 @@ namespace mplot static constexpr unsigned int max_instance_limit = 4 * max_instances; constexpr unsigned int max_instanced_items() { return max_instances; } //! If drawing with instancing, how many instances? - unsigned int instance_count = 1; + unsigned int instance_count = 0; //! Which datum in the instance buffer is the one to be drawn by the first thread? unsigned int instance_start = 0; diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index db5433bb..a01d7c23 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -270,6 +270,7 @@ namespace mplot _glfn->DrawElementsInstanced (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0, this->instance_count); } else { if (loc_is != -1) { _glfn->Uniform1i (loc_is, -1); } + if (loc_ic != -1) { _glfn->Uniform1i (loc_ic, -1); } _glfn->DrawElements (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0); } From be8558c10e4d9d27f38b11138acaf188c74e1116 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 15:01:59 +0000 Subject: [PATCH 21/46] Oops, make that last dim a 0 --- mplot/VisualDefaultShaders.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mplot/VisualDefaultShaders.h b/mplot/VisualDefaultShaders.h index aec69251..7c274391 100644 --- a/mplot/VisualDefaultShaders.h +++ b/mplot/VisualDefaultShaders.h @@ -30,7 +30,7 @@ namespace mplot "void main()\n" " {\n" " if (instance_count > 0) {\n" - " vec4 offset = { instance_data[gl_InstanceID * 4], instance_data[gl_InstanceID * 4 + 1], instance_data[gl_InstanceID * 4 + 2], 1 };\n" + " vec4 offset = { instance_data[gl_InstanceID * 4], instance_data[gl_InstanceID * 4 + 1], instance_data[gl_InstanceID * 4 + 2], 0 };\n" " float dval = instance_data[gl_InstanceID * 4 + 3];\n" " gl_Position = (p_matrix * v_matrix * m_matrix * (position + offset));\n" " } else {\n" From 2c696f6935e72b16501519fcfaf3dde94a90bd53 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 15:30:01 +0000 Subject: [PATCH 22/46] Gold default colour --- mplot/InstancedScatterVisual.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mplot/InstancedScatterVisual.h b/mplot/InstancedScatterVisual.h index 1bad4dea..1119e068 100644 --- a/mplot/InstancedScatterVisual.h +++ b/mplot/InstancedScatterVisual.h @@ -88,7 +88,7 @@ namespace mplot void initializeVertices() { // Draw one marker. It will then be instanced as many using instanced rendering - this->marker (sm::vec<>{}, mplot::colour::grey50, this->radiusFixed); + this->marker (sm::vec<>{}, mplot::colour::gold1, this->radiusFixed); } // The constexpr, unordered geodesic code is no slower than the regular From f2a1aae48d8238c338f9e5551be4537a7214f78e Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 15:38:53 +0000 Subject: [PATCH 23/46] Next thing to fix --- mplot/VisualModelBase.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index d47025e4..919d60fa 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -720,7 +720,9 @@ namespace mplot //! Instanced rendering mode (SSBO access) static constexpr unsigned int instance_index = 1; // instance data stored in SSBO index 1 (must match GLSL code) - static constexpr unsigned int max_instances = 1024; // Each instance has: location (3 floats), scale (1 float) + // Each instance has: location (3 floats), scale (1 float), rot (4 floats) colour/alpha (4 floats), so that's 48 bytes. + // 10^7 instances would be 500 MB of GPU RAM. But this crashes (even for 50 MB). + static constexpr unsigned int max_instances = 1024 * 1024; static constexpr unsigned int max_instance_limit = 4 * max_instances; constexpr unsigned int max_instanced_items() { return max_instances; } //! If drawing with instancing, how many instances? From 8aba98a9b8d32bcf7109c6e7501130f9cd99eb97 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 17:00:40 +0000 Subject: [PATCH 24/46] Switches ssbo to use vvec instead of vec as data store. --- mplot/InstancedScatterVisual.h | 5 +++- mplot/VisualModelBase.h | 6 ++--- mplot/VisualModelImplMX.h | 2 +- mplot/gl/ssbo_mx.h | 21 ++++++++++------ mplot/gl/ssbo_nomx.h | 46 ++++++++++++++++++++++++++++++---- 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/mplot/InstancedScatterVisual.h b/mplot/InstancedScatterVisual.h index 1119e068..d65725eb 100644 --- a/mplot/InstancedScatterVisual.h +++ b/mplot/InstancedScatterVisual.h @@ -51,8 +51,11 @@ namespace mplot if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } size_t j = 0; + this->instance_data.data.resize (mplot::VisualModelBase::floats_per_instance * points.size()); for (size_t i = 0; i < points.size(); ++i) { sm::vec c = points[i]; + // This to be a Member of VisualModelBase? + // this->addInstanceParams (c, data[i], scale, rotn, colour); // or something this->instance_data.data[j++] = c[0]; this->instance_data.data[j++] = c[1]; this->instance_data.data[j++] = c[2]; @@ -70,7 +73,7 @@ namespace mplot if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } - sm::vvec updated(4 * (1 + i_e - i_s)); + sm::vvec updated(mplot::VisualModelBase::floats_per_instance * (1 + i_e - i_s)); size_t j = 0; for (size_t i = i_s; i <= i_e; ++i) { //std::cout << "writing points/data["<::instance_index, - float, mplot::VisualModelBase::max_instance_limit> instance_data; + float, mplot::VisualModelBase::max_instance_floats> instance_data; protected: diff --git a/mplot/gl/ssbo_mx.h b/mplot/gl/ssbo_mx.h index ea25f25e..d45503dc 100644 --- a/mplot/gl/ssbo_mx.h +++ b/mplot/gl/ssbo_mx.h @@ -20,7 +20,7 @@ namespace mplot::gl // An SSBO and its data // @tparam index: The index of the buffer, used in the GLSL // @tparam T: The type of the data in the SSBO - // @tparam N: The number of elements of type T in the SSBO + // @tparam N: The max number of elements of type T in the SSBO. N elements are reserved in the vvec data template // requires std::is_arithmetic_v? struct ssbo { @@ -29,7 +29,7 @@ namespace mplot::gl // The OpenGL function pointer GladGLContext* glfn = nullptr; // The CPU-side data for the buffer - sm::vec data = {}; + sm::vvec data = {}; bool ready() const { return this->name != 0u; } @@ -41,15 +41,15 @@ namespace mplot::gl { this->glfn = _glfn; this->glfn->GenBuffers (1, &this->name); // has to happen after VBO buffers? + this->data.reserve (N * sizeof(T)); this->init_buffer_object(); } void init_buffer_object() { - sm::vec zd = {}; this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), zd.data(), GL_STATIC_DRAW); + this->glfn->BufferData (GL_SHADER_STORAGE_BUFFER, N * sizeof(T), this->data.data(), GL_STATIC_DRAW); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); @@ -63,6 +63,7 @@ namespace mplot::gl static_assert (_N <= N); // Update local cached version of data, a portion of which we're about to write to the SSBO + this->data.resize (_N); for (unsigned int i = 0; i < _N; ++i) { this->data[i + offset] = _data[i]; } this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); @@ -87,6 +88,7 @@ namespace mplot::gl // Update local cached version of data, a portion of which we're about to write to the SSBO //std::cout << "Copy " << _data.size() << " elements from _data[0] to this->data[" << offset << "]\n"; + this->data.resize (_data.size()); for (unsigned int i = 0; i < _data.size(); ++i) { this->data[i + offset] = _data[i]; } //std::cout << "this->data is now\n\n" << this->data << std::endl; @@ -118,7 +120,8 @@ namespace mplot::gl { this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_WRITE_BIT)); + std::size_t sz = this->data.size(); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, sz * sizeof(T), GL_MAP_WRITE_BIT)); if (cpuptr == nullptr) { std::cout << "ssbo::copy_to_gpu(): MapBufferRange error" << std::endl; mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); @@ -126,7 +129,7 @@ namespace mplot::gl } // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: //std::cout << "About to copy " << N << " things (prolly floats)...\n"; - for (unsigned int i = 0; i < N; ++i) { cpuptr[i] = this->data[i]; } + for (unsigned int i = 0; i < sz; ++i) { cpuptr[i] = this->data[i]; } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); @@ -151,6 +154,7 @@ namespace mplot::gl mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); return; } + this->data.resize (_N); for (unsigned int i = 0; i < _N; ++i) { this->data[i + offset] = cpuptr[i]; } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); @@ -164,17 +168,18 @@ namespace mplot::gl // ssbo_num_elements: The number of elements of type T in the SSBO. sm::range get_range() { + std::size_t sz = this->data.size(); sm::range r; r.search_init(); this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_READ_BIT)); + T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, sz * sizeof(T), GL_MAP_READ_BIT)); if (cpuptr == nullptr) { std::cout << "ssbo::get_range(): MapBufferRange error" << std::endl; mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); return r; } - for (unsigned int i = 0; i < N; ++i) { r.update (cpuptr[i]); } + for (unsigned int i = 0; i < sz; ++i) { r.update (cpuptr[i]); } this->glfn->UnmapBuffer (GL_SHADER_STORAGE_BUFFER); this->glfn->BindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); diff --git a/mplot/gl/ssbo_nomx.h b/mplot/gl/ssbo_nomx.h index 56a0b62c..96b4a296 100644 --- a/mplot/gl/ssbo_nomx.h +++ b/mplot/gl/ssbo_nomx.h @@ -20,14 +20,14 @@ namespace mplot::gl // An SSBO and its data // @tparam index: The index of the buffer, used in the GLSL // @tparam T: The type of the data in the SSBO - // @tparam N: The number of elements of type T in the SSBO + // @tparam N: The max number of elements of type T in the SSBO template // Could add version template params if necessary, to select correct gl function calls struct ssbo { // The name of the buffer, generated with glGenBuffers() unsigned int name = 0; // The CPU-side data for the buffer - sm::vec data = {}; + sm::vvec data = {}; bool ready() const { return this->name != 0u; } @@ -38,6 +38,7 @@ namespace mplot::gl void init() { glGenBuffers (1, &this->name); + this->data.reserve (N * sizeof(T)); this->init_buffer_object(); } @@ -59,6 +60,7 @@ namespace mplot::gl static_assert (_N <= N); // Update local cached version of data, a portion of which we're about to write to the SSBO + this->data.resize (_N); for (unsigned int i = 0; i < _N; ++i) { this->data[i + offset] = _data[i]; } glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); @@ -76,18 +78,50 @@ namespace mplot::gl mplot::gl::Util::checkError (__FILE__, __LINE__); } + void copy_to_gpu (sm::vvec& _data, std::size_t offset = 0u) + { + if (_data.size() > N) { throw std::runtime_error ("Too big"); } + + // Update local cached version of data, a portion of which we're about to write to the SSBO + this->data.resize (_data.size()); + for (unsigned int i = 0; i < _data.size(); ++i) { this->data[i + offset] = _data[i]; } + + glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); + mplot::gl::Util::checkError (__FILE__, __LINE__); + std::cout << "Mapping buffer range at offset " << offset << " floats = " << (offset * sizeof(T)) << " bytes" << std::endl; + T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, + offset * sizeof(T), _data.size() * sizeof(T), + GL_MAP_WRITE_BIT)); + if (cpuptr == nullptr) { + std::cout << "ssbo::copy_to_gpu(sm::vec&): MapBufferRange error" << std::endl; + mplot::gl::Util::checkError (__FILE__, __LINE__); + return; + } + // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: + std::cout << "About to copy " << _data.size() << " things (prolly floats)...\n"; + for (unsigned int i = 0; i < _data.size(); ++i) { + std::cout << "Writing value " << " _data[i] = " << _data[i] << " into mapped range" << std::endl; + cpuptr[i] = _data[i]; + } + + glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); // necessary + glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); // optional + mplot::gl::Util::checkError (__FILE__, __LINE__); + } + void copy_to_gpu () { glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__); - T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_WRITE_BIT)); + std::size_t sz = this->data.size(); + T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, sz * sizeof(T), GL_MAP_WRITE_BIT)); if (cpuptr == nullptr) { std::cout << "ssbo::copy_to_gpu(): MapBufferRange error" << std::endl; mplot::gl::Util::checkError (__FILE__, __LINE__); return; } // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: - for (unsigned int i = 0; i < N; ++i) { cpuptr[i] = this->data[i]; } + for (unsigned int i = 0; i < sz; ++i) { cpuptr[i] = this->data[i]; } glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); mplot::gl::Util::checkError (__FILE__, __LINE__); @@ -109,6 +143,7 @@ namespace mplot::gl mplot::gl::Util::checkError (__FILE__, __LINE__); return; } + this->data.resize (_N); for (unsigned int i = 0; i < _N; ++i) { data[i + offset] = cpuptr[i]; } glUnmapBuffer (GL_SHADER_STORAGE_BUFFER); glBindBuffer (GL_SHADER_STORAGE_BUFFER, 0); @@ -122,11 +157,12 @@ namespace mplot::gl // ssbo_num_elements: The number of elements of type T in the SSBO. sm::range get_range() { + std::size_t sz = this->data.size(); sm::range r; r.search_init(); glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__); - T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, N * sizeof(T), GL_MAP_READ_BIT)); + T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, 0, sz * sizeof(T), GL_MAP_READ_BIT)); if (cpuptr == nullptr) { std::cout << "ssbo::get_range(): MapBufferRange error" << std::endl; mplot::gl::Util::checkError (__FILE__, __LINE__); From 50c06264a58cee46c15d66cd54896200189ccc76 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 17:22:53 +0000 Subject: [PATCH 25/46] Was a skeleton, but haven't used it --- mplot/TrailVisual.h | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 mplot/TrailVisual.h diff --git a/mplot/TrailVisual.h b/mplot/TrailVisual.h deleted file mode 100644 index 843f6077..00000000 --- a/mplot/TrailVisual.h +++ /dev/null @@ -1,4 +0,0 @@ -/* - * A 'breadcrumb trail'. A bit like ScatterVisual, but with a fixed size of data container. Copies - * of a sphere are used for the crumbs - instanced rendering. Will need some changes in core mathplot for this - */ From bf285c1378c76093ccd594f8f554a27dc30a304f Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 16 Dec 2025 17:23:04 +0000 Subject: [PATCH 26/46] Install InstancedScatterVisual --- mplot/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/mplot/CMakeLists.txt b/mplot/CMakeLists.txt index 3c149f64..830f9412 100644 --- a/mplot/CMakeLists.txt +++ b/mplot/CMakeLists.txt @@ -76,6 +76,7 @@ install( HexGridVisual.h HSVWheelVisual.h IcosaVisual.h + InstancedScatterVisual.h LengthscaleVisual.h NormalsVisual.h PointRowsMeshVisual.h From 139b2b47d48b10399660c09c3a4fdaa3f8836310 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 17 Dec 2025 10:50:27 +0000 Subject: [PATCH 27/46] Preparing for instance params in a second SSBO --- examples/breadcrumbs.cpp | 6 ++-- examples/scatter_instanced.cpp | 2 +- mplot/InstancedScatterVisual.h | 42 ---------------------------- mplot/VisualDefaultShaders.h | 3 +- mplot/VisualModelBase.h | 21 +++++++++++--- mplot/VisualModelImplMX.h | 50 +++++++++++++++++++++++++++++++--- shaders/Visual.vert.glsl | 9 +++--- 7 files changed, 72 insertions(+), 61 deletions(-) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp index 286560a5..dd919a71 100644 --- a/examples/breadcrumbs.cpp +++ b/examples/breadcrumbs.cpp @@ -72,7 +72,7 @@ int main() auto isvp = v.addVisualModel (isv); v.render(); - isvp->set_data (points, data); + isvp->set_instance_data (points, data); // colour, rotn, scale std::cout << "isvp->instance_count = " << isvp->instance_count << std::endl; while (!v.readyToFinish()) { @@ -86,7 +86,7 @@ int main() #if 1 // Update all points/data // Place data in SSBO. first call of set_data must occur after first call to v.render() - isvp->set_data (points, data); + isvp->set_instance_data (points, data); v.render(); v.waitevents (0.03); @@ -94,7 +94,7 @@ int main() #else // I haven't mastered updating a subrange of data in an SSBO // update circularly, change isvp->instance_start each time std::cout << "updating data with points[i%dsz] = " << points[i%dsz] << std::endl; - isvp->update_data (points, data, (i % dsz), (i % dsz)); + isvp->update_instance_data (points, data, (i % dsz), (i % dsz)); std::cout << "Before render, isvp->instance_start is " << isvp->instance_start << std::endl; diff --git a/examples/scatter_instanced.cpp b/examples/scatter_instanced.cpp index 6f8e7046..e206ca83 100644 --- a/examples/scatter_instanced.cpp +++ b/examples/scatter_instanced.cpp @@ -48,7 +48,7 @@ int main() v.render(); // Place data in SSBO. You have to call v.render() once first. - isvp->set_data (points, data); + isvp->set_instance_data (points, data); v.keepOpen(); diff --git a/mplot/InstancedScatterVisual.h b/mplot/InstancedScatterVisual.h index d65725eb..009fda02 100644 --- a/mplot/InstancedScatterVisual.h +++ b/mplot/InstancedScatterVisual.h @@ -45,48 +45,6 @@ namespace mplot } } - void set_data (const sm::vvec>& points, const sm::vvec& data) - { - if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } - if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } - - size_t j = 0; - this->instance_data.data.resize (mplot::VisualModelBase::floats_per_instance * points.size()); - for (size_t i = 0; i < points.size(); ++i) { - sm::vec c = points[i]; - // This to be a Member of VisualModelBase? - // this->addInstanceParams (c, data[i], scale, rotn, colour); // or something - this->instance_data.data[j++] = c[0]; - this->instance_data.data[j++] = c[1]; - this->instance_data.data[j++] = c[2]; - this->instance_data.data[j++] = data[i]; - } - this->instance_count = points.size(); - this->instance_data.copy_to_gpu(); - } - - // Update the instance_data from points and data. Update the range of data starting at - // points/data index i_s and ending at i_e. - void update_data (const sm::vvec>& points, const sm::vvec& data, - std::size_t i_s, std::size_t i_e) - { - if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } - if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } - - sm::vvec updated(mplot::VisualModelBase::floats_per_instance * (1 + i_e - i_s)); - size_t j = 0; - for (size_t i = i_s; i <= i_e; ++i) { - //std::cout << "writing points/data["< c = points[i]; - updated[j++] = c[0]; - updated[j++] = c[1]; - updated[j++] = c[2]; - updated[j++] = data[i]; - } - //std::cout << "Copy updated, size " << updated.size() << " to gpu with offset " << i_s << "\n"; - this->instance_data.copy_to_gpu (updated, i_s); - } - //! Compute spheres for a scatter plot void initializeVertices() { diff --git a/mplot/VisualDefaultShaders.h b/mplot/VisualDefaultShaders.h index 7c274391..760db847 100644 --- a/mplot/VisualDefaultShaders.h +++ b/mplot/VisualDefaultShaders.h @@ -30,8 +30,7 @@ namespace mplot "void main()\n" " {\n" " if (instance_count > 0) {\n" - " vec4 offset = { instance_data[gl_InstanceID * 4], instance_data[gl_InstanceID * 4 + 1], instance_data[gl_InstanceID * 4 + 2], 0 };\n" - " float dval = instance_data[gl_InstanceID * 4 + 3];\n" + " vec4 offset = { instance_data[gl_InstanceID * 3], instance_data[gl_InstanceID * 3 + 1], instance_data[gl_InstanceID * 3 + 2], 0 };\n" " gl_Position = (p_matrix * v_matrix * m_matrix * (position + offset));\n" " } else {\n" " gl_Position = (p_matrix * v_matrix * m_matrix * position);\n" diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index dcb4a072..b5ba9aa2 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -718,12 +718,19 @@ namespace mplot GLuint idx = 0u; GLuint idx_bb = 0u; - //! Instanced rendering mode (SSBO access) - static constexpr unsigned int instance_index = 1; // instance data stored in SSBO index 1 (must match GLSL code) - // Each instance has: location (3 floats), scale (1 float), rot (4 floats) colour/alpha (4 floats), so that's 48 bytes. - static constexpr unsigned int floats_per_instance = 4; // May become 12 + //! Instanced rendering mode (SSBO access). position data stored in SSBO index 1 (must match GLSL code) + static constexpr unsigned int instance_index = 1; + //! colour, scale, rotation stored in SSBO index 2 + static constexpr unsigned int instparam_index = 2; + //! one 3D vector is 3 floats + static constexpr unsigned int floats_per_instance = 3; + //! Instance params are: colour/alpha (4 floats), rot (4 floats), scale (1 float) + static constexpr unsigned int floats_per_instparam = 9; + //! This will control how much GPU RAM is allocated when using instanced rendering (the RAM + //! is *only* allocated if this VisualModel is 'instanced'). static constexpr unsigned int max_instances = 256 * 1024; static constexpr unsigned int max_instance_floats = floats_per_instance * max_instances; + static constexpr unsigned int max_instparam_floats = floats_per_instparam * max_instances; constexpr unsigned int max_instanced_items() { return max_instances; } //! If drawing with instancing, how many instances? unsigned int instance_count = 0; @@ -746,6 +753,12 @@ namespace mplot //! Release OpenGL context. Should call parentVis->releaseContext(). std::function*)> releaseContext; + //! Set up the instance positions and params (colour, rotn, scale) + virtual void set_instance_data (const sm::vvec>& points, const sm::vvec& data) = 0; + //! Update the instance positions and params (colour, rotn, scale) in a range + virtual void update_instance_data (const sm::vvec>& points, const sm::vvec& data, + std::size_t i_s, std::size_t i_e) = 0; + //! Setter for the parent pointer, parentVis void set_parent (mplot::VisualBase* _vis) { diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 18856989..0df475d5 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -74,15 +74,55 @@ namespace mplot void init_instance_data() { if constexpr (mplot::gl::version::has_ssbo (glver) == true) { - if (this->instance_data.ready() == false) { - GladGLContext* _glfn = this->get_glfn (this->parentVis); - this->instance_data.init (_glfn); - } + GladGLContext* _glfn = this->get_glfn (this->parentVis); + if (this->instance_data.ready() == false) { this->instance_data.init (_glfn); } + if (this->instparam_data.ready() == false) { this->instparam_data.init (_glfn); } } else { throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); } } + //! Set up the instance positions and params (colour, rotn, scale) + void set_instance_data (const sm::vvec>& points, const sm::vvec& data) + { + if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } + if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } + + size_t j = 0; + this->instance_data.data.resize (mplot::VisualModelBase::floats_per_instance * points.size()); + for (size_t i = 0; i < points.size(); ++i) { + sm::vec c = points[i]; + // This to be a Member of VisualModelBase? + // this->addInstanceParams (c, data[i], scale, rotn, colour); // or something + this->instance_data.data[j++] = c[0]; + this->instance_data.data[j++] = c[1]; + this->instance_data.data[j++] = c[2]; + } + this->instance_count = points.size(); + this->instance_data.copy_to_gpu(); + } + + // Update the instance_data from points and data. Update the range of data starting at + // points/data index i_s and ending at i_e. + void update_instance_data (const sm::vvec>& points, const sm::vvec& data, + std::size_t i_s, std::size_t i_e) + { + if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } + if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } + + sm::vvec updated(mplot::VisualModelBase::floats_per_instance * (1 + i_e - i_s)); + size_t j = 0; + for (size_t i = i_s; i <= i_e; ++i) { + //std::cout << "writing points/data["< c = points[i]; + updated[j++] = c[0]; + updated[j++] = c[1]; + updated[j++] = c[2]; + } + //std::cout << "Copy updated, size " << updated.size() << " to gpu with offset " << i_s << "\n"; + this->instance_data.copy_to_gpu (updated, i_s); + } + //! Common code to call after the vertices have been set up. GL has to have been initialised. void postVertexInit() final { @@ -429,6 +469,8 @@ namespace mplot //! Shader Storage Buffer Object for instanced rendering mplot::gl::ssbo::instance_index, float, mplot::VisualModelBase::max_instance_floats> instance_data; + mplot::gl::ssbo::instparam_index, + float, mplot::VisualModelBase::max_instparam_floats> instparam_data; protected: diff --git a/shaders/Visual.vert.glsl b/shaders/Visual.vert.glsl index 7e8dbfdb..94baaa4d 100644 --- a/shaders/Visual.vert.glsl +++ b/shaders/Visual.vert.glsl @@ -13,7 +13,8 @@ uniform mat4 v_matrix; // scene view matrix uniform mat4 p_matrix; // projection matrix // alpha - to make a model see-through uniform float alpha; -uniform int instanced = 0; +uniform int instance_count = 0; +uniform int instance_start = -1; layout(location = 0) in vec4 position; // Attrib location 0 layout(location = 1) in vec4 normalin; // Attrib location 1 @@ -30,10 +31,8 @@ out VERTEX void main (void) { - if (instanced != 0) { - vec4 offset = { instance_data[gl_InstanceID * 4], instance_data[gl_InstanceID * 4 + 1], instance_data[gl_InstanceID * 4 + 2], 1 }; - float dval = instance_data[gl_InstanceID * 4 + 3]; - // Make colour from dval? Or rather, pass colour as three floats. + if (instance_count > 0) { + vec4 offset = { instance_data[gl_InstanceID * 3], instance_data[gl_InstanceID * 3 + 1], instance_data[gl_InstanceID * 3 + 2], 0 }; gl_Position = (p_matrix * v_matrix * m_matrix * (position + offset)); } else { gl_Position = (p_matrix * v_matrix * m_matrix * position); From 064ab71230a7cfef5255359a6c39af38f996ad66 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 17 Dec 2025 13:43:51 +0000 Subject: [PATCH 28/46] Progress towards parameters --- examples/breadcrumbs.cpp | 18 +++++++----- mplot/VisualDefaultShaders.h | 24 ++++++++++++---- mplot/VisualModelBase.h | 15 ++++++++-- mplot/VisualModelImplMX.h | 54 +++++++++++++++++++++++++++--------- shaders/Visual.vert.glsl | 35 +++++++++++++++++------ 5 files changed, 109 insertions(+), 37 deletions(-) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp index dd919a71..1c37f876 100644 --- a/examples/breadcrumbs.cpp +++ b/examples/breadcrumbs.cpp @@ -54,14 +54,14 @@ int main() constexpr int dsz = 260; sm::vvec> points(dsz); - sm::vvec data(dsz); + sm::vvec psz(dsz); int i = 0; sm::vec xyz = {}; for (i = 0; i < dsz; ++i) { xyz = f (i); points[i] = xyz; - data[i] = xyz[2]; + psz[i] = xyz[2]; } auto isv = std::make_unique> (sm::vec<>{}); @@ -72,8 +72,12 @@ int main() auto isvp = v.addVisualModel (isv); v.render(); - isvp->set_instance_data (points, data); // colour, rotn, scale - std::cout << "isvp->instance_count = " << isvp->instance_count << std::endl; + isvp->set_instance_data (points); // colour, alpha, scale + std::cout << "isvp->instance_count = " << isvp->instance_count << std::endl; + + sm::vvec> col = { mplot::colour::blue, mplot::colour::springgreen }; + sm::vvec alph = { 0.5f, 1.0f }; + sm::vvec scl = { 1.0f, 1.2f }; while (!v.readyToFinish()) { @@ -81,12 +85,12 @@ int main() //std::cout << "i = " << i << ", i%dsz = " << (i%dsz) << ", i%360 = " << (i%360) << " f(i%360) = " << xyz << " -> points[" << (i%dsz)<< "]" << std::endl; points[i%dsz] = xyz; - data[i%dsz] = xyz[2]; + psz[i%dsz] = xyz[2]; #if 1 - // Update all points/data + // Update all points/psz // Place data in SSBO. first call of set_data must occur after first call to v.render() - isvp->set_instance_data (points, data); + isvp->set_instance_data (points, col, alph, scl); v.render(); v.waitevents (0.03); diff --git a/mplot/VisualDefaultShaders.h b/mplot/VisualDefaultShaders.h index 760db847..50bbfaa3 100644 --- a/mplot/VisualDefaultShaders.h +++ b/mplot/VisualDefaultShaders.h @@ -16,6 +16,7 @@ namespace mplot "uniform float alpha;\n" "uniform int instance_count = 0;\n" "uniform int instance_start = -1;\n" + "uniform int instparam_count = 0;\n" "layout(location = 0) in vec4 position;\n" "layout(location = 1) in vec4 normalin;\n" "layout(location = 2) in vec3 color;\n"; @@ -30,13 +31,25 @@ namespace mplot "void main()\n" " {\n" " if (instance_count > 0) {\n" - " vec4 offset = { instance_data[gl_InstanceID * 3], instance_data[gl_InstanceID * 3 + 1], instance_data[gl_InstanceID * 3 + 2], 0 };\n" - " gl_Position = (p_matrix * v_matrix * m_matrix * (position + offset));\n" + " vec4 iposv = { ipos[gl_InstanceID * 3], ipos[gl_InstanceID * 3 + 1], ipos[gl_InstanceID * 3 + 2], 0 };\n" + " if (instparam_count > 0) {\n" + " int idx = gl_InstanceID % instparam_count;\n" + " float s = iparam[idx * 5 + 4] * 0.5f;\n" + " vec3 p_three = vec3(position) * s;\n" + " vec4 p_scaled = vec4(p_three, 1.0);\n" + " gl_Position = (p_matrix * v_matrix * m_matrix * (p_scaled + iposv));\n" + " vertex.color = vec4(iparam[idx * 5], iparam[idx * 5 + 1], iparam[idx * 5 + 2], iparam[idx * 5 + 3]);\n" + " vertex.fragpos = vec3(m_matrix * p_scaled);\n" + " } else {\n" + " gl_Position = (p_matrix * v_matrix * m_matrix * (position + iposv));\n" + " vertex.color = vec4(color, alpha);\n" + " vertex.fragpos = vec3(m_matrix * position);\n" + " }\n" " } else {\n" " gl_Position = (p_matrix * v_matrix * m_matrix * position);\n" + " vertex.color = vec4(color, alpha);\n" + " vertex.fragpos = vec3(m_matrix * position);\n" " }\n" - " vertex.color = vec4(color, alpha);\n" - " vertex.fragpos = vec3(m_matrix * position);\n" " vertex.normal = normalin;\n" "}\n"; @@ -46,7 +59,8 @@ namespace mplot shdr += mplot::gl::version::shaderpreamble (glver); shdr += defaultVtxShader_part1; if (mplot::gl::version::has_ssbo (glver)) { - shdr += "layout (std430, binding = 1) buffer InputBlock { float instance_data[]; };\n"; + shdr += "layout (std430, binding = 1) buffer InstPos { float ipos[]; };\n"; + shdr += "layout (std430, binding = 2) buffer InstParam { float iparam[]; };\n"; } shdr += defaultVtxShader_part2; return shdr; diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index b5ba9aa2..ab1dd440 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -724,8 +724,10 @@ namespace mplot static constexpr unsigned int instparam_index = 2; //! one 3D vector is 3 floats static constexpr unsigned int floats_per_instance = 3; - //! Instance params are: colour/alpha (4 floats), rot (4 floats), scale (1 float) - static constexpr unsigned int floats_per_instparam = 9; + //! Instance params are: colour/alpha (4 floats), scale (1 float) + static constexpr unsigned int floats_per_instparam = 5; + //! later: + // static constexpr unsigned int floats_per_instrotn = 4; //! This will control how much GPU RAM is allocated when using instanced rendering (the RAM //! is *only* allocated if this VisualModel is 'instanced'). static constexpr unsigned int max_instances = 256 * 1024; @@ -736,6 +738,9 @@ namespace mplot unsigned int instance_count = 0; //! Which datum in the instance buffer is the one to be drawn by the first thread? unsigned int instance_start = 0; + //! If drawing with instancing, how many params will be used (these will be cycled through + //! per-instance and there may be fewer than instance_count parameters) + unsigned int instparam_count = 0; /*! * A function that will be runtime defined to get_shaderprogs from a pointer to @@ -754,7 +759,11 @@ namespace mplot std::function*)> releaseContext; //! Set up the instance positions and params (colour, rotn, scale) - virtual void set_instance_data (const sm::vvec>& points, const sm::vvec& data) = 0; + virtual void set_instance_data (const sm::vvec>& position) = 0; + virtual void set_instance_data (const sm::vvec>& position, + const sm::vvec>& colour, + const sm::vvec& alpha, + const sm::vvec& scale) = 0; //! Update the instance positions and params (colour, rotn, scale) in a range virtual void update_instance_data (const sm::vvec>& points, const sm::vvec& data, std::size_t i_s, std::size_t i_e) = 0; diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 0df475d5..2215fbd4 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -82,24 +82,50 @@ namespace mplot } } - //! Set up the instance positions and params (colour, rotn, scale) - void set_instance_data (const sm::vvec>& points, const sm::vvec& data) + void set_instance_data (const sm::vvec>& position) final { - if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } - if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } + sm::vvec> c = { mplot::colour::crimson }; + sm::vvec a = { 1.0f }; + //sm::vvec< sm::vec > r = { sm::vec{1.0f, 0, 0, 0} }; + sm::vvec s = { 1.0f }; + this->set_instance_data (position, c, a, s); + } + + //! Set up the instance positions and params (colour, alpha, scale). Add rotation later on. + void set_instance_data (const sm::vvec>& position, + const sm::vvec>& colour, + const sm::vvec& alpha, const sm::vvec& scale) final + { + if (position.size() < 1) { throw std::runtime_error ("set_instance_data: pass some instance positions in"); } + if (position.size() > this->max_instanced_items()) { throw std::runtime_error ("set_instance_data: not enough space"); } size_t j = 0; - this->instance_data.data.resize (mplot::VisualModelBase::floats_per_instance * points.size()); - for (size_t i = 0; i < points.size(); ++i) { - sm::vec c = points[i]; - // This to be a Member of VisualModelBase? - // this->addInstanceParams (c, data[i], scale, rotn, colour); // or something - this->instance_data.data[j++] = c[0]; - this->instance_data.data[j++] = c[1]; - this->instance_data.data[j++] = c[2]; + this->instance_data.data.resize (mplot::VisualModelBase::floats_per_instance * position.size()); + for (size_t i = 0; i < position.size(); ++i) { + sm::vec p = position[i]; + this->instance_data.data[j++] = p[0]; + this->instance_data.data[j++] = p[1]; + this->instance_data.data[j++] = p[2]; } - this->instance_count = points.size(); + this->instance_count = position.size(); + + if (colour.size() != scale.size() || colour.size() != alpha.size()) { + throw std::runtime_error ("set_instance_data: params vvecs should all have same size (colour, rotn, scale)"); + } + + this->instparam_data.data.resize (mplot::VisualModelBase::floats_per_instparam * colour.size()); + j = 0; + for (size_t i = 0; i < colour.size(); ++i) { + this->instparam_data.data[j++] = colour[i][0]; + this->instparam_data.data[j++] = colour[i][1]; + this->instparam_data.data[j++] = colour[i][2]; + this->instparam_data.data[j++] = alpha[i]; + this->instparam_data.data[j++] = scale[i]; + } + this->instparam_count = colour.size(); + this->instance_data.copy_to_gpu(); + this->instparam_data.copy_to_gpu(); } // Update the instance_data from points and data. Update the range of data starting at @@ -304,9 +330,11 @@ namespace mplot // Draw the triangles GLint loc_is = _glfn->GetUniformLocation (this->get_gprog(this->parentVis), static_cast("instance_start")); GLint loc_ic = _glfn->GetUniformLocation (this->get_gprog(this->parentVis), static_cast("instance_count")); + GLint loc_ipc = _glfn->GetUniformLocation (this->get_gprog(this->parentVis), static_cast("instparam_count")); if (this->flags.test (vm_bools::instanced)) { if (loc_is != -1) { _glfn->Uniform1i (loc_is, this->instance_start); } if (loc_ic != -1) { _glfn->Uniform1i (loc_ic, this->instance_count); } + if (loc_ipc != -1) { _glfn->Uniform1i (loc_ipc, this->instparam_count); } _glfn->DrawElementsInstanced (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0, this->instance_count); } else { if (loc_is != -1) { _glfn->Uniform1i (loc_is, -1); } diff --git a/shaders/Visual.vert.glsl b/shaders/Visual.vert.glsl index 94baaa4d..30cfce51 100644 --- a/shaders/Visual.vert.glsl +++ b/shaders/Visual.vert.glsl @@ -3,7 +3,6 @@ // SSBO to be added optionally - // ProjMatrix * RotnMatrix operation can be carried out on CPU with a single matrix //uniform mat4 mvp_matrix; // Or, and this is important for lighting effects and possibly text, too, matrices can be passed separately @@ -15,12 +14,16 @@ uniform mat4 p_matrix; // projection matrix uniform float alpha; uniform int instance_count = 0; uniform int instance_start = -1; +// We may have 100 instances but only 1 set of params to apply to all instances +uniform int instparam_count = 0; layout(location = 0) in vec4 position; // Attrib location 0 layout(location = 1) in vec4 normalin; // Attrib location 1 layout(location = 2) in vec3 color; // Attrib location 2 -layout (std430, binding = 1) buffer InputBlock { float instance_data[]; }; +layout (std430, binding = 1) buffer InstPos { float ipos[]; }; +layout (std430, binding = 2) buffer InstParam { float iparam[]; }; +//later: layout (std430, binding = 3) buffer InstRotn { float irotn[]; }; out VERTEX { @@ -32,15 +35,29 @@ out VERTEX void main (void) { if (instance_count > 0) { - vec4 offset = { instance_data[gl_InstanceID * 3], instance_data[gl_InstanceID * 3 + 1], instance_data[gl_InstanceID * 3 + 2], 0 }; - gl_Position = (p_matrix * v_matrix * m_matrix * (position + offset)); + vec4 iposv = { ipos[gl_InstanceID * 3], ipos[gl_InstanceID * 3 + 1], ipos[gl_InstanceID * 3 + 2], 0 }; + if (instparam_count > 0) { + + int idx = gl_InstanceID % instparam_count; + float s = iparam[idx * 5 + 4] * 0.5f; + vec3 p_three = vec3(position) * s; + vec4 p_scaled = vec4(p_three, 1.0); // Note 1.0 in last element here, and 0.0 in last element of iposv adds to 1 + gl_Position = (p_matrix * v_matrix * m_matrix * (p_scaled + iposv)); + vertex.color = vec4(iparam[idx * 5], iparam[idx * 5 + 1], iparam[idx * 5 + 2], iparam[idx * 5 + 3]); + vertex.fragpos = vec3(m_matrix * p_scaled); // Required for correct lighting + + } else { + gl_Position = (p_matrix * v_matrix * m_matrix * (position + iposv)); + vertex.color = vec4(color, alpha); + vertex.fragpos = vec3(m_matrix * position); // Required for correct lighting + } } else { gl_Position = (p_matrix * v_matrix * m_matrix * position); + vertex.color = vec4(color, alpha); + vertex.fragpos = vec3(m_matrix * position); // Required for correct lighting } - vertex.color = vec4(color, alpha); - vertex.fragpos = vec3(m_matrix * position); - // Normals are all automatically computed, so there's no need for - // this line and the cube program doesn't bother to pass in the - // normals. Maybe required only for lighting? + + + // Required for correct lighting vertex.normal = normalin; } From ea3d43b9ed510c68175820a94cfc8dd175356180 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 17 Dec 2025 14:35:27 +0000 Subject: [PATCH 29/46] Nearly there with instanced models. Only problem to solve is how to add >1 instanced model (currently they'd use the same SSBO space) --- examples/breadcrumbs.cpp | 10 +++- examples/scatter_instanced.cpp | 12 +++-- mplot/VisualDefaultShaders.h | 21 +++++++- mplot/VisualModelBase.h | 16 +++++- mplot/VisualModelImplMX.h | 12 ++++- mplot/VisualModelImplNoMX.h | 89 ++++++++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 10 deletions(-) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp index 1c37f876..bf1e6572 100644 --- a/examples/breadcrumbs.cpp +++ b/examples/breadcrumbs.cpp @@ -67,14 +67,22 @@ int main() auto isv = std::make_unique> (sm::vec<>{}); v.bindmodel (isv); isv->radiusFixed = 0.03f; - isv->finalize(); auto isvp = v.addVisualModel (isv); + // Another one + isv = std::make_unique> (sm::vec<>{0,1,0}); + v.bindmodel (isv); + isv->radiusFixed = 0.03f; + isv->finalize(); + auto isvp2 = v.addVisualModel (isv); + v.render(); isvp->set_instance_data (points); // colour, alpha, scale std::cout << "isvp->instance_count = " << isvp->instance_count << std::endl; + isvp2->set_instance_data (points * 1.2f, mplot::colour::black, 0.7f, 1.0f); + sm::vvec> col = { mplot::colour::blue, mplot::colour::springgreen }; sm::vvec alph = { 0.5f, 1.0f }; sm::vvec scl = { 1.0f, 1.2f }; diff --git a/examples/scatter_instanced.cpp b/examples/scatter_instanced.cpp index e206ca83..7997046a 100644 --- a/examples/scatter_instanced.cpp +++ b/examples/scatter_instanced.cpp @@ -25,9 +25,12 @@ int main() v.lightingEffects(); sm::vvec> points(20*20); - sm::vvec data(20*20); + sm::vvec alpha(20*20, 1.0f); + sm::vvec scale(20*20); + sm::vvec> clrs(20*20); size_t k = 0; + mplot::ColourMap cm (mplot::ColourMapType::Plasma); for (int i = -10; i < 10; ++i) { for (int j = -10; j < 10; ++j) { float x = 0.1*i; @@ -35,7 +38,8 @@ int main() // z is some function of x, y float z = x * std::exp(-(x*x) - (y*y)); points[k] = {x, y, z}; - data[k] = z; + scale[k] = 1.0f + z; + clrs[k] = cm.convert (z); k++; } } @@ -47,8 +51,8 @@ int main() auto isvp = v.addVisualModel (isv); v.render(); - // Place data in SSBO. You have to call v.render() once first. - isvp->set_instance_data (points, data); + // We set the instance data, which adds points, colours, alpha and scale + isvp->set_instance_data (points, clrs, alpha, scale); v.keepOpen(); diff --git a/mplot/VisualDefaultShaders.h b/mplot/VisualDefaultShaders.h index 50bbfaa3..49a1dfa7 100644 --- a/mplot/VisualDefaultShaders.h +++ b/mplot/VisualDefaultShaders.h @@ -29,7 +29,22 @@ namespace mplot " vec3 fragpos;\n" "} vertex;\n" "void main()\n" - " {\n" + "{\n" + " gl_Position = (p_matrix * v_matrix * m_matrix * position);\n" + " vertex.color = vec4(color, alpha);\n" + " vertex.fragpos = vec3(m_matrix * position);\n" + " vertex.normal = normalin;\n" + "}\n"; + + const char* defaultVtxShader_part2_inst = + "out VERTEX\n" + "{\n" + " vec4 normal;\n" + " vec4 color;\n" + " vec3 fragpos;\n" + "} vertex;\n" + "void main()\n" + "{\n" " if (instance_count > 0) {\n" " vec4 iposv = { ipos[gl_InstanceID * 3], ipos[gl_InstanceID * 3 + 1], ipos[gl_InstanceID * 3 + 2], 0 };\n" " if (instparam_count > 0) {\n" @@ -61,8 +76,10 @@ namespace mplot if (mplot::gl::version::has_ssbo (glver)) { shdr += "layout (std430, binding = 1) buffer InstPos { float ipos[]; };\n"; shdr += "layout (std430, binding = 2) buffer InstParam { float iparam[]; };\n"; + shdr += defaultVtxShader_part2_inst; + } else { + shdr += defaultVtxShader_part2; } - shdr += defaultVtxShader_part2; return shdr; } diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index ab1dd440..40902964 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -758,16 +758,28 @@ namespace mplot //! Release OpenGL context. Should call parentVis->releaseContext(). std::function*)> releaseContext; - //! Set up the instance positions and params (colour, rotn, scale) + //! Set up the instance positions (with default params for colour, rotn, scale) virtual void set_instance_data (const sm::vvec>& position) = 0; + + //! Set instance positions, with a single colour, alpha and scale to apply to all points + virtual void set_instance_data (const sm::vvec>& position, + const std::array& colour, + const float alpha, + const float scale) = 0; + + //! Set instance positions, with an array containing colour, alpha and scale values to apply + //! to the instances. The size of colour, alpha and scale must match, but it may be of a + //! different size to position. virtual void set_instance_data (const sm::vvec>& position, const sm::vvec>& colour, const sm::vvec& alpha, const sm::vvec& scale) = 0; + +#if 0 //! Update the instance positions and params (colour, rotn, scale) in a range virtual void update_instance_data (const sm::vvec>& points, const sm::vvec& data, std::size_t i_s, std::size_t i_e) = 0; - +#endif //! Setter for the parent pointer, parentVis void set_parent (mplot::VisualBase* _vis) { diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 2215fbd4..a9e8c1bf 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -86,11 +86,19 @@ namespace mplot { sm::vvec> c = { mplot::colour::crimson }; sm::vvec a = { 1.0f }; - //sm::vvec< sm::vec > r = { sm::vec{1.0f, 0, 0, 0} }; sm::vvec s = { 1.0f }; this->set_instance_data (position, c, a, s); } + void set_instance_data (const sm::vvec>& position, const std::array& colour, + const float alpha = 1.0f, const float scale = 1.0f) final + { + sm::vvec> c = { colour }; + sm::vvec a = { alpha }; + sm::vvec s = { scale }; + this->set_instance_data (position, c, a, s); + } + //! Set up the instance positions and params (colour, alpha, scale). Add rotation later on. void set_instance_data (const sm::vvec>& position, const sm::vvec>& colour, @@ -128,6 +136,7 @@ namespace mplot this->instparam_data.copy_to_gpu(); } +#if 0 // Update the instance_data from points and data. Update the range of data starting at // points/data index i_s and ending at i_e. void update_instance_data (const sm::vvec>& points, const sm::vvec& data, @@ -148,6 +157,7 @@ namespace mplot //std::cout << "Copy updated, size " << updated.size() << " to gpu with offset " << i_s << "\n"; this->instance_data.copy_to_gpu (updated, i_s); } +#endif //! Common code to call after the vertices have been set up. GL has to have been initialised. void postVertexInit() final diff --git a/mplot/VisualModelImplNoMX.h b/mplot/VisualModelImplNoMX.h index c71d991a..30f2cf23 100644 --- a/mplot/VisualModelImplNoMX.h +++ b/mplot/VisualModelImplNoMX.h @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -65,6 +66,70 @@ namespace mplot model->releaseContext = &mplot::VisualBase::release_context; } + void init_instance_data() + { + if constexpr (mplot::gl::version::has_ssbo (glver) == true) { + if (this->instance_data.ready() == false) { this->instance_data.init(); } + if (this->instparam_data.ready() == false) { this->instparam_data.init(); } + } else { + throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); + } + } + + void set_instance_data (const sm::vvec>& position) final + { + sm::vvec> c = { mplot::colour::crimson }; + sm::vvec a = { 1.0f }; + sm::vvec s = { 1.0f }; + this->set_instance_data (position, c, a, s); + } + + void set_instance_data (const sm::vvec>& position, const std::array& colour, + const float alpha = 1.0f, const float scale = 1.0f) final + { + sm::vvec> c = { colour }; + sm::vvec a = { alpha }; + sm::vvec s = { scale }; + this->set_instance_data (position, c, a, s); + } + + //! Set up the instance positions and params (colour, alpha, scale). Add rotation later on. + void set_instance_data (const sm::vvec>& position, + const sm::vvec>& colour, + const sm::vvec& alpha, const sm::vvec& scale) final + { + if (position.size() < 1) { throw std::runtime_error ("set_instance_data: pass some instance positions in"); } + if (position.size() > this->max_instanced_items()) { throw std::runtime_error ("set_instance_data: not enough space"); } + + size_t j = 0; + this->instance_data.data.resize (mplot::VisualModelBase::floats_per_instance * position.size()); + for (size_t i = 0; i < position.size(); ++i) { + sm::vec p = position[i]; + this->instance_data.data[j++] = p[0]; + this->instance_data.data[j++] = p[1]; + this->instance_data.data[j++] = p[2]; + } + this->instance_count = position.size(); + + if (colour.size() != scale.size() || colour.size() != alpha.size()) { + throw std::runtime_error ("set_instance_data: params vvecs should all have same size (colour, rotn, scale)"); + } + + this->instparam_data.data.resize (mplot::VisualModelBase::floats_per_instparam * colour.size()); + j = 0; + for (size_t i = 0; i < colour.size(); ++i) { + this->instparam_data.data[j++] = colour[i][0]; + this->instparam_data.data[j++] = colour[i][1]; + this->instparam_data.data[j++] = colour[i][2]; + this->instparam_data.data[j++] = alpha[i]; + this->instparam_data.data[j++] = scale[i]; + } + this->instparam_count = colour.size(); + + this->instance_data.copy_to_gpu(); + this->instparam_data.copy_to_gpu(); + } + //! Common code to call after the vertices have been set up. GL has to have been initialised. void postVertexInit() final { @@ -98,6 +163,10 @@ namespace mplot glBindVertexArray(0); // carefully unbind and rebind mplot::gl::Util::checkError (__FILE__, __LINE__); + if (this->flags.test (vm_bools::instanced) && this->instance_data.ready() == false) { + this->init_instance_data(); + } + /* * Now do the same for the bounding box */ @@ -236,6 +305,20 @@ namespace mplot // Draw the triangles glDrawElements (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0); + GLint loc_is = glGetUniformLocation (this->get_gprog(this->parentVis), static_cast("instance_start")); + GLint loc_ic = glGetUniformLocation (this->get_gprog(this->parentVis), static_cast("instance_count")); + GLint loc_ipc = glGetUniformLocation (this->get_gprog(this->parentVis), static_cast("instparam_count")); + if (this->flags.test (vm_bools::instanced)) { + if (loc_is != -1) { glUniform1i (loc_is, this->instance_start); } + if (loc_ic != -1) { glUniform1i (loc_ic, this->instance_count); } + if (loc_ipc != -1) { glUniform1i (loc_ipc, this->instparam_count); } + glDrawElementsInstanced (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0, this->instance_count); + } else { + if (loc_is != -1) { glUniform1i (loc_is, -1); } + if (loc_ic != -1) { glUniform1i (loc_ic, -1); } + glDrawElements (GL_TRIANGLES, static_cast(this->indices.size()), GL_UNSIGNED_INT, 0); + } + // Unbind the VAO glBindVertexArray(0); @@ -384,6 +467,12 @@ namespace mplot while (ti != this->texts.end()) { (*ti)->addViewRotation (r); ti++; } } + //! Shader Storage Buffer Object for instanced rendering + mplot::gl::ssbo::instance_index, + float, mplot::VisualModelBase::max_instance_floats> instance_data; + mplot::gl::ssbo::instparam_index, + float, mplot::VisualModelBase::max_instparam_floats> instparam_data; + protected: //! A vector of pointers to text models that should be rendered. From d8465e2dc60d0f04505942f25eb43f51c49ad89e Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 17 Dec 2025 15:24:57 +0000 Subject: [PATCH 30/46] Adds a new output-shaders feature (Ctrl-shift-s) --- mplot/VisualBase.h | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/mplot/VisualBase.h b/mplot/VisualBase.h index 6bb78ec1..2c176cf8 100644 --- a/mplot/VisualBase.h +++ b/mplot/VisualBase.h @@ -1015,6 +1015,7 @@ namespace mplot << "Ctrl-u: Reduce zNear cutoff plane\n" << "Ctrl-i: Increase zNear cutoff plane\n" << "Ctrl-j: Toggle bounding boxes\n" + << "Ctrl-Shift-s: Output shaders to stdout\n" << "F1-F10: Select model index (with shift: toggle hide)\n" << "Shift-Left: Decrease opacity of selected model\n" << "Shift-Right: Increase opacity of selected model\n" @@ -1037,14 +1038,33 @@ namespace mplot } // else no-op } - if (_key == key::s && (mods & keymod::control) && action == keyaction::press) { - std::string fname (this->title); - mplot::tools::stripFileSuffix (fname); - fname += ".png"; - // Make fname 'filename safe' - mplot::tools::conditionAsFilename (fname); - this->saveImage (fname); - std::cout << "Saved image to '" << fname << "'\n"; + if (_key == key::s && (mods & (keymod::control | keymod::shift)) && action == keyaction::press) { + + if ((mods & (keymod::control | keymod::shift)) == (keymod::control | keymod::shift)) { + // Ctrl-Shift-s gives you the default shaders + std::cout << "The built-in shader programs are:\n"; + std::cout << "\nVisual.vert.glsl\n" + << "----------------\n" + << mplot::getDefaultVtxShader(glver) << std::endl; + std::cout << "\nVisual.frag.glsl\n" + << "----------------\n" + << mplot::getDefaultFragShader(glver) << std::endl; + std::cout << "\nVisText.vert.glsl\n" + << "----------------\n" + << mplot::getDefaultTextVtxShader(glver) << std::endl; + std::cout << "\nVisText.frag.glsl\n" + << "----------------\n" + << mplot::getDefaultTextFragShader(glver) << std::endl; + } else if (mods & keymod::control) { + // Ctrl-s saves a PNG + std::string fname (this->title); + mplot::tools::stripFileSuffix (fname); + fname += ".png"; + // Make fname 'filename safe' + mplot::tools::conditionAsFilename (fname); + this->saveImage (fname); + std::cout << "Saved image to '" << fname << "'\n"; + } } // Save gltf 3D file From bdd89c5ffb6dfbcfff4e4e6a3f84a5ccb33f3a20 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 17 Dec 2025 17:08:01 +0000 Subject: [PATCH 31/46] Beginning the architectural changes required to share SSBOs between instanced VisualModels --- mplot/VisualBase.h | 18 ++++++++++++ mplot/VisualModelBase.h | 20 +++---------- mplot/VisualModelImplMX.h | 61 ++++++++++++++++++--------------------- mplot/VisualOwnableMX.h | 42 +++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 49 deletions(-) diff --git a/mplot/VisualBase.h b/mplot/VisualBase.h index 2c176cf8..2973ce36 100644 --- a/mplot/VisualBase.h +++ b/mplot/VisualBase.h @@ -381,6 +381,24 @@ namespace mplot //! Strength of the diffuse light source float diffuse_intensity = 0.0f; + //! Instanced rendering mode (SSBO access). position data stored in SSBO index 1 (must match GLSL code) + static constexpr unsigned int instance_index = 1; + //! colour, scale, rotation stored in SSBO index 2 + static constexpr unsigned int instparam_index = 2; + //! one 3D vector is 3 floats + static constexpr unsigned int floats_per_instance = 3; + //! Instance params are: colour/alpha (4 floats), scale (1 float) + static constexpr unsigned int floats_per_instparam = 5; + + //! This will control how much GPU RAM is allocated when using instanced rendering (the RAM + //! is *only* allocated if at least one VisualModel is 'instanced'). + static constexpr unsigned int max_instances = 256 * 1024; + static constexpr unsigned int max_instance_floats = floats_per_instance * max_instances; + static constexpr unsigned int max_instparam_floats = floats_per_instparam * max_instances; + constexpr unsigned int max_instanced_items() { return max_instances; } + + //static void init_instance_data (mplot::VisualBase* _v) = 0; + //! Compute position and rotation of coordinate arrows in the bottom left of the screen void positionCoordArrows() { diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 40902964..8989cdf9 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -718,22 +718,6 @@ namespace mplot GLuint idx = 0u; GLuint idx_bb = 0u; - //! Instanced rendering mode (SSBO access). position data stored in SSBO index 1 (must match GLSL code) - static constexpr unsigned int instance_index = 1; - //! colour, scale, rotation stored in SSBO index 2 - static constexpr unsigned int instparam_index = 2; - //! one 3D vector is 3 floats - static constexpr unsigned int floats_per_instance = 3; - //! Instance params are: colour/alpha (4 floats), scale (1 float) - static constexpr unsigned int floats_per_instparam = 5; - //! later: - // static constexpr unsigned int floats_per_instrotn = 4; - //! This will control how much GPU RAM is allocated when using instanced rendering (the RAM - //! is *only* allocated if this VisualModel is 'instanced'). - static constexpr unsigned int max_instances = 256 * 1024; - static constexpr unsigned int max_instance_floats = floats_per_instance * max_instances; - static constexpr unsigned int max_instparam_floats = floats_per_instparam * max_instances; - constexpr unsigned int max_instanced_items() { return max_instances; } //! If drawing with instancing, how many instances? unsigned int instance_count = 0; //! Which datum in the instance buffer is the one to be drawn by the first thread? @@ -758,6 +742,10 @@ namespace mplot //! Release OpenGL context. Should call parentVis->releaseContext(). std::function*)> releaseContext; + //! Init the SSBOs for instanced rendering + std::function*)> init_instance_data; + std::function*, std::size_t)> resize_instance_data; + //! Set up the instance positions (with default params for colour, rotn, scale) virtual void set_instance_data (const sm::vvec>& position) = 0; diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index a9e8c1bf..d894dc5d 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -71,17 +71,6 @@ namespace mplot model->releaseContext = &mplot::VisualBase::release_context; } - void init_instance_data() - { - if constexpr (mplot::gl::version::has_ssbo (glver) == true) { - GladGLContext* _glfn = this->get_glfn (this->parentVis); - if (this->instance_data.ready() == false) { this->instance_data.init (_glfn); } - if (this->instparam_data.ready() == false) { this->instparam_data.init (_glfn); } - } else { - throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); - } - } - void set_instance_data (const sm::vvec>& position) final { sm::vvec> c = { mplot::colour::crimson }; @@ -105,15 +94,24 @@ namespace mplot const sm::vvec& alpha, const sm::vvec& scale) final { if (position.size() < 1) { throw std::runtime_error ("set_instance_data: pass some instance positions in"); } - if (position.size() > this->max_instanced_items()) { throw std::runtime_error ("set_instance_data: not enough space"); } + //if (position.size() > this->max_instanced_items()) { throw std::runtime_error ("set_instance_data: not enough space"); } + std::cout << "Colour, alpha, scale sizes: " + << colour.size() << ", " << alpha.size() << ", " << scale.size() << std::endl; +#if 0 size_t j = 0; - this->instance_data.data.resize (mplot::VisualModelBase::floats_per_instance * position.size()); + resize_instance_data (this->parentVis, position.size()); // callback to parent. + + //this->instance_data.data.resize (mplot::VisualBase::floats_per_instance * position.size()); + for (size_t i = 0; i < position.size(); ++i) { sm::vec p = position[i]; - this->instance_data.data[j++] = p[0]; - this->instance_data.data[j++] = p[1]; - this->instance_data.data[j++] = p[2]; + this->push_instance_data (this->parentVis, p[0]); + this->push_instance_data (this->parentVis, p[1]); + this->push_instance_data (this->parentVis, p[2]); + //this->instance_data.data[j++] = p[0]; + //this->instance_data.data[j++] = p[1]; + //this->instance_data.data[j++] = p[2]; } this->instance_count = position.size(); @@ -121,19 +119,22 @@ namespace mplot throw std::runtime_error ("set_instance_data: params vvecs should all have same size (colour, rotn, scale)"); } - this->instparam_data.data.resize (mplot::VisualModelBase::floats_per_instparam * colour.size()); - j = 0; + // resize_instance_data also does this: + //this->instparam_data.data.resize (mplot::VisualBase::floats_per_instparam * colour.size()); j = 0; for (size_t i = 0; i < colour.size(); ++i) { - this->instparam_data.data[j++] = colour[i][0]; - this->instparam_data.data[j++] = colour[i][1]; - this->instparam_data.data[j++] = colour[i][2]; - this->instparam_data.data[j++] = alpha[i]; - this->instparam_data.data[j++] = scale[i]; + + this->push_instparam_data (this->parentVis, colour[i][0]); + this->push_instparam_data (this->parentVis, colour[i][1]); + this->push_instparam_data (this->parentVis, colour[i][2]); + this->push_instparam_data (this->parentVis, alpha[i]); + this->push_instparam_data (this->parentVis, scale[i]); } this->instparam_count = colour.size(); - this->instance_data.copy_to_gpu(); - this->instparam_data.copy_to_gpu(); + // This has to occur once only + // this->instance_data.copy_to_gpu(); + // this->instparam_data.copy_to_gpu(); +#endif } #if 0 @@ -194,8 +195,8 @@ namespace mplot _glfn->BindVertexArray(0); // carefully unbind and rebind mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); - if (this->flags.test (vm_bools::instanced) && this->instance_data.ready() == false) { - this->init_instance_data(); + if (this->flags.test (vm_bools::instanced)) { + this->init_instance_data (this->parentVis); } /* @@ -504,12 +505,6 @@ namespace mplot //! Get the GladGLContext function pointer std::function*)> get_glfn; - //! Shader Storage Buffer Object for instanced rendering - mplot::gl::ssbo::instance_index, - float, mplot::VisualModelBase::max_instance_floats> instance_data; - mplot::gl::ssbo::instparam_index, - float, mplot::VisualModelBase::max_instparam_floats> instparam_data; - protected: //! A vector of pointers to text models that should be rendered. diff --git a/mplot/VisualOwnableMX.h b/mplot/VisualOwnableMX.h index 8aedc7b4..bdfafed4 100644 --- a/mplot/VisualOwnableMX.h +++ b/mplot/VisualOwnableMX.h @@ -335,6 +335,7 @@ namespace mplot model->get_gprog = &mplot::VisualBase::get_gprog; model->get_tprog = &mplot::VisualBase::get_tprog; model->get_glfn = &mplot::VisualOwnableMX::get_glfn; + model->init_instance_data = &mplot::VisualOwnableMX::init_instance_data; } //! Add a label _text to the scene at position _toffset. Font features are @@ -387,6 +388,47 @@ namespace mplot return tm->getTextGeometry(); } + static void init_instance_data (mplot::VisualBase* _v) + { + if constexpr (mplot::gl::version::has_ssbo (glver) == true) { + if (_v->instance_data.ready() == false) { _v->instance_data.init (_v->glfn); } + if (_v->instparam_data.ready() == false) { _v->instparam_data.init (_v->glfn); } + } else { + throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); + } + } +#if 0 + static void resize_instance_data (mplot::VisualBase* _v, std::size_t n_instances) + { + if (this->instance_data.ready()) { + // Or rather, n_instances + n_already_reserved + this->instance_data.data.resize (n_instances * mplot::VisualBase::floats_per_instance); + } + cur_instance_ptr = 0; + if (this->instparam_data.ready()) { + this->instparam_data.data.resize (n_instances * mplot::VisualBase::floats_per_instparam); + } + cur_instparam_ptr = 0; + } + + static void push_instance_data (mplot::VisualBase* _v, const float datum) + { + _v->instance_data.data[cur_instance_ptr++] = datum; + } + + static void push_instparam_data (mplot::VisualBase* _v, const float datum) + { + _v->instparam_data.data[cur_instparam_ptr++] = datum; + } +#endif + //! Shader Storage Buffer Object for instanced rendering + std::size_t cur_instance_ptr = 0; + mplot::gl::ssbo::instance_index, + float, mplot::VisualBase::max_instance_floats> instance_data; + std::size_t cur_instparam_ptr = 0; + mplot::gl::ssbo::instparam_index, + float, mplot::VisualBase::max_instparam_floats> instparam_data; + protected: // Initialize OpenGL shaders, set some flags (Alpha, Anti-aliasing), read in any external // state from json, and set up the coordinate arrows and any VisualTextModels that will be From 8e8620a94ab5c86d6fc4e1808caff9fed592d3c3 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 10:04:11 +0000 Subject: [PATCH 32/46] Compiles now, but I want ssbos to be program-wide objects, so they should got to VisualResources --- mplot/VisualModelImplMX.h | 2 +- mplot/VisualOwnableMX.h | 5 +++-- mplot/VisualTextModelBase.h | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index d894dc5d..3acf8990 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -196,7 +196,7 @@ namespace mplot mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); if (this->flags.test (vm_bools::instanced)) { - this->init_instance_data (this->parentVis); + if (this->init_instance_data) { this->init_instance_data (this->parentVis); } } /* diff --git a/mplot/VisualOwnableMX.h b/mplot/VisualOwnableMX.h index bdfafed4..c05d5dc5 100644 --- a/mplot/VisualOwnableMX.h +++ b/mplot/VisualOwnableMX.h @@ -390,9 +390,10 @@ namespace mplot static void init_instance_data (mplot::VisualBase* _v) { + auto __v = reinterpret_cast*>(_v); if constexpr (mplot::gl::version::has_ssbo (glver) == true) { - if (_v->instance_data.ready() == false) { _v->instance_data.init (_v->glfn); } - if (_v->instparam_data.ready() == false) { _v->instparam_data.init (_v->glfn); } + if (__v->instance_data.ready() == false) { __v->instance_data.init (__v->glfn); } + if (__v->instparam_data.ready() == false) { __v->instparam_data.init (__v->glfn); } } else { throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); } diff --git a/mplot/VisualTextModelBase.h b/mplot/VisualTextModelBase.h index da547f23..fa15524f 100644 --- a/mplot/VisualTextModelBase.h +++ b/mplot/VisualTextModelBase.h @@ -222,6 +222,9 @@ namespace mplot //! Release OpenGL context. Should call parentVis->releaseContext(). std::function*)> releaseContext; + //! Unused, but have to be present + std::function*)> init_instance_data; + //! Setter for the parent pointer, parentVis void set_parent (mplot::VisualBase* _vis) { From 8a18ce4d1b8e1d827871722d6b9494a99c8f2d96 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 10:07:46 +0000 Subject: [PATCH 33/46] Diff simplification --- mplot/VisualModelImplMX.h | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 3acf8990..f8781fc8 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -102,16 +102,11 @@ namespace mplot size_t j = 0; resize_instance_data (this->parentVis, position.size()); // callback to parent. - //this->instance_data.data.resize (mplot::VisualBase::floats_per_instance * position.size()); - for (size_t i = 0; i < position.size(); ++i) { sm::vec p = position[i]; this->push_instance_data (this->parentVis, p[0]); this->push_instance_data (this->parentVis, p[1]); this->push_instance_data (this->parentVis, p[2]); - //this->instance_data.data[j++] = p[0]; - //this->instance_data.data[j++] = p[1]; - //this->instance_data.data[j++] = p[2]; } this->instance_count = position.size(); @@ -119,8 +114,7 @@ namespace mplot throw std::runtime_error ("set_instance_data: params vvecs should all have same size (colour, rotn, scale)"); } - // resize_instance_data also does this: - //this->instparam_data.data.resize (mplot::VisualBase::floats_per_instparam * colour.size()); j = 0; + // resize_instance_data also resizes instparam_data for (size_t i = 0; i < colour.size(); ++i) { this->push_instparam_data (this->parentVis, colour[i][0]); @@ -131,7 +125,7 @@ namespace mplot } this->instparam_count = colour.size(); - // This has to occur once only + // This has to occur once only, in the right place (FIXME) // this->instance_data.copy_to_gpu(); // this->instparam_data.copy_to_gpu(); #endif From 505e08d48ceda668bec5fab5c73a29e70570a323 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 10:08:32 +0000 Subject: [PATCH 34/46] Diff simplification --- mplot/VisualModelImplMX.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index f8781fc8..489e0527 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -99,9 +99,7 @@ namespace mplot << colour.size() << ", " << alpha.size() << ", " << scale.size() << std::endl; #if 0 - size_t j = 0; resize_instance_data (this->parentVis, position.size()); // callback to parent. - for (size_t i = 0; i < position.size(); ++i) { sm::vec p = position[i]; this->push_instance_data (this->parentVis, p[0]); @@ -116,7 +114,6 @@ namespace mplot // resize_instance_data also resizes instparam_data for (size_t i = 0; i < colour.size(); ++i) { - this->push_instparam_data (this->parentVis, colour[i][0]); this->push_instparam_data (this->parentVis, colour[i][1]); this->push_instparam_data (this->parentVis, colour[i][2]); From 0df278316e5207b874b5eacfb10953f93703804d Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 10:09:32 +0000 Subject: [PATCH 35/46] Diff simplification --- mplot/VisualModelImplMX.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 489e0527..10328bcd 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -186,8 +186,8 @@ namespace mplot _glfn->BindVertexArray(0); // carefully unbind and rebind mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); - if (this->flags.test (vm_bools::instanced)) { - if (this->init_instance_data) { this->init_instance_data (this->parentVis); } + if (this->flags.test (vm_bools::instanced) && this->init_instance_data) { + this->init_instance_data (this->parentVis); } /* From 5e89f44cf97f4b0fa3d83be3255edb15b0b70e45 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 10:12:05 +0000 Subject: [PATCH 36/46] Moves some code to the 'for future use' ifdef --- mplot/VisualOwnableMX.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mplot/VisualOwnableMX.h b/mplot/VisualOwnableMX.h index c05d5dc5..538ec784 100644 --- a/mplot/VisualOwnableMX.h +++ b/mplot/VisualOwnableMX.h @@ -421,12 +421,12 @@ namespace mplot { _v->instparam_data.data[cur_instparam_ptr++] = datum; } + std::size_t cur_instance_ptr = 0; + std::size_t cur_instparam_ptr = 0; #endif //! Shader Storage Buffer Object for instanced rendering - std::size_t cur_instance_ptr = 0; mplot::gl::ssbo::instance_index, float, mplot::VisualBase::max_instance_floats> instance_data; - std::size_t cur_instparam_ptr = 0; mplot::gl::ssbo::instparam_index, float, mplot::VisualBase::max_instparam_floats> instparam_data; From ef1afcc5296a19921ca7f7aa4bc82033e3e2ee7e Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 10:59:24 +0000 Subject: [PATCH 37/46] Moves ssbos into VisualResources --- mplot/VisualBase.h | 18 ------------------ mplot/VisualModelImplMX.h | 1 - mplot/VisualOwnableMX.h | 13 ++----------- mplot/VisualResourcesBase.h | 20 ++++++++++++++++++++ mplot/VisualResourcesMX.h | 23 +++++++++++++++++++++++ 5 files changed, 45 insertions(+), 30 deletions(-) diff --git a/mplot/VisualBase.h b/mplot/VisualBase.h index 2973ce36..2c176cf8 100644 --- a/mplot/VisualBase.h +++ b/mplot/VisualBase.h @@ -381,24 +381,6 @@ namespace mplot //! Strength of the diffuse light source float diffuse_intensity = 0.0f; - //! Instanced rendering mode (SSBO access). position data stored in SSBO index 1 (must match GLSL code) - static constexpr unsigned int instance_index = 1; - //! colour, scale, rotation stored in SSBO index 2 - static constexpr unsigned int instparam_index = 2; - //! one 3D vector is 3 floats - static constexpr unsigned int floats_per_instance = 3; - //! Instance params are: colour/alpha (4 floats), scale (1 float) - static constexpr unsigned int floats_per_instparam = 5; - - //! This will control how much GPU RAM is allocated when using instanced rendering (the RAM - //! is *only* allocated if at least one VisualModel is 'instanced'). - static constexpr unsigned int max_instances = 256 * 1024; - static constexpr unsigned int max_instance_floats = floats_per_instance * max_instances; - static constexpr unsigned int max_instparam_floats = floats_per_instparam * max_instances; - constexpr unsigned int max_instanced_items() { return max_instances; } - - //static void init_instance_data (mplot::VisualBase* _v) = 0; - //! Compute position and rotation of coordinate arrows in the bottom left of the screen void positionCoordArrows() { diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 10328bcd..091dc472 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -20,7 +20,6 @@ #include #include -#include #include #include diff --git a/mplot/VisualOwnableMX.h b/mplot/VisualOwnableMX.h index 538ec784..0ab37c8f 100644 --- a/mplot/VisualOwnableMX.h +++ b/mplot/VisualOwnableMX.h @@ -391,12 +391,8 @@ namespace mplot static void init_instance_data (mplot::VisualBase* _v) { auto __v = reinterpret_cast*>(_v); - if constexpr (mplot::gl::version::has_ssbo (glver) == true) { - if (__v->instance_data.ready() == false) { __v->instance_data.init (__v->glfn); } - if (__v->instparam_data.ready() == false) { __v->instparam_data.init (__v->glfn); } - } else { - throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); - } + mplot::VisualResourcesMX::i().init_instance_data (__v->glfn); + // Also claim a start point/size within the SSBOs? } #if 0 static void resize_instance_data (mplot::VisualBase* _v, std::size_t n_instances) @@ -424,11 +420,6 @@ namespace mplot std::size_t cur_instance_ptr = 0; std::size_t cur_instparam_ptr = 0; #endif - //! Shader Storage Buffer Object for instanced rendering - mplot::gl::ssbo::instance_index, - float, mplot::VisualBase::max_instance_floats> instance_data; - mplot::gl::ssbo::instparam_index, - float, mplot::VisualBase::max_instparam_floats> instparam_data; protected: // Initialize OpenGL shaders, set some flags (Alpha, Anti-aliasing), read in any external diff --git a/mplot/VisualResourcesBase.h b/mplot/VisualResourcesBase.h index 32b986d4..964f9c3a 100644 --- a/mplot/VisualResourcesBase.h +++ b/mplot/VisualResourcesBase.h @@ -73,6 +73,26 @@ namespace mplot // Note: get/clearVisualFace functions are in derived classes virtual void clearVisualFaces (mplot::VisualBase* _vis) = 0; + + /*! + * SSBO management + */ + //! Instanced rendering mode (SSBO access). position data stored in SSBO index 1 (must match GLSL code) + static constexpr unsigned int instance_index = 1; + //! colour, scale, rotation stored in SSBO index 2 + static constexpr unsigned int instparam_index = 2; + //! one 3D vector is 3 floats + static constexpr unsigned int floats_per_instance = 3; + //! Instance params are: colour/alpha (4 floats), scale (1 float) + static constexpr unsigned int floats_per_instparam = 5; + + //! This will control how much GPU RAM is allocated when using instanced rendering + //! (Hopefully, when I'm finished, the RAM will be allocated only if at least one + //! VisualModel is marked 'instanced'). + static constexpr unsigned int max_instances = 256 * 1024; + static constexpr unsigned int max_instance_floats = floats_per_instance * max_instances; + static constexpr unsigned int max_instparam_floats = floats_per_instparam * max_instances; + //constexpr unsigned int max_instanced_items() { return max_instances; } }; } // namespace mplot diff --git a/mplot/VisualResourcesMX.h b/mplot/VisualResourcesMX.h index a8cf1d7c..4140b7ae 100644 --- a/mplot/VisualResourcesMX.h +++ b/mplot/VisualResourcesMX.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace mplot { @@ -108,6 +109,28 @@ namespace mplot } else { f++; } } } + + /*! + * We also manage some programm-wide SSBO objects for instanced rendering data in + * VisualResources. + */ + void init_instance_data (GladGLContext* glfn) + { + if constexpr (mplot::gl::version::has_ssbo (glver) == true) { + if (this->instance_data.ready() == false) { this->instance_data.init (glfn); } + if (this->instparam_data.ready() == false) { this->instparam_data.init (glfn); } + } else { + throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); + } + } + + //! Shader Storage Buffer Object for instanced rendering - this holds positions only + mplot::gl::ssbo::instance_index, + float, mplot::VisualResourcesBase::max_instance_floats> instance_data; + //! Shader Storage Buffer Object for instanced rendering - this holds colour, alpha and scale + mplot::gl::ssbo::instparam_index, + float, mplot::VisualResourcesBase::max_instparam_floats> instparam_data; + }; } // namespace mplot From f1339c6e8fa6963709b1e34952ee7e82f7dc97df Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 13:24:00 +0000 Subject: [PATCH 38/46] Am now allocating and then inserting data into SSBOs --- examples/breadcrumbs.cpp | 4 ++- mplot/VisualMX.h | 7 ++-- mplot/VisualModelBase.h | 17 +++++++--- mplot/VisualModelImplMX.h | 64 +++++++++++++++---------------------- mplot/VisualOwnableMX.h | 36 ++++++--------------- mplot/VisualResourcesBase.h | 4 ++- mplot/VisualResourcesMX.h | 24 ++++++++++++-- mplot/VisualTextModelBase.h | 8 +++-- 8 files changed, 85 insertions(+), 79 deletions(-) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp index bf1e6572..a9469b02 100644 --- a/examples/breadcrumbs.cpp +++ b/examples/breadcrumbs.cpp @@ -41,6 +41,7 @@ int main() // A normal, non instanced model. A sphere to orbit around. auto gv1 = std::make_unique> (sm::vec<>{}, 0.2f); + gv1->name = "geodesic"; v.bindmodel (gv1); gv1->iterations = 3; gv1->cm.setType (mplot::ColourMapType::Tofino); @@ -65,6 +66,7 @@ int main() } auto isv = std::make_unique> (sm::vec<>{}); + isv->name = "isv1"; v.bindmodel (isv); isv->radiusFixed = 0.03f; isv->finalize(); @@ -72,6 +74,7 @@ int main() // Another one isv = std::make_unique> (sm::vec<>{0,1,0}); + isv->name = "isv2"; v.bindmodel (isv); isv->radiusFixed = 0.03f; isv->finalize(); @@ -79,7 +82,6 @@ int main() v.render(); isvp->set_instance_data (points); // colour, alpha, scale - std::cout << "isvp->instance_count = " << isvp->instance_count << std::endl; isvp2->set_instance_data (points * 1.2f, mplot::colour::black, 0.7f, 1.0f); diff --git a/mplot/VisualMX.h b/mplot/VisualMX.h index bd9b9da9..1b941970 100644 --- a/mplot/VisualMX.h +++ b/mplot/VisualMX.h @@ -165,17 +165,18 @@ namespace mplot void bindmodel (std::unique_ptr& model) { mplot::VisualBase::template bindmodel (model); // base class binds - model->setContext = &mplot::VisualBase::set_context; - model->releaseContext = &mplot::VisualBase::release_context; - model->get_glfn = &mplot::VisualOwnableMX::get_glfn; + this->bindextra (model); } + // The GL-dependent binds template void bindextra (std::unique_ptr& model) { model->setContext = &mplot::VisualBase::set_context; model->releaseContext = &mplot::VisualBase::release_context; model->get_glfn = &mplot::VisualOwnableMX::get_glfn; + model->init_instance_data = &mplot::VisualOwnableMX::init_instance_data; + model->insert_instance_data = &mplot::VisualOwnableMX::insert_instance_data; } /* diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 8989cdf9..2e5cb40e 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -718,10 +718,19 @@ namespace mplot GLuint idx = 0u; GLuint idx_bb = 0u; - //! If drawing with instancing, how many instances? - unsigned int instance_count = 0; + /*! + * This is the upper limit for instance_count. We reserve max_isntances of space in the SSBO. + * + * Max number of instances in a model. Each *model* has to reserve space in the SSBOs which + * are managed by VisualResources. VisualResources::max_instances must be larger than this. + */ + unsigned int max_instances = 16 * 1024; + //! The offset in the SSBO for the instances for this VisualModel + unsigned int instance_offset = 0; //! Which datum in the instance buffer is the one to be drawn by the first thread? unsigned int instance_start = 0; + //! If drawing with instancing, how many instances? + unsigned int instance_count = 0; //! If drawing with instancing, how many params will be used (these will be cycled through //! per-instance and there may be fewer than instance_count parameters) unsigned int instparam_count = 0; @@ -743,8 +752,8 @@ namespace mplot std::function*)> releaseContext; //! Init the SSBOs for instanced rendering - std::function*)> init_instance_data; - std::function*, std::size_t)> resize_instance_data; + std::function*, const unsigned int)> init_instance_data; + std::function&)> insert_instance_data; //! Set up the instance positions (with default params for colour, rotn, scale) virtual void set_instance_data (const sm::vvec>& position) = 0; diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 091dc472..46b034bb 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -94,30 +94,34 @@ namespace mplot { if (position.size() < 1) { throw std::runtime_error ("set_instance_data: pass some instance positions in"); } //if (position.size() > this->max_instanced_items()) { throw std::runtime_error ("set_instance_data: not enough space"); } - std::cout << "Colour, alpha, scale sizes: " - << colour.size() << ", " << alpha.size() << ", " << scale.size() << std::endl; -#if 0 - resize_instance_data (this->parentVis, position.size()); // callback to parent. - for (size_t i = 0; i < position.size(); ++i) { - sm::vec p = position[i]; - this->push_instance_data (this->parentVis, p[0]); - this->push_instance_data (this->parentVis, p[1]); - this->push_instance_data (this->parentVis, p[2]); + if (position.size() > this->max_instances) { + throw std::runtime_error ("set_instance_data: Haven't reserved enough space for that"); + } + //if (!this->insert_instance_data) { + // throw std::runtime_error ("set_instance_data: Haven't connected insert_instance_data"); + //} + + if (this->insert_instance_data) { + for (size_t i = 0; i < position.size(); ++i) { + // Get access to the SSBO in VisualResources and add the 3 floats in position[i] at + // the location defined by this->instance_offset + i + this->insert_instance_data (this->instance_offset + i, position[i]); + } + } else { + std::cout << "No insert instance data?" << std::endl; } this->instance_count = position.size(); if (colour.size() != scale.size() || colour.size() != alpha.size()) { throw std::runtime_error ("set_instance_data: params vvecs should all have same size (colour, rotn, scale)"); } - +#if 0 // resize_instance_data also resizes instparam_data for (size_t i = 0; i < colour.size(); ++i) { - this->push_instparam_data (this->parentVis, colour[i][0]); - this->push_instparam_data (this->parentVis, colour[i][1]); - this->push_instparam_data (this->parentVis, colour[i][2]); - this->push_instparam_data (this->parentVis, alpha[i]); - this->push_instparam_data (this->parentVis, scale[i]); + this->push_instparam_data (this->parentVis, colour, alpha, scale); + //this->push_instparam_data (this->parentVis, alpha[i]); + //this->push_instparam_data (this->parentVis, scale[i]); } this->instparam_count = colour.size(); @@ -127,29 +131,6 @@ namespace mplot #endif } -#if 0 - // Update the instance_data from points and data. Update the range of data starting at - // points/data index i_s and ending at i_e. - void update_instance_data (const sm::vvec>& points, const sm::vvec& data, - std::size_t i_s, std::size_t i_e) - { - if (points.size() != data.size()) { throw std::runtime_error ("points and data must have same size"); } - if (points.size() > this->max_instanced_items()) { throw std::runtime_error ("Not enough space"); } - - sm::vvec updated(mplot::VisualModelBase::floats_per_instance * (1 + i_e - i_s)); - size_t j = 0; - for (size_t i = i_s; i <= i_e; ++i) { - //std::cout << "writing points/data["< c = points[i]; - updated[j++] = c[0]; - updated[j++] = c[1]; - updated[j++] = c[2]; - } - //std::cout << "Copy updated, size " << updated.size() << " to gpu with offset " << i_s << "\n"; - this->instance_data.copy_to_gpu (updated, i_s); - } -#endif - //! Common code to call after the vertices have been set up. GL has to have been initialised. void postVertexInit() final { @@ -186,7 +167,12 @@ namespace mplot mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn); if (this->flags.test (vm_bools::instanced) && this->init_instance_data) { - this->init_instance_data (this->parentVis); + // Here, we cause the SSBOs to be intialized if they haven't already, and we reserve + // some space in the SSBOs for *this model* + this->instance_offset = this->init_instance_data (this->parentVis, this->max_instances); + if (this->instance_offset == std::numeric_limits::max()) { + throw std::runtime_error ("Failed to reserve space in SSBO"); + } } /* diff --git a/mplot/VisualOwnableMX.h b/mplot/VisualOwnableMX.h index 0ab37c8f..d2cd3e92 100644 --- a/mplot/VisualOwnableMX.h +++ b/mplot/VisualOwnableMX.h @@ -327,6 +327,7 @@ namespace mplot } } + // Note: We have to have both VisualOwnableMX::bindmodel AND VisualMX::bindmodel (which calls VisualBase::bindmodel) template void bindmodel (std::unique_ptr& model) { @@ -336,6 +337,7 @@ namespace mplot model->get_tprog = &mplot::VisualBase::get_tprog; model->get_glfn = &mplot::VisualOwnableMX::get_glfn; model->init_instance_data = &mplot::VisualOwnableMX::init_instance_data; + model->insert_instance_data = &mplot::VisualOwnableMX::insert_instance_data; } //! Add a label _text to the scene at position _toffset. Font features are @@ -388,38 +390,17 @@ namespace mplot return tm->getTextGeometry(); } - static void init_instance_data (mplot::VisualBase* _v) + static unsigned int init_instance_data (mplot::VisualBase* _v, const unsigned int n_to_reserve) { auto __v = reinterpret_cast*>(_v); - mplot::VisualResourcesMX::i().init_instance_data (__v->glfn); - // Also claim a start point/size within the SSBOs? - } -#if 0 - static void resize_instance_data (mplot::VisualBase* _v, std::size_t n_instances) - { - if (this->instance_data.ready()) { - // Or rather, n_instances + n_already_reserved - this->instance_data.data.resize (n_instances * mplot::VisualBase::floats_per_instance); - } - cur_instance_ptr = 0; - if (this->instparam_data.ready()) { - this->instparam_data.data.resize (n_instances * mplot::VisualBase::floats_per_instparam); - } - cur_instparam_ptr = 0; - } - - static void push_instance_data (mplot::VisualBase* _v, const float datum) - { - _v->instance_data.data[cur_instance_ptr++] = datum; + unsigned int reservation = mplot::VisualResourcesMX::i().init_instance_ssbo (__v->glfn, n_to_reserve); + return reservation; } - static void push_instparam_data (mplot::VisualBase* _v, const float datum) + static void insert_instance_data (const unsigned int instance_idx, const sm::vec& coord) { - _v->instparam_data.data[cur_instparam_ptr++] = datum; + mplot::VisualResourcesMX::i().insert_instance_data (instance_idx, coord); } - std::size_t cur_instance_ptr = 0; - std::size_t cur_instparam_ptr = 0; -#endif protected: // Initialize OpenGL shaders, set some flags (Alpha, Anti-aliasing), read in any external @@ -470,6 +451,7 @@ namespace mplot // Use coordArrowsOffset to set the location of the CoordArrows *scene* this->coordArrows = std::make_unique>(); + this->coordArrows->name = "Scene axes"; // For CoordArrows, because we don't add via Visual::addVisualModel(), we // have to set the get_shaderprogs function here: this->bindmodel (this->coordArrows); @@ -480,6 +462,7 @@ namespace mplot // Create 'user frame of reference object' this->userFrame = std::make_unique>(); + this->userFrame->name = "User frame of ref object"; this->bindmodel (this->userFrame); this->userFrame->init (sm::vec{}, sm::vec{0.0f, 0.0f, -100.0f}, sm::vec{0.1f, 0.1f, 1.0f}, 0.05f, @@ -494,6 +477,7 @@ namespace mplot // Set up the title, which may or may not be rendered mplot::TextFeatures title_tf(0.035f, 64); this->textModel = std::make_unique> (title_tf); + this->textModel->name = "Title text"; this->bindmodel (this->textModel); this->textModel->setSceneTranslation ({0.0f, 0.0f, 0.0f}); this->textModel->setupText (this->title); diff --git a/mplot/VisualResourcesBase.h b/mplot/VisualResourcesBase.h index 964f9c3a..678d391f 100644 --- a/mplot/VisualResourcesBase.h +++ b/mplot/VisualResourcesBase.h @@ -92,7 +92,9 @@ namespace mplot static constexpr unsigned int max_instances = 256 * 1024; static constexpr unsigned int max_instance_floats = floats_per_instance * max_instances; static constexpr unsigned int max_instparam_floats = floats_per_instparam * max_instances; - //constexpr unsigned int max_instanced_items() { return max_instances; } + + // The Current location from which space in the instance SSBOs should be allocated + unsigned int instance_top = 0; }; } // namespace mplot diff --git a/mplot/VisualResourcesMX.h b/mplot/VisualResourcesMX.h index 4140b7ae..b60692ce 100644 --- a/mplot/VisualResourcesMX.h +++ b/mplot/VisualResourcesMX.h @@ -111,17 +111,35 @@ namespace mplot } /*! - * We also manage some programm-wide SSBO objects for instanced rendering data in - * VisualResources. + * We also manage some programm-wide SSBO objects for instanced rendering + * VisualResourcesdata in . Reserve n_to_reserve instances of data in the SSBOs. Return the + * start offset into the buffers in terms of number of instances */ - void init_instance_data (GladGLContext* glfn) + unsigned int init_instance_ssbo (GladGLContext* glfn, const unsigned int n_to_reserve) { + unsigned int reservation = std::numeric_limits::max(); if constexpr (mplot::gl::version::has_ssbo (glver) == true) { if (this->instance_data.ready() == false) { this->instance_data.init (glfn); } if (this->instparam_data.ready() == false) { this->instparam_data.init (glfn); } + if (n_to_reserve + this->instance_top <= this->max_instances) { + reservation = this->instance_top; + this->instance_top += n_to_reserve; + } } else { throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); } + return reservation; + } + + void insert_instance_data (const unsigned int instance_idx, const sm::vec& coord) + { + if (instance_idx >= this->max_instances) { + throw std::runtime_error ("insert_instance_data: bad instance_idx"); + } + unsigned int cur_fidx = instance_idx * this->floats_per_instance; + this->instance_data.data[cur_fidx++] = coord[0]; + this->instance_data.data[cur_fidx++] = coord[1]; + this->instance_data.data[cur_fidx++] = coord[2]; } //! Shader Storage Buffer Object for instanced rendering - this holds positions only diff --git a/mplot/VisualTextModelBase.h b/mplot/VisualTextModelBase.h index fa15524f..6456288c 100644 --- a/mplot/VisualTextModelBase.h +++ b/mplot/VisualTextModelBase.h @@ -201,6 +201,9 @@ namespace mplot virtual void postVertexInit() = 0; public: + // A VisualTextModel may be given a name + std::string name = "VisualTextModel"; + //! The colour of the text std::array clr_text = {0.0f, 0.0f, 0.0f}; //! Line spacing, in multiples of the height of an 'h' @@ -222,8 +225,9 @@ namespace mplot //! Release OpenGL context. Should call parentVis->releaseContext(). std::function*)> releaseContext; - //! Unused, but have to be present - std::function*)> init_instance_data; + //! SSBOs are unused in VisualTextModels, but these functions have to be present + std::function*, const unsigned int)> init_instance_data; + std::function&)> insert_instance_data; //! Setter for the parent pointer, parentVis void set_parent (mplot::VisualBase* _vis) From e8cad3364999000840ebb4fa93d7b11218ed6b5b Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 13:37:35 +0000 Subject: [PATCH 39/46] INserts instparam data --- mplot/VisualMX.h | 1 + mplot/VisualModelBase.h | 1 + mplot/VisualModelImplMX.h | 44 +++++++++++++++++-------------------- mplot/VisualOwnableMX.h | 7 ++++++ mplot/VisualResourcesMX.h | 14 ++++++++++++ mplot/VisualTextModelBase.h | 1 + 6 files changed, 44 insertions(+), 24 deletions(-) diff --git a/mplot/VisualMX.h b/mplot/VisualMX.h index 1b941970..9d3cc2b5 100644 --- a/mplot/VisualMX.h +++ b/mplot/VisualMX.h @@ -177,6 +177,7 @@ namespace mplot model->get_glfn = &mplot::VisualOwnableMX::get_glfn; model->init_instance_data = &mplot::VisualOwnableMX::init_instance_data; model->insert_instance_data = &mplot::VisualOwnableMX::insert_instance_data; + model->insert_instparam_data = &mplot::VisualOwnableMX::insert_instparam_data; } /* diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 2e5cb40e..6c395cf6 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -754,6 +754,7 @@ namespace mplot //! Init the SSBOs for instanced rendering std::function*, const unsigned int)> init_instance_data; std::function&)> insert_instance_data; + std::function&, const float, const float)> insert_instparam_data; //! Set up the instance positions (with default params for colour, rotn, scale) virtual void set_instance_data (const sm::vvec>& position) = 0; diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index 46b034bb..a46aaab3 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -92,43 +92,39 @@ namespace mplot const sm::vvec>& colour, const sm::vvec& alpha, const sm::vvec& scale) final { - if (position.size() < 1) { throw std::runtime_error ("set_instance_data: pass some instance positions in"); } - //if (position.size() > this->max_instanced_items()) { throw std::runtime_error ("set_instance_data: not enough space"); } - + if (position.size() < 1) { + throw std::runtime_error ("set_instance_data: pass some instance positions in"); + } if (position.size() > this->max_instances) { throw std::runtime_error ("set_instance_data: Haven't reserved enough space for that"); } - //if (!this->insert_instance_data) { - // throw std::runtime_error ("set_instance_data: Haven't connected insert_instance_data"); - //} - - if (this->insert_instance_data) { - for (size_t i = 0; i < position.size(); ++i) { - // Get access to the SSBO in VisualResources and add the 3 floats in position[i] at - // the location defined by this->instance_offset + i - this->insert_instance_data (this->instance_offset + i, position[i]); - } - } else { - std::cout << "No insert instance data?" << std::endl; + if (!this->insert_instance_data) { + throw std::runtime_error ("set_instance_data: Function insert_instance_data is not bound"); + } + if (!this->insert_instparam_data) { + throw std::runtime_error ("set_instance_data: Function insert_instparam_data is not bound"); } - this->instance_count = position.size(); - if (colour.size() != scale.size() || colour.size() != alpha.size()) { throw std::runtime_error ("set_instance_data: params vvecs should all have same size (colour, rotn, scale)"); } -#if 0 - // resize_instance_data also resizes instparam_data + + for (size_t i = 0; i < position.size(); ++i) { + // Get access to the SSBO in VisualResources and add the 3 floats in position[i] at + // the location defined by this->instance_offset + i + this->insert_instance_data (this->instance_offset + i, position[i]); + } + + this->instance_count = position.size(); + + for (size_t i = 0; i < colour.size(); ++i) { - this->push_instparam_data (this->parentVis, colour, alpha, scale); - //this->push_instparam_data (this->parentVis, alpha[i]); - //this->push_instparam_data (this->parentVis, scale[i]); + this->insert_instparam_data (this->instance_offset + i, colour[i], alpha[i], scale[i]); } this->instparam_count = colour.size(); - // This has to occur once only, in the right place (FIXME) + // This has to occur once only, in the right place (FIXME). (In Visual::render) // this->instance_data.copy_to_gpu(); // this->instparam_data.copy_to_gpu(); -#endif } //! Common code to call after the vertices have been set up. GL has to have been initialised. diff --git a/mplot/VisualOwnableMX.h b/mplot/VisualOwnableMX.h index d2cd3e92..072c37c6 100644 --- a/mplot/VisualOwnableMX.h +++ b/mplot/VisualOwnableMX.h @@ -338,6 +338,7 @@ namespace mplot model->get_glfn = &mplot::VisualOwnableMX::get_glfn; model->init_instance_data = &mplot::VisualOwnableMX::init_instance_data; model->insert_instance_data = &mplot::VisualOwnableMX::insert_instance_data; + model->insert_instparam_data = &mplot::VisualOwnableMX::insert_instparam_data; } //! Add a label _text to the scene at position _toffset. Font features are @@ -402,6 +403,12 @@ namespace mplot mplot::VisualResourcesMX::i().insert_instance_data (instance_idx, coord); } + static void insert_instparam_data (const unsigned int instance_idx, + const std::array& colour, const float& alpha, const float& scale) + { + mplot::VisualResourcesMX::i().insert_instparam_data (instance_idx, colour, alpha, scale); + } + protected: // Initialize OpenGL shaders, set some flags (Alpha, Anti-aliasing), read in any external // state from json, and set up the coordinate arrows and any VisualTextModels that will be diff --git a/mplot/VisualResourcesMX.h b/mplot/VisualResourcesMX.h index b60692ce..35ff6940 100644 --- a/mplot/VisualResourcesMX.h +++ b/mplot/VisualResourcesMX.h @@ -142,6 +142,20 @@ namespace mplot this->instance_data.data[cur_fidx++] = coord[2]; } + void insert_instparam_data (const unsigned int instance_idx, + const std::array& colour, const float& alpha, const float& scale) + { + if (instance_idx >= this->max_instances) { + throw std::runtime_error ("insert_instparam_data: bad instance_idx"); + } + unsigned int cur_fidx = instance_idx * this->floats_per_instparam; + this->instparam_data.data[cur_fidx++] = colour[0]; + this->instparam_data.data[cur_fidx++] = colour[1]; + this->instparam_data.data[cur_fidx++] = colour[2]; + this->instparam_data.data[cur_fidx++] = alpha; + this->instparam_data.data[cur_fidx++] = scale; + } + //! Shader Storage Buffer Object for instanced rendering - this holds positions only mplot::gl::ssbo::instance_index, float, mplot::VisualResourcesBase::max_instance_floats> instance_data; diff --git a/mplot/VisualTextModelBase.h b/mplot/VisualTextModelBase.h index 6456288c..ab01ea54 100644 --- a/mplot/VisualTextModelBase.h +++ b/mplot/VisualTextModelBase.h @@ -228,6 +228,7 @@ namespace mplot //! SSBOs are unused in VisualTextModels, but these functions have to be present std::function*, const unsigned int)> init_instance_data; std::function&)> insert_instance_data; + std::function&, const float, const float)> insert_instparam_data; //! Setter for the parent pointer, parentVis void set_parent (mplot::VisualBase* _vis) From b53dc412a4f8a49dbdd4ca26de7a26e3fb79ae07 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 14:23:02 +0000 Subject: [PATCH 40/46] More or less reinstates the working instanced model --- mplot/VisualBase.h | 9 ++++++++- mplot/VisualModelImplMX.h | 6 ------ mplot/VisualOwnableMX.h | 7 +++++++ mplot/VisualResourcesMX.h | 8 ++++++++ mplot/gl/ssbo_mx.h | 6 ++++++ 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/mplot/VisualBase.h b/mplot/VisualBase.h index 2c176cf8..5be3f983 100644 --- a/mplot/VisualBase.h +++ b/mplot/VisualBase.h @@ -61,7 +61,9 @@ namespace mplot //! When true, cursor movements induce translation of scene translateMode, //! We are scrolling (and so we will need to zero scenetrans_delta after enacting the change) - scrolling + scrolling, + //! True means that at least one of our VisualModels is an instanced rendering model + haveInstanced }; //! Boolean options - similar to state, but more likely to be modified by client code @@ -248,6 +250,7 @@ namespace mplot unsigned int addVisualModelId (std::unique_ptr& model) { std::unique_ptr> vmp = std::move(model); + if (vmp->instanced()) { this->state.set (visual_state::haveInstanced, true); } this->vm.push_back (std::move(vmp)); unsigned int rtn = (this->vm.size()-1); return rtn; @@ -260,6 +263,7 @@ namespace mplot T* addVisualModel (std::unique_ptr& model) { std::unique_ptr> vmp = std::move(model); + if (vmp->instanced()) { this->state.set (visual_state::haveInstanced, true); } this->vm.push_back (std::move(vmp)); return static_cast(this->vm.back().get()); } @@ -461,6 +465,9 @@ namespace mplot //! Returns true if we are in the paused state bool paused() const { return this->state.test (visual_state::paused); } + //! True if one of our added VisualModels is an instanced model + bool haveInstanced() const { return this->state.test (visual_state::haveInstanced); } + /* * User-settable projection values for the near clipping distance, the far clipping distance * and the field of view of the camera. diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index a46aaab3..d51c23c9 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -113,18 +113,12 @@ namespace mplot // the location defined by this->instance_offset + i this->insert_instance_data (this->instance_offset + i, position[i]); } - this->instance_count = position.size(); - for (size_t i = 0; i < colour.size(); ++i) { this->insert_instparam_data (this->instance_offset + i, colour[i], alpha[i], scale[i]); } this->instparam_count = colour.size(); - - // This has to occur once only, in the right place (FIXME). (In Visual::render) - // this->instance_data.copy_to_gpu(); - // this->instparam_data.copy_to_gpu(); } //! Common code to call after the vertices have been set up. GL has to have been initialised. diff --git a/mplot/VisualOwnableMX.h b/mplot/VisualOwnableMX.h index 072c37c6..d64c59c3 100644 --- a/mplot/VisualOwnableMX.h +++ b/mplot/VisualOwnableMX.h @@ -259,6 +259,8 @@ namespace mplot this->userFrame->render(); } + if (this->haveInstanced()) { this->copy_instance_data_to_gpu(); } + auto vmi = this->vm.begin(); while (vmi != this->vm.end()) { if ((*vmi)->twodimensional() == true) { @@ -409,6 +411,11 @@ namespace mplot mplot::VisualResourcesMX::i().insert_instparam_data (instance_idx, colour, alpha, scale); } + static void copy_instance_data_to_gpu() + { + mplot::VisualResourcesMX::i().copy_instance_ssbo_to_gpu(); + } + protected: // Initialize OpenGL shaders, set some flags (Alpha, Anti-aliasing), read in any external // state from json, and set up the coordinate arrows and any VisualTextModels that will be diff --git a/mplot/VisualResourcesMX.h b/mplot/VisualResourcesMX.h index 35ff6940..8fd67d34 100644 --- a/mplot/VisualResourcesMX.h +++ b/mplot/VisualResourcesMX.h @@ -124,6 +124,8 @@ namespace mplot if (n_to_reserve + this->instance_top <= this->max_instances) { reservation = this->instance_top; this->instance_top += n_to_reserve; + this->instance_data.resize (this->instance_top * this->floats_per_instance); + this->instparam_data.resize (this->instance_top * this->floats_per_instparam); } } else { throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); @@ -156,6 +158,12 @@ namespace mplot this->instparam_data.data[cur_fidx++] = scale; } + void copy_instance_ssbo_to_gpu() + { + if (this->instance_data.ready()) { this->instance_data.copy_to_gpu(); } + if (this->instparam_data.ready()) { this->instparam_data.copy_to_gpu(); } + } + //! Shader Storage Buffer Object for instanced rendering - this holds positions only mplot::gl::ssbo::instance_index, float, mplot::VisualResourcesBase::max_instance_floats> instance_data; diff --git a/mplot/gl/ssbo_mx.h b/mplot/gl/ssbo_mx.h index d45503dc..7544723a 100644 --- a/mplot/gl/ssbo_mx.h +++ b/mplot/gl/ssbo_mx.h @@ -33,6 +33,12 @@ namespace mplot::gl bool ready() const { return this->name != 0u; } + void resize (std::size_t sz) + { + if (sz > N) { throw std::runtime_error ("ssbo: can't resize to this size"); } + this->data.resize (sz); + } + ssbo() {} ~ssbo() {} From 6b60d739d96235db122ebddd7425dea25fdc1954 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 14:40:53 +0000 Subject: [PATCH 41/46] Copy to GPU only when required --- mplot/VisualBase.h | 11 ++++++++++- mplot/VisualModelBase.h | 1 + mplot/VisualModelImplMX.h | 2 ++ mplot/VisualOwnableMX.h | 6 +++++- mplot/VisualTextModelBase.h | 1 + 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/mplot/VisualBase.h b/mplot/VisualBase.h index 5be3f983..5a73e54d 100644 --- a/mplot/VisualBase.h +++ b/mplot/VisualBase.h @@ -63,7 +63,9 @@ namespace mplot //! We are scrolling (and so we will need to zero scenetrans_delta after enacting the change) scrolling, //! True means that at least one of our VisualModels is an instanced rendering model - haveInstanced + haveInstanced, + //! When true, the instanced data SSBO needs to be copied to the GPU + instancedNeedsUpdate }; //! Boolean options - similar to state, but more likely to be modified by client code @@ -240,6 +242,7 @@ namespace mplot model->get_shaderprogs = &mplot::VisualBase::get_shaderprogs; model->get_gprog = &mplot::VisualBase::get_gprog; model->get_tprog = &mplot::VisualBase::get_tprog; + model->instanced_needs_update = &mplot::VisualBase::instanced_needs_update; } /*! @@ -376,6 +379,8 @@ namespace mplot static GLuint get_gprog (mplot::VisualBase* _v) { return _v->shaders.gprog; }; static GLuint get_tprog (mplot::VisualBase* _v) { return _v->shaders.tprog; }; + static void instanced_needs_update (mplot::VisualBase* _v) { _v->instancedNeedsUpdate (true); } + //! The colour of ambient and diffuse light sources sm::vec light_colour = { 1.0f, 1.0f, 1.0f }; //! Strength of the ambient light @@ -468,6 +473,10 @@ namespace mplot //! True if one of our added VisualModels is an instanced model bool haveInstanced() const { return this->state.test (visual_state::haveInstanced); } + //! Does our instanced data need to be pushed over to the GPU during render()? + bool instancedNeedsUpdate() const { return this->state.test (visual_state::instancedNeedsUpdate); } + void instancedNeedsUpdate (const bool val) { this->state.set (visual_state::instancedNeedsUpdate, val); } + /* * User-settable projection values for the near clipping distance, the far clipping distance * and the field of view of the camera. diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 6c395cf6..fffaf3f1 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -755,6 +755,7 @@ namespace mplot std::function*, const unsigned int)> init_instance_data; std::function&)> insert_instance_data; std::function&, const float, const float)> insert_instparam_data; + std::function*)> instanced_needs_update; //! Set up the instance positions (with default params for colour, rotn, scale) virtual void set_instance_data (const sm::vvec>& position) = 0; diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index d51c23c9..e2c6df7e 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -119,6 +119,8 @@ namespace mplot this->insert_instparam_data (this->instance_offset + i, colour[i], alpha[i], scale[i]); } this->instparam_count = colour.size(); + + this->instanced_needs_update (this->parentVis); } //! Common code to call after the vertices have been set up. GL has to have been initialised. diff --git a/mplot/VisualOwnableMX.h b/mplot/VisualOwnableMX.h index d64c59c3..cb335fe7 100644 --- a/mplot/VisualOwnableMX.h +++ b/mplot/VisualOwnableMX.h @@ -259,7 +259,10 @@ namespace mplot this->userFrame->render(); } - if (this->haveInstanced()) { this->copy_instance_data_to_gpu(); } + if (this->haveInstanced() && this->instancedNeedsUpdate()) { + this->copy_instance_data_to_gpu(); + this->instancedNeedsUpdate (false); + } auto vmi = this->vm.begin(); while (vmi != this->vm.end()) { @@ -337,6 +340,7 @@ namespace mplot model->get_shaderprogs = &mplot::VisualBase::get_shaderprogs; model->get_gprog = &mplot::VisualBase::get_gprog; model->get_tprog = &mplot::VisualBase::get_tprog; + model->instanced_needs_update = &mplot::VisualBase::instanced_needs_update; model->get_glfn = &mplot::VisualOwnableMX::get_glfn; model->init_instance_data = &mplot::VisualOwnableMX::init_instance_data; model->insert_instance_data = &mplot::VisualOwnableMX::insert_instance_data; diff --git a/mplot/VisualTextModelBase.h b/mplot/VisualTextModelBase.h index ab01ea54..92b734ad 100644 --- a/mplot/VisualTextModelBase.h +++ b/mplot/VisualTextModelBase.h @@ -229,6 +229,7 @@ namespace mplot std::function*, const unsigned int)> init_instance_data; std::function&)> insert_instance_data; std::function&, const float, const float)> insert_instparam_data; + std::function*)> instanced_needs_update; //! Setter for the parent pointer, parentVis void set_parent (mplot::VisualBase* _vis) From 74c580cb21e0b0d8a58d31e0cc296fcd18b4cf32 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 15:09:10 +0000 Subject: [PATCH 42/46] Updates the shader to use the now-separate SSBO data --- mplot/VisualDefaultShaders.h | 8 +++++--- mplot/VisualModelBase.h | 6 ++---- mplot/VisualModelImplMX.h | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/mplot/VisualDefaultShaders.h b/mplot/VisualDefaultShaders.h index 49a1dfa7..45708c0a 100644 --- a/mplot/VisualDefaultShaders.h +++ b/mplot/VisualDefaultShaders.h @@ -46,14 +46,16 @@ namespace mplot "void main()\n" "{\n" " if (instance_count > 0) {\n" - " vec4 iposv = { ipos[gl_InstanceID * 3], ipos[gl_InstanceID * 3 + 1], ipos[gl_InstanceID * 3 + 2], 0 };\n" + " int ipos_i = instance_start * 3 + gl_InstanceID * 3;\n" + " vec4 iposv = { ipos[ipos_i], ipos[ipos_i + 1], ipos[ipos_i + 2], 0 };\n" " if (instparam_count > 0) {\n" " int idx = gl_InstanceID % instparam_count;\n" - " float s = iparam[idx * 5 + 4] * 0.5f;\n" + " int ippos_i = instance_start * 5;\n" + " float s = iparam[ippos_i + idx * 5 + 4];\n" " vec3 p_three = vec3(position) * s;\n" " vec4 p_scaled = vec4(p_three, 1.0);\n" " gl_Position = (p_matrix * v_matrix * m_matrix * (p_scaled + iposv));\n" - " vertex.color = vec4(iparam[idx * 5], iparam[idx * 5 + 1], iparam[idx * 5 + 2], iparam[idx * 5 + 3]);\n" + " vertex.color = vec4(iparam[ippos_i + idx * 5], iparam[ippos_i + idx * 5 + 1], iparam[ippos_i + idx * 5 + 2], iparam[ippos_i + idx * 5 + 3]);\n" " vertex.fragpos = vec3(m_matrix * p_scaled);\n" " } else {\n" " gl_Position = (p_matrix * v_matrix * m_matrix * (position + iposv));\n" diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index fffaf3f1..c2e65d8f 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -725,11 +725,9 @@ namespace mplot * are managed by VisualResources. VisualResources::max_instances must be larger than this. */ unsigned int max_instances = 16 * 1024; - //! The offset in the SSBO for the instances for this VisualModel - unsigned int instance_offset = 0; - //! Which datum in the instance buffer is the one to be drawn by the first thread? + //! The offset into the SSBOs for the instance data for this VisualModel unsigned int instance_start = 0; - //! If drawing with instancing, how many instances? + //! If drawing with instancing, how many instances? <= this->max_instances unsigned int instance_count = 0; //! If drawing with instancing, how many params will be used (these will be cycled through //! per-instance and there may be fewer than instance_count parameters) diff --git a/mplot/VisualModelImplMX.h b/mplot/VisualModelImplMX.h index e2c6df7e..861b0a4d 100644 --- a/mplot/VisualModelImplMX.h +++ b/mplot/VisualModelImplMX.h @@ -110,13 +110,13 @@ namespace mplot for (size_t i = 0; i < position.size(); ++i) { // Get access to the SSBO in VisualResources and add the 3 floats in position[i] at - // the location defined by this->instance_offset + i - this->insert_instance_data (this->instance_offset + i, position[i]); + // the location defined by this->instance_start + i + this->insert_instance_data (this->instance_start + i, position[i]); } this->instance_count = position.size(); for (size_t i = 0; i < colour.size(); ++i) { - this->insert_instparam_data (this->instance_offset + i, colour[i], alpha[i], scale[i]); + this->insert_instparam_data (this->instance_start + i, colour[i], alpha[i], scale[i]); } this->instparam_count = colour.size(); @@ -161,8 +161,8 @@ namespace mplot if (this->flags.test (vm_bools::instanced) && this->init_instance_data) { // Here, we cause the SSBOs to be intialized if they haven't already, and we reserve // some space in the SSBOs for *this model* - this->instance_offset = this->init_instance_data (this->parentVis, this->max_instances); - if (this->instance_offset == std::numeric_limits::max()) { + this->instance_start = this->init_instance_data (this->parentVis, this->max_instances); + if (this->instance_start == std::numeric_limits::max()) { throw std::runtime_error ("Failed to reserve space in SSBO"); } } From 4143d2b0cb05caba199feeeffbe238b8b953d083 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 15:49:07 +0000 Subject: [PATCH 43/46] Notes and reduction in size of SSBO's allocated --- mplot/VisualResourcesBase.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mplot/VisualResourcesBase.h b/mplot/VisualResourcesBase.h index 678d391f..4f262e5e 100644 --- a/mplot/VisualResourcesBase.h +++ b/mplot/VisualResourcesBase.h @@ -88,8 +88,9 @@ namespace mplot //! This will control how much GPU RAM is allocated when using instanced rendering //! (Hopefully, when I'm finished, the RAM will be allocated only if at least one - //! VisualModel is marked 'instanced'). - static constexpr unsigned int max_instances = 256 * 1024; + //! VisualModel is marked 'instanced'). Makes a big difference to speed of operation (unless + //! I can send a portion of a buffer to the GPU). + static constexpr unsigned int max_instances = 32 * 1024; static constexpr unsigned int max_instance_floats = floats_per_instance * max_instances; static constexpr unsigned int max_instparam_floats = floats_per_instparam * max_instances; From 1a23a331aa96f37b7efb55592dab13e1c704fa69 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 15:50:20 +0000 Subject: [PATCH 44/46] set max-instances for our instanced models --- examples/breadcrumbs.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/breadcrumbs.cpp b/examples/breadcrumbs.cpp index a9469b02..648da57f 100644 --- a/examples/breadcrumbs.cpp +++ b/examples/breadcrumbs.cpp @@ -68,14 +68,16 @@ int main() auto isv = std::make_unique> (sm::vec<>{}); isv->name = "isv1"; v.bindmodel (isv); + isv->max_instances = dsz; isv->radiusFixed = 0.03f; isv->finalize(); auto isvp = v.addVisualModel (isv); // Another one - isv = std::make_unique> (sm::vec<>{0,1,0}); + isv = std::make_unique> (sm::vec<>{0,0.1,0}); isv->name = "isv2"; v.bindmodel (isv); + isv->max_instances = dsz; isv->radiusFixed = 0.03f; isv->finalize(); auto isvp2 = v.addVisualModel (isv); @@ -103,7 +105,7 @@ int main() isvp->set_instance_data (points, col, alph, scl); v.render(); - v.waitevents (0.03); + v.waitevents (0.018); #else // I haven't mastered updating a subrange of data in an SSBO // update circularly, change isvp->instance_start each time From f1b5979753cb014e82004b7a54fc3892ead7989f Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 16:25:59 +0000 Subject: [PATCH 45/46] Applies changes to the NoMX classes --- mplot/VisualModelImplNoMX.h | 66 +++++++++++++++---------------------- mplot/VisualNoMX.h | 7 ++-- mplot/VisualOwnableMX.h | 3 -- mplot/VisualOwnableNoMX.h | 27 +++++++++++++++ mplot/VisualResourcesMX.h | 1 - mplot/VisualResourcesNoMX.h | 63 +++++++++++++++++++++++++++++++++++ mplot/gl/ssbo_mx.h | 6 ---- mplot/gl/ssbo_nomx.h | 11 ++++--- 8 files changed, 129 insertions(+), 55 deletions(-) diff --git a/mplot/VisualModelImplNoMX.h b/mplot/VisualModelImplNoMX.h index 30f2cf23..6ef6f8d2 100644 --- a/mplot/VisualModelImplNoMX.h +++ b/mplot/VisualModelImplNoMX.h @@ -66,16 +66,6 @@ namespace mplot model->releaseContext = &mplot::VisualBase::release_context; } - void init_instance_data() - { - if constexpr (mplot::gl::version::has_ssbo (glver) == true) { - if (this->instance_data.ready() == false) { this->instance_data.init(); } - if (this->instparam_data.ready() == false) { this->instparam_data.init(); } - } else { - throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); - } - } - void set_instance_data (const sm::vvec>& position) final { sm::vvec> c = { mplot::colour::crimson }; @@ -98,36 +88,35 @@ namespace mplot const sm::vvec>& colour, const sm::vvec& alpha, const sm::vvec& scale) final { - if (position.size() < 1) { throw std::runtime_error ("set_instance_data: pass some instance positions in"); } - if (position.size() > this->max_instanced_items()) { throw std::runtime_error ("set_instance_data: not enough space"); } - - size_t j = 0; - this->instance_data.data.resize (mplot::VisualModelBase::floats_per_instance * position.size()); - for (size_t i = 0; i < position.size(); ++i) { - sm::vec p = position[i]; - this->instance_data.data[j++] = p[0]; - this->instance_data.data[j++] = p[1]; - this->instance_data.data[j++] = p[2]; + if (position.size() < 1) { + throw std::runtime_error ("set_instance_data: pass some instance positions in"); + } + if (position.size() > this->max_instances) { + throw std::runtime_error ("set_instance_data: Haven't reserved enough space for that"); + } + if (!this->insert_instance_data) { + throw std::runtime_error ("set_instance_data: Function insert_instance_data is not bound"); + } + if (!this->insert_instparam_data) { + throw std::runtime_error ("set_instance_data: Function insert_instparam_data is not bound"); } - this->instance_count = position.size(); - if (colour.size() != scale.size() || colour.size() != alpha.size()) { throw std::runtime_error ("set_instance_data: params vvecs should all have same size (colour, rotn, scale)"); } - this->instparam_data.data.resize (mplot::VisualModelBase::floats_per_instparam * colour.size()); - j = 0; + for (size_t i = 0; i < position.size(); ++i) { + // Get access to the SSBO in VisualResources and add the 3 floats in position[i] at + // the location defined by this->instance_start + i + this->insert_instance_data (this->instance_start + i, position[i]); + } + this->instance_count = position.size(); + for (size_t i = 0; i < colour.size(); ++i) { - this->instparam_data.data[j++] = colour[i][0]; - this->instparam_data.data[j++] = colour[i][1]; - this->instparam_data.data[j++] = colour[i][2]; - this->instparam_data.data[j++] = alpha[i]; - this->instparam_data.data[j++] = scale[i]; + this->insert_instparam_data (this->instance_start + i, colour[i], alpha[i], scale[i]); } this->instparam_count = colour.size(); - this->instance_data.copy_to_gpu(); - this->instparam_data.copy_to_gpu(); + this->instanced_needs_update (this->parentVis); } //! Common code to call after the vertices have been set up. GL has to have been initialised. @@ -163,8 +152,13 @@ namespace mplot glBindVertexArray(0); // carefully unbind and rebind mplot::gl::Util::checkError (__FILE__, __LINE__); - if (this->flags.test (vm_bools::instanced) && this->instance_data.ready() == false) { - this->init_instance_data(); + if (this->flags.test (vm_bools::instanced) && this->init_instance_data) { + // Here, we cause the SSBOs to be intialized if they haven't already, and we reserve + // some space in the SSBOs for *this model* + this->instance_start = this->init_instance_data (this->parentVis, this->max_instances); + if (this->instance_start == std::numeric_limits::max()) { + throw std::runtime_error ("Failed to reserve space in SSBO"); + } } /* @@ -467,12 +461,6 @@ namespace mplot while (ti != this->texts.end()) { (*ti)->addViewRotation (r); ti++; } } - //! Shader Storage Buffer Object for instanced rendering - mplot::gl::ssbo::instance_index, - float, mplot::VisualModelBase::max_instance_floats> instance_data; - mplot::gl::ssbo::instparam_index, - float, mplot::VisualModelBase::max_instparam_floats> instparam_data; - protected: //! A vector of pointers to text models that should be rendered. diff --git a/mplot/VisualNoMX.h b/mplot/VisualNoMX.h index d4efc038..27086ef0 100644 --- a/mplot/VisualNoMX.h +++ b/mplot/VisualNoMX.h @@ -164,15 +164,18 @@ namespace mplot void bindmodel (std::unique_ptr& model) { mplot::VisualBase::template bindmodel (model); - model->setContext = &mplot::VisualBase::set_context; - model->releaseContext = &mplot::VisualBase::release_context; + this->bindextra (model); } + // GL dependent function binds template void bindextra (std::unique_ptr& model) { model->setContext = &mplot::VisualBase::set_context; model->releaseContext = &mplot::VisualBase::release_context; + model->init_instance_data = &mplot::VisualOwnableNoMX::init_instance_data; + model->insert_instance_data = &mplot::VisualOwnableNoMX::insert_instance_data; + model->insert_instparam_data = &mplot::VisualOwnableNoMX::insert_instparam_data; } /* diff --git a/mplot/VisualOwnableMX.h b/mplot/VisualOwnableMX.h index cb335fe7..6b67f6c8 100644 --- a/mplot/VisualOwnableMX.h +++ b/mplot/VisualOwnableMX.h @@ -469,7 +469,6 @@ namespace mplot // Use coordArrowsOffset to set the location of the CoordArrows *scene* this->coordArrows = std::make_unique>(); - this->coordArrows->name = "Scene axes"; // For CoordArrows, because we don't add via Visual::addVisualModel(), we // have to set the get_shaderprogs function here: this->bindmodel (this->coordArrows); @@ -480,7 +479,6 @@ namespace mplot // Create 'user frame of reference object' this->userFrame = std::make_unique>(); - this->userFrame->name = "User frame of ref object"; this->bindmodel (this->userFrame); this->userFrame->init (sm::vec{}, sm::vec{0.0f, 0.0f, -100.0f}, sm::vec{0.1f, 0.1f, 1.0f}, 0.05f, @@ -495,7 +493,6 @@ namespace mplot // Set up the title, which may or may not be rendered mplot::TextFeatures title_tf(0.035f, 64); this->textModel = std::make_unique> (title_tf); - this->textModel->name = "Title text"; this->bindmodel (this->textModel); this->textModel->setSceneTranslation ({0.0f, 0.0f, 0.0f}); this->textModel->setupText (this->title); diff --git a/mplot/VisualOwnableNoMX.h b/mplot/VisualOwnableNoMX.h index b1690c3f..af8f7d41 100644 --- a/mplot/VisualOwnableNoMX.h +++ b/mplot/VisualOwnableNoMX.h @@ -253,6 +253,11 @@ namespace mplot this->userFrame->render(); } + if (this->haveInstanced() && this->instancedNeedsUpdate()) { + this->copy_instance_data_to_gpu(); + this->instancedNeedsUpdate (false); + } + auto vmi = this->vm.begin(); while (vmi != this->vm.end()) { if ((*vmi)->twodimensional() == true) { @@ -345,6 +350,28 @@ namespace mplot return tm->getTextGeometry(); } + static unsigned int init_instance_data ([[maybe_unused]]mplot::VisualBase* _v, const unsigned int n_to_reserve) + { + unsigned int reservation = mplot::VisualResourcesNoMX::i().init_instance_ssbo (n_to_reserve); + return reservation; + } + + static void insert_instance_data (const unsigned int instance_idx, const sm::vec& coord) + { + mplot::VisualResourcesNoMX::i().insert_instance_data (instance_idx, coord); + } + + static void insert_instparam_data (const unsigned int instance_idx, + const std::array& colour, const float& alpha, const float& scale) + { + mplot::VisualResourcesNoMX::i().insert_instparam_data (instance_idx, colour, alpha, scale); + } + + static void copy_instance_data_to_gpu() + { + mplot::VisualResourcesNoMX::i().copy_instance_ssbo_to_gpu(); + } + protected: // Initialize OpenGL shaders, set some flags (Alpha, Anti-aliasing), read in any external // state from json, and set up the coordinate arrows and any VisualTextModels that will be diff --git a/mplot/VisualResourcesMX.h b/mplot/VisualResourcesMX.h index 8fd67d34..b275b5db 100644 --- a/mplot/VisualResourcesMX.h +++ b/mplot/VisualResourcesMX.h @@ -170,7 +170,6 @@ namespace mplot //! Shader Storage Buffer Object for instanced rendering - this holds colour, alpha and scale mplot::gl::ssbo::instparam_index, float, mplot::VisualResourcesBase::max_instparam_floats> instparam_data; - }; } // namespace mplot diff --git a/mplot/VisualResourcesNoMX.h b/mplot/VisualResourcesNoMX.h index ee342f8a..4334ad8a 100644 --- a/mplot/VisualResourcesNoMX.h +++ b/mplot/VisualResourcesNoMX.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace mplot { @@ -109,6 +110,68 @@ namespace mplot } else { f++; } } } + + /*! + * We also manage some programm-wide SSBO objects for instanced rendering + * VisualResourcesdata in . Reserve n_to_reserve instances of data in the SSBOs. Return the + * start offset into the buffers in terms of number of instances + */ + unsigned int init_instance_ssbo (const unsigned int n_to_reserve) + { + unsigned int reservation = std::numeric_limits::max(); + if constexpr (mplot::gl::version::has_ssbo (glver) == true) { + if (this->instance_data.ready() == false) { this->instance_data.init(); } + if (this->instparam_data.ready() == false) { this->instparam_data.init(); } + if (n_to_reserve + this->instance_top <= this->max_instances) { + reservation = this->instance_top; + this->instance_top += n_to_reserve; + this->instance_data.resize (this->instance_top * this->floats_per_instance); + this->instparam_data.resize (this->instance_top * this->floats_per_instparam); + } + } else { + throw std::runtime_error ("Instanced rendering requires OpenGL 4.3 or higher"); + } + return reservation; + } + + void insert_instance_data (const unsigned int instance_idx, const sm::vec& coord) + { + if (instance_idx >= this->max_instances) { + throw std::runtime_error ("insert_instance_data: bad instance_idx"); + } + unsigned int cur_fidx = instance_idx * this->floats_per_instance; + this->instance_data.data[cur_fidx++] = coord[0]; + this->instance_data.data[cur_fidx++] = coord[1]; + this->instance_data.data[cur_fidx++] = coord[2]; + } + + void insert_instparam_data (const unsigned int instance_idx, + const std::array& colour, const float& alpha, const float& scale) + { + if (instance_idx >= this->max_instances) { + throw std::runtime_error ("insert_instparam_data: bad instance_idx"); + } + unsigned int cur_fidx = instance_idx * this->floats_per_instparam; + this->instparam_data.data[cur_fidx++] = colour[0]; + this->instparam_data.data[cur_fidx++] = colour[1]; + this->instparam_data.data[cur_fidx++] = colour[2]; + this->instparam_data.data[cur_fidx++] = alpha; + this->instparam_data.data[cur_fidx++] = scale; + } + + void copy_instance_ssbo_to_gpu() + { + if (this->instance_data.ready()) { this->instance_data.copy_to_gpu(); } + if (this->instparam_data.ready()) { this->instparam_data.copy_to_gpu(); } + } + + //! Shader Storage Buffer Object for instanced rendering - this holds positions only + mplot::gl::ssbo::instance_index, + float, mplot::VisualResourcesBase::max_instance_floats> instance_data; + //! Shader Storage Buffer Object for instanced rendering - this holds colour, alpha and scale + mplot::gl::ssbo::instparam_index, + float, mplot::VisualResourcesBase::max_instparam_floats> instparam_data; + }; } // namespace mplot diff --git a/mplot/gl/ssbo_mx.h b/mplot/gl/ssbo_mx.h index 7544723a..814a5caa 100644 --- a/mplot/gl/ssbo_mx.h +++ b/mplot/gl/ssbo_mx.h @@ -93,15 +93,11 @@ namespace mplot::gl if (_data.size() > N) { throw std::runtime_error ("Too big"); } // Update local cached version of data, a portion of which we're about to write to the SSBO - //std::cout << "Copy " << _data.size() << " elements from _data[0] to this->data[" << offset << "]\n"; this->data.resize (_data.size()); for (unsigned int i = 0; i < _data.size(); ++i) { this->data[i + offset] = _data[i]; } - //std::cout << "this->data is now\n\n" << this->data << std::endl; - this->glfn->BindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__, this->glfn); - std::cout << "Mapping buffer range at offset " << offset << " floats = " << (offset * sizeof(T)) << " bytes" << std::endl; T* cpuptr = static_cast(this->glfn->MapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), _data.size() * sizeof(T), GL_MAP_WRITE_BIT)); @@ -111,9 +107,7 @@ namespace mplot::gl return; } // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: - std::cout << "About to copy " << _data.size() << " things (prolly floats)...\n"; for (unsigned int i = 0; i < _data.size(); ++i) { - std::cout << "Writing value " << " _data[i] = " << _data[i] << " into mapped range" << std::endl; cpuptr[i] = _data[i]; } diff --git a/mplot/gl/ssbo_nomx.h b/mplot/gl/ssbo_nomx.h index 96b4a296..24c4c222 100644 --- a/mplot/gl/ssbo_nomx.h +++ b/mplot/gl/ssbo_nomx.h @@ -21,7 +21,7 @@ namespace mplot::gl // @tparam index: The index of the buffer, used in the GLSL // @tparam T: The type of the data in the SSBO // @tparam N: The max number of elements of type T in the SSBO - template // Could add version template params if necessary, to select correct gl function calls + template struct ssbo { // The name of the buffer, generated with glGenBuffers() @@ -31,6 +31,12 @@ namespace mplot::gl bool ready() const { return this->name != 0u; } + void resize (std::size_t sz) + { + if (sz > N) { throw std::runtime_error ("ssbo: can't resize to this size"); } + this->data.resize (sz); + } + ssbo() {} ~ssbo() {} @@ -88,7 +94,6 @@ namespace mplot::gl glBindBufferBase (GL_SHADER_STORAGE_BUFFER, index, this->name); mplot::gl::Util::checkError (__FILE__, __LINE__); - std::cout << "Mapping buffer range at offset " << offset << " floats = " << (offset * sizeof(T)) << " bytes" << std::endl; T* cpuptr = static_cast(glMapBufferRange (GL_SHADER_STORAGE_BUFFER, offset * sizeof(T), _data.size() * sizeof(T), GL_MAP_WRITE_BIT)); @@ -98,9 +103,7 @@ namespace mplot::gl return; } // Note: cpuptr is a 'pointer to the beginning of the mapped range' and so we don't incorporate offset again, below: - std::cout << "About to copy " << _data.size() << " things (prolly floats)...\n"; for (unsigned int i = 0; i < _data.size(); ++i) { - std::cout << "Writing value " << " _data[i] = " << _data[i] << " into mapped range" << std::endl; cpuptr[i] = _data[i]; } From b66c8e5257c52018810c1008b9668277e47d84e4 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Dec 2025 16:43:08 +0000 Subject: [PATCH 46/46] Shader tweaks --- mplot/VisualDefaultShaders.h | 14 ++++-- shaders/Visual.vert.glsl | 68 ++++++----------------------- shaders/Visual_instancing.vert.glsl | 45 +++++++++++++++++++ 3 files changed, 68 insertions(+), 59 deletions(-) create mode 100644 shaders/Visual_instancing.vert.glsl diff --git a/mplot/VisualDefaultShaders.h b/mplot/VisualDefaultShaders.h index 45708c0a..41d8c33d 100644 --- a/mplot/VisualDefaultShaders.h +++ b/mplot/VisualDefaultShaders.h @@ -9,14 +9,16 @@ namespace mplot { // The default vertex shader. To study this GLSL, see Visual.vert.glsl, which has // some code comments. - const char* defaultVtxShader_part1 = + const char* defaultVtxShader_part1a = "uniform mat4 m_matrix;\n" "uniform mat4 v_matrix;\n" "uniform mat4 p_matrix;\n" - "uniform float alpha;\n" + "uniform float alpha;\n"; + const char* defaultVtxShader_instance_uniforms = "uniform int instance_count = 0;\n" "uniform int instance_start = -1;\n" - "uniform int instparam_count = 0;\n" + "uniform int instparam_count = 0;\n"; + const char* defaultVtxShader_part1b = "layout(location = 0) in vec4 position;\n" "layout(location = 1) in vec4 normalin;\n" "layout(location = 2) in vec3 color;\n"; @@ -74,7 +76,11 @@ namespace mplot { std::string shdr; shdr += mplot::gl::version::shaderpreamble (glver); - shdr += defaultVtxShader_part1; + shdr += defaultVtxShader_part1a; + if (mplot::gl::version::has_ssbo (glver)) { + shdr += defaultVtxShader_instance_uniforms; + } + shdr += defaultVtxShader_part1b; if (mplot::gl::version::has_ssbo (glver)) { shdr += "layout (std430, binding = 1) buffer InstPos { float ipos[]; };\n"; shdr += "layout (std430, binding = 2) buffer InstParam { float iparam[]; };\n"; diff --git a/shaders/Visual.vert.glsl b/shaders/Visual.vert.glsl index 30cfce51..2eadc5b3 100644 --- a/shaders/Visual.vert.glsl +++ b/shaders/Visual.vert.glsl @@ -1,63 +1,21 @@ -// The coded-in shaders tell non-Mac platforms that they use OpenGL 4.5, but Mac limited to 4.1 -#version 430 - -// SSBO to be added optionally - -// ProjMatrix * RotnMatrix operation can be carried out on CPU with a single matrix -//uniform mat4 mvp_matrix; -// Or, and this is important for lighting effects and possibly text, too, matrices can be passed separately -//uniform mat4 vp_matrix; // sceneview-projection matrix -uniform mat4 m_matrix; // model matrix -uniform mat4 v_matrix; // scene view matrix -uniform mat4 p_matrix; // projection matrix -// alpha - to make a model see-through +#version 410 +uniform mat4 m_matrix; +uniform mat4 v_matrix; +uniform mat4 p_matrix; uniform float alpha; -uniform int instance_count = 0; -uniform int instance_start = -1; -// We may have 100 instances but only 1 set of params to apply to all instances -uniform int instparam_count = 0; - -layout(location = 0) in vec4 position; // Attrib location 0 -layout(location = 1) in vec4 normalin; // Attrib location 1 -layout(location = 2) in vec3 color; // Attrib location 2 - -layout (std430, binding = 1) buffer InstPos { float ipos[]; }; -layout (std430, binding = 2) buffer InstParam { float iparam[]; }; -//later: layout (std430, binding = 3) buffer InstRotn { float irotn[]; }; - +layout(location = 0) in vec4 position; +layout(location = 1) in vec4 normalin; +layout(location = 2) in vec3 color; out VERTEX { vec4 normal; - vec4 color; // Could make vec4 and incorporate alpha - vec3 fragpos; // fragment position + vec4 color; + vec3 fragpos; } vertex; - -void main (void) +void main() { - if (instance_count > 0) { - vec4 iposv = { ipos[gl_InstanceID * 3], ipos[gl_InstanceID * 3 + 1], ipos[gl_InstanceID * 3 + 2], 0 }; - if (instparam_count > 0) { - - int idx = gl_InstanceID % instparam_count; - float s = iparam[idx * 5 + 4] * 0.5f; - vec3 p_three = vec3(position) * s; - vec4 p_scaled = vec4(p_three, 1.0); // Note 1.0 in last element here, and 0.0 in last element of iposv adds to 1 - gl_Position = (p_matrix * v_matrix * m_matrix * (p_scaled + iposv)); - vertex.color = vec4(iparam[idx * 5], iparam[idx * 5 + 1], iparam[idx * 5 + 2], iparam[idx * 5 + 3]); - vertex.fragpos = vec3(m_matrix * p_scaled); // Required for correct lighting - - } else { - gl_Position = (p_matrix * v_matrix * m_matrix * (position + iposv)); - vertex.color = vec4(color, alpha); - vertex.fragpos = vec3(m_matrix * position); // Required for correct lighting - } - } else { - gl_Position = (p_matrix * v_matrix * m_matrix * position); - vertex.color = vec4(color, alpha); - vertex.fragpos = vec3(m_matrix * position); // Required for correct lighting - } - - - // Required for correct lighting + gl_Position = (p_matrix * v_matrix * m_matrix * position); + vertex.color = vec4(color, alpha); + vertex.fragpos = vec3(m_matrix * position); vertex.normal = normalin; } diff --git a/shaders/Visual_instancing.vert.glsl b/shaders/Visual_instancing.vert.glsl new file mode 100644 index 00000000..e11e5e8d --- /dev/null +++ b/shaders/Visual_instancing.vert.glsl @@ -0,0 +1,45 @@ +#version 430 +uniform mat4 m_matrix; +uniform mat4 v_matrix; +uniform mat4 p_matrix; +uniform float alpha; +uniform int instance_count = 0; +uniform int instance_start = -1; +uniform int instparam_count = 0; +layout(location = 0) in vec4 position; +layout(location = 1) in vec4 normalin; +layout(location = 2) in vec3 color; +layout (std430, binding = 1) buffer InstPos { float ipos[]; }; +layout (std430, binding = 2) buffer InstParam { float iparam[]; }; +out VERTEX +{ + vec4 normal; + vec4 color; + vec3 fragpos; +} vertex; +void main() +{ + if (instance_count > 0) { + int ipos_i = instance_start * 3 + gl_InstanceID * 3; + vec4 iposv = { ipos[ipos_i], ipos[ipos_i + 1], ipos[ipos_i + 2], 0 }; + if (instparam_count > 0) { + int idx = gl_InstanceID % instparam_count; + int ippos_i = instance_start * 5; + float s = iparam[ippos_i + idx * 5 + 4]; + vec3 p_three = vec3(position) * s; + vec4 p_scaled = vec4(p_three, 1.0); + gl_Position = (p_matrix * v_matrix * m_matrix * (p_scaled + iposv)); + vertex.color = vec4(iparam[ippos_i + idx * 5], iparam[ippos_i + idx * 5 + 1], iparam[ippos_i + idx * 5 + 2], iparam[ippos_i + idx * 5 + 3]); + vertex.fragpos = vec3(m_matrix * p_scaled); + } else { + gl_Position = (p_matrix * v_matrix * m_matrix * (position + iposv)); + vertex.color = vec4(color, alpha); + vertex.fragpos = vec3(m_matrix * position); + } + } else { + gl_Position = (p_matrix * v_matrix * m_matrix * position); + vertex.color = vec4(color, alpha); + vertex.fragpos = vec3(m_matrix * position); + } + vertex.normal = normalin; +}