1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/ozone/platform/drm/gpu/drm_window.h"
6
7 #include <drm_fourcc.h>
8 #include <stdint.h>
9 #include <xf86drm.h>
10 #include <memory>
11 #include <utility>
12 #include <vector>
13
14 #include "base/bind.h"
15 #include "base/files/platform_file.h"
16 #include "base/macros.h"
17 #include "base/test/task_environment.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "third_party/skia/include/core/SkCanvas.h"
20 #include "third_party/skia/include/core/SkColor.h"
21 #include "third_party/skia/include/core/SkImageInfo.h"
22 #include "third_party/skia/include/core/SkSurface.h"
23 #include "ui/gfx/gpu_fence.h"
24 #include "ui/gfx/linux/gbm_buffer.h"
25 #include "ui/gfx/linux/test/mock_gbm_device.h"
26 #include "ui/gfx/presentation_feedback.h"
27 #include "ui/ozone/platform/drm/gpu/drm_device_generator.h"
28 #include "ui/ozone/platform/drm/gpu/drm_device_manager.h"
29 #include "ui/ozone/platform/drm/gpu/drm_framebuffer.h"
30 #include "ui/ozone/platform/drm/gpu/hardware_display_controller.h"
31 #include "ui/ozone/platform/drm/gpu/mock_drm_device.h"
32 #include "ui/ozone/platform/drm/gpu/screen_manager.h"
33 #include "ui/ozone/public/surface_ozone_canvas.h"
34
35 namespace {
36
37 // Mode of size 6x4.
38 const drmModeModeInfo kDefaultMode = {0, 6, 0, 0, 0, 0, 4, 0,
39 0, 0, 0, 0, 0, 0, {'\0'}};
40
41 const gfx::AcceleratedWidget kDefaultWidgetHandle = 1;
42 const uint32_t kDefaultCrtc = 1;
43 const uint32_t kDefaultConnector = 2;
44 const int kDefaultCursorSize = 64;
45
GetCursorBuffers(const scoped_refptr<ui::MockDrmDevice> drm)46 std::vector<sk_sp<SkSurface>> GetCursorBuffers(
47 const scoped_refptr<ui::MockDrmDevice> drm) {
48 std::vector<sk_sp<SkSurface>> cursor_buffers;
49 for (const auto& cursor_buffer : drm->buffers()) {
50 if (cursor_buffer && cursor_buffer->width() == kDefaultCursorSize &&
51 cursor_buffer->height() == kDefaultCursorSize) {
52 cursor_buffers.push_back(cursor_buffer);
53 }
54 }
55
56 return cursor_buffers;
57 }
58
AllocateBitmap(const gfx::Size & size)59 SkBitmap AllocateBitmap(const gfx::Size& size) {
60 SkBitmap image;
61 SkImageInfo info = SkImageInfo::Make(size.width(), size.height(),
62 kN32_SkColorType, kPremul_SkAlphaType);
63 image.allocPixels(info);
64 image.eraseColor(SK_ColorWHITE);
65 return image;
66 }
67
68 } // namespace
69
70 class DrmWindowTest : public testing::Test {
71 public:
72 DrmWindowTest() = default;
73
74 void SetUp() override;
75 void TearDown() override;
76
OnSubmission(gfx::SwapResult result,std::unique_ptr<gfx::GpuFence> out_fence)77 void OnSubmission(gfx::SwapResult result,
78 std::unique_ptr<gfx::GpuFence> out_fence) {
79 last_swap_buffers_result_ = result;
80 }
81
OnPresentation(const gfx::PresentationFeedback & feedback)82 void OnPresentation(const gfx::PresentationFeedback& feedback) {
83 on_swap_buffers_count_++;
84 last_presentation_feedback_ = feedback;
85 }
86
87 protected:
88 void InitializeDrmState(ui::MockDrmDevice* drm, bool is_atomic = true);
89
90 base::test::SingleThreadTaskEnvironment task_environment_{
91 base::test::SingleThreadTaskEnvironment::MainThreadType::UI};
92 scoped_refptr<ui::MockDrmDevice> drm_;
93 std::unique_ptr<ui::ScreenManager> screen_manager_;
94 std::unique_ptr<ui::DrmDeviceManager> drm_device_manager_;
95
96 int on_swap_buffers_count_;
97 gfx::SwapResult last_swap_buffers_result_;
98 gfx::PresentationFeedback last_presentation_feedback_;
99
100 private:
101 struct PlaneState {
102 std::vector<uint32_t> formats;
103 };
104
105 struct CrtcState {
106 std::vector<PlaneState> planes;
107 };
108
109 DISALLOW_COPY_AND_ASSIGN(DrmWindowTest);
110 };
111
SetUp()112 void DrmWindowTest::SetUp() {
113 on_swap_buffers_count_ = 0;
114 last_swap_buffers_result_ = gfx::SwapResult::SWAP_FAILED;
115
116 auto gbm_device = std::make_unique<ui::MockGbmDevice>();
117 drm_ = new ui::MockDrmDevice(std::move(gbm_device));
118 screen_manager_ = std::make_unique<ui::ScreenManager>();
119
120 InitializeDrmState(drm_.get());
121
122 screen_manager_->AddDisplayController(drm_, kDefaultCrtc, kDefaultConnector);
123 std::vector<ui::ScreenManager::ControllerConfigParams> controllers_to_enable;
124 controllers_to_enable.emplace_back(
125 1 /*display_id*/, drm_, kDefaultCrtc, kDefaultConnector, gfx::Point(),
126 std::make_unique<drmModeModeInfo>(kDefaultMode));
127 screen_manager_->ConfigureDisplayControllers(controllers_to_enable);
128
129 drm_device_manager_ = std::make_unique<ui::DrmDeviceManager>(nullptr);
130
131 std::unique_ptr<ui::DrmWindow> window(new ui::DrmWindow(
132 kDefaultWidgetHandle, drm_device_manager_.get(), screen_manager_.get()));
133 window->Initialize();
134 window->SetBounds(
135 gfx::Rect(gfx::Size(kDefaultMode.hdisplay, kDefaultMode.vdisplay)));
136 screen_manager_->AddWindow(kDefaultWidgetHandle, std::move(window));
137 }
138
TearDown()139 void DrmWindowTest::TearDown() {
140 std::unique_ptr<ui::DrmWindow> window =
141 screen_manager_->RemoveWindow(kDefaultWidgetHandle);
142 window->Shutdown();
143 }
144
InitializeDrmState(ui::MockDrmDevice * drm,bool is_atomic)145 void DrmWindowTest::InitializeDrmState(ui::MockDrmDevice* drm, bool is_atomic) {
146 // A Sample of CRTC states.
147 std::vector<CrtcState> crtc_states = {
148 {
149 /* .planes = */
150 {{/* .formats = */ {DRM_FORMAT_XRGB8888}}},
151 },
152 {
153 /* .planes = */
154 {{/* .formats = */ {DRM_FORMAT_XRGB8888}}},
155 },
156 };
157
158 constexpr uint32_t kPlaneIdBase = 300;
159 constexpr uint32_t kInFormatsBlobPropIdBase = 400;
160
161 std::vector<ui::MockDrmDevice::CrtcProperties> crtc_properties(
162 crtc_states.size());
163 std::map<uint32_t, std::string> crtc_property_names = {
164 {1000, "ACTIVE"},
165 {1001, "MODE_ID"},
166 };
167
168 std::vector<ui::MockDrmDevice::ConnectorProperties> connector_properties(3);
169 std::map<uint32_t, std::string> connector_property_names = {
170 {2000, "CRTC_ID"},
171 };
172 for (size_t i = 0; i < connector_properties.size(); ++i) {
173 connector_properties[i].id = kDefaultConnector + i;
174 for (const auto& pair : connector_property_names) {
175 connector_properties[i].properties.push_back(
176 {/* .id = */ pair.first, /* .value = */ 0});
177 }
178 }
179
180 std::vector<ui::MockDrmDevice::PlaneProperties> plane_properties;
181 std::map<uint32_t, std::string> plane_property_names = {
182 // Add all required properties.
183 {3000, "CRTC_ID"},
184 {3001, "CRTC_X"},
185 {3002, "CRTC_Y"},
186 {3003, "CRTC_W"},
187 {3004, "CRTC_H"},
188 {3005, "FB_ID"},
189 {3006, "SRC_X"},
190 {3007, "SRC_Y"},
191 {3008, "SRC_W"},
192 {3009, "SRC_H"},
193 // Defines some optional properties we use for convenience.
194 {3010, "type"},
195 {3011, "IN_FORMATS"},
196 };
197
198 uint32_t plane_id = kPlaneIdBase;
199 uint32_t property_id = kInFormatsBlobPropIdBase;
200
201 for (size_t crtc_idx = 0; crtc_idx < crtc_states.size(); ++crtc_idx) {
202 crtc_properties[crtc_idx].id = kDefaultCrtc + crtc_idx;
203 for (const auto& pair : crtc_property_names) {
204 crtc_properties[crtc_idx].properties.push_back(
205 {/* .id = */ pair.first, /* .value = */ 0});
206 }
207
208 std::vector<ui::MockDrmDevice::PlaneProperties> crtc_plane_properties(
209 crtc_states[crtc_idx].planes.size());
210 for (size_t plane_idx = 0; plane_idx < crtc_states[crtc_idx].planes.size();
211 ++plane_idx) {
212 crtc_plane_properties[plane_idx].id = plane_id++;
213 crtc_plane_properties[plane_idx].crtc_mask = 1 << crtc_idx;
214
215 for (const auto& pair : plane_property_names) {
216 uint64_t value = 0;
217 if (pair.first == 3010) {
218 value =
219 plane_idx == 0 ? DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY;
220 } else if (pair.first == 3011) {
221 value = property_id++;
222 drm->SetPropertyBlob(ui::MockDrmDevice::AllocateInFormatsBlob(
223 value, crtc_states[crtc_idx].planes[plane_idx].formats,
224 std::vector<drm_format_modifier>()));
225 } else if (pair.first >= 3001 && pair.first <= 3009) {
226 value = 27;
227 }
228
229 crtc_plane_properties[plane_idx].properties.push_back(
230 {/* .id = */ pair.first, /* .value = */ value});
231 }
232 }
233
234 plane_properties.insert(plane_properties.end(),
235 crtc_plane_properties.begin(),
236 crtc_plane_properties.end());
237 }
238
239 std::map<uint32_t, std::string> property_names;
240 property_names.insert(crtc_property_names.begin(), crtc_property_names.end());
241 property_names.insert(connector_property_names.begin(),
242 connector_property_names.end());
243 property_names.insert(plane_property_names.begin(),
244 plane_property_names.end());
245 drm->InitializeState(crtc_properties, connector_properties, plane_properties,
246 property_names, /*is_atomic=*/false);
247 }
248
TEST_F(DrmWindowTest,SetCursorImage)249 TEST_F(DrmWindowTest, SetCursorImage) {
250 const gfx::Size cursor_size(6, 4);
251 screen_manager_->GetWindow(kDefaultWidgetHandle)
252 ->SetCursor(std::vector<SkBitmap>(1, AllocateBitmap(cursor_size)),
253 gfx::Point(4, 2), 0);
254
255 SkBitmap cursor;
256 std::vector<sk_sp<SkSurface>> cursor_buffers = GetCursorBuffers(drm_);
257 EXPECT_EQ(2u, cursor_buffers.size());
258
259 // Buffers 1 is the cursor backbuffer we just drew in.
260 cursor.allocPixels(cursor_buffers[1]->getCanvas()->imageInfo());
261 EXPECT_TRUE(cursor_buffers[1]->getCanvas()->readPixels(cursor, 0, 0));
262
263 // Check that the frontbuffer is displaying the right image as set above.
264 for (int i = 0; i < cursor.height(); ++i) {
265 for (int j = 0; j < cursor.width(); ++j) {
266 if (j < cursor_size.width() && i < cursor_size.height())
267 EXPECT_EQ(SK_ColorWHITE, cursor.getColor(j, i));
268 else
269 EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT),
270 cursor.getColor(j, i));
271 }
272 }
273 }
274
TEST_F(DrmWindowTest,CheckCursorSurfaceAfterChangingDevice)275 TEST_F(DrmWindowTest, CheckCursorSurfaceAfterChangingDevice) {
276 const gfx::Size cursor_size(6, 4);
277 screen_manager_->GetWindow(kDefaultWidgetHandle)
278 ->SetCursor(std::vector<SkBitmap>(1, AllocateBitmap(cursor_size)),
279 gfx::Point(4, 2), 0);
280
281 // Add another device.
282 auto gbm_device = std::make_unique<ui::MockGbmDevice>();
283 scoped_refptr<ui::MockDrmDevice> drm =
284 new ui::MockDrmDevice(std::move(gbm_device));
285 InitializeDrmState(drm.get());
286
287 screen_manager_->AddDisplayController(drm, kDefaultCrtc, kDefaultConnector);
288
289 std::vector<ui::ScreenManager::ControllerConfigParams> controllers_to_enable;
290 controllers_to_enable.emplace_back(
291 2 /*display_id*/, drm, kDefaultCrtc, kDefaultConnector,
292 gfx::Point(0, kDefaultMode.vdisplay),
293 std::make_unique<drmModeModeInfo>(kDefaultMode));
294 screen_manager_->ConfigureDisplayControllers(controllers_to_enable);
295
296 // Move window to the display on the new device.
297 screen_manager_->GetWindow(kDefaultWidgetHandle)
298 ->SetBounds(gfx::Rect(0, kDefaultMode.vdisplay, kDefaultMode.hdisplay,
299 kDefaultMode.vdisplay));
300
301 EXPECT_EQ(2u, GetCursorBuffers(drm).size());
302 // Make sure the cursor is showing on the new display.
303 EXPECT_NE(0u, drm->get_cursor_handle_for_crtc(kDefaultCrtc));
304 }
305
TEST_F(DrmWindowTest,CheckDeathOnFailedSwap)306 TEST_F(DrmWindowTest, CheckDeathOnFailedSwap) {
307 const gfx::Size window_size(6, 4);
308 ui::DrmWindow* window = screen_manager_->GetWindow(kDefaultWidgetHandle);
309
310 std::unique_ptr<ui::GbmBuffer> buffer = drm_->gbm_device()->CreateBuffer(
311 DRM_FORMAT_XRGB8888, window_size, GBM_BO_USE_SCANOUT);
312 ASSERT_TRUE(buffer);
313 scoped_refptr<ui::DrmFramebuffer> framebuffer =
314 ui::DrmFramebuffer::AddFramebuffer(drm_, buffer.get(), window_size);
315 ui::DrmOverlayPlane plane(framebuffer, nullptr);
316
317 drm_->set_page_flip_expectation(false);
318
319 ui::DrmOverlayPlaneList planes;
320 planes.push_back(plane.Clone());
321
322 // Window was re-sized, so the expectation is to re-create the buffers first.
323 window->SchedulePageFlip(
324 ui::DrmOverlayPlane::Clone(planes),
325 base::BindOnce(&DrmWindowTest::OnSubmission, base::Unretained(this)),
326 base::BindOnce(&DrmWindowTest::OnPresentation, base::Unretained(this)));
327 EXPECT_EQ(1, on_swap_buffers_count_);
328 EXPECT_EQ(gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS,
329 last_swap_buffers_result_);
330 EXPECT_EQ(static_cast<uint32_t>(gfx::PresentationFeedback::Flags::kFailure),
331 last_presentation_feedback_.flags);
332
333 EXPECT_DEATH_IF_SUPPORTED(
334 window->SchedulePageFlip(
335 ui::DrmOverlayPlane::Clone(planes),
336 base::BindOnce(&DrmWindowTest::OnSubmission, base::Unretained(this)),
337 base::BindOnce(&DrmWindowTest::OnPresentation,
338 base::Unretained(this))),
339 "SchedulePageFlip failed");
340 }
341