| // Copyright 2018 The Chromium 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 "components/viz/service/display/display_resource_provider.h" |
| #include "components/viz/client/client_resource_provider.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/shared_memory.h" |
| #include "build/build_config.h" |
| #include "cc/test/render_pass_test_utils.h" |
| #include "cc/test/resource_provider_test_utils.h" |
| #include "components/viz/common/resources/bitmap_allocation.h" |
| #include "components/viz/common/resources/resource_format_utils.h" |
| #include "components/viz/common/resources/returned_resource.h" |
| #include "components/viz/common/resources/shared_bitmap.h" |
| #include "components/viz/common/resources/single_release_callback.h" |
| #include "components/viz/service/display/shared_bitmap_manager.h" |
| #include "components/viz/test/test_context_provider.h" |
| #include "components/viz/test/test_gles2_interface.h" |
| #include "components/viz/test/test_gpu_memory_buffer_manager.h" |
| #include "components/viz/test/test_shared_bitmap_manager.h" |
| #include "components/viz/test/test_texture.h" |
| #include "gpu/GLES2/gl2extchromium.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/khronos/GLES2/gl2.h" |
| #include "third_party/khronos/GLES2/gl2ext.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/gpu_memory_buffer.h" |
| |
| using testing::_; |
| using testing::Return; |
| using testing::SaveArg; |
| |
| namespace viz { |
| namespace { |
| |
| class MockReleaseCallback { |
| public: |
| MOCK_METHOD2(Released, void(const gpu::SyncToken& token, bool lost)); |
| }; |
| |
| MATCHER_P(MatchesSyncToken, sync_token, "") { |
| gpu::SyncToken other; |
| memcpy(&other, arg, sizeof(other)); |
| return other == sync_token; |
| } |
| |
| static void CollectResources(std::vector<ReturnedResource>* array, |
| const std::vector<ReturnedResource>& returned) { |
| array->insert(array->end(), returned.begin(), returned.end()); |
| } |
| |
| static SharedBitmapId CreateAndFillSharedBitmap(SharedBitmapManager* manager, |
| const gfx::Size& size, |
| ResourceFormat format, |
| uint32_t value) { |
| SharedBitmapId shared_bitmap_id = SharedBitmap::GenerateId(); |
| |
| std::unique_ptr<base::SharedMemory> shm = |
| bitmap_allocation::AllocateMappedBitmap(size, RGBA_8888); |
| manager->ChildAllocatedSharedBitmap( |
| bitmap_allocation::DuplicateAndCloseMappedBitmap(shm.get(), size, |
| RGBA_8888), |
| shared_bitmap_id); |
| |
| std::fill_n(static_cast<uint32_t*>(shm->memory()), size.GetArea(), value); |
| return shared_bitmap_id; |
| } |
| |
| // Shared data between multiple ResourceProviderGLES2Interface. This contains |
| // mailbox contents as well as information about sync points. |
| class ContextSharedData { |
| public: |
| static std::unique_ptr<ContextSharedData> Create() { |
| return base::WrapUnique(new ContextSharedData()); |
| } |
| |
| uint32_t InsertFenceSync() { return next_fence_sync_++; } |
| |
| void GenMailbox(GLbyte* mailbox) { |
| memset(mailbox, 0, GL_MAILBOX_SIZE_CHROMIUM); |
| memcpy(mailbox, &next_mailbox_, sizeof(next_mailbox_)); |
| ++next_mailbox_; |
| } |
| |
| void ProduceTexture(const GLbyte* mailbox_name, |
| const gpu::SyncToken& sync_token, |
| scoped_refptr<TestTexture> texture) { |
| uint32_t sync_point = static_cast<uint32_t>(sync_token.release_count()); |
| |
| unsigned mailbox = 0; |
| memcpy(&mailbox, mailbox_name, sizeof(mailbox)); |
| ASSERT_TRUE(mailbox && mailbox < next_mailbox_); |
| textures_[mailbox] = texture; |
| ASSERT_LT(sync_point_for_mailbox_[mailbox], sync_point); |
| sync_point_for_mailbox_[mailbox] = sync_point; |
| } |
| |
| scoped_refptr<TestTexture> ConsumeTexture(const GLbyte* mailbox_name, |
| const gpu::SyncToken& sync_token) { |
| unsigned mailbox = 0; |
| memcpy(&mailbox, mailbox_name, sizeof(mailbox)); |
| DCHECK(mailbox && mailbox < next_mailbox_); |
| |
| // If the latest sync point the context has waited on is before the sync |
| // point for when the mailbox was set, pretend we never saw that |
| // ProduceTexture. |
| if (sync_point_for_mailbox_[mailbox] > sync_token.release_count()) { |
| NOTREACHED(); |
| return scoped_refptr<TestTexture>(); |
| } |
| return textures_[mailbox]; |
| } |
| |
| private: |
| ContextSharedData() : next_fence_sync_(1), next_mailbox_(1) {} |
| |
| uint64_t next_fence_sync_; |
| unsigned next_mailbox_; |
| using TextureMap = std::unordered_map<unsigned, scoped_refptr<TestTexture>>; |
| TextureMap textures_; |
| std::unordered_map<unsigned, uint32_t> sync_point_for_mailbox_; |
| }; |
| |
| class ResourceProviderGLES2Interface : public TestGLES2Interface { |
| public: |
| explicit ResourceProviderGLES2Interface(ContextSharedData* shared_data) |
| : shared_data_(shared_data) {} |
| |
| void GenSyncTokenCHROMIUM(GLbyte* sync_token) override { |
| uint64_t fence_sync = shared_data_->InsertFenceSync(); |
| gpu::SyncToken sync_token_data(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x123), |
| fence_sync); |
| sync_token_data.SetVerifyFlush(); |
| // Commit the ProduceTextureDirectCHROMIUM calls at this point, so that |
| // they're associated with the sync point. |
| for (const std::unique_ptr<PendingProduceTexture>& pending_texture : |
| pending_produce_textures_) { |
| shared_data_->ProduceTexture(pending_texture->mailbox, sync_token_data, |
| pending_texture->texture); |
| } |
| pending_produce_textures_.clear(); |
| memcpy(sync_token, &sync_token_data, sizeof(sync_token_data)); |
| } |
| |
| void WaitSyncTokenCHROMIUM(const GLbyte* sync_token) override { |
| gpu::SyncToken sync_token_data; |
| if (sync_token) |
| memcpy(&sync_token_data, sync_token, sizeof(sync_token_data)); |
| |
| if (sync_token_data.release_count() > |
| last_waited_sync_token_.release_count()) { |
| last_waited_sync_token_ = sync_token_data; |
| } |
| } |
| |
| const gpu::SyncToken& last_waited_sync_token() const { |
| return last_waited_sync_token_; |
| } |
| |
| void TexStorage2DEXT(GLenum target, |
| GLint levels, |
| GLuint internalformat, |
| GLint width, |
| GLint height) override { |
| CheckTextureIsBound(target); |
| ASSERT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target); |
| ASSERT_EQ(1, levels); |
| GLenum format = GL_RGBA; |
| switch (internalformat) { |
| case GL_RGBA8_OES: |
| break; |
| case GL_BGRA8_EXT: |
| format = GL_BGRA_EXT; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| AllocateTexture(gfx::Size(width, height), format); |
| } |
| |
| void TexImage2D(GLenum target, |
| GLint level, |
| GLint internalformat, |
| GLsizei width, |
| GLsizei height, |
| GLint border, |
| GLenum format, |
| GLenum type, |
| const void* pixels) override { |
| CheckTextureIsBound(target); |
| ASSERT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target); |
| ASSERT_FALSE(level); |
| ASSERT_EQ(internalformat, static_cast<GLint>(format)); |
| ASSERT_FALSE(border); |
| ASSERT_EQ(static_cast<unsigned>(GL_UNSIGNED_BYTE), type); |
| AllocateTexture(gfx::Size(width, height), format); |
| if (pixels) |
| SetPixels(0, 0, width, height, pixels); |
| } |
| |
| void TexSubImage2D(GLenum target, |
| GLint level, |
| GLint xoffset, |
| GLint yoffset, |
| GLsizei width, |
| GLsizei height, |
| GLenum format, |
| GLenum type, |
| const void* pixels) override { |
| CheckTextureIsBound(target); |
| ASSERT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target); |
| ASSERT_FALSE(level); |
| ASSERT_EQ(static_cast<unsigned>(GL_UNSIGNED_BYTE), type); |
| { |
| base::AutoLock lock_for_texture_access(namespace_->lock); |
| ASSERT_EQ(GLDataFormat(BoundTexture(target)->format), format); |
| } |
| ASSERT_TRUE(pixels); |
| SetPixels(xoffset, yoffset, width, height, pixels); |
| } |
| |
| void ProduceTextureDirectCHROMIUM(GLuint texture, GLbyte* mailbox) override { |
| shared_data_->GenMailbox(mailbox); |
| // Delay moving the texture into the mailbox until the next |
| // sync token, so that it is not visible to other contexts that |
| // haven't waited on that sync point. |
| std::unique_ptr<PendingProduceTexture> pending(new PendingProduceTexture); |
| memcpy(pending->mailbox, mailbox, sizeof(pending->mailbox)); |
| base::AutoLock lock_for_texture_access(namespace_->lock); |
| pending->texture = UnboundTexture(texture); |
| pending_produce_textures_.push_back(std::move(pending)); |
| } |
| |
| GLuint CreateAndConsumeTextureCHROMIUM(const GLbyte* mailbox) override { |
| GLuint texture_id; |
| GenTextures(1, &texture_id); |
| |
| base::AutoLock lock_for_texture_access(namespace_->lock); |
| scoped_refptr<TestTexture> texture = |
| shared_data_->ConsumeTexture(mailbox, last_waited_sync_token_); |
| namespace_->textures.Replace(texture_id, texture); |
| return texture_id; |
| } |
| |
| void GetPixels(const gfx::Size& size, |
| ResourceFormat format, |
| uint8_t* pixels) { |
| CheckTextureIsBound(GL_TEXTURE_2D); |
| base::AutoLock lock_for_texture_access(namespace_->lock); |
| scoped_refptr<TestTexture> texture = BoundTexture(GL_TEXTURE_2D); |
| ASSERT_EQ(texture->size, size); |
| ASSERT_EQ(texture->format, format); |
| memcpy(pixels, texture->data.get(), TextureSizeBytes(size, format)); |
| } |
| |
| private: |
| void AllocateTexture(const gfx::Size& size, GLenum format) { |
| CheckTextureIsBound(GL_TEXTURE_2D); |
| ResourceFormat texture_format = RGBA_8888; |
| switch (format) { |
| case GL_RGBA: |
| texture_format = RGBA_8888; |
| break; |
| case GL_BGRA_EXT: |
| texture_format = BGRA_8888; |
| break; |
| } |
| base::AutoLock lock_for_texture_access(namespace_->lock); |
| BoundTexture(GL_TEXTURE_2D)->Reallocate(size, texture_format); |
| } |
| |
| void SetPixels(int xoffset, |
| int yoffset, |
| int width, |
| int height, |
| const void* pixels) { |
| CheckTextureIsBound(GL_TEXTURE_2D); |
| base::AutoLock lock_for_texture_access(namespace_->lock); |
| scoped_refptr<TestTexture> texture = BoundTexture(GL_TEXTURE_2D); |
| ASSERT_TRUE(texture->data.get()); |
| ASSERT_TRUE(xoffset >= 0 && xoffset + width <= texture->size.width()); |
| ASSERT_TRUE(yoffset >= 0 && yoffset + height <= texture->size.height()); |
| ASSERT_TRUE(pixels); |
| size_t in_pitch = TextureSizeBytes(gfx::Size(width, 1), texture->format); |
| size_t out_pitch = |
| TextureSizeBytes(gfx::Size(texture->size.width(), 1), texture->format); |
| uint8_t* dest = texture->data.get() + yoffset * out_pitch + |
| TextureSizeBytes(gfx::Size(xoffset, 1), texture->format); |
| const uint8_t* src = static_cast<const uint8_t*>(pixels); |
| for (int i = 0; i < height; ++i) { |
| memcpy(dest, src, in_pitch); |
| dest += out_pitch; |
| src += in_pitch; |
| } |
| } |
| |
| struct PendingProduceTexture { |
| GLbyte mailbox[GL_MAILBOX_SIZE_CHROMIUM]; |
| scoped_refptr<TestTexture> texture; |
| }; |
| ContextSharedData* shared_data_; |
| gpu::SyncToken last_waited_sync_token_; |
| std::vector<std::unique_ptr<PendingProduceTexture>> pending_produce_textures_; |
| }; |
| |
| class DisplayResourceProviderTest : public testing::TestWithParam<bool> { |
| public: |
| explicit DisplayResourceProviderTest(bool child_needs_sync_token) |
| : use_gpu_(GetParam()), |
| child_needs_sync_token_(child_needs_sync_token), |
| shared_data_(ContextSharedData::Create()) { |
| if (use_gpu_) { |
| auto gl_owned = |
| std::make_unique<ResourceProviderGLES2Interface>(shared_data_.get()); |
| gl_ = gl_owned.get(); |
| context_provider_ = TestContextProvider::Create(std::move(gl_owned)); |
| context_provider_->UnboundTestContextGL() |
| ->set_support_texture_format_bgra8888(true); |
| context_provider_->BindToCurrentThread(); |
| |
| auto child_gl_owned = |
| std::make_unique<ResourceProviderGLES2Interface>(shared_data_.get()); |
| child_gl_ = child_gl_owned.get(); |
| child_context_provider_ = |
| TestContextProvider::Create(std::move(child_gl_owned)); |
| child_context_provider_->UnboundTestContextGL() |
| ->set_support_texture_format_bgra8888(true); |
| child_context_provider_->BindToCurrentThread(); |
| gpu_memory_buffer_manager_ = |
| std::make_unique<TestGpuMemoryBufferManager>(); |
| } |
| // SharedBitmapManager may always be present, even if gpu compositing. |
| shared_bitmap_manager_ = std::make_unique<TestSharedBitmapManager>(); |
| |
| resource_provider_ = std::make_unique<DisplayResourceProvider>( |
| use_gpu_ ? DisplayResourceProvider::kGpu |
| : DisplayResourceProvider::kSoftware, |
| context_provider_.get(), shared_bitmap_manager_.get()); |
| |
| MakeChildResourceProvider(); |
| } |
| |
| DisplayResourceProviderTest() : DisplayResourceProviderTest(true) {} |
| |
| ~DisplayResourceProviderTest() { |
| if (child_resource_provider_) |
| child_resource_provider_->ShutdownAndReleaseAllResources(); |
| } |
| |
| bool use_gpu() const { return use_gpu_; } |
| |
| void MakeChildResourceProvider() { |
| child_resource_provider_ = |
| std::make_unique<ClientResourceProvider>(child_needs_sync_token_); |
| } |
| |
| static ReturnCallback GetReturnCallback( |
| std::vector<ReturnedResource>* array) { |
| return base::BindRepeating(&CollectResources, array); |
| } |
| |
| static void SetResourceFilter(DisplayResourceProvider* resource_provider, |
| ResourceId id, |
| GLenum filter) { |
| DisplayResourceProvider::ScopedSamplerGL sampler(resource_provider, id, |
| GL_TEXTURE_2D, filter); |
| } |
| |
| |
| TransferableResource CreateResource(ResourceFormat format) { |
| if (use_gpu()) { |
| unsigned texture; |
| child_gl_->GenTextures(1, &texture); |
| gpu::Mailbox gpu_mailbox; |
| child_gl_->ProduceTextureDirectCHROMIUM(texture, gpu_mailbox.name); |
| gpu::SyncToken sync_token; |
| child_gl_->GenSyncTokenCHROMIUM(sync_token.GetData()); |
| EXPECT_TRUE(sync_token.HasData()); |
| |
| TransferableResource gl_resource = TransferableResource::MakeGL( |
| gpu_mailbox, GL_LINEAR, GL_TEXTURE_2D, sync_token); |
| gl_resource.format = format; |
| return gl_resource; |
| } else { |
| gfx::Size size(64, 64); |
| SharedBitmapId shared_bitmap_id = CreateAndFillSharedBitmap( |
| shared_bitmap_manager_.get(), size, format, 0); |
| |
| return TransferableResource::MakeSoftware(shared_bitmap_id, size, format); |
| } |
| } |
| |
| ResourceId MakeGpuResourceAndSendToDisplay( |
| char c, |
| GLuint filter, |
| GLuint target, |
| const gpu::SyncToken& sync_token, |
| DisplayResourceProvider* resource_provider) { |
| ReturnCallback return_callback = base::DoNothing(); |
| |
| int child = resource_provider->CreateChild(return_callback, true); |
| |
| gpu::Mailbox gpu_mailbox; |
| gpu_mailbox.name[0] = c; |
| gpu_mailbox.name[1] = 0; |
| auto resource = TransferableResource::MakeGL(gpu_mailbox, GL_LINEAR, target, |
| sync_token); |
| resource.id = 11; |
| resource_provider->ReceiveFromChild(child, {resource}); |
| auto& map = resource_provider->GetChildToParentMap(child); |
| return map.find(resource.id)->second; |
| } |
| |
| protected: |
| const bool use_gpu_; |
| const bool child_needs_sync_token_; |
| const std::unique_ptr<ContextSharedData> shared_data_; |
| ResourceProviderGLES2Interface* gl_ = nullptr; |
| ResourceProviderGLES2Interface* child_gl_ = nullptr; |
| scoped_refptr<TestContextProvider> context_provider_; |
| scoped_refptr<TestContextProvider> child_context_provider_; |
| std::unique_ptr<TestGpuMemoryBufferManager> gpu_memory_buffer_manager_; |
| std::unique_ptr<DisplayResourceProvider> resource_provider_; |
| std::unique_ptr<ClientResourceProvider> child_resource_provider_; |
| std::unique_ptr<TestSharedBitmapManager> shared_bitmap_manager_; |
| }; |
| |
| INSTANTIATE_TEST_CASE_P(DisplayResourceProviderTests, |
| DisplayResourceProviderTest, |
| ::testing::Values(false, true)); |
| |
| TEST_P(DisplayResourceProviderTest, LockForExternalUse) { |
| // TODO(penghuang): consider supporting SW mode. |
| if (!use_gpu()) |
| return; |
| |
| gpu::SyncToken sync_token1(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x123), |
| 0x42); |
| auto mailbox = gpu::Mailbox::Generate(); |
| TransferableResource gl_resource = TransferableResource::MakeGL( |
| mailbox, GL_LINEAR, GL_TEXTURE_2D, sync_token1); |
| ResourceId id1 = child_resource_provider_->ImportResource( |
| gl_resource, SingleReleaseCallback::Create(base::DoNothing())); |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), true); |
| |
| // Transfer some resources to the parent. |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent({id1}, &list, |
| child_context_provider_.get()); |
| ASSERT_EQ(1u, list.size()); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1)); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| |
| unsigned parent_id = resource_map[list.front().id]; |
| |
| DisplayResourceProvider::LockSetForExternalUse lock_set( |
| resource_provider_.get()); |
| |
| ResourceMetadata metadata = lock_set.LockResource(parent_id); |
| ASSERT_EQ(metadata.mailbox, mailbox); |
| ASSERT_TRUE(metadata.sync_token.HasData()); |
| |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| // The resource should not be returned due to the external use lock. |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| gpu::SyncToken sync_token2(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x234), |
| 0x456); |
| sync_token2.SetVerifyFlush(); |
| lock_set.UnlockResources(sync_token2); |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| // The resource should be returned after the lock is released. |
| EXPECT_EQ(1u, returned_to_child.size()); |
| EXPECT_EQ(sync_token2, returned_to_child[0].sync_token); |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| child_resource_provider_->RemoveImportedResource(id1); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, ReadLockCountStopsReturnToChildOrDelete) { |
| if (!use_gpu()) |
| return; |
| |
| MockReleaseCallback release; |
| TransferableResource tran = CreateResource(RGBA_8888); |
| ResourceId id1 = child_resource_provider_->ImportResource( |
| tran, SingleReleaseCallback::Create(base::BindOnce( |
| &MockReleaseCallback::Released, base::Unretained(&release)))); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), true); |
| { |
| // Transfer some resources to the parent. |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent( |
| {id1}, &list, child_context_provider_.get()); |
| ASSERT_EQ(1u, list.size()); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1)); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| ResourceId mapped_resource_id = resource_map[list[0].id]; |
| resource_provider_->WaitSyncToken(mapped_resource_id); |
| DisplayResourceProvider::ScopedReadLockGL lock(resource_provider_.get(), |
| mapped_resource_id); |
| |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, |
| ResourceIdSet()); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| } |
| |
| EXPECT_EQ(1u, returned_to_child.size()); |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| |
| // No need to wait for the sync token here -- it will be returned to the |
| // client on delete. |
| { |
| EXPECT_CALL(release, Released(_, _)); |
| child_resource_provider_->RemoveImportedResource(id1); |
| } |
| |
| resource_provider_->DestroyChild(child_id); |
| } |
| |
| class TestFence : public ResourceFence { |
| public: |
| TestFence() = default; |
| |
| // ResourceFence implementation. |
| void Set() override {} |
| bool HasPassed() override { return passed; } |
| void Wait() override {} |
| |
| bool passed = false; |
| |
| private: |
| ~TestFence() override = default; |
| }; |
| |
| TEST_P(DisplayResourceProviderTest, ReadLockFenceStopsReturnToChildOrDelete) { |
| if (!use_gpu()) |
| return; |
| |
| MockReleaseCallback release; |
| TransferableResource tran1 = CreateResource(RGBA_8888); |
| tran1.read_lock_fences_enabled = true; |
| ResourceId id1 = child_resource_provider_->ImportResource( |
| tran1, SingleReleaseCallback::Create(base::BindOnce( |
| &MockReleaseCallback::Released, base::Unretained(&release)))); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), true); |
| |
| // Transfer some resources to the parent. |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent({id1}, &list, |
| child_context_provider_.get()); |
| ASSERT_EQ(1u, list.size()); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1)); |
| EXPECT_TRUE(list[0].read_lock_fences_enabled); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| |
| scoped_refptr<TestFence> fence(new TestFence); |
| resource_provider_->SetReadLockFence(fence.get()); |
| { |
| unsigned parent_id = resource_map[list.front().id]; |
| resource_provider_->WaitSyncToken(parent_id); |
| DisplayResourceProvider::ScopedReadLockGL lock(resource_provider_.get(), |
| parent_id); |
| } |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| fence->passed = true; |
| |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| EXPECT_EQ(1u, returned_to_child.size()); |
| |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| EXPECT_CALL(release, Released(_, _)); |
| child_resource_provider_->RemoveImportedResource(id1); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, ReadLockFenceDestroyChild) { |
| if (!use_gpu()) |
| return; |
| |
| MockReleaseCallback release; |
| |
| TransferableResource tran1 = CreateResource(RGBA_8888); |
| tran1.read_lock_fences_enabled = true; |
| ResourceId id1 = child_resource_provider_->ImportResource( |
| tran1, SingleReleaseCallback::Create(base::BindOnce( |
| &MockReleaseCallback::Released, base::Unretained(&release)))); |
| |
| TransferableResource tran2 = CreateResource(RGBA_8888); |
| tran2.read_lock_fences_enabled = false; |
| ResourceId id2 = child_resource_provider_->ImportResource( |
| tran2, SingleReleaseCallback::Create(base::BindOnce( |
| &MockReleaseCallback::Released, base::Unretained(&release)))); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), true); |
| |
| // Transfer resources to the parent. |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent({id1, id2}, &list, |
| child_context_provider_.get()); |
| ASSERT_EQ(2u, list.size()); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1)); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id2)); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| |
| scoped_refptr<TestFence> fence(new TestFence); |
| resource_provider_->SetReadLockFence(fence.get()); |
| { |
| for (size_t i = 0; i < list.size(); i++) { |
| unsigned parent_id = resource_map[list[i].id]; |
| resource_provider_->WaitSyncToken(parent_id); |
| DisplayResourceProvider::ScopedReadLockGL lock(resource_provider_.get(), |
| parent_id); |
| } |
| } |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| EXPECT_EQ(2u, resource_provider_->num_resources()); |
| |
| resource_provider_->DestroyChild(child_id); |
| |
| EXPECT_EQ(0u, resource_provider_->num_resources()); |
| EXPECT_EQ(2u, returned_to_child.size()); |
| |
| // id1 should be lost and id2 should not. |
| EXPECT_EQ(returned_to_child[0].lost, returned_to_child[0].id == id1); |
| EXPECT_EQ(returned_to_child[1].lost, returned_to_child[1].id == id1); |
| |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| EXPECT_CALL(release, Released(_, _)).Times(2); |
| child_resource_provider_->RemoveImportedResource(id1); |
| child_resource_provider_->RemoveImportedResource(id2); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, ReadLockFenceContextLost) { |
| if (!use_gpu()) |
| return; |
| |
| TransferableResource tran1 = CreateResource(RGBA_8888); |
| tran1.read_lock_fences_enabled = true; |
| ResourceId id1 = child_resource_provider_->ImportResource( |
| tran1, SingleReleaseCallback::Create(base::DoNothing())); |
| |
| TransferableResource tran2 = CreateResource(RGBA_8888); |
| tran2.read_lock_fences_enabled = false; |
| ResourceId id2 = child_resource_provider_->ImportResource( |
| tran2, SingleReleaseCallback::Create(base::DoNothing())); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), true); |
| |
| // Transfer resources to the parent. |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent({id1, id2}, &list, |
| child_context_provider_.get()); |
| ASSERT_EQ(2u, list.size()); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1)); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id2)); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| |
| scoped_refptr<TestFence> fence(new TestFence); |
| resource_provider_->SetReadLockFence(fence.get()); |
| { |
| for (size_t i = 0; i < list.size(); i++) { |
| unsigned parent_id = resource_map[list[i].id]; |
| resource_provider_->WaitSyncToken(parent_id); |
| DisplayResourceProvider::ScopedReadLockGL lock(resource_provider_.get(), |
| parent_id); |
| } |
| } |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| EXPECT_EQ(2u, resource_provider_->num_resources()); |
| resource_provider_->DidLoseContextProvider(); |
| resource_provider_ = nullptr; |
| |
| EXPECT_EQ(2u, returned_to_child.size()); |
| |
| EXPECT_TRUE(returned_to_child[0].lost); |
| EXPECT_TRUE(returned_to_child[1].lost); |
| |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| child_resource_provider_->RemoveImportedResource(id1); |
| child_resource_provider_->RemoveImportedResource(id2); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, ReturnResourcesWithoutSyncToken) { |
| if (!use_gpu()) |
| return; |
| |
| auto no_token_resource_provider = std::make_unique<ClientResourceProvider>( |
| /*delegated_sync_points_required=*/true); |
| |
| GLuint external_texture_id = child_gl_->CreateExternalTexture(); |
| |
| // A sync point is specified directly and should be used. |
| gpu::Mailbox external_mailbox; |
| child_gl_->ProduceTextureDirectCHROMIUM(external_texture_id, |
| external_mailbox.name); |
| gpu::SyncToken external_sync_token; |
| child_gl_->GenSyncTokenCHROMIUM(external_sync_token.GetData()); |
| EXPECT_TRUE(external_sync_token.HasData()); |
| ResourceId id = no_token_resource_provider->ImportResource( |
| TransferableResource::MakeGL(external_mailbox, GL_LINEAR, |
| GL_TEXTURE_EXTERNAL_OES, |
| external_sync_token), |
| SingleReleaseCallback::Create(base::DoNothing())); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), false); |
| { |
| // Transfer some resources to the parent. |
| std::vector<TransferableResource> list; |
| no_token_resource_provider->PrepareSendToParent( |
| {id}, &list, child_context_provider_.get()); |
| ASSERT_EQ(1u, list.size()); |
| // A given sync point should be passed through. |
| EXPECT_EQ(external_sync_token, list[0].mailbox_holder.sync_token); |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| ResourceIdSet resource_ids_to_receive; |
| resource_ids_to_receive.insert(id); |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, |
| resource_ids_to_receive); |
| } |
| |
| { |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| // Transfer resources back from the parent to the child. Set no resources as |
| // being in use. |
| ResourceIdSet no_resources; |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, no_resources); |
| |
| ASSERT_EQ(1u, returned_to_child.size()); |
| std::map<ResourceId, gpu::SyncToken> returned_sync_tokens; |
| for (const auto& returned : returned_to_child) |
| returned_sync_tokens[returned.id] = returned.sync_token; |
| |
| // Original sync point given should be returned. |
| ASSERT_TRUE(returned_sync_tokens.find(id) != returned_sync_tokens.end()); |
| EXPECT_EQ(external_sync_token, returned_sync_tokens[id]); |
| EXPECT_FALSE(returned_to_child[0].lost); |
| no_token_resource_provider->ReceiveReturnsFromParent(returned_to_child); |
| returned_to_child.clear(); |
| } |
| |
| resource_provider_->DestroyChild(child_id); |
| no_token_resource_provider->RemoveImportedResource(id); |
| } |
| |
| // Test that ScopedBatchReturnResources batching works. |
| TEST_P(DisplayResourceProviderTest, ScopedBatchReturnResourcesPreventsReturn) { |
| if (!use_gpu()) |
| return; |
| |
| MockReleaseCallback release; |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), true); |
| |
| // Transfer some resources to the parent. |
| constexpr size_t kTotalResources = 5; |
| constexpr size_t kLockedResources = 3; |
| constexpr size_t kUsedResources = 4; |
| ResourceId ids[kTotalResources]; |
| for (size_t i = 0; i < kTotalResources; i++) { |
| TransferableResource tran = CreateResource(RGBA_8888); |
| ids[i] = child_resource_provider_->ImportResource( |
| tran, SingleReleaseCallback::Create(base::BindOnce( |
| &MockReleaseCallback::Released, base::Unretained(&release)))); |
| } |
| std::vector<ResourceId> resource_ids_to_transfer(ids, ids + kTotalResources); |
| |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent(resource_ids_to_transfer, &list, |
| child_context_provider_.get()); |
| ASSERT_EQ(kTotalResources, list.size()); |
| for (const auto& id : ids) |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id)); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| std::vector<std::unique_ptr<DisplayResourceProvider::ScopedReadLockGL>> |
| read_locks; |
| for (size_t i = 0; i < kLockedResources; i++) { |
| unsigned int mapped_resource_id = resource_map[ids[i]]; |
| resource_provider_->WaitSyncToken(mapped_resource_id); |
| read_locks.push_back( |
| std::make_unique<DisplayResourceProvider::ScopedReadLockGL>( |
| resource_provider_.get(), mapped_resource_id)); |
| } |
| |
| // Mark all locked resources, and one unlocked resource as used for first |
| // batch. |
| { |
| DisplayResourceProvider::ScopedBatchReturnResources returner( |
| resource_provider_.get()); |
| resource_provider_->DeclareUsedResourcesFromChild( |
| child_id, ResourceIdSet(ids, ids + kUsedResources)); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| } |
| EXPECT_EQ(1u, returned_to_child.size()); |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| returned_to_child.clear(); |
| |
| // Return all locked resources. |
| { |
| DisplayResourceProvider::ScopedBatchReturnResources returner( |
| resource_provider_.get()); |
| resource_provider_->DeclareUsedResourcesFromChild( |
| child_id, ResourceIdSet(ids + kLockedResources, ids + kUsedResources)); |
| // Can be called multiple times while batching is enabled. This happens in |
| // practice when the same surface is visited using different paths during |
| // surface aggregation. |
| resource_provider_->DeclareUsedResourcesFromChild( |
| child_id, ResourceIdSet(ids + kLockedResources, ids + kUsedResources)); |
| read_locks.clear(); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| } |
| EXPECT_EQ(kLockedResources, returned_to_child.size()); |
| // Returned resources that were locked share the same sync token. |
| for (const auto& resource : returned_to_child) |
| EXPECT_EQ(resource.sync_token, returned_to_child[0].sync_token); |
| |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| returned_to_child.clear(); |
| |
| // Returns from destroying the child is also batched. |
| { |
| DisplayResourceProvider::ScopedBatchReturnResources returner( |
| resource_provider_.get()); |
| resource_provider_->DestroyChild(child_id); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| } |
| EXPECT_EQ(1u, returned_to_child.size()); |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| returned_to_child.clear(); |
| |
| EXPECT_CALL(release, Released(_, _)).Times(kTotalResources); |
| for (const auto& id : ids) |
| child_resource_provider_->RemoveImportedResource(id); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, LostMailboxInParent) { |
| gpu::SyncToken sync_token(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x12), 0x34); |
| auto tran = CreateResource(RGBA_8888); |
| tran.id = 11; |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| base::BindRepeating(&CollectResources, &returned_to_child), true); |
| |
| // Receive a resource then lose the gpu context. |
| resource_provider_->ReceiveFromChild(child_id, {tran}); |
| resource_provider_->DidLoseContextProvider(); |
| |
| // Transfer resources back from the parent to the child. |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, {}); |
| ASSERT_EQ(1u, returned_to_child.size()); |
| |
| // Losing an output surface only loses hardware resources. |
| EXPECT_EQ(returned_to_child[0].lost, use_gpu()); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, ReadSoftwareResources) { |
| if (use_gpu()) |
| return; |
| |
| gfx::Size size(64, 64); |
| ResourceFormat format = RGBA_8888; |
| const uint32_t kBadBeef = 0xbadbeef; |
| SharedBitmapId shared_bitmap_id = CreateAndFillSharedBitmap( |
| shared_bitmap_manager_.get(), size, format, kBadBeef); |
| |
| auto resource = |
| TransferableResource::MakeSoftware(shared_bitmap_id, size, format); |
| |
| MockReleaseCallback release; |
| ResourceId resource_id = child_resource_provider_->ImportResource( |
| resource, |
| SingleReleaseCallback::Create(base::BindOnce( |
| &MockReleaseCallback::Released, base::Unretained(&release)))); |
| EXPECT_NE(0u, resource_id); |
| |
| // Transfer resources to the parent. |
| std::vector<TransferableResource> send_to_parent; |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| base::BindRepeating(&CollectResources, &returned_to_child), true); |
| child_resource_provider_->PrepareSendToParent({resource_id}, &send_to_parent, |
| child_context_provider_.get()); |
| resource_provider_->ReceiveFromChild(child_id, send_to_parent); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| ResourceId mapped_resource_id = resource_map[resource_id]; |
| |
| { |
| DisplayResourceProvider::ScopedReadLockSkImage lock( |
| resource_provider_.get(), mapped_resource_id); |
| const SkImage* sk_image = lock.sk_image(); |
| SkBitmap sk_bitmap; |
| sk_image->asLegacyBitmap(&sk_bitmap); |
| EXPECT_EQ(sk_image->width(), size.width()); |
| EXPECT_EQ(sk_image->height(), size.height()); |
| EXPECT_EQ(*sk_bitmap.getAddr32(16, 16), kBadBeef); |
| } |
| |
| EXPECT_EQ(0u, returned_to_child.size()); |
| // Transfer resources back from the parent to the child. Set no resources as |
| // being in use. |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| EXPECT_EQ(1u, returned_to_child.size()); |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| |
| EXPECT_CALL(release, Released(_, false)); |
| child_resource_provider_->RemoveImportedResource(resource_id); |
| } |
| |
| class TextureStateTrackingGLES2Interface : public TestGLES2Interface { |
| public: |
| MOCK_METHOD2(BindTexture, void(GLenum target, GLuint texture)); |
| MOCK_METHOD3(TexParameteri, void(GLenum target, GLenum pname, GLint param)); |
| MOCK_METHOD1(WaitSyncTokenCHROMIUM, void(const GLbyte* sync_token)); |
| MOCK_METHOD2(ProduceTextureDirectCHROMIUM, |
| void(GLuint texture, GLbyte* mailbox)); |
| MOCK_METHOD1(CreateAndConsumeTextureCHROMIUM, |
| unsigned(const GLbyte* mailbox)); |
| |
| // Force all textures to be consecutive numbers starting at "1", |
| // so we easily can test for them. |
| GLuint NextTextureId() override { |
| base::AutoLock lock(namespace_->lock); |
| return namespace_->next_texture_id++; |
| } |
| |
| void RetireTextureId(GLuint) override {} |
| |
| void GenSyncTokenCHROMIUM(GLbyte* sync_token) override { |
| gpu::SyncToken sync_token_data(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x123), |
| next_fence_sync_++); |
| sync_token_data.SetVerifyFlush(); |
| memcpy(sync_token, &sync_token_data, sizeof(sync_token_data)); |
| } |
| |
| GLuint64 GetNextFenceSync() const { return next_fence_sync_; } |
| |
| GLuint64 next_fence_sync_ = 1; |
| }; |
| |
| class ResourceProviderTestImportedResourceGLFilters { |
| public: |
| static void RunTest(TestSharedBitmapManager* shared_bitmap_manager, |
| bool mailbox_nearest_neighbor, |
| GLenum sampler_filter) { |
| auto gl_owned = std::make_unique<TextureStateTrackingGLES2Interface>(); |
| TextureStateTrackingGLES2Interface* gl = gl_owned.get(); |
| auto context_provider = TestContextProvider::Create(std::move(gl_owned)); |
| context_provider->BindToCurrentThread(); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProvider>( |
| DisplayResourceProvider::kGpu, context_provider.get(), |
| shared_bitmap_manager); |
| |
| auto child_gl_owned = |
| std::make_unique<TextureStateTrackingGLES2Interface>(); |
| TextureStateTrackingGLES2Interface* child_gl = child_gl_owned.get(); |
| auto child_context_provider = |
| TestContextProvider::Create(std::move(child_gl_owned)); |
| child_context_provider->BindToCurrentThread(); |
| |
| auto child_resource_provider = std::make_unique<ClientResourceProvider>( |
| /*delegated_sync_points_required=*/true); |
| |
| unsigned texture_id = 1; |
| gpu::SyncToken sync_token(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x12), |
| 0x34); |
| const GLuint64 current_fence_sync = child_gl->GetNextFenceSync(); |
| |
| EXPECT_CALL(*child_gl, BindTexture(_, _)).Times(0); |
| EXPECT_CALL(*child_gl, WaitSyncTokenCHROMIUM(_)).Times(0); |
| EXPECT_CALL(*child_gl, ProduceTextureDirectCHROMIUM(_, _)).Times(0); |
| EXPECT_CALL(*child_gl, CreateAndConsumeTextureCHROMIUM(_)).Times(0); |
| |
| gpu::Mailbox gpu_mailbox; |
| memcpy(gpu_mailbox.name, "Hello world", strlen("Hello world") + 1); |
| GLuint filter = mailbox_nearest_neighbor ? GL_NEAREST : GL_LINEAR; |
| auto resource = TransferableResource::MakeGL(gpu_mailbox, filter, |
| GL_TEXTURE_2D, sync_token); |
| |
| MockReleaseCallback release; |
| ResourceId resource_id = child_resource_provider->ImportResource( |
| resource, |
| SingleReleaseCallback::Create(base::BindOnce( |
| &MockReleaseCallback::Released, base::Unretained(&release)))); |
| EXPECT_NE(0u, resource_id); |
| EXPECT_EQ(current_fence_sync, child_gl->GetNextFenceSync()); |
| |
| testing::Mock::VerifyAndClearExpectations(child_gl); |
| |
| // Transfer resources to the parent. |
| std::vector<TransferableResource> send_to_parent; |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider->CreateChild( |
| base::BindRepeating(&CollectResources, &returned_to_child), true); |
| child_resource_provider->PrepareSendToParent({resource_id}, &send_to_parent, |
| child_context_provider.get()); |
| resource_provider->ReceiveFromChild(child_id, send_to_parent); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId> resource_map = |
| resource_provider->GetChildToParentMap(child_id); |
| ResourceId mapped_resource_id = resource_map[resource_id]; |
| { |
| // The verified flush flag will be set by |
| // ClientResourceProvider::PrepareSendToParent. Before checking if |
| // the gpu::SyncToken matches, set this flag first. |
| sync_token.SetVerifyFlush(); |
| |
| // Mailbox sync point WaitSyncToken before using the texture. |
| EXPECT_CALL(*gl, WaitSyncTokenCHROMIUM(MatchesSyncToken(sync_token))); |
| resource_provider->WaitSyncToken(mapped_resource_id); |
| testing::Mock::VerifyAndClearExpectations(gl); |
| |
| EXPECT_CALL(*gl, CreateAndConsumeTextureCHROMIUM(_)) |
| .WillOnce(Return(texture_id)); |
| EXPECT_CALL(*gl, BindTexture(GL_TEXTURE_2D, texture_id)); |
| |
| EXPECT_CALL(*gl, ProduceTextureDirectCHROMIUM(_, _)).Times(0); |
| |
| // The sampler will reset these if |mailbox_nearest_neighbor| does not |
| // match |sampler_filter|. |
| if (mailbox_nearest_neighbor != (sampler_filter == GL_NEAREST)) { |
| EXPECT_CALL(*gl, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, |
| sampler_filter)); |
| EXPECT_CALL(*gl, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, |
| sampler_filter)); |
| } |
| |
| DisplayResourceProvider::ScopedSamplerGL lock( |
| resource_provider.get(), mapped_resource_id, sampler_filter); |
| testing::Mock::VerifyAndClearExpectations(gl); |
| EXPECT_EQ(current_fence_sync, gl->GetNextFenceSync()); |
| |
| // When done with it, a sync point should be inserted, but no produce is |
| // necessary. |
| EXPECT_CALL(*child_gl, BindTexture(_, _)).Times(0); |
| EXPECT_CALL(*child_gl, ProduceTextureDirectCHROMIUM(_, _)).Times(0); |
| |
| EXPECT_CALL(*child_gl, WaitSyncTokenCHROMIUM(_)).Times(0); |
| EXPECT_CALL(*child_gl, CreateAndConsumeTextureCHROMIUM(_)).Times(0); |
| } |
| |
| EXPECT_EQ(0u, returned_to_child.size()); |
| // Transfer resources back from the parent to the child. Set no resources as |
| // being in use. |
| resource_provider->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| EXPECT_EQ(1u, returned_to_child.size()); |
| child_resource_provider->ReceiveReturnsFromParent(returned_to_child); |
| |
| gpu::SyncToken released_sync_token; |
| { |
| EXPECT_CALL(release, Released(_, false)) |
| .WillOnce(SaveArg<0>(&released_sync_token)); |
| child_resource_provider->RemoveImportedResource(resource_id); |
| } |
| EXPECT_TRUE(released_sync_token.HasData()); |
| } |
| }; |
| |
| TEST_P(DisplayResourceProviderTest, ReceiveGLTexture2D_LinearToLinear) { |
| // Mailboxing is only supported for GL textures. |
| if (!use_gpu()) |
| return; |
| |
| ResourceProviderTestImportedResourceGLFilters::RunTest( |
| shared_bitmap_manager_.get(), false, GL_LINEAR); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, ReceiveGLTexture2D_NearestToNearest) { |
| // Mailboxing is only supported for GL textures. |
| if (!use_gpu()) |
| return; |
| |
| ResourceProviderTestImportedResourceGLFilters::RunTest( |
| shared_bitmap_manager_.get(), true, GL_NEAREST); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, ReceiveGLTexture2D_NearestToLinear) { |
| // Mailboxing is only supported for GL textures. |
| if (!use_gpu()) |
| return; |
| |
| ResourceProviderTestImportedResourceGLFilters::RunTest( |
| shared_bitmap_manager_.get(), true, GL_LINEAR); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, ReceiveGLTexture2D_LinearToNearest) { |
| // Mailboxing is only supported for GL textures. |
| if (!use_gpu()) |
| return; |
| |
| ResourceProviderTestImportedResourceGLFilters::RunTest( |
| shared_bitmap_manager_.get(), false, GL_NEAREST); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, ReceiveGLTextureExternalOES) { |
| // Mailboxing is only supported for GL textures. |
| if (!use_gpu()) |
| return; |
| |
| auto gl_owned = std::make_unique<TextureStateTrackingGLES2Interface>(); |
| TextureStateTrackingGLES2Interface* gl = gl_owned.get(); |
| auto context_provider = TestContextProvider::Create(std::move(gl_owned)); |
| context_provider->BindToCurrentThread(); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProvider>( |
| DisplayResourceProvider::kGpu, context_provider.get(), |
| shared_bitmap_manager_.get()); |
| |
| auto child_gl_owned = std::make_unique<TextureStateTrackingGLES2Interface>(); |
| TextureStateTrackingGLES2Interface* child_gl = child_gl_owned.get(); |
| auto child_context_provider = |
| TestContextProvider::Create(std::move(child_gl_owned)); |
| child_context_provider->BindToCurrentThread(); |
| |
| auto child_resource_provider = std::make_unique<ClientResourceProvider>( |
| /*delegated_sync_points_required=*/true); |
| |
| gpu::SyncToken sync_token(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x12), 0x34); |
| const GLuint64 current_fence_sync = child_gl->GetNextFenceSync(); |
| |
| EXPECT_CALL(*child_gl, BindTexture(_, _)).Times(0); |
| EXPECT_CALL(*child_gl, WaitSyncTokenCHROMIUM(_)).Times(0); |
| EXPECT_CALL(*child_gl, ProduceTextureDirectCHROMIUM(_, _)).Times(0); |
| EXPECT_CALL(*child_gl, CreateAndConsumeTextureCHROMIUM(_)).Times(0); |
| |
| gpu::Mailbox gpu_mailbox; |
| memcpy(gpu_mailbox.name, "Hello world", strlen("Hello world") + 1); |
| std::unique_ptr<SingleReleaseCallback> callback = |
| SingleReleaseCallback::Create(base::DoNothing()); |
| |
| auto resource = TransferableResource::MakeGL( |
| gpu_mailbox, GL_LINEAR, GL_TEXTURE_EXTERNAL_OES, sync_token); |
| |
| ResourceId resource_id = |
| child_resource_provider->ImportResource(resource, std::move(callback)); |
| EXPECT_NE(0u, resource_id); |
| EXPECT_EQ(current_fence_sync, child_gl->GetNextFenceSync()); |
| |
| testing::Mock::VerifyAndClearExpectations(child_gl); |
| |
| // Transfer resources to the parent. |
| std::vector<TransferableResource> send_to_parent; |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider->CreateChild( |
| base::BindRepeating(&CollectResources, &returned_to_child), true); |
| child_resource_provider->PrepareSendToParent({resource_id}, &send_to_parent, |
| child_context_provider_.get()); |
| resource_provider->ReceiveFromChild(child_id, send_to_parent); |
| |
| // Before create DrawQuad in DisplayResourceProvider's namespace, get the |
| // mapped resource id first. |
| std::unordered_map<ResourceId, ResourceId> resource_map = |
| resource_provider->GetChildToParentMap(child_id); |
| ResourceId mapped_resource_id = resource_map[resource_id]; |
| { |
| // The verified flush flag will be set by |
| // ClientResourceProvider::PrepareSendToParent. Before checking if |
| // the gpu::SyncToken matches, set this flag first. |
| sync_token.SetVerifyFlush(); |
| |
| // Mailbox sync point WaitSyncToken before using the texture. |
| EXPECT_CALL(*gl, WaitSyncTokenCHROMIUM(MatchesSyncToken(sync_token))); |
| resource_provider->WaitSyncToken(mapped_resource_id); |
| testing::Mock::VerifyAndClearExpectations(gl); |
| |
| unsigned texture_id = 1; |
| |
| EXPECT_CALL(*gl, CreateAndConsumeTextureCHROMIUM(_)) |
| .WillOnce(Return(texture_id)); |
| |
| EXPECT_CALL(*gl, ProduceTextureDirectCHROMIUM(_, _)).Times(0); |
| |
| DisplayResourceProvider::ScopedReadLockGL lock(resource_provider.get(), |
| mapped_resource_id); |
| testing::Mock::VerifyAndClearExpectations(gl); |
| |
| // When done with it, a sync point should be inserted, but no produce is |
| // necessary. |
| EXPECT_CALL(*gl, BindTexture(_, _)).Times(0); |
| EXPECT_CALL(*gl, ProduceTextureDirectCHROMIUM(_, _)).Times(0); |
| |
| EXPECT_CALL(*gl, WaitSyncTokenCHROMIUM(_)).Times(0); |
| EXPECT_CALL(*gl, CreateAndConsumeTextureCHROMIUM(_)).Times(0); |
| testing::Mock::VerifyAndClearExpectations(gl); |
| } |
| EXPECT_EQ(0u, returned_to_child.size()); |
| // Transfer resources back from the parent to the child. Set no resources as |
| // being in use. |
| resource_provider->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| EXPECT_EQ(1u, returned_to_child.size()); |
| child_resource_provider->ReceiveReturnsFromParent(returned_to_child); |
| |
| child_resource_provider->RemoveImportedResource(resource_id); |
| } |
| |
| TEST_P(DisplayResourceProviderTest, WaitSyncTokenIfNeeded) { |
| // Mailboxing is only supported for GL textures. |
| if (!use_gpu()) |
| return; |
| |
| auto gl_owned = std::make_unique<TextureStateTrackingGLES2Interface>(); |
| TextureStateTrackingGLES2Interface* gl = gl_owned.get(); |
| auto context_provider = TestContextProvider::Create(std::move(gl_owned)); |
| context_provider->BindToCurrentThread(); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProvider>( |
| DisplayResourceProvider::kGpu, context_provider.get(), |
| shared_bitmap_manager_.get()); |
| |
| const GLuint64 current_fence_sync = gl->GetNextFenceSync(); |
| |
| EXPECT_CALL(*gl, BindTexture(_, _)).Times(0); |
| EXPECT_CALL(*gl, WaitSyncTokenCHROMIUM(_)).Times(0); |
| EXPECT_CALL(*gl, ProduceTextureDirectCHROMIUM(_, _)).Times(0); |
| EXPECT_CALL(*gl, CreateAndConsumeTextureCHROMIUM(_)).Times(0); |
| |
| gpu::SyncToken sync_token(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x12), 0x34); |
| ResourceId id_with_sync = MakeGpuResourceAndSendToDisplay( |
| 'a', GL_LINEAR, GL_TEXTURE_2D, sync_token, resource_provider.get()); |
| ResourceId id_without_sync = MakeGpuResourceAndSendToDisplay( |
| 'a', GL_LINEAR, GL_TEXTURE_2D, gpu::SyncToken(), resource_provider.get()); |
| |
| EXPECT_EQ(current_fence_sync, gl->GetNextFenceSync()); |
| |
| // First call to WaitSyncToken should call WaitSyncToken, but only if a |
| // SyncToken was present. |
| { |
| EXPECT_CALL(*gl, WaitSyncTokenCHROMIUM(MatchesSyncToken(sync_token))) |
| .Times(1); |
| resource_provider->WaitSyncToken(id_with_sync); |
| EXPECT_CALL(*gl, WaitSyncTokenCHROMIUM(_)).Times(0); |
| resource_provider->WaitSyncToken(id_without_sync); |
| } |
| |
| { |
| // Subsequent calls to WaitSyncToken shouldn't call WaitSyncToken. |
| EXPECT_CALL(*gl, WaitSyncTokenCHROMIUM(_)).Times(0); |
| resource_provider->WaitSyncToken(id_with_sync); |
| resource_provider->WaitSyncToken(id_without_sync); |
| } |
| } |
| |
| #if defined(OS_ANDROID) |
| TEST_P(DisplayResourceProviderTest, OverlayPromotionHint) { |
| if (!use_gpu()) |
| return; |
| |
| GLuint external_texture_id = child_gl_->CreateExternalTexture(); |
| |
| gpu::Mailbox external_mailbox; |
| child_gl_->ProduceTextureDirectCHROMIUM(external_texture_id, |
| external_mailbox.name); |
| gpu::SyncToken external_sync_token; |
| child_gl_->GenSyncTokenCHROMIUM(external_sync_token.GetData()); |
| EXPECT_TRUE(external_sync_token.HasData()); |
| |
| TransferableResource id1_transfer = TransferableResource::MakeGLOverlay( |
| external_mailbox, GL_LINEAR, GL_TEXTURE_EXTERNAL_OES, external_sync_token, |
| gfx::Size(1, 1), true); |
| id1_transfer.wants_promotion_hint = true; |
| id1_transfer.is_backed_by_surface_texture = true; |
| ResourceId id1 = child_resource_provider_->ImportResource( |
| id1_transfer, SingleReleaseCallback::Create(base::DoNothing())); |
| |
| TransferableResource id2_transfer = TransferableResource::MakeGLOverlay( |
| external_mailbox, GL_LINEAR, GL_TEXTURE_EXTERNAL_OES, external_sync_token, |
| gfx::Size(1, 1), true); |
| id2_transfer.wants_promotion_hint = false; |
| id2_transfer.is_backed_by_surface_texture = false; |
| ResourceId id2 = child_resource_provider_->ImportResource( |
| id2_transfer, SingleReleaseCallback::Create(base::DoNothing())); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), true); |
| |
| // Transfer some resources to the parent. |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent({id1, id2}, &list, |
| child_context_provider_.get()); |
| ASSERT_EQ(2u, list.size()); |
| resource_provider_->ReceiveFromChild(child_id, list); |
| std::unordered_map<ResourceId, ResourceId> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| ResourceId mapped_id1 = resource_map[list[0].id]; |
| ResourceId mapped_id2 = resource_map[list[1].id]; |
| |
| // The promotion hints should not be recorded until after we wait. This is |
| // because we can't notify them until they're synchronized, and we choose to |
| // ignore unwaited resources rather than send them a "no" hint. If they end |
| // up in the request set before we wait, then the attempt to notify them wil; |
| // DCHECK when we try to lock them for reading in SendPromotionHints. |
| EXPECT_EQ(0u, resource_provider_->CountPromotionHintRequestsForTesting()); |
| EXPECT_FALSE(resource_provider_->DoAnyResourcesWantPromotionHints()); |
| { |
| resource_provider_->WaitSyncToken(mapped_id1); |
| DisplayResourceProvider::ScopedReadLockGL lock(resource_provider_.get(), |
| mapped_id1); |
| } |
| EXPECT_EQ(1u, resource_provider_->CountPromotionHintRequestsForTesting()); |
| EXPECT_TRUE(resource_provider_->DoAnyResourcesWantPromotionHints()); |
| |
| EXPECT_EQ(list[0].mailbox_holder.sync_token, gl_->last_waited_sync_token()); |
| ResourceIdSet resource_ids_to_receive; |
| resource_ids_to_receive.insert(id1); |
| resource_ids_to_receive.insert(id2); |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, |
| resource_ids_to_receive); |
| |
| EXPECT_EQ(2u, resource_provider_->num_resources()); |
| |
| EXPECT_NE(0u, mapped_id1); |
| EXPECT_NE(0u, mapped_id2); |
| |
| // Make sure that the request for a promotion hint was noticed. |
| EXPECT_TRUE(resource_provider_->IsOverlayCandidate(mapped_id1)); |
| EXPECT_TRUE(resource_provider_->IsBackedBySurfaceTexture(mapped_id1)); |
| EXPECT_TRUE(resource_provider_->DoesResourceWantPromotionHint(mapped_id1)); |
| |
| EXPECT_TRUE(resource_provider_->IsOverlayCandidate(mapped_id2)); |
| EXPECT_FALSE(resource_provider_->IsBackedBySurfaceTexture(mapped_id2)); |
| EXPECT_FALSE(resource_provider_->DoesResourceWantPromotionHint(mapped_id2)); |
| |
| // ResourceProvider maintains a set of promotion hint requests that should be |
| // cleared when resources are deleted. |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| EXPECT_EQ(2u, returned_to_child.size()); |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| |
| EXPECT_EQ(0u, resource_provider_->CountPromotionHintRequestsForTesting()); |
| EXPECT_FALSE(resource_provider_->DoAnyResourcesWantPromotionHints()); |
| |
| resource_provider_->DestroyChild(child_id); |
| |
| child_resource_provider_->RemoveImportedResource(id2); |
| child_resource_provider_->RemoveImportedResource(id1); |
| } |
| #endif |
| |
| } // namespace |
| } // namespace viz |