Catakig/Source/LibAppleII/A2Computer/Video.m

411 lines
9.9 KiB
Objective-C

/* class A2Computer (category Video)
Routines and tables for rendering frames of Apple II video.
*/
#import "LibAppleII-Priv.h"
@implementation A2Computer (Video)
//---------------------------------------------------------------------------
static unsigned DoubleBits(uint8_t b)
{/*
Returns a 16-bit vector formed by replicating the bits of an 8-bit
vector. (Each 0 becomes 00, and each 1 becomes 11.)
*/
static unsigned dbl[16] =
{
0x00,0x03,0x0C,0x0F, 0x30,0x33,0x3C,0x3F,
0xC0,0xC3,0xCC,0xCF, 0xF0,0xF3,0xFC,0xFF,
};
return dbl[b>>4]<<8 | dbl[b&15];
}
static unsigned SpreadBits(uint8_t b)
{ return DoubleBits(b) & 0x5555; }
// Expands an 8-bit vector to 16, interleaving it with zeros.
static unsigned ShiftColor(unsigned c, int s)
{ return c & 0xF0 | 0x0F & (((c&0x0F)*0x11)>>(s&3)); }
// { return ((c&0xF)*0x11) >> (s&3) & 0xF; }
static inline unsigned RandomByte(long* r)
{ *r = *r * 157 % 32363; return 0xFF & (*r>>8 ^ *r); }
//---------------------------------------------------------------------------
static void InitHPixelTables(void)
{
uint8_t colorOfBits[1 << 6] =
{
// 0 1 2 3 4 5 6 7
/* 0 */ 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x3, 0x3,
/* 1 */ 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF,
/* 2 */ 0x0, 0x8, 0x9, 0x9, 0xA, 0xA, 0xB, 0xB,
/* 3 */ 0xC, 0xC, 0xD, 0xD, 0xE, 0xE, 0xF, 0xF,
/* 4 */ 0x0, 0x0, 0x1, 0x1, 0x3, 0x3, 0x3, 0x3,
/* 5 */ 0x4, 0xC, 0x5, 0xD, 0x7, 0xF, 0x7, 0xF, // 55??
/* 6 */ 0x0, 0x0, 0x9, 0x9, 0xB, 0xB, 0xB, 0xB,
/* 7 */ 0xC, 0xC, 0xD, 0xD, 0xF, 0xF, 0xF, 0xF,
};
for (int i = 64; --i >= 0;)
colorOfBits[i] |= (i << 2 & 16); // monochrome bit
for (int i = LENGTH(A2T.vidPix.Hd); --i >= 0;)
for (int j = 4; --j >= 0;)
((uint8_t*) (A2T.vidPix.Hd + i))[j] =
ShiftColor(colorOfBits[i>>j & 0x3F], 2-j);
for (int i = LENGTH(A2T.vidPix.Hs); --i >= 0;)
{
unsigned mono = 0;
// the pattern of monochrome pixels _i_ represents
for (int j = 0; j <= 6; j += 2)
mono |= ("\0\3\0\2\4\7\4\6\0\7\0\6\0\7\0\6"[i>>j & 15]) << j;
mono &= 0x1FF;
for (int j = 4; --j >= 0;)
((uint8_t*) (A2T.vidPix.Hs + i))[j] =
ShiftColor(((uint8_t*)(A2T.vidPix.Hd + mono))[j], 1);
}
}
//---------------------------------------------------------------------------
static void InitPixelTables(void)
{/*
Initializes the 'A2T.vpix' tables, which map video bit patterns to
quartets of pixel values.
The eight-bit pixel values used:
0x00-0F HGR & DHGR colors, monochrome off
10-1F HGR & DHGR colors, monochrome on
20-2F GR & DGR colors (shades from 0 to 5 when in monochrome)
30-33 text: steady black, white; flashing black, white
*/
InitHPixelTables();
for (int i = 16; --i >= 0;)
A2T.vidPix.G[i] = 0x01010101 * (0x20 | i);
for (int i = LENGTH(A2T.vidPix.Ts); --i >= 0;)
{
uint8_t *pix = (uint8_t*)(A2T.vidPix.Ts + i);
pix[0] = pix[1] = 0x30 | (i & 3);
pix[2] = pix[3] = 0x30 | (i>>2 & 3);
}
for (int i = LENGTH(A2T.vidPix.Td); --i >= 0;)
{
uint8_t *pix = (uint8_t*)(A2T.vidPix.Td + i);
pix[0] = 0x30 | (i & 3);
pix[1] = 0x30 | (i>>2 & 3);
pix[2] = 0x30 | (i>>4 & 3);
pix[3] = 0x30 | (i>>6 & 3);
}
}
//---------------------------------------------------------------------------
static void InitBitPatterns(void)
{
#include "glyphs.xbm"
// defines: static char glyphs_xbm_bits[10*9*16]
char fix = ~glyphs_xbm_bits[0x20 * 9]; // bits from space character
for (int ch = 128; --ch >= 0;)
{
char* xbm = glyphs_xbm_bits + (ch&0x70)*9 + (ch&15);
for (int row = 0; row < 8; ++row, xbm += 16)
{
A2T.vidBits.T[1][row][128+ch] = 0x1555 ^ (
A2T.vidBits.T[1][row][ch] =
SpreadBits((*xbm ^ fix) & 0x7F) );
}
}
for (int row = 8; --row >= 0;)
{
uint16_t *p0 = A2T.vidBits.T[0][row],
*p1 = A2T.vidBits.T[1][row],
*p2 = A2T.vidBits.T[2][row];
memcpy(p1+0x40, p1+0xC0, 0x20*2); // Mouse Text
memcpy(p1+0xC0, p1+0x80, 0x20*2); // capitals
memcpy(p0, p1, 0x100*2);
memcpy(p0+0x40, p0, 0x40*2);
for (int i = 0x40; i <= 0x7F; ++i)
p0[i] |= 0x2AAA;
memcpy(p2, p0, 0x100*2);
memcpy(p2+0xE0, p0+0xC0, 0x20*2);
}
for (long i = sizeof(A2T.vidBits.T)/2; --i >= 0;)
(A2T.vidBits.T[0][0])[i] <<= 2;
for (int i = 0x80; --i >= 0;)
{
uint32_t sb = SpreadBits(i);
A2T.vidBits.Hs[ i] = sb << 6;
A2T.vidBits.Hs[0x80+i] = (sb | 0x2AAA) << 6;
}
}
//---------------------------------------------------------------------------
+ (void)_InitVideo
{
InitBitPatterns();
InitPixelTables();
}
//---------------------------------------------------------------------------
- (void)RenderScreen:(void*)pixBase:(int32_t)rowBytes
{/*
Renders a frame of Apple II video into the given 8-bit deep image.
Argument 'pixBase' points to the upper-left or lower-left pixel;
'rowBytes' is the stride in memory from one row to the next -- the
delta between pixels that are vertically adjacent.
*/
PixelGroup* pout = (PixelGroup*) pixBase;
rowBytes /= sizeof(*pout);
if (mHalts & kfHaltNoPower) // then power is off; render a screen of snow
{
for (int v = 192; --v >= 0; pout += rowBytes)
{
for (int h = 141; (h -= 4) >= 0;)
{
enum { kWhite4 = 0x2F * 0x01010101 };
unsigned r = A2Random16();
r &= r >> 4 & r >> 8;
(pout+h)[0] = kWhite4 & -(r&1); r >>= 1;
(pout+h)[1] = kWhite4 & -(r&1); r >>= 1;
(pout+h)[2] = kWhite4 & -(r&1); r >>= 1;
(pout+h)[3] = kWhite4 & -(r&1);
}
}
return;
}
//------------------------------------------------------------
// Otherwise the power is on, so render a real screen of
// Apple II video.
#define SETUP_FOR(TYPE) \
enum { kMask = (LENGTH(A2T.vidPix.TYPE) - 1) << 2, \
kShift = kShift##TYPE }; \
char* tpix = (char*)(A2T.vidPix.TYPE);
#define BAM_(I) pout[I] = *(PixelGroup*)(tpix + (bits & kMask))
#define BAM(I) BAM_(I); bits >>= kShift
#define HLOOP \
for (v -= 21*192; (v+=192) < 0; pout += 7, pin += 2)
#define GCASE(N) \
case kfUpRow + kfMIXED + (N): \
case kfUpRow + (N): \
case kfLoRow + (N)
#define TCASE(N) \
case kfLoRow + kfMIXED + (N): \
case kfUpRow + kfTEXT + (N): \
case kfLoRow + kfTEXT + (N): \
case kfUpRow + kfTEXT + kfMIXED + (N): \
case kfLoRow + kfTEXT + kfMIXED + (N)
enum
{
kAux = 0x2000,
kfUpRow = 1 << 5,
kfLoRow = 0,
kShiftTs = 4, kShiftHs = 4,
kShiftTd = 8, kShiftHd = 4,
};
rowBytes -= 20 * 7;
for (int v = -1; ++v < 192; pout += rowBytes)
{
uint32_t bits = mFlags; //!! mVideoFlags[v];
uint8_t* pin = (mMemory->RAM[0][0]) +
((v<<4 & 0x380) + 40*(v>>6));
int dispPage =
0x400 << (2 >> (bits>>ksPAGE2v & 3) & 1);
// = either 0x400 or 0x800
switch ( bits & 0x1F | ((v>>5)-5) & 0x20 )
{
//------------------------------------- 40-column text
default:
TCASE( 0 ):
TCASE( + kfSINGRES):
TCASE( + kfHIRESv ):
TCASE( + kfHIRESv + kfSINGRES):
{
SETUP_FOR(Ts)
uint16_t* tbits;
tbits = A2T.vidBits.T[bits>>ksALTCHAR & 1][v&7];
pin += dispPage;
*pout++ = 0;
HLOOP
{
bits = tbits[pin[0]];
BAM(0); BAM(1); BAM(2);
bits |= tbits[pin[1]] << 2;
BAM(3); BAM(4); BAM(5); BAM_(6);
}
*pout-- = 0;
} break;
//------------------------------------- 80-column text
TCASE(+ kf80COL ):
TCASE(+ kf80COL + kfSINGRES):
TCASE(+ kf80COL + kfHIRESv ):
TCASE(+ kf80COL + kfHIRESv + kfSINGRES):
{
SETUP_FOR(Td)
uint16_t* tbits;
tbits = A2T.vidBits.T[bits>>ksALTCHAR & 1][v&7];
pin += dispPage;
*pout++ = 0;
HLOOP
{
bits = tbits[pin[kAux+0]];
BAM(0);
bits |= tbits[pin[0]] << 6;
BAM(1); BAM(2);
bits |= tbits[pin[kAux+1]] << 4;
BAM(3); BAM(4);
bits |= tbits[pin[1]] << 2;
BAM(5); BAM_(6);
}
*pout-- = 0;
} break;
//------------------------------------- 40-column GR
GCASE( 0 ):
GCASE( + kfSINGRES):
GCASE(+ kf80COL + kfSINGRES):
{
pin += dispPage;
*pout++ = 0;
HLOOP
{
pout[0] = pout[1] = pout[2] = pout[3] =
A2T.vidPix.G[pin[0] >> (v&4) & 15];
((uint16_t*)pout)[7] = pout[4] = pout[5] = pout[6] =
A2T.vidPix.G[pin[1] >> (v&4) & 15];
}
*pout-- = 0;
} break;
//------------------------------------- 80-column GR
GCASE(+ kf80COL ):
{
pin += dispPage;
*pout++ = 0;
HLOOP
{
pout[0] = pout[1] =
A2T.vidPix.G[pin[kAux+0] >> (v&4) & 15];
((uint8_t*)pout)[7] = pout[2] = pout[3] =
A2T.vidPix.G[pin[0] >> (v&4) & 15];
((uint16_t*)pout)[7] = pout[4] = pout[5] =
A2T.vidPix.G[pin[kAux+1] >> (v&4) & 15];
((uint8_t*)pout)[21] = ((uint16_t*)pout)[11] = pout[6] =
A2T.vidPix.G[pin[1] >> (v&4) & 15];
}
*pout-- = 0;
} break;
//------------------------------------- HGR
GCASE( + kfHIRESv ):
GCASE( + kfHIRESv + kfSINGRES):
GCASE(+ kf80COL + kfHIRESv + kfSINGRES):
{
SETUP_FOR(Hs)
pin += dispPage<<4 | (v&7)<<10;
bits = 0;
HLOOP
{
bits |= A2T.vidBits.Hs[pin[0]];
BAM(0); BAM(1); BAM(2);
bits |= A2T.vidBits.Hs[pin[1]] << 2;
BAM(3); BAM(4); BAM(5); BAM(6);
}
BAM(0); BAM_(1);
} break;
//------------------------------------- DHGR
GCASE(+ kf80COL + kfHIRESv ):
{
SETUP_FOR(Hd)
pin += dispPage<<4 | (v&7)<<10;
bits = 0;
HLOOP
{
bits |= (pin[kAux+0] & 0x7F) << 6;
BAM(0);
bits |= (pin[0] & 0x7F) << 9;
BAM(1); BAM(2);
bits |= (pin[kAux+1] & 0x7F) << 8;
BAM(3); BAM(4);
bits |= (pin[1] & 0x7F) << 7;
BAM(5); BAM(6);
}
BAM(0); BAM_(1);
} break;
} // end of switch on video flags
} // end of for (v...)
}
//---------------------------------------------------------------------------
@end