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 }