Skip to content

Commit

Permalink
#5912: Extend IGeometryStore interface to allow for index remaps. It …
Browse files Browse the repository at this point in the history
…allows existing slots containing vertices to be re-used with a custom set of indices.
  • Loading branch information
codereader committed Mar 5, 2022
1 parent 566d3bb commit 0240911
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 0 deletions.
37 changes: 37 additions & 0 deletions include/igeometrystore.h
Expand Up @@ -60,6 +60,21 @@ class IGeometryStore
*/
virtual Slot allocateSlot(std::size_t numVertices, std::size_t numIndices) = 0;

/**
* Allocate memory to store an alternative set of indices referencing
* an existing set of vertices. When rendering this re-mapped geometry, it will
* re-use the vertices of that other slot with the indices defined in this slot.
*
* With the returned handle, user code can invoke the update[Sub]Data() methods
* to upload the indices, but the passed vertex set has to be empty.
*
* Use the regular deallocate() method to release this slot.
*
* The index remap slot is depending on the one containing the vertex data, if the latter
* is removed, this slot becomes invalid and behaviour is undefined.
*/
virtual Slot allocateIndexSlot(Slot slotContainingVertexData, std::size_t numIndices) = 0;

/**
* Load vertex and index data into the specified block. The given vertex and
* index arrays must not be larger than what has been allocated earlier,
Expand All @@ -68,6 +83,15 @@ class IGeometryStore
virtual void updateData(Slot slot, const std::vector<MeshVertex>& vertices,
const std::vector<unsigned int>& indices) = 0;

/**
* Updates the data of an index slot. Equivalent to calling updateData() with
* an empty set of vertices.
*/
virtual void updateIndexData(Slot slot, const std::vector<unsigned int>& indices)
{
updateData(slot, {}, indices);
}

/**
* 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
Expand All @@ -76,11 +100,24 @@ class IGeometryStore
virtual void updateSubData(Slot slot, std::size_t vertexOffset, const std::vector<MeshVertex>& vertices,
std::size_t indexOffset, const std::vector<unsigned int>& indices) = 0;

/**
* Updates a portion of index data in an index slot. Equivalent to calling updateSubData() with
* an empty set of vertices.
*/
virtual void updateIndexSubData(Slot slot, std::size_t indexOffset, const std::vector<unsigned int>& indices)
{
updateSubData(slot, 0, {}, indexOffset, indices);
}

/**
* Called in case the stored data in the given slot should just be cut off at the end.
*/
virtual void resizeData(Slot slot, std::size_t vertexSize, std::size_t indexSize) = 0;

/**
* Releases the memory allocated by the given slot.
* The Slot ID is invalidated by this operation and should no longer be used.
*/
virtual void deallocateSlot(Slot slot) = 0;

// The render parameters suitable for rendering surfaces using gl(Multi)DrawElements
Expand Down
5 changes: 5 additions & 0 deletions libs/render/GeometryStore.h
Expand Up @@ -97,6 +97,11 @@ class GeometryStore :
return slot;
}

Slot allocateIndexSlot(Slot slotContainingVertexData, std::size_t numIndices) override
{
return std::numeric_limits<Slot>::max();
}

void updateData(Slot slot, const std::vector<MeshVertex>& vertices,
const std::vector<unsigned int>& indices) override
{
Expand Down
124 changes: 124 additions & 0 deletions test/GeometryStore.cpp
@@ -1,6 +1,7 @@
#include "gtest/gtest.h"

#include <limits>
#include <numeric>
#include <random>
#include "render/GeometryStore.h"

Expand Down Expand Up @@ -465,4 +466,127 @@ TEST(GeometryStore, SyncObjectAcquisition)
"GeometryStore should have performed 5 frame buffer switches";
}

TEST(GeometryStore, AllocateIndexRemap)
{
render::GeometryStore store(NullSyncObjectProvider::Instance());

// Allocate a slot to hold indexed vertices
auto vertices = generateVertices(3, 15 * 20);
auto indices = generateIndices(vertices);

auto primarySlot = store.allocateSlot(vertices.size(), indices.size());
EXPECT_NE(primarySlot, std::numeric_limits<render::IGeometryStore::Slot>::max()) << "Invalid slot";

// Uploading the data should succeed
EXPECT_NO_THROW(store.updateData(primarySlot, vertices, indices));

auto secondarySlot = store.allocateIndexSlot(primarySlot, 20);
EXPECT_NE(secondarySlot, std::numeric_limits<render::IGeometryStore::Slot>::max()) << "Invalid slot";

// Deallocation through the regular method should succeed
EXPECT_NO_THROW(store.deallocateSlot(secondarySlot));
}

