// 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.
#ifndef DEVICE_VR_OPENXR_OPENXR_RENDER_LOOP_H_
#define DEVICE_VR_OPENXR_OPENXR_RENDER_LOOP_H_
#include <stdint.h>
#include <memory>
#include "base/functional/callback.h"
#include "base/memory/raw_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/viz/common/gpu/context_lost_observer.h"
#include "device/vr/openxr/context_provider_callbacks.h"
#include "device/vr/openxr/exit_xr_present_reason.h"
#include "device/vr/openxr/openxr_anchor_manager.h"
#include "device/vr/openxr/openxr_graphics_binding.h"
#include "device/vr/openxr/openxr_platform_helper.h"
#include "device/vr/public/mojom/isolated_xr_service.mojom.h"
#include "device/vr/public/mojom/vr_service.mojom.h"
#include "device/vr/public/mojom/xr_session.mojom.h"
#include "device/vr/util/fps_meter.h"
#include "device/vr/util/sliding_average.h"
#include "device/vr/vr_device.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/platform/platform_handle.h"
#include "third_party/openxr/src/include/openxr/openxr.h"
#include "ui/gfx/geometry/rect_f.h"
#if BUILDFLAG(IS_WIN)
#include "base/threading/thread.h"
#endif
#if BUILDFLAG(IS_ANDROID)
#include "base/android/java_handler_thread.h"
#endif
namespace gpu::gles2 {
class GLES2Interface;
} // namespace gpu::gles2
namespace gfx {
class GpuFence;
} // namespace gfx
namespace device {
class OpenXrApiWrapper;
#if BUILDFLAG(IS_ANDROID)
class XRThread : public base::android::JavaHandlerThread {
public:
explicit XRThread(const char* name)
: base::android::JavaHandlerThread(name) {}
~XRThread() override = default;
};
#elif BUILDFLAG(IS_WIN)
class XRThread : public base::Thread {
public:
explicit XRThread(const char* name) : base::Thread(name) {}
~XRThread() override = default;
};
#else
#error "Trying to build OpenXR for an unsupported platform"
#endif
class OpenXrRenderLoop : public XRThread,
public mojom::XRPresentationProvider,
public mojom::XRFrameDataProvider,
public mojom::ImmersiveOverlay,
public mojom::XREnvironmentIntegrationProvider,
public viz::ContextLostObserver {
public:
using RequestSessionCallback =
base::OnceCallback<void(bool result,
mojom::XRSessionPtr,
mojo::PendingRemote<mojom::ImmersiveOverlay>)>;
OpenXrRenderLoop(
VizContextProviderFactoryAsync context_provider_factory_async,
XrInstance instance,
const OpenXrExtensionHelper& extension_helper_,
OpenXrPlatformHelper* platform_helper);
OpenXrRenderLoop(const OpenXrRenderLoop&) = delete;
OpenXrRenderLoop& operator=(const OpenXrRenderLoop&) = delete;
~OpenXrRenderLoop() override;
void ExitPresent(ExitXrPresentReason reason);
gpu::gles2::GLES2Interface* GetContextGL();
void GetFrameData(
mojom::XRFrameDataRequestOptionsPtr options,
XRFrameDataProvider::GetFrameDataCallback callback) override;
void RequestSession(base::RepeatingCallback<void(mojom::XRVisibilityState)>
on_visibility_state_changed,
mojom::XRRuntimeSessionOptionsPtr options,
RequestSessionCallback callback);
private:
void SetVisibilityState(mojom::XRVisibilityState visibility_state);
void SetStageParameters(mojom::VRStageParametersPtr stage_parameters);
// base::Thread overrides:
void CleanUp() override;
void ClearPendingFrame();
void StartPendingFrame();
void StartRuntimeFinish(
base::RepeatingCallback<void(mojom::XRVisibilityState)>
on_visibility_state_changed,
mojom::XRRuntimeSessionOptionsPtr options,
bool success);
// Will Submit if we have textures submitted from the Overlay (if it is
// visible), and WebXR (if it is visible). We decide what to wait for during
// StartPendingFrame, may mark things as ready after SubmitFrameMissing and
// SubmitFrameWithTextureHandle (for WebXR), or SubmitOverlayTexture (for
// overlays), or SetOverlayAndWebXRVisibility (for WebXR and overlays).
// Finally, if we exit presentation while waiting for outstanding submits, we
// will clean up our pending-frame state.
void MaybeCompositeAndSubmit();
// Sets all relevant internal state to mark that we have successfully received
// a frame. Will return whether or not the given frame index was expected.
// If not expected, not all state may be successfully cleared.
bool MarkFrameSubmitted(int16_t frame_index);
// XRPresentationProvider overrides:
#if BUILDFLAG(IS_WIN)
void SubmitFrameWithTextureHandle(int16_t frame_index,
mojo::PlatformHandle texture_handle,
const gpu::SyncToken& sync_token) override;
#endif
void SubmitFrameMissing(int16_t frame_index, const gpu::SyncToken&) override;
void SubmitFrame(int16_t frame_index,
const gpu::MailboxHolder& mailbox,
base::TimeDelta time_waited) final;
void SubmitFrameDrawnIntoTexture(int16_t frame_index,
const gpu::SyncToken&,
base::TimeDelta time_waited) override;
void UpdateLayerBounds(int16_t frame_id,
const gfx::RectF& left_bounds,
const gfx::RectF& right_bounds,
const gfx::Size& source_size) override;
// ImmersiveOverlay:
void SubmitOverlayTexture(int16_t frame_id,
gfx::GpuMemoryBufferHandle texture,
const gpu::SyncToken& sync_token,
const gfx::RectF& left_bounds,
const gfx::RectF& right_bounds,
SubmitOverlayTextureCallback callback) override;
void RequestNextOverlayPose(RequestNextOverlayPoseCallback callback) override;
void SetOverlayAndWebXRVisibility(bool overlay_visible,
bool webxr_visible) override;
void RequestNotificationOnWebXrSubmitted(
RequestNotificationOnWebXrSubmittedCallback callback) override;
void SendFrameData(XRFrameDataProvider::GetFrameDataCallback callback,
mojom::XRFrameDataPtr frame_data);
struct OutstandingFrame {
OutstandingFrame();
~OutstandingFrame();
bool webxr_has_pose_ = false;
bool overlay_has_pose_ = false;
bool webxr_submitted_ = false;
bool overlay_submitted_ = false;
bool waiting_for_webxr_ = false;
bool waiting_for_overlay_ = false;
mojom::XRFrameDataPtr frame_data_;
mojom::XRRenderInfoPtr render_info_;
base::TimeTicks sent_frame_data_time_;
base::TimeTicks submit_frame_time_;
base::TimeTicks frame_ready_time_;
};
mojom::XRFrameDataPtr GetNextFrameData();
// TODO(crbug.com/41489956): Investigate removing this callback.
using ContextProviderAcquiredCallback =
base::OnceCallback<void(bool success)>;
void StartRuntime(base::RepeatingCallback<void(mojom::XRVisibilityState)>
on_visibility_state_changed,
mojom::XRRuntimeSessionOptionsPtr options);
void StopRuntime();
void OnSessionStart();
bool HasSessionEnded();
bool SubmitCompositedFrame();
// viz::ContextLostObserver Implementation
void OnContextLost() override;
void OnOpenXrSessionStarted(
base::RepeatingCallback<void(mojom::XRVisibilityState)>
on_visibility_state_changed,
mojom::XRRuntimeSessionOptionsPtr options,
XrResult result);
bool UpdateViews();
bool UpdateView(const XrView& view_head,
int width,
int height,
mojom::XRViewPtr* view) const;
void UpdateStageParameters();
// XREnvironmentIntegrationProvider
void GetEnvironmentIntegrationProvider(
mojo::PendingAssociatedReceiver<
device::mojom::XREnvironmentIntegrationProvider> environment_provider)
override;
void SubscribeToHitTest(
mojom::XRNativeOriginInformationPtr native_origin_information,
const std::vector<mojom::EntityTypeForHitTest>& entity_types,
mojom::XRRayPtr ray,
mojom::XREnvironmentIntegrationProvider::SubscribeToHitTestCallback
callback) override;
void SubscribeToHitTestForTransientInput(
const std::string& profile_name,
const std::vector<mojom::EntityTypeForHitTest>& entity_types,
mojom::XRRayPtr ray,
mojom::XREnvironmentIntegrationProvider::
SubscribeToHitTestForTransientInputCallback callback) override;
void UnsubscribeFromHitTest(uint64_t subscription_id) override;
void CreateAnchor(
mojom::XRNativeOriginInformationPtr native_origin_information,
const device::Pose& native_origin_from_anchor,
CreateAnchorCallback callback) override;
void CreatePlaneAnchor(
mojom::XRNativeOriginInformationPtr native_origin_information,
const device::Pose& native_origin_from_anchor,
uint64_t plane_id,
CreatePlaneAnchorCallback callback) override;
void DetachAnchor(uint64_t anchor_id) override;
void ProcessCreateAnchorRequests(
OpenXrAnchorManager* anchor_manager,
const std::vector<mojom::XRInputSourceStatePtr>& input_state);
void StartContextProviderIfNeeded(ContextProviderAcquiredCallback callback);
void OnContextProviderCreated(
ContextProviderAcquiredCallback start_runtime_callback,
scoped_refptr<viz::ContextProvider> context_provider);
void OnContextLostCallback(
scoped_refptr<viz::ContextProvider> context_provider);
void OnWebXrTokenSignaled(int16_t frame_index,
GLuint id,
std::unique_ptr<gfx::GpuFence> gpu_fence);
void MaybeRejectSessionCallback();
bool IsFeatureEnabled(device::mojom::XRSessionFeature feature) const;
int16_t next_frame_id_ = 0;
scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
// Owned by OpenXrStatics
XrInstance instance_;
const raw_ref<const OpenXrExtensionHelper> extension_helper_;
scoped_refptr<viz::ContextProvider> context_provider_;
VizContextProviderFactoryAsync context_provider_factory_async_;
FPSMeter fps_meter_;
SlidingTimeDeltaAverage webxr_js_time_;
SlidingTimeDeltaAverage webxr_gpu_time_;
std::optional<OutstandingFrame> pending_frame_;
bool is_presenting_ = false; // True if we have a presenting session.
bool webxr_visible_ = true; // The browser may hide a presenting session.
bool overlay_visible_ = false;
base::OnceCallback<void()> delayed_get_frame_data_callback_;
gfx::RectF left_webxr_bounds_;
gfx::RectF right_webxr_bounds_;
gfx::Size source_size_;
mojo::Remote<mojom::XRPresentationClient> submit_client_;
SubmitOverlayTextureCallback overlay_submit_callback_;
RequestNotificationOnWebXrSubmittedCallback on_webxr_submitted_;
bool webxr_has_pose_ = false;
base::RepeatingCallback<void(mojom::XRVisibilityState)>
on_visibility_state_changed_;
mojo::Receiver<mojom::XRPresentationProvider> presentation_receiver_{this};
mojo::Receiver<mojom::XRFrameDataProvider> frame_data_receiver_{this};
mojo::Receiver<mojom::ImmersiveOverlay> overlay_receiver_{this};
mojom::XRVisibilityState visibility_state_ =
mojom::XRVisibilityState::VISIBLE;
mojom::VRStageParametersPtr current_stage_parameters_;
uint32_t stage_parameters_id_;
// Lifetime of the platform helper is guaranteed by the OpenXrDevice.
raw_ptr<OpenXrPlatformHelper> platform_helper_;
std::unique_ptr<OpenXrGraphicsBinding> graphics_binding_;
std::unique_ptr<OpenXrApiWrapper> openxr_;
mojo::AssociatedReceiver<mojom::XREnvironmentIntegrationProvider>
environment_receiver_{this};
RequestSessionCallback request_session_callback_;
// This must be the last member
base::WeakPtrFactory<OpenXrRenderLoop> weak_ptr_factory_{this};
};
} // namespace device
#endif // DEVICE_VR_OPENXR_OPENXR_RENDER_LOOP_H_