blob: a8a521f777439850ad9f7a57b50d90c80a2093c7 [file] [log] [blame]
// Copyright 2021 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.
#include "ui/gl/delegated_ink_point_renderer_gpu.h"
#include <memory>
#include "base/run_loop.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/win/hidden_window.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/gl/dc_layer_tree.h"
#include "ui/gl/direct_composition_surface_win.h"
#include "ui/gl/gl_angle_util_win.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/init/gl_factory.h"
namespace gl {
namespace {
class DelegatedInkPointRendererGpuTest : public testing::Test {
public:
DelegatedInkPointRendererGpuTest() : parent_window_(ui::GetHiddenWindow()) {}
DirectCompositionSurfaceWin* surface() { return surface_.get(); }
DCLayerTree* layer_tree() { return surface()->GetLayerTreeForTesting(); }
DCLayerTree::DelegatedInkRenderer* ink_renderer() {
return layer_tree()->GetInkRendererForTesting();
}
void SendDelegatedInkPointBasedOnPrevious() {
const base::circular_deque<gfx::DelegatedInkPoint>& ink_points =
ink_renderer()->DelegatedInkPointsForTesting();
DCHECK(!ink_points.empty());
auto last_point = ink_points.back();
ink_renderer()->StoreDelegatedInkPoint(gfx::DelegatedInkPoint(
last_point.point() + gfx::Vector2dF(5, 5),
last_point.timestamp() + base::TimeDelta::FromMicroseconds(10),
last_point.pointer_id()));
}
void SendMetadata(const gfx::DelegatedInkMetadata& metadata) {
surface()->SetDelegatedInkTrailStartPoint(
std::make_unique<gfx::DelegatedInkMetadata>(metadata));
}
gfx::DelegatedInkMetadata SendMetadataBasedOnStoredPoint(uint64_t point) {
const base::circular_deque<gfx::DelegatedInkPoint>& ink_points =
ink_renderer()->DelegatedInkPointsForTesting();
EXPECT_GE(ink_points.size(), point);
auto ink_point = ink_points[point];
gfx::DelegatedInkMetadata metadata(
ink_point.point(), /*diameter=*/3, SK_ColorBLACK, ink_point.timestamp(),
gfx::RectF(0, 0, 100, 100), /*hovering=*/false);
SendMetadata(metadata);
return metadata;
}
void StoredMetadataMatchesSentMetadata(
const gfx::DelegatedInkMetadata& sent_metadata) {
gfx::DelegatedInkMetadata* renderer_metadata =
ink_renderer()->MetadataForTesting();
EXPECT_TRUE(renderer_metadata);
EXPECT_EQ(renderer_metadata->point(), sent_metadata.point());
EXPECT_EQ(renderer_metadata->diameter(), sent_metadata.diameter());
EXPECT_EQ(renderer_metadata->color(), sent_metadata.color());
EXPECT_EQ(renderer_metadata->timestamp(), sent_metadata.timestamp());
EXPECT_EQ(renderer_metadata->presentation_area(),
sent_metadata.presentation_area());
EXPECT_EQ(renderer_metadata->is_hovering(), sent_metadata.is_hovering());
}
protected:
void SetUp() override {
// Without this, the following check always fails.
gl::init::InitializeGLNoExtensionsOneOff(/*init_bindings=*/true);
if (!QueryDirectCompositionDevice(QueryD3D11DeviceObjectFromANGLE())) {
LOG(WARNING)
<< "GL implementation not using DirectComposition, skipping test.";
return;
}
CreateDirectCompositionSurfaceWin();
if (!surface_->SupportsDelegatedInk()) {
LOG(WARNING) << "Delegated ink unsupported, skipping test.";
return;
}
CreateGLContext();
surface_->SetEnableDCLayers(true);
// Create the swap chain
constexpr gfx::Size window_size(100, 100);
EXPECT_TRUE(surface_->Resize(window_size, 1.0, gfx::ColorSpace(), true));
EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(window_size)));
}
void TearDown() override {
context_ = nullptr;
if (surface_)
DestroySurface(std::move(surface_));
gl::init::ShutdownGL(false);
}
private:
void CreateDirectCompositionSurfaceWin() {
DirectCompositionSurfaceWin::Settings settings;
surface_ = base::MakeRefCounted<DirectCompositionSurfaceWin>(
parent_window_, DirectCompositionSurfaceWin::VSyncCallback(), settings);
EXPECT_TRUE(surface_->Initialize(GLSurfaceFormat()));
// ImageTransportSurfaceDelegate::DidCreateAcceleratedSurfaceChildWindow()
// is called in production code here. However, to remove dependency from
// gpu/ipc/service/image_transport_surface_delegate.h, here we directly
// executes the required minimum code.
if (parent_window_)
::SetParent(surface_->window(), parent_window_);
}
void CreateGLContext() {
context_ =
gl::init::CreateGLContext(nullptr, surface_.get(), GLContextAttribs());
EXPECT_TRUE(context_->MakeCurrent(surface_.get()));
}
void DestroySurface(scoped_refptr<DirectCompositionSurfaceWin> surface) {
scoped_refptr<base::TaskRunner> task_runner =
surface->GetWindowTaskRunnerForTesting();
DCHECK(surface->HasOneRef());
surface = nullptr;
base::RunLoop run_loop;
task_runner->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
HWND parent_window_;
scoped_refptr<DirectCompositionSurfaceWin> surface_;
scoped_refptr<GLContext> context_;
};
// Test to confirm that points and tokens are stored and removed correctly based
// on when the metadata and points arrive.
TEST_F(DelegatedInkPointRendererGpuTest, StoreAndRemovePointsAndTokens) {
if (!surface() || !surface()->SupportsDelegatedInk())
return;
// Send some points and make sure they are all stored even with no metadata.
ink_renderer()->StoreDelegatedInkPoint(
gfx::DelegatedInkPoint(gfx::PointF(10, 10), base::TimeTicks::Now(), 1));
const uint64_t kPointsToStore = 5u;
for (uint64_t i = 1; i < kPointsToStore; ++i)
SendDelegatedInkPointBasedOnPrevious();
EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(),
kPointsToStore);
EXPECT_TRUE(ink_renderer()->InkTrailTokensForTesting().empty());
EXPECT_FALSE(ink_renderer()->MetadataForTesting());
EXPECT_TRUE(ink_renderer()->WaitForNewTrailToDrawForTesting());
// Now send metadata that matches the first stored point. This should result
// in all of the points being drawn and matching tokens stored. None of the
// points should be removed from the circular deque because they are stored
// until a metadata arrives with a later timestamp
gfx::DelegatedInkMetadata metadata = SendMetadataBasedOnStoredPoint(0);
EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(),
kPointsToStore);
EXPECT_EQ(ink_renderer()->InkTrailTokensForTesting().size(), kPointsToStore);
EXPECT_FALSE(ink_renderer()->WaitForNewTrailToDrawForTesting());
StoredMetadataMatchesSentMetadata(metadata);
// Now send a metadata that matches a later one of the points. It should
// result in some of the stored points being erased, and one more token erased
// than points erased. This is because we don't need to store the token of the
// point that exactly matches the metadata.
const uint64_t kPointToSend = 3u;
metadata = SendMetadataBasedOnStoredPoint(kPointToSend);
EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(),
kPointsToStore - kPointToSend);
// Subtract one extra because the token for the point that matches the new
// metadata is erased too.
EXPECT_EQ(ink_renderer()->InkTrailTokensForTesting().size(),
kPointsToStore - kPointToSend - 1);
StoredMetadataMatchesSentMetadata(metadata);
// Now send a metadata after all of the stored point to make sure that it
// results in all the tokens and stored points being erased because a new
// trail is started.
gfx::DelegatedInkPoint last_point =
ink_renderer()->DelegatedInkPointsForTesting().back();
metadata = gfx::DelegatedInkMetadata(
last_point.point() + gfx::Vector2dF(2, 2), /*diameter=*/3, SK_ColorBLACK,
last_point.timestamp() + base::TimeDelta::FromMicroseconds(20),
gfx::RectF(0, 0, 100, 100), /*hovering=*/false);
SendMetadata(metadata);
EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(), 0u);
EXPECT_EQ(ink_renderer()->InkTrailTokensForTesting().size(), 0u);
StoredMetadataMatchesSentMetadata(metadata);
}
// Basic test to confirm that points are drawn as they arrive if they are in the
// presentation area and after the metadata's timestamp.
TEST_F(DelegatedInkPointRendererGpuTest, DrawPointsAsTheyArrive) {
if (!surface() || !surface()->SupportsDelegatedInk())
return;
gfx::DelegatedInkMetadata metadata(
gfx::PointF(12, 12), /*diameter=*/3, SK_ColorBLACK,
base::TimeTicks::Now(), gfx::RectF(10, 10, 90, 90), /*hovering=*/false);
SendMetadata(metadata);
// Send some points that should all be drawn to ensure that they are all drawn
// as they arrive.
const uint64_t kPointsToSend = 5u;
for (uint64_t i = 1u; i <= kPointsToSend; ++i) {
if (i == 1) {
ink_renderer()->StoreDelegatedInkPoint(gfx::DelegatedInkPoint(
metadata.point(), metadata.timestamp(), /*pointer_id=*/1));
} else {
SendDelegatedInkPointBasedOnPrevious();
}
EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(), i);
EXPECT_EQ(ink_renderer()->InkTrailTokensForTesting().size(), i);
}
// Now send a point that is outside of the presentation area to ensure that
// it is not drawn. It will still be stored - this is so if a future metadata
// arrives with a presentation area that would contain this point, it can
// still be drawn.
gfx::DelegatedInkPoint last_point =
ink_renderer()->DelegatedInkPointsForTesting().back();
gfx::DelegatedInkPoint outside_point(
gfx::PointF(5, 5),
last_point.timestamp() + base::TimeDelta::FromMicroseconds(10),
/*pointer_id=*/1);
EXPECT_FALSE(metadata.presentation_area().Contains((outside_point.point())));
ink_renderer()->StoreDelegatedInkPoint(outside_point);
const uint64_t kTotalPointsSent = kPointsToSend + 1u;
EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(),
kTotalPointsSent);
EXPECT_EQ(ink_renderer()->InkTrailTokensForTesting().size(), kPointsToSend);
// Then send a metadata with a larger presentation area and timestamp earlier
// than the above point to confirm it will be the only point drawn, but all
// the points with later timestamps will be stored.
const uint64_t kMetadataToSend = 3u;
SendMetadataBasedOnStoredPoint(kMetadataToSend);
EXPECT_EQ(ink_renderer()->DelegatedInkPointsForTesting().size(),
kTotalPointsSent - kMetadataToSend);
EXPECT_EQ(ink_renderer()->InkTrailTokensForTesting().size(), 1u);
}
} // namespace
} // namespace gl