Basic automatic instancing.

This commit is contained in:
Mathias Agopian
2022-06-10 09:27:07 -07:00
committed by Mathias Agopian
parent 6bc0e032fc
commit eda2b7955d
2 changed files with 142 additions and 11 deletions

View File

@@ -21,8 +21,6 @@
#include "details/Material.h"
#include "details/MaterialInstance.h"
// NOTE: We only need Renderer.h here because the definition of some FRenderer methods are here
#include "details/Renderer.h"
#include "details/View.h"
#include <private/filament/UibStructs.h>
@@ -186,8 +184,128 @@ void RenderPass::sortCommands() noexcept {
});
resize(uint32_t(last - mCommandBegin));
instanceify();
}
void RenderPass::instanceify() noexcept {
SYSTRACE_NAME("instanceify");
// instanceify works by scanning the **sorted** command stream, looking for repeat draw
// commands. When one is found, it is replaced by an instanced command.
// A "repeat" draw is one that ends-up using the same draw parameters and state.
// Currently, this relies somewhat on luck that "repeat draws" are found consecutively,
// we could improve this by including some or all of these "repeat" parameters in the
// sorting key (e.g. raster state, primitive handle, etc...), the key could even use a small
// hash of those parameters.
UTILS_UNUSED_IN_RELEASE uint32_t drawCallsSavedCount = 0;
Command* curr = mCommandBegin;
Command* const last = mCommandEnd;
Command* firstSentinel = nullptr;
PerRenderableData const* uboData = nullptr;
PerRenderableData* stagingBuffer = nullptr;
uint32_t stagingBufferSize = 0;
uint32_t instancedPrimitiveOffset = 0;
// TODO: for the case of instancing we could actually use 128 instead of 64 instances
constexpr size_t maxInstanceCount = sizeof(PerRenderableUib) / sizeof(PerRenderableData);
static_assert(maxInstanceCount == 64); // just to make sure we don't change by accident
while (curr != last) {
// we can't have nice things! No more than maxInstanceCount due to UBO size limits
Command const* const e = std::find_if_not(curr, std::min(last, last + maxInstanceCount),
[lhs = *curr](Command const& rhs) {
// primitives must be identical to be instanced. Currently, instancing doesn't support
// skinning/morphing.
return lhs.primitive.mi == rhs.primitive.mi &&
lhs.primitive.primitiveHandle == rhs.primitive.primitiveHandle &&
lhs.primitive.rasterState == rhs.primitive.rasterState &&
lhs.primitive.skinningHandle == rhs.primitive.skinningHandle &&
lhs.primitive.skinningOffset == rhs.primitive.skinningOffset &&
lhs.primitive.morphWeightBuffer == rhs.primitive.morphWeightBuffer &&
lhs.primitive.morphTargetBuffer == rhs.primitive.morphTargetBuffer;
});
uint32_t instanceCount = e - curr;
assert_invariant(instanceCount > 0);
if (UTILS_UNLIKELY(instanceCount > 1)) {
drawCallsSavedCount += instanceCount - 1;
// allocate our staging buffer only if needed
if (UTILS_UNLIKELY(!stagingBuffer)) {
// TODO: use stream inline buffer for small sizes
// TODO: use a pool for larger heap buffers
// buffer large enough for all instances data
stagingBufferSize = sizeof(PerRenderableData) * (last - curr);
stagingBuffer = (PerRenderableData*)::malloc(stagingBufferSize);
uboData = mRenderableSoa->data<FScene::UBO>();
}
// copy the ubo data to a staging buffer
assert_invariant(instancedPrimitiveOffset + instanceCount
<= stagingBufferSize / sizeof(PerRenderableData));
for (uint32_t i = 0; i < instanceCount; i++) {
stagingBuffer[instancedPrimitiveOffset + i] = uboData[curr[i].primitive.index];
}
// make the first command instanced
curr[0].primitive.instanceCount = instanceCount;
curr[0].primitive.index = instancedPrimitiveOffset;
instancedPrimitiveOffset += instanceCount;
// cancel commands that are now instances
firstSentinel = !firstSentinel ? curr : firstSentinel;
for (uint32_t i = 1; i < instanceCount; i++) {
curr[i].key = uint64_t(Pass::SENTINEL);
}
}
curr = const_cast<Command*>(e);
}
if (UTILS_UNLIKELY(firstSentinel)) {
#ifndef NDEBUG
// TODO: remove this eventually
slog.d << "auto-instancing, saving " << drawCallsSavedCount << " draw calls, out of "
<< mCommandEnd - mCommandBegin << io::endl;
#endif
// we have instanced primitives
DriverApi& driver = mEngine.getDriverApi();
// TODO: maybe use a pool? so we can reuse the buffer.
// create a ubo to hold the instanced primitive data
mInstancedUboHandle = driver.createBufferObject(
sizeof(PerRenderableData) * instancedPrimitiveOffset + sizeof(PerRenderableUib),
BufferObjectBinding::UNIFORM, backend::BufferUsage::STATIC);
// copy our instanced ubo data
driver.updateBufferObjectUnsynchronized(mInstancedUboHandle, {
stagingBuffer, sizeof(PerRenderableData) * instancedPrimitiveOffset,
+[](void* buffer, size_t size, void* user) {
::free(buffer);
}
}, 0);
stagingBuffer = nullptr;
// remove all the canceled commands
auto lastCommand = std::remove_if(firstSentinel, mCommandEnd, [](auto const& command) {
return command.key == uint64_t(Pass::SENTINEL);
});
resize(uint32_t(lastCommand - mCommandBegin));
}
assert_invariant(stagingBuffer == nullptr);
}
/* static */
UTILS_ALWAYS_INLINE // this function exists only to make the code more readable. we want it inlined.
inline // and we don't need it in the compilation unit
@@ -601,7 +719,6 @@ void RenderPass::Executor::recordDriverCommands(FEngine& engine, backend::Driver
Handle<HwBufferObject> uboHandle = mUboHandle;
FMaterialInstance const* UTILS_RESTRICT mi = nullptr;
FMaterial const* UTILS_RESTRICT ma = nullptr;
uint32_t uboDataIndex = std::numeric_limits<uint32_t>::max();
auto customCommands = mCustomCommands.data();
first--;
@@ -639,12 +756,12 @@ void RenderPass::Executor::recordDriverCommands(FEngine& engine, backend::Driver
pipeline.program = ma->getProgram(info.materialVariant);
if (uboDataIndex != info.index) {
uboDataIndex = info.index;
driver.bindUniformBufferRange(BindingPoints::PER_RENDERABLE,
uboHandle, uboDataIndex * sizeof(PerRenderableData),
sizeof(PerRenderableUib));
}
// bind per-renderable uniform block. there is no need to attempt to skip this command
// because the backends already do this.
driver.bindUniformBufferRange(BindingPoints::PER_RENDERABLE,
(info.instanceCount > 1) ? mInstancedUboHandle : uboHandle,
info.index * sizeof(PerRenderableData),
sizeof(PerRenderableUib));
if (UTILS_UNLIKELY(info.skinningHandle)) {
// note: we can't bind less than sizeof(PerRenderableBoneUib) due to glsl limitations
@@ -669,13 +786,20 @@ void RenderPass::Executor::recordDriverCommands(FEngine& engine, backend::Driver
driver.draw(pipeline, info.primitiveHandle, info.instanceCount);
}
}
if (mInstancedUboHandle) {
driver.destroyBufferObject(mInstancedUboHandle);
}
}
// ------------------------------------------------------------------------------------------------
RenderPass::Executor::Executor(RenderPass const* pass, Command const* b, Command const* e) noexcept
: mEngine(pass->mEngine), mBegin(b), mEnd(e),
mCustomCommands(pass->mCustomCommands), mUboHandle(pass->mUboHandle),
mCustomCommands(pass->mCustomCommands),
mUboHandle(pass->mUboHandle),
mInstancedUboHandle(pass->mInstancedUboHandle),
mPolygonOffset(pass->mPolygonOffset),
mScissor(pass->mScissor),
mPolygonOffsetOverride(pass->mPolygonOffsetOverride),

View File

@@ -54,6 +54,10 @@ public:
* t = two-pass transparency ordering
* 0 = reserved, must be zero
*
*
* TODO: we need to add a "primitive id" in the low-bits of material-id, so that
* auto-instancing can work better
*
* DEPTH command
* | 6 | 2| 2|1| 3 | 2| 6 | 10 | 32 |
* +------+--+--+-+---+--+------+----------+--------------------------------+
@@ -305,7 +309,7 @@ public:
// the current camera, geometry and flags set. This can be called multiple times if needed.
void appendCommands(CommandTypeFlags commandTypeFlags) noexcept;
// sorts commands, then trims sentinels
// sorts and instancify commands then trims sentinels
void sortCommands() noexcept;
// Helper to execute all the commands generated by this RenderPass
@@ -329,6 +333,7 @@ public:
Command const* mEnd;
const CustomCommandVector mCustomCommands;
const backend::Handle<backend::HwBufferObject> mUboHandle;
const backend::Handle<backend::HwBufferObject> mInstancedUboHandle;
const backend::PolygonOffset mPolygonOffset;
const backend::Viewport mScissor;
const bool mPolygonOffsetOverride : 1;
@@ -367,6 +372,7 @@ private:
Command* append(size_t count) noexcept;
void resize(size_t count) noexcept;
void instanceify() noexcept;
// on 64-bits systems, we process batches of 256 (64 bytes) cache-lines, or 512 (32 bytes) commands
// on 32-bits systems, we process batches of 512 (32 bytes) cache-lines, or 512 (32 bytes) commands
@@ -415,6 +421,7 @@ private:
// the UBO containing the data for the renderables
backend::Handle<backend::HwBufferObject> mUboHandle;
backend::Handle<backend::HwBufferObject> mInstancedUboHandle;
// info about the camera
math::float3 mCameraPosition{};