mirror of
https://github.com/encounter/osdev.git
synced 2026-07-10 12:18:43 -07:00
281 lines
9.9 KiB
C
281 lines
9.9 KiB
C
#include "vga.h"
|
|
#include "ports.h"
|
|
#include "../console.h"
|
|
#include "../kmalloc.h"
|
|
#include "../psf.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <errno.h>
|
|
|
|
// EGA text console
|
|
uint16_t *console_fb_addr = NULL;
|
|
|
|
// RGB text console
|
|
void *rgb_fb_addr = NULL;
|
|
framebuffer_info_t rgb_fb_info = {};
|
|
|
|
psf_font_t rgb_console_font = {};
|
|
void *rgb_console_font_glyphs = NULL;
|
|
|
|
unsigned int rgb_console_rows = 0;
|
|
unsigned int rgb_console_cols = 0;
|
|
int rgb_console_offset = 0;
|
|
|
|
typedef struct _packed rgb_console_buffer_entry {
|
|
vga_rgb_color_t fg;
|
|
_unused uint8_t __padding;
|
|
vga_rgb_color_t bg;
|
|
char c;
|
|
} rgb_console_buffer_entry_t;
|
|
|
|
rgb_console_buffer_entry_t *rgb_console_buffer = NULL;
|
|
|
|
static void vga_console_repaint();
|
|
|
|
void vga_init(void *fb_addr, uint8_t type, framebuffer_info_t *fb_info) {
|
|
if (type == VGA_FB_TYPE_EGA_TEXT) {
|
|
console_fb_addr = fb_addr;
|
|
} else if (type == VGA_FB_TYPE_RGB) {
|
|
rgb_fb_addr = fb_addr;
|
|
rgb_fb_info = *fb_info;
|
|
}
|
|
}
|
|
|
|
int vga_load_font(const char *filename) {
|
|
if (rgb_fb_addr == NULL) return 1;
|
|
|
|
int prev_width = 0, prev_height = 0;
|
|
void *old_glyphs = rgb_console_font_glyphs;
|
|
if (old_glyphs != NULL) {
|
|
prev_width = rgb_console_font.width;
|
|
prev_height = rgb_console_font.height;
|
|
}
|
|
|
|
rgb_console_font_glyphs = psf_read_font(filename, &rgb_console_font);
|
|
if (rgb_console_font_glyphs == NULL) {
|
|
rgb_console_font_glyphs = old_glyphs;
|
|
fprintf(stderr, "Failed to read font %s (err: %d)\n", filename, errno);
|
|
return 2;
|
|
}
|
|
kfree(old_glyphs);
|
|
|
|
rgb_console_cols = rgb_fb_info.width / rgb_console_font.width;
|
|
rgb_console_rows = rgb_fb_info.height / rgb_console_font.height;
|
|
|
|
size_t buffer_size = rgb_console_rows * rgb_console_cols * sizeof(rgb_console_buffer_entry_t);
|
|
rgb_console_buffer_entry_t *new_buffer = kmalloc(buffer_size);
|
|
if (new_buffer == NULL) {
|
|
errno = ENOMEM;
|
|
fprintf(stderr, "Failed to allocate memory for console buffer.\n");
|
|
return 1;
|
|
}
|
|
memset(new_buffer, 0, buffer_size);
|
|
|
|
if (rgb_console_buffer != NULL) {
|
|
size_t prev_size = (rgb_fb_info.width / prev_width)
|
|
* (rgb_fb_info.height / prev_height)
|
|
* sizeof(rgb_console_buffer_entry_t);
|
|
if (prev_size > buffer_size) {
|
|
size_t diff = prev_size - buffer_size;
|
|
memcpy(new_buffer, (void *) rgb_console_buffer + diff, buffer_size);
|
|
// FIXME refactor to avoid this kludgy division
|
|
rgb_console_offset -= diff / sizeof(rgb_console_buffer_entry_t);
|
|
if (rgb_console_offset < 0) rgb_console_offset = 0;
|
|
} else {
|
|
memcpy(new_buffer, rgb_console_buffer, prev_size);
|
|
memset((void *) new_buffer + prev_size, 0, buffer_size - prev_size);
|
|
}
|
|
kfree(rgb_console_buffer);
|
|
}
|
|
rgb_console_buffer = new_buffer;
|
|
vga_console_repaint();
|
|
return 0;
|
|
}
|
|
|
|
static inline void vga_set_pixel(int x, int y, uint32_t val) {
|
|
*(uint32_t *) (rgb_fb_addr + x * 4 + rgb_fb_info.pitch * y) = val;
|
|
}
|
|
|
|
void vga_fill_rect(int x1, int y1, int x2, int y2, vga_rgb_color_t *color) {
|
|
if (rgb_fb_addr == NULL || rgb_fb_info.bpp != 32) return;
|
|
uint32_t val = ((uint32_t) color->red << rgb_fb_info.red_field_position)
|
|
| ((uint32_t) color->green << rgb_fb_info.green_field_position)
|
|
| ((uint32_t) color->blue << rgb_fb_info.blue_field_position);
|
|
|
|
int xmin = MAX(MIN(x1, x2), 0);
|
|
int xmax = MIN(MAX(x1, x2), rgb_fb_info.width);
|
|
int ymin = MAX(MIN(y1, y2), 0);
|
|
int ymax = MIN(MAX(y1, y2), rgb_fb_info.height);
|
|
for (int y = ymin; y < ymax; y++) {
|
|
for (int x = xmin; x < xmax; x++) {
|
|
vga_set_pixel(x, y, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
void vga_display_image_bgra(int x, int y, bmp_info_header_t *header, uint8_t *image) {
|
|
if (rgb_fb_addr == NULL || rgb_fb_info.bpp != 32) return;
|
|
|
|
int xmax = MIN(x + header->width, rgb_fb_info.width);
|
|
int ymax = MIN(y + header->height, rgb_fb_info.height);
|
|
for (int y1 = y; y1 < ymax; y1++) {
|
|
for (int x1 = x; x1 < xmax; x1++) {
|
|
uint8_t *pixel = image + ((x1 + ((header->height - y1) * header->width)) * 4);
|
|
vga_set_pixel(x1, y1, (uint32_t) *pixel << rgb_fb_info.blue_field_position
|
|
| (uint32_t) *(pixel + 1) << rgb_fb_info.green_field_position
|
|
| (uint32_t) *(pixel + 2) << rgb_fb_info.red_field_position);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline uint32_t vga_rgb_color(vga_rgb_color_t *color) {
|
|
return ((uint32_t) color->red << rgb_fb_info.red_field_position)
|
|
| ((uint32_t) color->green << rgb_fb_info.green_field_position)
|
|
| ((uint32_t) color->blue << rgb_fb_info.blue_field_position);
|
|
}
|
|
|
|
static void vga_print_glyph(int offset, char c, vga_rgb_color_t *fg_color, vga_rgb_color_t *bg_color) {
|
|
if (rgb_fb_addr == NULL || rgb_fb_info.bpp != 32) return;
|
|
|
|
int row = offset / rgb_console_cols;
|
|
int x = (offset % rgb_console_cols) * rgb_console_font.width;
|
|
int y = row * rgb_console_font.height;
|
|
|
|
uint32_t fg_val = vga_rgb_color(fg_color);
|
|
uint32_t bg_val = vga_rgb_color(bg_color);
|
|
|
|
int xmax = MIN(x + rgb_console_font.width, rgb_fb_info.width);
|
|
int ymax = MIN(y + rgb_console_font.height, rgb_fb_info.height);
|
|
uint8_t *glyph = rgb_console_font_glyphs + (rgb_console_font.bytes_per_glyph * (uint8_t) c);
|
|
|
|
for (int y1 = y; y1 < ymax; y1++) {
|
|
for (int x1 = x; x1 < xmax; x1++) {
|
|
uint8_t gb = *(glyph + (y1 - y));
|
|
vga_set_pixel(x1, y1, (gb >> (8 - (x1 - x))) & 1 ? fg_val : bg_val);
|
|
}
|
|
}
|
|
}
|
|
|
|
int vga_print_char(char c, int offset, char attr) {
|
|
if (rgb_fb_addr != NULL) {
|
|
if (rgb_console_font_glyphs == NULL) return 0;
|
|
if (offset < 0) {
|
|
offset = rgb_console_offset;
|
|
} else if (offset >= rgb_console_rows * rgb_console_cols) {
|
|
return rgb_console_offset;
|
|
}
|
|
|
|
if (c == '\b') {
|
|
offset--;
|
|
} else if (c == '\n') {
|
|
unsigned int row = offset / rgb_console_cols;
|
|
offset = (row + 1) * rgb_console_cols;
|
|
} else {
|
|
vga_rgb_color_t fg_color = {255, 255, 255};
|
|
vga_rgb_color_t bg_color = {0, 0, 0};
|
|
|
|
// FIXME make more dynamic
|
|
if (attr == RED_ON_BLACK) {
|
|
fg_color = (vga_rgb_color_t) {255, 0, 0};
|
|
bg_color = (vga_rgb_color_t) {0, 0, 0};
|
|
}
|
|
|
|
rgb_console_buffer[offset] = (rgb_console_buffer_entry_t) {.c = c, .fg = fg_color, .bg = bg_color};
|
|
if (!c) c = ' ';
|
|
vga_print_glyph(offset++, c, &fg_color, &bg_color);
|
|
}
|
|
rgb_console_offset = offset = vga_handle_scrolling(offset);
|
|
} else if (console_fb_addr != NULL) {
|
|
if (!attr) attr = WHITE_ON_BLACK;
|
|
|
|
if (offset < 0) {
|
|
offset = vga_get_cursor_offset();
|
|
} else if (offset >= MAX_ROWS * MAX_COLS) {
|
|
console_fb_addr[MAX_COLS * MAX_ROWS - 1] = vga_entry('E', RED_ON_WHITE);
|
|
return vga_get_cursor_offset();
|
|
}
|
|
|
|
if (c == '\b') {
|
|
offset--;
|
|
} else if (c == '\n') {
|
|
offset = vga_get_offset(0, vga_get_offset_row(offset) + 1);
|
|
} else {
|
|
console_fb_addr[offset++] = ((uint16_t) attr << 8 | c);
|
|
}
|
|
vga_set_cursor_offset(offset = vga_handle_scrolling(offset));
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
int vga_get_cursor_offset() {
|
|
if (console_fb_addr == NULL) return 0;
|
|
|
|
port_byte_out(REG_SCREEN_CTRL, 14);
|
|
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
|
|
port_byte_out(REG_SCREEN_CTRL, 15);
|
|
offset += port_byte_in(REG_SCREEN_DATA);
|
|
return offset;
|
|
}
|
|
|
|
void vga_set_cursor_offset(int offset) {
|
|
if (console_fb_addr == NULL) return;
|
|
|
|
port_byte_out(REG_SCREEN_CTRL, 14);
|
|
port_byte_out(REG_SCREEN_DATA, (unsigned char) (offset >> 8));
|
|
port_byte_out(REG_SCREEN_CTRL, 15);
|
|
port_byte_out(REG_SCREEN_DATA, (unsigned char) (offset & 0xff));
|
|
}
|
|
|
|
void vga_clear_screen() {
|
|
if (console_fb_addr != NULL) {
|
|
memset(console_fb_addr, 0, MAX_COLS * MAX_ROWS * sizeof(*console_fb_addr));
|
|
vga_set_cursor_offset(vga_get_offset(0, 0));
|
|
} else if (rgb_fb_addr != NULL) {
|
|
vga_fill_rect(0, 0, rgb_fb_info.width, rgb_fb_info.height, &(vga_rgb_color_t) {0, 0, 0});
|
|
memset(rgb_console_buffer, 0, rgb_console_rows * rgb_console_cols * sizeof(rgb_console_buffer_entry_t));
|
|
rgb_console_offset = 0;
|
|
}
|
|
}
|
|
|
|
static void vga_console_repaint() {
|
|
for (size_t i = 0; i < rgb_console_rows * rgb_console_cols; ++i) {
|
|
rgb_console_buffer_entry_t *entry = &rgb_console_buffer[i];
|
|
char c = entry->c;
|
|
if (!c) c = ' '; // FIXME improve
|
|
vga_print_glyph(i, c, &entry->fg, &entry->bg);
|
|
}
|
|
}
|
|
|
|
// Advance the text cursor, scrolling the video buffer if necessary.
|
|
int vga_handle_scrolling(int cursor_offset) {
|
|
if (console_fb_addr != NULL) {
|
|
if (cursor_offset < MAX_ROWS * MAX_COLS)
|
|
return cursor_offset;
|
|
|
|
for (int i = 1; i < MAX_ROWS; i++) {
|
|
memcpy(console_fb_addr + vga_get_offset(0, i - 1),
|
|
console_fb_addr + vga_get_offset(0, i),
|
|
MAX_COLS * sizeof(*console_fb_addr));
|
|
}
|
|
|
|
memset(console_fb_addr + vga_get_offset(0, MAX_ROWS - 1),
|
|
0, MAX_COLS * sizeof(*console_fb_addr));
|
|
|
|
cursor_offset -= MAX_COLS;
|
|
} else if (rgb_fb_addr != NULL) {
|
|
if (cursor_offset < rgb_console_rows * rgb_console_cols)
|
|
return cursor_offset;
|
|
|
|
size_t end_offset = (rgb_console_rows - 1) * rgb_console_cols;
|
|
memmove(rgb_console_buffer, rgb_console_buffer + rgb_console_cols,
|
|
end_offset * sizeof(rgb_console_buffer_entry_t));
|
|
memset(rgb_console_buffer + end_offset, 0, rgb_console_cols * sizeof(rgb_console_buffer_entry_t));
|
|
cursor_offset -= rgb_console_cols;
|
|
vga_console_repaint();
|
|
}
|
|
|
|
return cursor_offset;
|
|
} |