Cogs and Levers A blog full of technical stuff

Plasma, plasma, plasma!

Kicking back into old, old, old school mode, I had found another cool effect laying around that seemed to work really well in dosbox. It’s a plasma (if you couldn’t tell from the title). Plasmas are the cool, blobby sort of shapeless eye-grabbers that are seriously cool and simple. The basic mathematical thory of the plasma is simple.

  • 4 state counters for the program track where you’re up to overall
  • 4 state counters per frame render track where you’re up to for that frame
  • Each pixel is the result of these 4 cosine wave intersections. You can include the x and y counting dimensions to add the 6 intersections.

That’s it really. There’s a little special sauce to make the plasma move and mutate but they really have no bearing over the generation of the effect itself.

Cosine? Easy, I’ll just call cosf()!

Well, not quite. This is a demo routine that will entirely written in assembly language (8086 assembly language to be exact) and as such we won’t have the luxury of a math library (or math-coprocessor) to do the work of finding out cosine values. So, we must pre-calculate. A small C application gives us all the table pre-calculation we’ll need for this application. It’s good to keep this application handy to re-pre-calculate this table to taste. If you like a little extra calculation to go into your cos table, that is. Me, I like nerdy numbers. So, according to this cos table, there are 256 degress in a circle (see what I did there) and the top of the cos curve (1.0) is 255 moving through the centre point (0.0) at 127 all the way down to the bottom point (-1.0) at 0.

Here’s the code to generate that table.

#include <stdio.h>
#include <math.h>

#define PI_BY_2		(3.14159f * 2)

int main(int argc, char *argv[]) {

	int theta = 0, count = 0, count2 = 0;
	unsigned char values[256];

	for (theta = 0; theta <= 255; theta ++) {
		float angle = ((float)theta / 256.0f) * PI_BY_2;
		values[theta] = (unsigned char)((cosf(angle) * 127.0f) + 127.0f);
	}

	printf("costab DB ");

	for (count = 0; count < 32; count ++) {
		for (count2 = 0; count2 < 8; count2 ++) {
			printf("%03xh, ", values[(count << 3) + count2]);
		}

		printf("\n       DB ");
	}

	return 0;
}

Here is the table that is generated when running this code.

costab DB 0feh, 0fdh, 0fdh, 0fdh, 0fdh, 0fdh, 0fch, 0fch
       DB 0fbh, 0fah, 0fah, 0f9h, 0f8h, 0f7h, 0f6h, 0f5h
       DB 0f4h, 0f3h, 0f1h, 0f0h, 0efh, 0edh, 0ebh, 0eah
       DB 0e8h, 0e6h, 0e5h, 0e3h, 0e1h, 0dfh, 0ddh, 0dah
       DB 0d8h, 0d6h, 0d4h, 0d1h, 0cfh, 0cdh, 0cah, 0c8h
       DB 0c5h, 0c2h, 0c0h, 0bdh, 0bah, 0b8h, 0b5h, 0b2h
       DB 0afh, 0ach, 0a9h, 0a6h, 0a3h, 0a0h, 09dh, 09ah
       DB 097h, 094h, 091h, 08eh, 08bh, 088h, 085h, 082h
       DB 07fh, 07bh, 078h, 075h, 072h, 06fh, 06ch, 069h
       DB 066h, 063h, 060h, 05dh, 05ah, 057h, 054h, 051h
       DB 04eh, 04bh, 048h, 045h, 043h, 040h, 03dh, 03bh
       DB 038h, 035h, 033h, 030h, 02eh, 02ch, 029h, 027h
       DB 025h, 023h, 020h, 01eh, 01ch, 01ah, 018h, 017h
       DB 015h, 013h, 012h, 010h, 00eh, 00dh, 00ch, 00ah
       DB 009h, 008h, 007h, 006h, 005h, 004h, 003h, 003h
       DB 002h, 001h, 001h, 000h, 000h, 000h, 000h, 000h
       DB 000h, 000h, 000h, 000h, 000h, 000h, 001h, 001h
       DB 002h, 003h, 003h, 004h, 005h, 006h, 007h, 008h
       DB 009h, 00ah, 00ch, 00dh, 00eh, 010h, 012h, 013h
       DB 015h, 017h, 018h, 01ah, 01ch, 01eh, 020h, 023h
       DB 025h, 027h, 029h, 02ch, 02eh, 030h, 033h, 035h
       DB 038h, 03bh, 03dh, 040h, 043h, 045h, 048h, 04bh
       DB 04eh, 051h, 054h, 057h, 05ah, 05dh, 060h, 063h
       DB 066h, 069h, 06ch, 06fh, 072h, 075h, 078h, 07bh
       DB 07eh, 082h, 085h, 088h, 08bh, 08eh, 091h, 094h
       DB 097h, 09ah, 09dh, 0a0h, 0a3h, 0a6h, 0a9h, 0ach
       DB 0afh, 0b2h, 0b5h, 0b8h, 0bah, 0bdh, 0c0h, 0c2h
       DB 0c5h, 0c8h, 0cah, 0cdh, 0cfh, 0d1h, 0d4h, 0d6h
       DB 0d8h, 0dah, 0ddh, 0dfh, 0e1h, 0e3h, 0e5h, 0e6h
       DB 0e8h, 0eah, 0ebh, 0edh, 0efh, 0f0h, 0f1h, 0f3h
       DB 0f4h, 0f5h, 0f6h, 0f7h, 0f8h, 0f9h, 0fah, 0fah
       DB 0fbh, 0fch, 0fch, 0fdh, 0fdh, 0fdh, 0fdh, 0fdh

