chromium/device/vr/android/arcore/ar_compositor_frame_sink.h

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

#ifndef DEVICE_VR_ANDROID_ARCORE_AR_COMPOSITOR_FRAME_SINK_H_
#define DEVICE_VR_ANDROID_ARCORE_AR_COMPOSITOR_FRAME_SINK_H_

#include <memory>
#include <vector>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "components/viz/common/frame_timing_details_map.h"
#include "components/viz/common/quads/compositor_frame_metadata.h"
#include "components/viz/common/resources/resource_id.h"
#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
#include "components/viz/host/host_display_client.h"
#include "device/vr/public/cpp/xr_frame_sink_client.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/viz/privileged/mojom/compositing/display_private.mojom.h"
#include "services/viz/privileged/mojom/compositing/external_begin_frame_controller.mojom.h"
#include "services/viz/privileged/mojom/compositing/frame_sink_manager.mojom.h"
#include "services/viz/public/mojom/compositing/compositor_frame_sink.mojom.h"
#include "ui/gfx/geometry/size.h"

namespace base {
class TimeDelta;
class TimeTicks;
}  // namespace base

namespace ui {
class WindowAndroid;
}

namespace viz {
class CompositorFrame;
}  // namespace viz

namespace device {
struct WebXrFrame;

// This class creates a RootCompositorFrameSink for use with the Viz Compositor
// and manages building an appropriate ArFrame based off of the data from the
// passed in WebXrFrames.
class ArCompositorFrameSink : public viz::mojom::CompositorFrameSinkClient {
 public:
  // Enum used to indicate to SubmitFrame if content from the Renderer is valid
  // or not. If it's invalid, only the Camera Image (and DOM Overlay if
  // applicable), will be composited into a frame.
  enum FrameType {
    kMissingWebXrContent,
    kHasWebXrContent,
  };

  // Used when the compositor acknowledges that it is ready to begin processing
  // or working on the frame that previously requested to Begin.
  using BeginFrameCallback =
      base::RepeatingCallback<void(const viz::BeginFrameArgs& args,
                                   const viz::FrameTimingDetailsMap&)>;

  using CompositorReceivedFrameCallback = base::RepeatingClosure;

  // This callback signals when all of the resources associated with the given
  // frame have been "returned" by the Compositor. Note that just because the
  // resources have been returned, does not mean that they can immediately be
  // re-used. Any |reclaimed_sync_tokens| on the |WebXrFrame| must be waited
  // on (and a gpu-context server wait issued), before the frame can be reused.
  using RenderingFinishedCallback = base::RepeatingCallback<void(WebXrFrame*)>;

  // Once a BeginFrame call has been issued, we cannot issue another one until
  // the compositor is ready. Note that this will be sometime *after* the
  // previously begun frame has been submitted back to the compositor.
  using CanIssueNewFrameCallback = base::RepeatingClosure;

  ArCompositorFrameSink(
      scoped_refptr<base::SingleThreadTaskRunner> gl_thread_task_runner,
      BeginFrameCallback on_begin_frame,
      CompositorReceivedFrameCallback on_compositor_received_frame,
      RenderingFinishedCallback on_rendering_finished,
      CanIssueNewFrameCallback on_can_issue_new_frame);

  ~ArCompositorFrameSink() override;

  ArCompositorFrameSink(const ArCompositorFrameSink&) = delete;
  ArCompositorFrameSink& operator=(const ArCompositorFrameSink&) = delete;

  bool IsInitialized() { return is_initialized_; }
  bool CanIssueBeginFrame() { return can_issue_new_begin_frame_; }
  viz::FrameSinkId FrameSinkId();

  void Initialize(gpu::SurfaceHandle surface_handle,
                  ui::WindowAndroid* root_window,
                  const gfx::Size& frame_size,
                  XrFrameSinkClient* xr_frame_sink_client,
                  DomOverlaySetup dom_setup,
                  base::OnceCallback<void(bool)> on_initialized,
                  base::OnceClosure on_bindings_disconnect);

