Files
VirtualC64-Core/C64/PixelEngine.cpp
T
duckey77 033e23c72e Update to 1.4.2
Update VirtualC64 to 1.4.2
created autoload& run functionality
first attempt at working save states
2016-01-27 15:43:47 -07:00

748 lines
23 KiB
C++

//
// PixelEngine.cpp
/*
* (C) 2015 Dirk W. Hoffmann. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "C64.h"
// DIRK DEBUG, REMOVE ASAP
extern unsigned dirktrace;
extern unsigned dirkcnt;
PixelEngine::PixelEngine() // C64 *c64)
{
setDescription("PixelEngine");
debug(3, " Creating PixelEngine at address %p...\n", this);
currentScreenBuffer = screenBuffer1[0];
pixelBuffer = currentScreenBuffer;
bufferoffset = 0;
// Register snapshot items
SnapshotItem items[] = {
// VIC state latching
{ &pipe.xCounter, sizeof(pipe.xCounter), CLEAR_ON_RESET },
{ pipe.spriteX, sizeof(pipe.spriteX), CLEAR_ON_RESET | WORD_FORMAT },
{ &pipe.spriteXexpand, sizeof(pipe.spriteXexpand), CLEAR_ON_RESET },
{ &pipe.registerCTRL1, sizeof(pipe.registerCTRL1), CLEAR_ON_RESET },
{ &pipe.registerCTRL2, sizeof(pipe.registerCTRL2), CLEAR_ON_RESET },
{ &pipe.g_data, sizeof(pipe.g_data), CLEAR_ON_RESET },
{ &pipe.g_character, sizeof(pipe.g_character), CLEAR_ON_RESET },
{ &pipe.g_color, sizeof(pipe.g_color), CLEAR_ON_RESET },
{ &pipe.mainFrameFF, sizeof(pipe.mainFrameFF), CLEAR_ON_RESET },
{ &pipe.verticalFrameFF, sizeof(pipe.verticalFrameFF), CLEAR_ON_RESET },
{ &bpipe.borderColor, sizeof(bpipe.borderColor), CLEAR_ON_RESET },
{ cpipe.backgroundColor, sizeof(cpipe.backgroundColor), CLEAR_ON_RESET | BYTE_FORMAT },
{ spipe.spriteColor, sizeof(spipe.spriteColor), CLEAR_ON_RESET | BYTE_FORMAT },
{ &spipe.spriteExtraColor1, sizeof(spipe.spriteExtraColor1), CLEAR_ON_RESET },
{ &spipe.spriteExtraColor2, sizeof(spipe.spriteExtraColor2), CLEAR_ON_RESET },
{ &displayMode, sizeof(displayMode), CLEAR_ON_RESET },
{ NULL, 0, 0 }};
registerSnapshotItems(items, sizeof(items));
}
PixelEngine::~PixelEngine()
{
debug(3, " Releasing PixelEngine...\n");
}
void
PixelEngine::reset()
{
VirtualComponent::reset();
// Establish bindings
vic = &c64->vic;
memset(&sr, 0, sizeof(sr));
memset(&sprite_sr, 0, sizeof(sprite_sr));
}
void
PixelEngine::resetScreenBuffers()
{
for (unsigned line = 0; line < PAL_RASTERLINES; line++) {
for (unsigned i = 0; i < NTSC_PIXELS; i++) {
screenBuffer1[line][i] = screenBuffer2[line][i] = (line % 2) ? colors[8] : colors[9];
}
}
}
void
PixelEngine::beginFrame()
{
visibleColumn = false;
}
void
PixelEngine::beginRasterline()
{
// Clear z buffer. The buffer is initialized with the highest positive 8-bit value (meaning the pixel is far away)
// Not necessary, because canvas pixels are drawn first
// memset(zBuffer, SCHAR_MAX, sizeof(zBuffer));
// Clear pixel source
// Not necessary, because canvas pixels are drawn first
// memset(pixelSource, 0x00, sizeof(pixelSource));
// Adjust position of first pixel in buffer (make sure that screen is always centered)
if (c64->isPAL()) {
bufferoffset = PAL_LEFT_BORDER_WIDTH - 32;
} else {
bufferoffset = NTSC_LEFT_BORDER_WIDTH - 32;
}
// Prepare sprite pixel shift register
for (unsigned i = 0; i < 8; i++) {
sprite_sr[i].remaining_bits = -1;
// sprite_sr[i].mcol_bits = sprite_sr[i].scol_bit = 0;
sprite_sr[i].col_bits = 0;
}
// Clear pixel buffer (has same size as pixelSource and zBuffer)
// FOR DEBUGGING ONLY, 0xBB is a randomly chose debug color
if (!vic->vblank)
memset(pixelBuffer, 0xBB, sizeof(pixelSource));
}
void
PixelEngine::endRasterline()
{
if (!vic->vblank) {
// Make the border look nice
expandBorders();
// Advance pixelBuffer
uint16_t nextline = c64->getRasterline() - PAL_UPPER_VBLANK + 1;
if (nextline < PAL_RASTERLINES) {
// Old code
// pixelBuffer += NTSC_PIXELS;
// pxbuf += NTSC_PIXELS;
// New code (slightly slower, but foolproof. Can't get outside the screen buffer)
pixelBuffer = currentScreenBuffer + (nextline * NTSC_PIXELS);
// pxbuf = pixelBuffer + bufshift;
}
}
}
void
PixelEngine::endFrame()
{
// Switch active screen buffer
currentScreenBuffer = (currentScreenBuffer == screenBuffer1[0]) ? screenBuffer2[0] : screenBuffer1[0];
pixelBuffer = currentScreenBuffer;
}
// -----------------------------------------------------------------------------------------------
// VIC state latching
// -----------------------------------------------------------------------------------------------
inline void
PixelEngine::updateSpriteOnOff()
{
dc.spriteOnOff = dc.spriteOnOffPipe;
dc.spriteOnOffPipe = vic->spriteOnOff;
}
// -----------------------------------------------------------------------------------------------
// High level drawing (canvas, sprites, border)
// -----------------------------------------------------------------------------------------------
void
PixelEngine::draw()
{
if (vic->vblank)
return;
drawCanvas();
drawBorder();
drawSprites();
bufferoffset += 8;
}
void
PixelEngine::draw17()
{
if (vic->vblank)
return;
drawCanvas();
drawBorder17();
drawSprites();
bufferoffset += 8;
}
void
PixelEngine::draw55()
{
if (vic->vblank)
return;
drawCanvas();
drawBorder55();
drawSprites();
bufferoffset += 8;
}
void
PixelEngine::drawOutsideBorder()
{
if (vic->vblank)
return;
drawSprites();
}
inline void
PixelEngine::drawBorder()
{
if (pipe.mainFrameFF) {
setFramePixel(0, colors[bpipe.borderColor]);
// After the first pixel has been drawn, color register changes show up
bpipe = vic->bp;
int rgba = colors[bpipe.borderColor];
setFramePixel(1, rgba);
setFramePixel(2, rgba);
setFramePixel(3, rgba);
setFramePixel(4, rgba);
setFramePixel(5, rgba);
setFramePixel(6, rgba);
setFramePixel(7, rgba);
}
}
inline void
PixelEngine::drawBorder17()
{
if (pipe.mainFrameFF && !vic->p.mainFrameFF) {
// 38 column mode
setFramePixel(0, colors[bpipe.borderColor]);
// After the first pixel has been drawn, color register changes show up
bpipe = vic->bp;
int rgba = colors[bpipe.borderColor];
setFramePixel(1, rgba);
setFramePixel(2, rgba);
setFramePixel(3, rgba);
setFramePixel(4, rgba);
setFramePixel(5, rgba);
setFramePixel(6, rgba);
// That's all, we only draw 7 pixels here
} else {
// 40 column mode
drawBorder();
}
}
inline void
PixelEngine::drawBorder55()
{
if (!pipe.mainFrameFF && vic->p.mainFrameFF) {
// 38 column mode
setFramePixel(7, colors[bpipe.borderColor]);
} else {
// 40 column mode
drawBorder();
}
}
inline void
PixelEngine::drawCanvas()
{
/* "Der Sequenzer gibt die Grafikdaten in jeder Rasterzeile im Bereich der
Anzeigespalte aus, sofern das vertikale Rahmenflipflop gelöscht ist (siehe
Abschnitt 3.9.). Außerhalb der Anzeigespalte und bei gesetztem Flipflop wird
die letzte aktuelle Hintergrundfarbe dargestellt (dieser Bereich ist
normalerweise vom Rahmen überdeckt)." [C.B.] */
if (!pipe.verticalFrameFF) {
uint8_t D011 = vic->p.registerCTRL1 & 0x60; // -xx- ----
uint8_t D016 = vic->p.registerCTRL2 & 0x10; // ---x ----
drawCanvasPixel(0);
// After the first pixel has been drawn, color register changes show up
cpipe = vic->cp;
drawCanvasPixel(1);
drawCanvasPixel(2);
drawCanvasPixel(3);
// After pixel 4, the one and zero bits in D016 and the one bits in D011 show up
// This corresponds to the behavior of the color latency chip model in VICE
displayMode |= D016; // latch 1s of D016
displayMode &= D016 | 0xEF; // latch 0s of D016
displayMode |= D011; // latch 1s of D011
drawCanvasPixel(4);
drawCanvasPixel(5);
// After pixel 6, the zero bits in D011 show up
// This corresponds to the behavior of the color latency chip model in VICE
displayMode &= D011 | 0x9F; // latch 0s of D011
drawCanvasPixel(6);
// TODO (seen in VICE)
// if D016 had a rising edge, clear the multicolor flipflop
/*
if (rising_edge_d016)
sr.mc_flop = false;
*/
drawCanvasPixel(7);
} else {
// "... bei gesetztem Flipflop wird die letzte aktuelle Hintergrundfarbe dargestellt."
int col = colors[vic->getBackgroundColor()];
// The following fix (which was done for border-bm-idle is wrong)
// int col = col_rgba[0];
setEightBackgroundPixels(col);
}
}
inline void
PixelEngine::drawCanvasPixel(uint8_t pixelnr)
{
assert(pixelnr < 8);
if (pixelnr == (pipe.registerCTRL2 & 0x07) /* horizontal raster scroll */ && sr.canLoad) {
// Load shift register
sr.data = pipe.g_data;
// Remember how to synthesize pixels
sr.latchedCharacter = pipe.g_character;
sr.latchedColor = pipe.g_color;
// Reset the multicolor synchronization flipflop
sr.mc_flop = true;
}
// Load colors
loadColors((DisplayMode)displayMode, sr.latchedCharacter, sr.latchedColor);
// Render pixel
if (multicol) {
if (sr.mc_flop)
sr.colorbits = (sr.data >> 6);
setMultiColorPixel(pixelnr, sr.colorbits);
} else {
setSingleColorPixel(pixelnr, sr.data >> 7);
}
// Shift register and toggle multicolor flipflop
sr.data <<= 1;
sr.mc_flop = !sr.mc_flop;
}
inline void
PixelEngine::drawSprites()
{
uint8_t firstDMA = vic->isFirstDMAcycle;
uint8_t secondDMA = vic->isSecondDMAcycle;
if (!dc.spriteOnOff && !dc.spriteOnOffPipe && !firstDMA && !secondDMA) // Quick exit
return;
// Update sprite color registers
spipe = vic->sp;
// Draw first four pixels for each sprite
for (unsigned i = 0; i < 8; i++) {
if (GET_BIT(dc.spriteOnOff, i)) {
bool firstDMAi = GET_BIT(firstDMA, i);
bool secondDMAi = GET_BIT(secondDMA, i);
drawSpritePixel(i, 0, secondDMAi /* freeze */, 0 /* halt */, 0 /* load */);
drawSpritePixel(i, 1, secondDMAi /* freeze */, 0 /* halt */, 0 /* load */);
drawSpritePixel(i, 2, secondDMAi /* freeze */, secondDMAi /* halt */, 0 /* load */);
drawSpritePixel(i, 3, firstDMAi | secondDMAi /* freeze */, 0 /* halt */, 0 /* load */);
}
}
updateSpriteOnOff();
// Draw last four pixels for each sprite
for (unsigned i = 0; i < 8; i++) {
if (GET_BIT(dc.spriteOnOff, i)) {
bool firstDMAi = GET_BIT(firstDMA, i);
bool secondDMAi = GET_BIT(secondDMA, i);
drawSpritePixel(i, 4, firstDMAi | secondDMAi /* freeze */, 0 /* halt */, secondDMAi /* load */);
drawSpritePixel(i, 5, firstDMAi | secondDMAi /* freeze */, 0 /* halt */, 0 /* load */);
drawSpritePixel(i, 6, firstDMAi | secondDMAi /* freeze */, 0 /* halt */, 0 /* load */);
drawSpritePixel(i, 7, firstDMAi /* freeze */, 0 /* halt */, 0 /* load */);
}
}
}
void
PixelEngine::drawSpritePixel(unsigned spritenr, unsigned pixelnr, bool freeze, bool halt, bool load)
{
assert(spritenr < 8);
assert(sprite_sr[spritenr].remaining_bits >= -1);
assert(sprite_sr[spritenr].remaining_bits <= 26);
bool multicol = vic->spriteIsMulticolor(spritenr);
// Load shift register if applicable
if (load) {
loadShiftRegister(spritenr);
}
// Stop shift register if applicable
if (halt) {
sprite_sr[spritenr].remaining_bits = -1;
sprite_sr[spritenr].col_bits = 0;
}
// Run shift register if applicable
if (!freeze) {
// Check for horizontal trigger condition
if (pipe.xCounter + pixelnr == pipe.spriteX[spritenr] && sprite_sr[spritenr].remaining_bits == -1) {
sprite_sr[spritenr].remaining_bits = 26; // 24 data bits + 2 clearing zeroes
sprite_sr[spritenr].exp_flop = true;
sprite_sr[spritenr].mc_flop = true;
}
// Run shift register if there are remaining pixels to draw
if (sprite_sr[spritenr].remaining_bits > 0) {
// Determine render mode (single color /multi color) and colors
// TODO: Latch multicolor value at proper cycles. Add dc. multicol
// sprite_sr[nr].mcol = vic->spriteIsMulticolor(nr);
sprite_sr[spritenr].col_bits = sprite_sr[spritenr].data >> (multicol && sprite_sr[spritenr].mc_flop ? 22 : 23);
// Toggle horizontal expansion flipflop for stretched sprites
if (GET_BIT(pipe.spriteXexpand, spritenr))
sprite_sr[spritenr].exp_flop = !sprite_sr[spritenr].exp_flop;
// Run shift register and toggle multicolor flipflop
if (sprite_sr[spritenr].exp_flop) {
sprite_sr[spritenr].data <<= 1;
sprite_sr[spritenr].mc_flop = !sprite_sr[spritenr].mc_flop;
sprite_sr[spritenr].remaining_bits--;
}
}
}
// Draw pixel
if (visibleColumn && vic->drawSprites) {
if (multicol)
setMultiColorSpritePixel(spritenr, pixelnr, sprite_sr[spritenr].col_bits & 0x03);
else
setSingleColorSpritePixel(spritenr, pixelnr, sprite_sr[spritenr].col_bits & 0x01);
}
}
// -----------------------------------------------------------------------------------------------
// Mid level drawing (semantic pixel rendering)
// -----------------------------------------------------------------------------------------------
inline void
PixelEngine::loadColors(DisplayMode mode, uint8_t characterSpace, uint8_t colorSpace)
{
switch (mode) {
case STANDARD_TEXT:
col_rgba[0] = colors[cpipe.backgroundColor[0]];
col_rgba[1] = colors[colorSpace];
multicol = false;
break;
case MULTICOLOR_TEXT:
if (colorSpace & 0x8 /* MC flag */) {
col_rgba[0] = colors[cpipe.backgroundColor[0]];
col_rgba[1] = colors[cpipe.backgroundColor[1]];
col_rgba[2] = colors[cpipe.backgroundColor[2]];
col_rgba[3] = colors[colorSpace & 0x07];
multicol = true;
} else {
col_rgba[0] = colors[cpipe.backgroundColor[0]];
col_rgba[1] = colors[colorSpace];
multicol = false;
}
break;
case STANDARD_BITMAP:
col_rgba[0] = colors[characterSpace & 0x0F]; // color of '0' pixels
col_rgba[1] = colors[characterSpace >> 4]; // color of '1' pixels
multicol = false;
break;
case MULTICOLOR_BITMAP:
col_rgba[0] = colors[cpipe.backgroundColor[0]];
col_rgba[1] = colors[characterSpace >> 4];
col_rgba[2] = colors[characterSpace & 0x0F];
col_rgba[3] = colors[colorSpace];
multicol = true;
break;
case EXTENDED_BACKGROUND_COLOR:
col_rgba[0] = colors[cpipe.backgroundColor[characterSpace >> 6]];
col_rgba[1] = colors[colorSpace];
multicol = false;
break;
case INVALID_TEXT:
col_rgba[0] = colors[PixelEngine::BLACK];
col_rgba[1] = colors[PixelEngine::BLACK];
col_rgba[2] = colors[PixelEngine::BLACK];
col_rgba[3] = colors[PixelEngine::BLACK];
multicol = (colorSpace & 0x8 /* MC flag */);
break;
case INVALID_STANDARD_BITMAP:
col_rgba[0] = colors[PixelEngine::BLACK];
col_rgba[1] = colors[PixelEngine::BLACK];
multicol = false;
break;
case INVALID_MULTICOLOR_BITMAP:
col_rgba[0] = colors[PixelEngine::BLACK];
col_rgba[1] = colors[PixelEngine::BLACK];
col_rgba[2] = colors[PixelEngine::BLACK];
col_rgba[3] = colors[PixelEngine::BLACK];
multicol = true;
break;
default:
assert(0);
break;
}
}
inline void
PixelEngine::setSingleColorPixel(unsigned pixelnr, uint8_t bit /* valid: 0, 1 */)
{
int rgba = col_rgba[bit];
if (bit)
setForegroundPixel(pixelnr, rgba);
else
setBackgroundPixel(pixelnr, rgba);
}
inline void
PixelEngine::setMultiColorPixel(unsigned pixelnr, uint8_t two_bits /* valid: 00, 01, 10, 11 */)
{
int rgba = col_rgba[two_bits];
if (two_bits & 0x02)
setForegroundPixel(pixelnr, rgba);
else
setBackgroundPixel(pixelnr, rgba);
}
inline void
PixelEngine::setSingleColorSpritePixel(unsigned spritenr, unsigned pixelnr, uint8_t bit)
{
if (bit) {
int rgba = colors[spipe.spriteColor[spritenr]];
setSpritePixel(pixelnr, rgba, spritenr);
}
}
inline void
PixelEngine::setMultiColorSpritePixel(unsigned spritenr, unsigned pixelnr, uint8_t two_bits)
{
int rgba;
switch (two_bits) {
case 0x01:
rgba = colors[spipe.spriteExtraColor1];
setSpritePixel(pixelnr, rgba, spritenr);
break;
case 0x02:
rgba = colors[spipe.spriteColor[spritenr]];
setSpritePixel(pixelnr, rgba, spritenr);
break;
case 0x03:
rgba = colors[spipe.spriteExtraColor2];
setSpritePixel(pixelnr, rgba, spritenr);
break;
}
}
inline void
PixelEngine::setSpritePixel(unsigned pixelnr, int color, int nr)
{
uint8_t mask = (1 << nr);
// Check sprite/sprite collision
if (vic->spriteSpriteCollisionEnabled && (pixelSource[pixelnr] & 0x7F)) {
vic->iomem[0x1E] |= ((pixelSource[pixelnr] & 0x7F) | mask);
vic->triggerIRQ(4);
}
// Check sprite/background collision
if (vic->spriteBackgroundCollisionEnabled && (pixelSource[pixelnr] & 0x80)) {
vic->iomem[0x1F] |= mask;
vic->triggerIRQ(2);
}
if (nr == 7)
mask = 0;
setSpritePixel(pixelnr, color, vic->spriteDepth(nr), mask);
}
// -----------------------------------------------------------------------------------------------
// Low level drawing (pixel buffer access)
// -----------------------------------------------------------------------------------------------
inline void
PixelEngine::setFramePixel(unsigned pixelnr, int rgba)
{
unsigned offset = bufferoffset + pixelnr;
assert(offset < NTSC_PIXELS);
pixelBuffer[offset] = rgba;
zBuffer[pixelnr] = BORDER_LAYER_DEPTH;
pixelSource[pixelnr] &= (~0x80); // disable sprite/foreground collision detection in border
}
inline void
PixelEngine::setForegroundPixel(unsigned pixelnr, int rgba)
{
unsigned offset = bufferoffset + pixelnr;
assert(offset < NTSC_PIXELS);
// The zBuffer check is not necessary as the canvas pixels are the first to draw
// if (FOREGROUND_LAYER_DEPTH <= zBuffer[offset])
{
pixelBuffer[offset] = rgba;
zBuffer[pixelnr] = FOREGROUND_LAYER_DEPTH;
pixelSource[pixelnr] = 0x80;
}
}
inline void
PixelEngine::setBackgroundPixel(unsigned pixelnr, int rgba)
{
unsigned offset = bufferoffset + pixelnr;
assert(offset < NTSC_PIXELS);
// The zBuffer check is not necessary as the canvas pixels are the first to draw
// if (BACKGROUD_LAYER_DEPTH <= zBuffer[offset])
{
pixelBuffer[offset] = rgba;
zBuffer[pixelnr] = BACKGROUD_LAYER_DEPTH;
pixelSource[pixelnr] = 0x00;
}
}
void
PixelEngine::setSpritePixel(unsigned pixelnr, int rgba, int depth, int source)
{
unsigned offset = bufferoffset + pixelnr;
assert(offset < NTSC_PIXELS);
if (depth <= zBuffer[pixelnr]) {
pixelBuffer[offset] = rgba;
zBuffer[pixelnr] = depth;
}
pixelSource[pixelnr] |= source;
}
void
PixelEngine::expandBorders()
{
int color, lastX;
unsigned leftPixelPos;
unsigned rightPixelPos;
if (c64->isPAL()) {
leftPixelPos = PAL_LEFT_BORDER_WIDTH - (4*8);
rightPixelPos = PAL_LEFT_BORDER_WIDTH + PAL_CANVAS_WIDTH + (4*8) - 1;
lastX = PAL_PIXELS;
} else {
leftPixelPos = NTSC_LEFT_BORDER_WIDTH - (4*8);
rightPixelPos = NTSC_LEFT_BORDER_WIDTH + NTSC_CANVAS_WIDTH + (4*8) - 1;
lastX = NTSC_PIXELS;
}
// Make picked pixels visible for debugging
// pixelBuffer[leftPixelPos + 1] = colors[5];
// pixelBuffer[rightPixelPos - 1] = colors[5];
color = pixelBuffer[leftPixelPos];
for (unsigned i = 0; i < leftPixelPos; i++) {
pixelBuffer[i] = color;
// pixelBuffer[i] = colors[5]; // for debugging
}
color = pixelBuffer[rightPixelPos];
for (unsigned i = rightPixelPos+1; i < lastX; i++) {
pixelBuffer[i] = color;
// pixelBuffer[i] = colors[5]; // for debugging
}
/*
// Draw grid lines
for (unsigned i = 0; i < NTSC_PIXELS; i += 10)
pixelBuffer[i] = 0xFFFFFFFF;
*/
}
void
PixelEngine::markLine(uint8_t color, unsigned start, unsigned end)
{
assert (end <= NTSC_PIXELS);
int rgba = colors[color];
for (unsigned i = start; i < end; i++) {
pixelBuffer[start + i] = rgba;
}
}