chromium/chrome/browser/vr/graphics_delegate_android.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/vr/graphics_delegate_android.h"

#include "base/android/android_hardware_buffer_compat.h"
#include "components/webxr/mailbox_to_surface_bridge_impl.h"
#include "device/vr/android/xr_image_transport_base.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/ahardwarebuffer_utils.h"
#include "gpu/ipc/common/android/android_hardware_buffer_utils.h"
#include "ui/gfx/color_space.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/init/gl_factory.h"

namespace vr {

GraphicsDelegateAndroid::GraphicsDelegateAndroid() = default;
GraphicsDelegateAndroid::~GraphicsDelegateAndroid() = default;

void GraphicsDelegateAndroid::Initialize(base::OnceClosure on_initialized) {
  // We can only share native GL resources with the runtimes, and they don't
  // support ANGLE, so disable it.
  // TODO(crbug.com/40744597): support ANGLE?
  gl::DisableANGLE();

  gl::GLDisplay* display = nullptr;
  if (gl::GetGLImplementation() == gl::kGLImplementationNone) {
    display = gl::init::InitializeGLOneOff(
        /*gpu_preference=*/gl::GpuPreference::kDefault);
    if (!display) {
      LOG(ERROR) << "gl::init::InitializeGLOneOff failed";
      return;
    }
  } else {
    display = gl::GetDefaultDisplayEGL();
  }

  surface_ = gl::init::CreateOffscreenGLSurface(display, gfx::Size());
  if (!surface_.get()) {
    LOG(ERROR) << "gl::init::CreateOffscreenGLSurface failed";
    return;
  }

  context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
                                       gl::GLContextAttribs());
  if (!context_.get()) {
    DLOG(ERROR) << "gl::init::CreateGLContext failed";
    return;
  }

  if (!context_->MakeCurrent(surface_.get())) {
    DLOG(ERROR) << "gl::GLContext::MakeCurrent() failed";
    return;
  }

  mailbox_bridge_ = std::make_unique<webxr::MailboxToSurfaceBridgeImpl>();
  mailbox_bridge_->CreateAndBindContextProvider(base::BindOnce(
      &GraphicsDelegateAndroid::OnMailboxBridgeReady,
      weak_ptr_factory_.GetWeakPtr(), std::move(on_initialized)));
}

void GraphicsDelegateAndroid::OnMailboxBridgeReady(
    base::OnceClosure on_inititalized) {
  DCHECK(mailbox_bridge_->IsConnected());
  std::move(on_inititalized).Run();
}

bool GraphicsDelegateAndroid::BindContext() {
  return true;
}

void GraphicsDelegateAndroid::ClearContext() {}

bool GraphicsDelegateAndroid::PreRender() {
  BindContext();

  // Create a memory buffer and a shared image referencing that memory buffer.
  if (!EnsureMemoryBuffer()) {
    DLOG(ERROR) << __func__ << " Failed to ensure memory buffer";
    return false;
  }
  DVLOG(3) << __func__ << " mailbox: "
           << shared_buffer_->shared_image->mailbox().ToDebugString()
           << " sync_token: " << shared_buffer_->sync_token.ToDebugString();

  GLenum target = shared_buffer_->local_texture.target;
  glBindTexture(target, shared_buffer_->local_texture.id);
  glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  // Bind our image/texture/memory buffer as the draw framebuffer.
  glGenFramebuffersEXT(1, &draw_frame_buffer_);
  glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, draw_frame_buffer_);
  glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target,
                            shared_buffer_->local_texture.id, 0);

  return true;
}

void GraphicsDelegateAndroid::PostRender() {
  // Unbind the drawing buffer.
  glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
  glDeleteFramebuffersEXT(1, &draw_frame_buffer_);

  glBindTexture(GL_TEXTURE_2D, 0);
  draw_frame_buffer_ = 0;

  // Generate a SyncToken after GPU is done accessing the texture.
  mailbox_bridge_->GenSyncToken(&shared_buffer_->sync_token);

  ClearContext();
  glFlush();
}