  // This will tick the ExternalBeginFrameController to start a frame in the viz
  // process. The BeginFrameCallback (an acknowledgement of this frame from the
  // viz side), should be fired before attempting to call SubmitFrame or
  // DidNotProduceFrame.
  void RequestBeginFrame(base::TimeDelta interval, base::TimeTicks deadline);

  // We don't take ownership of the WebXrFrame, it is the responsibility of the
  // caller to keep this frame alive until the RenderingFinishedCallback is
  // triggered.
  void SubmitFrame(WebXrFrame* xr_frame, FrameType frame_type);
  void DidNotProduceFrame(WebXrFrame* xr_frame);

  // For the purposes of our callers we *can* composite DOM content as long as
  // we think we *should* try to do so. This prevents issues with this check if
  // we temporarily have an invalid surface id for some reason.
  bool CanCompositeDomContent() { return should_composite_dom_overlay_; }

 private:
  bool IsOnGlThread() const;

  void OnRootCompositorFrameSinkReady(DomOverlaySetup dom_setup);

  viz::CompositorFrame CreateFrame(WebXrFrame* xr_frame, FrameType frame_type);

  // viz::mojom::CompositorFrameSinkClient:
  void OnBeginFramePausedChanged(bool paused) override {}
  void ReclaimResources(std::vector<viz::ReturnedResource> resources) override;
  void OnCompositorFrameTransitionDirectiveProcessed(
      uint32_t sequence_id) override {}
  void OnSurfaceEvicted(const viz::LocalSurfaceId& local_surface_id) override {}
  void DidReceiveCompositorFrameAck(
      std::vector<viz::ReturnedResource> resources) override;
  void OnBeginFrame(const viz::BeginFrameArgs& args,
                    const viz::FrameTimingDetailsMap& timing_details,
                    bool frame_ack,
                    std::vector<viz::ReturnedResource> resources) override;

  // Callback that we bind when submitting a frame. It lets us know that viz
  // will allow us to call "BeginFrame" again.
  void OnFrameSubmitAck(const viz::BeginFrameAck& ack);

  void OnBindingsDisconnect();
  void CloseBindingsIfOpen();

  scoped_refptr<base::SingleThreadTaskRunner> gl_thread_task_runner_;
  raw_ptr<XrFrameSinkClient> xr_frame_sink_client_;
  gfx::Size frame_size_;
  bool can_issue_new_begin_frame_ = true;
  bool is_initialized_ = false;

  // Each frame should only have two resources, and there should really only
  // be two frames with outstanding resources at a time (though we also have a
  // max size to our swapchain), so we generally expect this to be ~4 items, but
  // it could be 2*Swapchain size in a worst case.
  base::flat_map<viz::ResourceId, WebXrFrame*> id_to_frame_map_;

  base::OnceCallback<void(bool)> on_initialized_;
  BeginFrameCallback on_begin_frame_;
  CompositorReceivedFrameCallback on_compositor_received_frame_;
  RenderingFinishedCallback on_rendering_finished_;
  CanIssueNewFrameCallback on_can_issue_new_frame_;
  base::OnceClosure on_bindings_disconnect_;

  // State for various Viz IDs
  viz::ParentLocalSurfaceIdAllocator allocator_;
  viz::FrameTokenGenerator next_frame_token_;
  viz::ResourceIdGenerator resource_id_generator_;
  uint64_t next_begin_frame_id_ = 1;
  bool should_composite_dom_overlay_ = false;

  // Mojom remotes and helpers
  mojo::AssociatedRemote<viz::mojom::DisplayPrivate> display_private_;
  mojo::AssociatedRemote<viz::mojom::CompositorFrameSink> sink_remote_;
  mojo::AssociatedRemote<viz::mojom::ExternalBeginFrameController>
      frame_controller_remote_;
  mojo::Receiver<viz::mojom::CompositorFrameSinkClient> sink_receiver_{this};
  std::unique_ptr<viz::HostDisplayClient> display_client_;

  // Must be the last member so that it will be destructed first.
  base::WeakPtrFactory<ArCompositorFrameSink> weak_ptr_factory_{this};
};

}  // namespace device

#endif  // DEVICE_VR_ANDROID_ARCORE_AR_COMPOSITOR_FRAME_SINK_H_