From 23e9f4d891f94569b0cd08a55b0e3ee2d4ef29de Mon Sep 17 00:00:00 2001
From: Axel Davy <axel.davy@ens-cachan.fr>
Date: Fri, 26 Jul 2024 22:11:40 +0200
Subject: [PATCH 1/2] fix (mvUtilities): correct UpdateTexture

The textures are updated using a PBO.

The correct order is:
- Fill the PBO
- Trigger the texture update from the PBO

And not the other.

This fixes incorrect data being sent on some
frames.
---
 src/mvUtilities_linux.cpp | 52 +++++++++++++++++++--------------------
 1 file changed, 26 insertions(+), 26 deletions(-)

diff --git a/src/mvUtilities_linux.cpp b/src/mvUtilities_linux.cpp
index b26ef1ff6..3a59b32c1 100644
--- a/src/mvUtilities_linux.cpp
+++ b/src/mvUtilities_linux.cpp
@@ -219,18 +219,6 @@ UpdateTexture(void* texture, unsigned width, unsigned height, std::vector<float>
 {
     auto textureId = (GLuint)(size_t)texture;
 
-    // start to copy from PBO to texture object ///////
-
-    // bind the texture and PBO
-    glBindTexture(GL_TEXTURE_2D, textureId);
-    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, PBO_ids[textureId]);
-
-    // copy pixels from PBO to texture object
-    // Use offset instead of ponter.
-    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_FLOAT, 0);
-
-    ///////////////////////////////////////////////////
-
     // start to modify pixel values ///////////////////
 
     // bind PBO to update pixel values
@@ -256,16 +244,6 @@ UpdateTexture(void* texture, unsigned width, unsigned height, std::vector<float>
 
     ///////////////////////////////////////////////////
 
-    // it is good idea to release PBOs with ID 0 after use.
-    // Once bound with 0, all pixel operations behave normal ways.
-    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
-}
-
- void
-UpdateRawTexture(void* texture, unsigned width, unsigned height, float* data, int components)
-{
-    auto textureId = (GLuint)(size_t)texture;
-
     // start to copy from PBO to texture object ///////
 
     // bind the texture and PBO
@@ -274,13 +252,20 @@ UpdateRawTexture(void* texture, unsigned width, unsigned height, float* data, in
 
     // copy pixels from PBO to texture object
     // Use offset instead of ponter.
-    if(components == 4)
-        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_FLOAT, 0);
-    else
-        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_FLOAT, 0);
+    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_FLOAT, 0);
 
     ///////////////////////////////////////////////////
 
+    // it is good idea to release PBOs with ID 0 after use.
+    // Once bound with 0, all pixel operations behave normal ways.
+    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+}
+
+ void
+UpdateRawTexture(void* texture, unsigned width, unsigned height, float* data, int components)
+{
+    auto textureId = (GLuint)(size_t)texture;
+
     // start to modify pixel values ///////////////////
 
     // bind PBO to update pixel values
@@ -306,6 +291,21 @@ UpdateRawTexture(void* texture, unsigned width, unsigned height, float* data, in
 
     ///////////////////////////////////////////////////
 
+    // start to copy from PBO to texture object ///////
+
+    // bind the texture and PBO
+    glBindTexture(GL_TEXTURE_2D, textureId);
+    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, PBO_ids[textureId]);
+
+    // copy pixels from PBO to texture object
+    // Use offset instead of ponter.
+    if(components == 4)
+        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_FLOAT, 0);
+    else
+        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_FLOAT, 0);
+
+    ///////////////////////////////////////////////////
+
     // it is good idea to release PBOs with ID 0 after use.
     // Once bound with 0, all pixel operations behave normal ways.
     glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

From 379eb4961efe0ad6e5b874503587b72631f93e8a Mon Sep 17 00:00:00 2001
From: Axel Davy <axel.davy@ens-cachan.fr>
Date: Fri, 26 Jul 2024 22:12:48 +0200
Subject: [PATCH 2/2] opt: Update textures only when the content has changed

