Changeset 272734 in webkit


Ignore:
Timestamp:
Feb 11, 2021 9:26:14 AM (3 years ago)
Author:
commit-queue@webkit.org
Message:

Complete XRSession::requestAnimationFrame implementation
https://bugs.webkit.org/show_bug.cgi?id=220979

Patch by Sergio Villar Senin <svillar@igalia.com> and Imanol Fernandez <imanol> on 2021-02-11
Reviewed by Youenn Fablet.

LayoutTests/imported/w3c:

Enable XRSession RAF tests.

  • web-platform-tests/webxr/xrSession_requestAnimationFrame_timestamp.https-expected.txt: Added.

Source/WebCore:

  • Implement the render loop for immersive and inline XR sessions.
  • Implement WebXR render state updates.
  • Create the FrameData struct used to query frame specific data from XR devices.
  • Use window RAF for inline XR sessions.
  • Implement WebFakeXRDevice testing rendering loop using a timer.
  • Implement OpenXR session states and session tracking.
  • Improve OpenXR event handling.

Tested by the WebXR platform tests.

  • Modules/webxr/WebXRFrame.cpp: Add m_active, m_time and m_session members

(WebCore::WebXRFrame::create):
(WebCore::WebXRFrame::WebXRFrame):

  • Modules/webxr/WebXRFrame.h:

(WebCore::WebXRFrame::setTime):
(WebCore::WebXRFrame::setActive):
(WebCore::WebXRFrame::isActive const):

  • Modules/webxr/WebXRRenderState.h: Add m_compositionDisabled member and output canvas setter

(WebCore::WebXRRenderState::setOutputCanvas):
(WebCore::WebXRRenderState::compositionDisabled const):
(WebCore::WebXRRenderState::setCompositionDisabled):

  • Modules/webxr/WebXRSession.cpp:

(WebCore::WebXRSession::create):
(WebCore::WebXRSession::WebXRSession):
(WebCore::WebXRSession::initialize): required to creat the XRFrame with makeRef(this)
(WebCore::WebXRSession::requestReferenceSpace): implement reference space creation
(WebCore::WebXRSession::requestAnimationFrame): implement render loop for immersive and inline sessions
(WebCore::WebXRSession::applyPendingRenderState): implement WebXR render state updates
(WebCore::WebXRSession::frameShouldBeRendered const): add check based on the spec
(WebCore::WebXRSession::requestFrame): implement helper function to dispatch a frame request to XR devices
(WebCore::WebXRSession::onFrame): process the XR frame and call the RAF callbacks

  • Modules/webxr/WebXRSession.h:
  • Modules/webxr/WebXRSystem.cpp: Implement render loop using window raf loop.

(WebCore::WebXRSystem::WebXRSystem):
(WebCore::WebXRSystem::DummyInlineDevice::DummyInlineDevice): Add ScriptExecutionContext
(WebCore::WebXRSystem::DummyInlineDevice::requestFrame): Adapt to the new interface

  • Modules/webxr/WebXRSystem.h:
  • Modules/webxr/WebXRWebGLLayer.cpp:

(WebCore::WebXRWebGLLayer::canvas const): Implement canvas getter

  • Modules/webxr/WebXRWebGLLayer.h:

(WebCore::WebXRWebGLLayer::compositionDisabled const): add
(WebCore::WebXRWebGLLayer::setCompositionDisabled): add

  • platform/xr/PlatformXR.h: Add FrameData struct
  • platform/xr/openxr/PlatformXROpenXR.cpp: Implement render loop using OpenXR API

(PlatformXR::OpenXRDevice::resetSession):
(PlatformXR::OpenXRDevice::shutDownTrackingAndRendering):
(PlatformXR::OpenXRDevice::pollEvents): Implement event loop to query m_sessionState
(PlatformXR::sessionIsActive): add
(PlatformXR::sessionIsRunning): add
(PlatformXR::OpenXRDevice::beginSession):
(PlatformXR::xrViewToViewData): helper function to convert data from OpenXR
(PlatformXR::OpenXRDevice::requestFrame): start OpenXR frame, query pose and view data
(PlatformXR::OpenXRDevice::waitUntilStopping): properly wait for OpenXR event before ending the session.

  • platform/xr/openxr/PlatformXROpenXR.h:
  • testing/WebFakeXRDevice.cpp: Implement render loop using a timer

(WebCore::SimulatedXRDevice::SimulatedXRDevice):
(WebCore::SimulatedXRDevice::~SimulatedXRDevice):
(WebCore::SimulatedXRDevice::shutDownTrackingAndRendering):
(WebCore::SimulatedXRDevice::stopTimer):
(WebCore::SimulatedXRDevice::frameTimerFired):
(WebCore::SimulatedXRDevice::requestFrame):

  • testing/WebFakeXRDevice.h:

LayoutTests:

  • platform/wpe/TestExpectations:
