Commit 442f3e3a authored by Bo Liu's avatar Bo Liu Committed by Commit Bot

aw: Factor out vulkan gl interop

Keep comment code such as creating SkColorSpace or converting parameters
in AwDrawFnImpl.

Fixed: 1141685
Change-Id: I0c7191b59fffe413259d7ee308eb2f4610424ba6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2495918
Commit-Queue: Bo <boliu@chromium.org>
Reviewed-by: default avatarVasiliy Telezhnikov <vasilyt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#820871}
parent cc66c8f5
......@@ -70,6 +70,8 @@ source_set("gfx") {
"task_queue_web_view.h",
"viz_compositor_thread_runner_webview.cc",
"viz_compositor_thread_runner_webview.h",
"vulkan_gl_interop.cc",
"vulkan_gl_interop.h",
]
deps = [
......
......@@ -9,102 +9,20 @@
#include "android_webview/browser/gfx/aw_vulkan_context_provider.h"
#include "android_webview/browser_jni_headers/AwDrawFnImpl_jni.h"
#include "android_webview/common/aw_switches.h"
#include "android_webview/public/browser/draw_gl.h"
#include "base/android/android_hardware_buffer_compat.h"
#include "base/android/scoped_hardware_buffer_fence_sync.h"
#include "base/command_line.h"
#include "base/trace_event/trace_event.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.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/gpu/GrBackendSemaphore.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/gpu/GrDirectContext.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/color_space.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"
using base::android::JavaParamRef;
using content::BrowserThread;
namespace android_webview {
namespace {
GLNonOwnedCompatibilityContext* g_gl_context = nullptr;
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);
}
}
class GLNonOwnedCompatibilityContext : public gl::GLContextEGL {
public:
GLNonOwnedCompatibilityContext()
: gl::GLContextEGL(nullptr),
surface_(
base::MakeRefCounted<gl::PbufferGLSurfaceEGL>(gfx::Size(1, 1))) {
gl::GLContextAttribs attribs;
Initialize(surface_.get(), attribs);
DCHECK(!g_gl_context);
g_gl_context = this;
}
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_;
DISALLOW_COPY_AND_ASSIGN(GLNonOwnedCompatibilityContext);
};
namespace {
AwDrawFnFunctionTable* g_draw_fn_function_table = nullptr;
......@@ -177,6 +95,28 @@ sk_sp<GrVkSecondaryCBDrawContext> CreateDrawContext(
&props);
}
template <typename T>
HardwareRendererDrawParams CreateHRDrawParams(T* params,
SkColorSpace* color_space) {
struct HardwareRendererDrawParams hr_params {};
hr_params.clip_left = params->clip_left;
hr_params.clip_top = params->clip_top;
hr_params.clip_right = params->clip_right;
hr_params.clip_bottom = params->clip_bottom;
hr_params.width = params->width;
hr_params.height = params->height;
if (color_space)
hr_params.color_space = gfx::ColorSpace(*color_space);
static_assert(base::size(decltype(params->transform){}) ==
base::size(hr_params.transform),
"transform size mismatch");
for (size_t i = 0; i < base::size(hr_params.transform); ++i) {
hr_params.transform[i] = params->transform[i];
}
return hr_params;
}
template <typename T>
sk_sp<SkColorSpace> CreateColorSpace(T* params) {
skcms_TransferFunction transfer_fn{
......@@ -253,9 +193,8 @@ void AwDrawFnImpl::OnSync(AwDrawFn_OnSyncParams* params) {
}
void AwDrawFnImpl::OnContextDestroyed() {
// Try to make GL current to safely clean up. Ignore failures.
if (gl_context_)
gl_context_->MakeCurrent();
if (interop_)
interop_->MakeGLContextCurrentIgnoreFailure();
{
RenderThreadManager::InsideHardwareReleaseReset release_reset(
......@@ -264,16 +203,15 @@ void AwDrawFnImpl::OnContextDestroyed() {
false /* save_restore */);
}
// Clear the queue.
{ auto queue = std::move(in_flight_interop_draws_); }
interop_.reset();
vulkan_context_provider_.reset();
gl_context_.reset();
}
void AwDrawFnImpl::DrawGL(AwDrawFn_DrawGLParams* params) {
auto color_space = params->version >= 2 ? CreateColorSpace(params) : nullptr;
DrawInternal(params, color_space.get());
HardwareRendererDrawParams hr_params =
CreateHRDrawParams(params, color_space.get());
render_thread_manager_.DrawOnRT(false /* save_restore */, &hr_params);
}
void AwDrawFnImpl::InitVk(AwDrawFn_InitVkParams* params) {
......@@ -284,47 +222,58 @@ void AwDrawFnImpl::InitVk(AwDrawFn_InitVkParams* params) {
render_thread_manager_.SetVulkanContextProviderOnRT(
vulkan_context_provider_.get());
// Make sure we have a GL context.
DCHECK(!gl_context_);
gl_context_ = GLNonOwnedCompatibilityContext::GetOrCreateInstance();
if (is_interop_mode_ && vulkan_context_provider_) {
interop_.emplace(&render_thread_manager_, vulkan_context_provider_.get());
}
}
void AwDrawFnImpl::DrawVk(AwDrawFn_DrawVkParams* params) {
if (!vulkan_context_provider_)
return;
auto color_space = CreateColorSpace(params);
if (!color_space) {
// If we weren't passed a valid colorspace, default to sRGB.
LOG(ERROR) << "Received invalid colorspace.";
color_space = SkColorSpace::MakeSRGB();
}
auto draw_context = CreateDrawContext(
vulkan_context_provider_->GetGrContext(), params, color_space);
HardwareRendererDrawParams hr_params =
CreateHRDrawParams(params, color_space.get());
if (is_interop_mode_) {
DrawVkInterop(params);
DCHECK(interop_);
interop_->DrawVk(std::move(draw_context), std::move(color_space),
&hr_params);
} else {
DrawVkDirect(params);
DrawVkDirect(std::move(draw_context), std::move(color_space), &hr_params);
}
}
void AwDrawFnImpl::PostDrawVk(AwDrawFn_PostDrawVkParams* params) {
if (!vulkan_context_provider_)
return;
if (is_interop_mode_) {
PostDrawVkInterop(params);
DCHECK(interop_);
interop_->PostDrawVk();
} else {
PostDrawVkDirect(params);
}
}
void AwDrawFnImpl::DrawVkDirect(AwDrawFn_DrawVkParams* params) {
if (!vulkan_context_provider_)
return;
void AwDrawFnImpl::DrawVkDirect(sk_sp<GrVkSecondaryCBDrawContext> draw_context,
sk_sp<SkColorSpace> color_space,
HardwareRendererDrawParams* hr_params) {
DCHECK(!scoped_secondary_cb_draw_);
auto color_space = CreateColorSpace(params);
if (!color_space) {
// If we weren't passed a valid colorspace, default to sRGB.
LOG(ERROR) << "Received invalid colorspace.";
color_space = SkColorSpace::MakeSRGB();
}
auto draw_context = CreateDrawContext(
vulkan_context_provider_->GetGrContext(), params, color_space);
// Set the draw contexct in |vulkan_context_provider_|, so the SkiaRenderer
// and SkiaOutputSurface* will use it as frame render target.
scoped_secondary_cb_draw_.emplace(vulkan_context_provider_.get(),
std::move(draw_context));
DrawInternal(params, color_space.get());
render_thread_manager_.DrawOnRT(false /* save_restore */, hr_params);
}
void AwDrawFnImpl::PostDrawVkDirect(AwDrawFn_PostDrawVkParams* params) {
......@@ -335,328 +284,4 @@ void AwDrawFnImpl::PostDrawVkDirect(AwDrawFn_PostDrawVkParams* params) {
scoped_secondary_cb_draw_.reset();
}
void AwDrawFnImpl::DrawVkInterop(AwDrawFn_DrawVkParams* params) {
if (!vulkan_context_provider_ || !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_.get());
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));
auto color_space = CreateColorSpace(params);
if (!color_space) {
// If we weren't passed a valid colorspace, default to sRGB.
LOG(ERROR) << "Received invalid colorspace.";
color_space = SkColorSpace::MakeSRGB();
}
// 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);
DrawInternal(params, color_space.get());
gl_done_fd = gpu::CreateEglFenceAndExportFd();
}
pending_draw->draw_context = CreateDrawContext(
vulkan_context_provider_->GetGrContext(), params, color_space);
// 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,
0 /* usage */);
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, &paint);
pending_draw->draw_context->flush();
// Transfer |pending_draw| to |pending_draw_| for handling in
// PostDrawVkInterop.
pending_draw_ = std::move(pending_draw);
}
void AwDrawFnImpl::PostDrawVkInterop(AwDrawFn_PostDrawVkParams* params) {
// Use a temporary to automatically clean up if we hit a failure case.
std::unique_ptr<InFlightInteropDraw> pending_draw = std::move(pending_draw_);
if (!vulkan_context_provider_ || !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();
}
template <typename T>
void AwDrawFnImpl::DrawInternal(T* params, SkColorSpace* color_space) {
struct HardwareRendererDrawParams hr_params {};
hr_params.clip_left = params->clip_left;
hr_params.clip_top = params->clip_top;
hr_params.clip_right = params->clip_right;
hr_params.clip_bottom = params->clip_bottom;
hr_params.width = params->width;
hr_params.height = params->height;
if (color_space)
hr_params.color_space = gfx::ColorSpace(*color_space);
static_assert(base::size(decltype(params->transform){}) ==
base::size(hr_params.transform),
"transform size mismatch");
for (size_t i = 0; i < base::size(hr_params.transform); ++i) {
hr_params.transform[i] = params->transform[i];
}
render_thread_manager_.DrawOnRT(false /* save_restore */, &hr_params);
}
AwDrawFnImpl::InFlightInteropDraw::InFlightInteropDraw(
AwVulkanContextProvider* vk_context_provider)
: vk_context_provider(vk_context_provider) {}
AwDrawFnImpl::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));
}
}
} // namespace android_webview
......@@ -10,27 +10,14 @@
#include "android_webview/browser/gfx/aw_vulkan_context_provider.h"
#include "android_webview/browser/gfx/compositor_frame_consumer.h"
#include "android_webview/browser/gfx/render_thread_manager.h"
#include "android_webview/browser/gfx/vulkan_gl_interop.h"
#include "android_webview/public/browser/draw_fn.h"
#include "base/android/jni_weak_ref.h"
#include "base/containers/queue.h"
#include "base/files/scoped_file.h"
#include "base/android/scoped_java_ref.h"
#include "base/macros.h"
#include "base/optional.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/gpu/vk/GrVkTypes.h"
class GrVkSecondaryCBDrawContext;
namespace gl {
class GLImageAHardwareBuffer;
}
namespace gpu {
class VulkanImage;
}
namespace android_webview {
class GLNonOwnedCompatibilityContext;
class AwDrawFnImpl {
public:
......@@ -55,36 +42,11 @@ class AwDrawFnImpl {
private:
// With direct mode, we will render frames with Vulkan API directly.
void DrawVkDirect(AwDrawFn_DrawVkParams* params);
void DrawVkDirect(sk_sp<GrVkSecondaryCBDrawContext> draw_context,
sk_sp<SkColorSpace> color_space,
HardwareRendererDrawParams* params);
void PostDrawVkDirect(AwDrawFn_PostDrawVkParams* params);
// With interop mode, we will render frames on AHBs with GL api, and then draw
// AHBs with Vulkan API on the final target.
void DrawVkInterop(AwDrawFn_DrawVkParams* params);
void PostDrawVkInterop(AwDrawFn_PostDrawVkParams* params);
template <typename T>
void DrawInternal(T* params, SkColorSpace* color_space);
// Struct which represents one in-flight draw for the Vk interop path.
struct InFlightInteropDraw {
explicit InFlightInteropDraw(AwVulkanContextProvider* vk_context_provider);
~InFlightInteropDraw();
sk_sp<GrVkSecondaryCBDrawContext> draw_context;
VkFence post_draw_fence = VK_NULL_HANDLE;
VkSemaphore post_draw_semaphore = VK_NULL_HANDLE;
base::ScopedFD sync_fd;
scoped_refptr<gl::GLImageAHardwareBuffer> ahb_image;
sk_sp<SkImage> ahb_skimage;
uint32_t texture_id = 0;
uint32_t framebuffer_id = 0;
std::unique_ptr<gpu::VulkanImage> vulkan_image;
GrVkImageInfo image_info;
// Used to clean up Vulkan objects.
AwVulkanContextProvider* vk_context_provider;
};
CompositorFrameConsumer* GetCompositorFrameConsumer() {
return &render_thread_manager_;
}
......@@ -101,12 +63,7 @@ class AwDrawFnImpl {
base::Optional<AwVulkanContextProvider::ScopedSecondaryCBDraw>
scoped_secondary_cb_draw_;
// GL context used to draw via GL in Vk interop path.
scoped_refptr<GLNonOwnedCompatibilityContext> gl_context_;
// Queue of draw contexts pending cleanup.
base::queue<std::unique_ptr<InFlightInteropDraw>> in_flight_interop_draws_;
std::unique_ptr<InFlightInteropDraw> pending_draw_;
base::Optional<VulkanGLInterop> interop_;
DISALLOW_COPY_AND_ASSIGN(AwDrawFnImpl);
};
......
// 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/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>(gfx::Size(1, 1))) {
gl::GLContextAttribs attribs;
Initialize(surface_.get(), attribs);
DCHECK(!g_gl_context);
g_gl_context = this;
}
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_;
DISALLOW_COPY_AND_ASSIGN(GLNonOwnedCompatibilityContext);
};
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,
HardwareRendererDrawParams* 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(false /* save_restore */, 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,
0 /* usage */);
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, &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
// 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.
#ifndef ANDROID_WEBVIEW_BROWSER_GFX_VULKAN_GL_INTEROP_H_
#define ANDROID_WEBVIEW_BROWSER_GFX_VULKAN_GL_INTEROP_H_
#include <memory>
#include "android_webview/browser/gfx/aw_vulkan_context_provider.h"
#include "android_webview/public/browser/draw_fn.h"
#include "base/containers/queue.h"
#include "base/files/scoped_file.h"
#include "base/memory/scoped_refptr.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/gpu/vk/GrVkTypes.h"
class GrVkSecondaryCBDrawContext;
class SkColorSpace;
namespace gl {
class GLImageAHardwareBuffer;
}
namespace gpu {
class VulkanImage;
}
namespace android_webview {
class RenderThreadManager;
struct HardwareRendererDrawParams;
// With interop mode, we will render frames on AHBs with GL api, and then draw
// AHBs with Vulkan API on the final target.
class VulkanGLInterop {
public:
VulkanGLInterop(RenderThreadManager* render_thread_manager,
AwVulkanContextProvider* vulkan_context_provider);
~VulkanGLInterop();
void DrawVk(sk_sp<GrVkSecondaryCBDrawContext> draw_context,
sk_sp<SkColorSpace> color_space,
HardwareRendererDrawParams* params);
void PostDrawVk();
// For clean up.
void MakeGLContextCurrentIgnoreFailure();
private:
class GLNonOwnedCompatibilityContext;
static GLNonOwnedCompatibilityContext* g_gl_context;
// Struct which represents one in-flight draw for the Vk interop path.
struct InFlightInteropDraw {
explicit InFlightInteropDraw(AwVulkanContextProvider* vk_context_provider);
~InFlightInteropDraw();
sk_sp<GrVkSecondaryCBDrawContext> draw_context;
VkFence post_draw_fence = VK_NULL_HANDLE;
VkSemaphore post_draw_semaphore = VK_NULL_HANDLE;
base::ScopedFD sync_fd;
scoped_refptr<gl::GLImageAHardwareBuffer> ahb_image;
sk_sp<SkImage> ahb_skimage;
uint32_t texture_id = 0;
uint32_t framebuffer_id = 0;
std::unique_ptr<gpu::VulkanImage> vulkan_image;
GrVkImageInfo image_info;
// Used to clean up Vulkan objects.
AwVulkanContextProvider* vk_context_provider;
};
RenderThreadManager* const render_thread_manager_;
AwVulkanContextProvider* const vulkan_context_provider_;
// GL context used to draw via GL in Vk interop path.
scoped_refptr<GLNonOwnedCompatibilityContext> gl_context_;
// Queue of draw contexts pending cleanup.
base::queue<std::unique_ptr<InFlightInteropDraw>> in_flight_interop_draws_;
std::unique_ptr<InFlightInteropDraw> pending_draw_;
};
} // namespace android_webview
#endif // ANDROID_WEBVIEW_BROWSER_GFX_VULKAN_GL_INTEROP_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment