| // Copyright 2016 The Fuchsia Authors |
| // Copyright (c) 2008-2010, 2015 Travis Geiselbrecht |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| /** |
| * @file |
| * @brief Manage graphics console |
| * |
| * This file contains functions to provide stdout to the graphics console. |
| * |
| * @ingroup graphics |
| */ |
| |
| #include <align.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <lib/boot-options/boot-options.h> |
| #include <lib/boot-options/types.h> |
| #include <lib/gfx-font/gfx-font.h> |
| #include <lib/gfx.h> |
| #include <lib/gfxconsole.h> |
| #include <lib/io.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <dev/display.h> |
| |
| #define TEXT_COLOR 0xffffffff |
| #define BACK_COLOR 0xff000000 |
| |
| #define CRASH_TEXT_COLOR 0xffffffff |
| #define CRASH_BACK_COLOR 0xffe000e0 |
| |
| /** @addtogroup graphics |
| * @{ |
| */ |
| |
| /** |
| * @brief Represent state of graphics console |
| */ |
| static struct { |
| // main surface to draw on |
| gfx_surface* surface; |
| // underlying hw surface, if different from above |
| gfx_surface* hw_surface; |
| |
| // surface to do single line sub-region flushing with |
| gfx_surface line; |
| uint linestride; |
| |
| uint rows, columns; |
| uint extray; // extra pixels left over if the rows doesn't fit precisely |
| |
| uint x, y; |
| |
| uint32_t front_color; |
| uint32_t back_color; |
| } gfxconsole; |
| |
| static void draw_char(char c, const gfx_font_t* font) { |
| gfx_putchar(gfxconsole.surface, font, c, gfxconsole.x * font->width, gfxconsole.y * font->height, |
| gfxconsole.front_color, gfxconsole.back_color); |
| } |
| |
| void gfxconsole_putpixel(unsigned x, unsigned y, unsigned color) { |
| gfx_putpixel(gfxconsole.surface, x, y, color); |
| } |
| |
| static const gfx_font_t* font = &gfx_font_9x16; |
| |
| static bool gfxconsole_putc(char c) { |
| static enum { NORMAL, ESCAPE } state = NORMAL; |
| static uint32_t p_num = 0; |
| bool inval = 0; |
| |
| if (state == NORMAL) { |
| switch (c) { |
| case '\r': |
| gfxconsole.x = 0; |
| break; |
| case '\n': |
| gfxconsole.y++; |
| inval = 1; |
| break; |
| case '\b': |
| // back up one character unless we're at the left side |
| if (gfxconsole.x > 0) { |
| gfxconsole.x--; |
| } |
| break; |
| case '\t': |
| gfxconsole.x = ROUNDUP(gfxconsole.x + 1, 8); |
| break; |
| case 0x1b: |
| p_num = 0; |
| state = ESCAPE; |
| break; |
| default: |
| draw_char(c, font); |
| gfxconsole.x++; |
| break; |
| } |
| } else if (state == ESCAPE) { |
| if (c >= '0' && c <= '9') { |
| p_num = (p_num * 10) + (c - '0'); |
| } else if (c == 'D') { |
| if (p_num <= gfxconsole.x) |
| gfxconsole.x -= p_num; |
| state = NORMAL; |
| } else if (c == '[') { |
| // eat this character |
| } else { |
| draw_char(c, font); |
| gfxconsole.x++; |
| state = NORMAL; |
| } |
| } |
| |
| if (gfxconsole.x >= gfxconsole.columns) { |
| gfxconsole.x = 0; |
| gfxconsole.y++; |
| inval = 1; |
| } |
| if (gfxconsole.y >= gfxconsole.rows) { |
| // scroll up |
| gfx_copyrect(gfxconsole.surface, 0, font->height, gfxconsole.surface->width, |
| gfxconsole.surface->height - font->height - gfxconsole.extray, 0, 0); |
| gfxconsole.y--; |
| |
| // clear the bottom line |
| gfx_fillrect(gfxconsole.surface, 0, |
| gfxconsole.surface->height - font->height - gfxconsole.extray, |
| gfxconsole.surface->width, font->height, gfxconsole.back_color); |
| gfx_flush(gfxconsole.surface); |
| inval = 1; |
| } |
| return inval; |
| } |
| |
| static void gfxconsole_print_callback(PrintCallback* cb, ktl::string_view str) { |
| int refresh_full_screen = 0; |
| for (char c : str) { |
| if (c == '\n') |
| gfxconsole_putc('\r'); |
| refresh_full_screen |= gfxconsole_putc(c); |
| } |
| |
| // blit from the software surface to the hardware |
| if (gfxconsole.surface != gfxconsole.hw_surface) { |
| if (refresh_full_screen) { |
| gfx_surface_blend(gfxconsole.hw_surface, gfxconsole.surface, 0, 0); |
| } else { |
| // Only re-blit the active console line. |
| // Since blend only works in whole surfaces, configure a sub-surface |
| // to use as the blend source. |
| gfxconsole.line.ptr = |
| ((uint8_t*)gfxconsole.surface->ptr) + (gfxconsole.y * gfxconsole.linestride); |
| gfx_surface_blend(gfxconsole.hw_surface, &gfxconsole.line, 0, gfxconsole.y * font->height); |
| } |
| gfx_flush(gfxconsole.hw_surface); |
| } else { |
| gfx_flush(gfxconsole.surface); |
| } |
| } |
| |
| static PrintCallback cb{gfxconsole_print_callback}; |
| |
| static void gfxconsole_setup(gfx_surface* surface, gfx_surface* hw_surface) { |
| switch (gBootOptions->gfx_console_font) { |
| case GfxConsoleFont::k9x16: |
| font = &gfx_font_9x16; |
| break; |
| case GfxConsoleFont::k18x32: |
| font = &gfx_font_18x32; |
| break; |
| } |
| |
| // set up the surface |
| gfxconsole.surface = surface; |
| gfxconsole.hw_surface = hw_surface; |
| |
| // set up line-height sub-surface, for line-only invalidation |
| memcpy(&gfxconsole.line, surface, sizeof(*surface)); |
| gfxconsole.line.height = font->height; |
| gfxconsole.linestride = surface->stride * surface->pixelsize * font->height; |
| |
| // calculate how many rows/columns we have |
| gfxconsole.rows = surface->height / font->height; |
| gfxconsole.columns = surface->width / font->width; |
| gfxconsole.extray = surface->height - (gfxconsole.rows * font->height); |
| |
| dprintf(SPEW, "gfxconsole: rows %u, columns %u, extray %u\n", gfxconsole.rows, gfxconsole.columns, |
| gfxconsole.extray); |
| } |
| |
| static void gfxconsole_clear(bool crash_console) { |
| // start in the upper left |
| gfxconsole.x = 0; |
| gfxconsole.y = 0; |
| |
| if (crash_console) { |
| gfxconsole.front_color = CRASH_TEXT_COLOR; |
| gfxconsole.back_color = CRASH_BACK_COLOR; |
| } else { |
| gfxconsole.front_color = TEXT_COLOR; |
| gfxconsole.back_color = BACK_COLOR; |
| } |
| |
| // fill screen with back color |
| gfx_fillrect(gfxconsole.surface, 0, 0, gfxconsole.surface->width, gfxconsole.surface->height, |
| gfxconsole.back_color); |
| gfx_flush(gfxconsole.surface); |
| } |
| |
| /** |
| * @brief Initialize graphics console on given drawing surface. |
| * |
| * The graphics console subsystem is initialized, and registered as |
| * an output device for debug output. |
| */ |
| void gfxconsole_start(gfx_surface* surface, gfx_surface* hw_surface) { |
| DEBUG_ASSERT(gfxconsole.surface == NULL); |
| |
| gfxconsole_setup(surface, hw_surface); |
| gfxconsole_clear(false); |
| |
| // register for debug callbacks |
| register_print_callback(&cb); |
| } |
| |
| static gfx_surface hw_surface; |
| static gfx_surface sw_surface; |
| static struct display_info dispinfo; |
| |
| zx_status_t gfxconsole_display_get_info(struct display_info* info) { |
| if (gfxconsole.surface) { |
| memcpy(info, &dispinfo, sizeof(*info)); |
| return 0; |
| } else { |
| return -1; |
| } |
| } |
| |
| /** |
| * @brief Initialize graphics console and bind to a display |
| * |
| * If the display was previously initialized, first it is shut down and |
| * detached from the print callback. |
| * |
| * If the new display_info is NULL, nothing else is done, otherwise the |
| * display is initialized against the provided display_info. |
| * |
| * If raw_sw_fb is non-NULL it is a memory large enough to be a backing |
| * surface (stride * height * pixelsize) for the provided hardware display. |
| * This is used for very early framebuffer init before the heap is alive. |
| */ |
| void gfxconsole_bind_display(struct display_info* info, void* raw_sw_fb) { |
| static bool active = false; |
| bool same_as_before = false; |
| struct gfx_surface hw; |
| |
| if (active) { |
| // on re-init or detach, we need to unhook from print callbacks |
| active = false; |
| unregister_print_callback(&cb); |
| } |
| if (info == NULL) { |
| return; |
| } |
| |
| if (gfx_init_surface_from_display(&hw, info)) { |
| return; |
| } |
| if (info->flags & DISPLAY_FLAG_CRASH_FRAMEBUFFER) { |
| // "bluescreen" path. no allocations allowed |
| memcpy(&hw_surface, &hw, sizeof(hw)); |
| gfxconsole_setup(&hw_surface, &hw_surface); |
| memcpy(&dispinfo, info, sizeof(*info)); |
| gfxconsole_clear(true); |
| register_print_callback(&cb); |
| active = true; |
| return; |
| } |
| if ((hw.format == hw_surface.format) && (hw.width == hw_surface.width) && |
| (hw.height == hw_surface.height) && (hw.stride == hw_surface.stride) && |
| (hw.pixelsize == hw_surface.pixelsize)) { |
| // we are binding to a new hw surface with the same properties |
| // as the existing one |
| same_as_before = true; |
| } else { |
| // we cannot re-use the sw backing surface, so destroy it |
| if (sw_surface.ptr && (sw_surface.flags & GFX_FLAG_FREE_ON_DESTROY)) { |
| free(sw_surface.ptr); |
| } |
| memset(&sw_surface, 0, sizeof(sw_surface)); |
| } |
| memcpy(&hw_surface, &hw, sizeof(hw)); |
| |
| gfx_surface* s = &hw_surface; |
| if (info->flags & DISPLAY_FLAG_HW_FRAMEBUFFER) { |
| if (!same_as_before) { |
| // we can't re-use the existing sw_surface, create a new one |
| if (gfx_init_surface(&sw_surface, raw_sw_fb, hw_surface.width, hw_surface.height, |
| hw_surface.stride, hw_surface.format, 0)) { |
| return; |
| } |
| } |
| s = &sw_surface; |
| } else { |
| // for non-hw surfaces we're not using a backing surface |
| // so we can't be the same as before and must fully init |
| same_as_before = false; |
| } |
| |
| gfxconsole_setup(s, &hw_surface); |
| |
| if (!same_as_before) { |
| // on first init, or different-backing-buffer re-init |
| // we clear and reset to x,y @ 0,0 |
| gfxconsole_clear(false); |
| } |
| |
| memcpy(&dispinfo, info, sizeof(*info)); |
| register_print_callback(&cb); |
| active = true; |
| } |
| |
| void gfxconsole_flush() { |
| if (gfxconsole.surface != gfxconsole.hw_surface) { |
| gfx_surface_blend(gfxconsole.hw_surface, gfxconsole.surface, 0, 0); |
| gfx_flush(gfxconsole.hw_surface); |
| } else { |
| gfx_flush(gfxconsole.surface); |
| } |
| } |