blob: 864b2093670099f8731efeb254e03e20fb32a9d6 [file] [log] [blame]
// 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/cmdline.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 struct gfx_font* 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 struct gfx_font* 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) {
const char* fname = gCmdline.GetString(kernel_option::kGfxConsoleFont);
if (fname != NULL) {
if (!strcmp(fname, "18x32")) {
font = &gfx_font_18x32;
} else if (!strcmp(fname, "9x16")) {
font = &gfx_font_9x16;
}
}
// 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);
}
}