123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- // Copyright 2020 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 "android_webview/browser/gfx/vulkan_gl_interop.h"
- #include <utility>
- #include "android_webview/browser/gfx/render_thread_manager.h"
- #include "base/android/android_hardware_buffer_compat.h"
- #include "base/android/scoped_hardware_buffer_fence_sync.h"
- #include "gpu/command_buffer/service/skia_utils.h"
- #include "gpu/ipc/common/android/android_image_reader_utils.h"
- #include "gpu/vulkan/vulkan_fence_helper.h"
- #include "gpu/vulkan/vulkan_function_pointers.h"
- #include "gpu/vulkan/vulkan_image.h"
- #include "gpu/vulkan/vulkan_implementation.h"
- #include "third_party/skia/include/core/SkCanvas.h"
- #include "third_party/skia/include/core/SkColorSpace.h"
- #include "third_party/skia/include/gpu/GrBackendSemaphore.h"
- #include "third_party/skia/include/gpu/vk/GrVkBackendContext.h"
- #include "third_party/skia/include/gpu/vk/GrVkExtensions.h"
- #include "third_party/skia/src/gpu/vk/GrVkSecondaryCBDrawContext.h"
- #include "ui/gfx/gpu_memory_buffer.h"
- #include "ui/gl/gl_bindings.h"
- #include "ui/gl/gl_context_egl.h"
- #include "ui/gl/gl_image_ahardwarebuffer.h"
- #include "ui/gl/gl_surface_egl.h"
- #include "ui/gl/init/gl_factory.h"
- namespace android_webview {
- namespace {
- void CleanupInFlightDraw(sk_sp<GrVkSecondaryCBDrawContext> draw_context,
- VkSemaphore post_draw_semaphore,
- gpu::VulkanDeviceQueue* device_queue,
- bool /* context_lost */) {
- VkDevice device = device_queue->GetVulkanDevice();
- // We do the same thing whether or not context is lost.
- draw_context->releaseResources();
- draw_context.reset();
- if (post_draw_semaphore != VK_NULL_HANDLE)
- vkDestroySemaphore(device, post_draw_semaphore, nullptr);
- }
- } // namespace
- // static
- VulkanGLInterop::GLNonOwnedCompatibilityContext* VulkanGLInterop::g_gl_context =
- nullptr;
- class VulkanGLInterop::GLNonOwnedCompatibilityContext
- : public gl::GLContextEGL {
- public:
- GLNonOwnedCompatibilityContext()
- : gl::GLContextEGL(nullptr),
- surface_(base::MakeRefCounted<gl::PbufferGLSurfaceEGL>(
- gl::GLSurfaceEGL::GetGLDisplayEGL(),
- gfx::Size(1, 1))) {
- gl::GLContextAttribs attribs;
- Initialize(surface_.get(), attribs);
- DCHECK(!g_gl_context);
- g_gl_context = this;
- }
- GLNonOwnedCompatibilityContext(const GLNonOwnedCompatibilityContext&) =
- delete;
- GLNonOwnedCompatibilityContext& operator=(
- const GLNonOwnedCompatibilityContext&) = delete;
- bool MakeCurrentImpl(gl::GLSurface* surface) override {
- // A GLNonOwnedCompatibilityContext may have set the GetRealCurrent()
- // pointer to itself, while re-using our EGL context. In these cases just
- // call SetCurrent to restore expected GetRealContext().
- if (GetHandle() == eglGetCurrentContext()) {
- if (surface) {
- // No one should change the current EGL surface without also changing
- // the context.
- DCHECK(eglGetCurrentSurface(EGL_DRAW) == surface->GetHandle());
- }
- SetCurrent(surface);
- return true;
- }
- return gl::GLContextEGL::MakeCurrentImpl(surface);
- }
- bool MakeCurrent() { return gl::GLContext::MakeCurrent(surface_.get()); }
- static scoped_refptr<GLNonOwnedCompatibilityContext> GetOrCreateInstance() {
- if (g_gl_context)
- return base::WrapRefCounted(g_gl_context);
- return base::WrapRefCounted(new GLNonOwnedCompatibilityContext);
- }
- private:
- ~GLNonOwnedCompatibilityContext() override {
- DCHECK_EQ(g_gl_context, this);
- g_gl_context = nullptr;
- }
- scoped_refptr<gl::GLSurface> surface_;
- };
- VulkanGLInterop::InFlightInteropDraw::InFlightInteropDraw(
- AwVulkanContextProvider* vk_context_provider)
- : vk_context_provider(vk_context_provider) {}
- VulkanGLInterop::InFlightInteropDraw::~InFlightInteropDraw() {
- // If |draw_context| is valid, we encountered an error during Vk drawing and
- // should call vkQueueWaitIdle to ensure safe shutdown.
- bool encountered_error = !!draw_context;
- if (encountered_error) {
- // Clean up one-off objects which may have been left alive due to an error.
- // Clean up |ahb_skimage| first, as doing so generates Vk commands we need
- // to flush before the vkQueueWaitIdle below.
- if (ahb_skimage) {
- ahb_skimage.reset();
- vk_context_provider->GetGrContext()->flushAndSubmit();
- }
- // We encountered an error and are not sure when our Vk objects are safe to
- // delete. VkQueueWaitIdle to ensure safety.
- vkQueueWaitIdle(vk_context_provider->queue());
- if (draw_context) {
- draw_context->releaseResources();
- draw_context.reset();
- }
- if (post_draw_semaphore != VK_NULL_HANDLE) {
- vkDestroySemaphore(vk_context_provider->device(), post_draw_semaphore,
- nullptr);
- post_draw_semaphore = VK_NULL_HANDLE;
- }
- }
- DCHECK(!draw_context);
- DCHECK(!ahb_skimage);
- DCHECK(post_draw_semaphore == VK_NULL_HANDLE);
- // Clean up re-usable components that are expected to still be alive.
- if (texture_id)
- glDeleteTextures(1, &texture_id);
- if (framebuffer_id)
- glDeleteFramebuffersEXT(1, &framebuffer_id);
- if (vulkan_image) {
- vk_context_provider->GetDeviceQueue()
- ->GetFenceHelper()
- ->EnqueueVulkanObjectCleanupForSubmittedWork(std::move(vulkan_image));
- }
- }
- VulkanGLInterop::VulkanGLInterop(
- RenderThreadManager* render_thread_manager,
- AwVulkanContextProvider* vulkan_context_provider)
- : render_thread_manager_(render_thread_manager),
- vulkan_context_provider_(vulkan_context_provider),
- gl_context_(GLNonOwnedCompatibilityContext::GetOrCreateInstance()) {
- DCHECK(render_thread_manager_);
- DCHECK(vulkan_context_provider_);
- }
- VulkanGLInterop::~VulkanGLInterop() {
- // Clear the queue.
- { auto queue = std::move(in_flight_interop_draws_); }
- gl_context_.reset();
- }
- void VulkanGLInterop::DrawVk(sk_sp<GrVkSecondaryCBDrawContext> draw_context,
- sk_sp<SkColorSpace> color_space,
- const HardwareRendererDrawParams& params,
- const OverlaysParams& overlays_params) {
- if (!gl_context_)
- return;
- if (!gl_context_->MakeCurrent()) {
- LOG(ERROR) << "Failed to make GL context current for drawing.";
- return;
- }
- // If |pending_draw_| is non-null, we called DrawVk twice without PostDrawVk.
- DCHECK(!pending_draw_);
- // Use a temporary to automatically clean up if we hit a failure case.
- std::unique_ptr<InFlightInteropDraw> pending_draw;
- // If we've exhausted our buffers, re-use an existing one.
- // TODO(ericrk): Benchmark using more than 1 buffer.
- if (in_flight_interop_draws_.size() >= 1 /* single buffering */) {
- pending_draw = std::move(in_flight_interop_draws_.front());
- in_flight_interop_draws_.pop();
- }
- // If prev buffer is wrong size, just re-allocate.
- if (pending_draw && pending_draw->ahb_image->GetSize() !=
- gfx::Size(params.width, params.height)) {
- pending_draw.reset();
- }
- // If we weren't able to re-use a previous draw, create one.
- if (!pending_draw) {
- pending_draw =
- std::make_unique<InFlightInteropDraw>(vulkan_context_provider_);
- AHardwareBuffer_Desc desc = {};
- desc.width = params.width;
- desc.height = params.height;
- desc.layers = 1; // number of images
- // TODO(ericrk): Handle other formats.
- desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
- desc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER |
- AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
- AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
- AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT;
- AHardwareBuffer* buffer = nullptr;
- base::AndroidHardwareBufferCompat::GetInstance().Allocate(&desc, &buffer);
- if (!buffer) {
- LOG(ERROR) << "Failed to allocate AHardwareBuffer for WebView rendering.";
- return;
- }
- auto scoped_buffer =
- base::android::ScopedHardwareBufferHandle::Adopt(buffer);
- pending_draw->ahb_image = base::MakeRefCounted<gl::GLImageAHardwareBuffer>(
- gfx::Size(params.width, params.height));
- if (!pending_draw->ahb_image->Initialize(scoped_buffer.get(),
- false /* preserved */)) {
- LOG(ERROR) << "Failed to initialize GLImage for AHardwareBuffer.";
- return;
- }
- glGenTextures(1, static_cast<GLuint*>(&pending_draw->texture_id));
- GLenum target = GL_TEXTURE_2D;
- glBindTexture(target, pending_draw->texture_id);
- if (!pending_draw->ahb_image->BindTexImage(target)) {
- LOG(ERROR) << "Failed to bind GLImage for AHardwareBuffer.";
- return;
- }
- glBindTexture(target, 0);
- glGenFramebuffersEXT(1, &pending_draw->framebuffer_id);
- glBindFramebufferEXT(GL_FRAMEBUFFER, pending_draw->framebuffer_id);
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- GL_TEXTURE_2D, pending_draw->texture_id, 0);
- if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) !=
- GL_FRAMEBUFFER_COMPLETE) {
- LOG(ERROR) << "Failed to set up framebuffer for WebView GL drawing.";
- return;
- }
- }
- // Ask GL to wait on any Vk sync_fd before writing.
- gpu::InsertEglFenceAndWait(std::move(pending_draw->sync_fd));
- // Bind buffer and render with GL.
- base::ScopedFD gl_done_fd;
- {
- glBindFramebufferEXT(GL_FRAMEBUFFER, pending_draw->framebuffer_id);
- glViewport(0, 0, params.width, params.height);
- glDisable(GL_STENCIL_TEST);
- glDisable(GL_SCISSOR_TEST);
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- render_thread_manager_->DrawOnRT(/*save_restore=*/false, params,
- overlays_params);
- gl_done_fd = gpu::CreateEglFenceAndExportFd();
- }
- pending_draw->draw_context = std::move(draw_context);
- // If we have a |gl_done_fd|, create a Skia GrBackendSemaphore from
- // |gl_done_fd| and wait.
- if (gl_done_fd.is_valid()) {
- VkSemaphore gl_done_semaphore =
- vulkan_context_provider_->GetVulkanImplementation()
- ->ImportSemaphoreHandle(
- vulkan_context_provider_->device(),
- gpu::SemaphoreHandle(
- VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
- std::move(gl_done_fd)));
- if (gl_done_semaphore == VK_NULL_HANDLE) {
- LOG(ERROR) << "Could not create Vulkan semaphore for GL completion.";
- return;
- }
- GrBackendSemaphore gr_semaphore;
- gr_semaphore.initVulkan(gl_done_semaphore);
- if (!pending_draw->draw_context->wait(1, &gr_semaphore)) {
- // If wait returns false, we must clean up the |gl_done_semaphore|.
- vkDestroySemaphore(vulkan_context_provider_->device(), gl_done_semaphore,
- nullptr);
- LOG(ERROR) << "Could not wait on GL completion semaphore.";
- return;
- }
- }
- // Create a VkImage and import AHB.
- if (!pending_draw->vulkan_image) {
- auto handle = base::android::ScopedHardwareBufferHandle::Create(
- pending_draw->ahb_image->GetAHardwareBuffer()->buffer());
- gfx::GpuMemoryBufferHandle gmb_handle(std::move(handle));
- auto* device_queue = vulkan_context_provider_->GetDeviceQueue();
- auto vulkan_image = gpu::VulkanImage::CreateFromGpuMemoryBufferHandle(
- device_queue, std::move(gmb_handle),
- gfx::Size(params.width, params.height), VK_FORMAT_R8G8B8A8_UNORM,
- /*usage=*/0, /*flags=*/0, /*image_tiling=*/VK_IMAGE_TILING_OPTIMAL,
- /*queue_family_index=*/VK_QUEUE_FAMILY_EXTERNAL);
- if (!vulkan_image) {
- LOG(ERROR) << "Could not create VkImage from AHB.";
- return;
- }
- pending_draw->image_info = gpu::CreateGrVkImageInfo(vulkan_image.get());
- pending_draw->vulkan_image = std::move(vulkan_image);
- }
- // Create an SkImage from AHB.
- GrBackendTexture backend_texture(params.width, params.height,
- pending_draw->image_info);
- pending_draw->ahb_skimage = SkImage::MakeFromTexture(
- vulkan_context_provider_->GetGrContext(), backend_texture,
- kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
- color_space);
- if (!pending_draw->ahb_skimage) {
- LOG(ERROR) << "Could not create SkImage from VkImage.";
- return;
- }
- // Draw the SkImage.
- SkPaint paint;
- paint.setBlendMode(SkBlendMode::kSrcOver);
- pending_draw->draw_context->getCanvas()->drawImage(
- pending_draw->ahb_skimage, 0, 0, SkSamplingOptions(), &paint);
- pending_draw->draw_context->flush();
- // Transfer |pending_draw| to |pending_draw_| for handling in
- // PostDrawVkInterop.
- pending_draw_ = std::move(pending_draw);
- }
- void VulkanGLInterop::PostDrawVk() {
- // Use a temporary to automatically clean up if we hit a failure case.
- std::unique_ptr<InFlightInteropDraw> pending_draw = std::move(pending_draw_);
- if (!gl_context_ || !pending_draw)
- return;
- // Get the final state of the SkImage so that we can pass this back to Skia
- // during re-use.
- GrBackendTexture backend_texture =
- pending_draw->ahb_skimage->getBackendTexture(
- true /* flushPendingGrContextIO */);
- GrVkImageInfo image_info;
- if (!backend_texture.getVkImageInfo(&image_info)) {
- LOG(ERROR) << "Could not get Vk image info.";
- return;
- }
- // Copy image layout to our cached image info.
- pending_draw->image_info.fImageLayout = image_info.fImageLayout;
- // Release the SkImage so that Skia transitions it back to
- // VK_QUEUE_FAMILY_EXTERNAL.
- pending_draw->ahb_skimage.reset();
- // Create a semaphore to track the image's transition back to external.
- VkExportSemaphoreCreateInfo export_info;
- export_info.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
- export_info.pNext = nullptr;
- export_info.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
- VkSemaphoreCreateInfo sem_info;
- sem_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
- sem_info.pNext = &export_info;
- sem_info.flags = 0;
- VkResult result =
- vkCreateSemaphore(vulkan_context_provider_->device(), &sem_info, nullptr,
- &pending_draw->post_draw_semaphore);
- if (result != VK_SUCCESS) {
- LOG(ERROR) << "Could not create VkSemaphore.";
- return;
- }
- GrBackendSemaphore gr_post_draw_semaphore;
- gr_post_draw_semaphore.initVulkan(pending_draw->post_draw_semaphore);
- // Flush so that we know the image's transition has been submitted and that
- // the |post_draw_semaphore| is pending.
- GrFlushInfo flushInfo;
- flushInfo.fNumSemaphores = 1;
- flushInfo.fSignalSemaphores = &gr_post_draw_semaphore;
- GrSemaphoresSubmitted submitted =
- vulkan_context_provider_->GetGrContext()->flush(flushInfo);
- vulkan_context_provider_->GetGrContext()->submit();
- if (submitted != GrSemaphoresSubmitted::kYes) {
- LOG(ERROR) << "Skia could not submit GrSemaphore.";
- return;
- }
- gpu::SemaphoreHandle semaphore_handle =
- vulkan_context_provider_->GetVulkanImplementation()->GetSemaphoreHandle(
- vulkan_context_provider_->device(),
- pending_draw->post_draw_semaphore);
- if (!semaphore_handle.is_valid()) {
- LOG(ERROR) << "Could not retrieve SyncFD from |post_draw_semaphore|.";
- return;
- }
- pending_draw->sync_fd = semaphore_handle.TakeHandle();
- gpu::VulkanFenceHelper* fence_helper =
- vulkan_context_provider_->GetDeviceQueue()->GetFenceHelper();
- fence_helper->EnqueueCleanupTaskForSubmittedWork(base::BindOnce(
- &CleanupInFlightDraw, std::move(pending_draw->draw_context),
- pending_draw->post_draw_semaphore));
- pending_draw->post_draw_semaphore = VK_NULL_HANDLE;
- // Add the |pending_draw| to |in_flight_interop_draws_|.
- in_flight_interop_draws_.push(std::move(pending_draw));
- // Process cleanup tasks and generate fences at the end of each PostDrawVk.
- // TODO(ericrk): We'd ideally combine this with the flushAndSignalSemaphores
- // above.
- fence_helper->GenerateCleanupFence();
- fence_helper->ProcessCleanupTasks();
- }
- void VulkanGLInterop::MakeGLContextCurrentIgnoreFailure() {
- if (gl_context_)
- gl_context_->MakeCurrent();
- }
- } // namespace android_webview
|