256 lines
9.0 KiB
C
256 lines
9.0 KiB
C
// hal_screen.c - GOP framebuffer screen driver with embedded 8x16 font
|
||
//
|
||
// The 8x16 bitmap font is a standard VGA/PC font. We embed just the
|
||
// printable ASCII range (0x20–0x7E). Each glyph is 16 bytes; bit 7 of
|
||
// each byte is the leftmost pixel.
|
||
#include <stdint.h>
|
||
#include <string.h>
|
||
#include <intrin.h>
|
||
#include <hal_api.h>
|
||
|
||
static uint32_t* gFb = (uint32_t *)0;
|
||
static int gWidth = 0;
|
||
static int gHeight = 0;
|
||
static int gStride = 0; // pixels per row
|
||
static int gCursorX = 0, gCursorY = 0;
|
||
|
||
#define FONT_W 8
|
||
#define FONT_H 16
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 8x16 VGA font - printable ASCII only (glyph 0 = space = 0x20)
|
||
// ---------------------------------------------------------------------------
|
||
#include "font8x16.h" // see font8x16.h below
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Internals
|
||
// ---------------------------------------------------------------------------
|
||
static inline void PutPixelRaw(int x, int y, uint32_t color) {
|
||
if ((unsigned)x < (unsigned)gWidth && (unsigned)y < (unsigned)gHeight)
|
||
gFb[y * gStride + x] = color;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// HAL_SCREEN implementations
|
||
// ---------------------------------------------------------------------------
|
||
static void ScreenClear(uint32_t color) {
|
||
for (int y = 0; y < gHeight; y++)
|
||
for (int x = 0; x < gWidth; x++)
|
||
gFb[y * gStride + x] = color;
|
||
gCursorX = 0; gCursorY = 0;
|
||
}
|
||
|
||
static void ScreenPutPixel(int x, int y, uint32_t color) {
|
||
PutPixelRaw(x, y, color);
|
||
}
|
||
|
||
static void ScreenBlit(int x, int y, int w, int h, const uint32_t* pixels) {
|
||
// Calculate clipping bounds
|
||
int startX = (x < 0) ? 0 : x;
|
||
int startY = (y < 0) ? 0 : y;
|
||
int endX = (x + w > gWidth) ? gWidth : (x + w);
|
||
int endY = (y + h > gHeight) ? gHeight : (y + h);
|
||
|
||
int drawW = endX - startX;
|
||
int drawH = endY - startY;
|
||
|
||
// If the image is entirely off-screen, do nothing
|
||
if (drawW <= 0 || drawH <= 0) {
|
||
return;
|
||
}
|
||
|
||
// Adjust the source pointer if the image was clipped on the top or left
|
||
int srcOffsetX = startX - x;
|
||
int srcOffsetY = startY - y;
|
||
|
||
// Calculate bytes to copy per row
|
||
size_t bytesPerRow = (size_t)drawW * sizeof(uint32_t);
|
||
|
||
// If the width we are drawing exactly matches the physical screen stride,
|
||
// AND it matches the original source image width, memory is perfectly contiguous.
|
||
if (drawW == gStride && drawW == w) {
|
||
uint32_t* dst = gFb + (startY * gStride) + startX;
|
||
const uint32_t* src = pixels + (srcOffsetY * w) + srcOffsetX;
|
||
|
||
memcpy(dst, src, bytesPerRow * drawH);
|
||
return;
|
||
}
|
||
|
||
// Copy row-by-row
|
||
for (int row = 0; row < drawH; row++) {
|
||
// Destination in the actual framebuffer (accounting for stride)
|
||
uint32_t* dst = gFb + ((startY + row) * gStride) + startX;
|
||
|
||
// Source from the provided pixel buffer
|
||
const uint32_t* src = pixels + ((srcOffsetY + row) * w) + srcOffsetX;
|
||
|
||
memcpy(dst, src, bytesPerRow);
|
||
}
|
||
}
|
||
|
||
static void ScreenGetSize(int* w, int* h) { *w = gWidth; *h = gHeight; }
|
||
|
||
static void ScreenScroll(int lines) {
|
||
if (lines <= 0 || lines >= gHeight) { ScreenClear(0); return; }
|
||
int rowBytes = gStride * sizeof(uint32_t);
|
||
memmove(gFb, gFb + lines * gStride,
|
||
(size_t)(gHeight - lines) * rowBytes);
|
||
// Clear the revealed rows at the bottom.
|
||
memset(gFb + (gHeight - lines) * gStride, 0,
|
||
(size_t)lines * rowBytes);
|
||
}
|
||
|
||
static void ScreenPutChar(char c, int x, int y, uint32_t fg, uint32_t bg) {
|
||
if ((unsigned char)c < 0x20 || (unsigned char)c > 0x7E) c = '?';
|
||
const uint8_t* glyph = Font8x16 + ((unsigned char)c - 0x20) * 16;
|
||
for (int row = 0; row < 16; row++) {
|
||
uint8_t bits = glyph[row];
|
||
for (int col = 0; col < 8; col++) {
|
||
uint32_t color = (bits & (1 << col)) ? fg : bg; // i don't know why, i don't want to know why
|
||
// i shouldn't have to wonder why but the font is backwards unless we do this terriblness
|
||
PutPixelRaw(x + col, y + row, color);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void ScreenPrintChar(char c, uint32_t fg, uint32_t bg) {
|
||
int cols = gWidth / FONT_W;
|
||
int rows = gHeight / FONT_H;
|
||
|
||
// Mirror to serial
|
||
while ((__inbyte(0x3FD) & 0x20) == 0);
|
||
if (c == '\n') __outbyte(0x3F8, '\r');
|
||
__outbyte(0x3F8, c);
|
||
|
||
if (c == '\n') {
|
||
gCursorX = 0;
|
||
gCursorY++;
|
||
} else if (c == '\r') {
|
||
gCursorX = 0;
|
||
} else if (c == '\b') {
|
||
if (gCursorX > 0) {
|
||
gCursorX--;
|
||
} else if (gCursorY > 0) {
|
||
gCursorY--;
|
||
gCursorX = cols - 1;
|
||
}
|
||
} else {
|
||
ScreenPutChar(c, gCursorX * FONT_W, gCursorY * FONT_H, fg, bg);
|
||
if (++gCursorX >= cols) { gCursorX = 0; gCursorY++; }
|
||
}
|
||
if (gCursorY >= rows) {
|
||
ScreenScroll(FONT_H);
|
||
gCursorY = rows - 1;
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Mouse cursor – 12×19 monochrome arrow sprite
|
||
// 0 = transparent 1 = black outline 2 = white fill
|
||
// The hotspot is at (0,0) (top-left pixel = tip of the arrow).
|
||
// ---------------------------------------------------------------------------
|
||
#define CURSOR_W 12
|
||
#define CURSOR_H 19
|
||
|
||
static const uint8_t gCursorBitmap[CURSOR_H][CURSOR_W] = {
|
||
{1,0,0,0,0,0,0,0,0,0,0,0},
|
||
{1,1,0,0,0,0,0,0,0,0,0,0},
|
||
{1,2,1,0,0,0,0,0,0,0,0,0},
|
||
{1,2,2,1,0,0,0,0,0,0,0,0},
|
||
{1,2,2,2,1,0,0,0,0,0,0,0},
|
||
{1,2,2,2,2,1,0,0,0,0,0,0},
|
||
{1,2,2,2,2,2,1,0,0,0,0,0},
|
||
{1,2,2,2,2,2,2,1,0,0,0,0},
|
||
{1,2,2,2,2,2,2,2,1,0,0,0},
|
||
{1,2,2,2,2,2,2,2,2,1,0,0},
|
||
{1,2,2,2,2,2,2,2,2,2,1,0},
|
||
{1,2,2,2,2,2,1,1,1,1,1,1},
|
||
{1,2,2,2,1,2,2,1,0,0,0,0},
|
||
{1,2,2,1,0,1,2,2,1,0,0,0},
|
||
{1,2,1,0,0,0,1,2,2,1,0,0},
|
||
{1,1,0,0,0,0,0,1,2,2,1,0},
|
||
{1,0,0,0,0,0,0,0,1,2,1,0},
|
||
{0,0,0,0,0,0,0,0,0,1,1,0},
|
||
{0,0,0,0,0,0,0,0,0,0,0,0},
|
||
};
|
||
|
||
// Saved framebuffer pixels beneath the current cursor position.
|
||
static uint32_t gCursorSavedBg[CURSOR_W * CURSOR_H];
|
||
static int gCursorDrawX = -1; // -1 = cursor not yet drawn
|
||
static int gCursorDrawY = -1;
|
||
|
||
// Save the background rectangle [x,y,CURSOR_W,CURSOR_H] from the framebuffer.
|
||
static void CursorSaveBg(int x, int y) {
|
||
for (int row = 0; row < CURSOR_H; row++) {
|
||
int fy = y + row;
|
||
for (int col = 0; col < CURSOR_W; col++) {
|
||
int fx = x + col;
|
||
uint32_t px = 0;
|
||
if ((unsigned)fx < (unsigned)gWidth && (unsigned)fy < (unsigned)gHeight)
|
||
px = gFb[fy * gStride + fx];
|
||
gCursorSavedBg[row * CURSOR_W + col] = px;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Restore the background previously saved by CursorSaveBg.
|
||
static void CursorRestoreBg(int x, int y) {
|
||
for (int row = 0; row < CURSOR_H; row++) {
|
||
int fy = y + row;
|
||
for (int col = 0; col < CURSOR_W; col++) {
|
||
int fx = x + col;
|
||
if ((unsigned)fx < (unsigned)gWidth && (unsigned)fy < (unsigned)gHeight)
|
||
gFb[fy * gStride + fx] = gCursorSavedBg[row * CURSOR_W + col];
|
||
}
|
||
}
|
||
}
|
||
|
||
// Draw the arrow sprite at (x,y), saving the background first.
|
||
// Called from hal_mouse.c via extern declaration.
|
||
void HalScreenDrawCursor(int x, int y) {
|
||
CursorSaveBg(x, y);
|
||
gCursorDrawX = x;
|
||
gCursorDrawY = y;
|
||
for (int row = 0; row < CURSOR_H; row++) {
|
||
int fy = y + row;
|
||
for (int col = 0; col < CURSOR_W; col++) {
|
||
uint8_t p = gCursorBitmap[row][col];
|
||
if (!p) continue;
|
||
int fx = x + col;
|
||
if ((unsigned)fx < (unsigned)gWidth && (unsigned)fy < (unsigned)gHeight) {
|
||
uint32_t color = (p == 1) ? 0x000000u : 0xFFFFFFu;
|
||
gFb[fy * gStride + fx] = color;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Erase the cursor by restoring the saved background.
|
||
void HalScreenEraseCursor(void) {
|
||
if (gCursorDrawX < 0) return; // never drawn
|
||
CursorRestoreBg(gCursorDrawX, gCursorDrawY);
|
||
gCursorDrawX = -1;
|
||
gCursorDrawY = -1;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Populate the HAL_SCREEN vtable
|
||
// ---------------------------------------------------------------------------
|
||
void HalScreenInitialize(uintptr_t fb, int w, int h, int stride,
|
||
HAL_SCREEN* out) {
|
||
gFb = (uint32_t *)fb;
|
||
gWidth = w;
|
||
gHeight = h;
|
||
gStride = stride;
|
||
out->Clear = ScreenClear;
|
||
out->PutPixel = ScreenPutPixel;
|
||
out->Blit = ScreenBlit;
|
||
out->GetSize = ScreenGetSize;
|
||
out->Scroll = ScreenScroll;
|
||
out->PutChar = ScreenPutChar;
|
||
out->PrintChar = ScreenPrintChar;
|
||
out->DrawCursor = HalScreenDrawCursor;
|
||
out->EraseCursor = HalScreenEraseCursor;
|
||
ScreenClear(0x000000);
|
||
}
|