blob: 50afd8430309761c40d3bef75a81baa287f12cc0 [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/viz/common/gpu/context_provider.h"
#include "content/public/browser/android/compositor.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/context_support.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/common/mailbox_holder.h"
#include "gpu/ipc/client/gpu_channel_host.h"
#include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
#include "gpu/ipc/common/gpu_surface_tracker.h"
#include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
#include "ui/gl/android/surface_texture.h"
#include <android/native_window_jni.h>
#define VOID_OFFSET(x) reinterpret_cast<void*>(x)
#define SHADER(Src) #Src
namespace {
/* clang-format off */
const char kQuadCopyVertex[] = SHADER(
precision mediump float;
attribute vec4 a_Position;
attribute vec2 a_TexCoordinate;
varying highp vec2 v_TexCoordinate;
void main() {
v_TexCoordinate = a_TexCoordinate;
gl_Position = a_Position;
}
);
const char kQuadCopyFragment[] = SHADER(
precision highp float;
uniform sampler2D u_Texture;
varying vec2 v_TexCoordinate;
void main() {
gl_FragColor = texture2D(u_Texture, v_TexCoordinate);
}
);
const float kQuadVertices[] = {
// x y u, v
-1.f, 1.f, 0.f, 1.f,
-1.f, -1.f, 0.f, 0.f,
1.f, -1.f, 1.f, 0.f,
1.f, 1.f, 1.f, 1.f};
/* clang-format on */
static constexpr int kQuadVerticesSize = sizeof(kQuadVertices);
GLuint CompileShader(gpu::gles2::GLES2Interface* gl,
GLenum shader_type,
const GLchar* shader_source) {
GLuint shader_handle = gl->CreateShader(shader_type);
if (shader_handle != 0) {
// Pass in the shader source.
GLint len = strlen(shader_source);
gl->ShaderSource(shader_handle, 1, &shader_source, &len);
// Compile the shader.
gl->CompileShader(shader_handle);
// Get the compilation status.
GLint status = 0;
gl->GetShaderiv(shader_handle, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
GLint info_log_length = 0;
gl->GetShaderiv(shader_handle, GL_INFO_LOG_LENGTH, &info_log_length);
auto str_info_log = std::make_unique<GLchar[]>(info_log_length + 1);
gl->GetShaderInfoLog(shader_handle, info_log_length, nullptr,
str_info_log.get());
DLOG(ERROR) << "Error compiling shader: " << str_info_log.get();
gl->DeleteShader(shader_handle);
shader_handle = 0;
}
}
return shader_handle;
}
GLuint CreateAndLinkProgram(gpu::gles2::GLES2Interface* gl,
GLuint vertex_shader_handle,
GLuint fragment_shader_handle) {
GLuint program_handle = gl->CreateProgram();
if (program_handle != 0) {
// Bind the vertex shader to the program.
gl->AttachShader(program_handle, vertex_shader_handle);
// Bind the fragment shader to the program.
gl->AttachShader(program_handle, fragment_shader_handle);
// Link the two shaders together into a program.
gl->LinkProgram(program_handle);
// Get the link status.
GLint link_status = 0;
gl->GetProgramiv(program_handle, GL_LINK_STATUS, &link_status);
// If the link failed, delete the program.
if (link_status == GL_FALSE) {
GLint info_log_length;
gl->GetProgramiv(program_handle, GL_INFO_LOG_LENGTH, &info_log_length);
auto str_info_log = std::make_unique<GLchar[]>(info_log_length + 1);
gl->GetProgramInfoLog(program_handle, info_log_length, nullptr,
str_info_log.get());
DLOG(ERROR) << "Error compiling program: " << str_info_log.get();
gl->DeleteProgram(program_handle);
program_handle = 0;
}
}
return program_handle;
}
GLuint ConsumeTexture(gpu::gles2::GLES2Interface* gl,
const gpu::MailboxHolder& mailbox) {
TRACE_EVENT0("gpu", "MailboxToSurfaceBridge::ConsumeTexture");
gl->WaitSyncTokenCHROMIUM(mailbox.sync_token.GetConstData());
return gl->CreateAndTexStorage2DSharedImageCHROMIUM(mailbox.mailbox.name);
}
} // namespace
namespace vr {
MailboxToSurfaceBridge::MailboxToSurfaceBridge() {
DVLOG(1) << __FUNCTION__;
}
MailboxToSurfaceBridge::~MailboxToSurfaceBridge() {
if (surface_handle_) {
// Unregister from the surface tracker to avoid a resource leak.
gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
tracker->RemoveSurface(surface_handle_);
}
DestroyContext();
DVLOG(1) << __FUNCTION__;
}
bool MailboxToSurfaceBridge::IsConnected() {
return context_provider_ && gl_ && context_support_;
}
bool MailboxToSurfaceBridge::IsGpuWorkaroundEnabled(int32_t workaround) {
DCHECK(IsConnected());
return context_provider_->GetGpuFeatureInfo().IsWorkaroundEnabled(workaround);
}
void MailboxToSurfaceBridge::OnContextAvailableOnUiThread(
scoped_refptr<viz::ContextProvider> provider) {
DVLOG(1) << __FUNCTION__;
// Must save a reference to the viz::ContextProvider to keep it alive,
// otherwise the GL context created from it becomes invalid on its
// destruction.
context_provider_ = std::move(provider);
DCHECK(on_context_bound_);
gl_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&MailboxToSurfaceBridge::BindContextProviderToCurrentThread,
base::Unretained(this)));
}
void MailboxToSurfaceBridge::BindContextProviderToCurrentThread() {
auto result = context_provider_->BindToCurrentThread();
if (result != gpu::ContextResult::kSuccess) {
DLOG(ERROR) << "Failed to init viz::ContextProvider";
return;
}
gl_ = context_provider_->ContextGL();
context_support_ = context_provider_->ContextSupport();
if (!gl_) {
DLOG(ERROR) << "Did not get a GL context";
return;
}
if (!context_support_) {
DLOG(ERROR) << "Did not get a ContextSupport";
return;
}
InitializeRenderer();
DVLOG(1) << __FUNCTION__ << ": Context ready";
if (on_context_bound_) {
std::move(on_context_bound_).Run();
}
}
void MailboxToSurfaceBridge::CreateSurface(
gl::SurfaceTexture* surface_texture) {
ANativeWindow* window = surface_texture->CreateSurface();
gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
ANativeWindow_acquire(window);
// Skip ANativeWindow_setBuffersGeometry, the default size appears to work.
surface_ = std::make_unique<gl::ScopedJavaSurface>(surface_texture);
surface_handle_ =
tracker->AddSurfaceForNativeWidget(gpu::GpuSurfaceTracker::SurfaceRecord(
window, surface_->j_surface().obj(),
false /* can_be_used_with_surface_control */));
// Unregistering happens in the destructor.
ANativeWindow_release(window);
}
void MailboxToSurfaceBridge::CreateAndBindContextProvider(
base::OnceClosure on_bound_callback) {
gl_thread_task_runner_ = base::ThreadTaskRunnerHandle::Get();
on_context_bound_ = std::move(on_bound_callback);
// The callback to run in this thread. It is necessary to keep |surface| alive
// until the context becomes available. So pass it on to the callback, so that
// it stays alive, and is destroyed on the same thread once done.
auto callback =
base::BindRepeating(&MailboxToSurfaceBridge::OnContextAvailableOnUiThread,
weak_ptr_factory_.GetWeakPtr());
base::PostTask(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(
[](int surface_handle,
const content::Compositor::ContextProviderCallback& callback) {
// Our attributes must be compatible with the shared
// offscreen surface used by virtualized contexts,
// otherwise mailbox synchronization doesn't work
// properly - it assumes a shared underlying GL context.
// See GetCompositorContextAttributes in
// content/browser/renderer_host/compositor_impl_android.cc
// and https://crbug.com/699330.
gpu::ContextCreationAttribs attributes;
attributes.alpha_size = -1;
attributes.red_size = 8;
attributes.green_size = 8;
attributes.blue_size = 8;
attributes.stencil_size = 0;
attributes.depth_size = 0;
attributes.samples = 0;
attributes.sample_buffers = 0;
attributes.bind_generates_resource = false;
if (base::SysInfo::IsLowEndDevice()) {
attributes.alpha_size = 0;
attributes.red_size = 5;
attributes.green_size = 6;
attributes.blue_size = 5;
}
content::Compositor::CreateContextProvider(
surface_handle, attributes,
gpu::SharedMemoryLimits::ForMailboxContext(), callback);
},
surface_handle_, callback));
}
void MailboxToSurfaceBridge::ResizeSurface(int width, int height) {
surface_width_ = width;
surface_height_ = height;
if (!IsConnected()) {
// We're not initialized yet, save the requested size for later.
needs_resize_ = true;
return;
}
DVLOG(1) << __FUNCTION__ << ": resize Surface to " << surface_width_ << "x"
<< surface_height_;
gl_->ResizeCHROMIUM(surface_width_, surface_height_, 1.f,
GL_COLOR_SPACE_UNSPECIFIED_CHROMIUM, false);
gl_->Viewport(0, 0, surface_width_, surface_height_);
}
bool MailboxToSurfaceBridge::CopyMailboxToSurfaceAndSwap(
const gpu::MailboxHolder& mailbox) {
if (!IsConnected()) {
// We may not have a context yet, i.e. due to surface initialization
// being incomplete. This is not an error, but we obviously can't draw
// yet. TODO(klausw): change the caller to defer this until we are ready.
return false;
}
TRACE_EVENT0("gpu", __FUNCTION__);
if (needs_resize_) {
ResizeSurface(surface_width_, surface_height_);
needs_resize_ = false;
}
DCHECK(mailbox.mailbox.IsSharedImage());
// While it's not an error to use a zero-sized Surface, it's not going to
// produce any visible output. Show a debug mode warning in that case to avoid
// another annoying debugging session.
DLOG_IF(WARNING, !surface_width_ || !surface_height_)
<< "Surface is zero-sized. Missing call to ResizeSurface?";
GLuint sourceTexture = ConsumeTexture(gl_, mailbox);
gl_->BeginSharedImageAccessDirectCHROMIUM(
sourceTexture, GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
DrawQuad(sourceTexture);
gl_->EndSharedImageAccessDirectCHROMIUM(sourceTexture);
gl_->DeleteTextures(1, &sourceTexture);
gl_->SwapBuffers(swap_id_++);
return true;
}
void MailboxToSurfaceBridge::GenSyncToken(gpu::SyncToken* out_sync_token) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
gl_->GenSyncTokenCHROMIUM(out_sync_token->GetData());
}
void MailboxToSurfaceBridge::WaitSyncToken(const gpu::SyncToken& sync_token) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
}
void MailboxToSurfaceBridge::WaitForClientGpuFence(gfx::GpuFence* gpu_fence) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
GLuint id = gl_->CreateClientGpuFenceCHROMIUM(gpu_fence->AsClientGpuFence());
gl_->WaitGpuFenceCHROMIUM(id);
gl_->DestroyGpuFenceCHROMIUM(id);
}
void MailboxToSurfaceBridge::CreateGpuFence(
const gpu::SyncToken& sync_token,
base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
GLuint id = gl_->CreateGpuFenceCHROMIUM();
context_support_->GetGpuFence(id, std::move(callback));
gl_->DestroyGpuFenceCHROMIUM(id);
}
gpu::MailboxHolder MailboxToSurfaceBridge::CreateSharedImage(
gpu::GpuMemoryBufferImplAndroidHardwareBuffer* buffer,
const gfx::ColorSpace& color_space,
uint32_t usage) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
auto* sii = context_provider_->SharedImageInterface();
DCHECK(sii);
gpu::MailboxHolder mailbox_holder;
mailbox_holder.mailbox =
sii->CreateSharedImage(buffer, nullptr, color_space, usage);
mailbox_holder.sync_token = sii->GenVerifiedSyncToken();
DCHECK(!gpu::NativeBufferNeedsPlatformSpecificTextureTarget(
buffer->GetFormat()));
mailbox_holder.texture_target = GL_TEXTURE_2D;
return mailbox_holder;
}
void MailboxToSurfaceBridge::DestroySharedImage(
const gpu::MailboxHolder& mailbox_holder) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
auto* sii = context_provider_->SharedImageInterface();
DCHECK(sii);
sii->DestroySharedImage(mailbox_holder.sync_token, mailbox_holder.mailbox);
}
void MailboxToSurfaceBridge::DestroyContext() {
gl_ = nullptr;
context_provider_ = nullptr;
}
void MailboxToSurfaceBridge::InitializeRenderer() {
GLuint vertex_shader_handle =
CompileShader(gl_, GL_VERTEX_SHADER, kQuadCopyVertex);
if (!vertex_shader_handle) {
DestroyContext();
return;
}
GLuint fragment_shader_handle =
CompileShader(gl_, GL_FRAGMENT_SHADER, kQuadCopyFragment);
if (!fragment_shader_handle) {
DestroyContext();
return;
}
GLuint program_handle =
CreateAndLinkProgram(gl_, vertex_shader_handle, fragment_shader_handle);
if (!program_handle) {
DestroyContext();
return;
}
// Once the program is linked the shader objects are no longer needed
gl_->DeleteShader(vertex_shader_handle);
gl_->DeleteShader(fragment_shader_handle);
GLuint position_handle = gl_->GetAttribLocation(program_handle, "a_Position");
GLuint texCoord_handle =
gl_->GetAttribLocation(program_handle, "a_TexCoordinate");
GLuint texUniform_handle =
gl_->GetUniformLocation(program_handle, "u_Texture");
GLuint vertexBuffer = 0;
gl_->GenBuffers(1, &vertexBuffer);
gl_->BindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
gl_->BufferData(GL_ARRAY_BUFFER, kQuadVerticesSize, kQuadVertices,
GL_STATIC_DRAW);
// Set state once only, we assume that nobody else modifies GL state in a way
// that would interfere with our operations.
gl_->Disable(GL_CULL_FACE);
gl_->DepthMask(GL_FALSE);
gl_->Disable(GL_DEPTH_TEST);
gl_->Disable(GL_SCISSOR_TEST);
gl_->Disable(GL_BLEND);
gl_->Disable(GL_POLYGON_OFFSET_FILL);
// Not using gl_->Viewport, we assume that it defaults to the whole
// surface and gets updated by ResizeSurface externally as
// appropriate.
gl_->UseProgram(program_handle);
// Bind vertex attributes
gl_->BindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
gl_->EnableVertexAttribArray(position_handle);
gl_->EnableVertexAttribArray(texCoord_handle);
static constexpr size_t VERTEX_STRIDE = sizeof(float) * 4;
static constexpr size_t POSITION_ELEMENTS = 2;
static constexpr size_t TEXCOORD_ELEMENTS = 2;
static constexpr size_t POSITION_OFFSET = 0;
static constexpr size_t TEXCOORD_OFFSET = sizeof(float) * 2;
gl_->VertexAttribPointer(position_handle, POSITION_ELEMENTS, GL_FLOAT, false,
VERTEX_STRIDE, VOID_OFFSET(POSITION_OFFSET));
gl_->VertexAttribPointer(texCoord_handle, TEXCOORD_ELEMENTS, GL_FLOAT, false,
VERTEX_STRIDE, VOID_OFFSET(TEXCOORD_OFFSET));
gl_->ActiveTexture(GL_TEXTURE0);
gl_->Uniform1i(texUniform_handle, 0);
}
void MailboxToSurfaceBridge::DrawQuad(unsigned int texture_handle) {
DCHECK(IsConnected());
// We're redrawing over the entire viewport, but it's generally more
// efficient on mobile tiling GPUs to clear anyway as a hint that
// we're done with the old content. TODO(klausw, https://crbug.com/700389):
// investigate using gl_->DiscardFramebufferEXT here since that's more
// efficient on desktop, but it would need a capability check since
// it's not supported on older devices such as Nexus 5X.
gl_->Clear(GL_COLOR_BUFFER_BIT);
// Configure texture. This is a 1:1 pixel copy since the surface
// size is resized to match the source canvas, so we can use
// GL_NEAREST.
gl_->BindTexture(GL_TEXTURE_2D, texture_handle);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
gl_->DrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
} // namespace vr