// Copyright 2019 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/openxr/openxr_render_loop.h"

#include <algorithm>
#include <optional>
#include <string_view>
#include <utility>

#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/not_fatal_until.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/viz/common/gpu/context_provider.h"
#include "device/vr/openxr/exit_xr_present_reason.h"
#include "device/vr/openxr/openxr_api_wrapper.h"
#include "device/vr/openxr/openxr_input_helper.h"
#include "device/vr/openxr/openxr_util.h"
#include "device/vr/util/stage_utils.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 "mojo/public/cpp/bindings/message.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "third_party/openxr/src/include/openxr/openxr.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/gpu_fence.h"

namespace {
// Number of frames to use for sliding averages for pose timings,
// as used for estimating prediction times.
constexpr unsigned kSlidingAverageSize = 5;

device::mojom::XRRenderInfoPtr GetRenderInfo(
    const device::mojom::XRFrameData& frame_data) {
  device::mojom::XRRenderInfoPtr result = device::mojom::XRRenderInfo::New();

  result->frame_id = frame_data.render_info->frame_id;
  result->mojo_from_viewer = frame_data.render_info->mojo_from_viewer.Clone();

  for (size_t i = 0; i < frame_data.render_info->views.size(); i++) {
    result->views.push_back(frame_data.render_info->views[i]->Clone());
  }

  return result;
}

}  // namespace

namespace device {

OpenXrRenderLoop::OutstandingFrame::OutstandingFrame() = default;
OpenXrRenderLoop::OutstandingFrame::~OutstandingFrame() = default;

OpenXrRenderLoop::OpenXrRenderLoop(
    VizContextProviderFactoryAsync context_provider_factory_async,
    XrInstance instance,
    const OpenXrExtensionHelper& extension_helper,
    OpenXrPlatformHelper* platform_helper)
    : XRThread("OpenXrRenderLoop"),
      main_thread_task_runner_(
          base::SingleThreadTaskRunner::GetCurrentDefault()),
      instance_(instance),
      extension_helper_(extension_helper),
      context_provider_factory_async_(
          std::move(context_provider_factory_async)),
      webxr_js_time_(kSlidingAverageSize),
      webxr_gpu_time_(kSlidingAverageSize),
      platform_helper_(platform_helper) {
  DCHECK(main_thread_task_runner_);
  DCHECK(instance_ != XR_NULL_HANDLE);
}

OpenXrRenderLoop::~OpenXrRenderLoop() {
  Stop();
}

void OpenXrRenderLoop::ExitPresent(ExitXrPresentReason reason) {
  DVLOG(1) << __func__ << " reason=" << std::to_underlying(reason);
  TRACE_EVENT_INSTANT("xr", "OpenXrRenderLoop::ExitPresent", "reason",
                      std::to_underlying(reason));
  if (!is_presenting_) {
    return;
  }

  is_presenting_ = false;
  webxr_has_pose_ = false;
  presentation_receiver_.reset();
  frame_data_receiver_.reset();
  layer_manager_receiver_.reset();
  submit_client_.reset();

  pending_frame_.reset();
  delayed_get_frame_data_id_.reset();
  delayed_get_frame_data_callback_.Reset();

  // Reset webxr_visible_ for subsequent presentations.
  webxr_visible_ = true;

  // Kill outstanding overlays:
  overlay_visible_ = false;
  overlay_receiver_.reset();

  graphics_binding_->SetOverlayAndWebXrVisibility(false, false);

  // Don't call StopRuntime until this thread has finished the rest of the work.
  // This is to prevent the OpenXrApiWrapper from being deleted before its
  // cleanup work has finished.
  // If we're called as a result of the OpenXrApiWrapper being destroyed, we may
  // not have a task_runner anymore. Note that this appears to predominantly be
  // the case on Android, where the task_runner() is destroyed before
  // `StopRuntime` is called (which can lead to us being called), as Windows
  // appears to stop/destroy the task runner after calling `StopRuntime`.
  // Our `StopRuntime` method itself is resilient to multiple/re-entrant calls,
  // so simply validate the task runner rather than something more involved to
  // prevent that.
  if (task_runner()) {
    task_runner()->PostTask(
        FROM_HERE,
        base::BindOnce(&OpenXrRenderLoop::StopRuntime, base::Unretained(this)));
  }
}

void OpenXrRenderLoop::GetFrameData(
    mojom::XRFrameDataRequestOptionsPtr options,
    mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
  if (delayed_get_frame_data_id_) {
    TRACE_EVENT_END("xr", /*"DelayedGetFrameData"*/
                    perfetto::Track(*delayed_get_frame_data_id_));
    delayed_get_frame_data_id_.reset();
  }
  TRACE_EVENT0("xr", "OpenXrRenderLoop::GetFrameData");
  if (HasSessionEnded()) {
    ExitPresent(ExitXrPresentReason::kGetFrameAfterSessionEnded);
    return;
  }

  // HasSessionEnded() may do some work that alters the state of
  // `is_presenting_`, in which case we'll get the ExitPresent log to know that
  // we've ignored this request; but otherwise, we should log the rest of the
  // state after that.
  DVLOG(3) << __func__ << " is_presenting_=" << is_presenting_
           << " webxr_visible=" << webxr_visible_
           << " on_webxr_submitted_=" << !!on_webxr_submitted_
           << " webxr_has_pose_=" << webxr_has_pose_;

  if (!is_presenting_) {
    return;
  }

  if (ShouldDelayGetFrameData()) {
    // There should only be one outstanding GetFrameData call at a time.  We
    // shouldn't get new ones until this resolves or presentation ends/restarts.
    if (delayed_get_frame_data_callback_) {
      frame_data_receiver_.ReportBadMessage(
          "Multiple outstanding GetFrameData calls");
      return;
    }
    // next_frame_id_ is only changed once we successfully generate a frame.
    delayed_get_frame_data_id_ = next_frame_id_;
    TRACE_EVENT_BEGIN("xr", "DelayedGetFrameData",
                      perfetto::Track(next_frame_id_));
    delayed_get_frame_data_callback_ =
        base::BindOnce(&OpenXrRenderLoop::GetFrameData, base::Unretained(this),
                       std::move(options), std::move(callback));
    return;
  }

  // The browser overlay won't send |options| and as such, it should not control
  // whether depth is active or not.
  if (auto* depth = openxr_->GetDepthSensor(); depth && options) {
    // Function should no-op if no change is needed.
    depth->SetDepthActive(options->depth_active);
  }
  // Bail if the session ended while starting the frame. ExitPresent() has
  // already reset `frame_data_receiver_`, so dropping `callback` during
  // teardown is safe.
  if (!StartPendingFrame()) {
    return;
  }
  webxr_has_pose_ = true;
  pending_frame_->sent_frame_data_time_ = base::TimeTicks::Now();

  // frame_data_ is expected to be valid here. If it were null (indicating we
  // already sent the pose to WebXR and are waiting for the overlay),
  // ShouldDelayGetFrameData() would have deferred this call.
  CHECK(pending_frame_->frame_data_, base::NotFatalUntil::M158);

  // If the stage parameters have been updated since the last frame that was
  // sent, send the updated values.
  pending_frame_->frame_data_->stage_parameters_id = stage_parameters_id_;
  if (options->stage_parameters_id != stage_parameters_id_) {
    pending_frame_->frame_data_->stage_parameters =
        current_stage_parameters_.Clone();
  }

  // Yield here to let the event queue process pending mojo messages,
  // specifically the next gamepad callback request that's likely to
  // have been sent during WaitGetPoses.
  task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&OpenXrRenderLoop::SendFrameData,
                                base::Unretained(this), std::move(callback),
                                std::move(pending_frame_->frame_data_)));

  if (next_frame_id_ == std::numeric_limits<int16_t>::max()) {
    next_frame_id_ = 0;
  } else {
    next_frame_id_++;
  }
}

