Files
MentOS/mentos/src/io/vga/vga.c
T
Enrico Fraccaroli (Galfurian) 36aa392534 Fix VGA drawing functions.
2022-07-13 11:53:13 -04:00

753 lines
20 KiB
C

/// @file vga.c
/// @brief Implementation of Video Graphics Array (VGA) drivers.
/// @copyright (c) 2014-2022 This file is distributed under the MIT License.
/// See LICENSE.md for details.
// Include the kernel log levels.
#include "sys/kernel_levels.h"
/// Change the header.
#define __DEBUG_HEADER__ "[VGA ]"
/// Set the log level.
#define __DEBUG_LEVEL__ LOGLEVEL_NOTICE
#include "io/vga/vga.h"
#include "io/vga/vga_palette.h"
#include "io/vga/vga_mode.h"
#include "io/vga/vga_font.h"
#include "hardware/timer.h"
#include "io/port_io.h"
#include "io/debug.h"
#include "stdbool.h"
#include "string.h"
#include "math.h"
/// Counts the number of elements of an array.
#define COUNT_OF(x) ((sizeof(x) / sizeof(0 [x])) / ((size_t)(!(sizeof(x) % sizeof(0 [x])))))
/// Attribute Controller index port.
#define AC_INDEX 0x03C0
/// Attribute Controller write port.
#define AC_WRITE 0x03C0
/// Attribute Controller data port.
#define AC_READ 0x03C1
/// Miscellaneous output register.
#define MISC_WRITE 0x03C2
/// Miscellaneous input register.
#define MISC_READ 0x03CC
/// Sequence controller index.
#define SC_INDEX 0x03C4
/// Sequence controller data.
#define SC_DATA 0x03C5
/// DAC Mask Register.
#define PALETTE_MASK 0x03C6
/// Controls the DAC.
#define PALETTE_READ 0x03C7
/// Controls the DAC index.
#define PALETTE_INDEX 0x03C8
/// Controls the DAC data.
#define PALETTE_DATA 0x03C9
/// Graphics controller index.
#define GC_INDEX 0x03CE
/// Graphics controller data.
#define GC_DATA 0x03CF
/// CRT controller index.
#define CRTC_INDEX 0x03D4
/// CRT controller data.
#define CRTC_DATA 0x03D5
/// By reading this port it'll go to the index state.
#define INPUT_STATUS_READ 0x03DA
/// VGA pointers for drawing operations.
typedef struct {
/// Writes a pixel.
void (*write_pixel)(int x, int y, unsigned char c);
/// Reads a pixel.
unsigned (*read_pixel)(int x, int y);
/// Draws a rectangle.
void (*draw_rect)(int x, int y, int wd, int ht, unsigned char c);
/// Fills a rectangle.
void (*fill_rect)(int x, int y, int wd, int ht, unsigned char c);
} vga_ops_t;
/// VGA font details.
typedef struct {
unsigned char *font; ///< Pointer to the array holding the shape of each character.
unsigned width; ///< Width of the font.
unsigned height; ///< Height of the font.
} vga_font_t;
/// VGA driver details.
typedef struct {
int width; ///< Screen's width.
int height; ///< Screen's height.
int bpp; ///< Bits per pixel (bpp).
char *address; ///< Starting address of the screen.
vga_ops_t *ops; ///< Writing operations.
} vga_driver_t;
/// Is VGA enabled.
static bool_t vga_enable = false;
/// The stored palette.
palette_entry_t stored_palette[256];
/// A buffer for storing a copy of the video memory.
char vidmem[262144];
/// Current driver.
static vga_driver_t *driver = NULL;
/// Current font.
static vga_font_t *__font = NULL;
// ============================================================================
// == VGA MODEs ===============================================================
/// Number of sequencer registers.
#define MODE_NUM_SEQ_REGS 5
/// Number of CRTC registers.
#define MODE_NUM_CRTC_REGS 25
/// Number of Graphics Controller (GC) registers.
#define MODE_NUM_GC_REGS 9
/// Number of Attribute Controller (AC) registers.
#define MODE_NUM_AC_REGS (16 + 5)
/// Total number of registers.
#define MODE_NUM_REGS (1 + MODE_NUM_SEQ_REGS + MODE_NUM_CRTC_REGS + MODE_NUM_GC_REGS + MODE_NUM_AC_REGS) // 61
/// @brief Returns the video address.
/// @return pointer to the video.
static inline char *__get_seg(void)
{
unsigned int seg;
outportb(GC_INDEX, 6);
seg = (inportb(GC_DATA) >> 2) & 3;
if ((seg == 0) || (seg == 1))
return (char *)0xA0000;
if (seg == 2)
return (char *)0xB0000;
if (seg == 3)
return (char *)0xB8000;
return (char *)seg;
}
/// @brief Sets the color at the given index.
/// @param index index of the palette we want to change.
/// @param r red.
/// @param g green.
/// @param b blue.
void __vga_set_color_map(unsigned int index, unsigned char r, unsigned char g, unsigned char b)
{
outportb(PALETTE_MASK, 0xFF);
outportb(PALETTE_INDEX, index);
outportl(PALETTE_DATA, r);
outportl(PALETTE_DATA, g);
outportl(PALETTE_DATA, b);
}
/// @brief Gets the color at the given index.
/// @param index index of the palette we want to read.
/// @param r output value for red.
/// @param g output value for green.
/// @param b output value for blue.
void __vga_get_color_map(unsigned int index, unsigned char *r, unsigned char *g, unsigned char *b)
{
outportb(PALETTE_MASK, 0xFF);
outportb(PALETTE_READ, index);
*r = inportl(PALETTE_DATA);
*g = inportl(PALETTE_DATA);
*b = inportl(PALETTE_DATA);
}
/// @brief Saves the current palette in p.
/// @param p output variable where we save the palette.
/// @param size the size of the palette.
static void __save_palette(palette_entry_t *p, size_t size)
{
outportb(PALETTE_MASK, 0xFF);
outportb(PALETTE_READ, 0x00);
for (int i = 0; i < size; i++) {
p[i].red = inportl(PALETTE_DATA);
p[i].green = inportl(PALETTE_DATA);
p[i].blue = inportl(PALETTE_DATA);
}
}
/// @brief Loads the palette p.
/// @param p palette we are going to load.
/// @param size the size of the palette.
static void __load_palette(palette_entry_t *p, size_t size)
{
outportb(PALETTE_MASK, 0xFF);
outportb(PALETTE_INDEX, 0x00);
for (int i = 0; i < size; i++) {
outportl(PALETTE_DATA, p[i].red);
outportl(PALETTE_DATA, p[i].green);
outportl(PALETTE_DATA, p[i].blue);
}
}
/// @brief Sets the current plane.
/// @param plane the plane to set.
static inline void __set_plane(unsigned int plane)
{
unsigned char pmask;
plane &= 3;
pmask = 1 << plane;
// Set read plane.
outportb(GC_INDEX, 4);
outportb(GC_DATA, plane);
// Set write plane.
outportb(SC_INDEX, 2);
outportb(SC_DATA, pmask);
}
/// @brief Reads from the video memory.
/// @param offset where we are going to read.
/// @return the value we read.
static unsigned char __read_byte(unsigned int offset)
{
return (unsigned char)(*(driver->address + offset));
}
/// @brief Writes onto the video memory.
/// @param offset where we are going to write.
static void __write_byte(unsigned int offset, unsigned char value)
{
*(char *)(driver->address + offset) = value;
}
/// @brief Sets the given mode.
/// @param vga_mode the new mode we set.
static void __set_mode(vga_mode_t *vga_mode)
{
unsigned char *ptr = &vga_mode->misc;
// Write SEQUENCER regs.
outportb(MISC_WRITE, *ptr);
++ptr;
// Write SEQUENCER regs.
for (unsigned i = 0; i < MODE_NUM_SEQ_REGS; i++) {
outportb(SC_INDEX, i);
outportb(SC_DATA, *ptr);
++ptr;
}
// Wnlock CRTC registers.
outportb(CRTC_INDEX, 0x03);
outportb(CRTC_DATA, inportb(CRTC_DATA) | 0x80);
outportb(CRTC_INDEX, 0x11);
outportb(CRTC_DATA, inportb(CRTC_DATA) & ~0x80);
// Make sure they remain unlocked.
ptr[0x03] |= 0x80;
ptr[0x11] &= ~0x80;
// write CRTC regs.
for (unsigned i = 0; i < MODE_NUM_CRTC_REGS; i++) {
outportb(CRTC_INDEX, i);
outportb(CRTC_DATA, *ptr);
++ptr;
}
// write GRAPHICS CONTROLLER regs.
for (unsigned i = 0; i < MODE_NUM_GC_REGS; i++) {
outportb(GC_INDEX, i);
outportb(GC_DATA, *ptr);
++ptr;
}
// write ATTRIBUTE CONTROLLER regs.
for (unsigned i = 0; i < MODE_NUM_AC_REGS; i++) {
inportb(INPUT_STATUS_READ);
outportb(AC_INDEX, i);
outportb(AC_WRITE, *ptr);
++ptr;
}
// lock 16-color palette and unblank display.
(void)inportb(INPUT_STATUS_READ);
outportb(AC_INDEX, 0x20);
}
/// @brief Reads the VGA registers.
/// @param vga_mode the current VGA mode.
static void __read_registers(vga_mode_t *vga_mode)
{
unsigned char *ptr = &vga_mode->misc;
// read MISCELLANEOUS
*ptr = inportb(MISC_READ);
ptr++;
// read SEQUENCER
for (unsigned i = 0; i < MODE_NUM_SEQ_REGS; i++) {
outportb(SC_INDEX, i);
*ptr = inportb(SC_DATA);
ptr++;
}
// read CRTC
for (unsigned i = 0; i < MODE_NUM_CRTC_REGS; i++) {
outportb(CRTC_INDEX, i);
*ptr = inportb(CRTC_DATA);
ptr++;
}
// read GRAPHICS CONTROLLER
for (unsigned i = 0; i < MODE_NUM_GC_REGS; i++) {
outportb(GC_INDEX, i);
*ptr = inportb(GC_DATA);
ptr++;
}
// read ATTRIBUTE CONTROLLER
for (unsigned i = 0; i < MODE_NUM_AC_REGS; i++) {
(void)inportb(INPUT_STATUS_READ);
outportb(AC_INDEX, i);
*ptr = inportb(AC_READ);
ptr++;
}
// lock 16-color palette and unblank display
(void)inportb(INPUT_STATUS_READ);
outportb(AC_INDEX, 0x20);
}
/// @brief Writes the font.
/// @param buf buffer where the font resides.
/// @param font_height the height of the font.
static void __write_font(unsigned char *buf, unsigned font_height)
{
unsigned char seq2, seq4, gc4, gc5, gc6;
unsigned i;
/* save registers
__set_plane() modifies GC 4 and SEQ 2, so save them as well */
outportb(SC_INDEX, 2);
seq2 = inportb(SC_DATA);
outportb(SC_INDEX, 4);
seq4 = inportb(SC_DATA);
/* turn off even-odd addressing (set flat addressing)
assume: chain-4 addressing already off */
outportb(SC_DATA, seq4 | 0x04);
outportb(GC_INDEX, 4);
gc4 = inportb(GC_DATA);
outportb(GC_INDEX, 5);
gc5 = inportb(GC_DATA);
/* turn off even-odd addressing */
outportb(GC_DATA, gc5 & ~0x10);
outportb(GC_INDEX, 6);
gc6 = inportb(GC_DATA);
/* turn off even-odd addressing */
outportb(GC_DATA, gc6 & ~0x02);
/* write font to plane P4 */
__set_plane(2);
/* write font 0 */
for (i = 0; i < 256; i++) {
memcpy((char *)buf, (char *)(i * 32 + 0xA0000), font_height);
buf += font_height;
}
/* restore registers */
outportb(SC_INDEX, 2);
outportb(SC_DATA, seq2);
outportb(SC_INDEX, 4);
outportb(SC_DATA, seq4);
outportb(GC_INDEX, 4);
outportb(GC_DATA, gc4);
outportb(GC_INDEX, 5);
outportb(GC_DATA, gc5);
outportb(GC_INDEX, 6);
outportb(GC_DATA, gc6);
}
/// @brief Reverses the bits of the given number.
/// @param num the number of which we want to reverse the bits.
/// @return reversed bits.
static inline unsigned int __reverse_bits(char num)
{
unsigned int NO_OF_BITS = sizeof(num) * 8;
unsigned int reverse_num = 0;
int i;
for (i = 0; i < NO_OF_BITS; i++) {
if ((num & (1 << i)))
reverse_num |= 1 << ((NO_OF_BITS - 1) - i);
}
return reverse_num;
}
/// @brief Sets the given font.
/// @param font the new font.
void __vga_setfont(const vga_font_t *font)
{
__font->font = font->font;
__font->width = font->width;
__font->height = font->height;
}
// ============================================================================
// = WRITE PIXEL FUNCTIONS (and support)
// ============================================================================
/// @brief Writes a pixel.
/// @param x x coordinates.
/// @param y y coordinates.
/// @param c color.
static inline void __write_pixel_1(int x, int y, unsigned char c)
{
int wd_in_bytes;
int off, mask;
c = (c & 1) * 0xFF;
wd_in_bytes = driver->width / 8;
off = wd_in_bytes * y + x / 8;
x = (x & 7) * 1;
mask = 0x80 >> x;
__write_byte(off, (__read_byte(off) & ~mask) | (c & mask));
}
/// @brief Writes a pixel.
/// @param x x coordinates.
/// @param y y coordinates.
/// @param c color.
static inline void __write_pixel_2(int x, int y, unsigned char c)
{
int wd_in_bytes;
int off, mask;
c = (c & 3) * 0x55;
wd_in_bytes = driver->width / 4;
off = wd_in_bytes * y + x / 4;
x = (x & 3) * 2;
mask = 0xC0 >> x;
__write_byte(off, (__read_byte(off) & ~mask) | (c & mask));
}
/// @brief Writes a pixel.
/// @param x x coordinates.
/// @param y y coordinates.
/// @param c color.
static inline void __write_pixel_4(int x, int y, unsigned char color)
{
int rotation = 0;
int16_t t;
switch (rotation) {
case 1:
t = x;
x = driver->width - 1 - y;
y = t;
break;
case 2:
x = driver->width - 1 - x;
y = driver->height - 1 - y;
break;
case 3:
t = x;
x = y;
y = driver->height - 1 - t;
break;
}
int wd_in_bytes, off, mask, p, pmask;
wd_in_bytes = driver->width / 8;
off = wd_in_bytes * y + x / 8;
x = (x & 7) * 1;
mask = 0x80 >> x;
pmask = 1;
for (p = 0; p < 4; p++) {
__set_plane(p);
if (pmask & color)
__write_byte(off, __read_byte(off) | mask);
else
__write_byte(off, __read_byte(off) & ~mask);
pmask <<= 1;
}
}
/// @brief Writes a pixel.
/// @param x x coordinates.
/// @param y y coordinates.
/// @param c color.
static inline void __write_pixel_8(int x, int y, unsigned char color)
{
__set_plane(x);
__write_byte(((y * driver->width) + x) / 4, color);
// (y << 6) + (y << 4) + (x >> 2)
}
// ============================================================================
// = VGA PUBLIC FUNCTIONS
// ============================================================================
int vga_is_enabled()
{
return vga_enable;
}
int vga_width()
{
if (vga_enable)
return driver->width;
return 0;
}
int vga_height()
{
if (vga_enable)
return driver->height;
return 0;
}
void vga_clear_screen()
{
for (int p = 0; p < 4; p++) {
__set_plane(p);
memset(driver->address, 0, 64 * 1024);
}
}
void vga_draw_pixel(int x, int y, unsigned char color)
{
driver->ops->write_pixel(x, y, color);
}
void vga_draw_char(int x, int y, unsigned char c, unsigned char color)
{
static unsigned mask[] = {
1u << 0u, // 1
1u << 1u, // 2
1u << 2u, // 4
1u << 3u, // 8
1u << 4u, // 16
1u << 5u, // 32
1u << 6u, // 64
1u << 7u, // 128
1u << 8u, // 256
};
unsigned char *glyph = __font->font + c * __font->height;
for (unsigned cy = 0; cy < __font->height; ++cy) {
for (unsigned cx = 0; cx < __font->width; ++cx) {
vga_draw_pixel(x + (__font->width - cx), y + cy, glyph[cy] & mask[cx] ? color : 0x00u);
}
}
}
void vga_draw_string(int x, int y, char *str, unsigned char color)
{
char i = 0;
while (*str != '\0') {
vga_draw_char(x + i * 8, y, *str, color);
str++;
i++;
}
}
void vga_draw_line(int x0, int y0, int x1, int y1, unsigned char color)
{
int dx = abs(x1 - x0), sx = sign(x1 - x0);
int dy = abs(y1 - y0), sy = sign(y1 - y0);
int err = (dx > dy ? dx : -dy) / 2;
while (true) {
vga_draw_pixel(x0, y0, color);
if ((x0 == x1) && (y0 == y1))
break;
if (dx > dy) {
x0 += sx;
err -= dy;
if (err < 0)
err += dx;
y0 += sy;
} else {
y0 += sy;
err -= dx;
if (err < 0)
err += dy;
x0 += sx;
}
}
}
void vga_draw_rectangle(int sx, int sy, int w, int h, unsigned char color)
{
vga_draw_line(sx, sy, sx + w, sy, color);
vga_draw_line(sx, sy, sx, sy + h, color);
vga_draw_line(sx, sy + h, sx + w, sy + h, color);
vga_draw_line(sx + w, sy, sx + w, sy + h, color);
}
void vga_draw_circle(int xc, int yc, int r, unsigned char color)
{
int x = 0;
int y = r;
int p = 3 - 2 * r;
if (!r)
return;
while (y >= x) // only formulate 1/8 of circle
{
vga_draw_pixel(xc - x, yc - y, color); //upper left left
vga_draw_pixel(xc - y, yc - x, color); //upper upper left
vga_draw_pixel(xc + y, yc - x, color); //upper upper right
vga_draw_pixel(xc + x, yc - y, color); //upper right right
vga_draw_pixel(xc - x, yc + y, color); //lower left left
vga_draw_pixel(xc - y, yc + x, color); //lower lower left
vga_draw_pixel(xc + y, yc + x, color); //lower lower right
vga_draw_pixel(xc + x, yc + y, color); //lower right right
if (p < 0)
p += 4 * x++ + 6;
else
p += 4 * (x++ - y--) + 10;
}
}
void vga_draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, unsigned char color)
{
vga_draw_line(x1, y1, x2, y2, color);
vga_draw_line(x2, y2, x3, y3, color);
vga_draw_line(x3, y3, x1, y1, color);
}
void vga_run_test(void)
{
vga_clear_screen();
int w2h = (vga_width() / 2);
int h2h = (vga_height() / 2);
for (unsigned r = 0; r <= min(vga_width() / 2, vga_height() / 2); r += 4)
vga_draw_circle(vga_width() / 2, vga_height() / 2, r, 2);
for (unsigned y = 0; y < vga_height(); y += 2)
vga_draw_line(0, y, vga_width(), y, 3);
for (unsigned dim = 0; dim < min(vga_width(), vga_height()); dim += 4)
vga_draw_rectangle(w2h - (dim / 2), h2h - (dim / 2), dim, dim, 4);
vga_draw_triangle(0, 50, 50, 0, 100, 50, 2);
}
// == MODEs and DRIVERs =======================================================
static vga_ops_t ops_720_480_16 = {
.write_pixel = __write_pixel_4,
.read_pixel = NULL,
.draw_rect = NULL,
.fill_rect = NULL,
};
static vga_ops_t ops_640_480_16 = {
.write_pixel = __write_pixel_4,
.read_pixel = NULL,
.draw_rect = NULL,
.fill_rect = NULL,
};
static vga_ops_t ops_320_200_256 = {
.write_pixel = __write_pixel_8,
.read_pixel = NULL,
.draw_rect = NULL,
.fill_rect = NULL,
};
static vga_font_t font_8x8 = {
.font = arr_8x8_font,
.width = 8,
.height = 8,
};
static vga_font_t font_8x8_basic = {
.font = arr_8x8_basic_font,
.width = 8,
.height = 8,
};
static vga_font_t font_8x16 = {
.font = arr_8x16_font,
.width = 8,
.height = 16,
};
static vga_driver_t driver_720_480_16 = {
.width = 720,
.height = 480,
.bpp = 16,
.address = (char *)NULL,
.ops = &ops_720_480_16,
};
static vga_driver_t driver_640_480_16 = {
.width = 640,
.height = 480,
.bpp = 16,
.address = (char *)NULL,
.ops = &ops_640_480_16,
};
static vga_driver_t driver_320_200_256 = {
.width = 320,
.height = 200,
.bpp = 256,
.address = (char *)NULL,
.ops = &ops_320_200_256,
};
// == INITIALIZE and FINALIZE =================================================
void vga_initialize()
{
// Initialize the desired mode.
#if defined(VGA_MODE_320_200_256)
// Save the current palette.
__save_palette(stored_palette, 256);
// Write the registers.
__set_mode(&_mode_320_200_256);
// Initialize the mode.
driver = &driver_320_200_256;
// Load the color palette.
__load_palette(ansi_256_palette, 256);
#elif defined(VGA_MODE_640_480_16)
// Save the current palette.
__save_palette(stored_palette, 256);
// Write the registers.
__set_mode(&_mode_640_480_16);
// Initialize the mode.
driver = &driver_640_480_16;
// Load the color palette.
__load_palette(ansi_16_palette, 16);
#elif defined(VGA_MODE_720_480_16)
// Save the current palette.
__save_palette(stored_palette, 256);
// Write the registers.
__set_mode(&_mode_720_480_16);
// Initialize the mode.
driver = &driver_720_480_16;
// Load the color palette.
__load_palette(ansi_16_palette, 16);
#else // VGA_TEXT_MODE
__set_mode(&_mode_80_25_text);
return;
#endif
// Set the address.
driver->address = __get_seg();
// Set the font.
__vga_setfont(&font_8x8);
// Save the content of the memory.
memcpy(vidmem, driver->address, 0x4000);
// Clears the screen.
vga_clear_screen();
// Set the vga as enabled.
vga_enable = true;
}
void vga_finalize()
{
memcpy(driver->address, vidmem, 256 * 1024);
__set_mode(&_mode_80_25_text);
__load_palette(stored_palette, 256);
vga_enable = false;
}