changeset 0:cbded07e50d8 default tip

*: initial commit here's a simple VBE thing. It's intended to be able to work pretty much anywhere, but I don't really have anything to test VBE 1.0 with (and i'm not entirely sure if this code works in the first place) it's balls simple, no BS, and ONLY works with DJGPP.
author Paper <paper@tflc.us>
date Sat, 02 Aug 2025 12:55:21 -0400
parents
children
files .hgignore main.c makefile vbe.c vbe.h
diffstat 4 files changed, 590 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.c	Sat Aug 02 12:55:21 2025 -0400
@@ -0,0 +1,40 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <dpmi.h>
+#include <go32.h>
+#include <sys/farptr.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <dos.h>
+
+#include "vbe.h"
+
+int main(void)
+{
+	uint8_t *framebuffer;
+	struct vbe vbe;
+	int x, y;
+
+	framebuffer = malloc(640 * 400);
+
+	vbe_init(&vbe, 640, 400);
+
+	{
+		for (x = 0; x < 640; x++)
+			for (y = 0; y < 400; y++)
+				framebuffer[(y * 640) + x] = vbe_rgb(&vbe, (x + 10), (y + 10), 0xFF);
+
+		vbe_blit(&vbe, framebuffer);
+	}
+
+	delay(5000);
+
+	free(framebuffer);
+
+	vbe_quit(&vbe);
+
+	printf("vbe.bpp: %d", vbe.bpp);
+
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/makefile	Sat Aug 02 12:55:21 2025 -0400
@@ -0,0 +1,2 @@
+main: main.o vbe.o
+	$(CC) -o $@ $^
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vbe.c	Sat Aug 02 12:55:21 2025 -0400
@@ -0,0 +1,459 @@
+/**
+ * VBE.C -- interface into VESA BIOS extensions from DJGPP-compiled
+ * MS-DOS programs.
+ *
+ * Copyright (c) 2025 Paper
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#include <stdio.h>
+#include <stdint.h>
+#include <dpmi.h>
+#include <go32.h>
+#include <sys/farptr.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <dos.h>
+
+#include "vbe.h"
+
+/* pack all structures */
+#pragma pack(push, 1)
+
+struct VbeInfoBlock {
+	char VbeSignature[4];
+	uint16_t VbeVersion;
+	uint16_t OemStringPtr[2];
+	uint8_t Capabilities[4];
+	uint16_t VideoModePtr[2];
+	uint16_t TotalMemory; /* in units of 64kB blocks */
+	uint8_t Reserved[492];
+};
+
+struct VbeModeInfoBlock {
+	uint16_t Attributes;    /* deprecated */
+	uint8_t WindowA;        /* deprecated */
+	uint8_t WindowB;        /* deprecated */
+	uint16_t Granularity;   /* deprecated */
+	uint16_t WindowSize;
+	uint16_t SegmentA;
+	uint16_t SegmentB;
+	uint32_t WinFuncPtr;    /* deprecated */
+	uint16_t Pitch;         /* bytes per horizontal line */
+
+	/* --- OPTIONAL UNTIL VBE 1.2 */
+	uint16_t Width;         /* in pixels */
+	uint16_t Height;        /* in pixels */
+	uint8_t WChar;          /* unused */
+	uint8_t YChar;          /* unused */
+	uint8_t Planes;
+	uint8_t BitsPerPixel;
+	uint8_t Banks;          /* deprecated */
+	uint8_t MemoryModel;
+	uint8_t BankSize;       /* deprecated, almost always 64kB but maybe 16kB */
+	uint8_t ImagePages;
+	uint8_t Reserved0[1];
+
+	/* Direct color fields */
+	uint8_t RedMask;
+	uint8_t RedPosition;
+	uint8_t GreenMask;
+	uint8_t GreenPosition;
+	uint8_t BlueMask;
+	uint8_t BluePosition;
+	uint8_t ReservedMask;
+	uint8_t ReservedPosition;
+	uint8_t DirectColorAttributes;
+
+	/* --- OPTIONAL UNTIL VBE 2.0 */
+	uint32_t Framebuffer; /* physical address of the linear framebuffer */
+
+	uint8_t Reserved1[6];
+
+	/* --- OPTIONAL UNTIL VBE 3.0 */
+	uint16_t LinBytesPerScanLine;  /* bytes per scan line for linear modes */
+	uint8_t BnkNumberOfImagePages; /* number of images for banked modes */
+	uint8_t LinNumberOfImagePages; /* number of images for linear modes */
+	uint8_t LinRedMaskSize;        /* size of direct color red mask (linear modes) */
+	uint8_t LinRedFieldPosition;   /* bit position of lsb of red mask (linear modes) */
+	uint8_t LinGreenMaskSize;      /* size of direct color green mask (linear modes) */
+	uint8_t LinGreenFieldPosition; /* bit position of lsb of green mask (linear modes) */
+	uint8_t LinBlueMaskSize;       /* size of direct color blue mask (linear modes) */
+	uint8_t LinBlueFieldPosition;  /* bit position of lsb of blue mask (linear modes) */
+	uint8_t LinRsvdMaskSize;       /* size of direct color reserved mask (linear modes) */
+	uint8_t LinRsvdFieldPosition;  /* bit position of lsb of reserved mask (linear modes) */
+	uint32_t MaxPixelClock;        /* maximum pixel clock (in Hz) for graphics mode */
+
+	uint8_t Reserved2[189];
+};
+
+#pragma pack(pop)
+
+#define VMI_ATTRIBUTE_HARDWARE     0x01
+#define VMI_ATTRIBUTE_RESERVED     0x02 /* only relevant for VBE < 1.2 */
+#define VMI_ATTRIBUTE_TTY          0x04
+#define VMI_ATTRIBUTE_COLOR        0x08
+#define VMI_ATTRIBUTE_GRAPHICS     0x10
+#define VMI_ATTRIBUTE_VGACOMPAT    0x20
+#define VMI_ATTRIBUTE_VGAWINMEM    0x40
+#define VMI_ATTRIBUTE_LINEARBUFFER 0x80 /* linear framebuffer */
+#define VMI_ATTRIBUTE_DOUBLESCAN   0x100 /* ? */
+#define VMI_ATTRIBUTE_INTERLACE    0x200
+#define VMI_ATTRIBUTE_TRIPLEBUF    0x400
+#define VMI_ATTRIBUTE_STEREOSCOPIC 0x800 /* huh? 3D?? */
+
+/* check if a selection of attributes is enabled */
+#define VMI_ATTRIBUTE(x, a) (((x) & (a)) == (a))
+
+#define MIN(a, b) ((a)<(b)?(a):(b))
+
+#define VBE_FARPTR(x) (((x)[1] * 16) + (x)[0])
+
+/* ------------------------------------------------------------------------ */
+/* this code is kind of a mess, sorry... */
+
+/* loosely based off the information on the osdev wiki,
+ * thanks a lot!
+ *
+ * this function is a thin wrapper around real-mode
+ * VESA routines. `id` is the identifier that goes into
+ * the AX register, and `ptr` and `s` are the pointer and 
+ * size the resulting structure is supposed to be.
+ * `cx`, is obviously the "cx" register. this only needs
+ * to be provided if the underlying function requires it. */
+static int vesa_get_block(uint16_t id, uint16_t cx, void *ptr, size_t s)
+{
+	__dpmi_regs r;
+	long dosbuf;
+	size_t c;
+
+	/* use the conventional memory transfer buffer */
+	dosbuf = (__tb & 0xFFFFF);
+
+	/* zero-initialize */
+	for (c = 0; c < s; c++)
+		_farpokeb(_dos_ds, dosbuf + c, 0);
+
+	/* this isn't necessary for functions other than 
+	 * 0x4F00, but I suppose it won't hurt. */
+	dosmemput("VBE2", 4, dosbuf);
+
+	r.x.ax = id;
+	r.x.cx = cx;
+	r.x.di = dosbuf & 0xF;
+	r.x.es = (dosbuf >> 4) & 0xFFFF;
+	__dpmi_int(0x10, &r);
+
+	/* high byte is the result code,
+	 * low byte must always be 0x4F */
+	if (r.x.ax != 0x004F)
+		return -1;
+
+	/* copy the resulting data into our structure */
+	dosmemget(dosbuf, s, ptr);
+
+	return 0;
+}
+
+static int vesa_get_info(struct VbeInfoBlock *block)
+{
+	if (vesa_get_block(0x4F00, 0, block, sizeof(*block)) != 0)
+		return -1;
+
+	if (memcmp(block->VbeSignature, "VESA", 4) != 0)
+		return -1;
+
+	return 0;
+}
+
+/* This could probably be a public API. I just don't care enough. :) */
+
+typedef void (*vesa_video_mode_cb_spec)(uint16_t mode,
+	struct VbeModeInfoBlock *pvmi, void *userdata);
+
+static int vesa_enum_video_modes(const struct VbeInfoBlock *pvbe,
+	vesa_video_mode_cb_spec cb, void *userdata)
+{
+	struct VbeModeInfoBlock vmi;
+	uint16_t mode;
+	uint32_t ptr;
+
+	for (ptr = VBE_FARPTR(pvbe->VideoModePtr); /* nothing */; ptr += 2) {
+		mode = _farpeekw(_dos_ds, ptr);
+		if (mode == 0xFFFF)
+			break; /* end marker */
+
+		if (vesa_get_block(0x4F01, mode, &vmi, sizeof(vmi)) != 0)
+			continue; /* ...? */
+
+		cb(mode, &vmi, userdata);
+	}
+
+	return 0;
+}
+
+struct vesa_best_mode_info {
+	uint16_t width;
+	uint16_t height;
+
+	/* video mode score; higher is better */
+	int16_t score;
+	/* video mode */
+	uint16_t mode;
+	struct VbeModeInfoBlock vmi;
+};
+
+static void vesa_best_mode_cb(uint16_t mode, struct VbeModeInfoBlock *pvmi,
+	void *userdata)
+{
+	struct vesa_best_mode_info *info = userdata;
+	int16_t score = 0;
+
+	/* need a graphics mode. linear framebuffer is optional ;)
+	 * we also need the "extended" bits that were optional in VBE <1.2.
+	 * newer versions require that this bit be 1, so... */
+	if (!VMI_ATTRIBUTE(pvmi->Attributes, VMI_ATTRIBUTE_RESERVED|VMI_ATTRIBUTE_COLOR|VMI_ATTRIBUTE_GRAPHICS|VMI_ATTRIBUTE_HARDWARE))
+		return;
+
+	/* put significant emphasis on resolution, then 
+	 * prefer higher bpp if available. */
+	if (!(pvmi->Width % info->width))
+		score += 16 / (pvmi->Width / info->width);
+
+	if (!(pvmi->Height % info->height))
+		score += 16 / (pvmi->Width / info->width);
+
+	score += (pvmi->BitsPerPixel / 8);
+
+	if (score > info->score) {
+		info->score = score;
+		info->mode = mode;
+		memcpy(&info->vmi, pvmi, sizeof(info->vmi));
+	}
+}
+
+static int vesa_find_best_mode(struct VbeInfoBlock *vbe, uint16_t width,
+	uint16_t height, uint16_t *pmode, struct VbeModeInfoBlock *pvmi)
+{
+	struct vesa_best_mode_info bmi = {0};
+
+	/* start at non-zero, so we always get at least one valid video mode
+	 * (that is, if the video card actually gives us any) */
+	bmi.width = width;
+	bmi.height = height;
+	bmi.score = -1;
+
+	vesa_enum_video_modes(vbe, vesa_best_mode_cb, &bmi);
+
+	if (bmi.score == -1)
+		return -1;
+
+	*pmode = bmi.mode;
+	memcpy(pvmi, &bmi.vmi, sizeof(*pvmi));
+
+	return 0;
+}
+
+static int vesa_set_mode(uint16_t mode)
+{
+	__dpmi_regs r;
+
+	r.x.ax = 0x4F02;
+	r.x.bx = mode;
+
+	__dpmi_int(0x10, &r);
+
+	if (r.x.ax != 0x004F)
+		return -1;
+
+	return 0;
+}
+
+static int vesa_set_bank(int bank_number)
+{
+	__dpmi_regs r;
+
+	r.x.ax = 0x4F05;
+	r.x.bx = 0;
+	r.x.dx = bank_number;
+
+	__dpmi_int(0x10, &r);
+
+	if (r.x.ax != 0x004F)
+		return -1;
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/* plain VGA stuff for resetting the video mode */
+
+static void vga_set_mode(uint8_t mode)
+{
+	__dpmi_regs r;
+
+	r.h.ah = 0x00;
+	r.h.al = mode;
+
+	__dpmi_int(0x10, &r);
+}
+
+/* ------------------------------------------------------------------------ */
+/* public functions and structures */
+
+static int vesa_init_fb(struct vbe *vbe, uint16_t mode, uint32_t fb_address)
+{
+	/* since we're running from protected mode, we have to allocate
+	 * a logical address that maps to the physical framebuffer. */
+	__dpmi_meminfo mi;
+
+	return -1;
+
+	if (vesa_set_mode(mode | 0x4000) != 0)
+		return -1;
+
+	mi.address = fb_address;
+	mi.size    = vbe->size;
+
+	if (__dpmi_physical_address_mapping(&mi) != 0)
+		return -1;
+
+	vbe->blit.fb.selector = __dpmi_allocate_ldt_descriptors(1);
+	if (vbe->blit.fb.selector == -1)
+		return -1;
+
+	if (__dpmi_set_segment_base_address(vbe->blit.fb.selector, mi.address) != 0)
+		return -1;
+
+	if (__dpmi_set_segment_limit(vbe->blit.fb.selector, mi.size - 1) != 0)
+		return -1;
+
+	vbe->blit.fb.address = mi.address;
+
+	return 0;
+}
+
+int vbe_init(struct vbe *vbe, uint16_t width, uint16_t height)
+{
+	struct VbeInfoBlock vib;
+	struct VbeModeInfoBlock vmi;
+	uint16_t mode;
+
+	memset(vbe, 0, sizeof(*vbe));
+
+	if (vesa_get_info(&vib) != 0)
+		return -1;
+
+	if (vesa_find_best_mode(&vib, width, height, &mode, &vmi) != 0)
+		return -1;
+
+	/* I'm fairly sure this can be 15, for 5R-5G-5B. Whatever. :) */
+	if (vmi.BitsPerPixel % 8)
+		return -1;
+
+	vbe->width = vmi.Width;
+	vbe->height = vmi.Height;
+	vbe->pitch = vmi.Pitch;
+	vbe->bpp = (vmi.BitsPerPixel / 8);
+
+	vbe->size = (vbe->pitch * vbe->height * vbe->bpp);
+
+	if (VMI_ATTRIBUTE(vmi.Attributes, VMI_ATTRIBUTE_LINEARBUFFER)
+		&& !vesa_init_fb(vbe, mode, vmi.Framebuffer)) {
+		vbe->blit_type = VBE_BLIT_FRAMEBUFFER;
+	} else if (!vesa_set_mode(mode)) {
+		vbe->blit_type = VBE_BLIT_BANKS;
+
+		vbe->blit.banks.size = vmi.WindowSize * 1024;
+		vbe->blit.banks.granularity = vmi.Granularity * 1024;
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+static inline __attribute__((__always_inline__)) void farcpy(int selector,
+	uint32_t offset, const void *ptr, uint32_t size)
+{
+	if (!size)
+		return; /* LOL */
+
+	/* do the bulk of the copying with longs */
+	while (size >= 4) {
+		_farpokel(selector, offset, *(const uint32_t *)ptr);
+		ptr = (const char *)ptr + 4;
+		offset += 4;
+		size -= 4;
+	}
+
+	/* now, copy any remaining bytes. */
+	while (size > 0) {
+		_farpokeb(selector, offset, *(const uint8_t *)ptr);
+		ptr = (const char *)ptr + 1;
+		offset++;
+		size--;
+	}
+}
+
+void vbe_blit(struct vbe *vbe, const void *ptr)
+{
+	switch (vbe->blit_type) {
+	case VBE_BLIT_FRAMEBUFFER: {
+		farcpy(vbe->blit.fb.selector, 0, ptr, vbe->size);
+		break;
+	}
+	case VBE_BLIT_BANKS: {
+		const uint32_t bank_size = vbe->blit.banks.size;
+		const uint32_t bank_granularity = vbe->blit.banks.granularity;
+		int bank_number = 0;
+		int todo = vbe->size;
+
+		while (todo > 0) {
+			int copy_size;
+
+			/* select the appropriate bank */
+			vesa_set_bank(bank_number);
+
+			/* how much can we copy in one go? */
+			copy_size = MIN(todo, bank_size);
+
+			/* copy a bank of data to the screen */
+			dosmemput(ptr, copy_size, 0xA0000);
+
+			/* move on to the next bank of data */
+			todo -= copy_size;
+			ptr = (const char *)ptr + copy_size;
+			bank_number += (bank_size / bank_granularity);
+		}
+		break;
+	}
+	default:
+		/* nope ? */
+		break;
+	}
+}
+
+void vbe_quit(struct vbe *vbe)
+{
+	/* reset VGA mode to DOS text mode */
+	vga_set_mode(0x03);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vbe.h	Sat Aug 02 12:55:21 2025 -0400
@@ -0,0 +1,89 @@
+/**
+ * VBE.C -- interface into VESA BIOS extensions from DJGPP-compiled
+ * MS-DOS programs.
+ *
+ * Copyright (c) 2025 Paper
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+**/
+
+#include <stdint.h>
+
+enum {
+	VBE_BLIT_UNINITIALIZED = 0,
+	VBE_BLIT_FRAMEBUFFER,
+	VBE_BLIT_BANKS,
+};
+
+struct vbe {
+	/* ---- PUBLIC MEMBERS ---- */
+
+	uint16_t width, height, pitch;
+	uint8_t bpp; /* BYTES per pixel */
+
+	/* (width * height * bpp) */
+	uint32_t size;
+
+	uint8_t r_size;
+	uint8_t r_pos;
+	uint8_t g_size;
+	uint8_t g_pos;
+	uint8_t b_size;
+	uint8_t b_pos;
+
+	/* --- INTERNAL MEMBERS --- */
+
+	int blit_type;
+	union {
+		struct {
+			uint32_t address;
+			int selector;
+		} fb;
+		struct {
+			uint32_t size;
+			uint32_t granularity;
+		} banks;
+	} blit;
+};
+
+#ifdef __GNUC__
+# define VBE_INLINE static inline __attribute__((__always_inline__))
+#else
+# define VBE_INLINE static inline
+#endif
+
+/* VBE has a theoretical maximum resolution of 65535x65535. */
+int vbe_init(struct vbe *vbe, uint16_t width, uint16_t height);
+void vbe_blit(struct vbe *vbe, const void *ptr);
+void vbe_quit(struct vbe *vbe);
+
+/* this is a convenience macro for converting 8-bit RGB values into
+ * a full RGB value for use in a framebuffer. */
+#define VBE_RGB(/* struct vbe */vbe, /*uint8_t */r, /*uint8_t */g, /*uint8_t */b) \
+	( \
+		((((uint32_t)(r) & 0xFF) << (vbe).r_size >> 8) << (vbe).r_pos) \
+		| ((((uint32_t)(g) & 0xFF) << (vbe).g_size >> 8) << (vbe).g_pos) \
+		| ((((uint32_t)(b) & 0xFF) << (vbe).b_size >> 8) << (vbe).b_pos) \
+	) \
+
+/* function variation of the above. */
+VBE_INLINE uint32_t vbe_rgb(struct vbe *vbe, uint8_t r, uint8_t g, uint8_t b)
+{
+	return VBE_RGB(*vbe, r, g, b);
+}