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
#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)
{
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;
+}
return mDivisor;
}
+void VertexBuffer::SetVertexBufferUpdateCallback(VertexBufferUpdateCallback& callback)
+{
+ SceneGraph::SetVertexBufferUpdateCallback(mEventThreadServices.GetUpdateManager(), *mRenderObject, &callback);
+}
+
const Render::VertexBuffer* VertexBuffer::GetRenderObject() const
{
return mRenderObject;
*/
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
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);
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
* @param[in] data A vector containing the indices
// 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>
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;
namespace Dali
{
+class VertexBufferUpdateCallback;
+
namespace Internal
{
/**
~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.
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
*/
}
//@todo Figure out why this is being drawn without geometry having been uploaded
}
- if(buffers.empty())
+ if(buffers.empty() || buffers.size() != vertexBufferCount)
{
return false;
}
// 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;
}
mData(nullptr),
mGpuBuffer(nullptr),
mSize(0),
+ mElementCount(0),
mDataChanged(true)
{
}
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;
}
}
// 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;
}
namespace Dali
{
+class VertexBufferUpdateCallback;
namespace Internal
{
namespace Render
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
*/
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();
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
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
* @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
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
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)
{
Internal::VertexBufferPtr vertexBuffer = Internal::VertexBuffer::New(bufferFormat);
return GetImplementation(*this).GetDivisor();
}
+void VertexBuffer::SetVertexBufferUpdateCallback(std::unique_ptr<VertexBufferUpdateCallback>&& updateCallback)
+{
+ GetImplementation(*this).SetVertexBufferUpdateCallback(*updateCallback.release());
+}
+
VertexBuffer::VertexBuffer(Internal::VertexBuffer* pointer)
: BaseHandle(pointer)
{
}
/**
+ * @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.
*
* VertexBuffers can be used to provide data to Geometry objects.
*/
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.