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/pxr.h"
25
26 #include "pxr/imaging/garch/glApi.h"
27
28 #include "pxr/imaging/garch/glDebugWindow.h"
29 #include "pxr/imaging/glf/drawTarget.h"
30
31 #include "pxr/imaging/hd/driver.h"
32 #include "pxr/imaging/hd/engine.h"
33 #include "pxr/imaging/hd/renderPassState.h"
34 #include "pxr/imaging/hd/selection.h"
35 #include "pxr/imaging/hd/task.h"
36 #include "pxr/imaging/hd/tokens.h"
37
38 #include "pxr/imaging/hdSt/renderDelegate.h"
39 #include "pxr/imaging/hdSt/unitTestGLDrawing.h"
40
41 #include "pxr/imaging/hdx/selectionTask.h"
42 #include "pxr/imaging/hdx/selectionTracker.h"
43 #include "pxr/imaging/hdx/tokens.h"
44 #include "pxr/imaging/hdx/renderTask.h"
45 #include "pxr/imaging/hdx/unitTestDelegate.h"
46 #include "pxr/imaging/hdx/unitTestUtils.h"
47
48 #include "pxr/imaging/hgi/hgi.h"
49 #include "pxr/imaging/hgi/tokens.h"
50
51 #include "pxr/base/gf/frustum.h"
52 #include "pxr/base/gf/matrix4d.h"
53 #include "pxr/base/gf/vec2i.h"
54 #include "pxr/base/gf/vec4f.h"
55 #include "pxr/base/gf/vec4d.h"
56 #include "pxr/base/tf/errorMark.h"
57
58 #include <iostream>
59 #include <unordered_map>
60 #include <unordered_set>
61 #include <memory>
62
63 PXR_NAMESPACE_USING_DIRECTIVE
64
65 TF_DEFINE_PRIVATE_TOKENS(
66 _tokens,
67
68 (pickables)
69 );
70
71 namespace {
72
73 typedef std::unordered_map<SdfPath, std::vector<VtIntArray>, SdfPath::Hash>
74 InstanceMap;
75
76 // helper function that returns prims with selected instances in a map.
77 static InstanceMap
_GetSelectedInstances(HdSelectionSharedPtr const & sel,HdSelection::HighlightMode const & mode)78 _GetSelectedInstances(HdSelectionSharedPtr const& sel,
79 HdSelection::HighlightMode const &mode)
80 {
81 InstanceMap selInstances;
82 SdfPathVector selPrimPaths = sel->GetSelectedPrimPaths(mode);
83
84 for (const auto& path : selPrimPaths) {
85 HdSelection::PrimSelectionState const* primSelState =
86 sel->GetPrimSelectionState(mode, path);
87
88 TF_VERIFY(primSelState);
89 if (!primSelState->instanceIndices.empty()) {
90 selInstances[path] = primSelState->instanceIndices;
91 }
92 }
93
94 return selInstances;
95 }
96
97 }
98
99 class My_TestGLDrawing : public HdSt_UnitTestGLDrawing {
100 public:
My_TestGLDrawing()101 My_TestGLDrawing()
102 {
103 SetCameraRotate(0, 0);
104 SetCameraTranslate(GfVec3f(0));
105 _reprName = HdReprTokens->hull;
106 _refineLevel = 0;
107 }
108 ~My_TestGLDrawing();
109
110 void DrawScene();
111 void DrawMarquee();
112
113 // HdSt_UnitTestGLDrawing overrides
114 void InitTest() override;
115 void UninitTest() override;
116 void DrawTest() override;
117 void OffscreenTest() override;
118
119 void MousePress(int button, int x, int y, int modKeys) override;
120 void MouseRelease(int button, int x, int y, int modKeys) override;
121 void MouseMove(int x, int y, int modKeys) override;
122
123 protected:
124 void ParseArgs(int argc, char *argv[]) override;
125 void _InitScene();
126 void _Clear();
127 HdSelectionSharedPtr _Pick(
128 GfVec2i const& startPos, GfVec2i const& endPos,
129 HdSelection::HighlightMode mode);
130
131 private:
132 // Hgi and HdDriver should be constructed before HdEngine to ensure they
133 // are destructed last. Hgi may be used during engine/delegate destruction.
134 HgiUniquePtr _hgi;
135 std::unique_ptr<HdDriver> _driver;
136 HdEngine _engine;
137 HdStRenderDelegate _renderDelegate;
138 HdRenderIndex *_renderIndex;
139 std::unique_ptr<Hdx_UnitTestDelegate> _delegate;
140
141 HdRprimCollection _pickablesCol;
142 HdxUnitTestUtils::Marquee _marquee;
143 HdxSelectionTrackerSharedPtr _selTracker;
144
145 TfToken _reprName;
146 int _refineLevel;
147 GfVec2i _startPos, _endPos;
148 };
149
150 ////////////////////////////////////////////////////////////
151
152 GLuint vao;
153
154 static GfMatrix4d
_GetTranslate(float tx,float ty,float tz)155 _GetTranslate(float tx, float ty, float tz)
156 {
157 GfMatrix4d m(1.0f);
158 m.SetRow(3, GfVec4f(tx, ty, tz, 1.0));
159 return m;
160 }
161
~My_TestGLDrawing()162 My_TestGLDrawing::~My_TestGLDrawing()
163 {
164 delete _renderIndex;
165 }
166
167 void
InitTest()168 My_TestGLDrawing::InitTest()
169 {
170 _hgi = Hgi::CreatePlatformDefaultHgi();
171 _driver.reset(new HdDriver{HgiTokens->renderDriver, VtValue(_hgi.get())});
172
173 _renderIndex = HdRenderIndex::New(&_renderDelegate, {_driver.get()});
174 TF_VERIFY(_renderIndex != nullptr);
175 _delegate.reset(new Hdx_UnitTestDelegate(_renderIndex));
176 _delegate->SetRefineLevel(_refineLevel);
177 _selTracker.reset(new HdxSelectionTracker);
178
179 // prepare render task
180 SdfPath renderSetupTask("/renderSetupTask");
181 SdfPath renderTask("/renderTask");
182 SdfPath selectionTask("/selectionTask");
183 SdfPath pickTask("/pickTask");
184 _delegate->AddRenderSetupTask(renderSetupTask);
185 _delegate->AddRenderTask(renderTask);
186 _delegate->AddSelectionTask(selectionTask);
187 _delegate->AddPickTask(pickTask);
188
189 // render task parameters.
190 VtValue vParam = _delegate->GetTaskParam(renderSetupTask, HdTokens->params);
191 HdxRenderTaskParams param = vParam.Get<HdxRenderTaskParams>();
192 param.enableLighting = true; // use default lighting
193 _delegate->SetTaskParam(renderSetupTask, HdTokens->params,
194 VtValue(param));
195 _delegate->SetTaskParam(renderTask, HdTokens->collection,
196 VtValue(HdRprimCollection(HdTokens->geometry,
197 HdReprSelector(_reprName))));
198 HdxSelectionTaskParams selParam;
199 selParam.enableSelection = true;
200 selParam.selectionColor = GfVec4f(1, 1, 0, 1);
201 selParam.locateColor = GfVec4f(1, 0, 1, 1);
202 _delegate->SetTaskParam(selectionTask, HdTokens->params,
203 VtValue(selParam));
204
205 // prepare scene
206 _InitScene();
207 SetCameraTranslate(GfVec3f(0, 0, -20));
208
209 // picking related init
210 _pickablesCol = HdRprimCollection(_tokens->pickables,
211 HdReprSelector(HdReprTokens->refined));
212 _marquee.InitGLResources();
213 // We have to unfortunately explictly add collections besides 'geometry'
214 // See HdRenderIndex constructor.
215 _delegate->GetRenderIndex().GetChangeTracker().AddCollection(_tokens->pickables);
216
217 // XXX: Setup a VAO, the current drawing engine will not yet do this.
218 glGenVertexArrays(1, &vao);
219 glBindVertexArray(vao);
220 glBindVertexArray(0);
221 }
222
223 void
UninitTest()224 My_TestGLDrawing::UninitTest()
225 {
226 _marquee.DestroyGLResources();
227 }
228
229 void
_InitScene()230 My_TestGLDrawing::_InitScene()
231 {
232 _delegate->AddCube(SdfPath("/cube0"), _GetTranslate( 5, 0, 5));
233 _delegate->AddCube(SdfPath("/cube1"), _GetTranslate(-5, 0, 5));
234 _delegate->AddCube(SdfPath("/cube2"), _GetTranslate(-5, 0,-5));
235 _delegate->AddCube(SdfPath("/cube3"), _GetTranslate( 5, 0,-5));
236
237 {
238 _delegate->AddInstancer(SdfPath("/instancerTop"));
239 _delegate->AddCube(SdfPath("/protoTop"),
240 GfMatrix4d(1), false, SdfPath("/instancerTop"));
241
242 std::vector<SdfPath> prototypes;
243 prototypes.push_back(SdfPath("/protoTop"));
244
245 VtVec3fArray scale(3);
246 VtVec4fArray rotate(3);
247 VtVec3fArray translate(3);
248 VtIntArray prototypeIndex(3);
249
250 scale[0] = GfVec3f(1);
251 rotate[0] = GfVec4f(0);
252 translate[0] = GfVec3f(3, 0, 2);
253 prototypeIndex[0] = 0;
254
255 scale[1] = GfVec3f(1);
256 rotate[1] = GfVec4f(0);
257 translate[1] = GfVec3f(0, 0, 2);
258 prototypeIndex[1] = 0;
259
260 scale[2] = GfVec3f(1);
261 rotate[2] = GfVec4f(0);
262 translate[2] = GfVec3f(-3, 0, 2);
263 prototypeIndex[2] = 0;
264
265 _delegate->SetInstancerProperties(SdfPath("/instancerTop"),
266 prototypeIndex,
267 scale, rotate, translate);
268 }
269
270 {
271 _delegate->AddInstancer(SdfPath("/instancerBottom"));
272 _delegate->AddTet(SdfPath("/protoBottom"),
273 GfMatrix4d(1), false, SdfPath("/instancerBottom"));
274 _delegate->SetRefineLevel(SdfPath("/protoBottom"), 2);
275
276 std::vector<SdfPath> prototypes;
277 prototypes.push_back(SdfPath("/protoBottom"));
278
279 VtVec3fArray scale(3);
280 VtVec4fArray rotate(3);
281 VtVec3fArray translate(3);
282 VtIntArray prototypeIndex(3);
283
284 scale[0] = GfVec3f(1);
285 rotate[0] = GfVec4f(0);
286 translate[0] = GfVec3f(3, 0, -2);
287 prototypeIndex[0] = 0;
288
289 scale[1] = GfVec3f(1);
290 rotate[1] = GfVec4f(0);
291 translate[1] = GfVec3f(0, 0, -2);
292 prototypeIndex[1] = 0;
293
294 scale[2] = GfVec3f(1);
295 rotate[2] = GfVec4f(0);
296 translate[2] = GfVec3f(-3, 0, -2);
297 prototypeIndex[2] = 0;
298
299 _delegate->SetInstancerProperties(SdfPath("/instancerBottom"),
300 prototypeIndex,
301 scale, rotate, translate);
302 }
303 }
304
305 HdSelectionSharedPtr
_Pick(GfVec2i const & startPos,GfVec2i const & endPos,HdSelection::HighlightMode mode)306 My_TestGLDrawing::_Pick(GfVec2i const& startPos, GfVec2i const& endPos,
307 HdSelection::HighlightMode mode)
308 {
309 HdxPickHitVector allHits;
310 HdxPickTaskContextParams p;
311 p.resolution = HdxUnitTestUtils::CalculatePickResolution(
312 startPos, endPos, GfVec2i(4,4));
313 p.resolveMode = HdxPickTokens->resolveUnique;
314 p.viewMatrix = GetViewMatrix();
315 p.projectionMatrix = HdxUnitTestUtils::ComputePickingProjectionMatrix(
316 startPos, endPos, GfVec2i(GetWidth(), GetHeight()), GetFrustum());
317 p.collection = _pickablesCol;
318 p.outHits = &allHits;
319
320 HdTaskSharedPtrVector tasks;
321 tasks.push_back(_renderIndex->GetTask(SdfPath("/pickTask")));
322 VtValue pickParams(p);
323 _engine.SetTaskContextData(HdxPickTokens->pickParams, pickParams);
324 _engine.Execute(_renderIndex, &tasks);
325
326 return HdxUnitTestUtils::TranslateHitsToSelection(
327 p.pickTarget, mode, allHits);
328 }
329
330 void
_Clear()331 My_TestGLDrawing::_Clear()
332 {
333 GLfloat clearColor[4] = { 0.1f, 0.1f, 0.1f, 1.0f };
334 glClearBufferfv(GL_COLOR, 0, clearColor);
335
336 GLfloat clearDepth[1] = { 1.0f };
337 glClearBufferfv(GL_DEPTH, 0, clearDepth);
338 }
339
340 void
DrawTest()341 My_TestGLDrawing::DrawTest()
342 {
343 _Clear();
344
345 DrawScene();
346
347 DrawMarquee();
348 }
349
350 void
OffscreenTest()351 My_TestGLDrawing::OffscreenTest()
352 {
353 GLfloat clearColor[4] = { 0.1f, 0.1f, 0.1f, 1.0f };
354 glClearBufferfv(GL_COLOR, 0, clearColor);
355
356 GLfloat clearDepth[1] = { 1.0f };
357 glClearBufferfv(GL_DEPTH, 0, clearDepth);
358
359 DrawScene();
360 WriteToFile("color", "color1_unselected.png");
361
362 // --------------------- (active) selection --------------------------------
363 // select cube2
364 HdSelection::HighlightMode mode = HdSelection::HighlightModeSelect;
365 HdSelectionSharedPtr selection = _Pick(
366 GfVec2i(180, 390), GfVec2i(181, 391), mode);
367
368 _selTracker->SetSelection(selection);
369 DrawScene();
370 WriteToFile("color", "color2_select.png");
371 TF_VERIFY(selection->GetSelectedPrimPaths(mode).size() == 1);
372 TF_VERIFY(selection->GetSelectedPrimPaths(mode)[0] == SdfPath("/cube2"));
373
374 // select cube1, /protoTop:1, /protoTop:2, /protoBottom:1, /protoBottom:2
375 selection = _Pick(GfVec2i(105,62), GfVec2i(328,288), mode);
376 _selTracker->SetSelection(selection);
377 DrawScene();
378 WriteToFile("color", "color3_select.png");
379 // primPaths expected: {cube1, protoTop, protoBottom}
380 TF_VERIFY(selection->GetSelectedPrimPaths(mode).size() == 3);
381 // prims with non-empty instance indices {protoTop, protoBottom}
382 InstanceMap selInstances = _GetSelectedInstances(selection, mode);
383 TF_VERIFY(selInstances.size() == 2);
384 {
385 std::vector<VtIntArray> const& indices
386 = selInstances[SdfPath("/protoTop")];
387 TF_VERIFY(indices.size() == 2);
388 TF_VERIFY(indices[0][0] == 1 || indices[0][0] == 2);
389 TF_VERIFY(indices[1][0] == 1 || indices[1][0] == 2);
390 }
391 {
392 std::vector<VtIntArray> const& indices
393 = selInstances[SdfPath("/protoBottom")];
394 TF_VERIFY(indices.size() == 2);
395 TF_VERIFY(indices[0][0] == 1 || indices[0][0] == 2);
396 TF_VERIFY(indices[1][0] == 1 || indices[1][0] == 2);
397 }
398
399 // --------------------- locate (rollover) selection -----------------------
400 mode = HdSelection::HighlightModeLocate;
401 // select cube0
402 selection = _Pick(GfVec2i(472, 97), GfVec2i(473, 98), mode);
403 _selTracker->SetSelection(selection);
404 DrawScene();
405 WriteToFile("color", "color4_locate.png");
406 TF_VERIFY(selection->GetSelectedPrimPaths(mode).size() == 1);
407 TF_VERIFY(selection->GetSelectedPrimPaths(mode)[0] == SdfPath("/cube0"));
408
409 // select cube3, /protoBottom:0
410 selection = _Pick(GfVec2i(408,246), GfVec2i(546,420), mode);
411 _selTracker->SetSelection(selection);
412 DrawScene();
413 WriteToFile("color", "color5_locate.png");
414 TF_VERIFY(selection->GetSelectedPrimPaths(mode).size() == 2);
415 selInstances = _GetSelectedInstances(selection, mode);
416 TF_VERIFY(selInstances.size() == 1);
417 {
418 std::vector<VtIntArray> const& indices
419 = selInstances[SdfPath("/protoBottom")];
420 TF_VERIFY(indices.size() == 1);
421 TF_VERIFY(indices[0][0] == 0);
422 }
423
424 // deselect
425 mode = HdSelection::HighlightModeSelect;
426 selection = _Pick(GfVec2i(0,0), GfVec2i(0,0), mode);
427 _selTracker->SetSelection(selection);
428 DrawScene();
429
430 // select all instances of protoTop without picking
431 // This is to test whether HdSelection::AddInstance allows an empty indices
432 // array to encode "all instances".
433 selection->AddInstance(mode, SdfPath("/protoTop"), VtIntArray());
434 _selTracker->SetSelection(selection);
435 DrawScene();
436 // Expect to see earlier selection as well as all instances of protoTop
437 WriteToFile("color", "color6_select_all_instances.png");
438 }
439
440 void
DrawScene()441 My_TestGLDrawing::DrawScene()
442 {
443 _Clear();
444
445 int width = GetWidth(), height = GetHeight();
446
447 GfMatrix4d viewMatrix = GetViewMatrix();
448 GfFrustum frustum = GetFrustum();
449
450 GfVec4d viewport(0, 0, width, height);
451
452 GfMatrix4d projMatrix = frustum.ComputeProjectionMatrix();
453 _delegate->SetCamera(viewMatrix, projMatrix);
454
455 glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
456
457 SdfPath renderSetupTask("/renderSetupTask");
458 SdfPath renderTask("/renderTask");
459 SdfPath selectionTask("/selectionTask");
460
461 // viewport
462 HdxRenderTaskParams param
463 = _delegate->GetTaskParam(
464 renderSetupTask, HdTokens->params).Get<HdxRenderTaskParams>();
465 param.viewport = viewport;
466 _delegate->SetTaskParam(renderSetupTask, HdTokens->params, VtValue(param));
467
468 HdTaskSharedPtrVector tasks;
469 tasks.push_back(_renderIndex->GetTask(renderSetupTask));
470 tasks.push_back(_renderIndex->GetTask(renderTask));
471 tasks.push_back(_renderIndex->GetTask(selectionTask));
472
473 glEnable(GL_DEPTH_TEST);
474 glBindVertexArray(vao);
475
476 VtValue selTracker(_selTracker);
477 _engine.SetTaskContextData(HdxTokens->selectionState, selTracker);
478 _engine.Execute(&_delegate->GetRenderIndex(), &tasks);
479
480 glBindVertexArray(0);
481 }
482
483 void
DrawMarquee()484 My_TestGLDrawing::DrawMarquee()
485 {
486 _marquee.Draw(GetWidth(), GetHeight(), _startPos, _endPos);
487 }
488
489 void
MousePress(int button,int x,int y,int modKeys)490 My_TestGLDrawing::MousePress(int button, int x, int y, int modKeys)
491 {
492 HdSt_UnitTestGLDrawing::MousePress(button, x, y, modKeys);
493 _startPos = _endPos = GetMousePos();
494 }
495
496 void
MouseRelease(int button,int x,int y,int modKeys)497 My_TestGLDrawing::MouseRelease(int button, int x, int y, int modKeys)
498 {
499 HdSt_UnitTestGLDrawing::MouseRelease(button, x, y, modKeys);
500
501 if (!(modKeys & GarchGLDebugWindow::Alt)) {
502 HdSelectionSharedPtr selection = _Pick(_startPos, _endPos,
503 HdSelection::HighlightModeSelect);
504 _selTracker->SetSelection(selection);
505 }
506 _startPos = _endPos = GfVec2i(0);
507 }
508
509 void
MouseMove(int x,int y,int modKeys)510 My_TestGLDrawing::MouseMove(int x, int y, int modKeys)
511 {
512 HdSt_UnitTestGLDrawing::MouseMove(x, y, modKeys);
513
514 if (!(modKeys & GarchGLDebugWindow::Alt)) {
515 _endPos = GetMousePos();
516 }
517 }
518
519 void
ParseArgs(int argc,char * argv[])520 My_TestGLDrawing::ParseArgs(int argc, char *argv[])
521 {
522 for (int i=0; i<argc; ++i) {
523 std::string arg(argv[i]);
524 if (arg == "--repr") {
525 _reprName = TfToken(argv[++i]);
526 } else if (arg == "--refineLevel") {
527 _refineLevel = atoi(argv[++i]);
528 }
529 }
530 }
531
532 void
BasicTest(int argc,char * argv[])533 BasicTest(int argc, char *argv[])
534 {
535 My_TestGLDrawing driver;
536
537 driver.RunTest(argc, argv);
538 }
539
main(int argc,char * argv[])540 int main(int argc, char *argv[])
541 {
542 TfErrorMark mark;
543
544 BasicTest(argc, argv);
545
546 if (mark.IsClean()) {
547 std::cout << "OK" << std::endl;
548 return EXIT_SUCCESS;
549 } else {
550 std::cout << "FAILED" << std::endl;
551 return EXIT_FAILURE;
552 }
553 }
554
555