blob: 1a3ceed61da646ceeeabfa15898fe9ebab0fbead [file] [log] [blame]
// 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.
// clang-format off
#include "src/ui/lib/glm_workaround/glm_workaround.h"
// clang-format on
#include <fuchsia/ui/gfx/cpp/fidl.h>
#include <fuchsia/ui/policy/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/string_cast.hpp>
#include <gtest/gtest.h>
#include <lib/async/cpp/task.h>
#include <lib/fsl/vmo/vector.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <lib/ui/scenic/cpp/session.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/images/cpp/images.h>
#include <src/lib/fxl/logging.h>
#include <zircon/status.h>
#include <map>
#include <string>
#include <vector>
#include "garnet/lib/ui/gfx/tests/vk_session_test.h"
#include "garnet/testing/views/background_view.h"
#include "garnet/testing/views/coordinate_test_view.h"
#include "garnet/testing/views/opacity_view.h"
#include "garnet/testing/views/test_view.h"
#include "src/ui/lib/escher/impl/vulkan_utils.h"
#include "src/ui/lib/escher/hmd/pose_buffer.h"
#include "src/ui/lib/escher/test/gtest_vulkan.h"
#include "garnet/lib/ui/yuv/yuv.h"
namespace {
constexpr char kEnvironment[] = "ScenicPixelTest";
constexpr zx::duration kTimeout = zx::sec(15);
// If you change the size of YUV buffers, make sure that the YUV test in
// host_image_unittest.cc is also updated. Unlike that unit test,
// scenic_pixel_test.cc has no way to confirm that it is going through the
// direct-to-GPU path.
// TODO(SCN-1387): This number needs to be queried via sysmem or vulkan.
constexpr uint32_t kYuvSize = 64;
// These tests need Scenic and RootPresenter at minimum, which expand to the
// dependencies below. Using |TestWithEnvironment|, we use
// |fuchsia.sys.Environment| and |fuchsia.sys.Loader| from the system (declared
// in our *.cmx sandbox) and launch these other services in the environment we
// create in our test fixture.
//
// Another way to do this would be to whitelist these services in our sandbox
// and inject/start them via the |fuchsia.test| facet. However that has the
// disadvantage that it uses one instance of those services across all tests in
// the binary, making each test not hermetic wrt. the others. A trade-off is
// that the |TestWithEnvironment| method is more verbose.
const std::map<std::string, std::string> kServices = {
{"fuchsia.tracelink.Registry",
"fuchsia-pkg://fuchsia.com/trace_manager#meta/trace_manager.cmx"},
{"fuchsia.ui.policy.Presenter",
"fuchsia-pkg://fuchsia.com/root_presenter#meta/root_presenter.cmx"},
{"fuchsia.ui.scenic.Scenic",
"fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"},
{"fuchsia.vulkan.loader.Loader",
"fuchsia-pkg://fuchsia.com/vulkan_loader#meta/vulkan_loader.cmx"},
{"fuchsia.sysmem.Allocator",
"fuchsia-pkg://fuchsia.com/sysmem_connector#meta/sysmem_connector.cmx"}};
// Test fixture that sets up an environment suitable for Scenic pixel tests
// and provides related utilities. The environment includes Scenic and
// RootPresenter, and their dependencies.
class ScenicPixelTest : public sys::testing::TestWithEnvironment {
protected:
ScenicPixelTest() {
std::unique_ptr<sys::testing::EnvironmentServices> services =
CreateServices();
for (const auto& entry : kServices) {
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = entry.second;
services->AddServiceWithLaunchInfo(std::move(launch_info), entry.first);
}
environment_ =
CreateNewEnclosingEnvironment(kEnvironment, std::move(services));
environment_->ConnectToService(scenic_.NewRequest());
scenic_.set_error_handler([](zx_status_t status) {
FAIL() << "Lost connection to Scenic: " << zx_status_get_string(status);
});
}
// Blocking wrapper around |Scenic::TakeScreenshot|. This should not be called
// from within a loop |Run|, as it spins up its own to block and nested loops
// are undefined behavior.
fuchsia::ui::scenic::ScreenshotData TakeScreenshot() {
fuchsia::ui::scenic::ScreenshotData screenshot_out;
scenic_->TakeScreenshot(
[this, &screenshot_out](fuchsia::ui::scenic::ScreenshotData screenshot,
bool status) {
EXPECT_TRUE(status) << "Failed to take screenshot";
screenshot_out = std::move(screenshot);
QuitLoop();
});
EXPECT_FALSE(RunLoopWithTimeout(kTimeout))
<< "Timed out waiting for screenshot.";
return screenshot_out;
}
// Create a |ViewContext| that allows us to present a view via
// |RootPresenter|. See also examples/ui/hello_base_view
scenic::ViewContext CreatePresentationContext() {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
scenic::ViewContext view_context = {
.session_and_listener_request =
scenic::CreateScenicSessionPtrAndListenerRequest(scenic_.get()),
.view_token = std::move(view_token),
};
fuchsia::ui::policy::PresenterPtr presenter;
environment_->ConnectToService(presenter.NewRequest());
presenter->PresentView(std::move(view_holder_token), nullptr);
return view_context;
}
// Runs until the view renders its next frame. Technically, waits until the
// |Present| callback is invoked with an expected presentation timestamp, and
// then waits until that time.
void RunUntilPresent(scenic::TestView* view) {
// Typical sequence of events:
// 1. We set up a view bound as a |SessionListener|.
// 2. The view sends its initial |Present| to get itself connected, without
// a callback.
// 3. We call |RunUntilPresent| which sets a present callback on our
// |TestView|.
// 4. |RunUntilPresent| runs the message loop, which allows the view to
// receive a Scenic event telling us our metrics.
// 5. In response, the view sets up the scene graph with the test scene.
// 6. The view calls |Present| with the callback set in |RunUntilPresent|.
// 7. The still-running message loop eventually dispatches the present
// callback.
// 8. The callback schedules a quit for the presentation timestamp we got.
// 9. The message loop eventually dispatches the quit and exits.
bool present_received = false;
view->set_present_callback(
[&present_received](fuchsia::images::PresentationInfo info) {
present_received = true;
});
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[&present_received] { return present_received; }, zx::sec(10)));
}
fuchsia::ui::scenic::ScenicPtr scenic_;
private:
std::unique_ptr<sys::testing::EnclosingEnvironment> environment_;
};
TEST_F(ScenicPixelTest, SolidColor) {
scenic::BackgroundView view(CreatePresentationContext());
RunUntilPresent(&view);
fuchsia::ui::scenic::ScreenshotData screenshot = TakeScreenshot();
EXPECT_GT(screenshot.info.width, 0u);
EXPECT_GT(screenshot.info.height, 0u);
// We could assert on each pixel individually, but a histogram might give us a
// more meaningful failure.
std::map<scenic::Color, size_t> histogram = scenic::Histogram(screenshot);
EXPECT_GT(histogram[scenic::BackgroundView::kBackgroundColor], 0u);
histogram.erase(scenic::BackgroundView::kBackgroundColor);
// This assert is written this way so that, when it fails, it prints out all
// the unexpected colors
EXPECT_EQ((std::map<scenic::Color, size_t>){}, histogram)
<< "Unexpected colors";
}
TEST_F(ScenicPixelTest, NV12Texture) {
scenic::BackgroundView view(CreatePresentationContext());
fuchsia::images::ImageInfo image_info{
.width = kYuvSize,
.height = kYuvSize,
.stride = static_cast<uint32_t>(
kYuvSize *
images::StrideBytesPerWidthPixel(fuchsia::images::PixelFormat::NV12)),
.pixel_format = fuchsia::images::PixelFormat::NV12,
};
uint32_t num_pixels = image_info.width * image_info.height;
uint64_t image_vmo_bytes = images::ImageSize(image_info);
EXPECT_EQ((3 * num_pixels) / 2, image_vmo_bytes);
zx::vmo image_vmo;
zx_status_t status = zx::vmo::create(image_vmo_bytes, 0, &image_vmo);
EXPECT_EQ(ZX_OK, status);
uint8_t* vmo_base;
status = zx::vmar::root_self()->map(0, image_vmo, 0, image_vmo_bytes,
ZX_VM_PERM_WRITE | ZX_VM_PERM_READ,
reinterpret_cast<uintptr_t*>(&vmo_base));
EXPECT_EQ(ZX_OK, status);
static const uint8_t kYValue = 110;
static const uint8_t kUValue = 192;
static const uint8_t kVValue = 192;
// Set all the Y pixels at full res.
for (uint32_t i = 0; i < num_pixels; ++i) {
vmo_base[i] = kYValue;
}
// Set all the UV pixels pairwise at half res.
for (uint32_t i = num_pixels; i < num_pixels + num_pixels / 2; i += 2) {
vmo_base[i] = kUValue;
vmo_base[i + 1] = kVValue;
}
view.SetHostImage(std::move(image_vmo), image_vmo_bytes, image_info);
RunUntilPresent(&view);
fuchsia::ui::scenic::ScreenshotData screenshot = TakeScreenshot();
EXPECT_GT(screenshot.info.width, 0u);
EXPECT_GT(screenshot.info.height, 0u);
// We could assert on each pixel individually, but a histogram might give us a
// more meaningful failure.
std::map<scenic::Color, size_t> histogram = scenic::Histogram(screenshot);
uint8_t bgra[4];
yuv::YuvToBgra(kYValue, kUValue, kVValue, bgra);
scenic::Color color(bgra[2], bgra[1], bgra[0], bgra[3]);
EXPECT_GT(histogram[color], 0u);
histogram.erase(color);
// This assert is written this way so that, when it fails, it prints out all
// the unexpected colors
EXPECT_EQ((std::map<scenic::Color, size_t>){}, histogram)
<< "Unexpected colors";
}
TEST_F(ScenicPixelTest, ViewCoordinates) {
// Synchronously get display dimensions
float display_width;
float display_height;
scenic_->GetDisplayInfo([this, &display_width, &display_height](
fuchsia::ui::gfx::DisplayInfo display_info) {
display_width = static_cast<float>(display_info.width_in_px);
display_height = static_cast<float>(display_info.height_in_px);
QuitLoop();
});
RunLoop();
scenic::CoordinateTestView view(CreatePresentationContext());
RunUntilPresent(&view);
fuchsia::ui::scenic::ScreenshotData screenshot = TakeScreenshot();
std::vector<uint8_t> data;
EXPECT_TRUE(fsl::VectorFromVmo(screenshot.data, &data))
<< "Failed to read screenshot";
auto get_color_at_coordinates = [&display_width, &display_height, &data](
float x, float y) -> scenic::Color {
auto pixels = reinterpret_cast<scenic::Color*>(data.data());
uint32_t index_x = x * display_width;
uint32_t index_y = y * display_height;
uint32_t index = index_y * display_width + index_x;
return pixels[index];
};
EXPECT_EQ(scenic::Color({0, 0, 0, 255}),
get_color_at_coordinates(.25f, .25f));
EXPECT_EQ(scenic::Color({0, 0, 255, 255}),
get_color_at_coordinates(.25f, .75f));
EXPECT_EQ(scenic::Color({255, 0, 0, 255}),
get_color_at_coordinates(.75f, .25f));
EXPECT_EQ(scenic::Color({255, 0, 255, 255}),
get_color_at_coordinates(.75f, .75f));
EXPECT_EQ(scenic::Color({0, 255, 0, 255}),
get_color_at_coordinates(.5f, .5f));
}
// Draws and tests the following coordinate test pattern without views:
// ___________________________________
// | | |
// | BLACK | RED |
// | _____|_____ |
// |___________| GREEN |___________|
// | |_________| |
// | | |
// | BLUE | MAGENTA |
// |________________|________________|
//
TEST_F(ScenicPixelTest, GlobalCoordinates) {
// Synchronously get display dimensions
float display_width;
float display_height;
scenic_->GetDisplayInfo([this, &display_width, &display_height](
fuchsia::ui::gfx::DisplayInfo display_info) {
display_width = static_cast<float>(display_info.width_in_px);
display_height = static_cast<float>(display_info.height_in_px);
QuitLoop();
});
RunLoop();
// Initialize session
auto unique_session = std::make_unique<scenic::Session>(scenic_.get());
auto session = unique_session.get();
session->set_error_handler([this](zx_status_t status) {
FXL_LOG(ERROR) << "Session terminated.";
QuitLoop();
});
scenic::DisplayCompositor compositor(session);
scenic::LayerStack layer_stack(session);
scenic::Layer layer(session);
scenic::Renderer renderer(session);
scenic::Scene scene(session);
scenic::Camera camera(scene);
float eye_position[3] = {display_width / 2.f, display_height / 2.f, -1001};
float look_at[3] = {display_width / 2.f, display_height / 2.f, 1};
float up[3] = {0, -1, 0};
camera.SetTransform(eye_position, look_at, up);
compositor.SetLayerStack(layer_stack);
layer_stack.AddLayer(layer);
layer.SetSize(display_width, display_height);
layer.SetRenderer(renderer);
renderer.SetCamera(camera.id());
// Set up lights.
scenic::AmbientLight ambient_light(session);
scene.AddLight(ambient_light);
ambient_light.SetColor(1.f, 1.f, 1.f);
// Create an EntityNode to serve as the scene root.
scenic::EntityNode root_node(session);
scene.AddChild(root_node.id());
static const float pane_width = display_width / 2;
static const float pane_height = display_height / 2;
for (uint32_t i = 0; i < 2; i++) {
for (uint32_t j = 0; j < 2; j++) {
scenic::Rectangle pane_shape(session, pane_width, pane_height);
scenic::Material pane_material(session);
pane_material.SetColor(i * 255.f, 0, j * 255.f, 255);
scenic::ShapeNode pane_node(session);
pane_node.SetShape(pane_shape);
pane_node.SetMaterial(pane_material);
pane_node.SetTranslation((i + 0.5) * pane_width, (j + 0.5) * pane_height,
-20);
root_node.AddChild(pane_node);
}
}
scenic::Rectangle pane_shape(session, display_width / 4, display_height / 4);
scenic::Material pane_material(session);
pane_material.SetColor(0, 255, 0, 255);
scenic::ShapeNode pane_node(session);
pane_node.SetShape(pane_shape);
pane_node.SetMaterial(pane_material);
pane_node.SetTranslation(0.5 * display_width, 0.5 * display_height, -40);
root_node.AddChild(pane_node);
// Actual tests. Test the same scene with an orthographic and perspective
// camera.
std::string camera_type[2] = {"orthographic", "perspective"};
float fov[2] = {0, 2 * atan((display_height / 2.f) / abs(eye_position[2]))};
for (int i = 0; i < 2; i++) {
FXL_LOG(INFO) << "Testing " << camera_type[i] << " camera";
camera.SetProjection(fov[i]);
session->Present(
0, [this](fuchsia::images::PresentationInfo info) { QuitLoop(); });
RunLoop();
fuchsia::ui::scenic::ScreenshotData screenshot = TakeScreenshot();
std::vector<uint8_t> data;
EXPECT_TRUE(fsl::VectorFromVmo(screenshot.data, &data))
<< "Failed to read screenshot";
auto get_color_at_coordinates = [&display_width, &display_height, &data](
float x, float y) -> scenic::Color {
auto pixels = reinterpret_cast<scenic::Color*>(data.data());
uint32_t index_x = x * display_width;
uint32_t index_y = y * display_height;
uint32_t index = index_y * display_width + index_x;
return pixels[index];
};
EXPECT_EQ(scenic::Color({0, 0, 0, 255}),
get_color_at_coordinates(.25f, .25f));
EXPECT_EQ(scenic::Color({0, 0, 255, 255}),
get_color_at_coordinates(.25f, .75f));
EXPECT_EQ(scenic::Color({255, 0, 0, 255}),
get_color_at_coordinates(.75f, .25f));
EXPECT_EQ(scenic::Color({255, 0, 255, 255}),
get_color_at_coordinates(.75f, .75f));
EXPECT_EQ(scenic::Color({0, 255, 0, 255}),
get_color_at_coordinates(.5f, .5f));
}
}
// Draws a white rectangle on a black background rendered with a stereo
// camera, which produces an image something like this:
// _____________________________________
// | |
// | ___________ ___________ |
// | | | | | |
// | | | | | |
// | | WHITE | BLACK | WHITE | |
// | | | | | |
// | |_________| |_________| |
// | |
// |___________________________________|
//
TEST_F(ScenicPixelTest, StereoCamera) {
// Synchronously get display dimensions
float display_width;
float display_height;
scenic_->GetDisplayInfo([this, &display_width, &display_height](
fuchsia::ui::gfx::DisplayInfo display_info) {
display_width = static_cast<float>(display_info.width_in_px);
display_height = static_cast<float>(display_info.height_in_px);
QuitLoop();
});
RunLoop();
static const float viewport_width = display_width / 2;
static const float viewport_height = display_height;
// Initialize session
auto unique_session = std::make_unique<scenic::Session>(scenic_.get());
auto session = unique_session.get();
session->set_error_handler([this](zx_status_t status) {
FXL_LOG(ERROR) << "Session terminated.";
QuitLoop();
});
scenic::DisplayCompositor compositor(session);
scenic::LayerStack layer_stack(session);
scenic::Layer layer(session);
scenic::Renderer renderer(session);
scenic::Scene scene(session);
scenic::StereoCamera camera(scene);
float camera_offset = 1001;
float eye_position[3] = {display_width / 2.f, display_height / 2.f,
-camera_offset};
float look_at[3] = {display_width / 2.f, display_height / 2.f, 1};
float up[3] = {0, -1, 0};
camera.SetTransform(eye_position, look_at, up);
float fovy = 2 * atan((display_height / 2.f) / abs(eye_position[2]));
glm::mat4 projection = glm::perspective(
fovy, viewport_width / viewport_height, 0.1f, camera_offset);
projection = glm::scale(projection, glm::vec3(1.f, -1.f, 1.f));
camera.SetStereoProjection(glm::value_ptr(projection),
glm::value_ptr(projection));
compositor.SetLayerStack(layer_stack);
layer_stack.AddLayer(layer);
layer.SetSize(display_width, display_height);
layer.SetRenderer(renderer);
renderer.SetCamera(camera.id());
// Set up lights.
scenic::AmbientLight ambient_light(session);
scene.AddLight(ambient_light);
ambient_light.SetColor(1.f, 1.f, 1.f);
// Create an EntityNode to serve as the scene root.
scenic::EntityNode root_node(session);
scene.AddChild(root_node.id());
static const float pane_width = viewport_width / 2;
static const float pane_height = viewport_height / 2;
glm::vec3 translation(display_width * 0.5, display_height * 0.5, -10);
scenic::Rectangle pane_shape(session, pane_width, pane_height);
scenic::Material pane_material(session);
pane_material.SetColor(255, 255, 255, 255);
scenic::ShapeNode pane_shape_node(session);
pane_shape_node.SetShape(pane_shape);
pane_shape_node.SetMaterial(pane_material);
pane_shape_node.SetTranslation(translation.x, translation.y, translation.z);
root_node.AddChild(pane_shape_node);
session->Present(
0, [this](fuchsia::images::PresentationInfo info) { QuitLoop(); });
RunLoop();
fuchsia::ui::scenic::ScreenshotData screenshot = TakeScreenshot();
std::vector<uint8_t> data;
EXPECT_TRUE(fsl::VectorFromVmo(screenshot.data, &data))
<< "Failed to read screenshot";
auto get_color_at_coordinates = [&display_width, &display_height, &data](
float x, float y) -> scenic::Color {
auto pixels = reinterpret_cast<scenic::Color*>(data.data());
uint32_t index_x = x * display_width;
uint32_t index_y = y * display_height;
uint32_t index = index_y * display_width + index_x;
return pixels[index];
};
// Color array to index 0=BLACK 1=WHITE
scenic::Color colors[2] = {scenic::Color({0, 0, 0, 0}),
scenic::Color({255, 255, 255, 255})};
// Expected results by index into colors array. Column major.
// Note how this is a transposed, low-res version of the scene being drawn.
// clang-format off
int expected[8][4] = {{0, 0, 0, 0},
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}};
// clang-format on
// Test 8 columns of 4 samples each
int num_x_samples = 8;
int num_y_samples = 4;
float x_step = 1.f / num_x_samples;
float y_step = 1.f / num_y_samples;
// i maps to x, j maps to y
for (int i = 0; i < num_x_samples; i++) {
for (int j = 0; j < num_y_samples; j++) {
float x = x_step / 2 + i * x_step;
float y = y_step / 2 + j * y_step;
EXPECT_EQ(colors[expected[i][j]], get_color_at_coordinates(x, y))
<< "i = " << i << ", j = " << j << ", Sample Location: {" << x << ", "
<< y << "}";
}
}
}
// At a high level this test puts a camera inside a cube where each face is a
// different color, then uses a pose buffer to point the camera at different
// faces, using the colors to verify the pose buffer is working as expected.
VK_TEST_F(ScenicPixelTest, PoseBuffer) {
// Synchronously get display dimensions
float display_width;
float display_height;
scenic_->GetDisplayInfo([this, &display_width, &display_height](
fuchsia::ui::gfx::DisplayInfo display_info) {
display_width = static_cast<float>(display_info.width_in_px);
display_height = static_cast<float>(display_info.height_in_px);
QuitLoop();
});
RunLoop();
// Initialize session
auto unique_session = std::make_unique<scenic::Session>(scenic_.get());
auto session = unique_session.get();
session->set_error_handler([this](zx_status_t status) {
FXL_LOG(ERROR) << "Session terminated.";
QuitLoop();
});
scenic::DisplayCompositor compositor(session);
scenic::LayerStack layer_stack(session);
scenic::Layer layer(session);
scenic::Renderer renderer(session);
scenic::Scene scene(session);
scenic::StereoCamera camera(scene);
static const float viewport_width = display_width / 2;
static const float viewport_height = display_height;
static const float camera_offset = 500;
// View matrix matches vulkan clip space +Y down, looking in direction of +Z
static const glm::vec3 eye(display_width / 2.f, display_height / 2.f,
-camera_offset);
static const glm::vec3 look_at(eye + glm::vec3(0, 0, 1));
static const glm::vec3 up(0, -1, 0);
camera.SetTransform(glm::value_ptr(eye), glm::value_ptr(look_at),
glm::value_ptr(up));
glm::mat4 projection =
glm::perspective(glm::radians(120.f), viewport_width / viewport_height,
0.1f, camera_offset);
// projection = glm::scale(projection, glm::vec3(1.f, -1.f, 1.f));
glm::mat4 clip(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f);
projection = clip * projection;
glm::mat4 view = glm::lookAt(eye, look_at, up);
camera.SetStereoProjection(glm::value_ptr(projection),
glm::value_ptr(projection));
compositor.SetLayerStack(layer_stack);
layer_stack.AddLayer(layer);
layer.SetSize(display_width, display_height);
layer.SetRenderer(renderer);
renderer.SetCamera(camera.id());
// Set up lights.
scenic::AmbientLight ambient_light(session);
scene.AddLight(ambient_light);
ambient_light.SetColor(1.f, 1.f, 1.f);
// Create an EntityNode to serve as the scene root.
scenic::EntityNode root_node(session);
scene.AddChild(root_node.id());
// Configure PoseBuffer
const size_t kVmoSize = PAGE_SIZE;
zx_status_t status;
auto vulkan_queues =
scenic_impl::gfx::test::VkSessionTest::CreateVulkanDeviceQueues();
auto device = vulkan_queues->vk_device();
auto physical_device = vulkan_queues->vk_physical_device();
// TODO(SCN-1369): Scenic may use a different set of bits when creating a
// buffer, resulting in a memory pool mismatch.
const vk::BufferUsageFlags kUsageFlags =
vk::BufferUsageFlagBits::eTransferSrc |
vk::BufferUsageFlagBits::eTransferDst |
vk::BufferUsageFlagBits::eStorageTexelBuffer |
vk::BufferUsageFlagBits::eStorageBuffer |
vk::BufferUsageFlagBits::eIndexBuffer |
vk::BufferUsageFlagBits::eVertexBuffer;
auto memory_requirements =
scenic_impl::gfx::test::VkSessionTest::GetBufferRequirements(
device, kVmoSize, kUsageFlags);
auto memory = scenic_impl::gfx::test::VkSessionTest::AllocateExportableMemory(
device, physical_device, memory_requirements,
vk::MemoryPropertyFlagBits::eDeviceLocal |
vk::MemoryPropertyFlagBits::eHostVisible);
// If we can't make memory that is both host-visible and device-local, we
// can't run this test.
if (!memory) {
FXL_LOG(INFO)
<< "Could not find UMA compatible memory pool, aborting test.";
return;
}
zx::vmo pose_buffer_vmo =
scenic_impl::gfx::test::VkSessionTest::ExportMemoryAsVmo(
device, vulkan_queues->dispatch_loader(), memory);
zx::vmo remote_vmo;
status = pose_buffer_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &remote_vmo);
FXL_DCHECK(status == ZX_OK);
zx_time_t base_time = zx::clock::get_monotonic().get();
// Normally the time interval is the period of time between each entry in the
// pose buffer. In this example we only use one entry so the time interval is
// pretty meaningless. Set to 1 for simplicity (see ARGO-21).
zx_time_t time_interval = 1;
uint32_t num_entries = 1;
scenic::Memory mem(session, std::move(remote_vmo), kVmoSize,
fuchsia::images::MemoryType::VK_DEVICE_MEMORY);
scenic::Buffer pose_buffer(mem, 0, kVmoSize);
camera.SetPoseBuffer(pose_buffer, num_entries, base_time, time_interval);
// Setup Scene.
float pane_width = camera_offset / 2.f;
scenic::Rectangle pane_shape(session, pane_width, pane_width);
static const int num_panes = 6;
scenic::Color colors[num_panes] = {
scenic::Color({255, 0, 0, 255}), // RED
scenic::Color({0, 255, 255, 255}), // CYAN
scenic::Color({0, 255, 0, 255}), // GREEN
scenic::Color({255, 0, 255, 255}), // MAGENTA
scenic::Color({0, 0, 255, 255}), // BLUE
scenic::Color({255, 255, 0, 255}), // YELLOW
};
static const float pane_offset = pane_width / 2;
glm::vec3 translations[num_panes] = {
eye + glm::vec3(0, 0, pane_offset), // In front of camera.
eye + glm::vec3(0, 0, -pane_offset), // Behind camera.
eye + glm::vec3(-pane_offset, 0, 0), // Left of Camera
eye + glm::vec3(pane_offset, 0, 0), // Right of camera
eye + glm::vec3(0, -pane_offset, 0), // Above Camera
eye + glm::vec3(0, pane_offset, 0), // Below Camera
};
static const float pi = glm::pi<float>();
glm::quat orientations[num_panes] = {
glm::quat(), // identity quaternion
glm::angleAxis(pi, glm::vec3(1, 0, 0)),
glm::angleAxis(-pi / 2, glm::vec3(0, 1, 0)),
glm::angleAxis(pi / 2, glm::vec3(0, 1, 0)),
glm::angleAxis(pi / 2, glm::vec3(1, 0, 0)),
glm::angleAxis(-pi / 2, glm::vec3(1, 0, 0)),
};
for (int i = 0; i < num_panes; i++) {
scenic::Color color = colors[i];
glm::vec3 translation = translations[i];
glm::quat orientation = orientations[i];
FXL_LOG(ERROR) << "translation: " << glm::to_string(translation);
FXL_LOG(ERROR) << "orientation: " << glm::to_string(orientation);
scenic::Material pane_material(session);
pane_material.SetColor(color.r, color.g, color.b, color.a);
scenic::ShapeNode pane_shape_node(session);
pane_shape_node.SetShape(pane_shape);
pane_shape_node.SetMaterial(pane_material);
pane_shape_node.SetTranslation(translation.x, translation.y, translation.z);
pane_shape_node.SetRotation(orientation.x, orientation.y, orientation.z,
orientation.w);
root_node.AddChild(pane_shape_node);
}
static const int num_quaternions = 8;
glm::quat quaternions[num_quaternions] = {
glm::quat(), // dead ahead
glm::angleAxis(pi, glm::vec3(0, 0, 1)), // dead ahead but upside down
glm::angleAxis(pi, glm::vec3(1, 0, 0)), // behind around X
glm::angleAxis(pi, glm::vec3(0, 1, 0)), // behind around Y
glm::angleAxis(pi / 2, glm::vec3(0, 1, 0)), // left
glm::angleAxis(-pi / 2, glm::vec3(0, 1, 0)), // right
glm::angleAxis(pi / 2, glm::vec3(1, 0, 0)), // up
glm::angleAxis(-pi / 2, glm::vec3(1, 0, 0)), // down
};
int expected_color_index[num_quaternions] = {0, 0, 1, 1, 2, 3, 4, 5};
for (int i = 0; i < num_quaternions; i++) {
// Put pose into pose buffer.
// Only testing orientation so position is always the origin.
// Quaternion describes head orientation, so invert it to get a transform
// that takes you into head space.
escher::hmd::Pose pose(glm::inverse(quaternions[i]), glm::vec3(0, 0, 0));
// Use vmo::write here for test simplicity. In a real case the vmo should be
// mapped into a vmar so we dont need a syscall per write
zx_status_t status =
pose_buffer_vmo.write(&pose, 0, sizeof(escher::hmd::Pose));
FXL_DCHECK(status == ZX_OK);
session->Present(
0, [this](fuchsia::images::PresentationInfo info) { QuitLoop(); });
RunLoop();
fuchsia::ui::scenic::ScreenshotData screenshot = TakeScreenshot();
std::vector<uint8_t> data;
EXPECT_TRUE(fsl::VectorFromVmo(screenshot.data, &data))
<< "Failed to read screenshot";
auto get_color_at_coordinates = [&display_width, &display_height, &data](
float x, float y) -> scenic::Color {
auto pixels = reinterpret_cast<scenic::Color*>(data.data());
uint32_t index_x = x * display_width;
uint32_t index_y = y * display_height;
uint32_t index = index_y * display_width + index_x;
return pixels[index];
};
EXPECT_EQ(colors[expected_color_index[i]],
get_color_at_coordinates(0.25, 0.5))
<< "i = " << i;
}
device.freeMemory(memory);
}
TEST_F(ScenicPixelTest, Opacity) {
constexpr int kNumTests = 3;
// We use the same background/foreground color for each test iteration, but
// vary the opacity. When the opacity is 0% we expect the pure background
// color, and when it is 100% we expect the pure foreground color. When
// opacity is 50% we expect a blend of the two.
float opacities[kNumTests] = {0.f, 0.5f, 1.f};
scenic::Color expected_colors[kNumTests] = {{0xff, 0x00, 0xf0, 0xff},
{0x80, 0x80, 0x80, 0xff},
{0x00, 0xff, 0x0f, 0xff}};
for (int i = 0; i < kNumTests; ++i) {
scenic::OpacityView view(CreatePresentationContext());
view.set_background_color(0xff, 0x00, 0xf0);
view.set_foreground_color(0x00, 0xff, 0x0f);
view.set_foreground_opacity(opacities[i]);
RunUntilPresent(&view);
fuchsia::ui::scenic::ScreenshotData screenshot = TakeScreenshot();
EXPECT_GT(screenshot.info.width, 0u);
EXPECT_GT(screenshot.info.height, 0u);
// We could assert on each pixel individually, but a histogram might give us
// a more meaningful failure.
std::map<scenic::Color, size_t> histogram = scenic::Histogram(screenshot);
EXPECT_GT(histogram[expected_colors[i]], 0u);
histogram.erase(expected_colors[i]);
EXPECT_EQ((std::map<scenic::Color, size_t>){}, histogram)
<< "Unexpected colors";
}
}
// TODO(SCN-1375): Blocked against hardware inability
// to provide accurate screenshots from the physical
// display. Our "TakeScreenshot()" method only grabs
// pixel data from Escher before it gets sent off to
// the display controller and thus cannot accurately
// capture color conversion information.
VK_TEST_F(ScenicPixelTest, DISABLED_Compositor) {
// Synchronously get display dimensions.
float display_width;
float display_height;
scenic_->GetDisplayInfo([this, &display_width, &display_height](
fuchsia::ui::gfx::DisplayInfo display_info) {
display_width = static_cast<float>(display_info.width_in_px);
display_height = static_cast<float>(display_info.height_in_px);
QuitLoop();
});
RunLoop();
// Initialize session.
auto unique_session = std::make_unique<scenic::Session>(scenic_.get());
auto session = unique_session.get();
session->set_error_handler([this](zx_status_t status) {
FXL_LOG(ERROR) << "Session terminated.";
QuitLoop();
});
// Initialize components.
scenic::DisplayCompositor compositor(session);
scenic::LayerStack layer_stack(session);
scenic::Layer layer(session);
scenic::Renderer renderer(session);
scenic::Scene scene(session);
scenic::Camera camera(scene);
// Color correction data
std::array<float, 3> preoffsets = {0, 0, 0};
std::array<float, 9> matrix = {.288299, 0.052709, -0.257912,
0.711701, 0.947291, 0.257912,
0.000000, -0.000000, 1.000000};
std::array<float, 3> postoffsets = {0, 0, 0};
glm::mat4 glm_matrix(.288299, 0.052709, -0.257912, 0.00000, 0.711701,
0.947291, 0.257912, 0.00000, 0.000000, -0.000000,
1.000000, 0.00000, 0.000000, 0.000000, 0.00000, 1.00000);
// Position camera at the center of the display, looking down
float eye_position[3] = {display_width / 2.f, display_height / 2.f, -1001};
float look_at[3] = {display_width / 2.f, display_height / 2.f, 1};
float up[3] = {0, -1, 0};
camera.SetTransform(eye_position, look_at, up);
camera.SetProjection(0);
// Setup.
compositor.SetLayerStack(layer_stack);
layer_stack.AddLayer(layer);
layer.SetSize(display_width, display_height);
layer.SetRenderer(renderer);
renderer.SetCamera(camera.id());
// Set up lights.
scenic::AmbientLight ambient_light(session);
scene.AddLight(ambient_light);
ambient_light.SetColor(1.f, 1.f, 1.f);
// Create an EntityNode to serve as the scene root.
scenic::EntityNode root_node(session);
scene.AddChild(root_node.id());
static const float pane_width = display_width / 5;
static const float pane_height = display_height;
float colors[15] = {1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0};
for (uint32_t i = 0; i < 5; i++) {
scenic::Rectangle pane_shape(session, pane_width, pane_height);
scenic::Material pane_material(session);
pane_material.SetColor(255 * colors[3 * i], 255 * colors[3 * i + 1],
255 * colors[3 * i + 2], 255);
scenic::ShapeNode pane_node(session);
pane_node.SetShape(pane_shape);
pane_node.SetMaterial(pane_material);
pane_node.SetTranslation((i + 0.5) * pane_width, 0.5 * pane_height, -20);
root_node.AddChild(pane_node);
}
// Display uncorrected version first.
session->Present(
0, [this](fuchsia::images::PresentationInfo info) { QuitLoop(); });
RunLoop();
// Take screenshot.
fuchsia::ui::scenic::ScreenshotData prev_screenshot = TakeScreenshot();
std::vector<uint8_t> prev_data;
EXPECT_TRUE(fsl::VectorFromVmo(prev_screenshot.data, &prev_data))
<< "Failed to read screenshot";
// Apply color correction.
compositor.SetColorConversion(preoffsets, matrix, postoffsets);
// Display color corrected version.
session->Present(
1000000, [this](fuchsia::images::PresentationInfo info) { QuitLoop(); });
RunLoop();
// Take screenshot.
fuchsia::ui::scenic::ScreenshotData post_screenshot = TakeScreenshot();
std::vector<uint8_t> post_data;
EXPECT_TRUE(fsl::VectorFromVmo(post_screenshot.data, &post_data))
<< "Failed to read screenshot";
// Lambda function for getting pixel based on normalized coordintes.
auto get_color = [&display_width, &display_height](
std::vector<uint8_t>& pixel_data, float x,
float y) -> scenic::Color {
auto pixels = reinterpret_cast<scenic::Color*>(pixel_data.data());
uint32_t index_x = x * display_width;
uint32_t index_y = y * display_height;
uint32_t index = index_y * display_width + index_x;
return pixels[index];
};
for (uint32_t i = 0; i < 5; i++) {
scenic::Color prev_color = get_color(prev_data, i * .2, 0.5);
scenic::Color post_color = get_color(post_data, i * .2, 0.5);
glm::vec4 vec =
glm_matrix * glm::vec4(prev_color.r, prev_color.g, prev_color.b, 1);
scenic::Color res(vec.x, vec.y, vec.z, vec.w);
EXPECT_EQ(res, post_color);
}
}
// This test sets up a scene, takes a screenshot, rotates display configuration
// by 90 degrees and takes a second screenshot, then makes sure that the pixels
// in both screenshots map onto each other how you would expect.
VK_TEST_F(ScenicPixelTest, RotationTest) {
// Synchronously get display dimensions.
float display_width;
float display_height;
scenic_->GetDisplayInfo([this, &display_width, &display_height](
fuchsia::ui::gfx::DisplayInfo display_info) {
display_width = static_cast<float>(display_info.width_in_px);
display_height = static_cast<float>(display_info.height_in_px);
QuitLoop();
});
RunLoop();
// Initialize session.
auto unique_session = std::make_unique<scenic::Session>(scenic_.get());
auto session = unique_session.get();
session->set_error_handler([this](zx_status_t status) {
FXL_LOG(ERROR) << "Session terminated.";
QuitLoop();
});
// Initialize components.
scenic::DisplayCompositor compositor(session);
scenic::LayerStack layer_stack(session);
scenic::Layer layer(session);
scenic::Renderer renderer(session);
scenic::Scene scene(session);
scenic::Camera camera(scene);
// Position camera at the center of the display, looking down
float eye_position[3] = {display_width / 2.f, display_height / 2.f, -1001};
float look_at[3] = {display_width / 2.f, display_height / 2.f, 1};
float up[3] = {0, -1, 0};
camera.SetTransform(eye_position, look_at, up);
camera.SetProjection(0);
// Setup.
compositor.SetLayerStack(layer_stack);
layer_stack.AddLayer(layer);
layer.SetSize(display_width, display_height);
layer.SetRenderer(renderer);
renderer.SetCamera(camera.id());
// Set up lights.
scenic::AmbientLight ambient_light(session);
scene.AddLight(ambient_light);
ambient_light.SetColor(1.f, 1.f, 1.f);
// Create an EntityNode to serve as the scene root.
scenic::EntityNode root_node(session);
scene.AddChild(root_node.id());
static const float pane_width = display_width / 5;
static const float pane_height = display_height;
// For this test, create 5 vertical bands. This is an array of
// the rgb colors for each of the five bands that will be
// created below.
float colors[15] = {1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0};
for (uint32_t i = 0; i < 5; i++) {
scenic::Rectangle pane_shape(session, pane_width, pane_height);
scenic::Material pane_material(session);
pane_material.SetColor(255 * colors[3 * i], 255 * colors[3 * i + 1],
255 * colors[3 * i + 2], 255);
scenic::ShapeNode pane_node(session);
pane_node.SetShape(pane_shape);
pane_node.SetMaterial(pane_material);
pane_node.SetTranslation((i + 0.5) * pane_width, 0.5 * pane_height, -20);
root_node.AddChild(pane_node);
}
// Display unrotated version first.
session->Present(
0, [this](fuchsia::images::PresentationInfo info) { QuitLoop(); });
RunLoop();
// Take screenshot.
fuchsia::ui::scenic::ScreenshotData prev_screenshot = TakeScreenshot();
std::vector<uint8_t> prev_data;
uint32_t prev_width = prev_screenshot.info.width;
uint32_t prev_height = prev_screenshot.info.height;
EXPECT_TRUE(fsl::VectorFromVmo(prev_screenshot.data, &prev_data))
<< "Failed to read screenshot";
// Rotate 90 degrees
compositor.SetLayoutRotation(90);
// Display rotated version.
session->Present(
1000000, [this](fuchsia::images::PresentationInfo info) { QuitLoop(); });
RunLoop();
// Take screenshot.
fuchsia::ui::scenic::ScreenshotData post_screenshot = TakeScreenshot();
std::vector<uint8_t> post_data;
uint32_t post_width = post_screenshot.info.width;
uint32_t post_height = post_screenshot.info.height;
EXPECT_TRUE(fsl::VectorFromVmo(post_screenshot.data, &post_data))
<< "Failed to read screenshot";
// The pre and post width and height should be the reverse of eachother.
EXPECT_TRUE(prev_width == post_height);
EXPECT_TRUE(prev_height == post_width);
// Lambda function for getting pixel values from the pre-rotated screenshot.
auto get_prev_color = [&prev_width, &prev_data](float x,
float y) -> scenic::Color {
auto pixels = reinterpret_cast<scenic::Color*>(prev_data.data());
uint32_t index = y * prev_width + x;
return pixels[index];
};
// Lambda function for getting pixel values from the post-rotated screenshot.
auto get_post_color = [&post_width, &post_data](float x,
float y) -> scenic::Color {
auto pixels = reinterpret_cast<scenic::Color*>(post_data.data());
uint32_t index = y * post_width + x;
return pixels[index];
};
// All of the colors should be transposed.
for (uint32_t x = 0; x < prev_width; x++) {
for (uint32_t y = 0; y < prev_height; y++) {
EXPECT_EQ(get_prev_color(x, y), get_post_color(y, x));
}
}
}
} // namespace