text: // 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/console.h>
  #include <lib/gfx/gfx.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
  
  namespace gfx {
  
  /** @addtogroup graphics
   * @{
   */
  
  /**
   * @brief  Represent state of graphics console
   */
  static struct {
    // main surface to draw on
    Surface* surface;
    // underlying hw surface, if different from above
    Surface* hw_surface;
  
    // surface to do single line sub-region flushing with
    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) {
    PutChar(gfxconsole.surface, font, c, gfxconsole.x * font->width, gfxconsole.y * font->height,
            gfxconsole.front_color, gfxconsole.back_color);
  }
  
  void ConsolePutPixel(unsigned x, unsigned y, unsigned color) {
    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
      CopyRectangle(gfxconsole.surface, 0, font->height, gfxconsole.surface->width,
                    gfxconsole.surface->height - font->height - gfxconsole.extray, 0, 0);
      gfxconsole.y--;
  
      // clear the bottom line
      FillRectangle(gfxconsole.surface, 0,
                    gfxconsole.surface->height - font->height - gfxconsole.extray,
                    gfxconsole.surface->width, font->height, gfxconsole.back_color);
      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) {
        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);
        Blend(gfxconsole.hw_surface, &gfxconsole.line, 0, gfxconsole.y * font->height);
      }
      Flush(gfxconsole.hw_surface);
    } else {
      Flush(gfxconsole.surface);
    }
  }
  
  static PrintCallback cb{gfxconsole_print_callback};
  
  static void gfxconsole_setup(Surface* surface, 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
    FillRectangle(gfxconsole.surface, 0, 0, gfxconsole.surface->width, gfxconsole.surface->height,
                  gfxconsole.back_color);
    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 ConsoleStart(Surface* surface, 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 Surface hw_surface;
  static Surface sw_surface;
  static display_info dispinfo;
  
  zx_status_t ConsoleDisplayGetInfo(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 ConsoleBindDisplay(display_info* info, void* raw_sw_fb) {
    static bool active = false;
    bool same_as_before = false;
    struct 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 (InitSurfaceFromDisplay(&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));
  
    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 (InitSurface(&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 ConsoleFlush() {
    if (gfxconsole.surface != gfxconsole.hw_surface) {
      Blend(gfxconsole.hw_surface, gfxconsole.surface, 0, 0);
      Flush(gfxconsole.hw_surface);
    } else {
      Flush(gfxconsole.surface);
    }
  }
  
  }  // namespace gfx
  