1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2016 Toomas Soome <tsoome@me.com>
14 */
15
16 /*
17 * Graphics support for loader emulation.
18 * The interface in loader and here needs some more development,
19 * we can get colormap from gfx_private, but loader is currently
20 * relying on tem fg/bg colors for drawing, once the menu code
21 * will get some facelift, we would need to provide colors as menu component
22 * attributes and stop depending on tem.
23 */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <sys/mman.h>
32 #include <sys/fbio.h>
33 #include <string.h>
34 #include "gfx_fb.h"
35
36 struct framebuffer fb;
37
38 #define abs(x) ((x) < 0? -(x):(x))
39 #define max(x, y) ((x) >= (y) ? (x) : (y))
40
41 static void gfx_fb_cons_display(uint32_t, uint32_t,
42 uint32_t, uint32_t, uint8_t *);
43
44 /* This colormap should be replaced by colormap query from kernel */
45 typedef struct {
46 uint8_t red[16];
47 uint8_t green[16];
48 uint8_t blue[16];
49 } text_cmap_t;
50
51 text_cmap_t cmap4_to_24 = {
52 /* BEGIN CSTYLED */
53 /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
54 Wh+ Bk Bl Gr Cy Rd Mg Br Wh Bk+ Bl+ Gr+ Cy+ Rd+ Mg+ Yw */
55 .red = {0xff,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x40,0x00,0x00,0x00,0xff,0xff,0xff},
56 .green = {0xff,0x00,0x00,0x80,0x80,0x00,0x00,0x80,0x80,0x40,0x00,0xff,0xff,0x00,0x00,0xff},
57 .blue = {0xff,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x40,0xff,0x00,0xff,0x00,0xff,0x00}
58 /* END CSTYLED */
59 };
60
61 const uint8_t solaris_color_to_pc_color[16] = {
62 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
63 };
64
65 void gfx_framework_init(void)
66 {
67 struct fbgattr attr;
68 struct gfxfb_info *gfxfb_info;
69 char buf[10];
70
71 fb.fd = open("/dev/fb", O_RDWR);
72 if (fb.fd < 0)
73 return;
74
75 /* make sure we have GFX framebuffer */
76 if (ioctl(fb.fd, VIS_GETIDENTIFIER, &fb.ident) < 0 ||
77 strcmp(fb.ident.name, "illumos_fb") != 0) {
78 close(fb.fd);
79 fb.fd = -1;
80 return;
81 }
82
83 if (ioctl(fb.fd, FBIOGATTR, &attr) < 0) {
84 close(fb.fd);
85 fb.fd = -1;
86 return;
87 }
88 gfxfb_info = (struct gfxfb_info *)attr.sattr.dev_specific;
89
90 fb.fb_height = attr.fbtype.fb_height;
91 fb.fb_width = attr.fbtype.fb_width;
92 fb.fb_depth = attr.fbtype.fb_depth;
93 fb.fb_size = attr.fbtype.fb_size;
94 fb.fb_bpp = attr.fbtype.fb_depth >> 3;
95 if (attr.fbtype.fb_depth == 15)
96 fb.fb_bpp = 2;
97 fb.fb_pitch = gfxfb_info->pitch;
98 fb.terminal_origin_x = gfxfb_info->terminal_origin_x;
99 fb.terminal_origin_y = gfxfb_info->terminal_origin_y;
100 fb.font_width = gfxfb_info->font_width;
101 fb.font_height = gfxfb_info->font_height;
102
103 fb.red_mask_size = gfxfb_info->red_mask_size;
104 fb.red_field_position = gfxfb_info->red_field_position;
105 fb.green_mask_size = gfxfb_info->green_mask_size;
106 fb.green_field_position = gfxfb_info->green_field_position;
107 fb.blue_mask_size = gfxfb_info->blue_mask_size;
108 fb.blue_field_position = gfxfb_info->blue_field_position;
109
110 fb.fb_addr = (uint8_t *)mmap(0, fb.fb_size, (PROT_READ | PROT_WRITE),
111 MAP_SHARED, fb.fd, 0);
112
113 if (fb.fb_addr == NULL) {
114 close(fb.fd);
115 fb.fd = -1;
116 return;
117 }
118 snprintf(buf, sizeof (buf), "%d", fb.fb_height);
119 setenv("screen-height", buf, 1);
120 snprintf(buf, sizeof (buf), "%d", fb.fb_width);
121 setenv("screen-width", buf, 1);
122 }
123
124 void gfx_framework_fini(void)
125 {
126 if (fb.fd < 0)
127 return;
128
129 (void) munmap((caddr_t)fb.fb_addr, fb.fb_size);
130 close(fb.fd);
131 fb.fd = -1;
132 }
133
134 static int isqrt(int num) {
135 int res = 0;
136 int bit = 1 << 30;
137
138 /* "bit" starts at the highest power of four <= the argument. */
139 while (bit > num)
140 bit >>= 2;
141
142 while (bit != 0) {
143 if (num >= res + bit) {
144 num -= res + bit;
145 res = (res >> 1) + bit;
146 } else
147 res >>= 1;
148 bit >>= 2;
149 }
150 return (res);
151 }
152
153 void gfx_fb_setpixel(int x, int y)
154 {
155 uint32_t c, offset;
156
157 if (fb.fd < 0)
158 return;
159 c = 0; /* black */
160
161 if (x < 0 || y < 0)
162 return;
163
164 if (x >= fb.fb_width || y >= fb.fb_height)
165 return;
166
167 offset = y * fb.fb_pitch + x * fb.fb_bpp;
168 switch (fb.fb_depth) {
169 case 8:
170 fb.fb_addr[offset] = c & 0xff;
171 break;
172 case 15:
173 case 16:
174 *(uint16_t *)(fb.fb_addr + offset) = c & 0xffff;
175 break;
176 case 24:
177 fb.fb_addr[offset] = (c >> 16) & 0xff;
178 fb.fb_addr[offset + 1] = (c >> 8) & 0xff;
179 fb.fb_addr[offset + 2] = c & 0xff;
180 break;
181 case 32:
182 *(uint32_t *)(fb.fb_addr + offset) = c;
183 break;
184 }
185 }
186
187 void gfx_fb_drawrect(int x1, int y1, int x2, int y2, int fill)
188 {
189 int x, y;
190
191 if (fb.fd < 0)
192 return;
193
194 for (y = y1; y <= y2; y++) {
195 if (fill || (y == y1) || (y == y2)) {
196 for (x = x1; x <= x2; x++)
197 gfx_fb_setpixel(x, y);
198 } else {
199 gfx_fb_setpixel(x1, y);
200 gfx_fb_setpixel(x2, y);
201 }
202 }
203 }
204
205 void gfx_term_drawrect(int row1, int col1, int row2, int col2)
206 {
207 int x1, y1, x2, y2;
208 int xshift, yshift;
209 int width, i;
210
211 if (fb.fd < 0)
212 return;
213
214 width = fb.font_width / 4; /* line width */
215 xshift = (fb.font_width - width) / 2;
216 yshift = (fb.font_height - width) / 2;
217 /* Terminal coordinates start from (1,1) */
218 row1--;
219 col1--;
220 row2--;
221 col2--;
222
223 /*
224 * Draw horizontal lines width points thick, shifted from outer edge.
225 */
226 x1 = row1 * fb.font_width + fb.terminal_origin_x;
227 x1 += fb.font_width;
228 y1 = col1 * fb.font_height + fb.terminal_origin_y + yshift;
229 x2 = row2 * fb.font_width + fb.terminal_origin_x;
230 gfx_fb_drawrect(x1, y1, x2, y1 + width, 1);
231 y2 = col2 * fb.font_height + fb.terminal_origin_y;
232 y2 += fb.font_height - yshift - width;
233 gfx_fb_drawrect(x1, y2, x2, y2 + width, 1);
234
235 /*
236 * Draw vertical lines width points thick, shifted from outer edge.
237 */
238 x1 = row1 * fb.font_width + fb.terminal_origin_x + xshift;
239 y1 = col1 * fb.font_height + fb.terminal_origin_y;
240 y1 += fb.font_height;
241 y2 = col2 * fb.font_height + fb.terminal_origin_y;
242 gfx_fb_drawrect(x1, y1, x1 + width, y2, 1);
243 x1 = row2 * fb.font_width + fb.terminal_origin_x;
244 x1 += fb.font_width - xshift - width;
245 gfx_fb_drawrect(x1, y1, x1 + width, y2, 1);
246
247 /* Draw upper left corner. */
248 x1 = row1 * fb.font_width + fb.terminal_origin_x + xshift;
249 y1 = col1 * fb.font_height + fb.terminal_origin_y;
250 y1 += fb.font_height;
251 x2 = row1 * fb.font_width + fb.terminal_origin_x;
252 x2 += fb.font_width;
253 y2 = col1 * fb.font_height + fb.terminal_origin_y + yshift;
254 for (i = 0; i <= width; i++)
255 gfx_fb_bezier(x1 + i, y1, x1 + i, y2 + i, x2, y2 + i, width-i);
256
257 /* Draw lower left corner. */
258 x1 = row1 * fb.font_width + fb.terminal_origin_x;
259 x1 += fb.font_width;
260 y1 = col2 * fb.font_height + fb.terminal_origin_y;
261 y1 += fb.font_height - yshift;
262 x2 = row1 * fb.font_width + fb.terminal_origin_x + xshift;
263 y2 = col2 * fb.font_height + fb.terminal_origin_y;
264 for (i = 0; i <= width; i++)
265 gfx_fb_bezier(x1, y1 - i, x2 + i, y1 - i, x2 + i, y2, width-i);
266
267 /* Draw upper right corner. */
268 x1 = row2 * fb.font_width + fb.terminal_origin_x;
269 y1 = col1 * fb.font_height + fb.terminal_origin_y + yshift;
270 x2 = row2 * fb.font_width + fb.terminal_origin_x;
271 x2 += fb.font_width - xshift - width;
272 y2 = col1 * fb.font_height + fb.terminal_origin_y;
273 y2 += fb.font_height;
274 for (i = 0; i <= width; i++)
275 gfx_fb_bezier(x1, y1 + i, x2 + i, y1 + i, x2 + i, y2, width-i);
276
277 /* Draw lower right corner. */
278 x1 = row2 * fb.font_width + fb.terminal_origin_x;
279 y1 = col2 * fb.font_height + fb.terminal_origin_y;
280 y1 += fb.font_height - yshift;
281 x2 = row2 * fb.font_width + fb.terminal_origin_x;
282 x2 += fb.font_width - xshift - width;
283 y2 = col2 * fb.font_height + fb.terminal_origin_y;
284 for (i = 0; i <= width; i++)
285 gfx_fb_bezier(x1, y1 - i, x2 + i, y1 - i, x2 + i, y2, width-i);
286 }
287
288 void gfx_fb_line(int x0, int y0, int x1, int y1, int width)
289 {
290 int dx, sx, dy, sy;
291 int err, e2, x2, y2, ed;
292
293 if (fb.fd < 0)
294 return;
295
296 sx = x0 < x1? 1:-1;
297 sy = y0 < y1? 1:-1;
298 dx = abs(x1 - x0);
299 dy = abs(y1 - y0);
300 err = dx - dy;
301 ed = dx + dy == 0 ? 1: isqrt(dx * dx + dy * dy);
302
303 if (dx != 0 && dy != 0)
304 width = (width + 1) >> 1;
305
306 for (;;) {
307 gfx_fb_setpixel(x0, y0);
308 e2 = err;
309 x2 = x0;
310 if ((e2 << 1) >= -dx) { /* x step */
311 e2 += dy;
312 y2 = y0;
313 while (e2 < ed * width && (y1 != y2 || dx > dy)) {
314 y2 += sy;
315 gfx_fb_setpixel(x0, y2);
316 e2 += dx;
317 }
318 if (x0 == x1)
319 break;
320 e2 = err;
321 err -= dy;
322 x0 += sx;
323 }
324 if ((e2 << 1) <= dy) { /* y step */
325 e2 = dx-e2;
326 while (e2 < ed * width && (x1 != x2 || dx < dy)) {
327 x2 += sx;
328 gfx_fb_setpixel(x2, y0);
329 e2 += dy;
330 }
331 if (y0 == y1)
332 break;
333 err += dx;
334 y0 += sy;
335 }
336 }
337 }
338
339 void gfx_fb_bezier(int x0, int y0, int x1, int y1, int x2, int y2, int width)
340 {
341 int sx, sy;
342 int64_t xx, yy, xy;
343 int64_t dx, dy, err, ed, curvature;
344
345 if (fb.fd < 0)
346 return;
347
348 sx = x2 - x1;
349 sy = y2 - y1;
350 xx = x0 - x1;
351 yy = y0 - y1;
352 curvature = xx*sy - yy*sx;
353
354 if (!(xx * sx >= 0 && yy * sy >= 0))
355 printf("sign of gradient must not change\n");
356
357 if (sx * (int64_t)sx + sy * (int64_t)sy > xx * xx + yy * yy) {
358 x2 = x0;
359 x0 = sx + x1;
360 y2 = y0;
361 y0 = sy + y1;
362 curvature = -curvature;
363 }
364 if (curvature != 0) {
365 xx += sx;
366 sx = x0 < x2? 1:-1;
367 xx *= sx;
368 yy += sy;
369 sy = y0 < y2? 1:-1;
370 yy *= sy;
371 xy = 2 * xx * yy;
372 xx *= xx;
373 yy *= yy;
374 if (curvature * sx * sy < 0) {
375 xx = -xx;
376 yy = -yy;
377 xy = -xy;
378 curvature = -curvature;
379 }
380 dx = 4 * sy * curvature * (x1 - x0) + xx - xy;
381 dy = 4 * sx * curvature * (y0 - y1) + yy - xy;
382 xx += xx;
383 yy += yy;
384 err = dx + dy + xy;
385 do {
386 gfx_fb_setpixel(x0, y0);
387
388 if (x0 == x2 && y0 == y2)
389 return; /* last pixel -> curve finished */
390
391 curvature = dx - err;
392 ed = max(dx + xy, -xy - dy);
393 x1 = x0;
394 y1 = 2 * err + dy < 0;
395 if (2 * err + dx > 0) { /* x step */
396 if (err - dy < ed)
397 gfx_fb_setpixel(x0, y0 + sy);
398 x0 += sx;
399 dx -= xy;
400 dy += yy;
401 err += dy;
402 }
403 if (y1 != 0) {
404 if (curvature < ed)
405 gfx_fb_setpixel(x1 + sx, y0);
406 y0 += sy;
407 dy -= xy;
408 dx += xx;
409 err += dx;
410 }
411 } while (dy < dx ); /* gradient negates -> algorithm fails */
412 }
413 gfx_fb_line(x0, y0, x2, y2, width);
414 }
415
416 int
417 gfx_fb_putimage(png_t *png)
418 {
419 uint32_t i, j, col, row, height, width, color;
420 uint8_t r, g, b, a, *p, *data;
421
422 if (fb.fd < 0 || png->color_type != PNG_TRUECOLOR_ALPHA) {
423 return (1);
424 }
425
426 width = png->width;
427 height = png->height;
428 col = fb.fb_width - fb.terminal_origin_x;
429 col -= width;
430 row = fb.fb_height - fb.terminal_origin_y;
431 row -= height;
432
433 data = malloc(width * height * fb.fb_bpp);
434 if (data == NULL)
435 return (1);
436
437 /*
438 * Build image for our framebuffer.
439 */
440 for (i = 0; i < height * width * png->bpp; i += png->bpp) {
441 r = png->image[i];
442 g = png->image[i+1];
443 b = png->image[i+2];
444 a = png->image[i+3];
445
446 j = i / png->bpp * fb.fb_bpp;
447 color = r >> (8 - fb.red_mask_size)
448 << fb.red_field_position;
449 color |= g >> (8 - fb.green_mask_size)
450 << fb.green_field_position;
451 color |= b >> (8 - fb.blue_mask_size)
452 << fb.blue_field_position;
453
454 switch (fb.fb_depth) {
455 case 8: {
456 uint32_t best, dist, k;
457 int diff;
458
459 color = 0;
460 best = 256 * 256 * 256;
461 for (k = 0; k < 16; k++) {
462 diff = r - cmap4_to_24.red[k];
463 dist = diff * diff;
464 diff = g - cmap4_to_24.green[k];
465 dist += diff * diff;
466 diff = b - cmap4_to_24.blue[k];
467 dist += diff * diff;
468
469 if (dist < best) {
470 color = k;
471 best = dist;
472 if (dist == 0)
473 break;
474 }
475 }
476 data[j] = solaris_color_to_pc_color[color];
477 break;
478 }
479 case 15:
480 case 16:
481 *(uint16_t *)(data+j) = color;
482 break;
483 case 24:
484 p = (uint8_t *)&color;
485 data[j] = p[0];
486 data[j+1] = p[1];
487 data[j+2] = p[2];
488 break;
489 case 32:
490 color |= a << 24;
491 *(uint32_t *)(data+j) = color;
492 break;
493 }
494 }
495
496 gfx_fb_cons_display(row, col, width, height, data);
497 free(data);
498 return (0);
499 }
500
501 /*
502 * Implements alpha blending for RGBA data, could use pixels for arguments,
503 * but byte stream seems more generic.
504 * The generic alpha blending is:
505 * blend = alpha * fg + (1.0 - alpha) * bg.
506 * Since our alpha is not from range [0..1], we scale appropriately.
507 */
508 static uint8_t
509 alpha_blend(uint8_t fg, uint8_t bg, uint8_t alpha)
510 {
511 uint16_t blend, h, l;
512
513 /* trivial corner cases */
514 if (alpha == 0)
515 return (bg);
516 if (alpha == 0xFF)
517 return (fg);
518 blend = (alpha * fg + (0xFF - alpha) * bg);
519 /* Division by 0xFF */
520 h = blend >> 8;
521 l = blend & 0xFF;
522 if (h + l >= 0xFF)
523 h++;
524 return (h);
525 }
526
527 /* Copy memory to framebuffer or to memory. */
528 static void
529 bitmap_cpy(uint8_t *dst, uint8_t *src, uint32_t len, int bpp)
530 {
531 uint32_t i;
532 uint8_t a;
533
534 switch (bpp) {
535 case 4:
536 for (i = 0; i < len; i += bpp) {
537 a = src[i+3];
538 dst[i] = alpha_blend(src[i], dst[i], a);
539 dst[i+1] = alpha_blend(src[i+1], dst[i+1], a);
540 dst[i+2] = alpha_blend(src[i+2], dst[i+2], a);
541 dst[i+3] = a;
542 }
543 break;
544 default:
545 (void) memcpy(dst, src, len);
546 break;
547 }
548 }
549
550 /*
551 * gfx_fb_cons_display implements direct draw on frame buffer memory.
552 * It is needed till we have way to send bitmaps to tem, tem already has
553 * function to send data down to framebuffer.
554 */
555 static void
556 gfx_fb_cons_display(uint32_t row, uint32_t col,
557 uint32_t width, uint32_t height, uint8_t *data)
558 {
559 uint32_t size; /* write size per scanline */
560 uint8_t *fbp; /* fb + calculated offset */
561 int i;
562
563 /* make sure we will not write past FB */
564 if (col >= fb.fb_width || row >= fb.fb_height ||
565 col + width > fb.fb_width || row + height > fb.fb_height)
566 return;
567
568 size = width * fb.fb_bpp;
569 fbp = fb.fb_addr + col * fb.fb_bpp + row * fb.fb_pitch;
570
571 /* write all scanlines in rectangle */
572 for (i = 0; i < height; i++) {
573 uint8_t *dest = fbp + i * fb.fb_pitch;
574 uint8_t *src = data + i * size;
575 bitmap_cpy(dest, src, size, fb.fb_bpp);
576 }
577 }