gfx::GpuMemoryBufferHandle GraphicsDelegateAndroid::GetTexture() {
  if (!shared_buffer_) {
    return gfx::GpuMemoryBufferHandle();
  }
  return gfx::GpuMemoryBufferHandle(shared_buffer_->scoped_ahb_handle.Clone());
}

gpu::SyncToken GraphicsDelegateAndroid::GetSyncToken() {
  if (!shared_buffer_) {
    return gpu::SyncToken();
  }

  return shared_buffer_->sync_token;
}

bool GraphicsDelegateAndroid::EnsureMemoryBuffer() {
  // The code here is very similar to that used in both XrImageTransportBase
  // and OpenXrGraphicsDelegateOpenGLES's ResizeShardBuffer methods. However,
  // they all have subtly different uses.
  // TODO(https://crbug.com/40909689): Consolidate this usage.
  gfx::Size buffer_size = GetTextureSize();
  if (shared_buffer_ && shared_buffer_->size == buffer_size) {
    return true;
  }

  if (!shared_buffer_) {
    shared_buffer_ = std::make_unique<device::WebXrSharedBuffer>();
    shared_buffer_->local_texture.target =
        device::XrImageTransportBase::SharedBufferTextureTarget();
    glGenTextures(1, &shared_buffer_->local_texture.id);
  }

  if (shared_buffer_->shared_image) {
    mailbox_bridge_->DestroySharedImage(
        shared_buffer_->sync_token, std::move(shared_buffer_->shared_image));
  }

  // Remove reference to previous image (if any).
  shared_buffer_->local_eglimage.reset();

  static constexpr gfx::BufferFormat format = gfx::BufferFormat::RGBA_8888;
  static constexpr gfx::BufferUsage usage = gfx::BufferUsage::SCANOUT;
  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;

  shared_buffer_->scoped_ahb_handle =
      gpu::CreateScopedHardwareBufferHandle(buffer_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 =
      shared_buffer_->scoped_ahb_handle.Clone();

  shared_buffer_->shared_image = mailbox_bridge_->CreateSharedImage(
      std::move(gmb_handle), format, buffer_size, gfx::ColorSpace(),
      shared_image_usage, shared_buffer_->sync_token);
  DVLOG(2) << ": CreateSharedImage, mailbox="
           << shared_buffer_->shared_image->mailbox().ToDebugString()
           << ", SyncToken=" << shared_buffer_->sync_token.ToDebugString()
           << ", size=" << buffer_size.ToString();

  // Create an EGLImage for the buffer.
  auto egl_image = gpu::CreateEGLImageFromAHardwareBuffer(
      shared_buffer_->scoped_ahb_handle.get());
  if (!egl_image.is_valid()) {
    return false;
  }

  glBindTexture(shared_buffer_->local_texture.target,
                shared_buffer_->local_texture.id);
  glEGLImageTargetTexture2DOES(shared_buffer_->local_texture.target,
                               egl_image.get());
  shared_buffer_->local_eglimage = std::move(egl_image);

  // Save size to avoid resize next time.
  DVLOG(1) << __func__ << ": resized to " << buffer_size.width() << "x"
           << buffer_size.height();
  shared_buffer_->size = buffer_size;
  return true;
}

void GraphicsDelegateAndroid::ResetMemoryBuffer() {
  // Stop using a memory buffer if we had an error submitting with it.
  if (shared_buffer_ && shared_buffer_->shared_image) {
    DCHECK(mailbox_bridge_);
    DVLOG(2) << ": DestroySharedImage, mailbox="
             << shared_buffer_->shared_image->mailbox().ToDebugString();
    mailbox_bridge_->DestroySharedImage(
        shared_buffer_->sync_token, std::move(shared_buffer_->shared_image));
    shared_buffer_->size = {0, 0};
  }
}

void GraphicsDelegateAndroid::ClearBufferToBlack() {
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT);
}

}  // namespace vr