Location:
trunk
Files:
1 added
19 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r272726 r272734  
     12021-02-11  Sergio Villar Senin  <svillar@igalia.com> and Imanol Fernandez  <ifernandez@igalia.com>
     2
     3        Complete XRSession::requestAnimationFrame implementation
     4        https://bugs.webkit.org/show_bug.cgi?id=220979
     5
     6        Reviewed by Youenn Fablet.
     7
     8        * platform/wpe/TestExpectations:
     9
    1102021-02-11  Manuel Rego Casasnovas  <rego@igalia.com>
    211
  • trunk/LayoutTests/imported/w3c/ChangeLog

    r272726 r272734  
     12021-02-11  Sergio Villar Senin  <svillar@igalia.com> and Imanol Fernandez  <ifernandez@igalia.com>
     2
     3        Complete XRSession::requestAnimationFrame implementation
     4        https://bugs.webkit.org/show_bug.cgi?id=220979
     5
     6        Reviewed by Youenn Fablet.
     7
     8        Enable XRSession RAF tests.
     9
     10        * web-platform-tests/webxr/xrSession_requestAnimationFrame_timestamp.https-expected.txt: Added.
     11
    1122021-02-11  Manuel Rego Casasnovas  <rego@igalia.com>
    213
  • trunk/LayoutTests/platform/wpe/TestExpectations

    r272492 r272734  
    641641webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrSession_features_deviceSupport.https.html [ Pass ]
    642642webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrSession_requestAnimationFrame_callback_calls.https.html [ Pass ]
     643imported/w3c/web-platform-tests/webxr/xrSession_requestAnimationFrame_timestamp.https.html [ Pass ]
    643644webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrSession_requestReferenceSpace.https.html [ Pass ]
    644645imported/w3c/web-platform-tests/webxr/xrSession_viewer_availability.https.html [ Pass ]
  • trunk/Source/WebCore/ChangeLog

    r272731 r272734  
     12021-02-11  Sergio Villar Senin  <svillar@igalia.com> and Imanol Fernandez  <ifernandez@igalia.com>
     2
     3        Complete XRSession::requestAnimationFrame implementation
     4        https://bugs.webkit.org/show_bug.cgi?id=220979
     5
     6        Reviewed by Youenn Fablet.
     7
     8        - Implement the render loop for immersive and inline XR sessions.
     9        - Implement WebXR render state updates.
     10        - Create the FrameData struct used to query frame specific data from XR devices.
     11        - Use window RAF for inline XR sessions.
     12        - Implement WebFakeXRDevice testing rendering loop using a timer.
     13        - Implement OpenXR session states and session tracking.
     14        - Improve OpenXR event handling.
     15
     16        Tested by the WebXR platform tests.
     17
     18        * Modules/webxr/WebXRFrame.cpp: Add m_active, m_time and m_session members
     19        (WebCore::WebXRFrame::create):
     20        (WebCore::WebXRFrame::WebXRFrame):
     21        * Modules/webxr/WebXRFrame.h:
     22        (WebCore::WebXRFrame::setTime):
     23        (WebCore::WebXRFrame::setActive):
     24        (WebCore::WebXRFrame::isActive const):
     25
     26        * Modules/webxr/WebXRRenderState.h: Add m_compositionDisabled member and output canvas setter
     27        (WebCore::WebXRRenderState::setOutputCanvas):
     28        (WebCore::WebXRRenderState::compositionDisabled const):
     29        (WebCore::WebXRRenderState::setCompositionDisabled):
     30
     31        * Modules/webxr/WebXRSession.cpp:
     32        (WebCore::WebXRSession::create):
     33        (WebCore::WebXRSession::WebXRSession):
     34        (WebCore::WebXRSession::initialize): required to creat the XRFrame with makeRef(this)
     35        (WebCore::WebXRSession::requestReferenceSpace): implement reference space creation
     36        (WebCore::WebXRSession::requestAnimationFrame): implement render loop for immersive and inline sessions
     37        (WebCore::WebXRSession::applyPendingRenderState): implement WebXR render state updates
     38        (WebCore::WebXRSession::frameShouldBeRendered const): add check based on the spec
     39        (WebCore::WebXRSession::requestFrame): implement helper function to dispatch a frame request to XR devices
     40        (WebCore::WebXRSession::onFrame): process the XR frame and call the RAF callbacks
     41        * Modules/webxr/WebXRSession.h:
     42
     43        * Modules/webxr/WebXRSystem.cpp: Implement render loop using window raf loop.
     44        (WebCore::WebXRSystem::WebXRSystem):
     45        (WebCore::WebXRSystem::DummyInlineDevice::DummyInlineDevice): Add ScriptExecutionContext
     46        (WebCore::WebXRSystem::DummyInlineDevice::requestFrame): Adapt to the new interface
     47
     48        * Modules/webxr/WebXRSystem.h:
     49        * Modules/webxr/WebXRWebGLLayer.cpp:
     50        (WebCore::WebXRWebGLLayer::canvas const): Implement canvas getter
     51        * Modules/webxr/WebXRWebGLLayer.h:
     52        (WebCore::WebXRWebGLLayer::compositionDisabled const): add
     53        (WebCore::WebXRWebGLLayer::setCompositionDisabled): add
     54
     55        * platform/xr/PlatformXR.h: Add FrameData struct
     56
     57        * platform/xr/openxr/PlatformXROpenXR.cpp: Implement render loop using OpenXR API
     58        (PlatformXR::OpenXRDevice::resetSession):
     59        (PlatformXR::OpenXRDevice::shutDownTrackingAndRendering):
     60        (PlatformXR::OpenXRDevice::pollEvents): Implement event loop to query m_sessionState
     61        (PlatformXR::sessionIsActive): add
     62        (PlatformXR::sessionIsRunning): add
     63        (PlatformXR::OpenXRDevice::beginSession):
     64        (PlatformXR::xrViewToViewData): helper function to convert data from OpenXR
     65        (PlatformXR::OpenXRDevice::requestFrame): start OpenXR frame, query pose and view data
     66        (PlatformXR::OpenXRDevice::waitUntilStopping): properly wait for OpenXR event before ending the session.
     67        * platform/xr/openxr/PlatformXROpenXR.h:
     68
     69
     70        * testing/WebFakeXRDevice.cpp: Implement render loop using a timer
     71        (WebCore::SimulatedXRDevice::SimulatedXRDevice):
     72        (WebCore::SimulatedXRDevice::~SimulatedXRDevice):
     73        (WebCore::SimulatedXRDevice::shutDownTrackingAndRendering):
     74        (WebCore::SimulatedXRDevice::stopTimer):
     75        (WebCore::SimulatedXRDevice::frameTimerFired):
     76        (WebCore::SimulatedXRDevice::requestFrame):
     77        * testing/WebFakeXRDevice.h:
     78
    1792021-02-11  Sam Weinig  <weinig@apple.com>
    280
  • trunk/Source/WebCore/Modules/webxr/WebXRFrame.cpp

    r258498 r272734  
    4949WebXRFrame::~WebXRFrame() = default;
    5050
    51 const WebXRSession& WebXRFrame::session() const
    52 {
    53     return m_session;
    54 }
    5551
    5652RefPtr<WebXRViewerPose> WebXRFrame::getViewerPose(const WebXRReferenceSpace&)
  • trunk/Source/WebCore/Modules/webxr/WebXRFrame.h

    r258498 r272734  
    2828#if ENABLE(WEBXR)
    2929
     30#include "DOMHighResTimeStamp.h"
    3031#include <wtf/IsoMalloc.h>
    3132#include <wtf/Ref.h>
     
    4748    ~WebXRFrame();
    4849
    49     const WebXRSession& session() const;
     50    const WebXRSession& session() const { return m_session.get(); }
    5051
    5152    RefPtr<WebXRViewerPose> getViewerPose(const WebXRReferenceSpace&);
    5253    RefPtr<WebXRPose> getPose(const WebXRSpace&, const WebXRSpace&);
    5354
     55    void setTime(DOMHighResTimeStamp time) { m_time = time; }
     56    void setActive(bool active) { m_active = active; }
     57    bool isActive() const { return m_active; }
     58
    5459private:
    55     WebXRFrame(Ref<WebXRSession>&&);
     60    explicit WebXRFrame(Ref<WebXRSession>&&);
    5661
     62    bool m_active { false };
     63    DOMHighResTimeStamp m_time;
    5764    Ref<WebXRSession> m_session;
    5865};
  • trunk/Source/WebCore/Modules/webxr/WebXRRenderState.h

    r265665 r272734  
    5757
    5858    HTMLCanvasElement* outputCanvas() const { return m_outputCanvas.get(); }
     59    void setOutputCanvas(HTMLCanvasElement* canvas) { m_outputCanvas = makeWeakPtr(canvas); }
     60
     61    bool isCompositionEnabled() const { return m_compositionEnabled; }
     62    void setCompositionEnabled(bool compositionEnabled) { m_compositionEnabled = compositionEnabled; }
    5963
    6064private:
     
    7074    RefPtr<WebXRWebGLLayer> m_baseLayer;
    7175    WeakPtr<HTMLCanvasElement> m_outputCanvas;
     76    bool m_compositionEnabled { true };
    7277};
    7378
  • trunk/Source/WebCore/Modules/webxr/WebXRSession.cpp

    r272014 r272734  
    5656    , m_device(makeWeakPtr(device))
    5757    , m_activeRenderState(WebXRRenderState::create(mode))
    58     , m_animationTimer(*this, &WebXRSession::animationTimerFired)
     58    , m_timeOrigin(MonotonicTime::now())
    5959{
    6060    m_device->initializeTrackingAndRendering(mode);
    6161    m_device->setTrackingAndRenderingClient(makeWeakPtr(*this));
     62
     63    // https://immersive-web.github.io/webxr/#ref-for-dom-xrreferencespacetype-viewer%E2%91%A2
     64    // Every session MUST support viewer XRReferenceSpaces.
     65    m_device->initializeReferenceSpace(XRReferenceSpaceType::Viewer);
    6266
    6367    suspendIfNeeded();
     
    199203        return;
    200204    }
     205
    201206    // 1. Let promise be a new Promise.
    202207    // 2. Run the following steps in parallel:
    203     scriptExecutionContext()->postTask([this, promise = WTFMove(promise), type] (auto& context) mutable {
    204         //  2.1. Create a reference space, referenceSpace, with the XRReferenceSpaceType type.
    205         //  2.2. If referenceSpace is null, reject promise with a NotSupportedError and abort these steps.
     208    scriptExecutionContext()->postTask([this, weakThis = makeWeakPtr(*this), promise = WTFMove(promise), type](auto&) mutable {
     209        if (!weakThis)
     210            return;
     211        // 2.1. If the result of running reference space is supported for type and session is false, queue a task to reject promise
     212        // with a NotSupportedError and abort these steps.
    206213        if (!referenceSpaceIsSupported(type)) {
    207             promise.reject(Exception { NotSupportedError });
     214            queueTaskKeepingObjectAlive(*this, TaskSource::WebXR, [promise = WTFMove(promise)]() mutable {
     215                promise.reject(Exception { NotSupportedError });
     216            });
    208217            return;
    209218        }
    210 
    211         // https://immersive-web.github.io/webxr/#create-a-reference-space
    212         RefPtr<WebXRReferenceSpace> referenceSpace;
    213         if (type == XRReferenceSpaceType::BoundedFloor)
    214             referenceSpace = WebXRBoundedReferenceSpace::create(downcast<Document>(context), makeRef(*this), type);
    215         else
    216             referenceSpace = WebXRReferenceSpace::create(downcast<Document>(context), makeRef(*this), type);
    217 
    218         //  2.3. Resolve promise with referenceSpace.
    219         // 3. Return promise.
    220         promise.resolve(referenceSpace.releaseNonNull());
     219        // 2.2. Set up any platform resources required to track reference spaces of type type.
     220        m_device->initializeReferenceSpace(type);
     221
     222        // 2.3. Queue a task to run the following steps:
     223        queueTaskKeepingObjectAlive(*this, TaskSource::WebXR, [this, type, promise = WTFMove(promise)]() mutable {
     224            if (!scriptExecutionContext()) {
     225                promise.reject(Exception { InvalidStateError });
     226                return;
     227            }
     228            auto& document = downcast<Document>(*scriptExecutionContext());
     229            // 2.4. Create a reference space, referenceSpace, with type and session.
     230            // https://immersive-web.github.io/webxr/#create-a-reference-space
     231            RefPtr<WebXRReferenceSpace> referenceSpace;
     232            if (type == XRReferenceSpaceType::BoundedFloor)
     233                referenceSpace = WebXRBoundedReferenceSpace::create(document, makeRef(*this), type);
     234            else
     235                referenceSpace = WebXRReferenceSpace::create(document, makeRef(*this), type);
     236
     237            // 2.5. Resolve promise with referenceSpace.
     238            promise.resolve(referenceSpace.releaseNonNull());
     239        });
    221240    });
    222 }
    223 
    224 void WebXRSession::animationTimerFired()
    225 {
    226     m_lastAnimationFrameTimestamp = MonotonicTime::now();
    227 
    228     if (m_callbacks.isEmpty())
    229         return;
    230 
    231     // TODO: retrieve frame from platform.
    232     auto frame = WebXRFrame::create(*this);
    233 
    234     m_runningCallbacks.swap(m_callbacks);
    235     for (auto& callback : m_runningCallbacks) {
    236         if (callback->isCancelled())
    237             continue;
    238         callback->handleEvent(m_lastAnimationFrameTimestamp.secondsSinceEpoch().milliseconds(), frame.get());
    239     }
    240 
    241     m_runningCallbacks.clear();
    242 }
    243 
    244 void WebXRSession::scheduleAnimation()
    245 {
    246     if (m_animationTimer.isActive())
    247         return;
    248 
    249     if (m_ended)
    250         return;
    251 
    252     // TODO: use device's refresh rate. Let's start with 60fps.
    253     Seconds animationInterval = 15_ms;
    254     Seconds scheduleDelay = std::max(animationInterval - (MonotonicTime::now() - m_lastAnimationFrameTimestamp), 0_s);
    255     m_animationTimer.startOneShot(scheduleDelay);
    256241}
    257242
     
    259244unsigned WebXRSession::requestAnimationFrame(Ref<XRFrameRequestCallback>&& callback)
    260245{
     246    // Ignore any new frame requests once the session is ended.
     247    if (m_ended)
     248        return 0;
     249
    261250    // 1. Let session be the target XRSession object.
    262251    // 2. Increment session's animation frame callback identifier by one.
     
    268257    m_callbacks.append(WTFMove(callback));
    269258
    270     scheduleAnimation();
     259    // Script can add multiple requestAnimationFrame callbacks but we should only request a device frame once.
     260    // When requestAnimationFrame is called during processing RAF callbacks the next requestFrame is scheduled
     261    // at the end of WebXRSession::onFrame() to prevent requesting a new frame before the current one has ended.
     262    if (m_callbacks.size() == 1)
     263        requestFrame();
    271264
    272265    // 4. Return session's animation frame callback identifier's current value.
     
    287280
    288281    if (position != notFound) {
    289         m_callbacks[position]->cancel();
    290         m_callbacks.remove(position);
     282        m_callbacks[position]->setFiredOrCancelled();
    291283        return;
    292284    }
    293 
    294     position = m_runningCallbacks.findMatching([callbackId] (auto& item) {
    295         return item->callbackId() == callbackId;
    296     });
    297 
    298     if (position != notFound)
    299         m_runningCallbacks[position]->cancel();
    300285}
    301286
     
    421406}
    422407
     408void WebXRSession::applyPendingRenderState()
     409{
     410    // https: //immersive-web.github.io/webxr/#apply-the-pending-render-state
     411    // 1. Let activeState be session’s active render state.
     412    // 2. Let newState be session’s pending render state.
     413    // 3. Set session’s pending render state to null.
     414    auto newState = m_pendingRenderState;
     415    ASSERT(newState);
     416
     417    // 4. Let oldBaseLayer be activeState’s baseLayer.
     418    // 5. Let oldLayers be activeState’s layers.
     419    // FIXME: those are only needed for step 6.2.
     420
     421    // 6.1 Set activeState to newState.
     422    m_activeRenderState = newState;
     423
     424    // 6.2 If oldBaseLayer is not equal to activeState’s baseLayer, oldLayers is not equal to activeState’s layers, or the dimensions of any of the layers have changed, update the viewports for session.
     425    // FIXME: implement this.
     426
     427    // 6.3 If activeState’s inlineVerticalFieldOfView is less than session’s minimum inline field of view set activeState’s inlineVerticalFieldOfView to session’s minimum inline field of view.
     428    if (m_activeRenderState->inlineVerticalFieldOfView() < m_minimumInlineFOV)
     429        m_activeRenderState->setInlineVerticalFieldOfView(m_minimumInlineFOV);
     430
     431    // 6.4 If activeState’s inlineVerticalFieldOfView is greater than session’s maximum inline field of view set activeState’s inlineVerticalFieldOfView to session’s maximum inline field of view.
     432    if (m_activeRenderState->inlineVerticalFieldOfView() > m_maximumInlineFOV)
     433        m_activeRenderState->setInlineVerticalFieldOfView(m_maximumInlineFOV);
     434
     435    // 6.5 If activeState’s depthNear is less than session’s minimum near clip plane set activeState’s depthNear to session’s minimum near clip plane.
     436    if (m_activeRenderState->depthNear() < m_minimumNearClipPlane)
     437        m_activeRenderState->setDepthNear(m_minimumNearClipPlane);
     438
     439    // 6.6 If activeState’s depthFar is greater than session’s maximum far clip plane set activeState’s depthFar to session’s maximum far clip plane.
     440    if (m_activeRenderState->depthFar() > m_maximumFarClipPlane)
     441        m_activeRenderState->setDepthFar(m_maximumFarClipPlane);
     442
     443    // 6.7 Let baseLayer be activeState’s baseLayer.
     444    auto baseLayer = m_activeRenderState->baseLayer();
     445
     446    // 6.8 Set activeState’s composition enabled and output canvas as follows:
     447    if (m_mode == XRSessionMode::Inline && is<WebXRWebGLLayer>(baseLayer) && !baseLayer->isCompositionEnabled()) {
     448        m_activeRenderState->setCompositionEnabled(false);
     449        m_activeRenderState->setOutputCanvas(baseLayer->canvas());
     450    } else {
     451        m_activeRenderState->setCompositionEnabled(true);
     452        m_activeRenderState->setOutputCanvas(nullptr);
     453    }
     454}
     455
     456// https://immersive-web.github.io/webxr/#should-be-rendered
     457bool WebXRSession::frameShouldBeRendered() const
     458{
     459    if (!m_activeRenderState->baseLayer())
     460        return false;
     461    if (m_mode == XRSessionMode::Inline && !m_activeRenderState->outputCanvas())
     462        return false;
     463    return true;
     464}
     465
     466void WebXRSession::requestFrame()
     467{
     468    m_device->requestFrame([this, protectedThis = makeRef(*this)](auto&& frameData) {
     469        onFrame(WTFMove(frameData));
     470    });
     471}
     472
     473void WebXRSession::onFrame(PlatformXR::Device::FrameData&& frameData)
     474{
     475    ASSERT(isMainThread());
     476
     477    if (m_ended)
     478        return;
     479
     480    // Queue a task to perform the following steps.
     481    queueTaskKeepingObjectAlive(*this, TaskSource::WebXR, [this, frameData = WTFMove(frameData)]() {
     482        if (m_ended)
     483            return;
     484        //  1.Let now be the current high resolution time.
     485        auto now = (MonotonicTime::now() - m_timeOrigin).milliseconds();
     486
     487        auto frame = WebXRFrame::create(makeRef(*this));
     488        //  2.Let frame be session’s animation frame.
     489        //  3.Set frame’s time to frameTime.
     490        frame->setTime(static_cast<DOMHighResTimeStamp>(frameData.predictedDisplayTime));
     491
     492        // 4. For each view in list of views, set view’s viewport modifiable flag to true.
     493        // 5. If the active flag of any view in the list of views has changed since the last XR animation frame, update the viewports.
     494        // FIXME: implement.
     495
     496        // FIXME: I moved step 7 before 6 because of https://github.com/immersive-web/webxr/issues/1164
     497        // 7.If session’s pending render state is not null, apply the pending render state.
     498        if (m_pendingRenderState)
     499            applyPendingRenderState();
     500
     501        // 6. If the frame should be rendered for session:
     502        if (frameShouldBeRendered()) {
     503            // 6.1.Set session’s list of currently running animation frame callbacks to be session’s list of animation frame callbacks.
     504            // 6.2.Set session’s list of animation frame callbacks to the empty list.
     505            auto callbacks = m_callbacks;
     506
     507            // 6.3.Set frame’s active boolean to true.
     508            frame->setActive(true);
     509
     510            // 6.4.Apply frame updates for frame.
     511            // FIXME: implement.
     512
     513            // 6.5.For each entry in session’s list of currently running animation frame callbacks, in order:
     514            for (auto& callback : callbacks) {
     515                //  6.6.If the entry’s cancelled boolean is true, continue to the next entry.
     516                if (callback->isFiredOrCancelled())
     517                    continue;
     518                callback->setFiredOrCancelled();
     519                //  6.7.Invoke the Web IDL callback function for entry, passing now and frame as the arguments
     520                callback->handleEvent(now, frame.get());
     521
     522                //  6.8.If an exception is thrown, report the exception.
     523            }
     524            // 6.9.Set session’s list of currently running animation frame callbacks to the empty list.
     525            m_callbacks.removeAllMatching([](auto& callback) {
     526                return callback->isFiredOrCancelled();
     527            });
     528
     529            // 6.10.Set frame’s active boolean to false.
     530            // If the session is ended, m_animationFrame->setActive false is set in shutdown().
     531            frame->setActive(false);
     532
     533            if (!m_callbacks.isEmpty())
     534                requestFrame();
     535        }
     536
     537    });
     538}
     539
    423540} // namespace WebCore
    424541
  • trunk/Source/WebCore/Modules/webxr/WebXRSession.h

    r271988 r272734  
    3131#include "EventTarget.h"
    3232#include "JSDOMPromiseDeferred.h"
    33 #include "Timer.h"
     33#include "WebXRFrame.h"
    3434#include "WebXRInputSourceArray.h"
    3535#include "WebXRRenderState.h"
     
    110110    void didCompleteShutdown();
    111111
    112     void animationTimerFired();
    113     void scheduleAnimation();
     112    bool referenceSpaceIsSupported(XRReferenceSpaceType) const;
    114113
    115     bool referenceSpaceIsSupported(XRReferenceSpaceType) const;
     114    bool frameShouldBeRendered() const;
     115    void requestFrame();
     116    void onFrame(PlatformXR::Device::FrameData&&);
     117    void applyPendingRenderState();
    116118
    117119    XREnvironmentBlendMode m_environmentBlendMode;
     
    127129    RefPtr<WebXRRenderState> m_activeRenderState;
    128130    RefPtr<WebXRRenderState> m_pendingRenderState;
     131    MonotonicTime m_timeOrigin;
    129132
    130133    unsigned m_nextCallbackId { 1 };
    131134    Vector<Ref<XRFrameRequestCallback>> m_callbacks;
    132     Vector<Ref<XRFrameRequestCallback>> m_runningCallbacks;
    133135
    134     Timer m_animationTimer;
    135     MonotonicTime m_lastAnimationFrameTimestamp;
     136    double m_minimumInlineFOV { 0.0 };
     137    double m_maximumInlineFOV { piFloat };
     138
     139    // In meters.
     140    double m_minimumNearClipPlane { 0.1 };
     141    double m_maximumFarClipPlane { 1000.0 };
    136142};
    137143
  • trunk/Source/WebCore/Modules/webxr/WebXRSystem.cpp

    r272492 r272734  
    3636#include "JSXRReferenceSpaceType.h"
    3737#include "PlatformXR.h"
     38#include "RequestAnimationFrameCallback.h"
    3839#include "RuntimeEnabledFeatures.h"
    3940#include "SecurityOrigin.h"
     
    5051WTF_MAKE_ISO_ALLOCATED_IMPL(WebXRSystem);
    5152
    52 WebXRSystem::DummyInlineDevice::DummyInlineDevice()
    53 {
    54     setEnabledFeatures(XRSessionMode::Inline, { XRReferenceSpaceType::Viewer });
    55 }
    56 
    5753Ref<WebXRSystem> WebXRSystem::create(ScriptExecutionContext& scriptExecutionContext)
    5854{
     
    6258WebXRSystem::WebXRSystem(ScriptExecutionContext& scriptExecutionContext)
    6359    : ActiveDOMObject(&scriptExecutionContext)
     60    , m_defaultInlineDevice(scriptExecutionContext)
    6461{
    6562    m_inlineXRDevice = makeWeakPtr(m_defaultInlineDevice);
     
    487484}
    488485
     486class InlineRequestAnimationFrameCallback final: public RequestAnimationFrameCallback {
     487public:
     488    static Ref<InlineRequestAnimationFrameCallback> create(ScriptExecutionContext& scriptExecutionContext, Function<void()>&& callback)
     489    {
     490        return adoptRef(*new InlineRequestAnimationFrameCallback(scriptExecutionContext, WTFMove(callback)));
     491    }
     492private:
     493    InlineRequestAnimationFrameCallback(ScriptExecutionContext& scriptExecutionContext, Function<void()>&& callback)
     494        : RequestAnimationFrameCallback(&scriptExecutionContext), m_callback(WTFMove(callback))
     495    {
     496    }
     497
     498    CallbackResult<void> handleEvent(double) final
     499    {
     500        m_callback();
     501        return { };
     502    }
     503
     504    Function<void()> m_callback;
     505};
     506
     507
     508WebXRSystem::DummyInlineDevice::DummyInlineDevice(ScriptExecutionContext& scriptExecutionContext)
     509    : ContextDestructionObserver(&scriptExecutionContext)
     510{
     511    setEnabledFeatures(XRSessionMode::Inline, { XRReferenceSpaceType::Viewer });
     512}
     513
     514void WebXRSystem::DummyInlineDevice::requestFrame(PlatformXR::Device::RequestFrameCallback&& callback)
     515{
     516    if (!scriptExecutionContext())
     517        return;
     518    // Inline XR sessions rely on document.requestAnimationFrame to perform the render loop.
     519    auto document = downcast<Document>(scriptExecutionContext());
     520    if (!document)
     521        return;
     522
     523    auto raf = InlineRequestAnimationFrameCallback::create(*m_scriptExecutionContext, [callback = WTFMove(callback)]() mutable {
     524        callback({ });
     525    });
     526
     527    document->requestAnimationFrame(raf);
     528}
     529
     530
    489531} // namespace WebCore
    490532
  • trunk/Source/WebCore/Modules/webxr/WebXRSystem.h

    r271806 r272734  
    105105
    106106    // https://immersive-web.github.io/webxr/#default-inline-xr-device
    107     class DummyInlineDevice final : public PlatformXR::Device {
     107    class DummyInlineDevice final : public PlatformXR::Device, private ContextDestructionObserver {
    108108    public:
    109         DummyInlineDevice();
     109        explicit DummyInlineDevice(ScriptExecutionContext&);
    110110
    111111    private:
    112112        void initializeTrackingAndRendering(PlatformXR::SessionMode) final { }
    113113        void shutDownTrackingAndRendering() final { }
     114        void initializeReferenceSpace(PlatformXR::ReferenceSpaceType) final { };
     115
     116        void requestFrame(PlatformXR::Device::RequestFrameCallback&&) final;
    114117    };
    115118    DummyInlineDevice m_defaultInlineDevice;
  • trunk/Source/WebCore/Modules/webxr/WebXRWebGLLayer.cpp

    r272571 r272734  
    2929#if ENABLE(WEBXR)
    3030
     31#include "HTMLCanvasElement.h"
    3132#include "IntSize.h"
     33#include "OffscreenCanvas.h"
    3234#include "WebGLFramebuffer.h"
    3335#include "WebGLRenderingContext.h"
     
    199201}
    200202
     203HTMLCanvasElement* WebXRWebGLLayer::canvas() const
     204{
     205    return WTF::switchOn(m_context, [](const RefPtr<WebGLRenderingContextBase>& baseContext) {
     206        auto canvas = baseContext->canvas();
     207        return WTF::switchOn(canvas, [](const RefPtr<HTMLCanvasElement>& canvas) {
     208            return canvas.get();
     209        }, [](const RefPtr<OffscreenCanvas>) {
     210            ASSERT_NOT_REACHED("baseLayer of a WebXRWebGLLayer must be an HTMLCanvasElement");
     211            return nullptr;
     212        });
     213    });
     214}
     215
    201216} // namespace WebCore
    202217
  • trunk/Source/WebCore/Modules/webxr/WebXRWebGLLayer.h

    r272571 r272734  
    3737namespace WebCore {
    3838
     39class HTMLCanvasElement;
    3940class IntSize;
    4041class WebGLFramebuffer;
     
    7576    const WebXRSession& session() { return m_session; }
    7677
     78    bool isCompositionEnabled() const { return m_isCompositionEnabled; }
     79
     80    HTMLCanvasElement* canvas() const;
     81
    7782private:
    7883    WebXRWebGLLayer(Ref<WebXRSession>&&, WebXRRenderingContext&&, const XRWebGLLayerInit&);
  • trunk/Source/WebCore/Modules/webxr/XRFrameRequestCallback.h

    r263256 r272734  
    4444    unsigned callbackId() { ASSERT(m_id); return m_id; }
    4545    void setCallbackId(unsigned id) { ASSERT(!m_id); m_id = id; }
    46     void cancel() { m_cancelled = true; }
    47     bool isCancelled() const { return m_cancelled; }
     46    void setFiredOrCancelled() { m_firedOrCancelled = true; }
     47    bool isFiredOrCancelled() const { return m_firedOrCancelled; }
    4848
    4949private:
    5050    unsigned m_id { 0 };
    51     bool m_cancelled { false };
     51    bool m_firedOrCancelled { false };
    5252};
    5353
  • trunk/Source/WebCore/platform/xr/PlatformXR.h

    r271988 r272734  
    1919#pragma once
    2020
     21#include "FloatPoint3D.h"
    2122#include "IntSize.h"
    2223#include <memory>
     
    7677    // when the platform has completed all steps to shut down the XR session.
    7778    virtual bool supportsSessionShutdownNotification() const { return false; }
     79    virtual void initializeReferenceSpace(ReferenceSpaceType) = 0;
     80
     81    struct FrameData {
     82        long predictedDisplayTime;
     83        struct ViewData {
     84            struct {
     85                WebCore::FloatPoint3D position;
     86                struct {
     87                    float x;
     88                    float y;
     89                    float z;
     90                    float w;
     91                } orientation;
     92            } pose;
     93            struct {
     94                float rUp;
     95                float rDown;
     96                float rLeft;
     97                float rRight;
     98            } fov;
     99        };
     100        Vector<ViewData> viewPoses;
     101    };
     102    using RequestFrameCallback = Function<void(FrameData&&)>;
     103    virtual void requestFrame(RequestFrameCallback&&) = 0;
    78104
    79105protected:
  • trunk/Source/WebCore/platform/xr/openxr/PlatformXROpenXR.cpp

    r271288 r272734  
    261261}
    262262
    263 OpenXRDevice::~OpenXRDevice()
    264 {
    265     shutDownTrackingAndRendering();
    266 }
    267 
    268263Device::ListOfEnabledFeatures OpenXRDevice::enumerateReferenceSpaces(XrSession& session) const
    269264{
     
    396391}
    397392
     393static bool isSessionActive(XrSessionState state)
     394{
     395    return state == XR_SESSION_STATE_VISIBLE || state == XR_SESSION_STATE_FOCUSED;
     396}
     397
     398static bool isSessionReady(XrSessionState state)
     399{
     400    return state >= XR_SESSION_STATE_READY  && state < XR_SESSION_STATE_STOPPING;
     401}
     402
    398403void OpenXRDevice::initializeTrackingAndRendering(SessionMode mode)
    399404{
     
    415420void OpenXRDevice::resetSession()
    416421{
     422    ASSERT(&RunLoop::current() == &m_queue.runLoop());
     423    if (m_session != XR_NULL_HANDLE) {
     424        xrDestroySession(m_session);
     425        m_session = XR_NULL_HANDLE;
     426    }
     427    m_sessionState = XR_SESSION_STATE_UNKNOWN;
     428}
     429
     430void OpenXRDevice::handleSessionStateChange()
     431{
     432    ASSERT(&RunLoop::current() == &m_queue.runLoop());
     433    if (m_sessionState == XR_SESSION_STATE_STOPPING) {
     434        // The application should exit the render loop and call xrEndSession
     435        endSession();
     436    } else if (m_sessionState == XR_SESSION_STATE_READY) {
     437        // The application is ready to call xrBeginSession.
     438        beginSession();
     439    }
     440}
     441
     442void OpenXRDevice::shutDownTrackingAndRendering()
     443{
    417444    m_queue.dispatch([this]() {
    418445        if (m_session == XR_NULL_HANDLE)
    419446            return;
    420         xrDestroySession(m_session);
    421         m_session = XR_NULL_HANDLE;
    422     });
    423 }
    424 
    425 void OpenXRDevice::shutDownTrackingAndRendering()
    426 {
     447
     448        // xrRequestExitSession() will transition the session to STOPPED state.
     449        // If the session was not running we have to reset the session ourselves.
     450        if (XR_FAILED(xrRequestExitSession(m_session))) {
     451            resetSession();
     452            return;
     453        }
     454
     455        // OpenXR needs to wait for the XR_SESSION_STATE_STOPPING state to properly end the session.
     456        waitUntilStopping();
     457    });
     458}
     459
     460void OpenXRDevice::waitUntilStopping()
     461{
     462    ASSERT(&RunLoop::current() == &m_queue.runLoop());
     463    pollEvents();
     464    if (m_sessionState >= XR_SESSION_STATE_STOPPING)
     465        return;
     466    m_queue.dispatch([this]() {
     467        waitUntilStopping();
     468    });
     469}
     470
     471void OpenXRDevice::pollEvents()
     472{
     473    ASSERT(!isMainThread());
     474    auto runtimeEvent = createStructure<XrEventDataBuffer, XR_TYPE_EVENT_DATA_BUFFER>();
     475    while (xrPollEvent(m_instance, &runtimeEvent) == XR_SUCCESS) {
     476        switch (runtimeEvent.type) {
     477        case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
     478            auto* event = (XrEventDataSessionStateChanged*)&runtimeEvent;
     479            m_sessionState = event->state;
     480            handleSessionStateChange();
     481            break;
     482        }
     483        case XR_TYPE_EVENT_DATA_EVENTS_LOST:
     484        case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
     485        case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
     486        case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
     487        case XR_TYPE_EVENT_DATA_MAIN_SESSION_VISIBILITY_CHANGED_EXTX:
     488        case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR:
     489        case XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT:
     490            break;
     491        default:
     492            ASSERT_NOT_REACHED("Unhandled event type %d\n", runtimeEvent.type);
     493        }
     494    }
     495}
     496
     497XrResult OpenXRDevice::beginSession()
     498{
     499    ASSERT(!isMainThread());
     500    ASSERT(m_sessionState == XR_SESSION_STATE_READY);
     501
     502    auto sessionBeginInfo = createStructure<XrSessionBeginInfo, XR_TYPE_SESSION_BEGIN_INFO>();
     503    sessionBeginInfo.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
     504    auto result = xrBeginSession(m_session, &sessionBeginInfo);
     505#if !LOG_DISABLED
     506    if (XR_FAILED(result))
     507        LOG(XR, "%s %s: %s\n", __func__, "xrBeginSession", resultToString(result, m_instance).utf8().data());
     508#endif
     509    return result;
     510}
     511
     512void OpenXRDevice::endSession()
     513{
     514    ASSERT(m_session != XR_NULL_HANDLE);
     515    xrEndSession(m_session);
    427516    resetSession();
     517    if (!m_trackingAndRenderingClient)
     518        return;
     519
     520    // Notify did end event
     521    callOnMainThread([this, weakThis = makeWeakPtr(*this)]() {
     522        if (!weakThis)
     523            return;
     524        if (m_trackingAndRenderingClient)
     525            m_trackingAndRenderingClient->sessionDidEnd();
     526    });
     527}
     528
     529
     530Device::FrameData::ViewData xrViewToViewData(XrView view)
     531{
     532    Device::FrameData::ViewData data;
     533    data.fov = { view.fov.angleUp, view.fov.angleDown, view.fov.angleLeft, view.fov.angleRight };
     534    data.pose.orientation = { view.pose.orientation.x, view.pose.orientation.y, view.pose.orientation.z, view.pose.orientation.w };
     535    data.pose.position = { view.pose.position.x, view.pose.position.y, view.pose.position.z };
     536    return data;
     537}
     538
     539void OpenXRDevice::requestFrame(RequestFrameCallback&& callback)
     540{
     541    m_queue.dispatch([this, callback = WTFMove(callback)]() mutable {
     542        pollEvents();
     543        if (!isSessionReady(m_sessionState)) {
     544            callOnMainThread([callback = WTFMove(callback)]() mutable {
     545                // Device not ready or stopping. Report frameData with invalid tracking.
     546                callback({ });
     547            });
     548            return;
     549        }
     550
     551        auto frameState = createStructure<XrFrameState, XR_TYPE_FRAME_STATE>();
     552        auto frameWaitInfo = createStructure<XrFrameWaitInfo, XR_TYPE_FRAME_WAIT_INFO>();
     553        auto result = xrWaitFrame(m_session, &frameWaitInfo, &frameState);
     554        RETURN_IF_FAILED(result, "xrWaitFrame", m_instance);
     555        XrTime predictedTime = frameState.predictedDisplayTime;
     556
     557        auto frameBeginInfo = createStructure<XrFrameBeginInfo, XR_TYPE_FRAME_BEGIN_INFO>();
     558        result = xrBeginFrame(m_session, &frameBeginInfo);
     559        RETURN_IF_FAILED(result, "xrBeginFrame", m_instance);
     560
     561        Device::FrameData frameData;
     562        frameData.predictedDisplayTime = frameState.predictedDisplayTime;
     563
     564        if (isSessionActive(m_sessionState)) {
     565            ASSERT(m_configurationViews.contains(m_currentViewConfigurationType));
     566            const auto& configurationView = m_configurationViews.get(m_currentViewConfigurationType);
     567
     568            auto viewLocateInfo = createStructure<XrViewLocateInfo, XR_TYPE_VIEW_LOCATE_INFO>();
     569            viewLocateInfo.displayTime = predictedTime;
     570            // FIXME: use the current reference space.
     571            // viewLocateInfo.space = m_localSpace;
     572
     573            uint32_t viewCount = configurationView.size();
     574            Vector<XrView> views(viewCount, [] {
     575                XrView object;
     576                std::memset(&object, 0, sizeof(XrView));
     577                object.type = XR_TYPE_VIEW;
     578                return object;
     579            }());
     580
     581            auto viewState = createStructure<XrViewState, XR_TYPE_VIEW_STATE>();
     582            uint32_t viewCountOutput;
     583            result = xrLocateViews(m_session, &viewLocateInfo, &viewState, viewCount, &viewCountOutput, views.data());
     584            if (!XR_FAILED(result)) {
     585                for (auto& view : views)
     586                    frameData.viewPoses.append(xrViewToViewData(view));
     587            }
     588        }
     589
     590        callOnMainThread([frameData = WTFMove(frameData), callback = WTFMove(callback)]() mutable {
     591            callback(WTFMove(frameData));
     592        });
     593
     594        auto frameEndInfo = createStructure<XrFrameEndInfo, XR_TYPE_FRAME_END_INFO>();
     595        frameEndInfo.displayTime = predictedTime;
     596        frameEndInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
     597        frameEndInfo.layerCount = 0;
     598        result = xrEndFrame(m_session, &frameEndInfo);
     599        RETURN_IF_FAILED(result, "xrEndFrame", m_instance);
     600    });
    428601}
    429602
  • trunk/Source/WebCore/platform/xr/openxr/PlatformXROpenXR.h

    r271288 r272734  
    4545public:
    4646    OpenXRDevice(XrSystemId, XrInstance, WorkQueue&, CompletionHandler<void()>&&);
    47     ~OpenXRDevice();
    4847    XrSystemId xrSystemId() const { return m_systemId; }
    4948
     
    5352
    5453    ListOfEnabledFeatures enumerateReferenceSpaces(XrSession&) const;
     54    void initializeReferenceSpace(ReferenceSpaceType) final { };
    5555
    5656    WebCore::IntSize recommendedResolution(SessionMode) final;
     
    5858    void initializeTrackingAndRendering(SessionMode) final;
    5959    void shutDownTrackingAndRendering() final;
     60    bool supportsSessionShutdownNotification() const final { return true; }
     61    void waitUntilStopping();
    6062
     63    void pollEvents();
     64    XrResult beginSession();
     65    void endSession();
    6166    void resetSession();
     67    void handleSessionStateChange();
     68
     69    void requestFrame(RequestFrameCallback&&) final;
    6270
    6371    using ViewConfigurationPropertiesMap = HashMap<XrViewConfigurationType, XrViewConfigurationProperties, IntHash<XrViewConfigurationType>, WTF::StrongEnumHashTraits<XrViewConfigurationType>>;
     
    6977    XrInstance m_instance;
    7078    XrSession m_session { XR_NULL_HANDLE };
     79    XrSessionState m_sessionState { XR_SESSION_STATE_UNKNOWN };
    7180
    7281    WorkQueue& m_queue;
  • trunk/Source/WebCore/testing/WebFakeXRDevice.cpp

    r271988 r272734  
    3232#include "JSDOMPromiseDeferred.h"
    3333#include "WebFakeXRInputController.h"
     34#include <wtf/CompletionHandler.h>
    3435
    3536namespace WebCore {
    3637
     38static constexpr Seconds FakeXRFrameTime = 15_ms;
     39
    3740void FakeXRView::setFieldOfView(FakeXRViewInit::FieldOfViewInit fov)
    3841{
    3942    m_fov = fov;
     43}
     44
     45SimulatedXRDevice::SimulatedXRDevice()
     46    : m_frameTimer(*this, &SimulatedXRDevice::frameTimerFired)
     47{
     48    m_supportsOrientationTracking = true;
     49}
     50
     51SimulatedXRDevice::~SimulatedXRDevice()
     52{
     53    stopTimer();
    4054}
    4155
     
    5064    if (m_supportsShutdownNotification)
    5165        simulateShutdownCompleted();
     66    stopTimer();
     67}
     68
     69void SimulatedXRDevice::stopTimer()
     70{
     71    if (m_frameTimer.isActive())
     72        m_frameTimer.stop();
     73}
     74
     75void SimulatedXRDevice::frameTimerFired()
     76{
     77    auto callbacks = WTFMove(m_callbacks);
     78    for (auto& callback : callbacks)
     79        callback({ });
     80}
     81
     82void SimulatedXRDevice::requestFrame(RequestFrameCallback&& callback)
     83{
     84    m_callbacks.append(WTFMove(callback));
     85    if (!m_frameTimer.isActive())
     86        m_frameTimer.startOneShot(FakeXRFrameTime);
    5287}
    5388
  • trunk/Source/WebCore/testing/WebFakeXRDevice.h

    r271988 r272734  
    6464    WTF_MAKE_FAST_ALLOCATED;
    6565public:
    66     SimulatedXRDevice() { m_supportsOrientationTracking = true; }
     66    SimulatedXRDevice();
     67    ~SimulatedXRDevice();
    6768    void setNativeBoundsGeometry(Vector<FakeXRBoundsPoint> geometry) { m_nativeBoundsGeometry = geometry; }
    6869    void setViewerOrigin(RefPtr<WebXRRigidTransform>&& origin) { m_viewerOrigin = WTFMove(origin); }
     
    7677    void shutDownTrackingAndRendering() final;
    7778    bool supportsSessionShutdownNotification() const final { return m_supportsShutdownNotification; }
     79    void initializeReferenceSpace(PlatformXR::ReferenceSpaceType) final { }
     80    void requestFrame(RequestFrameCallback&&) final;
     81
     82    void stopTimer();
     83    void frameTimerFired();
     84
    7885    Optional<Vector<FakeXRBoundsPoint>> m_nativeBoundsGeometry;
    7986    RefPtr<WebXRRigidTransform> m_viewerOrigin;
     
    8289    Vector<Ref<FakeXRView>> m_views;
    8390    bool m_supportsShutdownNotification { false };
     91    Timer m_frameTimer;
     92    Vector<RequestFrameCallback> m_callbacks;
    8493};
    8594
Note: See TracChangeset for help on using the changeset viewer.