// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/webxr/mailbox_to_surface_bridge_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.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/client_shared_image.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/mailbox.h"
#include "gpu/command_buffer/common/mailbox_holder.h"
#include "gpu/ipc/client/gpu_channel_host.h"
#include "gpu/ipc/common/gpu_surface_tracker.h"
#include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/gpu_memory_buffer.h"
#include "ui/gl/android/surface_texture.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;
uniform mat4 u_UvTransform;
void main() {
highp vec4 uv_in = vec4(a_TexCoordinate.x, a_TexCoordinate.y, 0, 1);
v_TexCoordinate = (u_UvTransform * uv_in).xy;
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", "MailboxToSurfaceBridgeImpl::ConsumeTexture");
gl->WaitSyncTokenCHROMIUM(mailbox.sync_token.GetConstData());
return gl->CreateAndTexStorage2DSharedImageCHROMIUM(mailbox.mailbox.name);
}
} // namespace
namespace webxr {
MailboxToSurfaceBridgeImpl::MailboxToSurfaceBridgeImpl() {
DVLOG(1) << __FUNCTION__;
}
MailboxToSurfaceBridgeImpl::~MailboxToSurfaceBridgeImpl() {
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 MailboxToSurfaceBridgeImpl::IsConnected() {
return context_provider_ && gl_ && context_support_;
}
bool MailboxToSurfaceBridgeImpl::IsGpuWorkaroundEnabled(int32_t workaround) {
DCHECK(IsConnected());
return context_provider_->GetGpuFeatureInfo().IsWorkaroundEnabled(workaround);
}
void MailboxToSurfaceBridgeImpl::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(
&MailboxToSurfaceBridgeImpl::BindContextProviderToCurrentThread,
base::Unretained(this)));
}
void MailboxToSurfaceBridgeImpl::BindContextProviderToCurrentThread() {
auto result = context_provider_->BindToCurrentSequence();
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 MailboxToSurfaceBridgeImpl::CreateSurface(
gl::SurfaceTexture* surface_texture) {
gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
surface_handle_ =
tracker->AddSurfaceForNativeWidget(gpu::GpuSurfaceTracker::SurfaceRecord(
gl::ScopedJavaSurface(surface_texture),
false /* can_be_used_with_surface_control */));
// Unregistering happens in the destructor.
}
void MailboxToSurfaceBridgeImpl::CreateAndBindContextProvider(
base::OnceClosure on_bound_callback) {
gl_thread_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
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::BindOnce(&MailboxToSurfaceBridgeImpl::OnContextAvailableOnUiThread,
weak_ptr_factory_.GetWeakPtr());
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(
[](int surface_handle,
content::Compositor::ContextProviderCallback callback) {
content::Compositor::CreateContextProvider(
surface_handle,
gpu::SharedMemoryLimits::ForMailboxContext(),
std::move(callback));
},
surface_handle_, std::move(callback)));
}
void MailboxToSurfaceBridgeImpl::ResizeSurface(int width, int height) {
// Make sure we have the surface.
CHECK(surface_handle_);
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_;
gfx::ColorSpace color_space = gfx::ColorSpace::CreateSRGB();
gl_->ResizeCHROMIUM(surface_width_, surface_height_, 1.f,
color_space.AsGLColorSpace(), false);
gl_->Viewport(0, 0, surface_width_, surface_height_);
}
bool MailboxToSurfaceBridgeImpl::CopyMailboxToSurfaceAndSwap(
const gpu::MailboxHolder& mailbox,
const gfx::Transform& uv_transform) {
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. This affects the non-shared-buffer path on Android N (or
// if UseSharedBuffer was forced to false due to GPU bug workarounds),
// and may result in a couple of discarded images while waiting for
// initialization which is generally harmless.
return false;
}
TRACE_EVENT0("gpu", __FUNCTION__);
if (needs_resize_) {
ResizeSurface(surface_width_, surface_height_);
needs_resize_ = false;
}
// 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, uv_transform);
gl_->EndSharedImageAccessDirectCHROMIUM(sourceTexture);
gl_->DeleteTextures(1, &sourceTexture);
gl_->SwapBuffers(swap_id_++);
return true;
}
void MailboxToSurfaceBridgeImpl::GenSyncToken(gpu::SyncToken* out_sync_token) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
gl_->GenSyncTokenCHROMIUM(out_sync_token->GetData());
}
void MailboxToSurfaceBridgeImpl::WaitSyncToken(
const gpu::SyncToken& sync_token) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
}
void MailboxToSurfaceBridgeImpl::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 MailboxToSurfaceBridgeImpl::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);
}
scoped_refptr<gpu::ClientSharedImage>
MailboxToSurfaceBridgeImpl::CreateSharedImage(
gfx::GpuMemoryBufferHandle buffer_handle,
gfx::BufferFormat buffer_format,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
gpu::SharedImageUsageSet usage,
gpu::SyncToken& sync_token) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
auto* sii = context_provider_->SharedImageInterface();
DCHECK(sii);
CHECK_EQ(buffer_format, gfx::BufferFormat::RGBA_8888);
auto client_shared_image = sii->CreateSharedImage(
{viz::SinglePlaneFormat::kRGBA_8888, size, color_space, usage,
"WebXrMailboxToSurfaceBridge"},
std::move(buffer_handle));
CHECK(client_shared_image);
sync_token = sii->GenVerifiedSyncToken();
DCHECK(client_shared_image->GetTextureTarget() == GL_TEXTURE_2D);
return client_shared_image;
}
void MailboxToSurfaceBridgeImpl::DestroySharedImage(
const gpu::SyncToken& sync_token,
scoped_refptr<gpu::ClientSharedImage> shared_image) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
DCHECK(shared_image);
auto* sii = context_provider_->SharedImageInterface();
DCHECK(sii);
sii->DestroySharedImage(sync_token, std::move(shared_image));
}
void MailboxToSurfaceBridgeImpl::DestroyContext() {
gl_ = nullptr;
context_provider_ = nullptr;
}
void MailboxToSurfaceBridgeImpl::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");
uniform_uv_transform_handle_ =
gl_->GetUniformLocation(program_handle, "u_UvTransform");
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 MailboxToSurfaceBridgeImpl::DrawQuad(unsigned int texture_handle,
const gfx::Transform& uv_transform) {
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.
gl_->Clear(GL_COLOR_BUFFER_BIT);
float uv_transform_floats[16];
uv_transform.GetColMajorF(uv_transform_floats);
gl_->UniformMatrix4fv(uniform_uv_transform_handle_, 1, GL_FALSE,
&uv_transform_floats[0]);
// 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);
if (uv_transform.IsIdentity()) {
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
} else {
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
gl_->DrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
std::unique_ptr<device::MailboxToSurfaceBridge>
MailboxToSurfaceBridgeFactoryImpl::Create() const {
return std::make_unique<MailboxToSurfaceBridgeImpl>();
}
} // namespace webxr