1 // Copyright 2019 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 <drm_fourcc.h>
6 #include <fcntl.h>
7 #include <gbm.h>
8 #include <sys/mman.h>
9
10 #include <color-space-unstable-v1-client-protocol.h>
11
12 #include "base/at_exit.h"
13 #include "base/command_line.h"
14 #include "base/logging.h"
15 #include "base/message_loop/message_pump_type.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_split.h"
18 #include "base/task/single_thread_task_executor.h"
19 #include "components/exo/wayland/clients/client_base.h"
20 #include "components/exo/wayland/clients/client_helper.h"
21
22 namespace exo {
23 namespace wayland {
24 namespace clients {
25 namespace {
26
FrameCallback(void * data,wl_callback * callback,uint32_t time)27 void FrameCallback(void* data, wl_callback* callback, uint32_t time) {
28 bool* callback_pending = static_cast<bool*>(data);
29 *callback_pending = false;
30 }
31
WriteMixedPrimaries(gbm_bo * bo,const gfx::Size & size)32 void WriteMixedPrimaries(gbm_bo* bo, const gfx::Size& size) {
33 CHECK_EQ(gbm_bo_get_plane_count(bo), 1u);
34 uint32_t stride;
35 void* mapped_data;
36 void* void_data = gbm_bo_map(bo, 0, 0, size.width(), size.height(),
37 GBM_BO_TRANSFER_WRITE, &stride, &mapped_data, 0);
38 CHECK_NE(void_data, MAP_FAILED);
39 uint32_t* data = static_cast<uint32_t*>(void_data);
40 CHECK_EQ(stride % 4, 0u);
41 stride = stride / 4;
42 // Draw rows that interpolate from R->G->B->R so we draw the outside edges of
43 // the color gamut we are using and than ramp it from 0-255 going left to
44 // right.
45 int h1 = size.height() / 3;
46 int h2 = 2 * h1;
47 int h_rem = size.height() - h2;
48 int xscaler = size.width() - 1;
49 for (int y = 0; y < h1; ++y) {
50 for (int x = 0; x < size.width(); ++x) {
51 SkColor c = SkColorSetRGB(x * 0xFF * (h1 - y) / (h1 * xscaler),
52 x * 0xFF * y / (h1 * xscaler), 0);
53 data[stride * y + x] = c;
54 }
55 }
56 for (int y = h1; y < h2; ++y) {
57 for (int x = 0; x < size.width(); ++x) {
58 SkColor c = SkColorSetRGB(0, x * 0xFF * (h2 - y) / (h1 * xscaler),
59 x * 0xFF * (y - h1) / (h1 * xscaler));
60 data[stride * y + x] = c;
61 }
62 }
63 for (int y = h2; y < size.height(); ++y) {
64 for (int x = 0; x < size.width(); ++x) {
65 SkColor c =
66 SkColorSetRGB(x * 0xFF * (y - h2) / (h_rem * xscaler), 0,
67 x * 0xFF * (size.height() - y) / (h_rem * xscaler));
68 data[stride * y + x] = c;
69 }
70 }
71 gbm_bo_unmap(bo, mapped_data);
72 }
73
74 } // namespace
75
76 // Wayland client implementation which renders a gradient to a surface and
77 // switches between two different color spaces every 30 frames. There are
78 // command line options available to switch all attributes of each color space
79 // used. This is for testing setting color spaces on wayland surfaces.
80 class ColorSpaceClient : public ClientBase {
81 public:
ColorSpaceClient()82 ColorSpaceClient() {}
83
84 void Run(const ClientBase::InitParams& params,
85 uint32_t primary1,
86 uint32_t transfer1,
87 uint32_t matrix1,
88 uint32_t range1,
89 uint32_t primary2,
90 uint32_t transfer2,
91 uint32_t matrix2,
92 uint32_t range2);
93 };
94
Run(const ClientBase::InitParams & params,uint32_t primary1,uint32_t transfer1,uint32_t matrix1,uint32_t range1,uint32_t primary2,uint32_t transfer2,uint32_t matrix2,uint32_t range2)95 void ColorSpaceClient::Run(const ClientBase::InitParams& params,
96 uint32_t primary1,
97 uint32_t transfer1,
98 uint32_t matrix1,
99 uint32_t range1,
100 uint32_t primary2,
101 uint32_t transfer2,
102 uint32_t matrix2,
103 uint32_t range2) {
104 if (!ClientBase::Init(params))
105 return;
106
107 // The server needs to support the color space protocol.
108 CHECK(globals_.color_space) << "Server doesn't support zcr_color_space_v1.";
109
110 bool callback_pending = false;
111 std::unique_ptr<wl_callback> frame_callback;
112 wl_callback_listener frame_listener = {FrameCallback};
113
114 constexpr int kFramesPerCycle = 60;
115 size_t frame_number = 0;
116 for (auto& buff : buffers_) {
117 WriteMixedPrimaries(buff->bo.get(), size_);
118 }
119 do {
120 if (callback_pending)
121 continue;
122 frame_number++;
123
124 Buffer* buffer = DequeueBuffer();
125 if (!buffer) {
126 LOG(ERROR) << "Can't find free buffer";
127 return;
128 }
129 wl_surface_set_buffer_scale(surface_.get(), scale_);
130 wl_surface_set_buffer_transform(surface_.get(), transform_);
131 wl_surface_damage(surface_.get(), 0, 0, surface_size_.width(),
132 surface_size_.height());
133 wl_surface_attach(surface_.get(), buffer->buffer.get(), 0, 0);
134 if ((frame_number % kFramesPerCycle) < kFramesPerCycle / 2) {
135 if (frame_number % kFramesPerCycle == 0)
136 LOG(WARNING) << "Primary color space";
137 zcr_color_space_v1_set_color_space(globals_.color_space.get(),
138 surface_.get(), primary1, transfer1,
139 matrix1, range1);
140
141 } else {
142 if (frame_number % kFramesPerCycle == kFramesPerCycle / 2)
143 LOG(WARNING) << "Secondary color space";
144 zcr_color_space_v1_set_color_space(globals_.color_space.get(),
145 surface_.get(), primary2, transfer2,
146 matrix2, range2);
147 }
148 frame_callback.reset(wl_surface_frame(surface_.get()));
149 wl_callback_add_listener(frame_callback.get(), &frame_listener,
150 &callback_pending);
151 callback_pending = true;
152 wl_surface_commit(surface_.get());
153 wl_display_flush(display_.get());
154 } while (wl_display_dispatch(display_.get()) != -1);
155 }
156
157 } // namespace clients
158 } // namespace wayland
159 } // namespace exo
160
main(int argc,char * argv[])161 int main(int argc, char* argv[]) {
162 base::AtExitManager exit_manager;
163 base::CommandLine::Init(argc, argv);
164 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
165
166 exo::wayland::clients::ClientBase::InitParams params;
167 params.num_buffers = 4;
168 if (!params.FromCommandLine(*command_line))
169 return 1;
170
171 // sRGB
172 int32_t primary1 = ZCR_COLOR_SPACE_V1_PRIMARIES_BT709;
173 int32_t transfer1 = ZCR_COLOR_SPACE_V1_TRANSFER_FUNCTION_IEC61966_2_1;
174 int32_t matrix1 = ZCR_COLOR_SPACE_V1_MATRIX_RGB;
175 int32_t range1 = ZCR_COLOR_SPACE_V1_RANGE_FULL;
176 // HDR BT2020, PQ, full range RGB
177 int32_t primary2 = ZCR_COLOR_SPACE_V1_PRIMARIES_BT2020;
178 int32_t transfer2 = ZCR_COLOR_SPACE_V1_TRANSFER_FUNCTION_SMPTEST2084;
179 int32_t matrix2 = ZCR_COLOR_SPACE_V1_MATRIX_RGB;
180 int32_t range2 = ZCR_COLOR_SPACE_V1_RANGE_FULL;
181 // Use the values from the enums in the wayland protocol. For example to get
182 // the same behavior as default the flags would be:
183 // --cs1=0,12,0,1 --cs2=6,15,0,1
184 if (command_line->HasSwitch("cs1")) {
185 std::vector<std::string> cs1_values =
186 base::SplitString(command_line->GetSwitchValueASCII("cs1"), ",",
187 base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
188 if (cs1_values.size() != 4) {
189 LOG(ERROR) << "Invalid value for cs1, needs 4 tokens";
190 return 1;
191 }
192 if (!base::StringToInt(cs1_values[0], &primary1)) {
193 LOG(ERROR) << "Invalid value for primary in cs1";
194 return 1;
195 }
196 if (!base::StringToInt(cs1_values[1], &transfer1)) {
197 LOG(ERROR) << "Invalid value for transfer function in cs1";
198 return 1;
199 }
200 if (!base::StringToInt(cs1_values[2], &matrix1)) {
201 LOG(ERROR) << "Invalid value for matrix in cs1";
202 return 1;
203 }
204 if (!base::StringToInt(cs1_values[3], &range1)) {
205 LOG(ERROR) << "Invalid value for range in cs1";
206 return 1;
207 }
208 }
209 if (command_line->HasSwitch("cs2")) {
210 std::vector<std::string> cs2_values =
211 base::SplitString(command_line->GetSwitchValueASCII("cs2"), ",",
212 base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
213 if (cs2_values.size() != 4) {
214 LOG(ERROR) << "Invalid value for cs2, needs 4 tokens";
215 return 1;
216 }
217 if (!base::StringToInt(cs2_values[0], &primary2)) {
218 LOG(ERROR) << "Invalid value for primary in cs2";
219 return 1;
220 }
221 if (!base::StringToInt(cs2_values[1], &transfer2)) {
222 LOG(ERROR) << "Invalid value for transfer function in cs2";
223 return 1;
224 }
225 if (!base::StringToInt(cs2_values[2], &matrix2)) {
226 LOG(ERROR) << "Invalid value for matrix in cs2";
227 return 1;
228 }
229 if (!base::StringToInt(cs2_values[3], &range2)) {
230 LOG(ERROR) << "Invalid value for range in cs2";
231 return 1;
232 }
233 }
234
235 if (!params.use_drm) {
236 LOG(ERROR) << "Missing --use-drm parameter which is required for gbm "
237 "buffer allocation";
238 return 1;
239 }
240
241 params.drm_format = DRM_FORMAT_ARGB8888;
242 params.bo_usage =
243 GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR | GBM_BO_USE_TEXTURING;
244
245 base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
246 exo::wayland::clients::ColorSpaceClient client;
247 client.Run(params, primary1, transfer1, matrix1, range1, primary2, transfer2,
248 matrix2, range2);
249 return 0;
250 }
251