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/hdx/selectionTracker.h"
25 
26 #include "pxr/imaging/hdx/debugCodes.h"
27 
28 #include "pxr/imaging/hd/renderIndex.h"
29 #include "pxr/imaging/hd/rprim.h"
30 #include "pxr/base/trace/trace.h"
31 #include "pxr/base/work/loops.h"
32 
33 #include <algorithm>
34 #include <limits>
35 #include <iomanip>
36 #include <iostream>
37 
38 PXR_NAMESPACE_OPEN_SCOPE
39 
HdxSelectionTracker()40 HdxSelectionTracker::HdxSelectionTracker()
41     : _version(0)
42 {
43 }
44 
45 HdxSelectionTracker::~HdxSelectionTracker() = default;
46 
47 /*virtual*/
48 void
UpdateSelection(HdRenderIndex * index)49 HdxSelectionTracker::UpdateSelection(HdRenderIndex* index)
50 {
51 }
52 
53 int
GetVersion() const54 HdxSelectionTracker::GetVersion() const
55 {
56     return _version;
57 }
58 
59 void
_IncrementVersion()60 HdxSelectionTracker::_IncrementVersion()
61 {
62     ++_version;
63 }
64 
65 namespace {
66     template <class T>
_DebugPrintArray(std::string const & name,T const & array,bool withIndex=true)67     void _DebugPrintArray(std::string const& name,
68                     T const& array,
69                     bool withIndex=true)
70     {
71         if (ARCH_UNLIKELY(TfDebug::IsEnabled(HDX_SELECTION_SETUP))) {
72             std::stringstream out;
73             out << name << ": [ ";
74             for (auto const& i : array) {
75                 out << std::setfill(' ') << std::setw(3) << i << " ";
76             }
77             out << "] (offsets)" << std::endl;
78 
79             if (withIndex) {
80                 // Print the indices
81                 out << name << ": [ ";
82                 for (size_t i = 0; i < array.size(); i++) {
83                     out << std::setfill(' ') << std::setw(3) << i << " ";
84                 }
85                 out << "] (indices)" << std::endl;
86                 out << std::endl;
87                 std::cout << out.str();
88             }
89         }
90     }
91 }
92 
93 /*virtual*/
94 bool
GetSelectionOffsetBuffer(HdRenderIndex const * index,bool enableSelection,VtIntArray * offsets) const95 HdxSelectionTracker::GetSelectionOffsetBuffer(HdRenderIndex const* index,
96                                               bool enableSelection,
97                                               VtIntArray* offsets) const
98 {
99     TRACE_FUNCTION();
100     TfAutoMallocTag2 tag("Hdx", "GetSelectionOffsetBuffer");
101 
102      // XXX: Set minimum size for UBO/SSBO requirements. Seems like this should
103     // be handled by Hydra. Update all uses of minSize below when resolved.
104     const int minSize = 8;
105     offsets->resize(minSize);
106 
107     // Populate a selection offset buffer that holds offset data per selection
108     // highlight mode. See 'Buffer Layout' for the per-mode layout.
109     //
110     // The full layout is:
111     // [# modes] [per-mode offsets] [seloffsets mode0] ... [seloffsets modeM]
112     // [--------  header  --------]
113     //
114     // Example:
115     //   [2 ]         [4,30]       [seloffsets mode0] [seloffsets mode1]
116     //                 |  |         ^                  ^
117     //                 |  |_________|__________________|
118     //                 |____________|
119     //
120     //  Above:
121     //  Index 0 holds the number of selection highlight modes (2)
122     //  Indices [1,2] hold the start index for each mode's data.
123     //  If a mode doesn't have any selected items, we use 0 to encode this.
124     //  See hdx/shaders/renderPass.glslfx (ApplySelectionColor) for the
125     //  shader readback of this buffer
126 
127     bool hasSelection = false;
128     const size_t numHighlightModes =
129         static_cast<size_t>(HdSelection::HighlightModeCount);
130     // 1 for num modes, plus one per each mode for mode offset.
131     const size_t headerSize = numHighlightModes + 1;
132     const int SELECT_NONE = 0;
133 
134     if (ARCH_UNLIKELY(numHighlightModes >= minSize)) {
135         // allocate enough to hold the header
136         offsets->resize(headerSize);
137     }
138 
139     (*offsets)[0] = numHighlightModes;
140 
141     // We expect the collection of selected items to be created externally and
142     // set via SetSelection. Exit early if the tracker doesn't have one set,
143     // or it's empty. Likewise if enableSelection is false.
144     if (!_selection || !enableSelection || _selection->IsEmpty()) {
145         for (int mode = HdSelection::HighlightModeSelect;
146                  mode < HdSelection::HighlightModeCount;
147                  mode++) {
148             (*offsets)[mode + 1] = SELECT_NONE;
149         }
150         _DebugPrintArray("nothing selected", *offsets);
151         return false;
152     }
153 
154     size_t copyOffset = headerSize;
155 
156     for (int mode = HdSelection::HighlightModeSelect;
157              mode < HdSelection::HighlightModeCount;
158              mode++) {
159 
160         std::vector<int> output;
161         bool modeHasSelection = _GetSelectionOffsets(
162                                 static_cast<HdSelection::HighlightMode>(mode),
163                                 index, copyOffset, &output);
164         hasSelection = hasSelection || modeHasSelection;
165 
166         (*offsets)[mode + 1] = modeHasSelection? copyOffset : SELECT_NONE;
167 
168         if (modeHasSelection) {
169             // append the offset buffer for the highlight mode
170             offsets->resize(output.size() + copyOffset);
171 
172             for (size_t i = 0; i < output.size(); i++) {
173                 (*offsets)[i + copyOffset] = output[i];
174             }
175 
176             copyOffset += output.size();
177 
178             TF_DEBUG(HDX_SELECTION_SETUP).Msg("Highlight mode %d has %lu "
179                 "entries\n", mode, output.size());
180         }
181     }
182 
183     _DebugPrintArray("final output", *offsets);
184     return hasSelection;
185 }
186 
187 /*virtual*/
188 VtVec4fArray
GetSelectedPointColors() const189 HdxSelectionTracker::GetSelectedPointColors() const
190 {
191     if (!_selection) return VtVec4fArray();
192 
193     std::vector<GfVec4f> const& pointColors =
194         _selection->GetSelectedPointColors();
195 
196     VtVec4fArray vtColors(pointColors.size());
197     std::copy(pointColors.begin(), pointColors.end(), vtColors.begin());
198 
199     return vtColors;
200 }
201 
202 // Helper methods to fill the selection buffer
203 static std::pair<int, int>
_GetMinMax(std::vector<VtIntArray> const & vecIndices)204 _GetMinMax(std::vector<VtIntArray> const &vecIndices)
205 {
206     int min = std::numeric_limits<int>::max();
207     int max = std::numeric_limits<int>::lowest();
208 
209     for (VtIntArray const& indices : vecIndices ) {
210         for (int const& id : indices ) {
211             min = std::min(min, id);
212             max = std::max(max, id);
213         }
214     }
215 
216     return std::pair<int, int>(min,max);
217 }
218 
219 // The selection offsets in the buffer encode two pieces of information:
220 // (a) isSelected (bit 0)
221 //     This tells us if whatever we're encoding (prim/instance/subprim) is
222 //     selected.
223 // (b) offset (bits 31:1)
224 //     This tells us the offset to jump to, which may in turn encode either
225 //     instance/subprim selection state, thus allowing us to support selection
226 //     of multiple subprims.
227 //     If the offset is 0, it means there is nothing more to decode.
228 //     For points alone, the offset is overloaded to represent the point color
229 //     index for customized highlighting (or -1 if a color isn't specified;
230 //     see HdSelection::AddPoints)
231 static
_EncodeSelOffset(size_t offset,bool isSelected)232 int _EncodeSelOffset(size_t offset, bool isSelected)
233 {
234     return int(offset << 1) | static_cast<int>(isSelected);
235 }
236 
237 // This function takes care of encoding subprim selection offsets.
238 // Returns true if output was filled, and false if not.
239 static
_FillSubprimSelOffsets(int type,std::vector<VtIntArray> const & vecIndices,int nextSubprimOffset,std::vector<int> * output)240 bool _FillSubprimSelOffsets(int type,
241                          std::vector<VtIntArray> const &vecIndices,
242                          int nextSubprimOffset,
243                          std::vector<int>* output)
244 {
245     // Nothing to do if we have no indices arrays.
246     // Also worth noting that the HdxSelection::Add<Subprim> methods ensure
247     // empty indices arrays aren't inserted.
248     if (vecIndices.empty()) return false;
249 
250     std::pair<int, int> minmax = _GetMinMax(vecIndices);
251     int const& min = minmax.first;
252     int const& max = minmax.second;
253 
254     // Each subprims offsets' buffer encoding is:
255     // [subprim-type][min][max][     selOffsets     ]
256     // <----------3 ----------><--- max - min + 1 -->
257     int const SUBPRIM_SELOFFSETS_HEADER_SIZE = 3;
258     bool const SELECT_ALL = 1;
259     bool const SELECT_NONE = 0;
260     int numOffsetsToInsert = SUBPRIM_SELOFFSETS_HEADER_SIZE + (max - min + 1);
261     size_t startOutputSize = output->size();
262 
263     // Grow by the total size and then fill the header (to avoid an additional
264     // insert operation)
265     output->insert(output->end(),numOffsetsToInsert,
266                        _EncodeSelOffset(nextSubprimOffset, SELECT_NONE));
267 
268     (*output)[startOutputSize    ] = type;
269     (*output)[startOutputSize + 1] = min;
270     (*output)[startOutputSize + 2] = max+1;
271 
272     // For those subprim indices that are selected, set their LSB to 1.
273     size_t selOffsetsStart = startOutputSize + SUBPRIM_SELOFFSETS_HEADER_SIZE;
274     for (VtIntArray const& indices : vecIndices ) {
275         for (int const& id : indices ) {
276             if (id >= 0) {
277                 (*output)[selOffsetsStart + (id - min)] |= SELECT_ALL;
278             }
279         }
280     }
281 
282     return true;
283 }
284 
285 // Encode subprim selection offsets for points, with the offset representing the
286 // index of the point color to be used for custom point selection highlighting.
287 // Note: When a color isn't specified (see HdSelection::AddPoints), an index of
288 // -1 is used.
289 static
_FillPointSelOffsets(int type,std::vector<VtIntArray> const & pointIndices,std::vector<int> const & pointColorIndices,std::vector<int> * output)290 bool _FillPointSelOffsets(int type,
291                           std::vector<VtIntArray> const &pointIndices,
292                           std::vector<int> const &pointColorIndices,
293                           std::vector<int>* output)
294 {
295     size_t startOutputSize = output->size();
296     bool hasSelectedPoints = _FillSubprimSelOffsets(type,
297                                                     pointIndices,
298                                                     /*nextSubprimOffset=*/0,
299                                                     output);
300     if (hasSelectedPoints) {
301         // Update the 'offset' part of selOffset for each of the selected
302         // points to represent the point color index for customized point
303         // selection highlighting.
304         std::pair<int, int> minmax = _GetMinMax(pointIndices);
305         int const& min = minmax.first;
306         int const SUBPRIM_SELOFFSETS_HEADER_SIZE = 3;
307         bool const SELECT_ALL = 1;
308         size_t selOffsetsStart = startOutputSize +
309                                  SUBPRIM_SELOFFSETS_HEADER_SIZE;
310         size_t vtIndex = 0;
311         for (VtIntArray const& indices : pointIndices ) {
312             int pointColorId = pointColorIndices[vtIndex++];
313             int selOffset = (pointColorId << 1) | SELECT_ALL;
314             for (int const& id : indices ) {
315                 (*output)[selOffsetsStart + (id - min)] = selOffset;
316             }
317         }
318     }
319 
320     return hasSelectedPoints;
321 }
322 
323 namespace {
324 
325 constexpr int INVALID = -1;
326 
327 }
328 
329 /*virtual*/
330 bool
_GetSelectionOffsets(HdSelection::HighlightMode const & mode,HdRenderIndex const * index,size_t modeOffset,std::vector<int> * output) const331 HdxSelectionTracker::_GetSelectionOffsets(HdSelection::HighlightMode const& mode,
332                                           HdRenderIndex const *index,
333                                           size_t modeOffset,
334                                           std::vector<int> *output) const
335 {
336 
337     const SdfPathVector selectedPrims =  _selection->GetSelectedPrimPaths(mode);
338     const size_t numPrims = _selection ? selectedPrims.size() : 0;
339     if (numPrims == 0) {
340         TF_DEBUG(HDX_SELECTION_SETUP).Msg(
341             "No selected prims for mode %d\n", mode);
342         return false;
343     }
344 
345     std::vector<int> ids;
346     ids.resize(numPrims);
347 
348     size_t const N = 1000;
349     WorkParallelForN(numPrims/N + 1,
350        [&ids, index, N, &selectedPrims](size_t begin, size_t end) mutable {
351         end = std::min(end*N, ids.size());
352         begin = begin*N;
353         for (size_t i = begin; i < end; i++) {
354             if (auto const& rprim = index->GetRprim(selectedPrims[i])) {
355                 ids[i] = rprim->GetPrimId();
356             } else {
357                 // silently ignore non-existing prim
358                 ids[i] = INVALID;
359             }
360         }
361     });
362 
363     // Note that numeric_limits<float>::min for is surprising, so using lowest()
364     // here instead. Doing this for <int> here to avoid copy and paste bugs.
365     int min = std::numeric_limits<int>::max(),
366         max = std::numeric_limits<int>::lowest();
367 
368     for (int id : ids) {
369         if (id == INVALID) continue;
370         min = std::min(id, min);
371         max = std::max(id, max);
372     }
373 
374     if (max < min) {
375         return false;
376     }
377 
378     // ---------------------------------------------------------------------- //
379     // Buffer Layout
380     // ---------------------------------------------------------------------- //
381     // In the following code, we want to build up a buffer that is capable of
382     // driving selection highlighting. To do this, we leverage the fact that the
383     // fragment shader has access to the drawing coord, namely the PrimID,
384     // InstanceID, ElementID, EdgeID, VertexID, etc.
385     // The idea is to take one such ID and compare it against a [min, max) range
386     // of selected IDs. Since it is range based, it is possible that only a
387     // subset of values in the range are selected. Following the range is a set
388     // of "selection offset" values that encode whether the ID in question is
389     // selected, and the offset to next ID in the hierarchy.
390     //
391     // For example, imagine the PrimID is 6, then we can know this prim *may* be
392     // selected only if ID 6 is in the range of selected prims.
393     //
394     // The buffer layout is as follows:
395     //
396     // Prim: [ start index | end index | (selection offsets per prim) ]
397     //
398     // For subprims of a prim (specific instances, specific faces, etc),
399     // we add a 'type' field before the range.
400     //
401     // Subprim: [ type | start index | end index | (selection offsets) ]
402     //
403     // To test if a given fragment is selected, we do the following:
404     // (a) check if the PrimID is in the range [start,end), if so, the prim's
405     //     offset in the buffer is ID-start.
406     // (b) the value at that offset encodes the "selection offset" with bit 0
407     //     indicating if the prim is fully selected, and bits 31:1 holding the
408     //     offset to the next level in the hierarchy (instances/subprims).
409     // (c) jump to the offset, and hierarchically apply (b), gathering the
410     //     selection state for each level.
411     //
412     // All data is aggregated into a single  buffer with the following layout:
413     //
414     // [ prims | points | edges | elements | instances ]
415     //          <-------- subprims ------->
416     //          <------------- per prim --------------->
417     //
418     //  Each section above is prefixed with [start,end) ranges and the values of
419     //  each range follow the three cases outlined.
420     //
421     //  To see these values built incrementally, enable the TF_DEBUG flag
422     //  HDX_SELECTION_SETUP.
423     // ---------------------------------------------------------------------- //
424 
425     // Start with individual arrays. Splice arrays once finished.
426     int const PRIM_SELOFFSETS_HEADER_SIZE = 2;
427     bool const SELECT_NONE = 0;
428 
429     enum SubPrimType {
430         ELEMENT  = 0,
431         EDGE     = 1,
432         POINT    = 2,
433         INSTANCE = 3
434     };
435 
436     _DebugPrintArray("ids", ids);
437     // For initialization, use offset=0 in the seloffset encoding.
438     // This will be updated as need be once we process subprims and instances.
439     output->insert(output->end(), PRIM_SELOFFSETS_HEADER_SIZE + (max - min + 1),
440                    _EncodeSelOffset(/*offset=*/0, SELECT_NONE));
441     (*output)[0] = min;
442     (*output)[1] = max+1;
443 
444     _DebugPrintArray("prims", *output);
445 
446     for (size_t primIndex = 0; primIndex < ids.size(); primIndex++) {
447         // TODO: store ID and path in "ids" vector
448         int id = ids[primIndex];
449         if (id == INVALID) continue;
450 
451         SdfPath const& objPath = selectedPrims[primIndex];
452         TF_DEBUG(HDX_SELECTION_SETUP).Msg("Processing: %d - %s\n",
453                 id, objPath.GetText());
454 
455         HdSelection::PrimSelectionState const* primSelState =
456             _selection->GetPrimSelectionState(mode, objPath);
457         if (!primSelState) continue;
458 
459         // netSubprimOffset tracks the "net" offset to the start of each
460         // subprim's range-offsets encoding; it allows us to handle selection of
461         // multiple subprims per prim (XXX: not per instance of a prim) by
462         // backpointing from elements to edges to points.
463         // We process subprims in the reverse order to allow for this.
464 
465         size_t netSubprimOffset = 0;
466          //------------------------------------------------------------------- //
467         // Subprimitives: Points
468         // ------------------------------------------------------------------ //
469         size_t curOffset = output->size();
470         if (_FillPointSelOffsets(POINT,
471                                  primSelState->pointIndices,
472                                  primSelState->pointColorIndices,
473                                  output)) {
474             netSubprimOffset = curOffset + modeOffset;
475             _DebugPrintArray("points", *output);
476         }
477 
478         //------------------------------------------------------------------- //
479         // Subprimitives: Edges
480         // ------------------------------------------------------------------ //
481         curOffset = output->size();
482         if (_FillSubprimSelOffsets(EDGE,  primSelState->edgeIndices,
483                                 netSubprimOffset, output)) {
484             netSubprimOffset = curOffset + modeOffset;
485             _DebugPrintArray("edges", *output);
486         }
487 
488         // ------------------------------------------------------------------ //
489         // Subprimitives: Elements (coarse/authored face(s) for meshes,
490         //                          individual curve(s) for basis curves)
491         // ------------------------------------------------------------------ //
492         curOffset = output->size();
493         if (_FillSubprimSelOffsets(ELEMENT,  primSelState->elementIndices,
494                                 netSubprimOffset, output)) {
495             netSubprimOffset = curOffset + modeOffset;
496             _DebugPrintArray("elements", *output);
497         }
498 
499         // ------------------------------------------------------------------ //
500         // Instances
501         // ------------------------------------------------------------------ //
502         curOffset = output->size();
503         if (_FillSubprimSelOffsets(INSTANCE, primSelState->instanceIndices,
504                                 netSubprimOffset, output)) {
505             netSubprimOffset = curOffset + modeOffset;
506             _DebugPrintArray("instances", *output);
507         }
508 
509         // Finally, put the prim selection state in.
510         (*output)[id-min+2] = _EncodeSelOffset(netSubprimOffset,
511             primSelState->fullySelected);
512     }
513 
514     _DebugPrintArray("final output", *output);
515 
516     return true;
517 }
518 
519 PXR_NAMESPACE_CLOSE_SCOPE
520