// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/vr/android/xr_image_transport_base.h"
#include "base/android/android_hardware_buffer_compat.h"
#include "base/android/scoped_hardware_buffer_handle.h"
#include "base/feature_list.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "device/vr/android/mailbox_to_surface_bridge.h"
#include "device/vr/android/web_xr_presentation_state.h"
#include "device/vr/public/cpp/features.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/ahardwarebuffer_utils.h"
#include "gpu/config/gpu_driver_bug_workaround_type.h"
#include "gpu/ipc/common/android/android_hardware_buffer_utils.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/gpu_fence.h"
#include "ui/gl/android/surface_texture.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_fence.h"
namespace device {
// static
bool XrImageTransportBase::disable_shared_buffer_ = false;
bool XrImageTransportBase::UseSharedBuffer() {
// When available (Android O and up), use AHardwareBuffer-based shared
// images for frame transport, unless disabled due to bugs or by the user.
static bool support_shared_buffer =
base::FeatureList::IsEnabled(features::kWebXrSharedBuffers) &&
base::AndroidHardwareBufferCompat::IsSupportAvailable();
return support_shared_buffer && !XrImageTransportBase::disable_shared_buffer_;
}
GLenum XrImageTransportBase::SharedBufferTextureTarget() {
return base::FeatureList::IsEnabled(
features::kUseTargetTexture2DForSharedBuffers)
? GL_TEXTURE_2D
: GL_TEXTURE_EXTERNAL_OES;
}
XrImageTransportBase::XrImageTransportBase(
std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge)
: mailbox_bridge_(std::move(mailbox_bridge)),
gl_thread_task_runner_(
base::SingleThreadTaskRunner::GetCurrentDefault()) {
DVLOG(2) << __func__;
}
XrImageTransportBase::~XrImageTransportBase() = default;
void XrImageTransportBase::DestroySharedBuffers(WebXrPresentationState* webxr) {
DVLOG(2) << __func__;
CHECK(IsOnGlThread());
if (!webxr || !UseSharedBuffer()) {
return;
}
std::vector<std::unique_ptr<WebXrSharedBuffer>> buffers =
webxr->TakeSharedBuffers();
for (auto& buffer : buffers) {
if (buffer->shared_image) {
DCHECK(mailbox_bridge_);
DVLOG(2) << ": DestroySharedImage, mailbox="
<< buffer->shared_image->mailbox().ToDebugString();
// Note: the sync token may not be accurate. See comment in TransferFrame
// below.
mailbox_bridge_->DestroySharedImage(buffer->sync_token,
std::move(buffer->shared_image));
}
}
}
void XrImageTransportBase::Initialize(WebXrPresentationState* webxr,
XrInitStatusCallback callback) {
CHECK(IsOnGlThread());
DVLOG(2) << __func__;
DoRuntimeInitialization(UseSharedBuffer() ? SharedBufferTextureTarget()
: GL_TEXTURE_EXTERNAL_OES);
if (UseSharedBuffer()) {
DVLOG(2) << __func__ << ": UseSharedBuffer()=true";
} else {
DVLOG(2) << __func__ << ": UseSharedBuffer()=false, setting up surface";
glGenTextures(1, &transport_texture_.id);
// Transport Texture is bound to SurfaceTexture and must be TEXTURE_EXTERNAL
transport_texture_.target = GL_TEXTURE_EXTERNAL_OES;
transport_surface_texture_ =
gl::SurfaceTexture::Create(transport_texture_.id);
surface_size_ = {0, 0};
mailbox_bridge_->CreateSurface(transport_surface_texture_.get());
transport_surface_texture_->SetFrameAvailableCallback(
base::BindRepeating(&XrImageTransportBase::OnFrameAvailable,
weak_ptr_factory_.GetWeakPtr()));
}
mailbox_bridge_->CreateAndBindContextProvider(
base::BindOnce(&XrImageTransportBase::OnMailboxBridgeReady,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void XrImageTransportBase::OnMailboxBridgeReady(XrInitStatusCallback callback) {
DVLOG(2) << __func__;
CHECK(IsOnGlThread());
DCHECK(mailbox_bridge_->IsConnected());
bool success = true;
// DISABLE_RENDERING_TO_RGB_EXTERNAL_TEXTURE is needed because some drivers
// don't allow rendering to TEXTURE_EXTERNAL, it's not applicable if we use
// TEXTURE_2D.
if (!base::FeatureList::IsEnabled(
features::kUseTargetTexture2DForSharedBuffers) &&
UseSharedBuffer()) {
bool shared_buffer_not_usable = mailbox_bridge_->IsGpuWorkaroundEnabled(
gpu::DISABLE_RENDERING_TO_RGB_EXTERNAL_TEXTURE);
DVLOG(1) << __func__
<< ": shared_buffer_not_usable=" << shared_buffer_not_usable;
if (shared_buffer_not_usable) {
// This is a bug workaround for shared buffer mode being unusable due to
// device GLES driver bugs, see https://crbug.com/1355946 for an example.
// We want to retry initialization with UseSharedBuffers forced to false.
//
// It would be nice if we could avoid this retry and just do the right
// thing on the first attempt, but unfortunately that's difficult due to
// the initialization order. We only know that the GPU bug workaround is
// necessary after the GPU process connection is established (this is when
// OnMailboxBridgeReady gets called). However, in order to establish the
// GPU process connection, we already have to decide if we want to use
// shared buffers or not - when not using shared buffers, we need to set
// up a Surface/SurfaceTexture pair first, and doing that requires a local
// GL context. However, setting up the local GL context wants to know if
// we'll be using compositor mode which requires knowing if we're using
// shared buffer mode. This is a circular dependency. Instead, just fail
// the first initialization attempt, force UseSharedBuffers to false,
// and try again a single time.
XrImageTransportBase::disable_shared_buffer_ = true;
DCHECK(!UseSharedBuffer());
success = false;
}
}
std::move(callback).Run(success);
}
void XrImageTransportBase::SetFrameAvailableCallback(
XrFrameCallback on_transport_frame_available) {
CHECK(IsOnGlThread());
DVLOG(2) << __func__;
on_transport_frame_available_ = std::move(on_transport_frame_available);
}
void XrImageTransportBase::OnFrameAvailable() {
DVLOG(2) << __func__;
DCHECK(on_transport_frame_available_);
// This function assumes that there's only at most one frame in "processing"
// state at any given time, the webxr_ state handling ensures that. Drawing
// and swapping twice without an intervening UpdateTexImage call would lose
// an image, and that would lead to images and poses getting out of sync.
//
// It also assumes that the XrImageTransportBase and Surface only exist for
// the duration of a single session, and a new session will use fresh objects.
// For comparison, see GvrSchedulerDelegate::OnWebXrFrameAvailable which has
// more complex logic to support a lifetime across multiple sessions,
// including handling a possibly-unconsumed frame left over from a previous
// session.
transport_surface_texture_->UpdateTexImage();
// The SurfaceTexture needs to be drawn using the corresponding
// UV transform, that's usually a Y flip.
transport_surface_texture_->GetTransformMatrix(
transport_surface_texture_uv_matrix_);
transport_surface_texture_uv_transform_ =
gfx::Transform::ColMajorF(transport_surface_texture_uv_matrix_);
on_transport_frame_available_.Run(transport_surface_texture_uv_transform_);
}
bool XrImageTransportBase::ResizeSharedBuffer(WebXrPresentationState* webxr,
const gfx::Size& size,
WebXrSharedBuffer* buffer) {
CHECK(IsOnGlThread());
if (buffer->size == size) {
return false;
}
TRACE_EVENT0("gpu", __func__);
// Unbind previous image (if any).
if (buffer->shared_image) {
DVLOG(2) << ": DestroySharedImage, mailbox="
<< buffer->shared_image->mailbox().ToDebugString();
// Note: the sync token may not be accurate. See comment in TransferFrame
// below.
mailbox_bridge_->DestroySharedImage(buffer->sync_token,
std::move(buffer->shared_image));
}
DVLOG(2) << __func__ << ": width=" << size.width()
<< " height=" << size.height();
// Remove reference to previous image (if any).
buffer->local_eglimage.reset();
static constexpr gfx::BufferFormat format = gfx::BufferFormat::RGBA_8888;
static constexpr gfx::BufferUsage usage = gfx::BufferUsage::SCANOUT;
// The SharedImages created here will eventually be transferred to other
// processes to have their contents read/written via WebGL for WebXR.
gpu::SharedImageUsageSet shared_image_usage =
gpu::SHARED_IMAGE_USAGE_SCANOUT | gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
gpu::SHARED_IMAGE_USAGE_GLES2_READ | gpu::SHARED_IMAGE_USAGE_GLES2_WRITE;
// Create a new AHardwareBuffer backed handle.
buffer->scoped_ahb_handle =
gpu::CreateScopedHardwareBufferHandle(size, format, usage);
// Create a GMB Handle from AHardwareBuffer handle.
gfx::GpuMemoryBufferHandle gmb_handle;
gmb_handle.type = gfx::ANDROID_HARDWARE_BUFFER;
// GpuMemoryBufferId is not used in this case and hence hardcoding it to 1
// here.
gmb_handle.id = gfx::GpuMemoryBufferId(1);
gmb_handle.android_hardware_buffer = buffer->scoped_ahb_handle.Clone();
buffer->shared_image = mailbox_bridge_->CreateSharedImage(
std::move(gmb_handle), format, size, gfx::ColorSpace(),
shared_image_usage, buffer->sync_token);
CHECK(buffer->shared_image);
DVLOG(2) << ": CreateSharedImage, mailbox="
<< buffer->shared_image->mailbox().ToDebugString()
<< ", SyncToken=" << buffer->sync_token.ToDebugString();
// Create an EGLImage for the buffer.
auto egl_image =
gpu::CreateEGLImageFromAHardwareBuffer(buffer->scoped_ahb_handle.get());
if (!egl_image.is_valid()) {
DLOG(WARNING) << __func__ << ": ERROR: failed to initialize image!";
return false;
}
glBindTexture(buffer->local_texture.target, buffer->local_texture.id);
glTexParameteri(buffer->local_texture.target, GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE);
glTexParameteri(buffer->local_texture.target, GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE);
glTexParameteri(buffer->local_texture.target, GL_TEXTURE_MIN_FILTER,
GL_LINEAR);
glTexParameteri(buffer->local_texture.target, GL_TEXTURE_MAG_FILTER,
GL_LINEAR);
glEGLImageTargetTexture2DOES(buffer->local_texture.target, egl_image.get());
buffer->local_eglimage = std::move(egl_image);
// Save size to avoid resize next time.
DVLOG(1) << __func__ << ": resized to " << size.width() << "x"
<< size.height();
buffer->size = size;
return true;
}
std::unique_ptr<WebXrSharedBuffer> XrImageTransportBase::CreateBuffer() {
CHECK(IsOnGlThread());
std::unique_ptr<WebXrSharedBuffer> buffer =
std::make_unique<WebXrSharedBuffer>();
// Local resources
glGenTextures(1, &buffer->local_texture.id);
buffer->local_texture.target = SharedBufferTextureTarget();
return buffer;
}
WebXrSharedBuffer* XrImageTransportBase::TransferFrame(
WebXrPresentationState* webxr,
const gfx::Size& frame_size,
const gfx::Transform& uv_transform) {
CHECK(IsOnGlThread());
CHECK(UseSharedBuffer());
if (!webxr->GetAnimatingFrame()->shared_buffer) {
webxr->GetAnimatingFrame()->shared_buffer = CreateBuffer();
}
WebXrSharedBuffer* shared_buffer =
webxr->GetAnimatingFrame()->shared_buffer.get();
ResizeSharedBuffer(webxr, frame_size, shared_buffer);
// Sanity check that the lazily created/resized buffer looks valid.
DCHECK(shared_buffer->shared_image);
DCHECK(shared_buffer->local_eglimage.is_valid());
DCHECK_EQ(shared_buffer->size, frame_size);
// We don't need to create a sync token here. ResizeSharedBuffer has created
// one on reallocation, including initial buffer creation, and we can use
// that. The shared image interface internally uses its own command buffer ID
// and separate sync token release count namespace, and we must not overwrite
// that. We don't need a new sync token when reusing a correctly-sized buffer,
// it's only eligible for reuse after all reads from it are complete, meaning
// that it's transitioned through "processing" and "rendering" states back
// to "animating".
DCHECK(shared_buffer->sync_token.HasData());
DVLOG(2) << ": SyncToken=" << shared_buffer->sync_token.ToDebugString();
return shared_buffer;
}
void XrImageTransportBase::CreateGpuFenceForSyncToken(
const gpu::SyncToken& sync_token,
base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback) {
CHECK(IsOnGlThread());
DVLOG(2) << __func__;
mailbox_bridge_->CreateGpuFence(sync_token, std::move(callback));
}
void XrImageTransportBase::WaitSyncToken(const gpu::SyncToken& sync_token) {
CHECK(IsOnGlThread());
mailbox_bridge_->WaitSyncToken(sync_token);
}
void XrImageTransportBase::ServerWaitForGpuFence(
std::unique_ptr<gfx::GpuFence> gpu_fence) {
CHECK(IsOnGlThread());
std::unique_ptr<gl::GLFence> local_fence =
gl::GLFence::CreateFromGpuFence(*gpu_fence);
local_fence->ServerWait();
}
LocalTexture XrImageTransportBase::GetRenderingTexture(
WebXrPresentationState* webxr) {
CHECK(IsOnGlThread());
if (UseSharedBuffer()) {
return webxr->GetRenderingFrame()->shared_buffer->local_texture;
} else {
return transport_texture_;
}
}
void XrImageTransportBase::CopyMailboxToSurfaceAndSwap(
const gfx::Size& frame_size,
const gpu::MailboxHolder& mailbox,
const gfx::Transform& uv_transform) {
CHECK(IsOnGlThread());
DVLOG(2) << __func__;
if (frame_size != surface_size_) {
DVLOG(2) << __func__ << " resize from " << surface_size_.ToString()
<< " to " << frame_size.ToString();
transport_surface_texture_->SetDefaultBufferSize(frame_size.width(),
frame_size.height());
mailbox_bridge_->ResizeSurface(frame_size.width(), frame_size.height());
surface_size_ = frame_size;
}
// Draw the image to the surface in the GPU process's command buffer context.
// This will trigger an OnFrameAvailable event once the corresponding
// SurfaceTexture in the local GL context is ready for updating.
bool swapped =
mailbox_bridge_->CopyMailboxToSurfaceAndSwap(mailbox, uv_transform);
DCHECK(swapped);
}
bool XrImageTransportBase::IsOnGlThread() const {
return gl_thread_task_runner_->BelongsToCurrentThread();
}
} // namespace device