|  | // Copyright 2018 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "src/ui/lib/escher/renderer/render_queue.h" | 
|  |  | 
|  | #include <gtest/gtest.h> | 
|  |  | 
|  | #include "src/ui/lib/escher/renderer/render_queue_context.h" | 
|  |  | 
|  | namespace { | 
|  | using namespace escher; | 
|  |  | 
|  | struct TestStatistics; | 
|  |  | 
|  | // Per-object data stored in a RenderQueue.  If this were for rendering a mesh, | 
|  | // it might contain references to the vertex and index buffers. | 
|  | struct TestRenderObject { | 
|  | uint32_t id = 0; | 
|  | TestStatistics* stats = nullptr; | 
|  | }; | 
|  |  | 
|  | // Per-draw-call data stored in a RenderQueue.  If this were for rendering a | 
|  | // mesh, it might contain the transform matrix, as well as number of indices to | 
|  | // render. | 
|  | struct TestRenderInstance { | 
|  | uint32_t foo;  // unused | 
|  | }; | 
|  |  | 
|  | // Instead of emitting Vulkan commands, the render-functions add instances of | 
|  | // TestFuncInvocation to a TestStatistics container, for later analysis. | 
|  | struct TestFuncInvocation { | 
|  | // Which render-func was called, RenderFuncOne() or RenderFuncTwo() ? | 
|  | // 0 - not called | 
|  | // 1 - RenderFuncOne() | 
|  | // 2 - RenderFuncTwo() | 
|  | uint32_t render_func_id = 0; | 
|  |  | 
|  | // ID of TestRenderObject. | 
|  | uint32_t obj_id; | 
|  |  | 
|  | // Sort-keys of all instances drawn by a render-func invocation. | 
|  | std::vector<uint64_t> sort_keys; | 
|  |  | 
|  | // Pointers to all instance data passed to a render-func invocation. | 
|  | std::vector<const TestRenderInstance*> instance_data; | 
|  | }; | 
|  |  | 
|  | struct TestStatistics { | 
|  | std::vector<TestFuncInvocation> invocations; | 
|  | }; | 
|  |  | 
|  | void BaseRenderFunc(CommandBuffer* cb, const RenderQueueContext* context, | 
|  | const RenderQueueItem* items, uint32_t instance_count, | 
|  | uint32_t render_func_id) { | 
|  | EXPECT_NE(0U, instance_count); | 
|  | ASSERT_TRUE(render_func_id == 1 || render_func_id == 2); | 
|  |  | 
|  | TestFuncInvocation invocation; | 
|  | invocation.render_func_id = render_func_id; | 
|  |  | 
|  | auto* obj = static_cast<const TestRenderObject*>(items[0].object_data); | 
|  | invocation.obj_id = obj->id; | 
|  |  | 
|  | for (uint32_t i = 0; i < instance_count; ++i) { | 
|  | auto* other = static_cast<const TestRenderObject*>(items[i].object_data); | 
|  | auto* inst = static_cast<const TestRenderInstance*>(items[i].instance_data); | 
|  | EXPECT_EQ(obj->id, other->id); | 
|  | invocation.sort_keys.push_back(items[i].sort_key); | 
|  | invocation.instance_data.push_back(inst); | 
|  | } | 
|  |  | 
|  | obj->stats->invocations.push_back(invocation); | 
|  | } | 
|  |  | 
|  | static const uint32_t kRenderFuncOneId = 1; | 
|  | void RenderFuncOne(CommandBuffer* cb, const RenderQueueContext* context, | 
|  | const RenderQueueItem* items, uint32_t instance_count) { | 
|  | BaseRenderFunc(cb, context, items, instance_count, kRenderFuncOneId); | 
|  | } | 
|  |  | 
|  | static const uint32_t kRenderFuncTwoId = 2; | 
|  | void RenderFuncTwo(CommandBuffer* cb, const RenderQueueContext* context, | 
|  | const RenderQueueItem* items, uint32_t instance_count) { | 
|  | BaseRenderFunc(cb, context, items, instance_count, kRenderFuncTwoId); | 
|  | } | 
|  |  | 
|  | TEST(RenderQueue, PushSortGenerate) { | 
|  | RenderQueue queue; | 
|  |  | 
|  | TestStatistics stats; | 
|  |  | 
|  | TestRenderObject one = {.id = 1, .stats = &stats}; | 
|  | TestRenderInstance one1; | 
|  | TestRenderInstance one2; | 
|  | TestRenderInstance one3; | 
|  |  | 
|  | TestRenderObject two = {.id = 2, .stats = &stats}; | 
|  | TestRenderInstance two1; | 
|  | TestRenderInstance two2; | 
|  | TestRenderInstance two3; | 
|  |  | 
|  | queue.Push(1, &one, &one1, {RenderFuncOne}); | 
|  | queue.Push(2, &one, &one2, {RenderFuncOne}); | 
|  | queue.Push(6, &one, &one3, {RenderFuncOne}); | 
|  |  | 
|  | queue.Push(3, &two, &two1, {RenderFuncTwo}); | 
|  | queue.Push(5, &two, &two2, {RenderFuncTwo}); | 
|  | queue.Push(4, &two, &two3, {RenderFuncTwo}); | 
|  |  | 
|  | // A real application would sort the queue first, but this allows us to verify | 
|  | // that things are rendered in the order that they were inserted.  There | 
|  | // should be one invocation of RenderFuncOne() and RenderFuncTwo(), each with | 
|  | // an instance-count of 3. | 
|  | queue.GenerateCommands(nullptr, nullptr); | 
|  | ASSERT_EQ(2U, stats.invocations.size()); | 
|  | EXPECT_EQ(kRenderFuncOneId, stats.invocations[0].render_func_id); | 
|  | EXPECT_EQ(kRenderFuncTwoId, stats.invocations[1].render_func_id); | 
|  | ASSERT_EQ(3U, stats.invocations[0].sort_keys.size()); | 
|  | ASSERT_EQ(3U, stats.invocations[0].instance_data.size()); | 
|  | ASSERT_EQ(3U, stats.invocations[1].sort_keys.size()); | 
|  | ASSERT_EQ(3U, stats.invocations[1].instance_data.size()); | 
|  |  | 
|  | // Furthermore, both the sort-keys and instance data should appear in the same | 
|  | // order they were pushed. | 
|  | EXPECT_EQ(1U, stats.invocations[0].sort_keys[0]); | 
|  | EXPECT_EQ(2U, stats.invocations[0].sort_keys[1]); | 
|  | EXPECT_EQ(6U, stats.invocations[0].sort_keys[2]); | 
|  | EXPECT_EQ(3U, stats.invocations[1].sort_keys[0]); | 
|  | EXPECT_EQ(5U, stats.invocations[1].sort_keys[1]); | 
|  | EXPECT_EQ(4U, stats.invocations[1].sort_keys[2]); | 
|  | EXPECT_EQ(&one1, stats.invocations[0].instance_data[0]); | 
|  | EXPECT_EQ(&one2, stats.invocations[0].instance_data[1]); | 
|  | EXPECT_EQ(&one3, stats.invocations[0].instance_data[2]); | 
|  | EXPECT_EQ(&two1, stats.invocations[1].instance_data[0]); | 
|  | EXPECT_EQ(&two2, stats.invocations[1].instance_data[1]); | 
|  | EXPECT_EQ(&two3, stats.invocations[1].instance_data[2]); | 
|  |  | 
|  | // Clear stats and sort before generating commands.  This time we expect three | 
|  | // render-func invocations, two of RenderFuncOne() and one of RenderFuncTwo(), | 
|  | // because "one1" and "one2" have the lowest sort keys, and "one3" has the | 
|  | // highest sort key. | 
|  | stats.invocations.clear(); | 
|  | queue.Sort(); | 
|  | queue.GenerateCommands(nullptr, nullptr); | 
|  | ASSERT_EQ(3U, stats.invocations.size()); | 
|  | EXPECT_EQ(kRenderFuncOneId, stats.invocations[0].render_func_id); | 
|  | EXPECT_EQ(kRenderFuncTwoId, stats.invocations[1].render_func_id); | 
|  | EXPECT_EQ(kRenderFuncOneId, stats.invocations[2].render_func_id); | 
|  | EXPECT_EQ(1U, stats.invocations[0].obj_id); | 
|  | EXPECT_EQ(2U, stats.invocations[1].obj_id); | 
|  | EXPECT_EQ(1U, stats.invocations[2].obj_id); | 
|  | ASSERT_EQ(2U, stats.invocations[0].sort_keys.size()); | 
|  | ASSERT_EQ(2U, stats.invocations[0].instance_data.size()); | 
|  | ASSERT_EQ(3U, stats.invocations[1].sort_keys.size()); | 
|  | ASSERT_EQ(3U, stats.invocations[1].instance_data.size()); | 
|  | ASSERT_EQ(1U, stats.invocations[2].sort_keys.size()); | 
|  | ASSERT_EQ(1U, stats.invocations[2].instance_data.size()); | 
|  |  | 
|  | // Verify that all instances appear in order of their sort-key. | 
|  | EXPECT_EQ(1U, stats.invocations[0].sort_keys[0]); | 
|  | EXPECT_EQ(2U, stats.invocations[0].sort_keys[1]); | 
|  | EXPECT_EQ(3U, stats.invocations[1].sort_keys[0]); | 
|  | EXPECT_EQ(4U, stats.invocations[1].sort_keys[1]); | 
|  | EXPECT_EQ(5U, stats.invocations[1].sort_keys[2]); | 
|  | EXPECT_EQ(6U, stats.invocations[2].sort_keys[0]); | 
|  | EXPECT_EQ(&one1, stats.invocations[0].instance_data[0]); | 
|  | EXPECT_EQ(&one2, stats.invocations[0].instance_data[1]); | 
|  | EXPECT_EQ(&two1, stats.invocations[1].instance_data[0]); | 
|  | EXPECT_EQ(&two3, stats.invocations[1].instance_data[1]); | 
|  | EXPECT_EQ(&two2, stats.invocations[1].instance_data[2]); | 
|  | EXPECT_EQ(&one3, stats.invocations[2].instance_data[0]); | 
|  |  | 
|  | // Finally, verify that clearing the queue works. | 
|  | stats.invocations.clear(); | 
|  | queue.clear(); | 
|  | queue.Sort(); | 
|  | queue.GenerateCommands(nullptr, nullptr); | 
|  | EXPECT_TRUE(stats.invocations.empty()); | 
|  | } | 
|  |  | 
|  | TEST(RenderQueue, SameObjectDifferentFuncs) { | 
|  | RenderQueue queue; | 
|  |  | 
|  | TestStatistics stats; | 
|  |  | 
|  | TestRenderObject obj = {.id = 1, .stats = &stats}; | 
|  | TestRenderInstance inst1; | 
|  | TestRenderInstance inst2; | 
|  | TestRenderInstance inst3; | 
|  | TestRenderInstance inst4; | 
|  | TestRenderInstance inst5; | 
|  | TestRenderInstance inst6; | 
|  |  | 
|  | queue.Push(1, &obj, &inst1, {RenderFuncOne}); | 
|  | queue.Push(2, &obj, &inst2, {RenderFuncOne}); | 
|  | queue.Push(3, &obj, &inst3, {RenderFuncTwo}); | 
|  | queue.Push(4, &obj, &inst4, {RenderFuncTwo}); | 
|  | queue.Push(5, &obj, &inst5, {RenderFuncTwo}); | 
|  | queue.Push(6, &obj, &inst6, {RenderFuncOne}); | 
|  |  | 
|  | // Don't bother sorting, we already tested that in a different test case. | 
|  | queue.GenerateCommands(nullptr, nullptr); | 
|  | ASSERT_EQ(3U, stats.invocations.size()); | 
|  | // Expect two instances rendered with RenderFuncOne(). | 
|  | EXPECT_EQ(kRenderFuncOneId, stats.invocations[0].render_func_id); | 
|  | EXPECT_EQ(2U, stats.invocations[0].instance_data.size()); | 
|  | // Expect three instances rendered with RenderFuncTwo(). | 
|  | EXPECT_EQ(kRenderFuncTwoId, stats.invocations[1].render_func_id); | 
|  | EXPECT_EQ(3U, stats.invocations[1].instance_data.size()); | 
|  | // Expect one final instance rendered with RenderFuncOne(). | 
|  | EXPECT_EQ(kRenderFuncOneId, stats.invocations[2].render_func_id); | 
|  | EXPECT_EQ(1U, stats.invocations[2].instance_data.size()); | 
|  | } | 
|  |  | 
|  | TEST(RenderQueue, MultipleFuncs) { | 
|  | RenderQueue queue; | 
|  |  | 
|  | TestStatistics stats; | 
|  |  | 
|  | TestRenderObject obj = {.id = 1, .stats = &stats}; | 
|  | TestRenderInstance inst1; | 
|  | TestRenderInstance inst2; | 
|  | TestRenderInstance inst3; | 
|  | TestRenderInstance inst4; | 
|  | TestRenderInstance inst5; | 
|  | TestRenderInstance inst6; | 
|  |  | 
|  | // Hopefully real client code is never this confusing.  The first 4 instances | 
|  | // have RenderFuncOne as their first func and RenderFuncTwo as the second. | 
|  | // The next two instances are the opposite. | 
|  | queue.Push(1, &obj, &inst1, {RenderFuncOne, RenderFuncTwo}); | 
|  | queue.Push(2, &obj, &inst2, {RenderFuncOne, RenderFuncTwo}); | 
|  | queue.Push(3, &obj, &inst3, {RenderFuncOne, RenderFuncTwo}); | 
|  | queue.Push(4, &obj, &inst4, {RenderFuncOne, RenderFuncTwo}); | 
|  | queue.Push(5, &obj, &inst5, {RenderFuncTwo, RenderFuncOne}); | 
|  | queue.Push(6, &obj, &inst6, {RenderFuncTwo, RenderFuncOne}); | 
|  |  | 
|  | // Don't bother sorting, we already tested that in a different test case. | 
|  |  | 
|  | RenderQueueContext context; | 
|  |  | 
|  | // Each instance renders with its first func. | 
|  | context.render_queue_func_to_use = 0; | 
|  | queue.GenerateCommands(nullptr, nullptr, &context); | 
|  | ASSERT_EQ(2U, stats.invocations.size()); | 
|  | // Expect the first 4 instances to call RenderFuncOne() and the last two to | 
|  | // call  RenderFuncTwo(). | 
|  | EXPECT_EQ(kRenderFuncOneId, stats.invocations[0].render_func_id); | 
|  | EXPECT_EQ(4U, stats.invocations[0].instance_data.size()); | 
|  | EXPECT_EQ(kRenderFuncTwoId, stats.invocations[1].render_func_id); | 
|  | EXPECT_EQ(2U, stats.invocations[1].instance_data.size()); | 
|  |  | 
|  | // Now, each instance renderers with its second func. | 
|  | context.render_queue_func_to_use = 1; | 
|  | queue.GenerateCommands(nullptr, nullptr, &context); | 
|  | // Two new invocations should have been added. | 
|  | ASSERT_EQ(4U, stats.invocations.size()); | 
|  | // Expect the first 4 instances to call RenderFuncTwo() and the last two to | 
|  | // call  RenderFuncOne(). | 
|  | EXPECT_EQ(kRenderFuncTwoId, stats.invocations[2].render_func_id); | 
|  | EXPECT_EQ(4U, stats.invocations[2].instance_data.size()); | 
|  | EXPECT_EQ(kRenderFuncOneId, stats.invocations[3].render_func_id); | 
|  | EXPECT_EQ(2U, stats.invocations[3].instance_data.size()); | 
|  | } | 
|  |  | 
|  | }  // namespace |