void OpenXrRenderLoop::RequestSession(
    base::RepeatingCallback<void(mojom::XRVisibilityState)>
        on_visibility_state_changed,
    mojom::XRRuntimeSessionOptionsPtr options,
    RequestSessionCallback callback,
    base::OnceClosure end_callback) {
  webxr_has_pose_ = false;
  presentation_receiver_.reset();
  frame_data_receiver_.reset();
  request_session_callback_ =
      base::BindPostTask(main_thread_task_runner_, std::move(callback));

  StartRuntime(std::move(on_visibility_state_changed), std::move(options),
               std::move(end_callback));
}

void OpenXrRenderLoop::SetVisibilityState(
    mojom::XRVisibilityState visibility_state) {
  if (visibility_state_ != visibility_state) {
    visibility_state_ = visibility_state;
    if (visibility_state_ != mojom::XRVisibilityState::VISIBLE) {
      openxr_->OnHideInputSources();
    }
    if (on_visibility_state_changed_) {
      main_thread_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(on_visibility_state_changed_, visibility_state));
    }
  }
}

void OpenXrRenderLoop::SetStageParameters(
    mojom::VRStageParametersPtr stage_parameters) {
  // If the stage parameters are identical no need to update them.
  if ((!current_stage_parameters_ && !stage_parameters) ||
      (current_stage_parameters_ && stage_parameters &&
       current_stage_parameters_.Equals(stage_parameters))) {
    return;
  }

  // If they have changed, increment the ID and save the new parameters.
  stage_parameters_id_++;
  current_stage_parameters_ = std::move(stage_parameters);
}

#if BUILDFLAG(IS_WIN)
void OpenXrRenderLoop::SubmitFrameWithTextureHandle(
    int16_t frame_index,
    mojo::PlatformHandle texture_handle,
    const gpu::SyncToken& sync_token) {
  DVLOG(3) << __func__ << " frame_index=" << frame_index;
  TRACE_EVENT1("xr", "OpenXrRenderLoop::SubmitFrameWithTextureHandle",
               "frameIndex", frame_index);
  if (!MarkFrameSubmitted(frame_index)) {
    return;
  }

  graphics_binding_->SetWebXrTexture(std::move(texture_handle), sync_token,
                                     left_webxr_bounds_, right_webxr_bounds_);

  // Regardless of success - try to composite what we have.
  MaybeCompositeAndSubmit();
}
#endif

void OpenXrRenderLoop::CleanUp() {
  DVLOG(1) << __func__;
  submit_client_.reset();
  webxr_has_pose_ = false;
  presentation_receiver_.reset();
  frame_data_receiver_.reset();
  layer_manager_receiver_.reset();
  overlay_receiver_.reset();
  environment_receiver_.reset();
  StopRuntime();
}

void OpenXrRenderLoop::ClearPendingFrame() {
  // Complete the frame if OpenXR has started one with BeginFrame. This also
  // releases the swapchain image that was acquired in BeginFrame so that the
  // next frame can acquire it.
  if (openxr_->HasPendingFrame() && XR_FAILED(openxr_->EndFrame())) {
    // The start of the next frame will detect that the session has ended via
    // HasSessionEnded and will exit presentation.
    ExitPresent(ExitXrPresentReason::kXrEndFrameFailed);
    return;
  }

  pending_frame_.reset();
  // Send frame data to outstanding requests.
  if (delayed_get_frame_data_callback_ &&
      (webxr_visible_ || on_webxr_submitted_)) {
    // If WebXR is not visible, but the browser wants to know when it submits a
    // frame, we allow the renderer to receive poses.
    std::move(delayed_get_frame_data_callback_).Run();
  }
}

bool OpenXrRenderLoop::StartPendingFrame() {
  DVLOG(3) << __func__ << " pending_frame_=" << pending_frame_.has_value();
  if (!pending_frame_) {
    pending_frame_.emplace();
    pending_frame_->waiting_for_webxr_ = webxr_visible_;
    pending_frame_->waiting_for_overlay_ = overlay_visible_;
    // GetNextFrameData() can synchronously end the session (e.g. no locatable
    // views for kMaxInvalidViewFrames), which calls ExitPresent() and resets
    // `pending_frame_`. Compute into a local and report failure if that
    // happened, so callers stop instead of dereferencing a disengaged optional.
    mojom::XRFrameDataPtr next_frame_data = GetNextFrameData();
    if (!pending_frame_) {
      DVLOG(1) << __func__ << ": session ended during GetNextFrameData()";
      return false;
    }
    pending_frame_->frame_data_ = std::move(next_frame_data);
    // GetNextFrameData() should never return null:
    DCHECK(pending_frame_->frame_data_);
    pending_frame_->render_info_ = GetRenderInfo(*pending_frame_->frame_data_);
  }
  return true;
}

