reorganize push 2 code and logic to better handle device arrival after program startup
[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 <cairomm/region.h>
21 #include <cairomm/surface.h>
22 #include <cairomm/context.h>
23
24 #include "pbd/compose.h"
25
26 #include "ardour/debug.h"
27
28 #include "canvas.h"
29 #include "layout.h"
30 #include "push2.h"
31
32 #ifdef __APPLE__
33 #define Rect ArdourCanvas::Rect
34 #endif
35
36 using namespace ArdourCanvas;
37 using namespace ArdourSurface;
38 using namespace PBD;
39
40 const int Push2Canvas::pixels_per_row = 1024;
41
42 Push2Canvas::Push2Canvas (Push2& pr, int c, int r)
43         : p2 (pr)
44         , _cols (c)
45         , _rows (r)
46         , frame_buffer (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _cols, _rows))
47 {
48         context = Cairo::Context::create (frame_buffer);
49         expose_region = Cairo::Region::create ();
50
51         device_frame_buffer = new uint16_t[pixel_area()];
52         memset (device_frame_buffer, 0, sizeof (uint16_t) * pixel_area());
53
54         frame_header[0] = 0xef;
55         frame_header[1] = 0xcd;
56         frame_header[2] = 0xab;
57         frame_header[3] = 0x89;
58
59         memset (&frame_header[4], 0, 12);
60 }
61
62 Push2Canvas::~Push2Canvas ()
63 {
64         delete [] device_frame_buffer;
65         device_frame_buffer = 0;
66 }
67
68 bool
69 Push2Canvas::vblank ()
70 {
71         /* re-render dirty areas, if any */
72
73         if (expose ()) {
74                 /* something rendered, update device_frame_buffer */
75                 blit_to_device_frame_buffer ();
76
77 #undef RENDER_LAYOUTS
78 #ifdef RENDER_LAYOUTS
79                 if (p2.current_layout()) {
80                         std::string s = p2.current_layout()->name();
81                         s += ".png";
82                         frame_buffer->write_to_png (s);
83                 }
84 #endif
85         }
86
87         int transferred = 0;
88         const int timeout_msecs = 1000;
89         int err;
90
91         /* transfer to device */
92
93         if ((err = libusb_bulk_transfer (p2.usb_handle(), 0x01, frame_header, sizeof (frame_header), &transferred, timeout_msecs))) {
94                 return false;
95         }
96
97         if ((err = libusb_bulk_transfer (p2.usb_handle(), 0x01, (uint8_t*) device_frame_buffer, 2 * pixel_area (), &transferred, timeout_msecs))) {
98                 return false;
99         }
100
101         return true;
102 }
103
104 void
105 Push2Canvas::request_redraw ()
106 {
107         request_redraw (Rect (0, 0, _cols, _rows));
108 }
109
110 void
111 Push2Canvas::request_redraw (Rect const & r)
112 {
113         Cairo::RectangleInt cr;
114
115         cr.x = r.x0;
116         cr.y = r.y0;
117         cr.width = r.width();
118         cr.height = r.height();
119
120         // DEBUG_TRACE (DEBUG::Push2, string_compose ("invalidate rect %1\n", r));
121
122         expose_region->do_union (cr);
123
124         /* next vblank will redraw */
125 }
126
127 bool
128 Push2Canvas::expose ()
129 {
130         if (expose_region->empty()) {
131                 return false; /* nothing drawn */
132         }
133
134         /* set up clipping */
135
136         const int nrects = expose_region->get_num_rectangles ();
137
138         //DEBUG_TRACE (DEBUG::Push2, string_compose ("expose with %1 rects\n", nrects));
139
140         for (int n = 0; n < nrects; ++n) {
141                 Cairo::RectangleInt r = expose_region->get_rectangle (n);
142                 context->rectangle (r.x, r.y, r.width, r.height);
143         }
144
145         context->clip ();
146
147         Push2Layout* layout = p2.current_layout();
148
149         if (layout) {
150                 /* all layouts cover (at least) the full size of the video
151                    display, so we do not need to check if the layout intersects
152                    the bounding box of the full expose region.
153                 */
154                 Cairo::RectangleInt r = expose_region->get_extents();
155                 Rect rr (r.x, r.y, r.x + r.width, r.y + r.height);
156                 //DEBUG_TRACE (DEBUG::Push2, string_compose ("render layout with %1\n", rr));
157                 layout->render (Rect (r.x, r.y, r.x + r.width, r.y + r.height), context);
158         }
159
160         context->reset_clip ();
161
162         /* why is there no "reset()" method for Cairo::Region? */
163
164         expose_region = Cairo::Region::create ();
165
166         return true;
167 }
168
169 /** render host-side frame buffer (a Cairo ImageSurface) to the current
170  * device-side frame buffer. The device frame buffer will be pushed to the
171  * device on the next call to vblank()
172  */
173
174 int
175 Push2Canvas::blit_to_device_frame_buffer ()
176 {
177         /* ensure that all drawing has been done before we fetch pixel data */
178
179         frame_buffer->flush ();
180
181         const int stride = 3840; /* bytes per row for Cairo::FORMAT_ARGB32 */
182         const uint8_t* data = frame_buffer->get_data ();
183
184         /* fill frame buffer (320kB) */
185
186         uint16_t* fb = (uint16_t*) device_frame_buffer;
187
188         for (int row = 0; row < _rows; ++row) {
189
190                 const uint8_t* dp = data + row * stride;
191
192                 for (int col = 0; col < _cols; ++col) {
193
194                         /* fetch r, g, b (range 0..255). Ignore alpha */
195
196                         const int r = (*((const uint32_t*)dp) >> 16) & 0xff;
197                         const int g = (*((const uint32_t*)dp) >> 8) & 0xff;
198                         const int b = *((const uint32_t*)dp) & 0xff;
199
200                         /* convert to 5 bits, 6 bits, 5 bits, respectively */
201                         /* generate 16 bit BGB565 value */
202
203                         *fb++ = (r >> 3) | ((g & 0xfc) << 3) | ((b & 0xf8) << 8);
204
205                         /* the push2 docs state that we should xor the pixel
206                          * data. Doing so doesn't work correctly, and not doing
207                          * so seems to work fine (colors roughly match intended
208                          * values).
209                          */
210
211                         dp += 4;
212                 }
213
214                 /* skip 128 bytes to next line. This is filler, used to avoid line borders occuring in the middle of 512
215                    byte USB buffers
216                 */
217
218                 fb += 64; /* 128 bytes = 64 int16_t */
219         }
220
221         return 0;
222 }
223
224 void
225 Push2Canvas::request_size (Duple)
226 {
227         /* fixed size canvas */
228 }
229
230 Rect
231 Push2Canvas::visible_area () const
232 {
233         /* may need to get more sophisticated once we do scrolling */
234         return Rect (0, 0, 960, 160);
235 }