blob: aa3d5f7249b11e72fc453ab3a924254397edfe91 [file] [log] [blame]
// Copyright 2019 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/scenic/lib/gfx/resources/image_pipe2.h"
#include <lib/fdio/directory.h>
#include <lib/ui/scenic/cpp/commands.h>
#include "gtest/gtest.h"
#include "src/ui/lib/escher/flib/fence.h"
#include "src/ui/lib/escher/test/gtest_vulkan.h"
#include "src/ui/lib/escher/util/image_utils.h"
#include "src/ui/scenic/lib/gfx/engine/image_pipe_updater.h"
#include "src/ui/scenic/lib/gfx/tests/error_reporting_test.h"
#include "src/ui/scenic/lib/gfx/tests/image_pipe_unittest_common.h"
#include "src/ui/scenic/lib/gfx/tests/mocks/mocks.h"
#include "src/ui/scenic/lib/gfx/tests/mocks/util.h"
#include "src/ui/scenic/lib/gfx/tests/vk_session_test.h"
namespace scenic_impl::gfx::test {
namespace {
struct SysmemTokens {
fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token;
fuchsia::sysmem::BufferCollectionTokenSyncPtr dup_token;
};
SysmemTokens CreateSysmemTokens(fuchsia::sysmem::Allocator_Sync* sysmem_allocator,
bool duplicate_token) {
fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token;
zx_status_t status = sysmem_allocator->AllocateSharedCollection(local_token.NewRequest());
EXPECT_EQ(status, ZX_OK);
fuchsia::sysmem::BufferCollectionTokenSyncPtr dup_token;
if (duplicate_token) {
status = local_token->Duplicate(std::numeric_limits<uint32_t>::max(), dup_token.NewRequest());
EXPECT_EQ(status, ZX_OK);
}
status = local_token->Sync();
EXPECT_EQ(status, ZX_OK);
return {std::move(local_token), std::move(dup_token)};
}
void SetConstraints(fuchsia::sysmem::Allocator_Sync* sysmem_allocator,
fuchsia::sysmem::BufferCollectionTokenSyncPtr token, uint32_t width,
uint32_t height, uint32_t image_count,
fuchsia::sysmem::PixelFormatType pixel_format, bool wait_for_buffers_allocated,
fuchsia::sysmem::BufferCollectionSyncPtr* buffer_collection_output) {
fuchsia::sysmem::BufferCollectionSyncPtr buffer_collection;
zx_status_t status =
sysmem_allocator->BindSharedCollection(std::move(token), buffer_collection.NewRequest());
EXPECT_EQ(status, ZX_OK);
fuchsia::sysmem::BufferCollectionConstraints constraints;
constraints.min_buffer_count = image_count;
constraints.usage.vulkan = fuchsia::sysmem::vulkanUsageTransferSrc;
if (width && height) {
constraints.image_format_constraints_count = 1;
fuchsia::sysmem::ImageFormatConstraints& image_constraints =
constraints.image_format_constraints[0];
image_constraints = fuchsia::sysmem::ImageFormatConstraints();
image_constraints.required_min_coded_width = width;
image_constraints.required_min_coded_height = height;
image_constraints.required_max_coded_width = width;
image_constraints.required_max_coded_height = height;
image_constraints.max_coded_width = width * 4;
image_constraints.max_coded_height = width * 4;
image_constraints.max_bytes_per_row = 0xffffffff;
image_constraints.pixel_format.type = pixel_format;
image_constraints.color_spaces_count = 1;
switch (pixel_format) {
case fuchsia::sysmem::PixelFormatType::BGRA32:
case fuchsia::sysmem::PixelFormatType::R8G8B8A8:
image_constraints.color_space[0].type = fuchsia::sysmem::ColorSpaceType::SRGB;
break;
case fuchsia::sysmem::PixelFormatType::I420:
case fuchsia::sysmem::PixelFormatType::NV12:
image_constraints.color_space[0].type = fuchsia::sysmem::ColorSpaceType::REC709;
break;
default:
FXL_NOTREACHED();
}
}
status = buffer_collection->SetConstraints(true, constraints);
EXPECT_EQ(status, ZX_OK);
if (wait_for_buffers_allocated) {
zx_status_t allocation_status = ZX_OK;
fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info = {};
status =
buffer_collection->WaitForBuffersAllocated(&allocation_status, &buffer_collection_info);
EXPECT_EQ(status, ZX_OK);
EXPECT_EQ(allocation_status, ZX_OK);
EXPECT_GE(buffer_collection_info.buffer_count, image_count);
}
if (buffer_collection_output) {
*buffer_collection_output = std::move(buffer_collection);
} else {
status = buffer_collection->Close();
EXPECT_EQ(status, ZX_OK);
}
}
} // namespace
class CreateImagePipe2CmdTest : public VkSessionTest {};
VK_TEST_F(CreateImagePipe2CmdTest, ApplyCommand) {
zx::channel image_pipe_endpoint;
zx::channel remote_endpoint;
zx::channel::create(0, &image_pipe_endpoint, &remote_endpoint);
const uint32_t kImagePipeId = 1;
ASSERT_TRUE(Apply(scenic::NewCreateImagePipe2Cmd(
kImagePipeId,
fidl::InterfaceRequest<fuchsia::images::ImagePipe2>(std::move(remote_endpoint)))));
}
class ImagePipe2ThatCreatesFakeImages : public ImagePipe2 {
public:
ImagePipe2ThatCreatesFakeImages(gfx::Session* session, std::unique_ptr<ImagePipeUpdater> updater,
fidl::InterfaceRequest<fuchsia::images::ImagePipe2> request,
escher::ResourceManager* fake_resource_manager)
: ImagePipe2(session, 0u, std::move(request), std::move(updater),
session->shared_error_reporter()),
fake_resource_manager_(fake_resource_manager) {
zx_status_t status = fdio_service_connect(
"/svc/fuchsia.sysmem.Allocator", sysmem_allocator_.NewRequest().TakeChannel().release());
EXPECT_EQ(status, ZX_OK);
}
~ImagePipe2ThatCreatesFakeImages() { CloseConnectionAndCleanUp(); }
ImagePipeUpdateResults Update(scheduling::PresentId present_id) override {
auto result = ImagePipe2::Update(present_id);
if (result.image_updated) {
static_cast<FakeImage*>(current_image().get())->update_count_++;
}
return result;
}
fuchsia::sysmem::Allocator_Sync* sysmem_allocator() { return sysmem_allocator_.get(); }
void set_next_image_is_protected(bool is_protected) { next_image_is_protected_ = is_protected; }
fuchsia::sysmem::PixelFormatType pixel_format_;
std::vector<fxl::RefPtr<FakeImage>> fake_images_;
private:
bool SetBufferCollectionConstraints(
Session* session, fuchsia::sysmem::BufferCollectionTokenSyncPtr token,
const vk::ImageCreateInfo& create_info,
vk::BufferCollectionFUCHSIA* out_buffer_collection_fuchsia) override {
SetConstraints(sysmem_allocator_.get(), std::move(token), 0u, 0u, 1u,
fuchsia::sysmem::PixelFormatType::BGRA32, false, nullptr);
return true;
}
void DestroyBufferCollection(Session* session,
const vk::BufferCollectionFUCHSIA& vk_buffer_collection) override {}
ImagePtr CreateImage(Session* session, ResourceId image_id,
const ImagePipe2::BufferCollectionInfo& info,
uint32_t buffer_collection_index,
const ::fuchsia::sysmem::ImageFormat_2& image_format) override {
pixel_format_ = info.buffer_collection_info.settings.image_format_constraints.pixel_format.type;
escher::ImageInfo escher_info;
escher_info.width = image_format.coded_width;
escher_info.height = image_format.coded_height;
if (next_image_is_protected_) {
escher_info.memory_flags |= vk::MemoryPropertyFlagBits::eProtected;
next_image_is_protected_ = false;
}
escher::ImagePtr escher_image = escher::Image::WrapVkImage(
fake_resource_manager_, escher_info, vk::Image(), vk::ImageLayout::eUndefined);
FXL_CHECK(escher_image);
auto image = fxl::AdoptRef(new FakeImage(session, image_id, escher_image));
fake_images_.push_back(image);
return image;
}
fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator_;
escher::ResourceManager* fake_resource_manager_;
bool next_image_is_protected_ = false;
};
// Creates test environment.
class ImagePipe2Test : public ErrorReportingTest, public escher::ResourceManager {
public:
ImagePipe2Test() : escher::ResourceManager(escher::EscherWeakPtr()) {}
void OnReceiveOwnable(std::unique_ptr<escher::Resource> resource) override {}
void SetUp() override {
ErrorReportingTest::SetUp();
gfx_session_ = std::make_unique<gfx::Session>(/*id=*/1, SessionContext{},
shared_event_reporter(), shared_error_reporter());
auto updater = std::make_unique<MockImagePipeUpdater>();
image_pipe_updater_ = updater.get();
image_pipe_ = fxl::MakeRefCounted<ImagePipe2ThatCreatesFakeImages>(
gfx_session_.get(), std::move(updater), image_pipe_handle_.NewRequest(), this);
}
void TearDown() override {
image_pipe_.reset();
image_pipe_updater_ = nullptr;
gfx_session_.reset();
ErrorReportingTest::TearDown();
}
fxl::RefPtr<ImagePipe2ThatCreatesFakeImages> image_pipe_;
MockImagePipeUpdater* image_pipe_updater_;
private:
std::unique_ptr<gfx::Session> gfx_session_;
fidl::InterfacePtr<fuchsia::images::ImagePipe2> image_pipe_handle_;
};
// Present a BufferCollection with an Id of zero, and expect an error.
TEST_F(ImagePipe2Test, BufferCollectionIdMustNotBeZero) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), false);
const uint32_t kBufferId = 0;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
ExpectLastReportedError("AddBufferCollection: BufferCollection can not be assigned an ID of 0.");
}
// Present an image with an Id of zero, and expect an error.
TEST_F(ImagePipe2Test, ImagePipeImageIdMustNotBeZero) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), false);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kImageId = 0;
image_pipe_->AddImage(kImageId, kBufferId, 0, fuchsia::sysmem::ImageFormat_2());
ExpectLastReportedError("AddImage: Image can not be assigned an ID of 0.");
}
// Add multiple images from same buffer collection.
TEST_F(ImagePipe2Test, AddMultipleImagesFromABufferCollection) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
const uint32_t kImageCount = 2;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight,
kImageCount, fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
const uint32_t kImageId1 = 1;
image_pipe_->AddImage(kImageId1, kBufferId, 0, image_format);
const uint32_t kImageId2 = 2;
image_pipe_->AddImage(kImageId2, kBufferId, 1, image_format);
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
}
// Add multiple images from an invalid buffer collection id.
TEST_F(ImagePipe2Test, BufferCollectionIdMustBeValid) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
const uint32_t kImageCount = 2;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight,
kImageCount, fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
const uint32_t kImageId1 = 1;
image_pipe_->AddImage(kImageId1, kBufferId, 0, image_format);
const uint32_t kImageId2 = 2;
image_pipe_->AddImage(kImageId2, kBufferId + 1, 1, image_format);
ExpectLastReportedError("AddImage: resource with ID not found.");
}
// Add multiple images from same buffer collection.
TEST_F(ImagePipe2Test, BufferCollectionIndexMustBeValid) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
const uint32_t kImageCount = 2;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight,
kImageCount, fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
const uint32_t kImageId1 = 1;
image_pipe_->AddImage(kImageId1, kBufferId, 0, image_format);
const uint32_t kImageId2 = 2;
image_pipe_->AddImage(kImageId2, kBufferId, kImageCount, image_format);
ExpectLastReportedError("AddImage: buffer_collection_index out of bounds");
}
// Removing buffer collection removes associated images.
TEST_F(ImagePipe2Test, RemoveBufferCollectionRemovesImages) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
const uint32_t kImageCount = 2;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight,
kImageCount, fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
const uint32_t kImageId1 = 1;
image_pipe_->AddImage(kImageId1, kBufferId, 0, image_format);
image_pipe_->PresentImage(kImageId1, zx::time(0), std::vector<zx::event>(),
std::vector<zx::event>(), /*callback=*/[](auto) {});
// Remove buffer collection
image_pipe_->RemoveBufferCollection(kBufferId);
image_pipe_->PresentImage(kImageId1, zx::time(0), std::vector<zx::event>(),
std::vector<zx::event>(), /*callback=*/[](auto) {});
ExpectLastReportedError("PresentImage: could not find Image with ID: 1");
}
// Call Present with in-order presentation times, and expect no error.
TEST_F(ImagePipe2Test, PresentImage_ShouldCallScheduleUpdate) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight, 1u,
fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
const uint32_t kImageId = 1;
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
image_pipe_->AddImage(kImageId, kBufferId, 0, image_format);
EXPECT_EQ(image_pipe_updater_->schedule_update_call_count_, 0u);
image_pipe_->PresentImage(kImageId, zx::time(1), CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), /*callback=*/[](auto) {});
EXPECT_EQ(image_pipe_updater_->schedule_update_call_count_, 1u);
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
}
// Call Present with out-of-order presentation times, and expect an error.
TEST_F(ImagePipe2Test, PresentImagesOutOfOrder) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight, 1u,
fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
const uint32_t kImageId = 1;
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
image_pipe_->AddImage(kImageId, kBufferId, 0, image_format);
image_pipe_->PresentImage(kImageId, zx::time(1), CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), /*callback=*/[](auto) {});
image_pipe_->PresentImage(kImageId, zx::time(0), CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), /*callback=*/[](auto) {});
ExpectLastReportedError(
"PresentImage: Present called with out-of-order presentation "
"time. presentation_time=0, last scheduled presentation time=1");
}
// Call Present with in-order presentation times, and expect no error.
TEST_F(ImagePipe2Test, PresentImagesInOrder) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight, 1u,
fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
const uint32_t kImageId = 1;
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
image_pipe_->AddImage(kImageId, kBufferId, 0, image_format);
image_pipe_->PresentImage(kImageId, zx::time(1), CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), /*callback=*/[](auto) {});
image_pipe_->PresentImage(kImageId, zx::time(1), CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), /*callback=*/[](auto) {});
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
}
// Call Present with an image with an odd size(possible offset) into its memory, and expect no
// error.
TEST_F(ImagePipe2Test, PresentImagesWithOddSize) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 35;
const uint32_t kHeight = 35;
fuchsia::sysmem::BufferCollectionSyncPtr buffer_collection;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight, 1u,
fuchsia::sysmem::PixelFormatType::BGRA32, true, &buffer_collection);
const uint32_t kImageId = 1;
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
image_pipe_->AddImage(kImageId, kBufferId, 0, image_format);
image_pipe_->PresentImage(kImageId, zx::time(1), CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), /*callback=*/[](auto) {});
image_pipe_->PresentImage(kImageId, zx::time(1), CopyEventIntoFidlArray(CreateEvent()),
CopyEventIntoFidlArray(CreateEvent()), /*callback=*/[](auto) {});
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
}
// Present two frames on the ImagePipe, making sure that both buffers are allocated, and that both
// are updated with their respective Update calls.
TEST_F(ImagePipe2Test, ImagePipePresentTwoFrames) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
const uint32_t kImageCount = 2;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight,
kImageCount, fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
const uint32_t kImageId1 = 1;
image_pipe_->AddImage(kImageId1, kBufferId, 0, image_format);
const auto present_id =
image_pipe_->PresentImage(kImageId1, zx::time(0), /*acquire_fences=*/{},
/*release_fences=*/{}, /*callback=*/[](auto) {});
// Current presented image should be null, since we haven't called Update yet.
ASSERT_FALSE(image_pipe_->current_image());
ASSERT_FALSE(image_pipe_->GetEscherImage());
image_pipe_->Update(present_id);
ASSERT_TRUE(image_pipe_->current_image());
ASSERT_FALSE(image_pipe_->GetEscherImage());
// Image should now be presented.
ImagePtr image1 = image_pipe_->current_image();
ASSERT_TRUE(image1);
const uint32_t kImageId2 = 2;
image_pipe_->AddImage(kImageId2, kBufferId, 1, image_format);
const auto present_id2 =
image_pipe_->PresentImage(kImageId2, zx::time(0), /*acquire_fences=*/{},
/*release_fences=*/{}, /*callback=*/[](auto) {});
// Verify that the currently display image hasn't changed yet, since we
// haven't called Update yet.
ASSERT_FALSE(image_pipe_->GetEscherImage());
ASSERT_EQ(image_pipe_->current_image(), image1);
image_pipe_->Update(present_id2);
// There should be a new image presented.
ASSERT_FALSE(image_pipe_->GetEscherImage());
ImagePtr image2 = image_pipe_->current_image();
ASSERT_TRUE(image2);
ASSERT_NE(image1, image2);
}
// Present two frames on the ImagePipe and skip one, making sure that UpdatePixels is only called on
// images that are used.
TEST_F(ImagePipe2Test, ImagePipeUpdateTwoFrames) {
// Add first image 32x32
auto tokens1 = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBuffer1Id = 1;
image_pipe_->AddBufferCollection(kBuffer1Id, std::move(tokens1.local_token));
const uint32_t kImage1Width = 32;
const uint32_t kImage1Height = 32;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens1.dup_token), kImage1Width,
kImage1Height, 1u, fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
fuchsia::sysmem::ImageFormat_2 image_format1 = {};
image_format1.coded_width = kImage1Width;
image_format1.coded_height = kImage1Height;
const uint32_t kImageId1 = 1;
image_pipe_->AddImage(kImageId1, kBuffer1Id, 0, image_format1);
// Add second image 48x48
auto tokens2 = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBuffer2Id = 2;
image_pipe_->AddBufferCollection(kBuffer2Id, std::move(tokens2.local_token));
const uint32_t kImage2Width = 48;
const uint32_t kImage2Height = 48;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens2.dup_token), kImage2Width,
kImage2Height, 1u, fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
fuchsia::sysmem::ImageFormat_2 image_format_2 = {};
image_format_2.coded_width = kImage2Width;
image_format_2.coded_height = kImage2Height;
const uint32_t kImageId2 = 2;
image_pipe_->AddImage(kImageId2, kBuffer2Id, 0, image_format_2);
// Present both images
image_pipe_->PresentImage(kImageId1, zx::time(0), std::vector<zx::event>(),
std::vector<zx::event>(), /*callback=*/[](auto) {});
const auto present_id =
image_pipe_->PresentImage(kImageId2, zx::time(0), std::vector<zx::event>(),
std::vector<zx::event>(), /*callback=*/[](auto) {});
image_pipe_->Update(present_id);
auto image_out = image_pipe_->current_image();
// We should get the second image in the queue, since both should have been ready.
ASSERT_TRUE(image_out);
ASSERT_FALSE(image_pipe_->GetEscherImage());
ASSERT_EQ(static_cast<FakeImage*>(image_out.get())->image_info_.width, kImage2Width);
ASSERT_EQ(image_pipe_->fake_images_.size(), 2u);
ASSERT_EQ(image_pipe_->fake_images_[0]->update_count_, 0u);
ASSERT_EQ(image_pipe_->fake_images_[1]->update_count_, 1u);
// Do it again, to make sure that update is called a second time (since released images could be
// edited by the client before presentation).
//
// In this case, we need to run to idle after presenting image A, so that image B is returned by
// the pool, marked dirty, and is free to be acquired again.
const auto present_id2 =
image_pipe_->PresentImage(kImageId1, zx::time(0), std::vector<zx::event>(),
std::vector<zx::event>(), /*callback=*/[](auto) {});
image_pipe_->Update(present_id2);
const auto present_id3 =
image_pipe_->PresentImage(kImageId2, zx::time(0), std::vector<zx::event>(),
std::vector<zx::event>(), /*callback=*/[](auto) {});
image_pipe_->Update(present_id3);
image_out = image_pipe_->current_image();
ASSERT_EQ(image_pipe_->fake_images_.size(), 2u);
// Because Present was handled for image 1, we should have a call to
// UpdatePixels for that image.
ASSERT_EQ(image_pipe_->fake_images_[0]->update_count_, 1u);
ASSERT_EQ(image_pipe_->fake_images_[1]->update_count_, 2u);
}
// Present two frames on the ImagePipe. After presenting the first image but before signaling its
// acquire fence, remove it. Verify that this doesn't cause any errors.
TEST_F(ImagePipe2Test, ImagePipeRemoveImageThatIsPendingPresent) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
const uint32_t kImageCount = 2;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight,
kImageCount, fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
// Add first image
const uint32_t kImageId1 = 1;
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
image_pipe_->AddImage(kImageId1, kBufferId, 0, image_format);
const auto present_id =
image_pipe_->PresentImage(kImageId1, zx::time(0), /*acquire_fences=*/{},
/*release_fences=*/{}, /*callback=*/[](auto) {});
// Current presented image should be null, since we haven't called Update yet.
ASSERT_FALSE(image_pipe_->current_image());
ASSERT_FALSE(image_pipe_->GetEscherImage());
// Remove the image; by the ImagePipe semantics, the consumer will
// still keep a reference to it so any future presents will still work.
image_pipe_->RemoveImage(kImageId1);
// Update to image1.
image_pipe_->Update(present_id);
ASSERT_TRUE(image_pipe_->current_image());
ASSERT_FALSE(image_pipe_->GetEscherImage());
ImagePtr image1 = image_pipe_->current_image();
// Image should now be presented.
ASSERT_TRUE(image1);
// Add second image
const uint32_t kImageId2 = 2;
image_pipe_->AddImage(kImageId2, kBufferId, 1, image_format);
// Make gradient the currently displayed image.
const auto present_id2 =
image_pipe_->PresentImage(kImageId2, zx::time(0), /*acquire_fences=*/{},
/*release_fences=*/{}, /*callback=*/[](auto) {});
// Verify that the currently display image hasn't changed yet, since we haven't called Update yet.
ASSERT_EQ(image_pipe_->current_image(), image1);
// Update to image2.
image_pipe_->Update(present_id2);
// There should be a new image presented.
ImagePtr image2 = image_pipe_->current_image();
ASSERT_TRUE(image2);
ASSERT_FALSE(image_pipe_->GetEscherImage());
ASSERT_NE(image1, image2);
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
}
// Detects protected memory backed image added.
TEST_F(ImagePipe2Test, DetectsProtectedMemory) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
const uint32_t kImageCount = 2;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight,
kImageCount, fuchsia::sysmem::PixelFormatType::BGRA32, true, nullptr);
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
const uint32_t kImageId1 = 1;
image_pipe_->AddImage(kImageId1, kBufferId, 0, image_format);
ASSERT_FALSE(image_pipe_->use_protected_memory());
image_pipe_->set_next_image_is_protected(true);
const uint32_t kImageId2 = 2;
image_pipe_->AddImage(kImageId2, kBufferId, 1, image_format);
ASSERT_TRUE(image_pipe_->use_protected_memory());
image_pipe_->RemoveImage(kImageId2);
ASSERT_FALSE(image_pipe_->use_protected_memory());
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
}
// Checks all supported pixel formats can be added.
TEST_F(ImagePipe2Test, SupportsMultiplePixelFormats) {
std::vector<fuchsia::sysmem::PixelFormatType> formats{
fuchsia::sysmem::PixelFormatType::BGRA32, fuchsia::sysmem::PixelFormatType::I420,
fuchsia::sysmem::PixelFormatType::NV12, fuchsia::sysmem::PixelFormatType::R8G8B8A8};
for (auto format : formats) {
auto tokens = CreateSysmemTokens(image_pipe_->sysmem_allocator(), true);
const uint32_t kBufferId = 1;
image_pipe_->AddBufferCollection(kBufferId, std::move(tokens.local_token));
const uint32_t kWidth = 32;
const uint32_t kHeight = 32;
const uint32_t kImageCount = 1;
SetConstraints(image_pipe_->sysmem_allocator(), std::move(tokens.dup_token), kWidth, kHeight,
kImageCount, format, true, nullptr);
fuchsia::sysmem::ImageFormat_2 image_format = {};
image_format.coded_width = kWidth;
image_format.coded_height = kHeight;
const uint32_t kImageId1 = 1;
image_pipe_->AddImage(kImageId1, kBufferId, 0, image_format);
EXPECT_EQ(format, image_pipe_->pixel_format_);
image_pipe_->RemoveBufferCollection(kBufferId);
}
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
}
// TODO(23406): 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 scenic_impl::gfx::test