void OpenXrRenderLoop::StartRuntimeFinish(
    base::RepeatingCallback<void(mojom::XRVisibilityState)>
        on_visibility_state_changed,
    mojom::XRRuntimeSessionOptionsPtr options,
    base::OnceClosure end_callback,
    bool success) {
  if (!success) {
    TRACE_EVENT_INSTANT("xr", "Failed to start runtime");
    MaybeRejectSessionCallback();
    return;
  }

  on_visibility_state_changed_ = std::move(on_visibility_state_changed);

  // Queue up a notification to the requester of the current visibility state,
  // so that it can be initialized to the right value.
  if (on_visibility_state_changed_) {
    main_thread_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(on_visibility_state_changed_, visibility_state_));
  }

  device::mojom::XRPresentationTransportOptionsPtr transport_options =
      device::mojom::XRPresentationTransportOptions::New();

  if (graphics_binding_->IsUsingSharedImages()) {
    transport_options->transport_method =
        device::mojom::XRPresentationTransportMethod::DRAW_INTO_TEXTURE_MAILBOX;
  } else if constexpr (BUILDFLAG(IS_WIN)) {
    transport_options->transport_method =
        device::mojom::XRPresentationTransportMethod::SUBMIT_AS_TEXTURE_HANDLE;
  } else {
    // TODO(crbug.com/476100354): Verify that this path is not taken and remove
    // it.
    base::debug::DumpWithoutCrashing();
    transport_options->transport_method =
        device::mojom::XRPresentationTransportMethod::SUBMIT_AS_TEST;
  }

  if (graphics_binding_->IsWebGPUSession() &&
      !graphics_binding_->IsUsingSharedImages()) {
    // WebGPU sessions must use shared images. If not fail session creation.
    TRACE_EVENT_INSTANT("xr", "Failed to start WebGPU-compatible runtime");
    MaybeRejectSessionCallback();
    return;
  }

  // Only set boolean options that we need. Default is false, and we should be
  // able to safely ignore ones that our implementation doesn't care about.
  transport_options->wait_for_transfer_notification = true;

  auto submit_frame_sink = device::mojom::XRPresentationConnection::New();
  submit_frame_sink->provider =
      presentation_receiver_.BindNewPipeAndPassRemote();
  submit_frame_sink->client_receiver =
      submit_client_.BindNewPipeAndPassReceiver();
  submit_frame_sink->transport_options = std::move(transport_options);

  auto session = device::mojom::XRSession::New();
  session->data_provider = frame_data_receiver_.BindNewPipeAndPassRemote();
  if (openxr_->IsFeatureEnabled(mojom::XRSessionFeature::LAYERS)) {
    session->layer_manager = layer_manager_receiver_.BindNewPipeAndPassRemote();
  }
  session->submit_frame_sink = std::move(submit_frame_sink);

  const auto& enabled_features = openxr_->GetEnabledFeatures();
  session->enabled_features.insert(session->enabled_features.end(),
                                   enabled_features.begin(),
                                   enabled_features.end());

  session->device_config = device::mojom::XRSessionDeviceConfig::New();
  session->device_config->enable_anti_aliasing =
      openxr_->CanEnableAntiAliasing();
  session->device_config->default_framebuffer_scale =
      openxr_->RecommendedViewportScale();
  session->device_config->views = openxr_->GetDefaultViews();
  if (auto* depth = openxr_->GetDepthSensor(); depth) {
    session->device_config->depth_configuration = depth->GetDepthConfig();
  }
  if (openxr_->IsFeatureEnabled(mojom::XRSessionFeature::LAYERS)) {
    session->device_config->max_render_layers = openxr_->GetMaxRenderLayers();
  }

  session->enviroment_blend_mode =
      openxr_->PickEnvironmentBlendModeForSession(options->mode);
  session->interaction_mode = device::mojom::XRInteractionMode::kWorldSpace;

  mojo::PendingRemote<mojom::ImmersiveOverlay> overlay_remote;

  overlay_receiver_.reset();
  overlay_remote = overlay_receiver_.BindNewPipeAndPassRemote();

  CHECK(request_session_callback_);
  std::move(request_session_callback_)
      .Run(true, std::move(session), std::move(overlay_remote));
  is_presenting_ = true;
  end_callback_ =
      base::BindPostTask(main_thread_task_runner_, std::move(end_callback));

  graphics_binding_->SetOverlayAndWebXrVisibility(overlay_visible_,
                                                  webxr_visible_);
}

void OpenXrRenderLoop::MaybeCompositeAndSubmit(
    const std::vector<LayerId>& updated_layers) {
  DVLOG(3) << __func__;
  if (!pending_frame_) {
    // There is no outstanding frame, nor frame to composite, but there may be
    // pending GetFrameData calls, so ClearPendingFrame() to respond to them.
    ClearPendingFrame();
    return;
  }

  // Check if we have obtained all layers (overlay and webxr) that we need.
  if (pending_frame_->waiting_for_webxr_ ||
      pending_frame_->waiting_for_overlay_) {
    DVLOG(3) << __func__ << "Waiting for additional layers, waiting_for_webxr_="
             << pending_frame_->waiting_for_webxr_
             << " waiting_for_overlay=" << pending_frame_->waiting_for_overlay_;
    // Haven't received submits from all layers.
    return;
  }

  // Dropping the "Maybe", because now we've passed that point.
  TRACE_EVENT_BEGIN("xr", "CompositeAndSubmit");
  bool copy_successful = false;
  bool has_webxr_content = pending_frame_->webxr_submitted_ && webxr_visible_;
  bool has_overlay_content =
      pending_frame_->overlay_submitted_ && overlay_visible_;
  bool can_submit = has_webxr_content || has_overlay_content;

  // Tell texture helper to composite, then grab the output texture, and submit.
  // If we submitted, set up the next frame, and send outstanding pose requests.
  if (can_submit) {
    TRACE_EVENT0("xr", "GraphicsBinding Render");
    copy_successful =
        graphics_binding_->Render(context_provider_, updated_layers);
  } else {
    graphics_binding_->CleanupWithoutSubmit();
  }

  // A copy can only be successful if we actually tried to composite.
  bool submit_successful = false;
  if (copy_successful) {
    pending_frame_->frame_ready_time_ = base::TimeTicks::Now();
    submit_successful = SubmitCompositedFrame();
  }

  TRACE_EVENT_END("xr", "success", copy_successful && submit_successful);

  if (copy_successful && !submit_successful) {
    ExitPresent(ExitXrPresentReason::kSubmitFrameFailed);
    // ExitPresent() clears pending_frame_, so return here to avoid
    // accessing it below.
    return;
  }

  if (pending_frame_->webxr_submitted_ && copy_successful) {
    // We've submitted a frame.
    webxr_js_time_.AddSample(pending_frame_->submit_frame_time_ -
                             pending_frame_->sent_frame_data_time_);
    webxr_gpu_time_.AddSample(pending_frame_->frame_ready_time_ -
                              pending_frame_->submit_frame_time_);

    TRACE_EVENT_INSTANT("gpu", "WebXR frame time (ms)", "javascript",
                        webxr_js_time_.GetAverage().InMillisecondsF(),
                        "rendering",
                        webxr_gpu_time_.GetAverage().InMillisecondsF());
    fps_meter_.AddFrame(base::TimeTicks::Now());
    TRACE_COUNTER1("gpu", "WebXR FPS", fps_meter_.GetFPS());
  }

  if (pending_frame_->webxr_submitted_ && submit_client_) {
    // Tell WebVR that we are done with the texture (if we got a texture)
    submit_client_->OnSubmitFrameTransferred(copy_successful, updated_layers);
    submit_client_->OnSubmitFrameRendered();
    TRACE_EVENT_INSTANT("xr", "SubmitClientNotified", "success",
                        copy_successful);
  }

  if (pending_frame_->overlay_submitted_ && overlay_submit_callback_) {
    // Tell the browser/overlay that we are done with its texture so it can be
    // reused.
    std::move(overlay_submit_callback_).Run(copy_successful);
  }

  ClearPendingFrame();
}

