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