TEST(GeometryStore, AllocateInvalidIndexRemap)
{
render::GeometryStore store(NullSyncObjectProvider::Instance());

// Allocate a slot to hold indexed vertices
auto vertices = generateVertices(3, 15 * 20);
auto indices = generateIndices(vertices);

auto primarySlot = store.allocateSlot(vertices.size(), indices.size());
EXPECT_NE(primarySlot, std::numeric_limits<render::IGeometryStore::Slot>::max()) << "Invalid slot";

// This allocation is valid and will be a remap type
auto secondarySlot = store.allocateIndexSlot(primarySlot, 20);
EXPECT_NE(secondarySlot, std::numeric_limits<render::IGeometryStore::Slot>::max()) << "Invalid slot";

// This call is not valid and should throw, since the secondary slot cannot be re-used
EXPECT_THROW(store.allocateIndexSlot(secondarySlot, 10), std::logic_error);

// Trying to reference a non-existing slot should throw as well
EXPECT_THROW(store.allocateIndexSlot(56756756, 10), std::logic_error);
}

TEST(GeometryStore, UpdateIndexRemapData)
{
render::GeometryStore store(NullSyncObjectProvider::Instance());

// Allocate a slot to hold indexed vertices
auto vertices = generateVertices(3, 15 * 20);
auto indices = generateIndices(vertices);

auto primarySlot = store.allocateSlot(vertices.size(), indices.size());
EXPECT_NE(primarySlot, std::numeric_limits<render::IGeometryStore::Slot>::max()) << "Invalid slot";
EXPECT_NO_THROW(store.updateData(primarySlot, vertices, indices));

// Now allocate an index remapping slot, containing a straight, sequential set of indices 0..n-1
std::vector<unsigned int> remap;
remap.resize(vertices.size());
std::iota(remap.begin(), remap.end(), 0);

auto secondarySlot = store.allocateIndexSlot(primarySlot, remap.size());
EXPECT_NE(secondarySlot, std::numeric_limits<render::IGeometryStore::Slot>::max()) << "Invalid slot";

// Update the index data through updateData()
EXPECT_NO_THROW(store.updateData(secondarySlot, {}, remap));

// The render params should effectively point us the re-used vertices, in remapped order
verifyAllocation(store, secondarySlot, vertices, remap);

// Reverse the index order for testing the second way of uploading data
std::reverse(remap.begin(), remap.end());
EXPECT_NO_THROW(store.updateIndexData(secondarySlot, remap));

verifyAllocation(store, secondarySlot, vertices, remap);

// We expect an exception when trying to store vertex data
EXPECT_THROW(store.updateData(secondarySlot, vertices, remap), std::logic_error);
}

TEST(GeometryStore, UpdateIndexRemapSubData)
{
render::GeometryStore store(NullSyncObjectProvider::Instance());

// Allocate a slot to hold indexed vertices
auto vertices = generateVertices(3, 15 * 20);
auto indices = generateIndices(vertices);

auto primarySlot = store.allocateSlot(vertices.size(), indices.size());
EXPECT_NE(primarySlot, std::numeric_limits<render::IGeometryStore::Slot>::max()) << "Invalid slot";
EXPECT_NO_THROW(store.updateData(primarySlot, vertices, indices));

// Now allocate an index remapping slot, containing a straight, sequential set of indices 0..n-1
std::vector<unsigned int> remap;
remap.resize(vertices.size());
std::iota(remap.begin(), remap.end(), 0);

auto secondarySlot = store.allocateIndexSlot(primarySlot, remap.size());
EXPECT_NE(secondarySlot, std::numeric_limits<render::IGeometryStore::Slot>::max()) << "Invalid slot";

// Upload the sequential set of indices
EXPECT_NO_THROW(store.updateIndexData(secondarySlot, remap));
verifyAllocation(store, secondarySlot, vertices, remap);

// Generate a new set of indices, and make it half as large than the original remap
std::vector<unsigned int> indexSubset;
indexSubset.resize(remap.size() / 2);
std::iota(indexSubset.begin(), indexSubset.end(), 0); // [0..N-1]
std::reverse(indexSubset.begin(), indexSubset.end()); // make it [N-1...0]

// Apply the subset to the local remap copy
auto offset = indexSubset.size() / 4;
std::copy(indexSubset.begin(), indexSubset.end(), remap.begin() + offset);

// Apply the subset to the data in the store
EXPECT_NO_THROW(store.updateIndexSubData(secondarySlot, offset, indexSubset));

// The new subset should now be used when effective
verifyAllocation(store, secondarySlot, vertices, remap);

// We expect boundaries to be respected, this should be out of range
EXPECT_THROW(store.updateIndexSubData(secondarySlot, remap.size() - 1, indexSubset), std::logic_error);
}

}

0 comments on commit 0240911

Please sign in to comment.