blob: 1aed4cf96779290246b662452637d3dc5a82828c [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gtest/gtest.h"
#include "lib/escher/util/image_utils.h"
#include "garnet/bin/ui/scene_manager/resources/image_pipe.h"
#include "garnet/bin/ui/scene_manager/sync/fence.h"
#include "garnet/bin/ui/scene_manager/tests/mocks.h"
#include "garnet/bin/ui/scene_manager/tests/session_test.h"
#include "garnet/bin/ui/scene_manager/tests/util.h"
#include "lib/ui/scenic/fidl_helpers.h"
#include "lib/ui/tests/test_with_message_loop.h"
namespace scene_manager {
namespace test {
class ImagePipeTest : public SessionTest, public escher::ResourceManager {
public:
ImagePipeTest()
: escher::ResourceManager(nullptr), command_buffer_sequencer_() {}
std::unique_ptr<Engine> CreateEngine() override {
auto r = std::make_unique<ReleaseFenceSignallerForTest>(
&command_buffer_sequencer_);
mock_release_fence_signaller_ = r.get();
return std::make_unique<EngineForTest>(&display_manager_, std::move(r));
}
void OnReceiveOwnable(std::unique_ptr<escher::Resource> resource) override {}
DisplayManager display_manager_;
escher::impl::CommandBufferSequencer command_buffer_sequencer_;
ReleaseFenceSignallerForTest* mock_release_fence_signaller_;
};
fxl::RefPtr<fsl::SharedVmo> CreateVmoWithBuffer(
size_t buffer_size,
std::unique_ptr<uint8_t[]> buffer_pixels) {
auto shared_vmo = CreateSharedVmo(buffer_size);
memcpy(shared_vmo->Map(), buffer_pixels.get(), buffer_size);
return shared_vmo;
}
fxl::RefPtr<fsl::SharedVmo> CreateVmoWithCheckerboardPixels(size_t w,
size_t h) {
size_t pixels_size;
auto pixels = escher::image_utils::NewCheckerboardPixels(w, h, &pixels_size);
return CreateVmoWithBuffer(pixels_size, std::move(pixels));
}
scenic::ImageInfoPtr CreateImageInfoForBgra8Image(size_t w, size_t h) {
auto image_info = scenic::ImageInfo::New();
image_info->pixel_format = scenic::ImageInfo::PixelFormat::BGRA_8;
image_info->tiling = scenic::ImageInfo::Tiling::LINEAR;
image_info->width = w;
image_info->height = h;
image_info->stride = w;
return image_info;
}
fxl::RefPtr<fsl::SharedVmo> CreateVmoWithGradientPixels(size_t w, size_t h) {
size_t pixels_size;
auto pixels = escher::image_utils::NewGradientPixels(w, h, &pixels_size);
return CreateVmoWithBuffer(pixels_size, std::move(pixels));
}
class ImagePipeThatCreatesDummyImages : public ImagePipe {
public:
ImagePipeThatCreatesDummyImages(
Session* session,
escher::ResourceManager* dummy_resource_manager)
: ImagePipe(session, 0u),
dummy_resource_manager_(dummy_resource_manager) {}
private:
// Override to create an Image without a backing escher::Image.
ImagePtr CreateImage(Session* session,
MemoryPtr memory,
const scenic::ImageInfoPtr& image_info,
uint64_t memory_offset,
ErrorReporter* error_reporter) override {
return Image::NewForTesting(session, 0u, dummy_resource_manager_, memory);
}
escher::ResourceManager* dummy_resource_manager_;
};
// Present two frames on the ImagePipe, making sure that acquire fence is being
// listened to and release fences are signalled.
TEST_F(ImagePipeTest, ImagePipeImageIdMustNotBeZero) {
ImagePipePtr image_pipe =
fxl::MakeRefCounted<ImagePipeThatCreatesDummyImages>(session_.get(),
this);
uint32_t imageId1 = 0;
// Create a checkerboard image and copy it into a vmo.
{
size_t image_dim = 100;
auto checkerboard = CreateVmoWithCheckerboardPixels(image_dim, image_dim);
auto image_info = CreateImageInfoForBgra8Image(image_dim, image_dim);
// Add the image to the image pipe with ImagePipe.AddImage().
image_pipe->AddImage(imageId1, std::move(image_info),
CopyVmo(checkerboard->vmo()),
scenic::MemoryType::HOST_MEMORY, 0);
EXPECT_EQ("ImagePipe::AddImage: Image can not be assigned an ID of 0.",
reported_errors_.back());
}
}
// Call Present with out-of-order presentation times, and expect an error.
TEST_F(ImagePipeTest, PresentImagesOutOfOrder) {
ImagePipePtr image_pipe =
fxl::MakeRefCounted<ImagePipeThatCreatesDummyImages>(session_.get(),
this);
uint32_t imageId1 = 1;
// Create a checkerboard image and copy it into a vmo.
{
size_t image_dim = 100;
auto checkerboard = CreateVmoWithCheckerboardPixels(image_dim, image_dim);
auto image_info = CreateImageInfoForBgra8Image(image_dim, image_dim);
// Add the image to the image pipe with ImagePipe.AddImage().
image_pipe->AddImage(imageId1, std::move(image_info),
CopyVmo(checkerboard->vmo()),
scenic::MemoryType::HOST_MEMORY, 0);
}
scenic::ImagePipe::PresentImageCallback callback = [](auto) {};
image_pipe->PresentImage(imageId1, 1, CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), callback);
image_pipe->PresentImage(imageId1, 0, CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), callback);
EXPECT_EQ(
"scene_manager::ImagePipe: Present called with out-of-order presentation "
"time.presentation_time=0, last scheduled presentation time=1",
reported_errors_.back());
}
// Call Present with in-order presentation times, and expect no error.
TEST_F(ImagePipeTest, PresentImagesInOrder) {
ImagePipePtr image_pipe =
fxl::MakeRefCounted<ImagePipeThatCreatesDummyImages>(session_.get(),
this);
uint32_t imageId1 = 1;
// Create a checkerboard image and copy it into a vmo.
{
size_t image_dim = 100;
auto checkerboard = CreateVmoWithCheckerboardPixels(image_dim, image_dim);
auto image_info = CreateImageInfoForBgra8Image(image_dim, image_dim);
// Add the image to the image pipe with ImagePipe.AddImage().
image_pipe->AddImage(imageId1, std::move(image_info),
CopyVmo(checkerboard->vmo()),
scenic::MemoryType::HOST_MEMORY, 0);
}
scenic::ImagePipe::PresentImageCallback callback = [](auto) {};
image_pipe->PresentImage(imageId1, 1, CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), callback);
image_pipe->PresentImage(imageId1, 1, CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), callback);
EXPECT_TRUE(reported_errors_.empty());
}
// Present two frames on the ImagePipe, making sure that acquire fence is
// being listened to and release fences are signalled.
TEST_F(ImagePipeTest, ImagePipePresentTwoFrames) {
ImagePipePtr image_pipe =
fxl::MakeRefCounted<ImagePipeThatCreatesDummyImages>(session_.get(),
this);
uint32_t imageId1 = 1;
// Create a checkerboard image and copy it into a vmo.
{
size_t image_dim = 100;
auto checkerboard = CreateVmoWithCheckerboardPixels(image_dim, image_dim);
auto image_info = CreateImageInfoForBgra8Image(image_dim, image_dim);
// Add the image to the image pipe with ImagePipe.AddImage().
image_pipe->AddImage(imageId1, std::move(image_info),
CopyVmo(checkerboard->vmo()),
scenic::MemoryType::HOST_MEMORY, 0);
}
// Make checkerboard the currently displayed image.
zx::event acquire_fence1 = CreateEvent();
zx::event release_fence1 = CreateEvent();
image_pipe->PresentImage(imageId1, 0, CopyEventIntoFidlArray(acquire_fence1),
CopyEventIntoFidlArray(release_fence1), nullptr);
// Current presented image should be null, since we haven't signalled
// acquire fence yet.
::mozart::test::RunLoopWithTimeout(kPumpMessageLoopDuration);
ASSERT_FALSE(image_pipe->GetEscherImage());
// Signal on the acquire fence.
acquire_fence1.signal(0u, kFenceSignalled);
// Run until image1 is presented.
for (int i = 0; !image_pipe->GetEscherImage() && i < 400; i++) {
image_pipe->Update(0u, 0u);
::mozart::test::RunLoopWithTimeout(fxl::TimeDelta::FromMilliseconds(10));
}
ASSERT_TRUE(image_pipe->GetEscherImage());
escher::ImagePtr image1 = image_pipe->GetEscherImage();
// Image should now be presented.
ASSERT_TRUE(image1);
uint32_t imageId2 = 2;
// Create a new Image with a gradient.
{
size_t image_dim = 100;
auto gradient = CreateVmoWithGradientPixels(image_dim, image_dim);
auto image_info = CreateImageInfoForBgra8Image(image_dim, image_dim);
// Add the image to the image pipe.
image_pipe->AddImage(imageId2, std::move(image_info),
CopyVmo(gradient->vmo()),
scenic::MemoryType::HOST_MEMORY, 0);
}
// The first image should not have been released.
::mozart::test::RunLoopWithTimeout(kPumpMessageLoopDuration);
ASSERT_FALSE(IsEventSignalled(release_fence1, kFenceSignalled));
// Make gradient the currently displayed image.
zx::event acquire_fence2 = CreateEvent();
zx::event release_fence2 = CreateEvent();
image_pipe->PresentImage(imageId2, 0, CopyEventIntoFidlArray(acquire_fence2),
CopyEventIntoFidlArray(release_fence2), nullptr);
// Verify that the currently display image hasn't changed yet, since we
// haven't signalled the acquire fence.
::mozart::test::RunLoopWithTimeout(kPumpMessageLoopDuration);
ASSERT_EQ(image_pipe->GetEscherImage(), image1);
// Signal on the acquire fence.
acquire_fence2.signal(0u, kFenceSignalled);
// There should be a new image presented.
RUN_MESSAGE_LOOP_UNTIL(image1 != image_pipe->GetEscherImage());
escher::ImagePtr image2 = image_pipe->GetEscherImage();
ASSERT_TRUE(image2);
ASSERT_NE(image1, image2);
// The first image should have been released.
ASSERT_EQ(mock_release_fence_signaller_->num_calls_to_add_cpu_release_fence(),
1u);
ASSERT_TRUE(IsEventSignalled(release_fence1, kFenceSignalled));
ASSERT_FALSE(IsEventSignalled(release_fence2, kFenceSignalled));
}
// TODO(MZ-151): More tests.
// - Test that you can't add the same image twice.
// - Test that you can't present an image that doesn't exist.
// - Test what happens when an acquire fence is closed on the client end.
// - Test what happens if you present an image twice.
} // namespace test
} // namespace scene_manager