Utilizing unused VGA memory
It seems that only 64 kb of VGA memory are mapped to RAM and modes 10 and 13h are optimized to use less than 64 kb to control the screen. This means that on VGA cards with 256kb of RAM or 512kb most of the video ram is unused?
Is it possible to write a kind of frame buffer that uses page flipping and VGA X mode? X mode will reduce the usable memory to 185 kb, but that still seems plenty.
Below is what I am reading:
Performance Gains Page flipping and Mode X are used together to overcome two major performance bottlenecks of the VGA card:
A. Eliminating Screen Tearing (Page Flipping) Mode X is the first VGA mode that allows hardware-based page flipping, which is critical for smooth animation. Mode 13h (the standard 320×200 mode) does not allow it, resulting in noticeable screen tearing during animation.
B. Accelerated Drawing (Mode X) Mode X's unchained planar access allows the CPU to write data to all four memory planes simultaneously. This means a single CPU write operation can affect four bytes of VRAM, effectively quadrupling the VRAM write bandwidth for operations like clearing the screen or copying large sprites (blitting), which is highly beneficial for a frame buffer approach.
The primary reason that only 64KB RAM is now used on VGA cards is that on the DOS and ELKS versions of Microwindows, the code runs in real mode, which is limited to 64KB data. This limitation was the case for almost all early IBM PC cards, until the 80386 CPU came along with 32-bit protected mode, allowing access to much larger amounts of memory, including 256KB+ memory found on higher resolution video cards.
As you mention, the current implementation uses known BIOS modes, which always allow operation in real mode. To portably access higher resolutions, it isn't enough to just access more memory using say, far pointers, but one also should use the the portable VESA BIOS API for finding and using any higher resolution modes available on particular hardware. Typically, when these higher resolution video modes are activated, a large framebuffer is made accessible in high 32-bit address space, usually 0xFD000000, for instance. This memory can't be accessed via far pointers, but instead needs actual protected mode, or possibly unreal mode, to write the video data (framebuffer).
These issues are all presently solved and working using the Linux, SDL2 or X11 version of Microwindows, and run using 32-bit RGBA pixels using high resolution video cards. For systems like ELKS, it can't necessarily be assumed that the CPU is fast, or which "standard" high resolution video modes might be present.
For experimentation in this area, I have been working on a built-from scratch, simple but powerful internal framebuffer based graphics and event library with portability across SDL, bare metal and ELKS platforms: GFX. The design of this project uses some of the code that went into the ELKS Paint and 8086 Toolchain graphics programs, along with a portable event library. For now, this runs on Linux, macOS and SDL2, but plan on supporting ELKS when a 386+ CPU is present and unreal mode enabled. A 32-bit RGBA VESA video mode would be selected, and unreal mode used to access the hardware framebuffer.
GFX currently supports low level graphics primitives, circles, area fills, truetype fonts, rotated text, unicode, terminal emulation, and some converted X11 screensavers . After a bit more work on GFX, I plan on bringing some of its enhancements back into Microwindows, where hopefully higher resolution video cards would be supported with Nano-X on the ELKS platform.
I was taking about using Nano X on ELKS. I am searching if page flipping is possible on 8088:
Addressing Beyond 64 KB for Page Flipping To access the memory required for the second frame buffer (which starts after the first ∼75 KB), you need to manage memory addresses beyond the initial 64 KB window.
A. The Drawing Challenge (CPU Side) The Mode X memory is physically planar, but logically, it spans 256 KB. Since you can only draw within a 64 KB segment at a time:
To draw the front buffer, you use the segment 0xA000.
To draw the back buffer (which starts, say, at 76,800 bytes into VRAM), the code must calculate a new segment base to reach that memory location. A 76,800-byte offset corresponds to a Physical Address of 0xA0000+76800 (≈0xB2C00).
The driver would have to frequently change its segment register to 0xB2C0 to draw the start of the back buffer.
Any single drawing routine that spans a 64 KB boundary requires the programmer to recalculate and reload the segment register mid-routine. This is the complicated pointer/memory management endemic to 8088 programming.
B. The Flipping Solution (Hardware Side) Fortunately, the actual page flip itself does not require the CPU to directly address the memory above 64 KB.
Calculate Offset: The programmer calculates the logical 256 KB offset of the back buffer (e.g., 76,800 bytes).
Write to CRTC: The CPU writes this offset to the VGA's CRTC Start Address registers. This is an I/O operation, not a memory access.
VGA Does the Work: The VGA hardware takes this offset, and starts reading the display data from that location within its own 256 KB pool, regardless of which 64 KB segment the CPU is currently set to access.
Conclusion:
Yes, you need to manage memory spanning more than 64 KB of VRAM for page flipping. This is done by changing the segment register while drawing to the back buffer, and by using an I/O port write to the CRTC to execute the actual flip. The inherent complexity of Mode X is compounded by the 8088's 64 KB segment limits.
Mode X is also called "unchained mode". Here is a link: http://sininenankka.dy.fi/~sami/mirror/vga_all/vga/unchain.html
I think page flipping in unchained mode can achieve massive performance and visual gains on the oldest machines such XT and AT.
Here is code that compares the performance of double buffer vs page flipping in X mode:
/**************************************************************************
* unchain.c *
* written by David Brackeen *
* http://www.brackeen.com/home/vga/ *
* *
* This is a 16-bit program. *
* Tab stops are set to 2. *
* Remember to compile in the LARGE memory model! *
* To compile in Borland C: bcc -ml unchain.c *
* *
* This program will only work on DOS- or Windows-based systems with a *
* VGA, SuperVGA or compatible video adapter. *
* *
* Please feel free to copy this source code. *
* *
* DESCRIPTION: This program demonstrates VGA's unchained mode *
**************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <mem.h>
#define VIDEO_INT 0x10 /* the BIOS video interrupt. */
#define SET_MODE 0x00 /* BIOS func to set the video mode. */
#define VGA_256_COLOR_MODE 0x13 /* use to set 256-color mode. */
#define TEXT_MODE 0x03 /* use to set 80x25 text mode. */
#define SC_INDEX 0x03c4 /* VGA sequence controller */
#define SC_DATA 0x03c5
#define PALETTE_INDEX 0x03c8 /* VGA digital-to-analog converter */
#define PALETTE_DATA 0x03c9
#define GC_INDEX 0x03ce /* VGA graphics controller */
#define GC_DATA 0x03cf
#define CRTC_INDEX 0x03d4 /* VGA CRT controller */
#define CRTC_DATA 0x03d5
#define INPUT_STATUS_1 0x03da
#define MAP_MASK 0x02 /* Sequence controller registers */
#define ALL_PLANES 0xff02
#define MEMORY_MODE 0x04
#define LATCHES_ON 0x0008 /* Graphics controller registers */
#define LATCHES_OFF 0xff08
#define HIGH_ADDRESS 0x0C /* CRT controller registers */
#define LOW_ADDRESS 0x0D
#define UNDERLINE_LOCATION 0x14
#define MODE_CONTROL 0x17
#define DISPLAY_ENABLE 0x01 /* VGA input status bits */
#define VRETRACE 0x08
#define SCREEN_WIDTH 320 /* width in pixels of mode 0x13 */
#define SCREEN_HEIGHT 200 /* height in pixels of mode 0x13 */
#define SCREEN_SIZE (word)(SCREEN_WIDTH*SCREEN_HEIGHT)
#define NUM_COLORS 256 /* number of colors in mode 0x13 */
#define BITMAP_WIDTH 32
#define BITMAP_HEIGHT 25
#define ANIMATION_FRAMES 24
#define TOTAL_FRAMES 140
#define VERTICAL_RETRACE /* comment out this line for more
accurate timing */
typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;
byte *VGA=(byte *)0xA0000000L; /* this points to video memory. */
word *my_clock=(word *)0x0000046C; /* this points to the 18.2hz system
clock. */
typedef struct tagBITMAP /* the structure for a bitmap. */
{
word width;
word height;
byte palette[256*3];
byte *data;
} BITMAP;
typedef struct tagOBJECT /* the structure for a moving object
in 2d space; used for animation */
{
int x,y;
int dx,dy;
byte width,height;
} OBJECT;
/**************************************************************************
* fskip *
* Skips bytes in a file. *
**************************************************************************/
void fskip(FILE *fp, int num_bytes)
{
int i;
for (i=0; i<num_bytes; i++)
fgetc(fp);
}
/**************************************************************************
* set_mode *
* Sets the video mode. *
**************************************************************************/
void set_mode(byte mode)
{
union REGS regs;
regs.h.ah = SET_MODE;
regs.h.al = mode;
int86(VIDEO_INT, ®s, ®s);
}
/**************************************************************************
* set_unchained_mode *
* resets VGA mode 0x13 to unchained mode to access all 256K of memory *
**************************************************************************/
void set_unchained_mode(void)
{
word i;
dword *ptr=(dword *)VGA; /* used for faster screen clearing */
outp(SC_INDEX, MEMORY_MODE); /* turn off chain-4 mode */
outp(SC_DATA, 0x06);
outpw(SC_INDEX, ALL_PLANES); /* set map mask to all 4 planes */
for(i=0;i<0x4000;i++) /* clear all 256K of memory */
*ptr++ = 0;
outp(CRTC_INDEX,UNDERLINE_LOCATION);/* turn off long mode */
outp(CRTC_DATA, 0x00);
outp(CRTC_INDEX,MODE_CONTROL); /* turn on byte mode */
outp(CRTC_DATA, 0xe3);
}
/**************************************************************************
* page_flip *
* switches the pages at the appropriate time and waits for the *
* vertical retrace. *
**************************************************************************/
void page_flip(word *page1,word *page2)
{
word high_address,low_address;
word temp;
temp=*page1;
*page1=*page2;
*page2=temp;
high_address = HIGH_ADDRESS | (*page1 & 0xff00);
low_address = LOW_ADDRESS | (*page1 << 8);
#ifdef VERTICAL_RETRACE
while ((inp(INPUT_STATUS_1) & DISPLAY_ENABLE));
#endif
outpw(CRTC_INDEX, high_address);
outpw(CRTC_INDEX, low_address);
#ifdef VERTICAL_RETRACE
while (!(inp(INPUT_STATUS_1) & VRETRACE));
#endif
}
/**************************************************************************
* show_buffer *
* displays a memory buffer on the screen *
**************************************************************************/
void show_buffer(byte *buffer)
{
#ifdef VERTICAL_RETRACE
while ((inp(INPUT_STATUS_1) & VRETRACE));
while (!(inp(INPUT_STATUS_1) & VRETRACE));
#endif
memcpy(VGA,buffer,SCREEN_SIZE);
}
/**************************************************************************
* load_bmp *
* Loads a bitmap file into memory. *
**************************************************************************/
void load_bmp(char *file,BITMAP *b)
{
FILE *fp;
long index;
word num_colors;
int x;
/* open the file */
if ((fp = fopen(file,"rb")) == NULL)
{
printf("Error opening file %s.\n",file);
exit(1);
}
/* check to see if it is a valid bitmap file */
if (fgetc(fp)!='B' || fgetc(fp)!='M')
{
fclose(fp);
printf("%s is not a bitmap file.\n",file);
exit(1);
}
/* read in the width and height of the image, and the
number of colors used; ignore the rest */
fskip(fp,16);
fread(&b->width, sizeof(word), 1, fp);
fskip(fp,2);
fread(&b->height,sizeof(word), 1, fp);
fskip(fp,22);
fread(&num_colors,sizeof(word), 1, fp);
fskip(fp,6);
/* assume we are working with an 8-bit file */
if (num_colors==0) num_colors=256;
/* try to allocate memory */
if ((b->data = (byte *) malloc((word)(b->width*b->height))) == NULL)
{
fclose(fp);
printf("Error allocating memory for file %s.\n",file);
exit(1);
}
/* read the palette information */
for(index=0;index<num_colors;index++)
{
b->palette[(int)(index*3+2)] = fgetc(fp) >> 2;
b->palette[(int)(index*3+1)] = fgetc(fp) >> 2;
b->palette[(int)(index*3+0)] = fgetc(fp) >> 2;
x=fgetc(fp);
}
/* read the bitmap */
for(index = (b->height-1)*b->width; index >= 0;index-=b->width)
for(x = 0; x < b->width; x++)
b->data[(int)(index+x)]=(byte)fgetc(fp);
fclose(fp);
}
/**************************************************************************
* set_palette *
* Sets all 256 colors of the palette. *
**************************************************************************/
void set_palette(byte *palette)
{
int i;
outp(PALETTE_INDEX,0); /* tell the VGA that palette data
is coming. */
for(i=0;i<256*3;i++)
outp(PALETTE_DATA,palette[i]); /* write the data */
}
/**************************************************************************
* plot_pixel *
* Plots a pixel in unchained mode *
**************************************************************************/
void plot_pixel(int x,int y,byte color)
{
outp(SC_INDEX, MAP_MASK); /* select plane */
outp(SC_DATA, 1 << (x&3) );
VGA[(y<<6)+(y<<4)+(x>>2)]=color;
}
/**************************************************************************
* Main *
**************************************************************************/
void main(int argc, char *argv[])
{
word bitmap_offset,screen_offset;
word visual_page = 0;
word active_page = SCREEN_SIZE/4;
word start;
float t1,t2;
int i,repeat,plane,num_objects=0;
word x,y;
byte *double_buffer;
BITMAP bmp;
OBJECT *object;
/* get command-line options */
if (argc>0) num_objects=atoi(argv[1]);
if (num_objects<=0) num_objects=8;
/* allocate memory for double buffer and background image */
if ((double_buffer = (byte *) malloc(SCREEN_SIZE)) == NULL)
{
printf("Not enough memory for double buffer.\n");
exit(1);
}
/* allocate memory for objects */
if ((object = (OBJECT *) malloc(sizeof(OBJECT)*num_objects)) == NULL)
{
printf("Not enough memory for objects.\n");
free(double_buffer);
exit(1);
}
/* load the images */
load_bmp("balls.bmp",&bmp);
/* set the object positions */
srand(*my_clock);
for(i=0;i<num_objects;i++)
{
object[i].width = BITMAP_WIDTH;
object[i].height = BITMAP_HEIGHT;
object[i].x = rand() % (SCREEN_WIDTH - BITMAP_WIDTH );
object[i].y = rand() % (SCREEN_HEIGHT- BITMAP_HEIGHT);
object[i].dx = (rand()%5) - 2;
object[i].dy = (rand()%5) - 2;
}
set_mode(VGA_256_COLOR_MODE); /* set the video mode. */
set_palette(bmp.palette);
start=*my_clock; /* record the starting time. */
for(repeat=0;repeat<TOTAL_FRAMES;repeat++)
{
if ((repeat%ANIMATION_FRAMES)==0) bitmap_offset=0;
/* clear background */
memset(double_buffer,0,SCREEN_SIZE);
for(i=0;i<num_objects;i++)
{
screen_offset = (object[i].y<<8) + (object[i].y<<6) + object[i].x;
/* draw the object. */
for(y=0;y<BITMAP_HEIGHT*bmp.width;y+=bmp.width)
for(x=0;x<BITMAP_WIDTH;x++)
if (bmp.data[bitmap_offset+y+x]!=0)
double_buffer[screen_offset+y+x]=bmp.data[bitmap_offset+y+x];
/* check to see if the object is within boundries */
if (object[i].x + object[i].dx < 0 ||
object[i].x + object[i].dx > SCREEN_WIDTH-object[i].width-1)
object[i].dx=-object[i].dx;
if (object[i].y + object[i].dy < 0 ||
object[i].y + object[i].dy > SCREEN_HEIGHT-object[i].height-1)
object[i].dy=-object[i].dy;
/* move the object */
object[i].x+=object[i].dx;
object[i].y+=object[i].dy;
}
/* point to the next image in the animation */
bitmap_offset+=BITMAP_WIDTH;
if ((bitmap_offset%bmp.width)==0)
bitmap_offset+=bmp.width*(BITMAP_HEIGHT-1);
/* show the buffer */
show_buffer(double_buffer);
}
t1=(*my_clock-start)/18.2; /* calculate how long it took. */
free(double_buffer); /* free up memory used */
/************************************************************************/
set_unchained_mode(); /* set unchained mode */
start=*my_clock; /* record the starting time. */
for(repeat=0;repeat<TOTAL_FRAMES;repeat++)
{
if ((repeat%ANIMATION_FRAMES)==0) bitmap_offset=0;
/* clear background */
outpw(SC_INDEX,ALL_PLANES);
memset(&VGA[active_page],0,SCREEN_SIZE/4);
outp(SC_INDEX, MAP_MASK); /* select plane */
for(i=0;i<num_objects;i++)
{
screen_offset = (object[i].y<<6) + (object[i].y<<4) + (object[i].x>>2);
/* draw the object. */
for(plane=0;plane<4;plane++)
{
/* select plane */
outp(SC_DATA, 1 << ((plane+object[i].x)&3) );
for(y=0;y<BITMAP_HEIGHT*bmp.width;y+=bmp.width)
for(x=plane;x<BITMAP_WIDTH;x+=4)
if (bmp.data[bitmap_offset+y+x]!=0)
VGA[active_page+screen_offset+(y>>2)+((x+(object[i].x&3)) >> 2)]=
bmp.data[bitmap_offset+y+x];
}
/* check to see if the object is within boundries */
if (object[i].x + object[i].dx < 0 ||
object[i].x + object[i].dx > SCREEN_WIDTH-object[i].width-1)
object[i].dx=-object[i].dx;
if (object[i].y + object[i].dy < 0 ||
object[i].y + object[i].dy > SCREEN_HEIGHT-object[i].height-1)
object[i].dy=-object[i].dy;
/* move the object */
object[i].x+=object[i].dx;
object[i].y+=object[i].dy;
}
/* point to the next image in the animation */
bitmap_offset+=BITMAP_WIDTH;
if ((bitmap_offset%bmp.width)==0)
bitmap_offset+=bmp.width*(BITMAP_HEIGHT-1);
/* flip the pages */
page_flip(&visual_page,&active_page);
}
t2=(*my_clock-start)/18.2; /* calculate how long it took. */
free(bmp.data);
free(object);
set_mode(TEXT_MODE); /* set the video mode back to
text mode. */
/* output the results... */
printf("Results with %i objects",num_objects);
#ifdef VERTICAL_RETRACE
printf(":\n");
#else
printf(" (vertical retrace *ignored*):\n");
#endif
printf(" Mode 0x13 with double buffering:\n");
printf(" %f seconds,\n",t1);
printf(" %f frames per second.\n",(float)TOTAL_FRAMES/t1);
printf(" Unchained mode with page flipping:\n");
printf(" %f seconds,\n",t2);
printf(" %f frames per second.\n",(float)TOTAL_FRAMES/t2);
if (t2 != 0)
printf(" Unchained mode with page flipping was %f times faster.\n",t1/t2);
return;
}
Here I expanded my idea for a new driver for Nano X. It takes several factors into account such as slow CPU and constraint memory.
/* nxvga_x.c - Nano-X Mode X VGA driver for ELKS
*
* 16-bit, ELKS compatible. Uses Mode X unchained 320x200x256, plane-aware VRAM writes.
* Back buffer stores only window contents; desktop background is a single color.
*
*/
#include <stdlib.h>
#include <dos.h>
#include <string.h>
#include "nano-X.h"
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 200
#define SCREEN_SIZE (SCREEN_WIDTH*SCREEN_HEIGHT/4)
#define SC_INDEX 0x03c4
#define SC_DATA 0x03c5
#define CRTC_INDEX 0x03d4
#define CRTC_DATA 0x03d5
#define MAP_MASK 0x02
#define MEMORY_MODE 0x04
#define HIGH_ADDRESS 0x0C
#define LOW_ADDRESS 0x0D
typedef unsigned char byte;
static byte *VGA = (byte*)0xA0000L; /* Mode X VRAM */
static byte *backbuf = NULL; /* back buffer for window regions */
static byte bg_color = 0; /* desktop background color */
/* ------------------- Hardware Helper Functions ------------------- */
/* Set VGA into 320x200 Mode X unchained */
static int vga_set_modeX(void) {
union REGS regs;
regs.h.ah = 0x00;
regs.h.al = 0x13; /* 320x200 256-color */
int86(0x10,®s,®s);
/* Verify mode 0x13 */
if (regs.h.al != 0x13) return -1;
/* Unchain mode (turn off chain4) */
outp(SC_INDEX, MEMORY_MODE);
outp(SC_DATA, 0x06);
/* Clear all planes */
outpw(SC_INDEX, 0xff02);
for(int i=0;i<0x4000;i++) ((unsigned int*)VGA)[i]=0;
return 0;
}
/* Plane-aware write of rectangle from back buffer to VRAM */
static void vga_update_region(int x, int y, int w, int h) {
int plane, px, py;
for (plane=0; plane<4; plane++) {
outp(SC_INDEX, MAP_MASK);
outp(SC_DATA, 1 << plane);
for (py=0; py<h; py++) {
for (px=0; px<w; px++) {
int vx = x+px;
int vy = y+py;
if ((vx&3)==plane) {
VGA[(vy<<6)+(vy<<4)+(vx>>2)] = backbuf[vy*SCREEN_WIDTH + vx];
}
}
}
}
}
/* ------------------- Nano-X Driver Callbacks ------------------- */
static GR_BOOL nxvga_open(GR_SCREEN_INFO *pinfo) {
if (vga_set_modeX() < 0) return GR_FALSE;
/* Allocate back buffer for window regions */
backbuf = malloc(SCREEN_WIDTH*SCREEN_HEIGHT);
if (!backbuf) return GR_FALSE;
bg_color = 0; /* default background color */
memset(backbuf,bg_color,SCREEN_WIDTH*SCREEN_HEIGHT);
/* Copy full buffer to VRAM */
vga_update_region(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
/* Fill screen info */
pinfo->Width = SCREEN_WIDTH;
pinfo->Height = SCREEN_HEIGHT;
pinfo->Planes = 1;
pinfo->Bpp = 8;
pinfo->RefreshRate = 60;
return GR_TRUE;
}
static void nxvga_close(void) {
if (backbuf) free(backbuf);
backbuf = NULL;
/* Restore text mode */
union REGS regs;
regs.h.ah = 0x00;
regs.h.al = 0x03; /* 80x25 text mode */
int86(0x10,®s,®s);
}
static void nxvga_setpixel(int x,int y,GR_PIXELVAL color) {
if (x<0 || x>=SCREEN_WIDTH || y<0 || y>=SCREEN_HEIGHT) return;
backbuf[y*SCREEN_WIDTH + x] = color;
vga_update_region(x,y,1,1);
}
static GR_PIXELVAL nxvga_getpixel(int x,int y) {
if (x<0 || x>=SCREEN_WIDTH || y<0 || y>=SCREEN_HEIGHT) return bg_color;
return backbuf[y*SCREEN_WIDTH + x];
}
/* Flush a rectangle (Nano-X calls this to update regions) */
static void nxvga_flush(GR_RECT *prect) {
int x = prect->x;
int y = prect->y;
int w = prect->w;
int h = prect->h;
/* Clip to screen */
if (x<0) { w+=x; x=0; }
if (y<0) { h+=y; y=0; }
if (x+w>SCREEN_WIDTH) w=SCREEN_WIDTH-x;
if (y+h>SCREEN_HEIGHT) h=SCREEN_HEIGHT-y;
if (w<=0 || h<=0) return;
vga_update_region(x,y,w,h);
}
/* Set background color */
static void nxvga_setbg(GR_PIXELVAL color) {
bg_color = color;
}
/* ------------------- GR_DRIVER Structure ------------------- */
GR_DRIVER NXVGA_Driver = {
"nxvga_x",
nxvga_open,
nxvga_close,
nxvga_setpixel,
nxvga_getpixel,
nxvga_flush,
nxvga_setbg,
NULL, /* line draw (optional) */
NULL, /* rect fill (optional) */
NULL /* blit (optional) */
};
- Full VRAM access with unchained mode
Standard VGA mode 0x13 (chained) exposes only 64 KB of linear address space, even though VGA has 256 KB of VRAM.
By switching to unchained X mode, the driver can access all four VGA planes, giving 4× more addressable memory.
This allows finer control over updates, partial writes, and avoids constantly re-mapping memory planes.
Effect: Less redundant memory copying; the driver can update only the areas that actually changed.
- Single system-memory back buffer for desktop
Instead of keeping a full-screen copy of all VRAM in main memory, the driver only keeps a single back buffer storing the desktop background.
Windows and applications update only their regions in VRAM; the rest of the desktop background is already known.
When a window moves or is obscured/unobscured, the driver can quickly restore the background by reading from this small back buffer.
Effect: Saves main memory (especially important on 16-bit ELKS systems), and reduces unnecessary VRAM writes.
- Partial updates instead of full redraws
For window moves, window resizing, or overlapping window repainting, only the affected region is updated.
Standard VGA drivers often use full-screen writes, even if only a small area changed.
Effect: Less memory bandwidth used and more fluent graphics in windowed environments.
- Optional vertical retrace sync
The driver can flip pages during vertical retrace to avoid tearing.
Standard VGA drivers may write directly to the visible frame at any time, causing flicker if a window is moving.
Effect: Visual updates are smoother and more professional-looking.
- CPU efficiency for windowed Nano-X operations
Because only changed regions are written, CPU time is saved on memory copying.
- Memory savings
Using just one small background buffer instead of a full double buffer means more memory is free for applications.
On ELKS, where memory is extremely limited, this is a huge benefit.
In summary:
This driver is more efficient for Nano-X windowed graphics because it:
Writes only the pixels that need updating.
Uses full VRAM access in unchained mode.
Keeps memory overhead minimal (one small background buffer).
Avoids tearing with page flips and vertical retrace sync.
| Feature | 4-plane VGA driver | Unchained page-flipping driver |
|---|---|---|
| Main memory usage | High (double buffers for windows) | Low (only background) |
| VRAM usage | Moderate (64 KB per plane, planar layout) | High (full 256 KB linear) |
| Drawing speed | Slower (bit/plane manipulation) | Faster (linear writes, page flipping) |
| Flicker reduction | Moderate | Excellent (page flipping & region updates) |
| Ease of windowed graphics | Moderate | High (direct region updates) |
First attempt for implementation: https://github.com/ghaerr/microwindows/pull/142