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