bool OpenXrRenderLoop::MarkFrameSubmitted(int16_t frame_index) {
  DVLOG(3) << __func__;
  webxr_has_pose_ = false;
  // Tell the browser that WebXR has submitted a frame.
  if (on_webxr_submitted_) {
    std::move(on_webxr_submitted_).Run();
  }

  if (!pending_frame_ ||
      pending_frame_->render_info_->frame_id != frame_index) {
    // We weren't expecting a submitted frame.  This can happen if WebXR was
    // hidden by an overlay for some time.
    if (submit_client_) {
      submit_client_->OnSubmitFrameTransferred(false, {});
      submit_client_->OnSubmitFrameRendered();
      TRACE_EVENT1("xr", "SubmitFrameTransferred", "success", false);
    }
    return false;
  }

  pending_frame_->waiting_for_webxr_ = false;
  pending_frame_->webxr_submitted_ = true;
  pending_frame_->submit_frame_time_ = base::TimeTicks::Now();

  return true;
}

void OpenXrRenderLoop::SubmitFrameMissing(
    int16_t frame_index,
    gpu::SharedImageExportResult camera_export_multi_result) {
  DVLOG(3) << __func__ << " frame_index=" << frame_index;
  TRACE_EVENT_INSTANT("xr", "OpenXrRenderLoop::SubmitFrameMissing");
  if (pending_frame_) {
    // WebXR for this frame is hidden.
    pending_frame_->waiting_for_webxr_ = false;
  }
  webxr_has_pose_ = false;
  MaybeCompositeAndSubmit();
}

void OpenXrRenderLoop::UpdateLayerBounds(int16_t frame_id,
                                         const gfx::RectF& left_bounds,
                                         const gfx::RectF& right_bounds,
                                         const gfx::Size& source_size) {
  // Bounds are updated instantly, rather than waiting for frame_id.  This works
  // since blink always passes the current frame_id when updating the bounds.
  // Ignoring the frame_id keeps the logic simpler, so this can more easily
  // merge with vr_shell_gl eventually.
  left_webxr_bounds_ = left_bounds;
  right_webxr_bounds_ = right_bounds;

  // Swap top/bottom to account for differences between mojom and GL
  // coordinates.
  left_webxr_bounds_.set_y(
      1 - (left_webxr_bounds_.y() + left_webxr_bounds_.height()));
  right_webxr_bounds_.set_y(
      1 - (right_webxr_bounds_.y() + right_webxr_bounds_.height()));

  source_size_ = source_size;

  graphics_binding_->SetProjectionLayerTransferSize(source_size);

  // if `pending_frame_` exists and still has a `frame_data_`, then we haven't
  // sent the current texture to the page yet, and it will expect to receive the
  // shared image at this new size when it requests it. This can happen if e.g.
  // the overlay got a request in before the page made this call.
  if (pending_frame_ && pending_frame_->frame_data_ && context_provider_) {
    graphics_binding_->UpdateProjectionLayerActiveSwapchainImageSize(
        context_provider_->SharedImageInterface());
    graphics_binding_->PopulateSharedImageData(*pending_frame_->frame_data_);
  }
}

void OpenXrRenderLoop::SubmitOverlayTexture(
    int16_t frame_id,
    gfx::GpuMemoryBufferHandle texture,
    const gpu::SyncToken& sync_token,
    const gfx::RectF& left_bounds,
    const gfx::RectF& right_bounds,
    SubmitOverlayTextureCallback overlay_submit_callback) {
  TRACE_EVENT_INSTANT("xr", "OpenXrRenderLoop::SubmitOverlay");
  DCHECK(overlay_visible_);
  overlay_submit_callback_ = std::move(overlay_submit_callback);
  if (!pending_frame_) {
    // We may stop presenting while there is a pending SubmitOverlayTexture
    // outstanding.  If we get an overlay submit we weren't expecting, just
    // ignore it.
    DCHECK(!is_presenting_);
    std::move(overlay_submit_callback_).Run(false);
    return;
  }

  pending_frame_->waiting_for_overlay_ = false;

  graphics_binding_->SetOverlayTexture(std::move(texture), sync_token,
                                       left_bounds, right_bounds);
  pending_frame_->overlay_submitted_ = true;

  // Regardless of success - try to composite what we have.
  MaybeCompositeAndSubmit();
}

void OpenXrRenderLoop::RequestNextOverlayPose(
    RequestNextOverlayPoseCallback callback) {
  DVLOG(3) << __func__;
  // We will only request poses while the overlay is visible.
  DCHECK(overlay_visible_);
  TRACE_EVENT_INSTANT("xr", "OpenXrRenderLoop::RequestOverlayPose");

  // Ensure we have a pending frame; bail if the session ended while starting
  // it. ExitPresent() has already reset the overlay receiver, so dropping this
  // callback during teardown is safe.
  if (!StartPendingFrame()) {
    return;
  }

  std::move(callback).Run(pending_frame_->render_info_->Clone());
}

void OpenXrRenderLoop::SetOverlayAndWebXRVisibility(bool overlay_visible,
                                                    bool webxr_visible) {
  DVLOG(1) << __func__ << " overlay_visible=" << overlay_visible
           << " webxr_visible=" << webxr_visible;
  TRACE_EVENT_INSTANT("xr", "OpenXrRenderLoop::SetOverlayAndWebXRVisibility",
                      "overlay", overlay_visible, "webxr", webxr_visible);
  // Update state.
  webxr_visible_ = webxr_visible;
  overlay_visible_ = overlay_visible;
  if (pending_frame_) {
    pending_frame_->waiting_for_webxr_ =
        pending_frame_->waiting_for_webxr_ && webxr_visible;
    pending_frame_->waiting_for_overlay_ =
        pending_frame_->waiting_for_overlay_ && overlay_visible;
  }

  graphics_binding_->SetOverlayAndWebXrVisibility(overlay_visible,
                                                  webxr_visible);

  // Maybe composite and submit if we have a pending that is now valid to
  // submit.
  MaybeCompositeAndSubmit();
}

void OpenXrRenderLoop::RequestNotificationOnWebXrSubmitted(
    RequestNotificationOnWebXrSubmittedCallback callback) {
  on_webxr_submitted_ = std::move(callback);
}

void OpenXrRenderLoop::SendFrameData(
    XRFrameDataProvider::GetFrameDataCallback callback,
    mojom::XRFrameDataPtr frame_data) {
  DVLOG(3) << __func__;
  TRACE_EVENT0("xr", "OpenXrRenderLoop::SendFrameData");

  // This method represents a call from the renderer process. If our visibility
  // state is hidden, we should avoid handing "sensitive" information, like the
  // pose back up to the renderer. Note that this check is done here as other
  // methods (RequestNextOverlayPose) represent a call from the browser process,
  // which should receive the pose.
  bool is_visible =
      (visibility_state_ != device::mojom::XRVisibilityState::HIDDEN);

  // We have posted a message to allow other calls to get through, and now state
  // may have changed.  WebXR may not be presenting any more, or may be hidden.
  if (is_presenting_ && is_visible && (webxr_visible_ || on_webxr_submitted_)) {
    DCHECK(frame_data->render_info);
    std::move(callback).Run(std::move(frame_data));
  } else {
    auto empty_frame_data = mojom::XRFrameData::New();
    empty_frame_data->render_info = mojom::XRRenderInfo::New();
    if (frame_data->render_info) {
      // Ensure that the frame_id is accurate, even if the rest of the frame
      // data has been suppressed.
      empty_frame_data->render_info->frame_id =
          frame_data->render_info->frame_id;
    }
    std::move(callback).Run(std::move(empty_frame_data));
  }
}

