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