Skip to content

Commit d4bbb7c

Browse files
authored
buffer update opt: Some optimizations (#9438)
1 parent 92e620d commit d4bbb7c

5 files changed

Lines changed: 107 additions & 118 deletions

File tree

filament/src/details/BufferAllocator.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "details/BufferAllocator.h"
1818

19+
#include <private/utils/Tracing.h>
1920
#include <utils/Panic.h>
2021
#include <utils/debug.h>
2122

@@ -169,6 +170,7 @@ void BufferAllocator::releaseGpu(AllocationId id) {
169170
}
170171

171172
void BufferAllocator::releaseFreeSlots() {
173+
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
172174
if (!mHasPendingFrees) {
173175
return;
174176
}

filament/src/details/Engine.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -718,8 +718,7 @@ void FEngine::prepare() {
718718

719719
if (useUboBatching) {
720720
assert_invariant(mUboManager != nullptr);
721-
722-
mUboManager->beginFrame(driver, mMaterialInstances);
721+
mUboManager->beginFrame(driver);
723722
}
724723

725724
UboManager* uboManager = mUboManager;
@@ -758,7 +757,7 @@ void FEngine::gc() {
758757
void FEngine::submitFrame() {
759758
if (isUboBatchingEnabled()) {
760759
DriverApi& driver = getDriverApi();
761-
getUboManager()->endFrame(driver, getMaterialInstanceResourceList());
760+
getUboManager()->endFrame(driver);
762761
}
763762
}
764763

@@ -1285,11 +1284,6 @@ UTILS_NOINLINE
12851284
bool FEngine::destroy(const FMaterialInstance* p) {
12861285
if (p == nullptr) return true;
12871286

1288-
if (p->isUsingUboBatching()) {
1289-
assert_invariant(isUboBatchingEnabled());
1290-
mUboManager->retireSlot(p->getAllocationId());
1291-
}
1292-
12931287
// Check that the material instance we're destroying is not in use in the RenderableManager
12941288
// To do this, we currently need to inspect all render primitives in the RenderableManager
12951289
EntityManager const& em = mEntityManager;

filament/src/details/MaterialInstance.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
9595

9696
if (mUseUboBatching) {
9797
mUboData = BufferAllocator::UNALLOCATED;
98+
engine.getUboManager()->manageMaterialInstance(this);
9899
} else {
99100
mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM,
100101
BufferUsage::STATIC, ImmutableCString{ material->getName().c_str_safe() });
@@ -167,6 +168,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
167168

168169
if (mUseUboBatching) {
169170
mUboData = BufferAllocator::UNALLOCATED;
171+
engine.getUboManager()->manageMaterialInstance(this);
170172
} else {
171173
mUboData = driver.createBufferObject(mUniforms.getSize(), BufferObjectBinding::UNIFORM,
172174
BufferUsage::DYNAMIC, ImmutableCString{ material->getName().c_str_safe() });
@@ -211,6 +213,10 @@ FMaterialInstance::~FMaterialInstance() noexcept = default;
211213
void FMaterialInstance::terminate(FEngine& engine) {
212214
FEngine::DriverApi& driver = engine.getDriverApi();
213215
mDescriptorSet.terminate(driver);
216+
if (mUseUboBatching) {
217+
engine.getUboManager()->unmanageMaterialInstance(this);
218+
}
219+
214220
auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData);
215221
if (ubHandle){
216222
driver.destroyBufferObject(*ubHandle);

filament/src/details/UboManager.cpp

Lines changed: 84 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ void UboManager::FenceManager::track(DriverApi& driver, std::unordered_set<Alloc
4848

4949
void UboManager::FenceManager::reclaimCompletedResources(DriverApi& driver,
5050
std::function<void(AllocationId)> const& onReclaimed) {
51+
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
5152
uint32_t signaledCount = 0;
5253
bool seenSignaledFence = false;
5354

@@ -115,8 +116,7 @@ UboManager::UboManager(DriverApi& driver, allocation_size_t defaultSlotSizeInByt
115116
reallocate(driver, defaultTotalSizeInBytes);
116117
}
117118

118-
void UboManager::beginFrame(DriverApi& driver,
119-
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& materialInstances) {
119+
void UboManager::beginFrame(DriverApi& driver) {
120120
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
121121
// Check finished frames and decrement GPU count accordingly.
122122
mFenceManager.reclaimCompletedResources(driver,
@@ -126,7 +126,7 @@ void UboManager::beginFrame(DriverApi& driver,
126126
mAllocator.releaseFreeSlots();
127127

128128
// Traverse all MIs and see which of them need slot allocation.
129-
if (allocateOnDemand(materialInstances) == SUCCESS) {
129+
if (allocateOnDemand() == SUCCESS) {
130130
// No need to grow the buffer, so we can just map the buffer for writing and return.
131131
mMemoryMappedBufferHandle = driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT,
132132
"UboManager");
@@ -135,25 +135,19 @@ void UboManager::beginFrame(DriverApi& driver,
135135
}
136136

137137
// Calculate the required size and grow the Ubo.
138-
const allocation_size_t requiredSize = calculateRequiredSize(materialInstances);
138+
const allocation_size_t requiredSize = calculateRequiredSize();
139139
reallocate(driver, requiredSize);
140140

141141
// Allocate slots for each MI on the new Ubo.
142-
allocateAllInstances(materialInstances);
142+
allocateAllInstances();
143143

144144
// Map the buffer so that we can write to it
145145
mMemoryMappedBufferHandle =
146146
driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT, "UboManager");
147147

148148
// Invalidate the migrated MIs, so that next commit() call must be triggered.
149-
for (const auto& materialInstance : materialInstances) {
150-
materialInstance.second.forEach([](const FMaterialInstance* mi) {
151-
if (!mi->isUsingUboBatching()) {
152-
return;
153-
}
154-
155-
mi->getUniformBuffer().invalidate();
156-
});
149+
for (const auto* mi : mManagedInstances) {
150+
mi->getUniformBuffer().invalidate();
157151
}
158152
}
159153

@@ -164,24 +158,16 @@ void UboManager::finishBeginFrame(DriverApi& driver) {
164158
}
165159
}
166160

167-
void UboManager::endFrame(DriverApi& driver,
168-
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& materialInstances) {
169-
BufferAllocator& allocator = mAllocator;
161+
void UboManager::endFrame(DriverApi& driver) {
170162
std::unordered_set<AllocationId> allocationIds;
171-
for (const auto& materialInstance : materialInstances) {
172-
materialInstance.second.forEach([&allocator, &allocationIds](const FMaterialInstance* mi) {
173-
if (!mi->isUsingUboBatching()) {
174-
return;
175-
}
176-
177-
const AllocationId id = mi->getAllocationId();
178-
if (!BufferAllocator::isValid(id)) {
179-
return;
180-
}
163+
for (const auto* mi : mManagedInstances) {
164+
const AllocationId id = mi->getAllocationId();
165+
if (UTILS_UNLIKELY(!BufferAllocator::isValid(id))) {
166+
continue;
167+
}
181168

182-
allocator.acquireGpu(id);
183-
allocationIds.insert(id);
184-
});
169+
mAllocator.acquireGpu(id);
170+
allocationIds.insert(id);
185171
}
186172

187173
mFenceManager.track(driver, std::move(allocationIds));
@@ -194,76 +180,91 @@ void UboManager::terminate(DriverApi& driver) {
194180

195181
void UboManager::updateSlot(DriverApi& driver, AllocationId id,
196182
BufferDescriptor bufferDescriptor) const {
197-
if (!mMemoryMappedBufferHandle)
183+
if (!mMemoryMappedBufferHandle) {
198184
return;
185+
}
199186

200187
const allocation_size_t offset = mAllocator.getAllocationOffset(id);
201188
driver.copyToMemoryMappedBuffer(mMemoryMappedBufferHandle, offset, std::move(bufferDescriptor));
202189
}
203190

204-
void UboManager::retireSlot(BufferAllocator::AllocationId id) {
205-
if (!BufferAllocator::isValid(id))
206-
return;
207-
mAllocator.retire(id);
191+
void UboManager::manageMaterialInstance(FMaterialInstance* instance) {
192+
mPendingInstances.insert(instance);
208193
}
209194

210-
UboManager::AllocationResult UboManager::allocateOnDemand(
211-
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
212-
materialInstances) {
213-
// Collect all MIs that need allocation into two groups.
214-
std::vector<FMaterialInstance*> newInstances;
215-
std::vector<FMaterialInstance*> existingInstances;
216-
for (const auto& [_, miList] : materialInstances) {
217-
miList.forEach([&](FMaterialInstance* mi) {
218-
if (!mi->isUsingUboBatching()) {
219-
return;
220-
}
221-
if (BufferAllocator::isValid(mi->getAllocationId())) {
222-
existingInstances.push_back(mi);
223-
} else {
224-
newInstances.push_back(mi);
225-
}
226-
});
195+
void UboManager::unmanageMaterialInstance(const FMaterialInstance* materialInstance) {
196+
AllocationId id = materialInstance->getAllocationId();
197+
// const_cast should be safe here since this cast is just to match the container type.
198+
auto mi = const_cast<FMaterialInstance*>(materialInstance);
199+
mPendingInstances.erase(mi);
200+
mManagedInstances.erase(mi);
201+
202+
if (!BufferAllocator::isValid(id)) {
203+
return;
227204
}
228205

206+
mAllocator.retire(id);
207+
}
208+
209+
UboManager::AllocationResult UboManager::allocateOnDemand() {
210+
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
229211
bool reallocationNeeded = false;
230212

231213
// Pass 1: Allocate slots for new material instances (that don't have a slot yet).
232-
for (FMaterialInstance* mi : newInstances) {
214+
for (auto* mi : mPendingInstances) {
215+
mManagedInstances.insert(mi);
233216
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
217+
218+
// Even if the newId is not valid, we assign it to the MI so that the following process knows
219+
// this material instance was not allocated successfully. Then we can calculate the new
220+
// required UBO size properly.
234221
mi->assignUboAllocation(mUbHandle, newId, newOffset);
222+
235223
if (!BufferAllocator::isValid(newId)) {
236224
reallocationNeeded = true;
237225
}
238226
}
227+
mPendingInstances.clear();
239228

240229
// Pass 2: Allocate slots for existing material instances that need to be orphaned.
241-
for (FMaterialInstance* mi : existingInstances) {
242-
if (mi->getUniformBuffer().isDirty() && mAllocator.isLockedByGpu(mi->getAllocationId())) {
243-
mAllocator.retire(mi->getAllocationId());
244-
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
245-
mi->assignUboAllocation(mUbHandle, newId, newOffset);
246-
if (!BufferAllocator::isValid(newId)) {
247-
reallocationNeeded = true;
248-
}
230+
for (auto* mi: mManagedInstances) {
231+
if (!BufferAllocator::isValid(mi->getAllocationId())) {
232+
continue;
233+
}
234+
235+
// This instance doesn't need orphaning.
236+
if (!mi->getUniformBuffer().isDirty() || !mAllocator.isLockedByGpu(mi->getAllocationId())) {
237+
continue;
238+
}
239+
240+
mAllocator.retire(mi->getAllocationId());
241+
242+
// If the space is already not sufficient, we don't need to give another try on allocation.
243+
if (reallocationNeeded) {
244+
mi->assignUboAllocation(mUbHandle, REALLOCATION_REQUIRED, 0);
245+
continue;
246+
}
247+
248+
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
249+
250+
// Even if the newId is not valid, we assign it to the MI so that the following process knows
251+
// this material instance was not allocated successfully. Then we can calculate the new
252+
// required UBO size properly.
253+
mi->assignUboAllocation(mUbHandle, newId, newOffset);
254+
255+
if (!BufferAllocator::isValid(newId)) {
256+
reallocationNeeded = true;
249257
}
250258
}
251259

252260
return reallocationNeeded ? REALLOCATION_REQUIRED : SUCCESS;
253261
}
254262

255-
void UboManager::allocateAllInstances(
256-
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
257-
materialInstances) {
258-
for (const auto& [_, miList] : materialInstances) {
259-
miList.forEach([this](FMaterialInstance* mi) {
260-
if (!mi->isUsingUboBatching()) {
261-
return;
262-
}
263-
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
264-
assert_invariant(BufferAllocator::isValid(newId));
265-
mi->assignUboAllocation(mUbHandle, newId, newOffset);
266-
});
263+
void UboManager::allocateAllInstances() {
264+
for (auto* mi: mManagedInstances) {
265+
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
266+
assert_invariant(BufferAllocator::isValid(newId));
267+
mi->assignUboAllocation(mUbHandle, newId, newOffset);
267268
}
268269
}
269270

@@ -288,28 +289,19 @@ void UboManager::reallocate(DriverApi& driver, allocation_size_t requiredSize) {
288289
BufferUsage::DYNAMIC | BufferUsage::SHARED_WRITE_BIT);
289290
}
290291

291-
allocation_size_t UboManager::calculateRequiredSize(
292-
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
293-
materialInstances) {
294-
BufferAllocator& allocator = mAllocator;
292+
allocation_size_t UboManager::calculateRequiredSize() {
295293
allocation_size_t newBufferSize = 0;
296-
for (const auto& materialInstance: materialInstances) {
297-
materialInstance.second.forEach([&newBufferSize, &allocator](const FMaterialInstance* mi) {
298-
if (!mi->isUsingUboBatching()) {
299-
return;
300-
}
301-
302-
const AllocationId allocationId = mi->getAllocationId();
303-
if (allocationId == BufferAllocator::REALLOCATION_REQUIRED) {
304-
// For MIs whose parameters have been updated, aside from the slot it is being
305-
// occupied by the GPU, we need to preserve an additional slot for it.
306-
newBufferSize += 2 * allocator.alignUp(mi->getUniformBuffer().getSize());
307-
} else {
308-
newBufferSize += allocator.alignUp(mi->getUniformBuffer().getSize());
309-
}
310-
});
294+
for (const auto* mi: mManagedInstances) {
295+
const AllocationId allocationId = mi->getAllocationId();
296+
if (allocationId == BufferAllocator::REALLOCATION_REQUIRED) {
297+
// For MIs whose parameters have been updated, aside from the slot it is being
298+
// occupied by the GPU, we need to preserve an additional slot for it.
299+
newBufferSize += 2 * mAllocator.alignUp(mi->getUniformBuffer().getSize());
300+
} else {
301+
newBufferSize += mAllocator.alignUp(mi->getUniformBuffer().getSize());
302+
}
311303
}
312-
return allocator.alignUp(newBufferSize * BUFFER_SIZE_GROWTH_MULTIPLIER);
304+
return mAllocator.alignUp(newBufferSize * BUFFER_SIZE_GROWTH_MULTIPLIER);
313305
}
314306

315307
} // namespace filament

0 commit comments

Comments
 (0)