mojom::XRFrameDataPtr OpenXrRenderLoop::GetNextFrameData() {
  DVLOG(3) << __func__;
  TRACE_EVENT0("xr", "OpenXrRenderLoop::GetNextFrameData");
  mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
  frame_data->render_info = mojom::XRRenderInfo::New();
  frame_data->render_info->frame_id = next_frame_id_;

  if (XR_FAILED(openxr_->BeginFrame())) {
    return frame_data;
  }

  graphics_binding_->PopulateSharedImageData(*frame_data);

  const XrTime frame_time = openxr_->GetPredictedDisplayTime();

  frame_data->time_delta = base::Nanoseconds(frame_time);
  frame_data->render_info->views = openxr_->GetViews();

  // Unless we are fully synchronized/visible we shouldn't report input state.
  if (visibility_state_ == mojom::XRVisibilityState::VISIBLE) {
    frame_data->input_state = openxr_->GetInputState();
  }

  frame_data->render_info->mojo_from_viewer = openxr_->GetViewerPose();

  UpdateStageParameters();

  std::optional<gfx::Transform> local_from_floor = openxr_->GetLocalFromFloor();
  if (local_from_floor) {
    frame_data->mojo_from_floor = mojo_from_local() * *local_from_floor;
  }

  if (openxr_->HasFrameState()) {
    OpenXrAnchorManager* anchor_manager = openxr_->GetAnchorManager();

    if (anchor_manager) {
      frame_data->anchors_data =
          anchor_manager->ProcessAnchorsForFrame(openxr_.get(), frame_time);
    }

    OpenXrLightEstimator* light_estimator = openxr_->GetLightEstimator();

    if (light_estimator) {
      frame_data->light_estimation_data =
          light_estimator->GetLightEstimate(frame_time);
    }

    OpenXrPlaneManager* plane_manager = openxr_->GetPlaneManager();
    if (plane_manager) {
      frame_data->detected_planes_data = plane_manager->GetDetectedPlanesData();
    }

    OpenXrMeshManager* mesh_manager = openxr_->GetMeshManager();
    if (mesh_manager) {
      XrSpace view_space = openxr_->GetReferenceSpace(
          mojom::XRReferenceSpaceType::kViewer);
      frame_data->detected_meshes_data =
          mesh_manager->GetDetectedMeshesData(frame_time, view_space);
    }
  }

  // Get results for hit test subscriptions.
  OpenXrHitTestManager* hit_test_manager = openxr_->GetHitTestManager();
  if (hit_test_manager && frame_data->render_info->mojo_from_viewer &&
      frame_data->render_info->mojo_from_viewer->position &&
      frame_data->render_info->mojo_from_viewer->orientation) {
    device::Pose mojo_from_viewer(
        *frame_data->render_info->mojo_from_viewer->position,
        *frame_data->render_info->mojo_from_viewer->orientation);
    frame_data->hit_test_subscription_results =
        hit_test_manager->GetHitTestResults(frame_time,
                                            mojo_from_viewer.ToTransform(),
                                            frame_data->input_state);
  }

  // If we don't have a depth_sensor, depth wasn't enabled.
  // We don't need to worry about whether depth is actually active or not, as
  // it'll simply no-op if it's inactive.
  OpenXrDepthSensor* depth_sensor = openxr_->GetDepthSensor();
  if (depth_sensor) {
    depth_sensor->PopulateDepthData(frame_time, frame_data->render_info->views);
  }

  return frame_data;
}

// StartRuntime is called by OpenXrRenderLoop::RequestSession. When the
// runtime is fully started, start_runtime_callback.Run must be called with a
// success boolean, or false on failure. OpenXrRenderLoop::StartRuntime waits
// until the Viz context provider is fully started before running
// start_runtime_callback.
void OpenXrRenderLoop::StartRuntime(
    base::RepeatingCallback<void(mojom::XRVisibilityState)>
        on_visibility_state_changed,
    mojom::XRRuntimeSessionOptionsPtr options,
    base::OnceClosure end_callback) {
  DCHECK(instance_ != XR_NULL_HANDLE);
  DCHECK(!openxr_);

  graphics_binding_ = platform_helper_->GetGraphicsBinding();

  if (!graphics_binding_) {
    DVLOG(1) << "Could not create graphics binding";
    // We aren't actually presenting yet; so ExitPresent won't clean us up.
    // Still call it to log the failure reason; but also explicitly call
    // StopRuntime, which should be resilient to duplicate calls.
    ExitPresent(ExitXrPresentReason::kStartRuntimeFailed);
    StopRuntime();
    MaybeRejectSessionCallback();
    return;
  }

  openxr_ = OpenXrApiWrapper::Create(instance_, graphics_binding_.get());
  if (!openxr_) {
    DVLOG(1) << __func__ << " Could not create OpenXrApiWrapper";
    MaybeRejectSessionCallback();
    return;
  }

  SessionStartedCallback on_session_started_callback = base::BindOnce(
      &OpenXrRenderLoop::OnOpenXrSessionStarted, weak_ptr_factory_.GetWeakPtr(),
      std::move(on_visibility_state_changed), std::move(end_callback));
  SessionEndedCallback on_session_ended_callback = base::BindRepeating(
      &OpenXrRenderLoop::ExitPresent, weak_ptr_factory_.GetWeakPtr());
  VisibilityChangedCallback on_visibility_state_changed_callback =
      base::BindRepeating(&OpenXrRenderLoop::SetVisibilityState,
                          weak_ptr_factory_.GetWeakPtr());
  if (XR_FAILED(openxr_->InitSession(
          std::move(options), *extension_helper_,
          std::move(on_session_started_callback),
          std::move(on_session_ended_callback),
          std::move(on_visibility_state_changed_callback)))) {
    DVLOG(1) << __func__ << " InitSession Failed";
    // We aren't actually presenting yet; so ExitPresent won't clean us up.
    // Still call it to log the failure reason; but also explicitly call
    // StopRuntime, which should be resilient to duplicate calls.
    ExitPresent(ExitXrPresentReason::kStartRuntimeFailed);
    StopRuntime();
  }
}

void OpenXrRenderLoop::MaybeRejectSessionCallback() {
  if (request_session_callback_) {
    std::move(request_session_callback_)
        .Run(false, nullptr, mojo::PendingRemote<mojom::ImmersiveOverlay>());
  }
}