Ooooh aaahhh, that’s a nerdy cosine table! Now that we’ve solved all of the world’s mathematical problems here, it’s on to the effect! Just getting 4 counters to run over this cosine table and intersect with each other can produce a mesmerising result. Without setting a palette (the standard vga palette is a bit: ewwwwww), here’s how the effect looks:

Plasma on standard VGA

Feel like you’re at Woodstock yet? So, the effect really spans across two smaller functions, which I’ve tried to comment as best I can below. Here’s drawing a single frame:

plasma_frame:

	; jump over the local variables
	jmp plasma_frame_code

	temp_phase_1 DB	0
	temp_phase_2 DB 0
	temp_phase_3 DB 0
	temp_phase_4 DB 0

	y_loc 		 DW 0

plasma_frame_code:	

	; setup where we'll draw to
	xor		di, di

	; setup a pointer to our cos table
	lea		si, costab

	; iterate over every pixel
	mov		cx, 64000

	; setup temp state into 3 and 4
	mov		al, phase_3
	mov		temp_phase_3, al
	
	mov		al, phase_4
	mov		temp_phase_4, al
	
reset_1_and_2:

	; re-setup temp state into 1 and 2
	mov		al, phase_1
	mov		temp_phase_1, al
	
	mov		al, phase_2
	mov		temp_phase_2, al
	
	; save our overall progress
	push	cx
	; process the next row of pixels
	mov		cx, 320

plasma_frame_pixel:

	; calculate the pixel value
	; col = costab[t1] + costab[t2] + costab[t3] + costab[t4] 

	xor		bx, bx
	
	mov		bl, temp_phase_1
	mov		al, ds:[si + bx]
	
	mov		bl, temp_phase_2
	add		al, ds:[si + bx]
	adc		ah, 0
	
	mov		bl, temp_phase_3
	add		al, ds:[si + bx]
	adc		ah, 0
	
	mov		bl, temp_phase_4
	add		al, ds:[si + bx]
	adc		ah, 0

	; draw the pixel
	mov		es:[di], al

	; adjust counter 1
	mov		al, temp_phase_1
	add		al, 2
	mov		temp_phase_1, al
	
	; adjust counter 2
	mov		al, temp_phase_2
	sub		al, 1
	mov		temp_phase_2, al

	; move onto the next pixel
	inc		di
	dec		cx
	jnz		plasma_frame_pixel

	; adjust the y location by 1
	inc		y_loc

	pop		cx
	
	; adjust counter 3
	mov		al, temp_phase_3
	add		al, 2
	mov		temp_phase_3, al
	
	; adjust counter 4
	mov		al, temp_phase_4
	sub		al, 1
	mov		temp_phase_4, al		
	
	sub		cx, 320
	jnz		reset_1_and_2
	
	ret

Drawing a single frame isn’t too difficult at all. It’s important to remember that es:[di] is pointing to the vga buffer to draw to where as ds:[si] is pointing at the cosine table. We’re using bx as a base pointer to offset si such that it acts as our array index. Neat-O!

