VGA Graphics
Unit: VGA
VGA Mode 13h driver (320×200, 256 colors).
Types
type
TRGBColor = record R, G, B: Byte; end; { 0-63 VGA DAC }
TPalette = array[0..255] of TRGBColor;
TFrameBuffer = array[0..63999] of Byte;
PFrameBuffer = ^TFrameBuffer;
TImage = record
Width: Word;
Height: Word;
Data: Pointer;
end;
PImage = ^TImage;
TRectangle = record
X, Y: Integer;
Width, Height: Word;
end;
Functions
Initialization
procedure InitVGA;
procedure DoneVGA; { CRITICAL: Call before exit }
procedure WaitForVSync;
Framebuffers
function CreateFrameBuffer: PFrameBuffer;
function GetScreenBuffer: PFrameBuffer; { VGA memory, don't free }
procedure ClearFrameBuffer(FrameBuffer: PFrameBuffer);
procedure CopyFrameBuffer(Source, Dest: PFrameBuffer);
procedure CopyFrameBufferRect(Source, Dest: PFrameBuffer; const Rect: TRectangle);
procedure RenderFrameBuffer(FrameBuffer: PFrameBuffer);
procedure FreeFrameBuffer(var FrameBuffer: PFrameBuffer);
Palette
procedure SetPalette(const Palette: TPalette);
procedure SetPartialPalette(const Palette: TPalette; FromColor, ToColor: Byte);
procedure SetRGB(Index: Byte; const RGB: TRGBColor);
procedure GetRGB(Index: Byte; var RGB: TRGBColor);
procedure RotatePalette(StartColor: Byte; Count: Byte; Direction: ShortInt);
function LoadPalette(const FileName: string; var Palette: TPalette): Boolean;
SetPartialPalette - Sets a range of palette colors (FromColor..ToColor inclusive) without affecting other colors. Useful for reserving palette ranges (e.g., colors 0-223 for game graphics, 224-255 for UI/HUD), allowing you to change game palettes without affecting UI.
RotatePalette - Rotates a range of colors in the VGA DAC palette for animation effects (water, fire, etc.). Directly modifies hardware palette (no need to call SetPalette afterward). Direction: 1 = rotate right, -1 = rotate left.
Clipping
procedure SetClipRectangle(const Rect: TRectangle); { Set render bounds }
Drawing
procedure DrawLine(X1, Y1, X2, Y2: Integer; Color: Byte; FrameBuffer: PFrameBuffer);
procedure DrawHLine(X, Y, Width: Integer; Color: Byte; FrameBuffer: PFrameBuffer);
procedure DrawVLine(X, Y, Height: Integer; Color: Byte; FrameBuffer: PFrameBuffer);
procedure DrawRect(X, Y, Width, Height: Integer; Color: Byte; FrameBuffer: PFrameBuffer);
procedure DrawFillRect(X, Y, Width, Height: Integer; Color: Byte; FrameBuffer: PFrameBuffer);
Images
procedure GetImage(var Image: TImage; X, Y, Width, Height: Word; FrameBuffer: PFrameBuffer);
procedure PutImage(X, Y: Word; Image: PImage; FrameBuffer: PFrameBuffer);
procedure PutImageRect(X, Y: Integer; Image: PImage; const SourceRect: TRectangle; FrameBuffer: PFrameBuffer);
procedure PutFlippedImage(X, Y: Integer; Image: PImage; FlipX, FlipY: Boolean; FrameBuffer: PFrameBuffer);
procedure PutFlippedImageRect(X, Y: Integer; Image: PImage; const SourceRect: TRectangle; FlipX, FlipY: Boolean; FrameBuffer: PFrameBuffer);
procedure ClearImage(var Image: TImage);
procedure FreeImage(var Image: TImage);
Example (Double-Buffering)
uses VGA, PCX;
var
BackBuffer: PFrameBuffer;
PlayerImage: TImage;
Palette: TPalette;
begin
InitVGA;
BackBuffer := CreateFrameBuffer;
{ Load image with palette }
LoadPCXWithPalette('PLAYER.PCX', PlayerImage, Palette);
SetPalette(Palette);
{ Game loop }
while Running do
begin
ClearFrameBuffer(BackBuffer);
{ Draw to backbuffer }
PutImage(100, 50, @PlayerImage, BackBuffer);
DrawRect(10, 10, 100, 50, 15, BackBuffer);
{ Display }
WaitForVSync;
RenderFrameBuffer(BackBuffer);
end;
{ Cleanup }
FreeImage(PlayerImage);
FreeFrameBuffer(BackBuffer);
DoneVGA;
end.
Palette Animation
var
Pal: TPalette;
begin
LoadPalette('WATER.PAL', Pal);
SetPalette(Pal);
{ Animate water (rotate colors 16-31) }
while Running do
begin
RotatePalette(16, 16, 1); { Start at color 16, rotate 16 colors, direction right }
Delay(50);
end;
end;
Note: RotatePalette directly modifies the VGA DAC palette, so no need to call SetPalette again.
Partial Palette Updates
Use SetPartialPalette to reserve palette ranges for different purposes (e.g., game graphics vs UI):
var
Level1Pal, Level2Pal, UIPal: TPalette;
begin
{ Load UI palette (colors 224-255, fixed across all levels) }
LoadPalette('UI.PAL', UIPal);
{ --- Level 1 --- }
LoadPalette('LEVEL1.PAL', Level1Pal);
SetPartialPalette(Level1Pal, 0, 223); { Game graphics: colors 0-223 }
SetPartialPalette(UIPal, 224, 255); { UI/HUD: colors 224-255 }
{ ... gameplay ... }
{ --- Level 2 (different graphics, same UI) --- }
LoadPalette('LEVEL2.PAL', Level2Pal);
SetPartialPalette(Level2Pal, 0, 223); { New game palette }
{ UI palette at 224-255 remains unchanged }
end;
Common palette splits:
0-223 (224 colors): Game graphics/sprites
224-255 (32 colors): UI/HUD/text (stays consistent across levels)
Sprite Sheets
var
SpriteSheet: TImage;
Frame: TRectangle;
begin
LoadPCX('SPRITES.PCX', SpriteSheet);
{ Draw frame 0 (32×32) }
Frame.X := 0;
Frame.Y := 0;
Frame.Width := 32;
Frame.Height := 32;
PutImageRect(100, 100, @SpriteSheet, Frame, BackBuffer);
{ Draw frame 1 (next frame) }
Frame.X := 32;
PutImageRect(150, 100, @SpriteSheet, Frame, BackBuffer);
{ Draw frame 0 flipped }
PutFlippedImageRect(200, 100, @SpriteSheet, Frame, True, False, BackBuffer);
end;
Critical Notes
DoneVGA - MUST call before exit or terminal stuck in graphics mode
Color 0 = transparent - When drawing images
Palette range - RGB values 0-63, not 0-255
WaitForVSync - Call before RenderFrameBuffer to prevent tearing
Free buffers - Match CreateFrameBuffer with FreeFrameBuffer
Don’t free screen buffer - GetScreenBuffer returns VGA memory
Performance
Use double-buffering (CreateFrameBuffer + RenderFrameBuffer)
Call WaitForVSync to limit to 60 FPS
Fast assembly implementations (REP MOVSW for copies)
Auto-clipping on all drawing functions
Notes
Mode 13h: 320×200, 256 colors, linear framebuffer at $A000:0000
Palette animation for water/fire effects (no redraw needed)
Supports horizontal/vertical flipping for sprites
See PCX.PAS for loading images, SPRITE.PAS for animation