void OpenXrRenderLoop::OnOpenXrSessionStarted(
    base::RepeatingCallback<void(mojom::XRVisibilityState)>
        on_visibility_state_changed,
    base::OnceClosure end_callback,
    mojom::XRRuntimeSessionOptionsPtr options,
    XrResult result) {
  DVLOG(1) << __func__ << " Result: " << result;
  if (XR_FAILED(result)) {
    // We aren't actually presenting yet; so ExitPresent won't clean us up.
    // Still call it to log the failure reason; but also explicitly call
    // StopRuntime, which should be resilient to duplicate calls.
    ExitPresent(ExitXrPresentReason::kOpenXrStartFailed);

    // We're only called from the OpenXrApiWrapper, which StopRuntime will
    // destroy. To prevent some re-entrant behavior, yield to let it finish
    // anything it's doing from before it called us before we stop the runtime.
    task_runner()->PostTask(FROM_HERE,
                            base::BindOnce(&OpenXrRenderLoop::StopRuntime,
                                           weak_ptr_factory_.GetWeakPtr()));

    // Technically until the StopRuntime task is called we can't service another
    // session request, which theoretically could come in once we reject the
    // session callback. Post a task to run it so that it runs after StopRuntime
    // to avoid this potential (albeit unlikely) race.
    // `MaybeRejectSessionCallback` will ensure it's run on the appropriate
    // thread. base::Unretained is safe here since we own and ensure the
    // task_runner() stops before destruction.
    task_runner()->PostTask(
        FROM_HERE, base::BindOnce(&OpenXrRenderLoop::MaybeRejectSessionCallback,
                                  base::Unretained(this)));

    return;
  }

  StartContextProviderIfNeeded(base::BindOnce(
      &OpenXrRenderLoop::StartRuntimeFinish, weak_ptr_factory_.GetWeakPtr(),
      std::move(on_visibility_state_changed), std::move(options),
      std::move(end_callback)));
}

void OpenXrRenderLoop::StopRuntime() {
  if (end_callback_) {
    std::move(end_callback_).Run();
  }
  openxr_ = nullptr;
  // Need to destroy the graphics binding after the OpenXrApiWrapper, which
  // depends on it.
  graphics_binding_.reset();
  context_provider_.reset();
}

bool OpenXrRenderLoop::HasSessionEnded() {
  return openxr_ && openxr_->UpdateAndGetSessionEnded();
}

bool OpenXrRenderLoop::SubmitCompositedFrame() {
  return XR_SUCCEEDED(openxr_->EndFrame());
}

void OpenXrRenderLoop::SubmitFrame(int16_t frame_index,
                                   base::TimeDelta time_waited) {
  DVLOG(3) << __func__ << " frame_index=" << frame_index;
  CHECK(!graphics_binding_->IsUsingSharedImages());
  DCHECK(BUILDFLAG(IS_ANDROID));
  // The sync token passed here is unused by OpenXR backend's implementation of
  // SubmitFrameMissing.
  // TODO(crbug.com/476100354): Android OpenXR only supports Shared Buffer
  // mode in production. This non-shared buffer path is only used in tests
  // (SUBMIT_AS_TEST) and should be removed when that path is cleaned up.
  SubmitFrameMissing(frame_index,
                     gpu::SharedImageExportResult::CreateEmptyResult());
}

void OpenXrRenderLoop::SubmitFrameDrawnIntoTexture(
    int16_t frame_index,
    std::vector<device::mojom::XRLayerUpdatePtr> layer_updates,
    gpu::SharedImageExportResult camera_export_multi_result,
    base::TimeDelta time_waited) {
  TRACE_EVENT_BEGIN("xr", "OpenXrRenderLoop::WaitSyncToken",
                    perfetto::Track(frame_index));
  DVLOG(3) << __func__ << " frame_index=" << frame_index;

  // This frame can arrive after the session ended, or while a context loss is
  // being repaired (context_provider_ null, is_presenting_ still true). Either
  // way there is nothing to submit into, so bail before dereferencing it.
  if (!is_presenting_ || !context_provider_) {
    TRACE_EVENT_END("xr", perfetto::Track(frame_index));
    return;
  }

  if (camera_export_multi_result.HasData()) {
    presentation_receiver_.ReportBadMessage(
        "Received unexpected camera sync tokens.");
    return;
  }

  std::vector<LayerId> layer_ids;
  layer_ids.reserve(layer_updates.size());
  for (auto& layer : layer_updates) {
    layer_ids.push_back(layer->layer_id);
  }

  std::vector<gpu::SyncToken> combined_sync_tokens;
  std::vector<scoped_refptr<gpu::ClientSharedImage>> shared_images =
      graphics_binding_->EndSharedImagesExport(std::move(layer_updates),
                                               combined_sync_tokens);

  gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL();

  // supports_gpu_fence_ was established once in OnContextProviderCreated().
  if (supports_gpu_fence_) {
    gpu::ClientSharedImage::CreateGpuFenceForSyncTokens(
        std::move(shared_images), std::move(combined_sync_tokens), gl,
        context_provider_->ContextSupport(),
        base::BindOnce(&OpenXrRenderLoop::OnWebXrTokenSignaled,
                       weak_ptr_factory_.GetWeakPtr(), frame_index, layer_ids));
    return;
  }

  // No GPU fence: a CPU finish guarantees the WebGL write completed before the
  // binding reads the shared image. WaitOnFence is a no-op without a fence FD.
  // TODO(crbug.com/506004811): use a GL-signal/Vulkan-wait fence if available.
  for (const auto& sync_token : combined_sync_tokens) {
    gl->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
  }
  gl->Finish();
  TRACE_EVENT_END("xr", perfetto::Track(frame_index));
  MarkFrameSubmitted(frame_index);
  MaybeCompositeAndSubmit(layer_ids);
}

void OpenXrRenderLoop::OnWebXrTokenSignaled(
    int16_t frame_index,
    std::vector<LayerId> updated_layers,
    std::unique_ptr<gfx::GpuFence> gpu_fence) {
  TRACE_EVENT_END("xr", /*"OpenXrRenderLoop::WaitSyncToken"*/
                  perfetto::Track(frame_index));
  // openxr_ and context_provider can be nullptr if we receive
  // OnWebXrTokenSignaled after the session has ended. Ensure we don't crash in
  // that case.
  if (!is_presenting_ || !openxr_ || !context_provider_) {
    return;
  }

  {
    TRACE_EVENT0("xr", "OpenXrRenderLoop::WaitOnFence");
    if (!graphics_binding_->WaitOnBaseLayerFence(*gpu_fence)) {
      return;
    }
  }

  MarkFrameSubmitted(frame_index);
  MaybeCompositeAndSubmit(updated_layers);
}

void OpenXrRenderLoop::UpdateStageParameters() {
  std::vector<gfx::Point3F> stage_bounds;
  gfx::Transform local_from_stage;
  if (openxr_->GetStageParameters(stage_bounds, local_from_stage)) {
    mojom::VRStageParametersPtr stage_parameters =
        mojom::VRStageParameters::New();
    stage_parameters->mojo_from_stage = mojo_from_local() * local_from_stage;
    stage_parameters->bounds = std::move(stage_bounds);
    SetStageParameters(std::move(stage_parameters));
  } else {
    SetStageParameters(nullptr);
  }
}