Between frame draws, we need to make the plasma MOVE!!.. This is just some simple additions or subtractions. Using random values adds a sense of entropy to the process making the plasma move in an almost unpredictable way. It’s a little more organic this way. I haven’t done it this way though. The code you’ll see below moves the plasma by fixed amounts per frame. Still gives it some movement.

move_plasma:

	mov		al, phase_1
	add		al, 2	
	mov		phase_1, al
	
	mov		al, phase_2
	add		al, 1	
	mov		phase_2, al
	
	mov		al, phase_3
	sub		al, 1	
	mov		phase_3, al
	
	mov		al, phase_4
	add		al, 2	
	mov		phase_4, al

	ret

Wrapping those two calls in a loop that waits for a key to be pressed is all you should need to draw a plasma to the screen.

Things for you to do:

  • Change the cosine table generation to produce a more interesting cosine curve
  • Apply a palette to take the 60’s-ness out of the default palette
  • Apply a palette that you can cycle through (like 1024 or 2048 entries in size) so that the palette (and therefore the plasma) will morph colour as frames progress
  • Add randomness.

Have fun.

Plan 9

Bell Labs look like they’ve just opened up a little more of their source-code in the shape of some system commands.

It’s worth a browse just to have a look at how some of these things were implemented.

A quick update, seems that someone has Plan9 running on a raspberry PI.

Simple random number generation with MASM32

Here’s a little tid-bit that I’d like to make note of. In a previous MASM32 windows development tutorial, I needed a random number generator and was able to dig this one up from the archives.

First of all, you need to make sure we’re using the 586 instruction set - at least!

; minimum instruction set requirement
.586

Shortly, it will become clear why we need the 586 instruction set. To support the random number generator, we need a bit of state in the shape of two double-words.

; calculation state
prng_x  DD 0

; current seed
prng_a  DD 100711433

These two variables allow the random number generator to know where it’s up to for the next iteration. Changing the initial seed of course will change the sequence of random values that are produced by this routine. The actual function that produces the random number look like this:

PrngGet PROC range:DWORD

    ; count the number of cycles since
    ; the machine has been reset
    rdtsc

    ; accumulate the value in eax and manage
    ; any carry-spill into the x state var
    adc eax, edx
    adc eax, prng_x

    ; multiply this calculation by the seed
    mul prng_a

    ; manage the spill into the x state var
    adc eax, edx
    mov prng_x, eax

    ; put the calculation in range of what
    ; was requested
    mul range

    ; ranged-random value in eax
    mov eax, edx

    ret

PrngGet ENDP

There it is. That first instruction RDTSC is what we need to turn the 586 instruction set on for. This mnemonic stands for Read Time-Stamp Counter. It will take the number of cycles since the machine has been reset and put this count into the EDX:EAX pair (as the time stamp counter is a 64bit value). From there some calculations are performed to ensure the consistency of state between random value retrieves and also capping to the requested range.

Simple and it works too!

Mode X.. plus some!

So, not much of tutorial here - just a neat layout of setup code for a few mode X modes I’ve come across now and then.

First of all, just some formalities. Setting Mode X tweaks to the VGA is a massive port-out exercise. Creating the following macro cut down on my code heaps.

outp MACRO port, value
	mov dx, port
	mov al, value
	out dx, al
ENDM

So you can see, it just gives sending a byte out to a port a bit of syntactical sugar in assembly language. Easy.

The only other piece of formality is, you must have set the video display into MCGA (mode 13) first:

set_mcga:
    mov ax, 0013h
    int 10h
	
    ret

On to the modes.

