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