void OpenXrRenderLoop::GetEnvironmentIntegrationProvider(
    mojo::PendingAssociatedReceiver<
        device::mojom::XREnvironmentIntegrationProvider> environment_provider) {
  DVLOG(2) << __func__;

  environment_receiver_.reset();
  environment_receiver_.Bind(std::move(environment_provider));
}

void OpenXrRenderLoop::SubscribeToHitTest(
    mojom::XRNativeOriginInformationPtr native_origin_information,
    const std::vector<mojom::EntityTypeForHitTest>& entity_types,
    mojom::XRRayPtr ray,
    mojom::XREnvironmentIntegrationProvider::SubscribeToHitTestCallback
        callback) {
  DVLOG(2) << __func__ << ": ray origin=" << ray->origin.ToString()
           << ", ray direction=" << ray->direction.ToString();

  OpenXrHitTestManager* hit_test_manager = openxr_->GetHitTestManager();

  if (!hit_test_manager) {
    std::move(callback).Run(std::nullopt);
    return;
  }

  std::optional<HitTestSubscriptionId> maybe_subscription_id =
      hit_test_manager->SubscribeToHitTest(std::move(native_origin_information),
                                           entity_types, std::move(ray));
  DVLOG(2) << __func__ << ": subscription_id="
           << maybe_subscription_id.value_or(kInvalidHitTestSubscriptionId);
  std::move(callback).Run(maybe_subscription_id);
}

void OpenXrRenderLoop::SubscribeToHitTestForTransientInput(
    const std::string& profile_name,
    const std::vector<mojom::EntityTypeForHitTest>& entity_types,
    mojom::XRRayPtr ray,
    mojom::XREnvironmentIntegrationProvider::
        SubscribeToHitTestForTransientInputCallback callback) {
  DVLOG(2) << __func__ << ": ray origin=" << ray->origin.ToString()
           << ", ray direction=" << ray->direction.ToString();

  OpenXrHitTestManager* hit_test_manager = openxr_->GetHitTestManager();

  if (!hit_test_manager) {
    std::move(callback).Run(std::nullopt);
    return;
  }

  std::optional<HitTestSubscriptionId> maybe_subscription_id =
      hit_test_manager->SubscribeToHitTestForTransientInput(
          profile_name, entity_types, std::move(ray));
  DVLOG(2) << __func__ << ": subscription_id="
           << maybe_subscription_id.value_or(kInvalidHitTestSubscriptionId);
  std::move(callback).Run(maybe_subscription_id);
}

void OpenXrRenderLoop::UnsubscribeFromHitTest(
    const HitTestSubscriptionId& subscription_id) {
  DVLOG(2) << __func__;
  OpenXrHitTestManager* hit_test_manager = openxr_->GetHitTestManager();
  if (hit_test_manager) {
    hit_test_manager->UnsubscribeFromHitTest(subscription_id);
  }
}

void OpenXrRenderLoop::CreateCompositionLayer(
    mojom::XRCompositionLayerDataPtr layer_data,
    CreateCompositionLayerCallback callback) {
  absl::Cleanup fail_on_exit = [&callback] {
    if (callback) {
      std::move(callback).Run(
          device::mojom::CreateCompositionLayerResult::FAILURE);
    }
  };

  if (!openxr_->IsFeatureEnabled(mojom::XRSessionFeature::LAYERS)) {
    return;
  }
  if (!context_provider_) {
    return;
  }

  if (!graphics_binding_->CreateCompositionLayer(
          std::move(layer_data), context_provider_->SharedImageInterface())) {
    return;
  }

  std::move(callback).Run(device::mojom::CreateCompositionLayerResult::SUCCESS);
}

void OpenXrRenderLoop::UpdateCompositionLayer(
    const LayerId& layer_id,
    mojom::XRLayerMutableDataPtr layer_data) {
  if (!openxr_->IsFeatureEnabled(mojom::XRSessionFeature::LAYERS)) {
    layer_manager_receiver_.ReportBadMessage("Layers feature is not enabled.");
    return;
  }

  OpenXrCompositionLayer* layer =
      graphics_binding_->GetCompositionLayer(layer_id);

  if (!layer) {
    layer_manager_receiver_.ReportBadMessage("Invalid layer id.");
    return;
  }

  if (layer->type() !=
      OpenXrCompositionLayer::GetTypeFromMojomData(*layer_data->layer_data)) {
    layer_manager_receiver_.ReportBadMessage("Layer type cannot be modified.");
    return;
  }

  layer->UpdateMutableLayerData(std::move(layer_data));
}

void OpenXrRenderLoop::DestroyCompositionLayer(const LayerId& layer_id) {
  graphics_binding_->DestroyCompositionLayer(
      layer_id,
      context_provider_ ? context_provider_->SharedImageInterface() : nullptr);
}

void OpenXrRenderLoop::SetEnabledCompositionLayers(
    const std::vector<LayerId>& layer_ids) {
  if (!openxr_->IsFeatureEnabled(mojom::XRSessionFeature::LAYERS)) {
    layer_manager_receiver_.ReportBadMessage("Layers feature is not enabled.");
    return;
  }
  if (!context_provider_) {
    layer_manager_receiver_.ReportBadMessage("Context was lost.");
    return;
  }
  if (layer_ids.size() > openxr_->GetMaxRenderLayers()) {
    layer_manager_receiver_.ReportBadMessage(
        "Tried to enable too many layers.");
    return;
  }

  graphics_binding_->SetEnabledCompositionLayers(
      layer_ids, openxr_->session(),
      openxr_->GetRecommendedSwapchainSampleCount(),
      context_provider_->SharedImageInterface());
}

void OpenXrRenderLoop::CreateAnchor(
    mojom::XRNativeOriginInformationPtr native_origin_information,
    const device::Pose& native_origin_from_anchor,
    const std::optional<PlaneId>& plane_id,
    CreateAnchorCallback callback) {
  OpenXrAnchorManager* anchor_manager = openxr_->GetAnchorManager();
  if (!anchor_manager) {
    std::move(callback).Run(std::nullopt);
    return;
  }
  anchor_manager->AddCreateAnchorRequest(*native_origin_information,
                                         native_origin_from_anchor, plane_id,
                                         std::move(callback));
}

void OpenXrRenderLoop::DetachAnchor(const AnchorId& anchor_id) {
  OpenXrAnchorManager* anchor_manager = openxr_->GetAnchorManager();
  if (!anchor_manager) {
    return;
  }
  anchor_manager->DetachAnchor(anchor_id);
}

void OpenXrRenderLoop::StartContextProviderIfNeeded(
    ContextProviderAcquiredCallback callback) {
  DCHECK(task_runner()->BelongsToCurrentThread());
  DCHECK_EQ(context_provider_, nullptr);
  // We could arrive here in scenarios where we've shutdown the render loop or
  // runtime. In that case, there is no need to start the context provider.
  // If openxr_ has been torn down the context provider is unnecessary as
  // there is nothing to connect to the GPU process.
  if (openxr_) {
    auto created_callback =
        base::BindOnce(&OpenXrRenderLoop::OnContextProviderCreated,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback));

    main_thread_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(
            context_provider_factory_async_,
            base::BindPostTask(task_runner(), std::move(created_callback))));
  }
}

