1// Copyright 2016 The Fuchsia Authors 2// Copyright (c) 2008-2010, 2015 Travis Geiselbrecht 3// 4// Use of this source code is governed by a MIT-style 5// license that can be found in the LICENSE file or at 6// https://opensource.org/licenses/MIT 7 8/** 9 * @file 10 * @brief Manage graphics console 11 * 12 * This file contains functions to provide stdout to the graphics console. 13 * 14 * @ingroup graphics 15 */ 16 17#include <lib/gfxconsole.h> 18 19#include <assert.h> 20#include <debug.h> 21#include <dev/display.h> 22#include <kernel/cmdline.h> 23#include <lib/gfx.h> 24#include <lib/io.h> 25#include <stdlib.h> 26#include <string.h> 27 28#define TEXT_COLOR 0xffffffff 29#define BACK_COLOR 0xff000000 30 31#define CRASH_TEXT_COLOR 0xffffffff 32#define CRASH_BACK_COLOR 0xffe000e0 33 34/** @addtogroup graphics 35 * @{ 36 */ 37 38/** 39 * @brief Represent state of graphics console 40 */ 41static struct { 42 // main surface to draw on 43 gfx_surface* surface; 44 // underlying hw surface, if different from above 45 gfx_surface* hw_surface; 46 47 // surface to do single line sub-region flushing with 48 gfx_surface line; 49 uint linestride; 50 51 uint rows, columns; 52 uint extray; // extra pixels left over if the rows doesn't fit precisely 53 54 uint x, y; 55 56 uint32_t front_color; 57 uint32_t back_color; 58} gfxconsole; 59 60static void draw_char(char c, const struct gfx_font* font) { 61 gfx_putchar(gfxconsole.surface, font, c, 62 gfxconsole.x * font->width, gfxconsole.y * font->height, 63 gfxconsole.front_color, gfxconsole.back_color); 64} 65 66void gfxconsole_putpixel(unsigned x, unsigned y, unsigned color) { 67 gfx_putpixel(gfxconsole.surface, x, y, color); 68} 69 70static const struct gfx_font* font = &font_9x16; 71 72static bool gfxconsole_putc(char c) { 73 static enum { NORMAL, 74 ESCAPE } state = NORMAL; 75 static uint32_t p_num = 0; 76 bool inval = 0; 77 78 if (state == NORMAL) { 79 switch (c) { 80 case '\r': 81 gfxconsole.x = 0; 82 break; 83 case '\n': 84 gfxconsole.y++; 85 inval = 1; 86 break; 87 case '\b': 88 // back up one character unless we're at the left side 89 if (gfxconsole.x > 0) { 90 gfxconsole.x--; 91 } 92 break; 93 case '\t': 94 gfxconsole.x = ROUNDUP(gfxconsole.x + 1, 8); 95 break; 96 case 0x1b: 97 p_num = 0; 98 state = ESCAPE; 99 break; 100 default: 101 draw_char(c, font); 102 gfxconsole.x++; 103 break; 104 } 105 } else if (state == ESCAPE) { 106 if (c >= '0' && c <= '9') { 107 p_num = (p_num * 10) + (c - '0'); 108 } else if (c == 'D') { 109 if (p_num <= gfxconsole.x) 110 gfxconsole.x -= p_num; 111 state = NORMAL; 112 } else if (c == '[') { 113 // eat this character 114 } else { 115 draw_char(c, font); 116 gfxconsole.x++; 117 state = NORMAL; 118 } 119 } 120 121 if (gfxconsole.x >= gfxconsole.columns) { 122 gfxconsole.x = 0; 123 gfxconsole.y++; 124 inval = 1; 125 } 126 if (gfxconsole.y >= gfxconsole.rows) { 127 // scroll up 128 gfx_copyrect(gfxconsole.surface, 0, font->height, gfxconsole.surface->width, 129 gfxconsole.surface->height - font->height - gfxconsole.extray, 0, 0); 130 gfxconsole.y--; 131 132 // clear the bottom line 133 gfx_fillrect(gfxconsole.surface, 0, gfxconsole.surface->height - font->height - gfxconsole.extray, 134 gfxconsole.surface->width, font->height, gfxconsole.back_color); 135 gfx_flush(gfxconsole.surface); 136 inval = 1; 137 } 138 return inval; 139} 140 141static void gfxconsole_print_callback(print_callback_t* cb, const char* str, size_t len) { 142 int refresh_full_screen = 0; 143 for (size_t i = 0; i < len; i++) { 144 if (str[i] == '\n') 145 gfxconsole_putc('\r'); 146 refresh_full_screen |= gfxconsole_putc(str[i]); 147 } 148 149 // blit from the software surface to the hardware 150 if (gfxconsole.surface != gfxconsole.hw_surface) { 151 if (refresh_full_screen) { 152 gfx_surface_blend(gfxconsole.hw_surface, gfxconsole.surface, 0, 0); 153 } else { 154 // Only re-blit the active console line. 155 // Since blend only works in whole surfaces, configure a sub-surface 156 // to use as the blend source. 157 gfxconsole.line.ptr = ((uint8_t*)gfxconsole.surface->ptr) + 158 (gfxconsole.y * gfxconsole.linestride); 159 gfx_surface_blend(gfxconsole.hw_surface, &gfxconsole.line, 160 0, gfxconsole.y * font->height); 161 } 162 gfx_flush(gfxconsole.hw_surface); 163 } else { 164 gfx_flush(gfxconsole.surface); 165 } 166} 167 168static print_callback_t cb = { 169 .entry = {}, 170 .print = gfxconsole_print_callback, 171 .context = NULL}; 172 173static void gfxconsole_setup(gfx_surface* surface, gfx_surface* hw_surface) { 174 const char* fname = cmdline_get("gfxconsole.font"); 175 if (fname != NULL) { 176 if (!strcmp(fname, "18x32")) { 177 font = &font_18x32; 178 } else if (!strcmp(fname, "9x16")) { 179 font = &font_9x16; 180 } 181 } 182 // set up the surface 183 gfxconsole.surface = surface; 184 gfxconsole.hw_surface = hw_surface; 185 186 // set up line-height sub-surface, for line-only invalidation 187 memcpy(&gfxconsole.line, surface, sizeof(*surface)); 188 gfxconsole.line.height = font->height; 189 gfxconsole.linestride = surface->stride * surface->pixelsize * font->height; 190 191 // calculate how many rows/columns we have 192 gfxconsole.rows = surface->height / font->height; 193 gfxconsole.columns = surface->width / font->width; 194 gfxconsole.extray = surface->height - (gfxconsole.rows * font->height); 195 196 dprintf(SPEW, "gfxconsole: rows %u, columns %u, extray %u\n", gfxconsole.rows, 197 gfxconsole.columns, gfxconsole.extray); 198} 199 200static void gfxconsole_clear(bool crash_console) { 201 // start in the upper left 202 gfxconsole.x = 0; 203 gfxconsole.y = 0; 204 205 if (crash_console) { 206 gfxconsole.front_color = CRASH_TEXT_COLOR; 207 gfxconsole.back_color = CRASH_BACK_COLOR; 208 } else { 209 gfxconsole.front_color = TEXT_COLOR; 210 gfxconsole.back_color = BACK_COLOR; 211 } 212 213 // fill screen with back color 214 gfx_fillrect(gfxconsole.surface, 0, 0, gfxconsole.surface->width, gfxconsole.surface->height, 215 gfxconsole.back_color); 216 gfx_flush(gfxconsole.surface); 217} 218 219/** 220 * @brief Initialize graphics console on given drawing surface. 221 * 222 * The graphics console subsystem is initialized, and registered as 223 * an output device for debug output. 224 */ 225void gfxconsole_start(gfx_surface* surface, gfx_surface* hw_surface) { 226 DEBUG_ASSERT(gfxconsole.surface == NULL); 227 228 gfxconsole_setup(surface, hw_surface); 229 gfxconsole_clear(false); 230 231 // register for debug callbacks 232 register_print_callback(&cb); 233} 234 235static gfx_surface hw_surface; 236static gfx_surface sw_surface; 237static struct display_info dispinfo; 238 239zx_status_t gfxconsole_display_get_info(struct display_info* info) { 240 if (gfxconsole.surface) { 241 memcpy(info, &dispinfo, sizeof(*info)); 242 return 0; 243 } else { 244 return -1; 245 } 246} 247 248/** 249 * @brief Initialize graphics console and bind to a display 250 * 251 * If the display was previously initialized, first it is shut down and 252 * detached from the print callback. 253 * 254 * If the new display_info is NULL, nothing else is done, otherwise the 255 * display is initialized against the provided display_info. 256 * 257 * If raw_sw_fb is non-NULL it is a memory large enough to be a backing 258 * surface (stride * height * pixelsize) for the provided hardware display. 259 * This is used for very early framebuffer init before the heap is alive. 260 */ 261void gfxconsole_bind_display(struct display_info* info, void* raw_sw_fb) { 262 static bool active = false; 263 bool same_as_before = false; 264 struct gfx_surface hw; 265 266 if (active) { 267 // on re-init or detach, we need to unhook from print callbacks 268 active = false; 269 unregister_print_callback(&cb); 270 } 271 if (info == NULL) { 272 return; 273 } 274 275 if (gfx_init_surface_from_display(&hw, info)) { 276 return; 277 } 278 if (info->flags & DISPLAY_FLAG_CRASH_FRAMEBUFFER) { 279 // "bluescreen" path. no allocations allowed 280 memcpy(&hw_surface, &hw, sizeof(hw)); 281 gfxconsole_setup(&hw_surface, &hw_surface); 282 memcpy(&dispinfo, info, sizeof(*info)); 283 gfxconsole_clear(true); 284 register_print_callback(&cb); 285 active = true; 286 return; 287 } 288 if ((hw.format == hw_surface.format) && (hw.width == hw_surface.width) && 289 (hw.height == hw_surface.height) && (hw.stride == hw_surface.stride) && 290 (hw.pixelsize == hw_surface.pixelsize)) { 291 // we are binding to a new hw surface with the same properties 292 // as the existing one 293 same_as_before = true; 294 } else { 295 // we cannot re-use the sw backing surface, so destroy it 296 if (sw_surface.ptr && (sw_surface.flags & GFX_FLAG_FREE_ON_DESTROY)) { 297 free(sw_surface.ptr); 298 } 299 memset(&sw_surface, 0, sizeof(sw_surface)); 300 } 301 memcpy(&hw_surface, &hw, sizeof(hw)); 302 303 gfx_surface* s = &hw_surface; 304 if (info->flags & DISPLAY_FLAG_HW_FRAMEBUFFER) { 305 if (!same_as_before) { 306 // we can't re-use the existing sw_surface, create a new one 307 if (gfx_init_surface(&sw_surface, raw_sw_fb, hw_surface.width, 308 hw_surface.height, hw_surface.stride, hw_surface.format, 0)) { 309 return; 310 } 311 } 312 s = &sw_surface; 313 } else { 314 // for non-hw surfaces we're not using a backing surface 315 // so we can't be the same as before and must fully init 316 same_as_before = false; 317 } 318 319 gfxconsole_setup(s, &hw_surface); 320 321 if (!same_as_before) { 322 // on first init, or different-backing-buffer re-init 323 // we clear and reset to x,y @ 0,0 324 gfxconsole_clear(false); 325 } 326 327 memcpy(&dispinfo, info, sizeof(*info)); 328 register_print_callback(&cb); 329 active = true; 330} 331 332void gfxconsole_flush() { 333 if (gfxconsole.surface != gfxconsole.hw_surface) { 334 gfx_surface_blend(gfxconsole.hw_surface, gfxconsole.surface, 0, 0); 335 gfx_flush(gfxconsole.hw_surface); 336 } else { 337 gfx_flush(gfxconsole.surface); 338 } 339} 340