diff --git a/include/igeometrystore.h b/include/igeometrystore.h index 5adf86e00c..7e6b0a78ad 100644 --- a/include/igeometrystore.h +++ b/include/igeometrystore.h @@ -45,6 +45,14 @@ class IGeometryStore virtual void updateData(Slot slot, const std::vector& vertices, const std::vector& indices) = 0; + /** + * Load a chunk of vertex and index data into the specified range, starting + * from vertexOffset/indexOffset respectively. The affected range must not be out of bounds + * of the allocated slot. + */ + virtual void updateSubData(Slot slot, std::size_t vertexOffset, const std::vector& vertices, + std::size_t indexOffset, const std::vector& indices) = 0; + virtual void deallocateSlot(Slot slot) = 0; // The render parameters suitable for rendering surfaces using gl(Multi)DrawElements diff --git a/libs/render/ContinuousBuffer.h b/libs/render/ContinuousBuffer.h index deb34f1b30..250eacb0d8 100644 --- a/libs/render/ContinuousBuffer.h +++ b/libs/render/ContinuousBuffer.h @@ -144,6 +144,20 @@ class ContinuousBuffer slot.Used = numElements; } + void setSubData(Handle handle, std::size_t elementOffset, const std::vector& elements) + { + auto& slot = _slots[handle]; + + auto numElements = elements.size(); + if (elementOffset + numElements > slot.Size) + { + throw std::logic_error("Cannot store more data than allocated in GeometryStore::Buffer::setSubData"); + } + + std::copy(elements.begin(), elements.end(), _buffer.begin() + slot.Offset + elementOffset); + slot.Used = std::max(slot.Used, elementOffset + numElements); + } + void deallocate(Handle handle) { auto& releasedSlot = _slots[handle]; diff --git a/radiantcore/rendersystem/backend/GeometryStore.h b/radiantcore/rendersystem/backend/GeometryStore.h index 6c26a3923f..c5a1d6c00d 100644 --- a/radiantcore/rendersystem/backend/GeometryStore.h +++ b/radiantcore/rendersystem/backend/GeometryStore.h @@ -125,6 +125,22 @@ class GeometryStore : }); } + void updateSubData(Slot slot, std::size_t vertexOffset, const std::vector& vertices, + std::size_t indexOffset, const std::vector& indices) override + { + assert(!vertices.empty()); + assert(!indices.empty()); + + auto& current = getCurrentBuffer(); + + current.vertices.setSubData(GetVertexSlot(slot), vertexOffset, vertices); + current.indices.setSubData(GetIndexSlot(slot), indexOffset, indices); + + _transactionLog.emplace_back(detail::BufferTransaction{ + slot, detail::BufferTransaction::Type::Update + }); + } + void deallocateSlot(Slot slot) override { auto& current = getCurrentBuffer(); diff --git a/test/ContinuousBuffer.cpp b/test/ContinuousBuffer.cpp index 95df196381..1b025a11d8 100644 --- a/test/ContinuousBuffer.cpp +++ b/test/ContinuousBuffer.cpp @@ -132,6 +132,82 @@ TEST(ContinuousBufferTest, ReplaceDataOverflow) EXPECT_THROW(buffer.setData(handle, eight), std::logic_error); } +TEST(ContinuousBufferTest, ReplaceSubData) +{ + auto eight = std::vector({ 0,1,2,3,4,5,6,7 }); + auto five = std::vector({ 8,9,10,11,12 }); + + render::ContinuousBuffer buffer(24); + + auto handle = buffer.allocate(eight.size()); + buffer.setData(handle, eight); + + EXPECT_TRUE(checkData(buffer, handle, eight)); + + EXPECT_EQ(buffer.getSize(handle), eight.size()); + EXPECT_EQ(buffer.getNumUsedElements(handle), eight.size()); + + // Update the data portion at various offsets + buffer.setSubData(handle, 0, five); + EXPECT_TRUE(checkData(buffer, handle, { 8,9,10,11,12, 5,6,7 })); + EXPECT_EQ(buffer.getNumUsedElements(handle), 8); + + buffer.setSubData(handle, 1, five); + EXPECT_TRUE(checkData(buffer, handle, { 8, 8,9,10,11,12, 6,7 })); + EXPECT_EQ(buffer.getNumUsedElements(handle), 8); + + buffer.setSubData(handle, 2, five); + EXPECT_TRUE(checkData(buffer, handle, { 8,8, 8,9,10,11,12, 7 })); + EXPECT_EQ(buffer.getNumUsedElements(handle), 8); + + buffer.setSubData(handle, 3, five); + EXPECT_TRUE(checkData(buffer, handle, { 8,8,8, 8,9,10,11,12 })); + EXPECT_EQ(buffer.getNumUsedElements(handle), 8); +} + +// In a chunk of memory that is only partially used, calling setSubData +// can increase that amount of used elements +TEST(ContinuousBufferTest, ReplaceSubDataIncreasesUsedSize) +{ + auto eight = std::vector({ 0,1,2,3,4,5,6,7 }); + auto five = std::vector({ 8,9,10,11,12 }); + + render::ContinuousBuffer buffer(24); + + // Allocate 6 more elements than needed + auto allocationSize = eight.size() + 6; + auto handle = buffer.allocate(allocationSize); + buffer.setData(handle, eight); + + EXPECT_TRUE(checkData(buffer, handle, eight)); + + EXPECT_EQ(buffer.getSize(handle), allocationSize); + EXPECT_EQ(buffer.getNumUsedElements(handle), eight.size()); + + // Update the data portion at an offset that increases the fill rate + buffer.setSubData(handle, 4, five); + EXPECT_TRUE(checkData(buffer, handle, { 0,1,2,3,8,9,10,11,12 })); + EXPECT_EQ(buffer.getSize(handle), allocationSize); + EXPECT_EQ(buffer.getNumUsedElements(handle), 9) << "Should have increased use count to 9"; +} + +// Tests that subdata is still respecting the allocation bounds +TEST(ContinuousBufferTest, ReplaceSubDataOverflow) +{ + auto eight = std::vector({ 0,1,2,3,4,5,6,7 }); + auto five = std::vector({ 8,9,10,11,12 }); + + render::ContinuousBuffer buffer(24); + + auto handle = buffer.allocate(eight.size()); + buffer.setData(handle, eight); + + EXPECT_TRUE(checkData(buffer, handle, eight)); + + // Update the data portion at an invalid offset, exceeding the allocated slot size + EXPECT_THROW(buffer.setSubData(handle, 4, five), std::logic_error); +} + TEST(ContinuousBufferTest, BufferGrowth) { auto sixteen = std::vector({ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 });