// viz::ContextLostObserver Implementation.
// Called on the render loop thread.
void OpenXrRenderLoop::OnContextLost() {
  DCHECK(task_runner()->BelongsToCurrentThread());
  DCHECK_NE(context_provider_, nullptr);

  // Avoid OnContextLost getting called multiple times by removing
  // the observer right away.
  context_provider_->RemoveObserver(this);

  if (openxr_) {
    openxr_->OnContextProviderLost();
  }

  // Destroying the context provider in the OpenXrRenderLoop::OnContextLost
  // callback leads to UAF deep inside the GpuChannel callback code. To avoid
  // UAF, post a task to ourselves which does the real context lost work. Pass
  // the context_provider_ as a parameters to the callback to avoid the invalid
  // one getting used on the context thread.
  task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&OpenXrRenderLoop::OnContextLostCallback,
                                weak_ptr_factory_.GetWeakPtr(),
                                std::move(context_provider_)));
}

// Called on the render loop thread as a continuation of OnContextLost.
void OpenXrRenderLoop::OnContextLostCallback(
    scoped_refptr<viz::ContextProvider> lost_context_provider) {
  DCHECK(task_runner()->BelongsToCurrentThread());
  DCHECK_EQ(context_provider_, nullptr);

  // Context providers are required to be released on the context thread they
  // were bound to.
  lost_context_provider.reset();

  if (graphics_binding_ && graphics_binding_->RequiresSharedImages()) {
    // The in-flight frame's shared images died with the context, so neither
    // MarkFrameSubmitted() nor SubmitFrameMissing() will run to clear
    // `webxr_has_pose_`. End it so the deferred frame request can resume.
    webxr_has_pose_ = false;
    ClearPendingFrame();
    if (!is_presenting_) {
      // ClearPendingFrame() ends presentation if the OpenXR frame could not be
      // ended; there is nothing left to restart.
      return;
    }
  }

  StartContextProviderIfNeeded(base::BindOnce(
      &OpenXrRenderLoop::OnContextProviderRestarted, base::Unretained(this)));
}

// Continuation of OnContextLostCallback: if the replacement context provider
// fails (context bind or shared image creation), nothing else notices, so end
// the session instead of leaving deferred frame requests hanging.
void OpenXrRenderLoop::OnContextProviderRestarted(bool success) {
  DCHECK(task_runner()->BelongsToCurrentThread());
  if (!success) {
    ExitPresent(ExitXrPresentReason::kSharedImagesUnavailable);
  }
}

// OpenXrRenderLoop::StartContextProvider queues a task on the main thread's
// task runner to run IsolatedXRRuntimeProvider::CreateContextProviderAsync.
// When CreateContextProviderAsync finishes creating the Viz context provider,
// it will queue a task onto the render loop's task runner to run
// OnContextProviderCreated, passing it the newly created context provider.
// StartContextProvider uses BindOnce to passthrough the start_runtime_callback
// given to it from it's caller. OnContextProviderCreated must run the
// start_runtime_callback, passing true on successful call to
// BindToCurrentSequence and false if not.
void OpenXrRenderLoop::OnContextProviderCreated(
    ContextProviderAcquiredCallback start_runtime_callback,
    scoped_refptr<viz::ContextProvider> context_provider) {
  DCHECK(task_runner()->BelongsToCurrentThread());
  DCHECK_EQ(context_provider_, nullptr);

  const gpu::ContextResult context_result =
      context_provider->BindToCurrentSequence();
  if (context_result != gpu::ContextResult::kSuccess) {
    DVLOG(1) << __func__ << " Could not get context provider";
    std::move(start_runtime_callback).Run(false);
    return;
  }

  if (openxr_) {
    openxr_->OnContextProviderCreated(context_provider);

    // CreateSharedMailboxes() has no failure signal, and creation can fail if
    // the GPU process cannot serve a GL-accessible DMA-BUF SharedImage. The
    // renderer expects shared buffers, so fail session establishment instead.
    if (graphics_binding_->RequiresSharedImages() &&
        openxr_->session() != XR_NULL_HANDLE &&
        !graphics_binding_->IsUsingSharedImages()) {
      LOG(ERROR) << __func__ << " shared image creation failed; ending session";
      std::move(start_runtime_callback).Run(false);
      return;
    }
  }

  context_provider->AddObserver(this);
  context_provider_ = std::move(context_provider);

  // GL_CHROMIUM_gpu_fence (EGL_ANDROID_native_fence_sync) is a fixed property
  // of this context, so query it once here. Requesting a fence without it
  // crashes the GPU process, so SubmitFrameDrawnIntoTexture() uses glFinish.
  const char* extensions = reinterpret_cast<const char*>(
      context_provider_->ContextGL()->GetString(GL_EXTENSIONS));
  supports_gpu_fence_ =
      extensions &&
      std::string_view(extensions).find("GL_CHROMIUM_gpu_fence") !=
          std::string_view::npos;
  DVLOG(1) << __func__
           << " GL_CHROMIUM_gpu_fence supported=" << supports_gpu_fence_;

  std::move(start_runtime_callback).Run(true);

  // A frame request that ShouldDelayGetFrameData() deferred while the shared
  // images were unavailable can be served now that they have been recreated.
  if (delayed_get_frame_data_callback_ &&
      (webxr_visible_ || on_webxr_submitted_)) {
    std::move(delayed_get_frame_data_callback_).Run();
  }
}

bool OpenXrRenderLoop::ShouldDelayGetFrameData() const {
  // If we've already given out a pose for the current frame, delay.
  if (webxr_has_pose_) {
    return true;
  }

  // Blink DCHECKs on a drawable frame (non-negative frame_id) carrying no
  // shared image, so defer instead of answering one while they are gone.
  // OnContextProviderCreated() resumes the request once they are back.
  if (graphics_binding_ && graphics_binding_->RequiresSharedImages() &&
      !graphics_binding_->IsUsingSharedImages()) {
    return true;
  }

  // If we aren't visible and the browser is not waiting to learn that we've
  // submitted a frame we should delay. If the browser *is* waiting to learn
  // that we've submitted a frame though, we can give out a pose as though we
  // are visible.
  if (!webxr_visible_ && !on_webxr_submitted_) {
    return true;
  }

  // If the pending frame is already used for the last WebXR Frame, we must be
  // waiting for the overlay texture to be submitted for the last frame. Do
  // not proceed to next frame until the overlay is submitted, otherwise
  // we will send null frame_data_ and webxr_has_pose_ will always stay true
  // so that the loop will get stuck.
  if (pending_frame_ && !pending_frame_->frame_data_) {
    DCHECK(pending_frame_->waiting_for_overlay_);
    return true;
  }

  return false;
}

}  // namespace device
