| // 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 "cc/resources/display_resource_provider.h" |
| #include "cc/resources/layer_tree_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 "cc/test/render_pass_test_utils.h" |
| #include "cc/test/resource_provider_test_utils.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_manager.h" |
| #include "components/viz/common/resources/single_release_callback.h" |
| #include "components/viz/test/test_context_provider.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 "components/viz/test/test_web_graphics_context_3d.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" |
| |
| namespace cc { |
| namespace { |
| |
| static const bool kUseGpuMemoryBufferResources = false; |
| |
| MATCHER_P(MatchesSyncToken, sync_token, "") { |
| gpu::SyncToken other; |
| memcpy(&other, arg, sizeof(other)); |
| return other == sync_token; |
| } |
| |
| static void ReleaseSharedBitmapCallback( |
| std::unique_ptr<viz::SharedBitmap> shared_bitmap, |
| bool* release_called, |
| gpu::SyncToken* release_sync_token, |
| bool* lost_resource_result, |
| const gpu::SyncToken& sync_token, |
| bool lost_resource) { |
| *release_called = true; |
| *release_sync_token = sync_token; |
| *lost_resource_result = lost_resource; |
| } |
| |
| static std::unique_ptr<viz::SharedBitmap> CreateAndFillSharedBitmap( |
| viz::SharedBitmapManager* manager, |
| const gfx::Size& size, |
| viz::ResourceFormat format, |
| uint32_t value) { |
| std::unique_ptr<viz::SharedBitmap> shared_bitmap = |
| manager->AllocateSharedBitmap(size, format); |
| CHECK(shared_bitmap); |
| uint32_t* pixels = reinterpret_cast<uint32_t*>(shared_bitmap->pixels()); |
| CHECK(pixels); |
| std::fill_n(pixels, size.GetArea(), value); |
| return shared_bitmap; |
| } |
| |
| static viz::ResourceSettings CreateResourceSettings() { |
| viz::ResourceSettings resource_settings; |
| resource_settings.use_gpu_memory_buffer_resources = |
| kUseGpuMemoryBufferResources; |
| return resource_settings; |
| } |
| |
| // Shared data between multiple ResourceProviderContext. 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<viz::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<viz::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<viz::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<viz::TestTexture>>; |
| TextureMap textures_; |
| std::unordered_map<unsigned, uint32_t> sync_point_for_mailbox_; |
| }; |
| |
| class ResourceProviderContext : public viz::TestWebGraphicsContext3D { |
| public: |
| static std::unique_ptr<ResourceProviderContext> Create( |
| ContextSharedData* shared_data) { |
| return base::WrapUnique(new ResourceProviderContext(shared_data)); |
| } |
| |
| void genSyncToken(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 waitSyncToken(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, |
| GLenum 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, 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 genMailboxCHROMIUM(GLbyte* mailbox) override { |
| return shared_data_->GenMailbox(mailbox); |
| } |
| |
| void produceTextureDirectCHROMIUM(GLuint texture, |
| const GLbyte* mailbox) override { |
| // 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 = createTexture(); |
| base::AutoLock lock_for_texture_access(namespace_->lock); |
| scoped_refptr<viz::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, |
| viz::ResourceFormat format, |
| uint8_t* pixels) { |
| CheckTextureIsBound(GL_TEXTURE_2D); |
| base::AutoLock lock_for_texture_access(namespace_->lock); |
| scoped_refptr<viz::TestTexture> texture = BoundTexture(GL_TEXTURE_2D); |
| ASSERT_EQ(texture->size, size); |
| ASSERT_EQ(texture->format, format); |
| memcpy(pixels, texture->data.get(), TextureSizeBytes(size, format)); |
| } |
| |
| protected: |
| explicit ResourceProviderContext(ContextSharedData* shared_data) |
| : shared_data_(shared_data) {} |
| |
| private: |
| void AllocateTexture(const gfx::Size& size, GLenum format) { |
| CheckTextureIsBound(GL_TEXTURE_2D); |
| viz::ResourceFormat texture_format = viz::RGBA_8888; |
| switch (format) { |
| case GL_RGBA: |
| texture_format = viz::RGBA_8888; |
| break; |
| case GL_BGRA_EXT: |
| texture_format = viz::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<viz::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<viz::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 context3d(ResourceProviderContext::Create(shared_data_.get())); |
| context3d_ = context3d.get(); |
| context_provider_ = |
| viz::TestContextProvider::Create(std::move(context3d)); |
| context_provider_->UnboundTestContext3d() |
| ->set_support_texture_format_bgra8888(true); |
| context_provider_->BindToCurrentThread(); |
| |
| auto child_context_owned = |
| ResourceProviderContext::Create(shared_data_.get()); |
| child_context_ = child_context_owned.get(); |
| child_context_provider_ = |
| viz::TestContextProvider::Create(std::move(child_context_owned)); |
| child_context_provider_->UnboundTestContext3d() |
| ->set_support_texture_format_bgra8888(true); |
| child_context_provider_->BindToCurrentThread(); |
| gpu_memory_buffer_manager_ = |
| std::make_unique<viz::TestGpuMemoryBufferManager>(); |
| child_gpu_memory_buffer_manager_ = |
| gpu_memory_buffer_manager_->CreateClientGpuMemoryBufferManager(); |
| } else { |
| shared_bitmap_manager_ = std::make_unique<viz::TestSharedBitmapManager>(); |
| } |
| |
| resource_provider_ = std::make_unique<DisplayResourceProvider>( |
| context_provider_.get(), shared_bitmap_manager_.get()); |
| |
| MakeChildResourceProvider(); |
| } |
| |
| DisplayResourceProviderTest() : DisplayResourceProviderTest(true) {} |
| |
| bool use_gpu() const { return use_gpu_; } |
| |
| void MakeChildResourceProvider() { |
| child_resource_provider_ = std::make_unique<LayerTreeResourceProvider>( |
| child_context_provider_.get(), shared_bitmap_manager_.get(), |
| child_gpu_memory_buffer_manager_.get(), child_needs_sync_token_, |
| CreateResourceSettings()); |
| } |
| |
| static void CollectResources( |
| std::vector<viz::ReturnedResource>* array, |
| const std::vector<viz::ReturnedResource>& returned) { |
| array->insert(array->end(), returned.begin(), returned.end()); |
| } |
| |
| static ReturnCallback GetReturnCallback( |
| std::vector<viz::ReturnedResource>* array) { |
| return base::BindRepeating(&DisplayResourceProviderTest::CollectResources, |
| array); |
| } |
| |
| static void SetResourceFilter(DisplayResourceProvider* resource_provider, |
| viz::ResourceId id, |
| GLenum filter) { |
| DisplayResourceProvider::ScopedSamplerGL sampler(resource_provider, id, |
| GL_TEXTURE_2D, filter); |
| } |
| |
| ResourceProviderContext* context() { return context3d_; } |
| |
| viz::ResourceId CreateChildMailbox(gpu::SyncToken* release_sync_token, |
| bool* lost_resource, |
| bool* release_called, |
| gpu::SyncToken* sync_token, |
| viz::ResourceFormat format) { |
| if (use_gpu()) { |
| unsigned texture = child_context_->createTexture(); |
| gpu::Mailbox gpu_mailbox; |
| child_context_->genMailboxCHROMIUM(gpu_mailbox.name); |
| child_context_->produceTextureDirectCHROMIUM(texture, gpu_mailbox.name); |
| child_context_->genSyncToken(sync_token->GetData()); |
| EXPECT_TRUE(sync_token->HasData()); |
| |
| std::unique_ptr<viz::SharedBitmap> shared_bitmap; |
| std::unique_ptr<viz::SingleReleaseCallback> callback = |
| viz::SingleReleaseCallback::Create(base::BindRepeating( |
| ReleaseSharedBitmapCallback, base::Passed(&shared_bitmap), |
| release_called, release_sync_token, lost_resource)); |
| viz::TransferableResource gl_resource = viz::TransferableResource::MakeGL( |
| gpu_mailbox, GL_LINEAR, GL_TEXTURE_2D, *sync_token); |
| gl_resource.format = format; |
| return child_resource_provider_->ImportResource(gl_resource, |
| std::move(callback)); |
| } else { |
| gfx::Size size(64, 64); |
| std::unique_ptr<viz::SharedBitmap> shared_bitmap( |
| CreateAndFillSharedBitmap(shared_bitmap_manager_.get(), size, format, |
| 0)); |
| |
| viz::SharedBitmap* shared_bitmap_ptr = shared_bitmap.get(); |
| std::unique_ptr<viz::SingleReleaseCallback> callback = |
| viz::SingleReleaseCallback::Create(base::BindRepeating( |
| ReleaseSharedBitmapCallback, base::Passed(&shared_bitmap), |
| release_called, release_sync_token, lost_resource)); |
| return child_resource_provider_->ImportResource( |
| viz::TransferableResource::MakeSoftware( |
| shared_bitmap_ptr->id(), shared_bitmap_ptr->sequence_number(), |
| size, format), |
| std::move(callback)); |
| } |
| } |
| |
| viz::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); |
| |
| gpu::Mailbox gpu_mailbox; |
| gpu_mailbox.name[0] = c; |
| gpu_mailbox.name[1] = 0; |
| auto resource = viz::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_; |
| ResourceProviderContext* context3d_ = nullptr; |
| ResourceProviderContext* child_context_ = nullptr; |
| scoped_refptr<viz::TestContextProvider> context_provider_; |
| scoped_refptr<viz::TestContextProvider> child_context_provider_; |
| std::unique_ptr<viz::TestGpuMemoryBufferManager> gpu_memory_buffer_manager_; |
| std::unique_ptr<DisplayResourceProvider> resource_provider_; |
| std::unique_ptr<viz::TestGpuMemoryBufferManager> |
| child_gpu_memory_buffer_manager_; |
| std::unique_ptr<LayerTreeResourceProvider> child_resource_provider_; |
| std::unique_ptr<viz::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; |
| |
| gfx::Size size(1, 1); |
| viz::ResourceFormat format = viz::RGBA_8888; |
| |
| viz::ResourceId id1 = child_resource_provider_->CreateGpuTextureResource( |
| size, viz::ResourceTextureHint::kDefault, format, gfx::ColorSpace()); |
| uint8_t data1[4] = {1, 2, 3, 4}; |
| child_resource_provider_->CopyToResource(id1, data1, size); |
| std::vector<viz::ReturnedResource> returned_to_child; |
| int child_id = |
| resource_provider_->CreateChild(GetReturnCallback(&returned_to_child)); |
| |
| // Transfer some resources to the parent. |
| ResourceProvider::ResourceIdArray resource_ids_to_transfer; |
| resource_ids_to_transfer.push_back(id1); |
| |
| std::vector<viz::TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent(resource_ids_to_transfer, |
| &list); |
| 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. |
| ResourceProvider::ResourceIdMap resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| |
| unsigned parent_id = resource_map[list.front().id]; |
| |
| DisplayResourceProvider::LockSetForExternalUse lock_set( |
| resource_provider_.get()); |
| |
| viz::ResourceMetadata metadata = lock_set.LockResource(parent_id); |
| ASSERT_EQ(size, metadata.size); |
| ASSERT_FALSE(metadata.mailbox.IsZero()); |
| ASSERT_TRUE(metadata.sync_token.HasData()); |
| |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, |
| viz::ResourceIdSet()); |
| // The resource should not be returned due to the external use lock. |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| gpu::SyncToken sync_token(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x234), |
| 0x456); |
| sync_token.SetVerifyFlush(); |
| lock_set.UnlockResources(sync_token); |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, |
| viz::ResourceIdSet()); |
| // The resource should be returned after the lock is released. |
| EXPECT_EQ(1u, returned_to_child.size()); |
| EXPECT_EQ(sync_token, returned_to_child[0].sync_token); |
| child_resource_provider_->ReceiveReturnsFromParent(returned_to_child); |
| child_resource_provider_->DeleteResource(id1); |
| EXPECT_EQ(0u, child_resource_provider_->num_resources()); |
| } |
| |
| } // namespace |
| } // namespace cc |