| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_SESSION_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_SESSION_H_ |
| |
| #include "base/containers/span.h" |
| #include "device/vr/public/mojom/vr_service.mojom-blink.h" |
| #include "mojo/public/cpp/bindings/associated_binding.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise.h" |
| #include "third_party/blink/renderer/core/dom/events/event_target.h" |
| #include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h" |
| #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h" |
| #include "third_party/blink/renderer/modules/xr/xr_frame_request_callback_collection.h" |
| #include "third_party/blink/renderer/modules/xr/xr_input_source.h" |
| #include "third_party/blink/renderer/modules/xr/xr_input_source_array.h" |
| #include "third_party/blink/renderer/platform/geometry/double_size.h" |
| #include "third_party/blink/renderer/platform/heap/handle.h" |
| #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h" |
| #include "third_party/blink/renderer/platform/wtf/forward.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_view.h" |
| |
| #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h" |
| |
| namespace blink { |
| |
| class Element; |
| class ExceptionState; |
| class HTMLCanvasElement; |
| class ResizeObserver; |
| class ScriptPromiseResolver; |
| class V8XRFrameRequestCallback; |
| class XR; |
| class XRAnchor; |
| class XRAnchorSet; |
| class XRCanvasInputProvider; |
| class XRHitTestOptionsInit; |
| class XRHitTestSource; |
| class XRPlane; |
| class XRRay; |
| class XRReferenceSpace; |
| class XRRenderState; |
| class XRRenderStateInit; |
| class XRRigidTransform; |
| class XRSpace; |
| class XRViewData; |
| class XRWebGLLayer; |
| class XRWorldInformation; |
| class XRWorldTrackingState; |
| class XRWorldTrackingStateInit; |
| |
| using XRSessionFeatureSet = WTF::HashSet<device::mojom::XRSessionFeature>; |
| |
| class XRSession final |
| : public EventTargetWithInlineData, |
| public device::mojom::blink::XRSessionClient, |
| public device::mojom::blink::XRInputSourceButtonListener, |
| public ActiveScriptWrappable<XRSession> { |
| DEFINE_WRAPPERTYPEINFO(); |
| USING_GARBAGE_COLLECTED_MIXIN(XRSession); |
| |
| public: |
| enum SessionMode { kModeInline = 0, kModeImmersiveVR, kModeImmersiveAR }; |
| |
| enum EnvironmentBlendMode { |
| kBlendModeOpaque = 0, |
| kBlendModeAdditive, |
| kBlendModeAlphaBlend |
| }; |
| |
| XRSession(XR* xr, |
| mojo::PendingReceiver<device::mojom::blink::XRSessionClient> |
| client_receiver, |
| SessionMode mode, |
| EnvironmentBlendMode environment_blend_mode, |
| bool uses_input_eventing, |
| bool sensorless_session, |
| XRSessionFeatureSet enabled_features); |
| ~XRSession() override = default; |
| |
| XR* xr() const { return xr_; } |
| const String& environmentBlendMode() const { return blend_mode_string_; } |
| const String visibilityState() const; |
| XRRenderState* renderState() const { return render_state_; } |
| XRWorldTrackingState* worldTrackingState() { return world_tracking_state_; } |
| XRSpace* viewerSpace() const; |
| XRAnchorSet* trackedAnchors() const; |
| |
| bool immersive() const; |
| |
| DEFINE_ATTRIBUTE_EVENT_LISTENER(end, kEnd) |
| DEFINE_ATTRIBUTE_EVENT_LISTENER(select, kSelect) |
| DEFINE_ATTRIBUTE_EVENT_LISTENER(inputsourceschange, kInputsourceschange) |
| DEFINE_ATTRIBUTE_EVENT_LISTENER(selectstart, kSelectstart) |
| DEFINE_ATTRIBUTE_EVENT_LISTENER(selectend, kSelectend) |
| DEFINE_ATTRIBUTE_EVENT_LISTENER(visibilitychange, kVisibilitychange) |
| |
| void updateRenderState(XRRenderStateInit* render_state_init, |
| ExceptionState& exception_state); |
| void updateWorldTrackingState( |
| XRWorldTrackingStateInit* world_tracking_state_init, |
| ExceptionState& exception_state); |
| ScriptPromise requestReferenceSpace(ScriptState* script_state, |
| const String& type, |
| ExceptionState&); |
| |
| // IDL-exposed |
| ScriptPromise createAnchor(ScriptState* script_state, |
| XRRigidTransform* initial_pose, |
| XRSpace* space, |
| ExceptionState& exception_state); |
| |
| // helper, not IDL-exposed |
| ScriptPromise CreateAnchor(ScriptState* script_state, |
| XRRigidTransform* pose, |
| XRSpace* space, |
| XRPlane* plane, |
| ExceptionState& exception_state); |
| |
| int requestAnimationFrame(V8XRFrameRequestCallback* callback); |
| void cancelAnimationFrame(int id); |
| |
| XRInputSourceArray* inputSources() const; |
| |
| ScriptPromise requestHitTestSource(ScriptState* script_state, |
| XRHitTestOptionsInit* options, |
| ExceptionState& exception_state); |
| |
| ScriptPromise requestHitTest(ScriptState* script_state, |
| XRRay* ray, |
| XRSpace* space, |
| ExceptionState&); |
| |
| // Called by JavaScript to manually end the session. |
| ScriptPromise end(ScriptState* script_state, ExceptionState&); |
| |
| bool ended() const { return ended_; } |
| |
| // Called when the session is ended, either via calling the "end" function or |
| // when the presentation service connection is closed. |
| void ForceEnd(); |
| |
| // Describes the scalar to be applied to the default framebuffer dimensions |
| // which gives 1:1 pixel ratio at the center of the user's view. |
| double NativeFramebufferScale() const; |
| |
| // Describes the recommended dimensions of layer framebuffers. Should be a |
| // value that provides a good balance between quality and performance. |
| DoubleSize DefaultFramebufferSize() const; |
| |
| // Reports the size of the output canvas, if one is available. If not |
| // reports (0, 0); |
| DoubleSize OutputCanvasSize() const; |
| void DetachOutputCanvas(HTMLCanvasElement* output_canvas); |
| |
| void LogGetPose() const; |
| |
| // EventTarget overrides. |
| ExecutionContext* GetExecutionContext() const override; |
| const AtomicString& InterfaceName() const override; |
| |
| void OnFocusChanged(); |
| void OnFrame(double timestamp, |
| const base::Optional<gpu::MailboxHolder>& output_mailbox_holder); |
| |
| void OnInputStateChange( |
| int16_t frame_id, |
| base::span<const device::mojom::blink::XRInputSourceStatePtr> |
| input_states); |
| |
| // XRInputSourceButtonListener |
| void OnButtonEvent( |
| device::mojom::blink::XRInputSourceStatePtr input_source) override; |
| |
| WTF::Vector<XRViewData>& views(); |
| |
| void AddTransientInputSource(XRInputSource* input_source); |
| void RemoveTransientInputSource(XRInputSource* input_source); |
| |
| void OnPoseReset(); |
| |
| const device::mojom::blink::VRDisplayInfoPtr& GetVRDisplayInfo() const { |
| return display_info_; |
| } |
| |
| device::mojom::blink::XRInputSourceButtonListenerAssociatedPtrInfo |
| GetInputClickListener(); |
| |
| bool EmulatedPosition() const { |
| // If we don't have display info then we should be using the identity |
| // reference space, which by definition will be emulating the position. |
| if (!display_info_) { |
| return true; |
| } |
| |
| return emulated_position_; |
| } |
| |
| // Immersive sessions currently use two views for VR, and only a single view |
| // for smartphone immersive AR mode. Convention is that we use the left eye |
| // if there's only a single view. |
| bool StereoscopicViews() { return display_info_ && display_info_->right_eye; } |
| |
| void UpdateEyeParameters( |
| const device::mojom::blink::VREyeParametersPtr& left_eye, |
| const device::mojom::blink::VREyeParametersPtr& right_eye); |
| void UpdateStageParameters( |
| const device::mojom::blink::VRStageParametersPtr& stage_parameters); |
| bool External() const { return is_external_; } |
| // Incremented every time display_info_ is changed, so that other objects that |
| // depend on it can know when they need to update. |
| unsigned int DisplayInfoPtrId() const { return display_info_id_; } |
| unsigned int StageParametersId() const { return stage_parameters_id_; } |
| |
| // Returns true if the session recognizes passed in hit_test_source as still |
| // existing. |
| bool ValidateHitTestSourceExists(XRHitTestSource* hit_test_source); |
| |
| void SetXRDisplayInfo(device::mojom::blink::VRDisplayInfoPtr display_info); |
| |
| bool UsesInputEventing() { return uses_input_eventing_; } |
| |
| void Trace(blink::Visitor* visitor) override; |
| |
| // ScriptWrappable |
| bool HasPendingActivity() const override; |
| |
| bool CanReportPoses(); |
| |
| // Creates presentation frame based on current state of the session. |
| // State currently used in XRFrame creation is mojo_from_viewer_ and |
| // world_information_. The created XRFrame also stores a reference to this |
| // XRSession. |
| XRFrame* CreatePresentationFrame(); |
| |
| // Updates the internal XRSession state that is relevant to creating |
| // presentation frames. |
| void UpdatePresentationFrameState( |
| double timestamp, |
| std::unique_ptr<TransformationMatrix> mojo_from_viewer, |
| const device::mojom::blink::XRFrameDataPtr& frame_data, |
| bool emulated_position); |
| |
| private: |
| class XRSessionResizeObserverDelegate; |
| |
| using XRVisibilityState = device::mojom::blink::XRVisibilityState; |
| |
| void UpdateCanvasDimensions(Element*); |
| void ApplyPendingRenderState(); |
| |
| void MaybeRequestFrame(); |
| |
| void OnInputStateChangeInternal( |
| int16_t frame_id, |
| base::span<const device::mojom::blink::XRInputSourceStatePtr> |
| input_states, |
| bool from_eventing); |
| |
| // XRSessionClient |
| void OnChanged(device::mojom::blink::VRDisplayInfoPtr display_info) override; |
| void OnExitPresent() override; |
| void OnVisibilityStateChanged( |
| device::mojom::blink::XRVisibilityState visibility_state) override; |
| |
| void UpdateVisibilityState(); |
| |
| void OnHitTestResults( |
| ScriptPromiseResolver* resolver, |
| base::Optional<WTF::Vector<device::mojom::blink::XRHitResultPtr>> |
| results); |
| |
| void OnCreateAnchorResult(ScriptPromiseResolver* resolver, |
| device::mojom::CreateAnchorResult result, |
| uint32_t id); |
| |
| void EnsureEnvironmentErrorHandler(); |
| void OnEnvironmentProviderError(); |
| |
| void ProcessAnchorsData( |
| const device::mojom::blink::XRAnchorsDataPtr& tracked_anchors_data, |
| double timestamp); |
| |
| const Member<XR> xr_; |
| const SessionMode mode_; |
| const bool environment_integration_; |
| String blend_mode_string_; |
| XRVisibilityState device_visibility_state_ = XRVisibilityState::VISIBLE; |
| XRVisibilityState visibility_state_ = XRVisibilityState::VISIBLE; |
| String visibility_state_string_; |
| Member<XRRenderState> render_state_; |
| Member<XRWorldTrackingState> world_tracking_state_; |
| Member<XRWorldInformation> world_information_; |
| HeapVector<Member<XRRenderStateInit>> pending_render_state_; |
| |
| XRSessionFeatureSet enabled_features_; |
| |
| bool is_tracked_anchors_null_ = true; |
| HeapHashMap<uint32_t, Member<XRAnchor>> anchor_ids_to_anchors_; |
| |
| // Mapping of hit test source ids (aka hit test subscription ids) to hit test |
| // sources. Hit test source has to be stored via weak member - JavaScript side |
| // will communicate that it's no longer interested in the subscription by |
| // dropping all its references to the hit test source & we need to make sure |
| // that we don't keep the XRHitTestSources alive. |
| HeapHashMap<uint32_t, WeakMember<XRHitTestSource>> |
| hit_test_source_ids_to_hit_test_sources_; |
| |
| WTF::Vector<XRViewData> views_; |
| |
| Member<XRInputSourceArray> input_sources_; |
| Member<XRWebGLLayer> prev_base_layer_; |
| Member<ResizeObserver> resize_observer_; |
| Member<XRCanvasInputProvider> canvas_input_provider_; |
| bool environment_error_handler_subscribed_ = false; |
| HeapHashSet<Member<ScriptPromiseResolver>> hit_test_promises_; |
| // Set of promises returned from CreateAnchor that are still in-flight. |
| HeapHashSet<Member<ScriptPromiseResolver>> create_anchor_promises_; |
| // Set of promises returned from requestHitTestSource that are still |
| // in-flight. |
| HeapHashSet<Member<ScriptPromiseResolver>> request_hit_test_source_promises_; |
| HeapVector<Member<XRReferenceSpace>> reference_spaces_; |
| |
| bool is_external_ = false; |
| unsigned int display_info_id_ = 0; |
| unsigned int stage_parameters_id_ = 0; |
| device::mojom::blink::VRDisplayInfoPtr display_info_; |
| |
| mojo::Receiver<device::mojom::blink::XRSessionClient> client_receiver_; |
| mojo::AssociatedBinding<device::mojom::blink::XRInputSourceButtonListener> |
| input_binding_; |
| |
| Member<XRFrameRequestCallbackCollection> callback_collection_; |
| // Viewer pose in mojo space. |
| std::unique_ptr<TransformationMatrix> mojo_from_viewer_; |
| |
| bool ended_ = false; |
| bool pending_frame_ = false; |
| bool resolving_frame_ = false; |
| bool update_views_next_frame_ = false; |
| bool views_dirty_ = true; |
| bool frames_throttled_ = false; |
| |
| // Indicates that we've already logged a metric, so don't need to log it |
| // again. |
| mutable bool did_log_getInputSources_ = false; |
| mutable bool did_log_getViewerPose_ = false; |
| |
| // Dimensions of the output canvas. |
| int output_width_ = 1; |
| int output_height_ = 1; |
| |
| bool uses_input_eventing_ = false; |
| |
| // Indicates that this is a sensorless session which should only support the |
| // identity reference space. |
| bool sensorless_session_ = false; |
| |
| int16_t last_frame_id_ = -1; |
| |
| bool emulated_position_ = false; |
| }; |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_SESSION_H_ |