blob: 3bc033addcdc7f0dea76a86114c771568d3ddc82 [file] [log] [blame]
// 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 UI_GL_DELEGATED_INK_POINT_RENDERER_GPU_H_
#define UI_GL_DELEGATED_INK_POINT_RENDERER_GPU_H_
#include <dcomp.h>
#include <wrl/client.h>
#include <algorithm>
#include <iterator>
#include <memory>
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/gfx/delegated_ink_metadata.h"
#include "ui/gfx/mojom/delegated_ink_point_renderer.mojom.h"
// Required for SFINAE to check if these Win10 types exist.
// TODO(1171374): Remove this when the types are available in the Win10 SDK.
struct IDCompositionInkTrailDevice;
struct IDCompositionDelegatedInkTrail;
struct DCompositionInkTrailPoint;
namespace gl {
namespace {
// Maximum number of pointer ids that will be considered for drawing. Number is
// chosen arbitrarily, as it seems unlikely that the total number of input
// sources using delegated ink trails would exceed 10, but it can be raised if
// the need arises.
constexpr uint64_t kMaximumNumberOfPointerIds = 10;
struct DelegatedInkPointCompare {
bool operator()(const gfx::DelegatedInkPoint& lhs,
const gfx::DelegatedInkPoint& rhs) const {
return lhs.timestamp() < rhs.timestamp();
}
};
using DelegatedInkPointTokenMap = base::flat_map<gfx::DelegatedInkPoint,
absl::optional<unsigned int>,
DelegatedInkPointCompare>;
} // namespace
// TODO(1171374): Remove this class and remove templates when the types are
// available in the Win10 SDK.
template <typename InkTrailDevice,
typename DelegatedInkTrail,
typename InkTrailPoint,
typename = void,
typename = void,
typename = void>
class DelegatedInkPointRendererGpu {
public:
DelegatedInkPointRendererGpu() = default;
bool Initialize(
const Microsoft::WRL::ComPtr<IDCompositionDevice2>& dcomp_device,
const Microsoft::WRL::ComPtr<IDXGISwapChain1>& root_swap_chain) {
NOTREACHED();
return false;
}
bool HasBeenInitialized() const { return false; }
IDCompositionVisual* GetInkVisual() const {
NOTREACHED();
return nullptr;
}
bool DelegatedInkIsSupported(
const Microsoft::WRL::ComPtr<IDCompositionDevice2>& dcomp_device) const {
return false;
}
void SetDelegatedInkTrailStartPoint(
std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
NOTREACHED();
}
void InitMessagePipeline(
mojo::PendingReceiver<gfx::mojom::DelegatedInkPointRenderer> receiver) {
NOTREACHED();
}
void StoreDelegatedInkPoint(const gfx::DelegatedInkPoint& point) {
NOTREACHED();
}
gfx::DelegatedInkMetadata* MetadataForTesting() const {
NOTREACHED();
return nullptr;
}
uint64_t DelegatedInkPointPointerIdCountForTesting() const {
NOTREACHED();
return 0u;
}
DelegatedInkPointTokenMap DelegatedInkPointsForTesting(
int32_t pointer_id) const {
NOTREACHED();
return DelegatedInkPointTokenMap();
}
uint64_t InkTrailTokenCountForTesting() const {
NOTREACHED();
return 0u;
}
bool WaitForNewTrailToDrawForTesting() const {
NOTREACHED();
return false;
}
bool CheckForPointerIdForTesting(int32_t pointer_id) const {
NOTREACHED();
return false;
}
uint64_t GetMaximumNumberOfPointerIdsForTesting() const {
NOTREACHED();
return kMaximumNumberOfPointerIds;
}
void SetNeedsDcompPropertiesUpdate() const { NOTREACHED(); }
};
// This class will call the OS delegated ink trail APIs when they become
// available. Currently using SFINAE to land changes ahead of the SDK, so that
// the OS APIs can be used immediately.
// TODO(1171374): Remove the template SFINAE.
//
// On construction, this class will create a new visual for the visual tree with
// an IDCompositionDelegatedInk object as the contents. This will be added as
// a child of the root surface visual in the tree, and the trail will be drawn
// to it. It is a child of the root surface visual because this visual contains
// the swapchain, and there will be no transforms applied to the delegated ink
// visual this way.
// For more information about the design of this class and using the OS APIs,
// view the design doc here: https://aka.ms/GPUBackedDesignDoc
template <typename InkTrailDevice,
typename DelegatedInkTrail,
typename InkTrailPoint>
class DelegatedInkPointRendererGpu<InkTrailDevice,
DelegatedInkTrail,
InkTrailPoint,
decltype(typeid(InkTrailDevice), void()),
decltype(typeid(DelegatedInkTrail), void()),
decltype(typeid(InkTrailPoint), void())>
: public gfx::mojom::DelegatedInkPointRenderer {
public:
DelegatedInkPointRendererGpu() = default;
void InitMessagePipeline(
mojo::PendingReceiver<gfx::mojom::DelegatedInkPointRenderer>
pending_receiver) {
// The remote end of this pipeline exists on a per-tab basis, so if tab A
// is using the feature and then tab B starts trying to use it, a new
// PendingReceiver will arrive here while |receiver_| is still bound to the
// remote in tab A. Because of this possibility, we unconditionally reset
// |receiver_| before trying to bind anything here. This is fine to do
// because we only ever need to actively receive points from one tab as only
// one tab can be in the foreground and actively inking at a time.
receiver_.reset();
receiver_.Bind(std::move(pending_receiver));
}
bool Initialize(
const Microsoft::WRL::ComPtr<IDCompositionDevice2>& dcomp_device2,
const Microsoft::WRL::ComPtr<IDXGISwapChain1>& root_swap_chain) {
if (dcomp_device_ == dcomp_device2.Get() &&
swap_chain_ == root_swap_chain.Get() && HasBeenInitialized()) {
return true;
}
dcomp_device_ = dcomp_device2.Get();
swap_chain_ = root_swap_chain.Get();
Microsoft::WRL::ComPtr<InkTrailDevice> ink_trail_device;
TraceEventOnFailure(dcomp_device2.As(&ink_trail_device),
"DelegatedInkPointRendererGpu::Initialize - "
"DCompDevice2 as InkTrailDevice failed");
if (!ink_trail_device)
return false;
TraceEventOnFailure(
ink_trail_device->CreateDelegatedInkTrailForSwapChain(
root_swap_chain.Get(), &delegated_ink_trail_),
"DelegatedInkPointRendererGpu::Initialize - Failed to create "
"delegated ink trail.");
if (!delegated_ink_trail_)
return false;
Microsoft::WRL::ComPtr<IDCompositionDevice> dcomp_device;
TraceEventOnFailure(dcomp_device2.As(&dcomp_device),
"DelegatedInkPointRendererGpu::Initialize - "
"DCompDevice2 as DCompDevice failed");
if (!dcomp_device)
return false;
TraceEventOnFailure(dcomp_device->CreateVisual(&ink_visual_),
"DelegatedInkPointRendererGpu::Initialize - "
"Failed to create ink visual.");
if (!ink_visual_)
return false;
if (TraceEventOnFailure(
ink_visual_->SetContent(delegated_ink_trail_.Get()),
"DelegatedInkPointRendererGpu::Initialize - SetContent failed")) {
// Initialization has failed because SetContent failed. However, we must
// reset the members so that HasBeenInitialized() does not return true
// when queried.
ink_visual_.Reset();
delegated_ink_trail_.Reset();
return false;
}
return true;
}
bool HasBeenInitialized() const {
return ink_visual_ && delegated_ink_trail_;
}
IDCompositionVisual* GetInkVisual() const { return ink_visual_.Get(); }
bool DelegatedInkIsSupported(
const Microsoft::WRL::ComPtr<IDCompositionDevice2>& dcomp_device) const {
Microsoft::WRL::ComPtr<InkTrailDevice> ink_trail_device;
HRESULT hr = dcomp_device.As(&ink_trail_device);
return hr == S_OK;
}
void SetDelegatedInkTrailStartPoint(
std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
TRACE_EVENT_WITH_FLOW1(
"delegated_ink_trails",
"DelegatedInkPointRendererGpu::SetDelegatedInkTrailStartPoint",
TRACE_ID_GLOBAL(metadata_->trace_id()), TRACE_EVENT_FLAG_FLOW_IN,
"metadata", metadata_->ToString());
DCHECK(ink_visual_);
DCHECK(delegated_ink_trail_);
// If certain conditions are met, we can simply erase the trail up to the
// point matching |metadata|, instead of starting a new trail. These
// conditions include:
// 1. A trail has already been started, meaning that |metadata_| and
// |pointer_id_| exist.
// 2. The current |metadata_| and |metadata| both have the same color, so
// the color of the trail does not need to change.
// 3. |needs_dcomp_properties_update_| isn't forcing an update of the DCOMP
// resources: |ink_visual_|, |delegated_ink_trail_|. This happens after
// a swap chain recreation.
// (continued below)
if (!needs_dcomp_properties_update_ && metadata_ &&
metadata->color() == metadata_->color() && pointer_id_) {
DelegatedInkPointTokenMap& token_map =
delegated_ink_points_[pointer_id_.value()];
auto point_matching_metadata_it = token_map.find(
gfx::DelegatedInkPoint(metadata->point(), metadata->timestamp()));
// (continued from above)
// 4. We have a DelegatedInkPoint with a timestamp equal to or greater
// than |metadata|'s timestamp in the token map associated with
// |pointer_id_|.
// (continued below)
if (point_matching_metadata_it != token_map.end()) {
gfx::DelegatedInkPoint point_matching_metadata =
point_matching_metadata_it->first;
absl::optional<unsigned int> token = point_matching_metadata_it->second;
// (continued from above)
// 5. The DelegatedInkPoint retrieved above matches |metadata| - this
// means that the timestamp is an exact match and the point is
// acceptably close.
// 6. The token associated with this DelegatedInkPoint is valid,
// meaning that the point has previously been drawn as part of a
// trail.
//
// If all of these conditions are met, then we can attempt to just
// remove points from the trail instead of starting a new trail
// altogether.
if (point_matching_metadata.MatchesDelegatedInkMetadata(
metadata.get()) &&
token) {
bool remove_trail_points_failed = TraceEventOnFailure(
delegated_ink_trail_->RemoveTrailPoints(token.value()),
"DelegatedInkPointRendererGpu::SetDelegatedInkTrailStartPoint - "
"Failed to remove trail points.");
if (!remove_trail_points_failed &&
UpdateVisualClip(metadata->presentation_area(), false)) {
// Remove all points up to and including the point that matches
// |metadata|. No need to hold on to the point that matches metadata
// because we've already added it to AddTrailPoints previously, and
// the next valid |metadata| is guaranteed to be after it.
token_map.erase(token_map.begin(),
std::next(point_matching_metadata_it));
metadata_ = std::move(metadata);
return;
}
}
}
}
if (!UpdateVisualClip(metadata->presentation_area(),
needs_dcomp_properties_update_))
return;
D2D1_COLOR_F d2d1_color;
const float kMaxRGBAValue = 255.f;
d2d1_color.a = SkColorGetA(metadata->color()) / kMaxRGBAValue;
d2d1_color.r = SkColorGetR(metadata->color()) / kMaxRGBAValue;
d2d1_color.g = SkColorGetG(metadata->color()) / kMaxRGBAValue;
d2d1_color.b = SkColorGetB(metadata->color()) / kMaxRGBAValue;
if (TraceEventOnFailure(
delegated_ink_trail_->StartNewTrail(d2d1_color),
"DelegatedInkPointRendererGpu::SetDelegatedInkTrailStartPoint - "
"Failed to start a new trail.")) {
return;
}
wait_for_new_trail_to_draw_ = false;
metadata_ = std::move(metadata);
DrawSavedTrailPoints();
needs_dcomp_properties_update_ = false;
}
void StoreDelegatedInkPoint(const gfx::DelegatedInkPoint& point) override {
TRACE_EVENT_WITH_FLOW1(
"delegated_ink_trails",
"DelegatedInkPointRendererGpu::StoreDelegatedInkPoint",
TRACE_ID_GLOBAL(point.trace_id()),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "point",
point.ToString());
const int32_t pointer_id = point.pointer_id();
// TODO(1238497): Understand why we are being sent points from browser
// process that break this assertion so frequently and prevent it from
// happening.
// DCHECK(delegated_ink_points_.find(pointer_id) ==
// delegated_ink_points_.end() ||
// point.timestamp() >
// delegated_ink_points_[pointer_id].rbegin()->
// first.timestamp());
if (metadata_ && point.timestamp() < metadata_->timestamp())
return;
DelegatedInkPointTokenMap& token_map = delegated_ink_points_[pointer_id];
// Always save the point so that it can be drawn later. This allows points
// that arrive before the first metadata makes it here to be drawn after
// StartNewTrail is called. If we get to the maximum number of points that
// we will store, start erasing the oldest ones first. This matches what the
// OS compositor does internally when it hits the max number of points.
if (token_map.size() == gfx::kMaximumNumberOfDelegatedInkPoints)
token_map.erase(token_map.begin());
token_map.insert({point, absl::nullopt});
EraseExcessPointerIds();
if (pointer_id_ && pointer_id_.value() == pointer_id) {
DrawDelegatedInkPoint(point);
} else if (!pointer_id_ && metadata_) {
DrawSavedTrailPoints();
}
}
void ResetPrediction() override {
// Don't reset |metadata_| here so that RemoveTrailPoints() can continue
// to be called as the final metadata(s) arrive.
// TODO(1052145): Start predicting points and reset it here.
wait_for_new_trail_to_draw_ = true;
}
gfx::DelegatedInkMetadata* MetadataForTesting() const {
return metadata_.get();
}
uint64_t InkTrailTokenCountForTesting() const {
DCHECK_EQ(delegated_ink_points_.size(), 1u);
uint64_t valid_tokens = 0u;
for (const auto& it : delegated_ink_points_.begin()->second) {
if (it.second)
valid_tokens++;
}
return valid_tokens;
}
uint64_t DelegatedInkPointPointerIdCountForTesting() const {
return delegated_ink_points_.size();
}
bool CheckForPointerIdForTesting(int32_t pointer_id) const {
return delegated_ink_points_.find(pointer_id) !=
delegated_ink_points_.end();
}
const DelegatedInkPointTokenMap& DelegatedInkPointsForTesting(
int32_t pointer_id) {
DCHECK(delegated_ink_points_.find(pointer_id) !=
delegated_ink_points_.end());
return delegated_ink_points_[pointer_id];
}
bool WaitForNewTrailToDrawForTesting() const {
return wait_for_new_trail_to_draw_;
}
uint64_t GetMaximumNumberOfPointerIdsForTesting() const {
return kMaximumNumberOfPointerIds;
}
void SetNeedsDcompPropertiesUpdate() {
// This should be set from an external event that invalidates our DCOMP
// resources: |ink_visual_|, |delegated_ink_trail_|. This will be checked in
// the next call to |SetDelegatedInkTrailStartPoint| - the entry point for
// using these resources to render a new trail. That code optimizes based on
// the consideration that properties persist after being set.
needs_dcomp_properties_update_ = true;
}
private:
// Note that this returns true if the HRESULT is anything other than S_OK,
// meaning that it returns true when an event is traced (because of a
// failure).
static bool TraceEventOnFailure(HRESULT hr, const char* name) {
if (SUCCEEDED(hr))
return false;
TRACE_EVENT_INSTANT1("delegated_ink_trails", name, TRACE_EVENT_SCOPE_THREAD,
"hr", hr);
return true;
}
bool UpdateVisualClip(const gfx::RectF& new_presentation_area,
bool force_update) {
if (!force_update && metadata_ &&
metadata_->presentation_area() == new_presentation_area)
return true;
// If (0,0) of a visual is clipped out, it can result in delegated ink not
// being drawn at all. This is more common when DComp Surfaces are enabled,
// but doesn't negatively impact things when the swapchain is used, so just
// offset the visual instead of clipping the top left corner in all cases.
ink_visual_->SetOffsetX(new_presentation_area.x());
ink_visual_->SetOffsetY(new_presentation_area.y());
D2D_RECT_F clip_rect;
clip_rect.bottom = new_presentation_area.bottom();
clip_rect.right = new_presentation_area.right();
// If setting the clip or committing failed, we want to bail early so that a
// trail can't incorrectly appear over things on the user's screen.
return SUCCEEDED(ink_visual_->SetClip(clip_rect)) &&
SUCCEEDED(dcomp_device_->Commit());
}
void EraseExcessPointerIds() {
auto token_map_it = delegated_ink_points_.begin();
while (token_map_it != delegated_ink_points_.end()) {
const DelegatedInkPointTokenMap& token_map = token_map_it->second;
if (token_map.empty())
token_map_it = delegated_ink_points_.erase(token_map_it);
else
token_map_it++;
}
// In order to get to the maximum pointer id limit, sometimes one token map
// will need to be removed even if it has a valid DelegatedInkPoint. In this
// case, we'll remove the token map that has gone the longest without
// receiving a new point - similar to a least recently used eviction policy.
// This would also mean that the token map that would result in the smallest
// latency improvement if it were drawn is being removed. The only exception
// to this is if the above heuristic would result in the token map that has
// a pointer id matching |pointer_id_| being removed. Since |pointer_id_|
// matches the trail that is currently being drawn to the screen, we prefer
// to save it so we can try to continue that trail. In this case, just
// remove the token map with the oldest new point that is not currently
// being drawn.
if (delegated_ink_points_.size() > kMaximumNumberOfPointerIds) {
std::vector<std::pair<base::TimeTicks, int32_t>>
earliest_time_and_pointer_id;
for (const auto& it : delegated_ink_points_) {
earliest_time_and_pointer_id.emplace_back(
it.second.rbegin()->first.timestamp(), it.first);
}
std::sort(earliest_time_and_pointer_id.begin(),
earliest_time_and_pointer_id.end());
uint64_t pointer_id_to_remove = 0;
while (pointer_id_to_remove < earliest_time_and_pointer_id.size() &&
pointer_id_ &&
earliest_time_and_pointer_id[pointer_id_to_remove].second ==
pointer_id_.value()) {
pointer_id_to_remove++;
}
CHECK_LT(pointer_id_to_remove, earliest_time_and_pointer_id.size());
delegated_ink_points_.erase(
earliest_time_and_pointer_id[pointer_id_to_remove].second);
}
DCHECK_LE(delegated_ink_points_.size(), kMaximumNumberOfPointerIds);
}
absl::optional<int32_t> GetPointerIdForMetadata() {
if (pointer_id_ && delegated_ink_points_.find(pointer_id_.value()) !=
delegated_ink_points_.end()) {
// Since we remove all DelegatedInkPoints with timestamp before
// |metadata_|'s before calling GetPointerIdForMetadata(), we know we can
// just grab the first DelegatedInkPoint matching |pointer_id_|.
const gfx::DelegatedInkPoint& point_matching_metadata =
delegated_ink_points_[pointer_id_.value()].begin()->first;
if (point_matching_metadata.MatchesDelegatedInkMetadata(
metadata_.get())) {
return pointer_id_;
}
}
pointer_id_ = absl::nullopt;
for (auto token_map_it = delegated_ink_points_.begin();
token_map_it != delegated_ink_points_.end() && !pointer_id_;
++token_map_it) {
int32_t potential_pointer_id = token_map_it->first;
const DelegatedInkPointTokenMap& token_map = token_map_it->second;
DCHECK(!token_map.empty());
if (token_map.begin()->first.MatchesDelegatedInkMetadata(
metadata_.get())) {
DCHECK(!pointer_id_);
pointer_id_ = potential_pointer_id;
}
}
return pointer_id_;
}
void DrawSavedTrailPoints() {
DCHECK(metadata_);
TRACE_EVENT0("delegated_ink_trails", "DrawSavedTrailPoints");
// Remove all points that have a timestamp earlier than |metadata_|'s, since
// we know that we won't need to draw them. This is subtly different than
// the erasing done in SetDelegatedInkTrailStartPoint() - there the point
// matching the metadata is removed, here it is not. The reason for this
// difference is because there we know that it matches |metadata_| and
// therefore it cannot possibly be drawn again, so it is safe to remove.
// Here however, we are erasing all points up to, but not including, the
// first point with a timestamp equal to or greater than |metadata_|'s so
// that we can then check that point to confirm that it matches |metadata_|
// before deciding to draw an ink trail. The point could then be erased
// after it is successfully checked against |metadata_| and drawn, it just
// isn't for simplicity's sake.
for (auto& it : delegated_ink_points_) {
DelegatedInkPointTokenMap& token_map = it.second;
token_map.erase(token_map.begin(),
token_map.lower_bound(gfx::DelegatedInkPoint(
metadata_->point(), metadata_->timestamp())));
}
EraseExcessPointerIds();
absl::optional<unsigned int> pointer_id = GetPointerIdForMetadata();
// Now, the very first point must match |metadata_|, and as long as it does
// we can continue to draw everything else. If at any point something can't
// or fails to draw though, don't attempt to draw anything after it so that
// the trail can match the user's actual stroke.
if (pointer_id && delegated_ink_points_.find(pointer_id.value()) !=
delegated_ink_points_.end()) {
DelegatedInkPointTokenMap& token_map =
delegated_ink_points_[pointer_id.value()];
if (!token_map.empty() &&
token_map.begin()->first.MatchesDelegatedInkMetadata(
metadata_.get())) {
for (const auto& it : token_map) {
if (!DrawDelegatedInkPoint(it.first))
break;
}
}
} else {
TRACE_EVENT_INSTANT0("delegated_ink_trails",
"DrawSavedTrailPoints failed - no pointer id",
TRACE_EVENT_SCOPE_THREAD);
}
}
bool DrawDelegatedInkPoint(const gfx::DelegatedInkPoint& point) {
// Always wait for a new trail to be started before attempting to draw
// anything, even if |metadata_| exists.
if (wait_for_new_trail_to_draw_)
return false;
DCHECK(metadata_);
if (!metadata_->presentation_area().Contains(point.point()))
return false;
DCHECK(delegated_ink_trail_);
InkTrailPoint ink_point;
ink_point.radius = metadata_->diameter() / 2.f;
// In order to account for the visual offset, the point must be offset in
// the opposite direction.
ink_point.x = point.point().x() - metadata_->presentation_area().x();
ink_point.y = point.point().y() - metadata_->presentation_area().y();
unsigned int token;
// AddTrailPoints() can accept and draw more than one InkTrailPoint per
// call. However, all the points get lumped together in the one token then,
// which means that they can only be removed all together. This may be fine
// in some scenarios, but in the vast majority of cases we will need to
// remove one point at a time, so we choose to only add one InkTrailPoint at
// a time.
if (TraceEventOnFailure(
delegated_ink_trail_->AddTrailPoints(&ink_point,
/*inkPointsCount*/ 1, &token),
"DelegatedInkPointRendererGpu::DrawDelegatedInkPoint - Failed to "
"add trail point")) {
// TODO(1052145): Start predicting points.
return false;
}
TRACE_EVENT_WITH_FLOW1(
"delegated_ink_trails",
"DelegatedInkPointRendererGpu::DrawDelegatedInkPoint "
"- Point added to trail",
TRACE_ID_GLOBAL(point.trace_id()), TRACE_EVENT_FLAG_FLOW_IN, "point",
point.ToString());
delegated_ink_points_[point.pointer_id()][point] = token;
return true;
}
// The visual within the tree that will contain the delegated ink trail. It
// should be a child of the root surface visual.
Microsoft::WRL::ComPtr<IDCompositionVisual> ink_visual_;
// The delegated ink trail object that the ink trail is drawn on. This is the
// content of the ink visual.
Microsoft::WRL::ComPtr<DelegatedInkTrail> delegated_ink_trail_;
// Remember the dcomp device and swap chain used to create
// |delegated_ink_trail_| and |ink_visual_| so that we can avoid recreating
// them when it isn't necessary.
raw_ptr<IDCompositionDevice2> dcomp_device_ = nullptr;
raw_ptr<IDXGISwapChain1> swap_chain_ = nullptr;
// The most recent metadata received. The metadata marks the last point of
// the app rendered stroke, which corresponds to the first point of the
// delegated ink trail that will be drawn.
std::unique_ptr<gfx::DelegatedInkMetadata> metadata_;
// A base::flat_map of all the points that have arrived in
// StoreDelegatedInkPoint() with a timestamp greater than or equal to that of
// |metadata_|, where the key is the DelegatedInkPoint that was received, and
// the value is an optional token. The value will be null until the
// DelegatedInkPoint is added to the trail, at which point the value is the
// token that was returned by AddTrailPoints. The elements of the flat_map are
// sorted by the timestamp of the DelegatedInkPoint. All points are stored in
// here until we receive a |metadata_|, then any DelegatedInkPoints that have
// a timestamp earlier than |metadata_|'s are removed, and any new
// DelegatedInkPoints that may arrive with an earlier timestamp are ignored.
// Then as each new |metadata| arrives in SetDelegatedInkTrailStartPoint(),
// we remove any old elements with earlier timestamps, up to and including the
// element that matches the DelegatedInkMetadata.
base::flat_map<int32_t, DelegatedInkPointTokenMap> delegated_ink_points_;
// Cached pointer id of the most recently drawn trail.
absl::optional<int32_t> pointer_id_;
// Flag to know if new DelegatedInkPoints that arrive should be drawn
// immediately or if they should wait for a new trail to be started. Set to
// true when ResetPrediction is called, as this tells us that something about
// the pointerevents in the browser process changed. When that happens, we
// should continue to remove points for any metadata that arrive with
// timestamps that match DelegatedInkPoints that arrived before the
// ResetPrediction call. However, once a metadata arrives with a timestamp
// after the ResetPrediction, then we know that inking should continue, so a
// new trail is started and we try to draw everything in
// |delegated_ink_points_|.
bool wait_for_new_trail_to_draw_ = true;
// When the visual tree was updated, all properties we've set on DCOMP are
// outdated, and need to be re-set (i.e. from |ink_visual_| and
// |delegated_ink_trail_|). This is done at |SetDelegatedInkTrailStartPoint|.
bool needs_dcomp_properties_update_ = false;
mojo::Receiver<gfx::mojom::DelegatedInkPointRenderer> receiver_{this};
};
} // namespace gl
#endif // UI_GL_DELEGATED_INK_POINT_RENDERER_GPU_H_