2
* Copyright Ā© 2013 Canonical Ltd.
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License version 3 as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
19
#include "mir/graphics/display.h"
20
#include "mir/graphics/display_buffer.h"
21
#include "mir/graphics/display_configuration.h"
22
#include "src/platform/graphics/mesa/platform.h"
24
#include "mir_test_doubles/mock_egl.h"
25
#include "mir_test_doubles/mock_gl.h"
26
#include "mir/graphics/null_display_report.h"
27
#include "mir/graphics/display_configuration_policy.h"
28
#include "mir_test_doubles/null_virtual_terminal.h"
30
#include "mir_test_framework/udev_environment.h"
32
#include "mir_test_doubles/mock_drm.h"
33
#include "mir_test_doubles/mock_gbm.h"
35
#include <gtest/gtest.h>
36
#include <gmock/gmock.h>
38
#include <unordered_set>
40
namespace mg = mir::graphics;
41
namespace mgm = mir::graphics::mesa;
42
namespace geom = mir::geometry;
43
namespace mtd = mir::test::doubles;
44
namespace mtf = mir::mir_test_framework;
49
class ClonedDisplayConfigurationPolicy : public mg::DisplayConfigurationPolicy
52
void apply_to(mg::DisplayConfiguration& conf)
55
[&](mg::DisplayConfigurationOutput const& conf_output)
57
if (conf_output.connected && conf_output.modes.size() > 0)
59
conf.configure_output(conf_output.id, true, geom::Point{0, 0},
60
conf_output.preferred_mode_index, mir_power_mode_on);
64
conf.configure_output(conf_output.id, false, conf_output.top_left,
65
conf_output.current_mode_index, mir_power_mode_on);
71
class SideBySideDisplayConfigurationPolicy : public mg::DisplayConfigurationPolicy
74
void apply_to(mg::DisplayConfiguration& conf)
79
[&](mg::DisplayConfigurationOutput const& conf_output)
81
if (conf_output.connected && conf_output.modes.size() > 0)
83
conf.configure_output(conf_output.id, true, geom::Point{max_x, 0},
84
conf_output.preferred_mode_index, mir_power_mode_on);
85
max_x += conf_output.modes[conf_output.preferred_mode_index].size.width.as_int();
89
conf.configure_output(conf_output.id, false, conf_output.top_left,
90
conf_output.current_mode_index, mir_power_mode_on);
96
class MesaDisplayMultiMonitorTest : public ::testing::Test
99
MesaDisplayMultiMonitorTest()
101
using namespace testing;
103
/* Needed for display start-up */
104
ON_CALL(mock_egl, eglChooseConfig(_,_,_,1,_))
105
.WillByDefault(DoAll(SetArgPointee<2>(mock_egl.fake_configs[0]),
109
const char* egl_exts = "EGL_KHR_image EGL_KHR_image_base EGL_MESA_drm_image";
110
const char* gl_exts = "GL_OES_texture_npot GL_OES_EGL_image";
112
ON_CALL(mock_egl, eglQueryString(_,EGL_EXTENSIONS))
113
.WillByDefault(Return(egl_exts));
114
ON_CALL(mock_gl, glGetString(GL_EXTENSIONS))
115
.WillByDefault(Return(reinterpret_cast<const GLubyte*>(gl_exts)));
118
* Silence uninteresting calls called when cleaning up resources in
119
* the MockGBM destructor, and which are not handled by NiceMock<>.
121
EXPECT_CALL(mock_gbm, gbm_bo_get_device(_))
123
EXPECT_CALL(mock_gbm, gbm_device_get_fd(_))
126
fake_devices.add_standard_drm_devices();
129
std::shared_ptr<mgm::Platform> create_platform()
131
return std::make_shared<mgm::Platform>(
132
std::make_shared<mg::NullDisplayReport>(),
133
std::make_shared<mtd::NullVirtualTerminal>());
136
std::shared_ptr<mg::Display> create_display_cloned(
137
std::shared_ptr<mg::Platform> const& platform)
139
auto conf_policy = std::make_shared<ClonedDisplayConfigurationPolicy>();
140
return platform->create_display(conf_policy);
143
std::shared_ptr<mg::Display> create_display_side_by_side(
144
std::shared_ptr<mg::Platform> const& platform)
146
auto conf_policy = std::make_shared<SideBySideDisplayConfigurationPolicy>();
147
return platform->create_display(conf_policy);
150
void setup_outputs(int connected, int disconnected)
152
using fake = mtd::FakeDRMResources;
154
mtd::FakeDRMResources& resources(mock_drm.fake_drm);
157
modes0.push_back(fake::create_mode(1920, 1080, 138500, 2080, 1111, fake::NormalMode));
158
modes0.push_back(fake::create_mode(1920, 1080, 148500, 2200, 1125, fake::PreferredMode));
159
modes0.push_back(fake::create_mode(1680, 1050, 119000, 1840, 1080, fake::NormalMode));
160
modes0.push_back(fake::create_mode(832, 624, 57284, 1152, 667, fake::NormalMode));
162
geom::Size const connector_physical_size_mm{1597, 987};
166
uint32_t const crtc_base_id{10};
167
uint32_t const encoder_base_id{20};
168
uint32_t const connector_base_id{30};
170
for (int i = 0; i < connected; i++)
172
uint32_t const crtc_id{crtc_base_id + i};
173
uint32_t const encoder_id{encoder_base_id + i};
174
uint32_t const all_crtcs_mask{0xff};
176
crtc_ids.push_back(crtc_id);
177
resources.add_crtc(crtc_id, drmModeModeInfo());
179
encoder_ids.push_back(encoder_id);
180
resources.add_encoder(encoder_id, crtc_id, all_crtcs_mask);
183
for (int i = 0; i < connected; i++)
185
uint32_t const connector_id{connector_base_id + i};
187
connector_ids.push_back(connector_id);
188
resources.add_connector(connector_id, DRM_MODE_CONNECTOR_VGA,
189
DRM_MODE_CONNECTED, encoder_ids[i],
190
modes0, encoder_ids, connector_physical_size_mm);
193
for (int i = 0; i < disconnected; i++)
195
uint32_t const connector_id{connector_base_id + connected + i};
197
connector_ids.push_back(connector_id);
198
resources.add_connector(connector_id, DRM_MODE_CONNECTOR_VGA,
199
DRM_MODE_DISCONNECTED, 0,
200
modes_empty, encoder_ids, geom::Size{});
207
testing::NiceMock<mtd::MockEGL> mock_egl;
208
testing::NiceMock<mtd::MockGL> mock_gl;
209
testing::NiceMock<mtd::MockDRM> mock_drm;
210
testing::NiceMock<mtd::MockGBM> mock_gbm;
212
std::vector<drmModeModeInfo> modes0;
213
std::vector<drmModeModeInfo> modes_empty;
214
std::vector<uint32_t> crtc_ids;
215
std::vector<uint32_t> encoder_ids;
216
std::vector<uint32_t> connector_ids;
218
mtf::UdevEnvironment fake_devices;
223
TEST_F(MesaDisplayMultiMonitorTest, create_display_sets_all_connected_crtcs)
225
using namespace testing;
227
int const num_connected_outputs{3};
228
int const num_disconnected_outputs{2};
229
uint32_t const fb_id{66};
231
setup_outputs(num_connected_outputs, num_disconnected_outputs);
234
EXPECT_CALL(mock_drm, drmModeAddFB(mock_drm.fake_drm.fd(),
235
_, _, _, _, _, _, _))
236
.WillRepeatedly(DoAll(SetArgPointee<7>(fb_id), Return(0)));
238
ExpectationSet crtc_setups;
240
/* All crtcs are set */
241
for (int i = 0; i < num_connected_outputs; i++)
243
crtc_setups += EXPECT_CALL(mock_drm,
244
drmModeSetCrtc(mock_drm.fake_drm.fd(),
247
Pointee(connector_ids[i]),
252
/* All crtcs are restored at teardown */
253
for (int i = 0; i < num_connected_outputs; i++)
255
EXPECT_CALL(mock_drm, drmModeSetCrtc(mock_drm.fake_drm.fd(),
256
crtc_ids[i], Ne(fb_id),
258
Pointee(connector_ids[i]),
264
auto display = create_display_cloned(create_platform());
267
TEST_F(MesaDisplayMultiMonitorTest, create_display_creates_shared_egl_contexts)
269
using namespace testing;
271
int const num_connected_outputs{3};
272
int const num_disconnected_outputs{2};
273
EGLContext const shared_context{reinterpret_cast<EGLContext>(0x77)};
275
setup_outputs(num_connected_outputs, num_disconnected_outputs);
277
/* Will create only one shared context */
278
EXPECT_CALL(mock_egl, eglCreateContext(_, _, EGL_NO_CONTEXT, _))
279
.WillOnce(Return(shared_context));
281
/* Will use the shared context when creating other contexts */
282
EXPECT_CALL(mock_egl, eglCreateContext(_, _, shared_context, _))
288
/* Contexts are made current to initialize DisplayBuffers */
289
EXPECT_CALL(mock_egl, eglMakeCurrent(_,_,_,Ne(shared_context)))
292
/* The shared context is made current finally */
293
EXPECT_CALL(mock_egl, eglMakeCurrent(_,_,_,shared_context))
297
auto display = create_display_cloned(create_platform());
303
ACTION_P(InvokePageFlipHandler, param)
305
int const dont_care{0};
308
arg1->page_flip_handler(dont_care, dont_care, dont_care, dont_care, *param);
309
ASSERT_EQ(1, read(arg0, &dummy, 1));
314
TEST_F(MesaDisplayMultiMonitorTest, post_update_flips_all_connected_crtcs)
316
using namespace testing;
318
int const num_connected_outputs{3};
319
int const num_disconnected_outputs{2};
320
uint32_t const fb_id{66};
321
std::vector<void*> user_data(num_connected_outputs, nullptr);
323
setup_outputs(num_connected_outputs, num_disconnected_outputs);
326
EXPECT_CALL(mock_drm, drmModeAddFB(mock_drm.fake_drm.fd(),
327
_, _, _, _, _, _, _))
328
.WillRepeatedly(DoAll(SetArgPointee<7>(fb_id), Return(0)));
330
/* All crtcs are flipped */
331
for (int i = 0; i < num_connected_outputs; i++)
333
EXPECT_CALL(mock_drm, drmModePageFlip(mock_drm.fake_drm.fd(),
337
.WillOnce(DoAll(SaveArg<4>(&user_data[i]), Return(0)));
339
/* Emit fake DRM page-flip events */
340
EXPECT_EQ(1, write(mock_drm.fake_drm.write_fd(), "a", 1));
343
/* Handle the events properly */
344
EXPECT_CALL(mock_drm, drmHandleEvent(mock_drm.fake_drm.fd(), _))
345
.Times(num_connected_outputs)
346
.WillOnce(DoAll(InvokePageFlipHandler(&user_data[0]), Return(0)))
347
.WillOnce(DoAll(InvokePageFlipHandler(&user_data[1]), Return(0)))
348
.WillOnce(DoAll(InvokePageFlipHandler(&user_data[2]), Return(0)));
350
auto display = create_display_cloned(create_platform());
352
display->for_each_display_buffer([](mg::DisplayBuffer& buffer)
354
buffer.post_update();
363
FBIDContainer(uint32_t base_fb_id) : last_fb_id{base_fb_id} {}
365
int add_fb(int, uint32_t, uint32_t, uint8_t,
366
uint8_t, uint32_t, uint32_t,
369
*buf_id = last_fb_id;
370
fb_ids.insert(last_fb_id);
375
bool check_fb_id(uint32_t i)
377
if (fb_ids.find(i) != fb_ids.end())
386
std::unordered_set<uint32_t> fb_ids;
390
MATCHER_P(IsValidFB, fb_id_container, "") { return fb_id_container->check_fb_id(arg); }
394
TEST_F(MesaDisplayMultiMonitorTest, create_display_uses_different_drm_fbs_for_side_by_side)
396
using namespace testing;
398
int const num_connected_outputs{3};
399
int const num_disconnected_outputs{2};
400
uint32_t const base_fb_id{66};
401
FBIDContainer fb_id_container{base_fb_id};
403
setup_outputs(num_connected_outputs, num_disconnected_outputs);
406
EXPECT_CALL(mock_drm, drmModeAddFB(mock_drm.fake_drm.fd(),
407
_, _, _, _, _, _, _))
408
.Times(num_connected_outputs)
409
.WillRepeatedly(Invoke(&fb_id_container, &FBIDContainer::add_fb));
411
ExpectationSet crtc_setups;
413
/* All crtcs are set */
414
for (int i = 0; i < num_connected_outputs; i++)
416
crtc_setups += EXPECT_CALL(mock_drm,
417
drmModeSetCrtc(mock_drm.fake_drm.fd(),
419
IsValidFB(&fb_id_container),
421
Pointee(connector_ids[i]),
426
/* All crtcs are restored at teardown */
427
for (int i = 0; i < num_connected_outputs; i++)
429
EXPECT_CALL(mock_drm, drmModeSetCrtc(mock_drm.fake_drm.fd(),
432
Pointee(connector_ids[i]),
438
auto display = create_display_side_by_side(create_platform());
441
TEST_F(MesaDisplayMultiMonitorTest, configure_clears_unused_connected_outputs)
443
using namespace testing;
445
int const num_connected_outputs{3};
446
int const num_disconnected_outputs{2};
448
setup_outputs(num_connected_outputs, num_disconnected_outputs);
450
auto display = create_display_cloned(create_platform());
452
Mock::VerifyAndClearExpectations(&mock_drm);
454
/* All unused connected outputs are cleared */
455
for (int i = 0; i < num_connected_outputs; i++)
457
EXPECT_CALL(mock_drm,
458
drmModeSetCursor(mock_drm.fake_drm.fd(),
459
crtc_ids[i], 0, 0, 0))
461
EXPECT_CALL(mock_drm,
462
drmModeSetCrtc(mock_drm.fake_drm.fd(),
463
crtc_ids[i], 0, 0, 0,
464
nullptr, 0, nullptr))
468
/* Set all outputs to unused */
469
auto conf = display->configuration();
471
conf->for_each_output(
472
[&](mg::DisplayConfigurationOutput const& conf_output)
474
conf->configure_output(conf_output.id, false, conf_output.top_left,
475
conf_output.preferred_mode_index, mir_power_mode_on);
478
display->configure(*conf);
480
Mock::VerifyAndClearExpectations(&mock_drm);
482
/* All crtcs are restored at teardown */
483
for (int i = 0; i < num_connected_outputs; i++)
485
EXPECT_CALL(mock_drm,
486
drmModeSetCrtc(mock_drm.fake_drm.fd(), crtc_ids[i],
487
0, _, _, Pointee(connector_ids[i]),
493
TEST_F(MesaDisplayMultiMonitorTest, resume_clears_unused_connected_outputs)
495
using namespace testing;
497
int const num_connected_outputs{3};
498
int const num_disconnected_outputs{2};
500
setup_outputs(num_connected_outputs, num_disconnected_outputs);
502
auto display = create_display_cloned(create_platform());
504
/* Set all outputs to unused */
505
auto conf = display->configuration();
507
conf->for_each_output(
508
[&](mg::DisplayConfigurationOutput const& conf_output)
510
conf->configure_output(conf_output.id, false, conf_output.top_left,
511
conf_output.preferred_mode_index, mir_power_mode_on);
514
display->configure(*conf);
518
Mock::VerifyAndClearExpectations(&mock_drm);
520
/* All unused connected outputs are cleared */
521
for (int i = 0; i < num_connected_outputs; i++)
523
EXPECT_CALL(mock_drm,
524
drmModeSetCrtc(mock_drm.fake_drm.fd(),
525
crtc_ids[i], 0, 0, 0,
526
nullptr, 0, nullptr))
532
Mock::VerifyAndClearExpectations(&mock_drm);
534
/* All crtcs are restored at teardown */
535
for (int i = 0; i < num_connected_outputs; i++)
537
EXPECT_CALL(mock_drm,
538
drmModeSetCrtc(mock_drm.fake_drm.fd(), crtc_ids[i],
539
0, _, _, Pointee(connector_ids[i]),