tweak_160x120:
    ; --------------------------
    ; 160x120
    ;
    ; pages = 13
    ; line size = 40
    ; page size = 19200
    ; --------------------------
	
    outp 03d4h, 011h
	
    mov dx, 03d5h
    in  al, dx
    and al, 07fh
    mov bl, al
	
    outp 03d4h, 011h
    outp 03d5h, bl
    outp 03c2h, 0e3h
    outp 03d4h, 000h
    outp 03d5h, 032h
    outp 03d4h, 001h
    outp 03d5h, 027h
    outp 03d4h, 002h
    outp 03d5h, 028h
    outp 03d4h, 003h
    outp 03d5h, 020h
    outp 03d4h, 004h
    outp 03d5h, 02bh                  
    outp 03d4h, 005h
    outp 03d5h, 070h                  
    outp 03d4h, 006h
    outp 03d5h, 00dh                  
    outp 03d4h, 007h
    outp 03d5h, 03eh                  
    outp 03d4h, 008h
    outp 03d5h, 000h                 
    outp 03d4h, 009h
    outp 03d5h, 043h                 
    outp 03d4h, 010h
    outp 03d5h, 0eah                  
    outp 03d4h, 011h
    outp 03d5h, 0ach                 
    outp 03d4h, 012h
    outp 03d5h, 0dfh                  
    outp 03d4h, 013h
    outp 03d5h, 014h                  
    outp 03d4h, 014h
    outp 03d5h, 000h                  
    outp 03d4h, 015h
    outp 03d5h, 0e7h                 
    outp 03d4h, 016h
    outp 03d5h, 006h                  
    outp 03d4h, 017h
    outp 03d5h, 0e3h                  
    outp 03c4h, 001h
    outp 03c5h, 001h                  
    outp 03c4h, 003h
    outp 03c5h, 000h                  
    outp 03c4h, 004h
    outp 03c5h, 006h                 
    outp 03ceh, 005h
    outp 03cfh, 040h                
    outp 03ceh, 006h
    outp 03cfh, 005h

    mov dx, 03dah
    in  al, dx

    outp 03c0h, 010h or 020h
    outp 03c0h, 041h

    mov dx, 03dah
    in  al, dx

    outp 03c0h, 011h or 020h
    outp 03c0h, 0

    mov dx, 03dah
    in  al, dx

    outp 03c0h, 012h or 020h
    outp 03c0h, 0fh

    mov dx, 03dah
    in  al, dx

    outp 03c0h, 013h or 020h
    outp 03c0h, 0

    mov dx, 03dah
    in  al, dx

    outp 03c0h, 014h or 020h
    outp 03c0h, 0

    outp 03d4h, 011h
    
    mov dx, 03d5h
    in  al, dx
    and al, 80h
    mov bl, al

    outp 03d4h, 011h
    outp 03d5h, bl

    ret

tweak_296x220:
    ; --------------------------
    ; 296x220
    ;
    ; pages = 4
    ; line size = 74
    ; page size = 65120
    ; --------------------------

    outp 03d4h, 011h

    mov dx, 03d5h
    in  al, dx
    and al, 7fh
    mov bl, al
	
    outp 03d4h, 011h
    outp 03d5h, bl

    outp 03c2h, 0e3h
    outp 03d4h, 000h
    outp 03d5h, 05fh
    outp 03d4h, 001h
    outp 03d5h, 049h
    outp 03d4h, 002h
    outp 03d5h, 050h
    outp 03d4h, 003h
    outp 03d5h, 082h
    outp 03d4h, 004h
    outp 03d5h, 053h
    outp 03d4h, 005h
    outp 03d5h, 080h
    outp 03d4h, 006h
    outp 03d5h, 00dh
    outp 03d4h, 007h
    outp 03d5h, 03eh
    outp 03d4h, 008h
    outp 03d5h, 000h
    outp 03d4h, 009h
    outp 03d5h, 041h
    outp 03d4h, 010h
    outp 03d5h, 0d7h
    outp 03d4h, 011h
    outp 03d5h, 0ach
    outp 03d4h, 012h
    outp 03d5h, 0b7h
    outp 03d4h, 013h
    outp 03d5h, 025h
    outp 03d4h, 014h
    outp 03d5h, 000h
    outp 03d4h, 015h
    outp 03d5h, 0e7h
    outp 03d4h, 016h
    outp 03d5h, 006h
    outp 03d4h, 017h
    outp 03d5h, 0e3h
    outp 03c4h, 001h
    outp 03c5h, 001h
    outp 03c4h, 004h
    outp 03c5h, 006h
    outp 03ceh, 005h
    outp 03cfh, 040h
    outp 03ceh, 006h
    outp 03cfh, 005h

    mov dx, 03dah
    in  al, dx

    outp 03c0h, 010h or 020h
    outp 03c0h, 041h

    mov dx, 03dah
    in  al, dx
	
    outp 03c0h, 013h or 020h
    outp 03c0h, 0

    outp 03d4h, 011h

    mov dx, 03d5h
    in  al, dx
    and al, 80h
    mov bl, al
	
    outp 03d4h, 011h
    outp 03d5h, bl

    ret

