Commit 31063c9e authored by Piotr Bialecki's avatar Piotr Bialecki Committed by Commit Bot

WebXR Anchors - update implementation to match the spec draft

Latest spec: https://immersive-web.github.io/anchors/

- remove last changed time from anchor object
- anchor space is not nullable on anchor object now
- anchor objects are not created in zombie state anymore - when anchor
  creation promise resolves, it should receive a fully built object
- XRAnchor got simplified due to the above
- XRFrameProvider needs to make sure that JS promises can run between
  presentation frame update and rAF callbacks so that the app has a chance
  to see newly created anchors before they are delivered to it via
  XRFrame.trackedAnchors

Changes: 
Change-Id: If582552907274d472097f7d7b119bb7ee216736e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2085701
Commit-Queue: Piotr Bialecki <bialpio@chromium.org>
Reviewed-by: default avatarKlaus Weidner <klausw@chromium.org>
Cr-Commit-Position: refs/heads/master@{#747901}
parent 89ac6365
...@@ -10,26 +10,19 @@ ...@@ -10,26 +10,19 @@
namespace blink { namespace blink {
XRAnchor::XRAnchor(uint64_t id, XRSession* session)
: id_(id), session_(session), anchor_data_(base::nullopt) {}
XRAnchor::XRAnchor(uint64_t id, XRAnchor::XRAnchor(uint64_t id,
XRSession* session, XRSession* session,
const device::mojom::blink::XRAnchorDataPtr& anchor_data, const device::mojom::blink::XRAnchorDataPtr& anchor_data)
double timestamp)
: id_(id), : id_(id),
session_(session), session_(session),
anchor_data_(base::in_place, anchor_data, timestamp) {} mojo_from_anchor_(std::make_unique<TransformationMatrix>(
mojo::ConvertTo<blink::TransformationMatrix>(anchor_data->pose))) {}
void XRAnchor::Update(const device::mojom::blink::XRAnchorDataPtr& anchor_data,
double timestamp) { void XRAnchor::Update(
if (!anchor_data_) { const device::mojom::blink::XRAnchorDataPtr& anchor_data) {
anchor_data_ = AnchorData(anchor_data, timestamp); // Just copy, already allocated.
} else { *mojo_from_anchor_ =
*anchor_data_->pose_matrix_ = mojo::ConvertTo<blink::TransformationMatrix>(anchor_data->pose);
mojo::ConvertTo<blink::TransformationMatrix>(anchor_data->pose);
anchor_data_->last_changed_time_ = timestamp;
}
} }
uint64_t XRAnchor::id() const { uint64_t XRAnchor::id() const {
...@@ -37,9 +30,7 @@ uint64_t XRAnchor::id() const { ...@@ -37,9 +30,7 @@ uint64_t XRAnchor::id() const {
} }
XRSpace* XRAnchor::anchorSpace() const { XRSpace* XRAnchor::anchorSpace() const {
if (!anchor_data_) { DCHECK(mojo_from_anchor_);
return nullptr;
}
if (!anchor_space_) { if (!anchor_space_) {
anchor_space_ = anchor_space_ =
...@@ -50,25 +41,9 @@ XRSpace* XRAnchor::anchorSpace() const { ...@@ -50,25 +41,9 @@ XRSpace* XRAnchor::anchorSpace() const {
} }
TransformationMatrix XRAnchor::MojoFromObject() const { TransformationMatrix XRAnchor::MojoFromObject() const {
if (anchor_data_) { DCHECK(mojo_from_anchor_);
return *anchor_data_->pose_matrix_;
}
// |poseMatrix()| shouldn't be called by anyone except XRObjectSpace and if return *mojo_from_anchor_;
// XRObjectSpace already exists for this anchor, then anchor_data_ should also
// exist for this anchor.
NOTREACHED();
return {};
}
double XRAnchor::lastChangedTime(bool& is_null) const {
if (!anchor_data_) {
is_null = true;
return 0;
}
is_null = false;
return anchor_data_->last_changed_time_;
} }
void XRAnchor::detach() { void XRAnchor::detach() {
...@@ -81,11 +56,4 @@ void XRAnchor::Trace(Visitor* visitor) { ...@@ -81,11 +56,4 @@ void XRAnchor::Trace(Visitor* visitor) {
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
} }
XRAnchor::AnchorData::AnchorData(
const device::mojom::blink::XRAnchorDataPtr& anchor_data,
double timestamp)
: pose_matrix_(std::make_unique<TransformationMatrix>(
mojo::ConvertTo<blink::TransformationMatrix>(anchor_data->pose))),
last_changed_time_(timestamp) {}
} // namespace blink } // namespace blink
...@@ -21,12 +21,9 @@ class XRAnchor : public ScriptWrappable { ...@@ -21,12 +21,9 @@ class XRAnchor : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
public: public:
XRAnchor(uint64_t id, XRSession* session);
XRAnchor(uint64_t id, XRAnchor(uint64_t id,
XRSession* session, XRSession* session,
const device::mojom::blink::XRAnchorDataPtr& anchor_data, const device::mojom::blink::XRAnchorDataPtr& anchor_data);
double timestamp);
uint64_t id() const; uint64_t id() const;
...@@ -34,33 +31,24 @@ class XRAnchor : public ScriptWrappable { ...@@ -34,33 +31,24 @@ class XRAnchor : public ScriptWrappable {
TransformationMatrix MojoFromObject() const; TransformationMatrix MojoFromObject() const;
double lastChangedTime(bool& is_null) const;
void detach(); void detach();
void Update(const device::mojom::blink::XRAnchorDataPtr& anchor_data, void Update(const device::mojom::blink::XRAnchorDataPtr& anchor_data);
double timestamp);
void Trace(Visitor* visitor) override; void Trace(Visitor* visitor) override;
private: private:
// AnchorData will only be present in an XRAnchor after the anchor was updated
// for the first time (CreateAnchor returns a promise that will resolve to an
// XRAnchor prior to first update of the anchor).
struct AnchorData {
// Anchor's pose in device (mojo) space.
std::unique_ptr<TransformationMatrix> pose_matrix_;
double last_changed_time_;
AnchorData(const device::mojom::blink::XRAnchorDataPtr& anchor_data,
double timestamp);
};
const uint64_t id_; const uint64_t id_;
Member<XRSession> session_; Member<XRSession> session_;
base::Optional<AnchorData> anchor_data_; // |mojo_from_anchor_| will be non-null in an XRAnchor after the anchor was
// updated for the first time - this *must* happen in the same frame in which
// the anchor was created for the anchor to be fully usable. It is currently
// ensured by XRSession - anchors that got created prior to receiving the
// result from mojo call to GetFrameData are not returned to the application
// until their poses are known.
std::unique_ptr<TransformationMatrix> mojo_from_anchor_;
// Cached anchor space - it will be created by `anchorSpace()` if it's not // Cached anchor space - it will be created by `anchorSpace()` if it's not
// set. // set.
......
...@@ -8,8 +8,7 @@ ...@@ -8,8 +8,7 @@
RuntimeEnabled=WebXRIncubations RuntimeEnabled=WebXRIncubations
] ]
interface XRAnchor { interface XRAnchor {
readonly attribute XRSpace? anchorSpace; readonly attribute XRSpace anchorSpace;
readonly attribute DOMHighResTimeStamp? lastChangedTime;
void detach(); void detach();
}; };
...@@ -370,6 +370,11 @@ void XRFrameProvider::ProcessScheduledFrame( ...@@ -370,6 +370,11 @@ void XRFrameProvider::ProcessScheduledFrame(
TRACE_EVENT2("gpu", "XRFrameProvider::ProcessScheduledFrame", "frame", TRACE_EVENT2("gpu", "XRFrameProvider::ProcessScheduledFrame", "frame",
frame_id_, "timestamp", high_res_now_ms); frame_id_, "timestamp", high_res_now_ms);
LocalFrame* frame = xr_->GetFrame();
if (!frame) {
return;
}
if (!xr_->IsFrameFocused() && !immersive_session_) { if (!xr_->IsFrameFocused() && !immersive_session_) {
return; // Not currently focused, so we won't expose poses (except to return; // Not currently focused, so we won't expose poses (except to
// immersive sessions). // immersive sessions).
...@@ -429,7 +434,14 @@ void XRFrameProvider::ProcessScheduledFrame( ...@@ -429,7 +434,14 @@ void XRFrameProvider::ProcessScheduledFrame(
immersive_session_->UpdateStageParameters(frame_data->stage_parameters); immersive_session_->UpdateStageParameters(frame_data->stage_parameters);
} }
immersive_session_->OnFrame(high_res_now_ms, buffer_mailbox_holder_); // Run immersive_session_->OnFrame() in a posted task to ensure that
// createAnchor promises get a chance to run - the presentation frame state
// is already updated.
frame->GetTaskRunner(blink::TaskType::kInternalMedia)
->PostTask(FROM_HERE,
WTF::Bind(&XRSession::OnFrame,
WrapWeakPersistent(immersive_session_.Get()),
high_res_now_ms, buffer_mailbox_holder_));
} else { } else {
// In the process of fulfilling the frame requests for each session they are // In the process of fulfilling the frame requests for each session they are
// extremely likely to request another frame. Work off of a separate list // extremely likely to request another frame. Work off of a separate list
...@@ -470,7 +482,13 @@ void XRFrameProvider::ProcessScheduledFrame( ...@@ -470,7 +482,13 @@ void XRFrameProvider::ProcessScheduledFrame(
if (session->ended()) if (session->ended())
continue; continue;
session->OnFrame(high_res_now_ms, base::nullopt); // Run session->OnFrame() in a posted task to ensure that createAnchor
// promises get a chance to run - the presentation frame state is already
// updated.
frame->GetTaskRunner(blink::TaskType::kInternalMedia)
->PostTask(FROM_HERE,
WTF::Bind(&XRSession::OnFrame, WrapWeakPersistent(session),
high_res_now_ms, base::nullopt));
} }
} }
} }
......
...@@ -842,9 +842,9 @@ void XRSession::OnCreateAnchorResult(ScriptPromiseResolver* resolver, ...@@ -842,9 +842,9 @@ void XRSession::OnCreateAnchorResult(ScriptPromiseResolver* resolver,
create_anchor_promises_.erase(resolver); create_anchor_promises_.erase(resolver);
if (result == device::mojom::CreateAnchorResult::SUCCESS) { if (result == device::mojom::CreateAnchorResult::SUCCESS) {
XRAnchor* anchor = MakeGarbageCollected<XRAnchor>(id, this); // Anchor was created successfully on the device. Subsequent frame update
anchor_ids_to_anchors_.insert(id, anchor); // must contain newly created anchor data.
resolver->Resolve(anchor); newly_created_anchor_ids_to_resolvers_.insert(id, resolver);
} else { } else {
resolver->Reject(); resolver->Reject();
} }
...@@ -910,25 +910,36 @@ void XRSession::ProcessAnchorsData( ...@@ -910,25 +910,36 @@ void XRSession::ProcessAnchorsData(
HeapHashMap<uint64_t, Member<XRAnchor>> updated_anchors; HeapHashMap<uint64_t, Member<XRAnchor>> updated_anchors;
// First, process all planes that had their information updated (new planes // First, process all anchors that had their information updated (new anchors
// are also processed here). // are also processed here).
for (const auto& anchor : tracked_anchors_data->updated_anchors_data) { for (const auto& anchor : tracked_anchors_data->updated_anchors_data) {
auto it = anchor_ids_to_anchors_.find(anchor->id); auto it = anchor_ids_to_anchors_.find(anchor->id);
if (it != anchor_ids_to_anchors_.end()) { if (it != anchor_ids_to_anchors_.end()) {
updated_anchors.insert(anchor->id, it->value); updated_anchors.insert(anchor->id, it->value);
it->value->Update(anchor, timestamp); it->value->Update(anchor);
} else { } else {
updated_anchors.insert( auto resolver_it =
anchor->id, newly_created_anchor_ids_to_resolvers_.find(anchor->id);
MakeGarbageCollected<XRAnchor>(anchor->id, this, anchor, timestamp)); if (resolver_it == newly_created_anchor_ids_to_resolvers_.end()) {
DCHECK(false)
<< "Newly created anchor must have a corresponding resolver!";
continue;
}
XRAnchor* xr_anchor =
MakeGarbageCollected<XRAnchor>(anchor->id, this, anchor);
resolver_it->value->Resolve(xr_anchor);
newly_created_anchor_ids_to_resolvers_.erase(resolver_it);
updated_anchors.insert(anchor->id, xr_anchor);
} }
} }
// Then, copy over the planes that were not updated but are still present. // Then, copy over the anchors that were not updated but are still present.
for (const auto& anchor_id : tracked_anchors_data->all_anchors_ids) { for (const auto& anchor_id : tracked_anchors_data->all_anchors_ids) {
auto it_updated = updated_anchors.find(anchor_id); auto it_updated = updated_anchors.find(anchor_id);
// If the plane was already updated, there is nothing to do as it was // If the anchor was already updated, there is nothing to do as it was
// already moved to |updated_anchors|. Otherwise just copy it over as-is. // already moved to |updated_anchors|. Otherwise just copy it over as-is.
if (it_updated == updated_anchors.end()) { if (it_updated == updated_anchors.end()) {
auto it = anchor_ids_to_anchors_.find(anchor_id); auto it = anchor_ids_to_anchors_.find(anchor_id);
...@@ -938,6 +949,11 @@ void XRSession::ProcessAnchorsData( ...@@ -938,6 +949,11 @@ void XRSession::ProcessAnchorsData(
} }
anchor_ids_to_anchors_.swap(updated_anchors); anchor_ids_to_anchors_.swap(updated_anchors);
DCHECK(newly_created_anchor_ids_to_resolvers_.IsEmpty())
<< "All newly created anchors should be updated in subsequent frame, got "
<< newly_created_anchor_ids_to_resolvers_.size()
<< " anchors that have not been updated";
} }
void XRSession::CleanUpUnusedHitTestSources() { void XRSession::CleanUpUnusedHitTestSources() {
...@@ -1834,6 +1850,7 @@ void XRSession::Trace(Visitor* visitor) { ...@@ -1834,6 +1850,7 @@ void XRSession::Trace(Visitor* visitor) {
visitor->Trace(request_hit_test_source_promises_); visitor->Trace(request_hit_test_source_promises_);
visitor->Trace(reference_spaces_); visitor->Trace(reference_spaces_);
visitor->Trace(anchor_ids_to_anchors_); visitor->Trace(anchor_ids_to_anchors_);
visitor->Trace(newly_created_anchor_ids_to_resolvers_);
visitor->Trace(prev_base_layer_); visitor->Trace(prev_base_layer_);
visitor->Trace(hit_test_source_ids_to_hit_test_sources_); visitor->Trace(hit_test_source_ids_to_hit_test_sources_);
visitor->Trace(hit_test_source_ids_to_transient_input_hit_test_sources_); visitor->Trace(hit_test_source_ids_to_transient_input_hit_test_sources_);
......
...@@ -396,6 +396,17 @@ class XRSession final ...@@ -396,6 +396,17 @@ class XRSession final
bool is_tracked_anchors_null_ = true; bool is_tracked_anchors_null_ = true;
HeapHashMap<uint64_t, Member<XRAnchor>> anchor_ids_to_anchors_; HeapHashMap<uint64_t, Member<XRAnchor>> anchor_ids_to_anchors_;
// Set of promises returned from CreateAnchor that are still in-flight to the
// device. Once the device calls us back with the newly created anchor id, the
// resolver will be moved to |newly_created_anchor_ids_to_resolvers_|.
HeapHashSet<Member<ScriptPromiseResolver>> create_anchor_promises_;
// Promises for which anchors have already been created on the device side but
// have not yet been resolved as their data is not yet available to blink.
// Next frame update should contain the necessary data - the promise will be
// resolved then.
HeapHashMap<uint64_t, Member<ScriptPromiseResolver>>
newly_created_anchor_ids_to_resolvers_;
// Mapping of hit test source ids (aka hit test subscription ids) to hit test // 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 // 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 // will communicate that it's no longer interested in the subscription by
...@@ -415,8 +426,6 @@ class XRSession final ...@@ -415,8 +426,6 @@ class XRSession final
Member<Element> overlay_element_; Member<Element> overlay_element_;
Member<XRDOMOverlayState> dom_overlay_state_; Member<XRDOMOverlayState> dom_overlay_state_;
bool environment_error_handler_subscribed_ = false; bool environment_error_handler_subscribed_ = false;
// Set of promises returned from CreateAnchor that are still in-flight.
HeapHashSet<Member<ScriptPromiseResolver>> create_anchor_promises_;
// Set of promises returned from requestHitTestSource and // Set of promises returned from requestHitTestSource and
// requestHitTestSourceForTransientInput that are still in-flight. // requestHitTestSourceForTransientInput that are still in-flight.
HeapHashSet<Member<ScriptPromiseResolver>> request_hit_test_source_promises_; HeapHashSet<Member<ScriptPromiseResolver>> request_hit_test_source_promises_;
......
...@@ -11168,7 +11168,6 @@ interface XPathResult ...@@ -11168,7 +11168,6 @@ interface XPathResult
interface XRAnchor interface XRAnchor
attribute @@toStringTag attribute @@toStringTag
getter anchorSpace getter anchorSpace
getter lastChangedTime
method constructor method constructor
method detach method detach
interface XRAnchorSet interface XRAnchorSet
......
...@@ -431,10 +431,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -431,10 +431,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
}); });
tracked_anchors.forEach(anchor => { tracked_anchors.forEach(anchor => {
if (t == anchor.lastChangedTime) { anchor.context.sceneObject.matrix = frame.getPose(anchor.anchorSpace, xrRefSpace).transform.matrix;
anchor.context.sceneObject.matrix = frame.getPose(anchor.anchorSpace, xrRefSpace).transform.matrix; anchor.context.sceneObject.visible = true;
anchor.context.sceneObject.visible = true;
}
}); });
all_previous_anchors = tracked_anchors; all_previous_anchors = tracked_anchors;
......
...@@ -526,10 +526,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -526,10 +526,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
}); });
tracked_anchors.forEach(anchor => { tracked_anchors.forEach(anchor => {
if(t == anchor.lastChangedTime) { anchor.context.sceneObject.matrix = frame.getPose(anchor.anchorSpace, xrRefSpace).transform.matrix;
anchor.context.sceneObject.matrix = frame.getPose(anchor.anchorSpace, xrRefSpace).transform.matrix; anchor.context.sceneObject.visible = true;
anchor.context.sceneObject.visible = true;
}
}); });
all_previous_anchors = tracked_anchors; all_previous_anchors = tracked_anchors;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment