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 */
41 static 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
draw_char(char c,const struct gfx_font * font)60 static 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
gfxconsole_putpixel(unsigned x,unsigned y,unsigned color)66 void gfxconsole_putpixel(unsigned x, unsigned y, unsigned color) {
67 gfx_putpixel(gfxconsole.surface, x, y, color);
68 }
69
70 static const struct gfx_font* font = &font_9x16;
71
gfxconsole_putc(char c)72 static 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
gfxconsole_print_callback(print_callback_t * cb,const char * str,size_t len)141 static 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
168 static print_callback_t cb = {
169 .entry = {},
170 .print = gfxconsole_print_callback,
171 .context = NULL};
172
gfxconsole_setup(gfx_surface * surface,gfx_surface * hw_surface)173 static 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
gfxconsole_clear(bool crash_console)200 static 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 */
gfxconsole_start(gfx_surface * surface,gfx_surface * hw_surface)225 void 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
235 static gfx_surface hw_surface;
236 static gfx_surface sw_surface;
237 static struct display_info dispinfo;
238
gfxconsole_display_get_info(struct display_info * info)239 zx_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 */
gfxconsole_bind_display(struct display_info * info,void * raw_sw_fb)261 void 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
gfxconsole_flush()332 void 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