tweak_320x200:
    ; --------------------------
    ; 320x200
    ;
    ; pages = 4
    ; line size = 80
    ; page size = 64000
    ; --------------------------

    outp 03c4h, 04h
    outp 03c5h, 06h

    outp 03d4h, 017h
    outp 03d5h, 0E3h

    outp 03d4h, 014h
    outp 03d5h, 0
	
    ret

tweak_320x240:
    ; --------------------------
    ; 320x240
    ;
    ; pages = 3
    ; line size = 80
    ; page size = 76800
    ; --------------------------

    outp 03d4h, 11h

    mov dx, 03d5h
    in  al, dx
    and al, 7fh
    mov bl, al

    outp 03d4h, 11h
    outp 03d5h, bl

    outp 03c2h, 0e3h
    outp 03d4h, 000h
    outp 03d5h, 05fh
    outp 03d4h, 001h
    outp 03d5h, 04fh
    outp 03d4h, 002h
    outp 03d5h, 050h
    outp 03d4h, 003h
    outp 03d5h, 082h
    outp 03d4h, 004h
    outp 03d5h, 054h
    outp 03d4h, 005h
    outp 03d5h, 080h
    outp 03d4h, 006h
    outp 03d5h, 00dh
    outp 03d4h, 007h
    outp 03d5h, 03eh
    outp 03d4h, 008h
    outp 03d5h, 000h
    outp 03d4h, 009h
    outp 03d5h, 041h
    outp 03d4h, 010h
    outp 03d5h, 0eah
    outp 03d4h, 011h
    outp 03d5h, 0ach
    outp 03d4h, 012h
    outp 03d5h, 0dfh
    outp 03d4h, 013h
    outp 03d5h, 028h
    outp 03d4h, 014h
    outp 03d5h, 000h
    outp 03d4h, 015h
    outp 03d5h, 0e7h
    outp 03d4h, 016h
    outp 03d5h, 006h
    outp 03d4h, 017h
    outp 03d5h, 0e3h
    outp 03c4h, 001h
    outp 03c5h, 001h
    outp 03c4h, 004h
    outp 03c5h, 006h
    outp 03ceh, 005h
    outp 03cfh, 040h
    outp 03ceh, 006h
    outp 03cfh, 005h

    mov dx, 03dah
    in  al, dx
	
    outp 03c0h, 010h or 020h
    outp 03c0h, 041h

    mov dx, 03dah
    in  al, dx
	
    outp 03c0h, 013h or 020h
    outp 03c0h, 0

    outp 03d4h, 011h

    mov dx, 03d5h
    in  al, dx
    or  al, 80h
    mov bl, al

    outp 03d4h, 11h
    outp 03d5h, bl

    ret

tweak_320x400:
    ; --------------------------
    ; 320x400
    ;
    ; pages = 2
    ; line size = 80
    ; page size = 128000
    ; --------------------------

    outp 03c4h, 004h
    outp 03c5h, 006h
    outp 03d4h, 017h
    outp 03d5h, 0E3h
    outp 03d4h, 014h
    outp 03d5h, 000h
    outp 03d4h, 009h
    outp 03d5h, 040h

    ret

tweak_360x360:
    ; --------------------------
    ; 360x360
    ;
    ; pages = 2
    ; line size = 90
    ; page size = 129600
    ; --------------------------

    outp 03d4h, 011h

    mov dx, 03d5h
    in  al, dx
    and al, 7fh
    mov bl, al

    outp 03d4h, 011h
    outp 03d5h, bl

    outp 03c2h, 067h
    outp 03d4h, 000h
    outp 03d5h, 06bh
    outp 03d4h, 001h
    outp 03d5h, 059h
    outp 03d4h, 002h
    outp 03d5h, 05ah
    outp 03d4h, 003h
    outp 03d5h, 08eh
    outp 03d4h, 004h
    outp 03d5h, 05eh
    outp 03d4h, 005h
    outp 03d5h, 08ah
    outp 03d4h, 006h
    outp 03d5h, 0bfh
    outp 03d4h, 007h
    outp 03d5h, 01fh
    outp 03d4h, 008h
    outp 03d5h, 000h
    outp 03d4h, 009h
    outp 03d5h, 040h
    outp 03d4h, 010h
    outp 03d5h, 088h
    outp 03d4h, 011h
    outp 03d5h, 085h
    outp 03d4h, 012h
    outp 03d5h, 067h
    outp 03d4h, 013h
    outp 03d5h, 02dh
    outp 03d4h, 014h
    outp 03d5h, 000h
    outp 03d4h, 015h
    outp 03d5h, 06dh
    outp 03d4h, 016h
    outp 03d5h, 0bah
    outp 03d4h, 017h
    outp 03d5h, 0e3h
    outp 03c4h, 001h
    outp 03c5h, 001h
    outp 03c4h, 004h
    outp 03c5h, 006h
    outp 03ceh, 005h
    outp 03cfh, 040h
    outp 03ceh, 006h
    outp 03cfh, 005h

    mov dx, 03dah
    in  al, dx
	
    outp 03c0h, 010h or 020h
    outp 03c0h, 041h

    mov dx, 03dah
    in  al, dx

    outp 03c0h, 013h or 020h
    outp 03c0h, 0

    outp 03d4h, 011h

    mov dx, 03d5h
    in  al, dx
    or  al, 80h
    mov bl, al
	
    outp 03d4h, 011h
    outp 03d5h, bl	

    ret

