Added VertexBufferUpdateCallback 61/290561/8
authorAdam Bialogonski <adam.b@samsung.com>
Tue, 28 Mar 2023 20:52:18 +0000 (21:52 +0100)
committerAdam Bialogonski <adam.b@samsung.com>
Fri, 12 May 2023 13:34:47 +0000 (14:34 +0100)
Added the callback invoked when the vertex buffer needs to be updated.
The callback passes pointer and maximum size that should be written.

The callback returns number of valid elements to draw.

In case there are more than 1 vertex buffers attached, the render geometry iterates and looks for lowest number of elements to render (so we won't go out of boundaries of any of attached buffers).

The update of VertexBuffer through the callback takes place on the update/render thread and it's up to the user to ensure explicit synchronization. Event side DALi objects must not be accessed (most likely, that will fail) so alternative way of
passing data should be added by developers.

Change-Id: I4db7812cf6d6579c8cd05bcc5c25d966f6ae1aad

14 files changed:
automated-tests/src/dali/utc-Dali-VertexBuffer.cpp
dali/internal/event/rendering/vertex-buffer-impl.cpp
dali/internal/event/rendering/vertex-buffer-impl.h
dali/internal/render/common/render-manager.cpp
dali/internal/render/common/render-manager.h
dali/internal/render/renderers/gpu-buffer.cpp
dali/internal/render/renderers/gpu-buffer.h
dali/internal/render/renderers/render-geometry.cpp
dali/internal/render/renderers/render-vertex-buffer.cpp
dali/internal/render/renderers/render-vertex-buffer.h
dali/internal/update/manager/update-manager.cpp
dali/internal/update/manager/update-manager.h
dali/public-api/rendering/vertex-buffer.cpp
dali/public-api/rendering/vertex-buffer.h

index b03d93864edb20915c9eb1f6599602eae5389481..079a3fe9048abe29c7c0d0f686f16be45aa4f15d 100644 (file)
 
 #include <dali-test-suite-utils.h>
 #include <dali/public-api/dali-core.h>
-
+#include <chrono>
+using namespace std::chrono_literals;
 using namespace Dali;
 
 #include <mesh-builder.h>
+#include <future>
+
+struct VertexBufferUpdater
+{
+  struct Diagnostics
+  {
+    uint32_t counter{0};
+    void*    lastPtr{nullptr};
+    size_t   lastSize{0};
+    size_t   lastReturned{0};
+  };
+
+  VertexBufferUpdater() = default;
+
+  uint32_t UpdateVertices(void* ptr, size_t size)
+  {
+    diagnostics.lastPtr      = ptr;
+    diagnostics.lastSize     = size;
+    diagnostics.lastReturned = returnSize;
+    diagnostics.counter++;
+
+    promise.set_value(diagnostics);
+    return returnSize;
+  }
+
+  void SetCallbackReturnValue(size_t size)
+  {
+    returnSize = size;
+  }
+
+  void Reset()
+  {
+    promise = std::promise<Diagnostics>();
+  }
+
+  std::unique_ptr<VertexBufferUpdateCallback> CreateCallback()
+  {
+    return VertexBufferUpdateCallback::New(this, &VertexBufferUpdater::UpdateVertices);
+  }
+
+  Diagnostics GetValue()
+  {
+    auto value = promise.get_future().get();
+
+    // reset promise automatically
+    promise = {};
+    return value;
+  }
+
+  bool IsValueReady()
+  {
+    // fake-wait for two frames
+    auto status = promise.get_future().wait_for(32ms);
+    return status == std::future_status::ready;
+  }
+
+  Diagnostics               diagnostics;
+  size_t                    returnSize{0u};
+  std::promise<Diagnostics> promise;
+};
 
 void vertexBuffer_test_startup(void)
 {
@@ -446,3 +507,112 @@ int UtcDaliVertexBufferSetDivisor(void)
   DALI_TEST_EQUALS(params2["instanceCount"].str(), oss.str(), TEST_LOCATION);
   END_TEST;
 }
+
+int UtcDaliVertexBufferUpdateCallback(void)
+{
+  TestApplication application;
+
+  // Create vertex buffer
+  VertexBuffer vertexBuffer = VertexBuffer::New(Property::Map() = {
+                                                  {"aPosition", Property::Type::VECTOR2},
+                                                  {"aTexCoord", Property::Type::VECTOR2}});
+
+  // set callback
+  auto callback = std::make_unique<VertexBufferUpdater>();
+  vertexBuffer.SetVertexBufferUpdateCallback(callback->CreateCallback());
+
+  struct Vertex
+  {
+    Vector2 pos;
+    Vector2 uv;
+  };
+
+  std::vector<Vertex> vertices;
+  vertices.resize(16);
+  vertexBuffer.SetData(vertices.data(), 16);
+
+  Geometry geometry = Geometry::New();
+  geometry.AddVertexBuffer(vertexBuffer);
+  Shader   shader   = CreateShader();
+  Renderer renderer = Renderer::New(geometry, shader);
+  Actor    actor    = Actor::New();
+  actor.SetProperty(Actor::Property::SIZE, Vector3::ONE * 100.f);
+  actor.AddRenderer(renderer);
+  application.GetScene().Add(actor);
+
+  auto& gl    = application.GetGlAbstraction();
+  auto& trace = gl.GetDrawTrace();
+  trace.Enable(true);
+  trace.EnableLogging(true);
+
+  callback->SetCallbackReturnValue(16 * sizeof(Vertex));
+
+  application.SendNotification();
+  application.Render();
+
+  auto value = callback->GetValue();
+
+  // Test whether callback ran
+  DALI_TEST_EQUALS(value.counter, 1, TEST_LOCATION);
+  DALI_TEST_EQUALS(value.lastSize, 16 * sizeof(Vertex), TEST_LOCATION);
+  DALI_TEST_EQUALS(value.lastReturned, 16 * sizeof(Vertex), TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(value.lastPtr, (void*)nullptr, 0, TEST_LOCATION);
+
+  // test whether draw call has been issued (return value indicates end of array to be drawn)
+  auto result = trace.FindMethod("DrawArrays");
+  DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+  result = trace.FindMethodAndParams("DrawArrays", "4, 0, 16");
+  DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+
+  // Test 2. Update and render only half of vertex buffer
+  callback->SetCallbackReturnValue(8 * sizeof(Vertex));
+  trace.Reset();
+
+  application.SendNotification();
+  application.Render();
+
+  value = callback->GetValue();
+  // Test whether callback ran
+  DALI_TEST_EQUALS(value.counter, 2, TEST_LOCATION);
+  DALI_TEST_EQUALS(value.lastSize, 16 * sizeof(Vertex), TEST_LOCATION);
+  DALI_TEST_EQUALS(value.lastReturned, 8 * sizeof(Vertex), TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(value.lastPtr, (void*)nullptr, 0, TEST_LOCATION);
+  result = trace.FindMethod("DrawArrays");
+  DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+  result = trace.FindMethodAndParams("DrawArrays", "4, 0, 8");
+  DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+
+  // Test 3. callback returns 0 elements to render, the draw call shouldn't happen.
+  callback->SetCallbackReturnValue(0);
+  trace.Reset();
+
+  application.SendNotification();
+  application.Render();
+
+  value = callback->GetValue();
+  // Test whether callback ran
+  DALI_TEST_EQUALS(value.counter, 3, TEST_LOCATION);
+  DALI_TEST_EQUALS(value.lastSize, 16 * sizeof(Vertex), TEST_LOCATION);
+  DALI_TEST_EQUALS(value.lastReturned, 0, TEST_LOCATION);
+  DALI_TEST_NOT_EQUALS(value.lastPtr, (void*)nullptr, 0, TEST_LOCATION);
+  result = trace.FindMethod("DrawArrays");
+  DALI_TEST_EQUALS(result, false, TEST_LOCATION);
+
+  // Test 4. removing callback, original behaviour should kick in
+  vertexBuffer.SetVertexBufferUpdateCallback(nullptr);
+  trace.Reset();
+  callback->Reset();
+
+  application.SendNotification();
+  application.Render();
+
+  auto valueReady = callback->IsValueReady();
+  DALI_TEST_EQUALS(valueReady, false, TEST_LOCATION);
+  DALI_TEST_EQUALS(callback->diagnostics.counter, 3, TEST_LOCATION);
+  result = trace.FindMethod("DrawArrays");
+  DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+  result = trace.FindMethodAndParams("DrawArrays", "4, 0, 16");
+  DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+
+  END_TEST;
+}
index 547d60dfc602de9581cc3f071fc3d6a1cf9aa5c7..b1b58a071a778cc86329907d9ee5c7a2d4a633f8 100644 (file)
@@ -182,6 +182,11 @@ uint32_t VertexBuffer::GetDivisor() const
   return mDivisor;
 }
 
+void VertexBuffer::SetVertexBufferUpdateCallback(VertexBufferUpdateCallback& callback)
+{
+  SceneGraph::SetVertexBufferUpdateCallback(mEventThreadServices.GetUpdateManager(), *mRenderObject, &callback);
+}
+
 const Render::VertexBuffer* VertexBuffer::GetRenderObject() const
 {
   return mRenderObject;
index a10a8862be879eff91354dc660520aa5592268d3..17f3b62ec9d92818f09676b3bb6e52533b4280f4 100644 (file)
@@ -68,6 +68,11 @@ public:
    */
   uint32_t GetDivisor() const;
 
+  /**
+   * @copydoc Dali::VertexBuffer::SetVertexBufferUpdateCallback()
+   */
+  void SetVertexBufferUpdateCallback(VertexBufferUpdateCallback& callback);
+
 public: // Default property extensions from Object
   /**
    * @brief Get the render thread side of the VertexBuffer
index 6b58571b4181f7b5cf61426e6977a14ed5a31bc7..2dff2e549e236c12f64679574669934f960644ca 100644 (file)
@@ -384,6 +384,11 @@ void RenderManager::SetVertexBufferData(Render::VertexBuffer* vertexBuffer, Owne
   vertexBuffer->SetData(data.Release(), size);
 }
 
+void RenderManager::SetVertexBufferUpdateCallback(Render::VertexBuffer* vertexBuffer, Dali::VertexBufferUpdateCallback* callback)
+{
+  vertexBuffer->SetVertexBufferUpdateCallback(callback);
+}
+
 void RenderManager::SetIndexBuffer(Render::Geometry* geometry, Render::Geometry::Uint16ContainerType& indices)
 {
   geometry->SetIndexBuffer(indices);
index 6bd9a3d6c92a71115c4f81bd544925848bafa983..eb23b29e7a9cd65980715a8af6eaf524eb8792b9 100644 (file)
@@ -206,6 +206,13 @@ public:
    */
   void SetVertexBufferData(Render::VertexBuffer* vertexBuffer, OwnerPointer<Vector<uint8_t>>& data, uint32_t size);
 
+  /**
+   * Sets vertex buffer update callback
+   * @param vertexBuffer
+   * @param callback
+   */
+  void SetVertexBufferUpdateCallback(Render::VertexBuffer* vertexBuffer, Dali::VertexBufferUpdateCallback* callback);
+
   /**
    * Sets the data for the index buffer of an existing geometry
    * @param[in] geometry The geometry
index 11cc2a94e5bc43a135c284592f4b54e78c090a42..9b2e42a91b94a69f05750612bb12a6bb3ee1ce10 100644 (file)
@@ -17,6 +17,7 @@
 
 // CLASS HEADER
 #include <dali/internal/render/renderers/gpu-buffer.h>
+#include <dali/public-api/rendering/vertex-buffer.h>
 
 // INTERNAL INCLUDES
 #include <dali/graphics-api/graphics-types.h>
@@ -74,6 +75,32 @@ void GpuBuffer::UpdateDataBuffer(Graphics::Controller& graphicsController, uint3
   graphicsController.UnmapMemory(std::move(memory));
 }
 
+void GpuBuffer::UpdateDataBufferWithCallback(Graphics::Controller& graphicsController, Dali::VertexBufferUpdateCallback* callback, uint32_t& bytesUpdatedCount)
+{
+  // create or orphan object so mapping can be instant
+  if(!mGraphicsObject || mWritePolicy == WritePolicy::DISCARD)
+  {
+    Graphics::BufferCreateInfo createInfo{};
+    createInfo.SetUsage(mUsage).SetSize(mSize);
+    mGraphicsObject = graphicsController.CreateBuffer(createInfo, std::move(mGraphicsObject));
+    mCapacity       = mSize;
+  }
+
+  Graphics::MapBufferInfo info{};
+  info.buffer = mGraphicsObject.get();
+  info.usage  = 0 | Graphics::MemoryUsageFlagBits::WRITE;
+  info.offset = 0;
+  info.size   = mSize; // map entire buffer
+
+  auto  memory = graphicsController.MapBufferRange(info);
+  void* ptr    = memory->LockRegion(0, mSize);
+
+  bytesUpdatedCount = callback->Invoke(ptr, mSize); // passing low level binary size, not element count!
+
+  memory->Unlock(true);
+  graphicsController.UnmapMemory(std::move(memory));
+}
+
 void GpuBuffer::Destroy()
 {
   mCapacity = 0;
index 645480a78ac24d34b1adde1494d6bcc9eb83d8a5..45ac7854296c52c4dafbf2d151ddcae2c5c04d91 100644 (file)
@@ -23,6 +23,8 @@
 
 namespace Dali
 {
+class VertexBufferUpdateCallback;
+
 namespace Internal
 {
 /**
@@ -70,7 +72,6 @@ public:
   ~GpuBuffer() = default;
 
   /**
-   *
    * Creates or updates a buffer object and binds it to the target.
    * @param graphicsController The graphics controller
    * @param size Specifies the size in bytes of the buffer object's new data store.
@@ -78,6 +79,17 @@ public:
    */
   void UpdateDataBuffer(Graphics::Controller& graphicsController, uint32_t size, const void* data);
 
+  /**
+   * Updates existing buffer by calling associated VertexBufferUpdateCallback
+   *
+   * bytesUpdatedCount limits next draw call to that amount of data.
+   *
+   * @param[in] graphicsController Valid controller
+   * @param[in] callback  Valid pointer to the VertexBufferUpdateCallback
+   * @param[out] bytesUpdatedCount Number of bytes updated
+   */
+  void UpdateDataBufferWithCallback(Graphics::Controller& graphicsController, Dali::VertexBufferUpdateCallback* callback, uint32_t& bytesUpdatedCount);
+
   /**
    * Get the size of the buffer
    * @return size Size of the buffer in bytes
index 03afcd7e2c780227570a1fcdde878ff93bd9655b..ed21d36e9ec45a8cf0e7145982daa967ce42973e 100644 (file)
@@ -179,7 +179,7 @@ bool Geometry::BindVertexAttributes(Graphics::CommandBuffer& commandBuffer)
     }
     //@todo Figure out why this is being drawn without geometry having been uploaded
   }
-  if(buffers.empty())
+  if(buffers.empty() || buffers.size() != vertexBufferCount)
   {
     return false;
   }
@@ -233,13 +233,32 @@ bool Geometry::Draw(
     // Un-indexed draw call
     uint32_t numVertices(0u);
 
-    if(mVertexBuffers.Count() > 0)
+    // Use element buffer count for drawing arrays (needs changing API, for workaround)
+    if(elementBufferCount)
+    {
+      numVertices = elementBufferCount;
+    }
+    else if(mVertexBuffers.Count() > 0)
     {
       // truncated, no value loss happening in practice
-      numVertices = static_cast<uint32_t>(mVertexBuffers[0]->GetElementCount());
+      numVertices = static_cast<uint32_t>(mVertexBuffers[0]->GetRenderableElementCount());
+    }
+    // In case we have more buffers, we select buffer with less elements to render
+    // TODO: we may eventually support wrapping around buffers????
+    else if(mVertexBuffers.Count() > 1)
+    {
+      auto elementsCount = mVertexBuffers[0]->GetRenderableElementCount();
+      for(auto& vertexBuffer : mVertexBuffers)
+      {
+        elementsCount = std::min(elementsCount, vertexBuffer->GetRenderableElementCount());
+      }
+      numVertices = elementsCount;
+    }
+    // Issue draw call only if there's non-zero numVertices
+    if(numVertices)
+    {
+      commandBuffer.Draw(numVertices, mInstanceCount, 0, 0);
     }
-
-    commandBuffer.Draw(numVertices, mInstanceCount, 0, 0);
   }
   return true;
 }
index a383551ab594f9bd7cc8690621b4204c3940943f..0ccf583ff1f18bdd1b3e14be1dc5e621063c9944 100644 (file)
@@ -32,6 +32,7 @@ VertexBuffer::VertexBuffer()
   mData(nullptr),
   mGpuBuffer(nullptr),
   mSize(0),
+  mElementCount(0),
   mDataChanged(true)
 {
 }
@@ -51,9 +52,19 @@ void VertexBuffer::SetData(Dali::Vector<uint8_t>* data, uint32_t size)
   mDataChanged = true;
 }
 
+void VertexBuffer::SetVertexBufferUpdateCallback(Dali::VertexBufferUpdateCallback* callback)
+{
+  mVertexBufferUpdateCallback.reset(callback);
+}
+
 bool VertexBuffer::Update(Graphics::Controller& graphicsController)
 {
-  if(!mData || !mFormat || !mSize)
+  if(!mFormat || !mSize)
+  {
+    return false;
+  }
+
+  if(!mVertexBufferUpdateCallback && !mData)
   {
     return false;
   }
@@ -66,15 +77,27 @@ bool VertexBuffer::Update(Graphics::Controller& graphicsController)
     }
 
     // Update the GpuBuffer
-    if(mGpuBuffer)
+    if(mGpuBuffer && mData)
     {
       DALI_ASSERT_DEBUG(mSize && "No data in the property buffer!");
       mGpuBuffer->UpdateDataBuffer(graphicsController, GetDataSize(), &((*mData)[0]));
     }
 
+    mElementCount = mSize;
+
     mDataChanged = false;
   }
 
+  // To execute the callback the buffer must be already initialized.
+  if(mVertexBufferUpdateCallback && mGpuBuffer)
+  {
+    // If running callback, we may end up with less elements in the buffer
+    // of the same capacity
+    uint32_t updatedSize = mSize * mFormat->size;
+    mGpuBuffer->UpdateDataBufferWithCallback(graphicsController, mVertexBufferUpdateCallback.get(), updatedSize);
+    mElementCount = updatedSize / mFormat->size;
+  }
+
   return true;
 }
 
index a8b86c1ccafee3008fa00e443c3bf9f1cee2e6d6..e0079778261cd63b781a1a610cbbb013bb50aaf1 100644 (file)
@@ -28,6 +28,7 @@
 
 namespace Dali
 {
+class VertexBufferUpdateCallback;
 namespace Internal
 {
 namespace Render
@@ -80,6 +81,16 @@ public:
    */
   void SetData(Dali::Vector<uint8_t>* data, uint32_t size);
 
+  /**
+   * @brief Sets vertex buffer update callback
+   *
+   * This function takes ownership over the callback object.
+   *
+   * The callback will run during rendering on the update/render thread.
+   * @param[in] callback Valid update callback
+   */
+  void SetVertexBufferUpdateCallback(Dali::VertexBufferUpdateCallback* callback);
+
   /**
    * Perform the upload of the buffer only when required
    * @param[in] graphicsController The controller
@@ -147,6 +158,16 @@ public:
     return mSize;
   }
 
+  /**
+   * Retrieves number of renderable elements when vertex update callback
+   * is used. If there is no callback set the total number of elements
+   * is returned.
+   */
+  [[nodiscard]] inline uint32_t GetRenderableElementCount() const
+  {
+    return mVertexBufferUpdateCallback ? mElementCount : mSize;
+  }
+
   [[nodiscard]] inline const VertexBuffer::Format* GetFormat() const
   {
     return mFormat.Get();
@@ -162,9 +183,12 @@ private:
   OwnerPointer<Dali::Vector<uint8_t> > mData;      ///< Data
   OwnerPointer<GpuBuffer>              mGpuBuffer; ///< Pointer to the GpuBuffer associated with this RenderVertexBuffer
 
-  uint32_t mSize;        ///< Number of Elements in the buffer
-  uint32_t mDivisor{0};  ///< The divisor (0:not instanced, >=1:instanced)
-  bool     mDataChanged; ///< Flag to know if data has changed in a frame
+  uint32_t                                          mSize;         ///< Number of Elements in the buffer
+  uint32_t                                          mDivisor{0};   ///< The divisor (0:not instanced, >=1:instanced)
+  uint32_t                                          mElementCount; ///< Number of valid elements in the buffer
+  std::unique_ptr<Dali::VertexBufferUpdateCallback> mVertexBufferUpdateCallback;
+
+  bool mDataChanged; ///< Flag to know if data has changed in a frame
 };
 
 } // namespace Render
index d7437f188d09930b83deb93385d17d0ca2d04839..03659c3ac81f0c4f25a0dda91028226f58ba5d44 100644 (file)
@@ -1407,6 +1407,18 @@ void UpdateManager::SetVertexBufferDivisor(Render::VertexBuffer* vertexBuffer, u
   new(slot) LocalType(vertexBuffer, &Render::VertexBuffer::SetDivisor, divisor);
 }
 
+void UpdateManager::SetVertexBufferUpdateCallback(Render::VertexBuffer* vertexBuffer, Dali::VertexBufferUpdateCallback* callback)
+{
+  // Message has ownership of format while in transit from update -> render
+  using DerivedType = MessageValue2<RenderManager, Render::VertexBuffer*, Dali::VertexBufferUpdateCallback*>;
+
+  // Reserve some memory inside the render queue
+  uint32_t* slot = mImpl->renderQueue.ReserveMessageSlot(mSceneGraphBuffers.GetUpdateBufferIndex(), sizeof(DerivedType));
+
+  // Construct message in the render queue memory; note that delete should not be called on the return value
+  new(slot) DerivedType(&mImpl->renderManager, &RenderManager::SetVertexBufferUpdateCallback, vertexBuffer, callback);
+}
+
 void UpdateManager::AddGeometry(OwnerPointer<Render::Geometry>& geometry)
 {
   // Message has ownership of format while in transit from update -> render
index 674a603fbf6010fa67a57f13b7497ea00da42484..b42bc151a5fa9a264646532ca3ca854d460c08a6 100644 (file)
@@ -483,6 +483,12 @@ public:
    * @param[in] divisor The instance divisor. 0 to turn instancing off.
    */
   void SetVertexBufferDivisor(Render::VertexBuffer* vertexBuffer, uint32_t divisor);
+  /**
+   * Sets vertex buffer update callback
+   * @param[in] vertexBuffer
+   * @param[in] callback
+   */
+  void SetVertexBufferUpdateCallback(Render::VertexBuffer* vertexBuffer, Dali::VertexBufferUpdateCallback* callback);
 
   /**
    * Adds a geometry to the RenderManager
@@ -1283,6 +1289,18 @@ inline void SetVertexBufferDivisorMessage(UpdateManager& manager, Render::Vertex
   new(slot) LocalType(&manager, &UpdateManager::SetVertexBufferDivisor, &vertexBuffer, divisor);
 }
 
+inline void SetVertexBufferUpdateCallback(UpdateManager& manager, Render::VertexBuffer& vertexBuffer, Dali::VertexBufferUpdateCallback* callback)
+{
+  // Message has ownership of VertexBuffer data while in transit from event -> update
+  using LocalType = MessageValue2<UpdateManager, Render::VertexBuffer*, Dali::VertexBufferUpdateCallback*>;
+
+  // Reserve some memory inside the message queue
+  uint32_t* slot = manager.ReserveMessageSlot(sizeof(LocalType));
+
+  // Construct message in the message queue memory; note that delete should not be called on the return value
+  new(slot) LocalType(&manager, &UpdateManager::SetVertexBufferUpdateCallback, &vertexBuffer, callback);
+}
+
 inline void AddGeometry(UpdateManager& manager, OwnerPointer<Render::Geometry>& geometry)
 {
   // Message has ownership of Geometry while in transit from event -> update
index 85b8bb34d2f22deda2114b0ba5faf04177a03ca4..69ac95a80e935655bb9f069b9e0180ff699d923d 100644 (file)
 #include <dali/internal/event/rendering/vertex-buffer-impl.h> // Dali::Internal::VertexBuffer
 #include <dali/public-api/object/property-map.h>              // Dali::Property::Map
 
+namespace Dali
+{
+struct VertexBufferUpdateCallback::Impl
+{
+  Impl(CallbackBase* callback)
+  : mCallback(callback)
+  {
+  }
+
+  uint32_t Invoke(void* data, size_t size)
+  {
+    return CallbackBase::ExecuteReturn<uint32_t>(*mCallback, data, size);
+  }
+
+  std::unique_ptr<CallbackBase> mCallback;
+};
+
+VertexBufferUpdateCallback::~VertexBufferUpdateCallback() = default;
+
+std::unique_ptr<VertexBufferUpdateCallback> VertexBufferUpdateCallback::New(CallbackBase* callbackBase)
+{
+  std::unique_ptr<VertexBufferUpdateCallback> retval;
+  auto                                        impl = std::make_unique<Impl>(callbackBase);
+  retval.reset(new VertexBufferUpdateCallback(std::move(impl)));
+  return retval;
+}
+
+VertexBufferUpdateCallback::VertexBufferUpdateCallback(std::unique_ptr<VertexBufferUpdateCallback::Impl>&& impl)
+: mImpl(std::move(impl))
+{
+}
+
+uint32_t VertexBufferUpdateCallback::Invoke(void* data, size_t size)
+{
+  return mImpl->Invoke(data, size);
+}
+
+} // namespace Dali
+
 namespace Dali
 {
 VertexBuffer VertexBuffer::New(Dali::Property::Map& bufferFormat)
@@ -68,6 +107,11 @@ uint32_t VertexBuffer::GetDivisor() const
   return GetImplementation(*this).GetDivisor();
 }
 
+void VertexBuffer::SetVertexBufferUpdateCallback(std::unique_ptr<VertexBufferUpdateCallback>&& updateCallback)
+{
+  GetImplementation(*this).SetVertexBufferUpdateCallback(*updateCallback.release());
+}
+
 VertexBuffer::VertexBuffer(Internal::VertexBuffer* pointer)
 : BaseHandle(pointer)
 {
index 7300a38f02b2924d7f0497a4fcfeb7ed75f2d0c0..c8dad495a219830307cc50c7f7f5eaf18503e970 100644 (file)
@@ -38,6 +38,69 @@ namespace Internal DALI_INTERNAL
 class VertexBuffer;
 }
 
+/**
+ * @class VertexBufferUpdateCallback
+ *
+ * The class defines a callback that VertexBuffer object may call
+ * to obtain new data. The callback runs before draw call is issued
+ * and it will run on update/render thread (developers must synchronize
+ * explicitly).
+ *
+ * Callback returns number of bytes written. This will limit next draw call
+ * to number of elements that have been written by the callback.
+ *
+ * Using callback invalidates current vertex buffer data. Unchanged data
+ * stays undefined.
+ */
+class DALI_CORE_API VertexBufferUpdateCallback
+{
+public:
+  /**
+   * @brief Destructor
+   */
+  ~VertexBufferUpdateCallback();
+
+  /**
+   * @brief Callback functor signature:
+   *
+   * uint32_t T::*(void* pointer, size_t dataSizeInBytes)
+   */
+  template<class T>
+  using FuncType = uint32_t (T::*)(void*, size_t);
+
+  /**
+   * @brief Creates a new instance of VertexBufferUpdateCallback
+   *
+   * @tparam T class the functor is a member of
+   * @param[in] object Object associated with callback
+   * @param[in] functor Member function to be executed
+   *
+   * @return Returns valid VertexBufferUpdateCallback object
+   */
+  template<class T>
+  static std::unique_ptr<VertexBufferUpdateCallback> New(T* object, FuncType<T> functor)
+  {
+    auto callback = Dali::MakeCallback(object, functor);
+    return New(callback);
+  }
+
+  /**
+   * @brief Invokes callback directly
+   * @param[in] data pointer to write into
+   * @param[in] size size of region in bytes
+   *
+   * @return Number of bytes written
+   */
+  uint32_t Invoke(void* data, size_t size);
+
+private:
+  static std::unique_ptr<VertexBufferUpdateCallback> New(CallbackBase* callbackBase);
+
+  struct Impl;
+  explicit VertexBufferUpdateCallback(std::unique_ptr<VertexBufferUpdateCallback::Impl>&& impl);
+  std::unique_ptr<Impl> mImpl;
+};
+
 /**
  * @brief VertexBuffer is a handle to an object that contains a buffer of structured data.
  *
@@ -192,6 +255,18 @@ public:
    */
   uint32_t GetDivisor() const;
 
+  /**
+   * @brief Sets VertexBufferUpdateCallback
+   *
+   * Function takes over the ownership over the callback.
+   *
+   * Developers must make sure the lifetime of used objects within the callback
+   * will remain valid as long as the callback exists.
+   *
+   * @param[in] updateCallback Valid VertexBufferUpdateCallback object
+   */
+  void SetVertexBufferUpdateCallback(std::unique_ptr<VertexBufferUpdateCallback>&& updateCallback);
+
 public:
   /**
    * @brief The constructor.