stop using gkd_pango_context_get() in ArdourCanvas::Canvas and require concrete insta...
[ardour.git] / libs / surfaces / push2 / canvas.cc
1 /*
2     Copyright (C) 2016 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <vector>
21
22 #include <cairomm/region.h>
23 #include <cairomm/surface.h>
24 #include <cairomm/context.h>
25
26 #include "pbd/compose.h"
27 #include "pbd/error.h"
28 #include "pbd/i18n.h"
29
30 #include "ardour/debug.h"
31
32 #include "canvas.h"
33 #include "layout.h"
34 #include "push2.h"
35
36 #ifdef __APPLE__
37 #define Rect ArdourCanvas::Rect
38 #endif
39
40 using namespace ArdourCanvas;
41 using namespace ArdourSurface;
42 using namespace PBD;
43
44 const int Push2Canvas::pixels_per_row = 1024;
45
46 Push2Canvas::Push2Canvas (Push2& pr, int c, int r)
47         : p2 (pr)
48         , _cols (c)
49         , _rows (r)
50         , frame_buffer (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _cols, _rows))
51 {
52         context = Cairo::Context::create (frame_buffer);
53         expose_region = Cairo::Region::create ();
54
55         device_frame_buffer = new uint16_t[pixel_area()];
56         memset (device_frame_buffer, 0, sizeof (uint16_t) * pixel_area());
57
58         frame_header[0] = 0xef;
59         frame_header[1] = 0xcd;
60         frame_header[2] = 0xab;
61         frame_header[3] = 0x89;
62
63         memset (&frame_header[4], 0, 12);
64 }
65
66 Push2Canvas::~Push2Canvas ()
67 {
68         delete [] device_frame_buffer;
69         device_frame_buffer = 0;
70 }
71
72 bool
73 Push2Canvas::vblank ()
74 {
75         /* re-render dirty areas, if any */
76
77         if (expose ()) {
78                 /* something rendered, update device_frame_buffer */
79                 blit_to_device_frame_buffer ();
80
81 #undef RENDER_LAYOUTS
82 #ifdef RENDER_LAYOUTS
83                 if (p2.current_layout()) {
84                         std::string s = p2.current_layout()->name();
85                         s += ".png";
86                         frame_buffer->write_to_png (s);
87                 }
88 #endif
89         }
90
91         int transferred = 0;
92         const int timeout_msecs = 1000;
93         int err;
94
95         /* transfer to device */
96
97         if ((err = libusb_bulk_transfer (p2.usb_handle(), 0x01, frame_header, sizeof (frame_header), &transferred, timeout_msecs))) {
98                 return false;
99         }
100
101         if ((err = libusb_bulk_transfer (p2.usb_handle(), 0x01, (uint8_t*) device_frame_buffer, 2 * pixel_area (), &transferred, timeout_msecs))) {
102                 return false;
103         }
104
105         return true;
106 }
107
108 void
109 Push2Canvas::request_redraw ()
110 {
111         request_redraw (Rect (0, 0, _cols, _rows));
112 }
113
114 void
115 Push2Canvas::request_redraw (Rect const & r)
116 {
117         Cairo::RectangleInt cr;
118
119         cr.x = r.x0;
120         cr.y = r.y0;
121         cr.width = r.width();
122         cr.height = r.height();
123
124         // DEBUG_TRACE (DEBUG::Push2, string_compose ("invalidate rect %1\n", r));
125
126         expose_region->do_union (cr);
127
128         /* next vblank will redraw */
129 }
130
131 bool
132 Push2Canvas::expose ()
133 {
134         if (expose_region->empty()) {
135                 return false; /* nothing drawn */
136         }
137
138         /* set up clipping */
139
140         const int nrects = expose_region->get_num_rectangles ();
141
142         //DEBUG_TRACE (DEBUG::Push2, string_compose ("expose with %1 rects\n", nrects));
143
144         for (int n = 0; n < nrects; ++n) {
145                 Cairo::RectangleInt r = expose_region->get_rectangle (n);
146                 context->rectangle (r.x, r.y, r.width, r.height);
147         }
148
149         context->clip ();
150
151         Push2Layout* layout = p2.current_layout();
152
153         if (layout) {
154                 /* all layouts cover (at least) the full size of the video
155                    display, so we do not need to check if the layout intersects
156                    the bounding box of the full expose region.
157                 */
158                 Cairo::RectangleInt r = expose_region->get_extents();
159                 Rect rr (r.x, r.y, r.x + r.width, r.y + r.height);
160                 //DEBUG_TRACE (DEBUG::Push2, string_compose ("render layout with %1\n", rr));
161                 layout->render (Rect (r.x, r.y, r.x + r.width, r.y + r.height), context);
162         }
163
164         context->reset_clip ();
165
166         /* why is there no "reset()" method for Cairo::Region? */
167
168         expose_region = Cairo::Region::create ();
169
170         return true;
171 }
172
173 /** render host-side frame buffer (a Cairo ImageSurface) to the current
174  * device-side frame buffer. The device frame buffer will be pushed to the
175  * device on the next call to vblank()
176  */
177
178 int
179 Push2Canvas::blit_to_device_frame_buffer ()
180 {
181         /* ensure that all drawing has been done before we fetch pixel data */
182
183         frame_buffer->flush ();
184
185         const int stride = 3840; /* bytes per row for Cairo::FORMAT_ARGB32 */
186         const uint8_t* data = frame_buffer->get_data ();
187
188         /* fill frame buffer (320kB) */
189
190         uint16_t* fb = (uint16_t*) device_frame_buffer;
191
192         for (int row = 0; row < _rows; ++row) {
193
194                 const uint8_t* dp = data + row * stride;
195
196                 for (int col = 0; col < _cols; ++col) {
197
198                         /* fetch r, g, b (range 0..255). Ignore alpha */
199
200                         const int r = (*((const uint32_t*)dp) >> 16) & 0xff;
201                         const int g = (*((const uint32_t*)dp) >> 8) & 0xff;
202                         const int b = *((const uint32_t*)dp) & 0xff;
203
204                         /* convert to 5 bits, 6 bits, 5 bits, respectively */
205                         /* generate 16 bit BGB565 value */
206
207                         *fb++ = (r >> 3) | ((g & 0xfc) << 3) | ((b & 0xf8) << 8);
208
209                         /* the push2 docs state that we should xor the pixel
210                          * data. Doing so doesn't work correctly, and not doing
211                          * so seems to work fine (colors roughly match intended
212                          * values).
213                          */
214
215                         dp += 4;
216                 }
217
218                 /* skip 128 bytes to next line. This is filler, used to avoid line borders occuring in the middle of 512
219                    byte USB buffers
220                 */
221
222                 fb += 64; /* 128 bytes = 64 int16_t */
223         }
224
225         return 0;
226 }
227
228 void
229 Push2Canvas::request_size (Duple)
230 {
231         /* fixed size canvas */
232 }
233
234 Rect
235 Push2Canvas::visible_area () const
236 {
237         /* may need to get more sophisticated once we do scrolling */
238         return Rect (0, 0, 960, 160);
239 }
240
241 Glib::RefPtr<Pango::Context>
242 Push2Canvas::get_pango_context ()
243 {
244         if (!pango_context) {
245                 PangoFontMap* map = pango_cairo_font_map_get_default ();
246                 if (!map) {
247                         error << _("Default Cairo font map is null!") << endmsg;
248                         return Glib::RefPtr<Pango::Context> ();
249                 }
250
251                 PangoContext* context = pango_font_map_create_context (map);
252
253                 if (!context) {
254                         error << _("cannot create new PangoContext from cairo font map") << endmsg;
255                         return Glib::RefPtr<Pango::Context> ();
256                 }
257
258                 pango_context = Glib::wrap (context);
259         }
260
261         return pango_context;
262 }