Track whether the texture content has changed.

This enables to avoid updating the texture every frame
if the content hasn't changed.
---
 src/mvTextureItems.cpp | 14 ++++++++++++--
 src/mvTextureItems.h   |  2 ++
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/mvTextureItems.cpp b/src/mvTextureItems.cpp
index f4036ebc6..342a51fc7 100644
--- a/src/mvTextureItems.cpp
+++ b/src/mvTextureItems.cpp
@@ -114,6 +114,7 @@ PyObject* mvDynamicTexture::getPyValue()
 void mvDynamicTexture::setPyValue(PyObject* value)
 {
 	*_value = ToFloatVect(value);
+	_updateNeeded = true;
 }
 
 void mvDynamicTexture::setDataSource(mvUUID dataSource)
@@ -135,6 +136,7 @@ void mvDynamicTexture::setDataSource(mvUUID dataSource)
 		return;
 	}
 	_value = *static_cast<std::shared_ptr<std::vector<float>>*>(item->getValue());
+	_updateNeeded = true;
 }
 
 void mvDynamicTexture::draw(ImDrawList* drawlist, float x, float y)
@@ -151,8 +153,11 @@ void mvDynamicTexture::draw(ImDrawList* drawlist, float x, float y)
 		return;
 	}
 
-	UpdateTexture(_texture, _permWidth, _permHeight, *_value);
+	if (!_updateNeeded)
+		return;
 
+	UpdateTexture(_texture, _permWidth, _permHeight, *_value);
+	_updateNeeded = false;
 }
 
 void mvDynamicTexture::handleSpecificRequiredArgs(PyObject* dict)
@@ -165,6 +170,7 @@ void mvDynamicTexture::handleSpecificRequiredArgs(PyObject* dict)
 	_permHeight = ToInt(PyTuple_GetItem(dict, 1));
 	config.height = _permHeight;
 	*_value = ToFloatVect(PyTuple_GetItem(dict, 2));
+	_updateNeeded = true;
 }
 
 void mvDynamicTexture::handleSpecificKeywordArgs(PyObject* dict)
@@ -203,6 +209,7 @@ void mvRawTexture::setPyValue(PyObject* value)
 			{
 				mvThrowPythonError(mvErrorCode::mvTextureNotFound, GetEntityCommand(type), "Texture data not valid", this);
 			}
+			_updateNeeded = true;
 		}
 		PyBuffer_Release(&buffer_info);
 		if (_buffer)
@@ -238,9 +245,12 @@ void mvRawTexture::draw(ImDrawList* drawlist, float x, float y)
 		return;
 	}
 
+	if (!_updateNeeded)
+		return;
+
 	if (_componentType == ComponentType::MV_FLOAT_COMPONENT)
 		UpdateRawTexture(_texture, _permWidth, _permHeight, (float*)_value, _components);
-
+	_updateNeeded = false;
 }
 
 void mvRawTexture::handleSpecificRequiredArgs(PyObject* dict)
diff --git a/src/mvTextureItems.h b/src/mvTextureItems.h
index e3157c04e..0546e3160 100644
--- a/src/mvTextureItems.h
+++ b/src/mvTextureItems.h
@@ -75,6 +75,7 @@ class mvRawTexture : public mvAppItem
     void* _value = nullptr;
     void* _texture = nullptr;
     bool          _dirty = true;
+    bool          _updateNeeded = true;
     ComponentType _componentType = ComponentType::MV_FLOAT_COMPONENT;
     int           _components = 4;
     int           _permWidth = 0;
@@ -106,6 +107,7 @@ class mvDynamicTexture : public mvAppItem
     std::shared_ptr<std::vector<float>> _value = std::make_shared<std::vector<float>>(std::vector<float>{0.0f});
     void* _texture = nullptr;
     bool                      _dirty = true;
+    bool                      _updateNeeded = true;
     int                       _permWidth = 0;
     int                       _permHeight = 0;