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