1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 // names, trademarks, service marks, or product names of the Licensor
11 // and its affiliates, except as required to comply with Section 4(c) of
12 // the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 // http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #include "pxr/imaging/glf/contextCaps.h"
25
26 #include "pxr/imaging/hdSt/commandBuffer.h"
27 #include "pxr/imaging/hdSt/debugCodes.h"
28 #include "pxr/imaging/hdSt/geometricShader.h"
29 #include "pxr/imaging/hdSt/immediateDrawBatch.h"
30 #include "pxr/imaging/hdSt/indirectDrawBatch.h"
31 #include "pxr/imaging/hdSt/resourceRegistry.h"
32 #include "pxr/imaging/hdSt/materialNetworkShader.h"
33 #include "pxr/imaging/hdSt/materialParam.h"
34
35 #include "pxr/imaging/hd/bufferArrayRange.h"
36 #include "pxr/imaging/hd/perfLog.h"
37 #include "pxr/imaging/hd/tokens.h"
38
39 #include "pxr/base/gf/matrix4f.h"
40 #include "pxr/base/tf/diagnostic.h"
41 #include "pxr/base/tf/stl.h"
42
43 #include "pxr/base/work/loops.h"
44
45 #include <boost/functional/hash.hpp>
46
47 #include <tbb/enumerable_thread_specific.h>
48
49 #include <functional>
50 #include <unordered_map>
51
52 PXR_NAMESPACE_OPEN_SCOPE
53
54
HdStCommandBuffer()55 HdStCommandBuffer::HdStCommandBuffer()
56 : _visibleSize(0)
57 , _visChangeCount(0)
58 , _drawBatchesVersion(0)
59 {
60 /*NOTHING*/
61 }
62
~HdStCommandBuffer()63 HdStCommandBuffer::~HdStCommandBuffer()
64 {
65 }
66
67 static
68 HdSt_DrawBatchSharedPtr
_NewDrawBatch(HdStDrawItemInstance * drawItemInstance)69 _NewDrawBatch(HdStDrawItemInstance * drawItemInstance)
70 {
71 GlfContextCaps const &caps = GlfContextCaps::GetInstance();
72
73 if (caps.multiDrawIndirectEnabled) {
74 return std::make_shared<HdSt_IndirectDrawBatch>(drawItemInstance);
75 } else {
76 return std::make_shared<HdSt_ImmediateDrawBatch>(drawItemInstance);
77 }
78 }
79
80 void
PrepareDraw(HdStRenderPassStateSharedPtr const & renderPassState,HdStResourceRegistrySharedPtr const & resourceRegistry)81 HdStCommandBuffer::PrepareDraw(
82 HdStRenderPassStateSharedPtr const &renderPassState,
83 HdStResourceRegistrySharedPtr const &resourceRegistry)
84 {
85 HD_TRACE_FUNCTION();
86
87 for (auto const& batch : _drawBatches) {
88 batch->PrepareDraw(renderPassState, resourceRegistry);
89 }
90 }
91
92 void
ExecuteDraw(HdStRenderPassStateSharedPtr const & renderPassState,HdStResourceRegistrySharedPtr const & resourceRegistry)93 HdStCommandBuffer::ExecuteDraw(
94 HdStRenderPassStateSharedPtr const &renderPassState,
95 HdStResourceRegistrySharedPtr const &resourceRegistry)
96 {
97 HD_TRACE_FUNCTION();
98
99 //
100 // TBD: sort draw items
101 //
102
103 // Reset per-commandBuffer performance counters, updated by batch execution
104 HD_PERF_COUNTER_SET(HdPerfTokens->drawCalls, 0);
105 HD_PERF_COUNTER_SET(HdTokens->itemsDrawn, 0);
106
107 //
108 // draw batches
109 //
110 for (auto const& batch : _drawBatches) {
111 batch->ExecuteDraw(renderPassState, resourceRegistry);
112 }
113 HD_PERF_COUNTER_SET(HdPerfTokens->drawBatches, _drawBatches.size());
114 }
115
116 void
SetDrawItems(HdDrawItemConstPtrVectorSharedPtr const & drawItems,unsigned currentDrawBatchesVersion)117 HdStCommandBuffer::SetDrawItems(
118 HdDrawItemConstPtrVectorSharedPtr const &drawItems,
119 unsigned currentDrawBatchesVersion)
120 {
121 if (drawItems == _drawItems &&
122 currentDrawBatchesVersion == _drawBatchesVersion) {
123 return;
124 }
125 _drawItems = drawItems;
126 _RebuildDrawBatches();
127 _drawBatchesVersion = currentDrawBatchesVersion;
128 }
129
130 void
RebuildDrawBatchesIfNeeded(unsigned currentBatchesVersion)131 HdStCommandBuffer::RebuildDrawBatchesIfNeeded(unsigned currentBatchesVersion)
132 {
133 HD_TRACE_FUNCTION();
134
135 bool deepValidation = (currentBatchesVersion != _drawBatchesVersion);
136 _drawBatchesVersion = currentBatchesVersion;
137
138 if (TfDebug::IsEnabled(HDST_DRAW_BATCH) && !_drawBatches.empty()) {
139 TfDebug::Helper().Msg(
140 "Command buffer %p : RebuildDrawBatchesIfNeeded "
141 "(deepValidation=%d)\n", (void*)(this), deepValidation);
142 }
143
144 // Force rebuild of all batches for debugging purposes. This helps quickly
145 // triage issues wherein the command buffer wasn't updated correctly.
146 bool rebuildAllDrawBatches =
147 TfDebug::IsEnabled(HDST_FORCE_DRAW_BATCH_REBUILD);
148
149 if (ARCH_LIKELY(!rebuildAllDrawBatches)) {
150 // Gather results of validation ...
151 std::vector<HdSt_DrawBatch::ValidationResult> results;
152 results.reserve(_drawBatches.size());
153
154 for (auto const& batch : _drawBatches) {
155 const HdSt_DrawBatch::ValidationResult result =
156 batch->Validate(deepValidation);
157
158 if (result == HdSt_DrawBatch::ValidationResult::RebuildAllBatches) {
159 // Skip validation of remaining batches since we need to rebuild
160 // all batches. We don't expect to use this hammer on a frequent
161 // basis.
162 rebuildAllDrawBatches = true;
163 break;
164 }
165
166 results.push_back(result);
167 }
168
169 // ... and attempt to rebuild necessary batches
170 if (!rebuildAllDrawBatches) {
171 TF_VERIFY(results.size() == _drawBatches.size());
172 size_t const numBatches = results.size();
173 for (size_t i = 0; i < numBatches; i++) {
174 if (results[i] ==
175 HdSt_DrawBatch::ValidationResult::RebuildBatch) {
176
177 if (!_drawBatches[i]->Rebuild()) {
178 // If a batch rebuild fails, we fallback to rebuilding
179 // all draw batches. This can be improved in the future.
180 rebuildAllDrawBatches = true;
181 break;
182 }
183 }
184 }
185 }
186 }
187
188 if (rebuildAllDrawBatches) {
189 _RebuildDrawBatches();
190 }
191 }
192
193 void
_RebuildDrawBatches()194 HdStCommandBuffer::_RebuildDrawBatches()
195 {
196 HD_TRACE_FUNCTION();
197
198 TF_DEBUG(HDST_DRAW_BATCH).Msg(
199 "Rebuilding all draw batches for command buffer %p ...\n", (void*)this);
200
201 _visibleSize = 0;
202
203 _drawBatches.clear();
204 _drawItemInstances.clear();
205 _drawItemInstances.reserve(_drawItems->size());
206
207 HD_PERF_COUNTER_INCR(HdPerfTokens->rebuildBatches);
208
209 const bool bindlessTexture = GlfContextCaps::GetInstance()
210 .bindlessTextureEnabled;
211
212 // Use a cheap bucketing strategy to reduce to number of comparison tests
213 // required to figure out if a draw item can be batched.
214 // We use a hash of the geometric shader, BAR version and (optionally)
215 // material params as the key, and test (in the worst case) against each of
216 // the batches for the key.
217 // Test against the previous draw item's hash and batch prior to looking up
218 // the map.
219 struct _PrevBatchHit {
220 _PrevBatchHit() : key(0) {}
221 void Update(size_t _key, HdSt_DrawBatchSharedPtr &_batch) {
222 key = _key;
223 batch = _batch;
224 }
225 size_t key;
226 HdSt_DrawBatchSharedPtr batch;
227 };
228 _PrevBatchHit prevBatch;
229
230 using _DrawBatchMap =
231 std::unordered_map<size_t, HdSt_DrawBatchSharedPtrVector>;
232 _DrawBatchMap batchMap;
233
234 // Downcast the HdDrawItem entries to HdStDrawItems:
235 std::vector<HdStDrawItem const*>* stDrawItemsPtr =
236 reinterpret_cast< std::vector<HdStDrawItem const*>* >(_drawItems.get());
237 auto const &drawItems = *stDrawItemsPtr;
238
239 for (size_t i = 0; i < drawItems.size(); i++) {
240 HdStDrawItem const * drawItem = drawItems[i];
241
242 if (!TF_VERIFY(drawItem->GetGeometricShader(), "%s",
243 drawItem->GetRprimID().GetText()) ||
244 !TF_VERIFY(drawItem->GetMaterialNetworkShader(), "%s",
245 drawItem->GetRprimID().GetText())) {
246 continue;
247 }
248
249 _drawItemInstances.push_back(HdStDrawItemInstance(drawItem));
250 HdStDrawItemInstance* drawItemInstance = &_drawItemInstances.back();
251
252 size_t key = drawItem->GetGeometricShader()->ComputeHash();
253 boost::hash_combine(key, drawItem->GetBufferArraysHash());
254 if (!bindlessTexture) {
255 // Geometric, RenderPass and Lighting shaders should never break
256 // batches, however materials can. We consider the textures
257 // used by the material to be part of the batch key for that
258 // reason.
259 // Since textures can be animated and thus materials can be batched
260 // at some times but not other times, we use the texture prim path
261 // for the hash which does not vary over time.
262 //
263 boost::hash_combine(
264 key, drawItem->GetMaterialNetworkShader()->
265 ComputeTextureSourceHash());
266 }
267
268 // Do a quick check to see if the draw item can be batched with the
269 // previous draw item, before checking the batchMap.
270 if (key == prevBatch.key && prevBatch.batch) {
271 if (prevBatch.batch->Append(drawItemInstance)) {
272 continue;
273 }
274 }
275
276 _DrawBatchMap::iterator const batchIter = batchMap.find(key);
277 bool const foundKey = batchIter != batchMap.end();
278 bool batched = false;
279 if (foundKey) {
280 HdSt_DrawBatchSharedPtrVector &batches = batchIter->second;
281 for (HdSt_DrawBatchSharedPtr &batch : batches) {
282 if (batch->Append(drawItemInstance)) {
283 batched = true;
284 prevBatch.Update(key, batch);
285 break;
286 }
287 }
288 }
289
290 if (!batched) {
291 HdSt_DrawBatchSharedPtr batch = _NewDrawBatch(drawItemInstance);
292 _drawBatches.emplace_back(batch);
293 prevBatch.Update(key, batch);
294
295 if (foundKey) {
296 HdSt_DrawBatchSharedPtrVector &batches = batchIter->second;
297 batches.emplace_back(batch);
298 } else {
299 batchMap[key] = HdSt_DrawBatchSharedPtrVector({batch});
300 }
301 }
302 }
303
304 TF_DEBUG(HDST_DRAW_BATCH).Msg(
305 " %lu draw batches created for %lu draw items\n", _drawBatches.size(),
306 drawItems.size());
307 }
308
309 void
SyncDrawItemVisibility(unsigned visChangeCount)310 HdStCommandBuffer::SyncDrawItemVisibility(unsigned visChangeCount)
311 {
312 HD_TRACE_FUNCTION();
313
314 if (_visChangeCount == visChangeCount) {
315 // There were no changes to visibility since the last time sync was
316 // called, no need to re-sync now. Note that visChangeCount starts at
317 // 0 in the class and starts at 1 in the change tracker, which ensures a
318 // sync after contruction.
319 return;
320 }
321
322 _visibleSize = 0;
323 int const N = 10000;
324 tbb::enumerable_thread_specific<size_t> visCounts;
325
326 WorkParallelForN(_drawItemInstances.size()/N+1,
327 [&visCounts, this, N](size_t start, size_t end) {
328 TRACE_SCOPE("SetVis");
329 start *= N;
330 end = std::min(end*N, _drawItemInstances.size());
331 size_t& count = visCounts.local();
332 for (size_t i = start; i < end; ++i) {
333 HdStDrawItem const* item = _drawItemInstances[i].GetDrawItem();
334
335 bool visible = item->GetVisible();
336 // DrawItemInstance->SetVisible is not only an inline function but
337 // also internally calling virtual HdDrawBatch
338 // DrawItemInstanceChanged. shortcut by looking IsVisible(), which
339 // is inline, if it's not actually changing.
340
341 // however, if this is an instancing prim and visible, it always has
342 // to be called since instanceCount may changes over time.
343 if ((_drawItemInstances[i].IsVisible() != visible) ||
344 (visible && item->HasInstancer())) {
345 _drawItemInstances[i].SetVisible(visible);
346 }
347 if (visible) {
348 ++count;
349 }
350 }
351 });
352
353 for (size_t i : visCounts) {
354 _visibleSize += i;
355 }
356
357 // Mark visible state as clean;
358 _visChangeCount = visChangeCount;
359 }
360
361 void
FrustumCull(GfMatrix4d const & viewProjMatrix)362 HdStCommandBuffer::FrustumCull(GfMatrix4d const &viewProjMatrix)
363 {
364 HD_TRACE_FUNCTION();
365
366 const bool mtCullingDisabled =
367 TfDebug::IsEnabled(HDST_DISABLE_MULTITHREADED_CULLING) ||
368 _drawItems->size() < 10000;
369
370 struct _Worker {
371 static
372 void cull(std::vector<HdStDrawItemInstance> * drawItemInstances,
373 GfMatrix4d const &viewProjMatrix,
374 size_t begin, size_t end)
375 {
376 for(size_t i = begin; i < end; i++) {
377 HdStDrawItemInstance& itemInstance = (*drawItemInstances)[i];
378 HdStDrawItem const* item = itemInstance.GetDrawItem();
379 bool visible = item->GetVisible() &&
380 item->IntersectsViewVolume(viewProjMatrix);
381 if ((itemInstance.IsVisible() != visible) ||
382 (visible && item->HasInstancer())) {
383 itemInstance.SetVisible(visible);
384 }
385 }
386 }
387 };
388
389 if (!mtCullingDisabled) {
390 WorkParallelForN(_drawItemInstances.size(),
391 std::bind(&_Worker::cull, &_drawItemInstances,
392 std::cref(viewProjMatrix),
393 std::placeholders::_1,
394 std::placeholders::_2));
395 } else {
396 _Worker::cull(&_drawItemInstances,
397 viewProjMatrix,
398 0,
399 _drawItemInstances.size());
400 }
401
402 _visibleSize = 0;
403 for (auto const& instance : _drawItemInstances) {
404 if (instance.IsVisible()) {
405 ++_visibleSize;
406 }
407 }
408 }
409
410 void
SetEnableTinyPrimCulling(bool tinyPrimCulling)411 HdStCommandBuffer::SetEnableTinyPrimCulling(bool tinyPrimCulling)
412 {
413 for(auto const& batch : _drawBatches) {
414 batch->SetEnableTinyPrimCulling(tinyPrimCulling);
415 }
416 }
417
418 PXR_NAMESPACE_CLOSE_SCOPE
419
420