tweak_400x300:
    ; --------------------------
    ; 400x300
    ;
    ; pages = 2
    ; line size = 100
    ; page size = 120000
    ; --------------------------
	
    outp 03d4h, 011h

    mov dx, 03d5h
    in  al, dx
    and al, 7fh
    mov bl, al

    outp 03d4h, 011h
    outp 03d5h, bl	

    outp 03c2h, 0e7h                    
    outp 03d4h, 000h 
    outp 03d5h, 071h                    
    outp 03d4h, 001h 
    outp 03d5h, 063h                    
    outp 03d4h, 002h 
    outp 03d5h, 064h                    
    outp 03d4h, 003h 
    outp 03d5h, 092h                    
    outp 03d4h, 004h 
    outp 03d5h, 067h                   
    outp 03d4h, 005h 
    outp 03d5h, 082h                    
    outp 03d4h, 006h 
    outp 03d5h, 046h                    
    outp 03d4h, 007h 
    outp 03d5h, 01fh                    
    outp 03d4h, 008h 
    outp 03d5h, 000h                    
    outp 03d4h, 009h 
    outp 03d5h, 040h                    
    outp 03d4h, 010h 
    outp 03d5h, 031h                    
    outp 03d4h, 011h 
    outp 03d5h, 080h                     
    outp 03d4h, 012h 
    outp 03d5h, 02bh                    
    outp 03d4h, 013h 
    outp 03d5h, 032h                    
    outp 03d4h, 014h 
    outp 03d5h, 000h                    
    outp 03d4h, 015h 
    outp 03d5h, 02fh                    
    outp 03d4h, 016h 
    outp 03d5h, 044h                    
    outp 03d4h, 017h 
    outp 03d5h, 0e3h                    
    outp 03c4h, 001h 
    outp 03c5h, 001h                    
    outp 03c4h, 002h 
    outp 03c5h, 00fh                    
    outp 03c4h, 004h 
    outp 03c5h, 006h                    
    outp 03ceh, 005h 
    outp 03cfh, 040h                    
    outp 03ceh, 006h 
    outp 03cfh, 005h 
                       
    mov dx, 03dah
    in  al, dx
	
    outp 03c0h, 010h or 020h
    outp 03c0h, 041h
    
    mov dx, 03dah
    in  al, dx

    outp 03c0h, 013h or 020h
    outp 03c0h, 0
    
    outp 03d4h, 011h
	
    mov dx, 03d5h
    in  al, dx
    or  al, 80h
    mov bl, al	
	
    outp 03d4h, 011h
    outp 03d5h, bl

    ret

I will have an update to this post. There are some nuances that I’d much prefer explain to you with a couple of nice code blocks rather than how I’m just going to throw it into the page.

These chunks will be helpful in page selection and optimizing page draws to multiple pages at once.

; setting a page
; activeOffset = vgapage + (page * pageSize / 4);
; enable all planes
; outp 03c4h, 02h
; outp 03c5h, 0fh

Some really good references on this topic are

Well, that’s it for now.

Offscreen Mesa3D

As a quick bookmark, this part of the Mesa3D project: Off-screen Rendering.

Looks like it might be a viable option for some of the lower-end hardware I’ve come across lately.