kopia lustrzana https://github.com/Jean-MarcHarvengt/MCUME
2175 wiersze
67 KiB
C++
2175 wiersze
67 KiB
C++
/*
|
||
Copyright Frank Bösing, 2017
|
||
|
||
This file is part of Teensy64.
|
||
|
||
Teensy64 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 3 of the License, or
|
||
(at your option) any later version.
|
||
|
||
Teensy64 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 Teensy64. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
Diese Datei ist Teil von Teensy64.
|
||
|
||
Teensy64 ist Freie Software: Sie können es unter den Bedingungen
|
||
der GNU General Public License, wie von der Free Software Foundation,
|
||
Version 3 der Lizenz oder (nach Ihrer Wahl) jeder späteren
|
||
veröffentlichten Version, weiterverbreiten und/oder modifizieren.
|
||
|
||
Teensy64 wird in der Hoffnung, dass es nützlich sein wird, aber
|
||
OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
|
||
Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
|
||
Siehe die GNU General Public License für weitere Details.
|
||
|
||
Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
|
||
Programm erhalten haben. Wenn nicht, siehe <http://www.gnu.org/licenses/>.
|
||
|
||
*/
|
||
|
||
/*
|
||
TODOs:
|
||
- Fix Bugs..
|
||
- FLD - (OK 08/17) test this more..
|
||
- Sprite Stretching (requires "MOBcounter")
|
||
- BA Signal -> CPU
|
||
- xFLI
|
||
- ...
|
||
- DMA Delay (?) - needs partial rewrite (idle - > badline in middle of line. Is the 3.6 fast enough??)
|
||
- optimize more
|
||
*/
|
||
|
||
|
||
#include "Teensy64.h"
|
||
#include "vic.h"
|
||
#include <string.h>
|
||
#include <math.h>
|
||
#include <stdlib.h>
|
||
|
||
#define min(a, b) (((a) < (b)) ? (a) : (b))
|
||
#define max(a, b) (((a) > (b)) ? (a) : (b))
|
||
|
||
#define PALETTE(r,g,b) (RGBVAL16(r,g,b)) //(((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3))
|
||
#include "vic_palette.h"
|
||
|
||
|
||
|
||
#define BORDER (240-200)/2
|
||
#define SCREEN_HEIGHT (200+2*BORDER)
|
||
#define SCREEN_WIDTH 320
|
||
//#define LINE_MEM_WIDTH 320
|
||
#define FIRSTDISPLAYLINE ( 51 - BORDER )
|
||
#define LASTDISPLAYLINE ( 250 + BORDER )
|
||
#define BORDER_LEFT (400-320)/2
|
||
#define BORDER_RIGHT 0
|
||
|
||
#ifdef USE_VGA
|
||
typedef uint8_t tpixel;
|
||
#else
|
||
typedef uint16_t tpixel;
|
||
#endif
|
||
|
||
#define MAXCYCLESSPRITES0_2 3
|
||
#define MAXCYCLESSPRITES3_7 5
|
||
#define MAXCYCLESSPRITES (MAXCYCLESSPRITES0_2 + MAXCYCLESSPRITES3_7)
|
||
|
||
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
inline __attribute__((always_inline))
|
||
void fastFillLine(tpixel * p, const tpixel * pe, const uint16_t col, uint16_t * spl);
|
||
inline __attribute__((always_inline))
|
||
void fastFillLineNoSprites(tpixel * p, const tpixel * pe, const uint16_t col);
|
||
|
||
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
#define SPRITENUM(data) (1 << ((data >> 8) & 0x07))
|
||
#define CHARSETPTR() cpu.vic.charsetPtr = cpu.vic.charsetPtrBase + cpu.vic.rc;
|
||
#define CYCLES(x) {if (cpu.vic.badline) {cia_clockt(x);} else {cpu_clock(x);} }
|
||
|
||
#define BADLINE(x) {if (cpu.vic.badline) { \
|
||
cpu.vic.lineMemChr[x] = cpu.RAM[cpu.vic.videomatrix + vc + x]; \
|
||
cpu.vic.lineMemCol[x] = cpu.vic.COLORRAM[vc + x]; \
|
||
cia1_clock(1); \
|
||
cia2_clock(1); \
|
||
} else { \
|
||
cpu_clock(1); \
|
||
} \
|
||
};
|
||
|
||
#define SPRITEORFIXEDCOLOR() \
|
||
sprite = *spl++; \
|
||
if (sprite) { \
|
||
*p++ = cpu.vic.palette[sprite & 0x0f]; \
|
||
} else { \
|
||
*p++ = col; \
|
||
}
|
||
|
||
|
||
#if 0
|
||
#define PRINTOVERFLOW \
|
||
if (p>pe) { \
|
||
Serial.print("VIC overflow Mode "); \
|
||
Serial.println(mode); \
|
||
}
|
||
|
||
#define PRINTOVERFLOWS \
|
||
if (p>pe) { \
|
||
Serial.print("VIC overflow (Sprite) Mode "); \
|
||
Serial.println(mode); \
|
||
}
|
||
#else
|
||
#define PRINTOVERFLOW
|
||
#define PRINTOVERFLOWS
|
||
#endif
|
||
|
||
/*****************************************************************************************************/
|
||
void mode0 (tpixel *p, const tpixel *pe, uint16_t *spl, const uint16_t vc) {
|
||
// Standard-Textmodus(ECM/BMM/MCM=0/0/0)
|
||
/*
|
||
Standard-Textmodus (ECM / BMM / MCM = 0/0/0)
|
||
In diesem Modus (wie in allen Textmodi) liest der VIC aus der videomatrix 8-Bit-Zeichenzeiger,
|
||
die die Adresse der Punktmatrix des Zeichens im Zeichengenerator angibt. Damit ist ein Zeichensatz
|
||
von 256 Zeichen verfügbar, die jeweils aus 8×8 Pixeln bestehen, die in 8 aufeinanderfolgenden Bytes
|
||
im Zeichengenerator abgelegt sind. Mit den Bits VM10-13 und CB11-13 aus Register $d018 lassen sich
|
||
videomatrix und Zeichengenerator im Speicher verschieben. Im Standard-Textmodus entspricht jedes Bit
|
||
im Zeichengenerator direkt einem Pixel auf dem Bildschirm. Die Vordergrundfarbe ist für jedes Zeichen
|
||
im Farbnibble aus der videomatrix angegeben, die Hintergrundfarbe wird global durch Register $d021 festgelegt.
|
||
|
||
+----+----+----+----+----+----+----+----+
|
||
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||
+----+----+----+----+----+----+----+----+
|
||
| 8 Pixel (1 Bit/Pixel) |
|
||
| |
|
||
| "0": Hintergrundfarbe 0 ($d021) |
|
||
| "1": Farbe aus Bits 8-11 der c-Daten |
|
||
+---------------------------------------+
|
||
|
||
*/
|
||
|
||
uint8_t chr, pixel;
|
||
uint16_t fgcol;
|
||
uint16_t bgcol;
|
||
uint16_t x = 0;
|
||
|
||
CHARSETPTR();
|
||
|
||
if (cpu.vic.lineHasSprites) {
|
||
|
||
do {
|
||
|
||
BADLINE(x);
|
||
|
||
chr = cpu.vic.charsetPtr[cpu.vic.lineMemChr[x] * 8];
|
||
fgcol = cpu.vic.lineMemCol[x];
|
||
x++;
|
||
unsigned m = min(8, pe - p);
|
||
for (unsigned i = 0; i < m; i++) {
|
||
int sprite = *spl++;
|
||
|
||
if (sprite) { // Sprite: Ja
|
||
int spritenum = SPRITENUM(sprite);
|
||
int spritepixel = sprite & 0x0f;
|
||
|
||
if (sprite & 0x4000) { // Sprite: Hinter Text MDP = 1
|
||
if (chr & 0x80) {
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = fgcol;
|
||
} else {
|
||
pixel = spritepixel;
|
||
}
|
||
} else { // Sprite: Vor Text //MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum;
|
||
pixel = spritepixel;
|
||
}
|
||
} else { // Kein Sprite
|
||
pixel = (chr & 0x80) ? fgcol : cpu.vic.B0C;
|
||
}
|
||
|
||
*p++ = cpu.vic.palette[pixel];
|
||
chr = chr << 1;
|
||
|
||
}
|
||
} while (p < pe);
|
||
PRINTOVERFLOWS
|
||
} else { //Keine Sprites
|
||
|
||
while (p < pe - 8) {
|
||
|
||
BADLINE(x)
|
||
|
||
chr = cpu.vic.charsetPtr[cpu.vic.lineMemChr[x] * 8];
|
||
fgcol = cpu.vic.palette[cpu.vic.lineMemCol[x]];
|
||
bgcol = cpu.vic.colors[1];
|
||
x++;
|
||
|
||
*p++ = (chr & 0x80) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x40) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x20) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x10) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x08) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x04) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x02) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x01) ? fgcol : bgcol;
|
||
|
||
};
|
||
|
||
while (p < pe) {
|
||
|
||
BADLINE(x)
|
||
|
||
chr = cpu.vic.charsetPtr[cpu.vic.lineMemChr[x] * 8];
|
||
fgcol = cpu.vic.palette[cpu.vic.lineMemCol[x]];
|
||
bgcol = cpu.vic.colors[1];
|
||
x++;
|
||
|
||
*p++ = (chr & 0x80) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x40) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x20) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x10) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x08) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x04) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x02) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x01) ? fgcol : bgcol;
|
||
};
|
||
PRINTOVERFLOW
|
||
}
|
||
while (x<40) {BADLINE(x); x++;}
|
||
};
|
||
|
||
/*****************************************************************************************************/
|
||
void mode1 (tpixel *p, const tpixel *pe, uint16_t *spl, const uint16_t vc) {
|
||
/*
|
||
Multicolor-Textmodus (ECM/BMM/MCM=0/0/1)
|
||
Dieser Modus ermöglicht es, auf Kosten der horizontalen Auflösung vierfarbige Zeichen darzustellen.
|
||
Ist Bit 11 der c-Daten Null, wird das Zeichen wie im Standard-Textmodus dargestellt, wobei aber nur die
|
||
Farben 0-7 für den Vordergrund zur Verfügung stehen. Ist Bit 11 gesetzt, bilden jeweils zwei horizontal
|
||
benachbarte Bits der Punktmatrix ein Pixel. Dadurch ist die Auflösung des Zeichens auf 4×8 reduziert
|
||
(die Pixel sind doppelt so breit, die Gesamtbreite der Zeichen ändert sich also nicht).
|
||
Interessant ist, daß nicht nur die Bitkombination „00”, sondern auch „01” für die Spritepriorität
|
||
und -kollisionserkennung zum "Hintergrund" gezählt wird.
|
||
|
||
+----+----+----+----+----+----+----+----+
|
||
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||
+----+----+----+----+----+----+----+----+
|
||
| 8 Pixel (1 Bit/Pixel) |
|
||
| | MC-Flag = 0
|
||
| "0": Hintergrundfarbe 0 ($d021) |
|
||
| "1": Farbe aus Bits 8-10 der c-Daten |
|
||
+---------------------------------------+
|
||
| 4 Pixel (2 Bit/Pixel) |
|
||
| |
|
||
| "00": Hintergrundfarbe 0 ($d021) | MC-Flag = 1
|
||
| "01": Hintergrundfarbe 1 ($d022) |
|
||
| "10": Hintergrundfarbe 2 ($d023) |
|
||
| "11": Farbe aus Bits 8-10 der c-Daten |
|
||
+---------------------------------------+
|
||
|
||
*/
|
||
|
||
// POKE 53270,PEEK(53270) OR 16
|
||
// poke 53270,peek(53270) or 16
|
||
|
||
uint16_t bgcol, fgcol, pixel;
|
||
uint16_t colors[4];
|
||
uint8_t chr;
|
||
uint8_t x = 0;
|
||
|
||
CHARSETPTR();
|
||
|
||
if (cpu.vic.lineHasSprites) {
|
||
|
||
colors[0] = cpu.vic.B0C;
|
||
|
||
do {
|
||
|
||
if (cpu.vic.idle) {
|
||
cpu_clock(1);
|
||
fgcol = colors[1] = colors[2] = colors[3] = 0;
|
||
chr = cpu.RAM[cpu.vic.bank + 0x3fff];
|
||
} else {
|
||
BADLINE(x);
|
||
fgcol = cpu.vic.lineMemCol[x];
|
||
colors[1] = cpu.vic.R[0x22];
|
||
colors[2] = cpu.vic.R[0x23];
|
||
colors[3] = fgcol & 0x07;
|
||
chr = cpu.vic.charsetPtr[cpu.vic.lineMemChr[x] * 8];
|
||
}
|
||
|
||
x++;
|
||
|
||
if ((fgcol & 0x08) == 0) { //Zeichen ist HIRES
|
||
|
||
unsigned m = min(8, pe - p);
|
||
for (unsigned i = 0; i < m; i++) {
|
||
|
||
int sprite = *spl++;
|
||
|
||
if (sprite) { // Sprite: Ja
|
||
|
||
/*
|
||
Sprite-Prioritäten (Anzeige)
|
||
MDP = 1: Grafikhintergrund, Sprite, Vordergrund
|
||
MDP = 0: Grafikhintergrund, Vordergrund, Sprite
|
||
|
||
Kollision:
|
||
Eine Kollision zwischen Sprites und anderen Grafikdaten wird erkannt,
|
||
sobald beim Bildaufbau ein nicht transparentes Spritepixel und ein Vordergrundpixel ausgegeben wird.
|
||
|
||
*/
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f; //Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
if (chr & 0x80) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = colors[3];
|
||
}
|
||
} else { // MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum; //Vordergrundpixel ist gesetzt
|
||
}
|
||
|
||
} else { // Kein Sprite
|
||
pixel = (chr >> 7) ? colors[3] : colors[0];
|
||
}
|
||
|
||
*p++ = cpu.vic.palette[pixel];
|
||
|
||
chr = chr << 1;
|
||
}
|
||
|
||
} else {//Zeichen ist MULTICOLOR
|
||
|
||
for (unsigned i = 0; i < 4; i++) {
|
||
if (p >= pe) break;
|
||
int c = (chr >> 6) & 0x03;
|
||
chr = chr << 2;
|
||
|
||
int sprite = *spl++;
|
||
|
||
if (sprite) { // Sprite: Ja
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f; //Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
|
||
if (chr & 0x80) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = colors[c];
|
||
}
|
||
|
||
} else { // MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum; //Vordergrundpixel ist gesetzt
|
||
}
|
||
|
||
} else { // Kein Sprite
|
||
pixel = colors[c];
|
||
|
||
}
|
||
*p++ = cpu.vic.palette[pixel];
|
||
if (p >= pe) break;
|
||
|
||
sprite = *spl++;
|
||
|
||
//Das gleiche nochmal für das nächste Pixel
|
||
if (sprite) { // Sprite: Ja
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f; //Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
|
||
if (chr & 0x80) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = colors[c];
|
||
}
|
||
} else { // MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum; //Vordergrundpixel ist gesetzt
|
||
}
|
||
} else { // Kein Sprite
|
||
pixel = colors[c];
|
||
}
|
||
*p++ = cpu.vic.palette[pixel];
|
||
|
||
}
|
||
|
||
}
|
||
|
||
} while (p < pe);
|
||
PRINTOVERFLOWS
|
||
} else { //Keine Sprites
|
||
|
||
while (p < pe - 8) {
|
||
|
||
int c;
|
||
|
||
bgcol = cpu.vic.colors[1];
|
||
colors[0] = bgcol;
|
||
|
||
if (cpu.vic.idle) {
|
||
cpu_clock(1);
|
||
c = colors[1] = colors[2] = colors[3] = 0;
|
||
chr = cpu.RAM[cpu.vic.bank + 0x3fff];
|
||
} else {
|
||
BADLINE(x);
|
||
|
||
colors[1] = cpu.vic.colors[2];
|
||
colors[2] = cpu.vic.colors[3];
|
||
|
||
chr = cpu.vic.charsetPtr[cpu.vic.lineMemChr[x] * 8];
|
||
c = cpu.vic.lineMemCol[x];
|
||
}
|
||
|
||
x++;
|
||
|
||
if ((c & 0x08) == 0) { //Zeichen ist HIRES
|
||
fgcol = cpu.vic.palette[c & 0x07];
|
||
*p++ = (chr & 0x80) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x40) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x20) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x10) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x08) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x04) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x02) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x01) ? fgcol : bgcol;
|
||
} else {//Zeichen ist MULTICOLOR
|
||
|
||
colors[3] = cpu.vic.palette[c & 0x07];
|
||
pixel = colors[(chr >> 6) & 0x03]; *p++ = pixel; *p++ = pixel;
|
||
pixel = colors[(chr >> 4) & 0x03]; *p++ = pixel; *p++ = pixel;
|
||
pixel = colors[(chr >> 2) & 0x03]; *p++ = pixel; *p++ = pixel;
|
||
pixel = colors[(chr ) & 0x03]; *p++ = pixel; *p++ = pixel;
|
||
}
|
||
|
||
};
|
||
|
||
while (p < pe) {
|
||
|
||
int c;
|
||
|
||
bgcol = cpu.vic.colors[1];
|
||
colors[0] = bgcol;
|
||
|
||
if (cpu.vic.idle) {
|
||
cpu_clock(1);
|
||
c = colors[1] = colors[2] = colors[3] = 0;
|
||
chr = cpu.RAM[cpu.vic.bank + 0x3fff];
|
||
} else {
|
||
BADLINE(x);
|
||
|
||
colors[1] = cpu.vic.colors[2];
|
||
colors[2] = cpu.vic.colors[3];
|
||
|
||
chr = cpu.vic.charsetPtr[cpu.vic.lineMemChr[x] * 8];
|
||
c = cpu.vic.lineMemCol[x];
|
||
}
|
||
|
||
x++;
|
||
|
||
if ((c & 0x08) == 0) { //Zeichen ist HIRES
|
||
fgcol = cpu.vic.palette[c & 0x07];
|
||
*p++ = (chr & 0x80) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x40) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x20) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x10) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x08) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x04) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x02) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x01) ? fgcol : bgcol;
|
||
} else {//Zeichen ist MULTICOLOR
|
||
|
||
colors[3] = cpu.vic.palette[c & 0x07];
|
||
pixel = colors[(chr >> 6) & 0x03]; *p++ = pixel; if (p >= pe) break; *p++ = pixel; if (p >= pe) break;
|
||
pixel = colors[(chr >> 4) & 0x03]; *p++ = pixel; if (p >= pe) break; *p++ = pixel; if (p >= pe) break;
|
||
pixel = colors[(chr >> 2) & 0x03]; *p++ = pixel; if (p >= pe) break; *p++ = pixel; if (p >= pe) break;
|
||
pixel = colors[(chr ) & 0x03]; *p++ = pixel; if (p >= pe) break; *p++ = pixel;
|
||
}
|
||
|
||
};
|
||
PRINTOVERFLOW
|
||
}
|
||
while (x<40) {BADLINE(x); x++;}
|
||
}
|
||
|
||
/*****************************************************************************************************/
|
||
void mode2 (tpixel *p, const tpixel *pe, uint16_t *spl, const uint16_t vc) {
|
||
/*
|
||
Standard-Bitmap-Modus (ECM / BMM / MCM = 0/1/0) ("HIRES")
|
||
In diesem Modus (wie in allen Bitmap-Modi) liest der VIC die Grafikdaten aus einer 320×200-Bitmap,
|
||
in der jedes Bit direkt einem Punkt auf dem Bildschirm entspricht. Die Daten aus der videomatrix
|
||
werden für die Farbinformation benutzt. Da die videomatrix weiterhin nur eine 40×25-Matrix ist,
|
||
können die Farben nur für Blöcke von 8×8 Pixeln individuell bestimmt werden (also eine Art YC-8:1-Format).
|
||
Da die Entwickler des VIC-II den Bitmap-Modus mit sowenig zusätzlichem Schaltungsaufwand wie möglich realisieren wollten
|
||
(der VIC-I hatte noch keinen Bitmap-Modus), ist die Bitmap etwas ungewöhnlich im Speicher abgelegt:
|
||
Im Gegensatz zu modernen Videochips, die die Bitmap linear aus dem Speicher lesen, bilden beim VIC jeweils 8 aufeinanderfolgende Bytes einen 8×8-Pixelblock
|
||
auf dem Bildschirm. Mit den Bits VM10-13 und CB13 aus Register $d018 lassen sich videomatrix und Bitmap im Speicher verschieben.
|
||
Im Standard-Bitmap-Modus entspricht jedes Bit in der Bitmap direkt einem Pixel auf dem Bildschirm.
|
||
Für jeden 8×8-Block können Vorder- und Hintergrundfarbe beliebig eingestellt werden.
|
||
|
||
+----+----+----+----+----+----+----+----+
|
||
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||
+----+----+----+----+----+----+----+----+
|
||
| 8 Pixel (1 Bit/Pixel) |
|
||
| |
|
||
| "0": Farbe aus Bits 0-3 der c-Daten |
|
||
| "1": Farbe aus Bits 4-7 der c-Daten |
|
||
+---------------------------------------+
|
||
|
||
|
||
http://www.devili.iki.fi/Computers/Commodore/C64/Programmers_Reference/Chapter_3/page_127.html
|
||
*/
|
||
|
||
uint8_t chr;
|
||
uint16_t fgcol, pixel;
|
||
uint16_t bgcol;
|
||
uint8_t x = 0;
|
||
uint8_t * bP = cpu.vic.bitmapPtr + vc * 8 + cpu.vic.rc;
|
||
|
||
if (cpu.vic.lineHasSprites) {
|
||
do {
|
||
|
||
BADLINE(x);
|
||
|
||
uint8_t t = cpu.vic.lineMemChr[x];
|
||
fgcol = t >> 4;
|
||
bgcol = t & 0x0f;
|
||
chr = bP[x * 8];
|
||
|
||
x++;
|
||
|
||
unsigned m = min(8, pe - p);
|
||
for (unsigned i = 0; i < m; i++) {
|
||
|
||
int sprite = *spl++;
|
||
|
||
chr = chr << 1;
|
||
if (sprite) { // Sprite: Ja
|
||
/*
|
||
Sprite-Prioritäten (Anzeige)
|
||
MDP = 1: Grafikhintergrund, Sprite, Vordergrund
|
||
MDP = 0: Grafikhintergung, Vordergrund, Sprite
|
||
|
||
Kollision:
|
||
Eine Kollision zwischen Sprites und anderen Grafikdaten wird erkannt,
|
||
sobald beim Bildaufbau ein nicht transparentes Spritepixel und ein Vordergrundpixel ausgegeben wird.
|
||
|
||
*/
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f; //Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
if (chr & 0x80) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = fgcol;
|
||
}
|
||
} else { // MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum; //Vordergrundpixel ist gesetzt
|
||
}
|
||
|
||
} else { // Kein Sprite
|
||
pixel = (chr & 0x80) ? fgcol :cpu.vic.B0C;
|
||
}
|
||
|
||
*p++ = cpu.vic.palette[pixel];
|
||
|
||
}
|
||
} while (p < pe);
|
||
PRINTOVERFLOWS
|
||
} else { //Keine Sprites
|
||
|
||
while (p < pe - 8) {
|
||
//color-ram not used!
|
||
BADLINE(x);
|
||
|
||
uint8_t t = cpu.vic.lineMemChr[x];
|
||
fgcol = cpu.vic.palette[t >> 4];
|
||
bgcol = cpu.vic.palette[t & 0x0f];
|
||
chr = bP[x * 8];
|
||
x++;
|
||
|
||
*p++ = (chr & 0x80) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x40) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x20) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x10) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x08) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x04) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x02) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x01) ? fgcol : bgcol;
|
||
};
|
||
while (p < pe) {
|
||
//color-ram not used!
|
||
BADLINE(x);
|
||
|
||
uint8_t t = cpu.vic.lineMemChr[x];
|
||
fgcol = cpu.vic.palette[t >> 4];
|
||
bgcol = cpu.vic.palette[t & 0x0f];
|
||
chr = bP[x * 8];
|
||
|
||
x++;
|
||
|
||
*p++ = (chr & 0x80) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x40) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x20) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x10) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x08) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x04) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x02) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x01) ? fgcol : bgcol;
|
||
|
||
};
|
||
PRINTOVERFLOW
|
||
}
|
||
while (x<40) {BADLINE(x); x++;}
|
||
}
|
||
/*****************************************************************************************************/
|
||
void mode3 (tpixel *p, const tpixel *pe, uint16_t *spl, const uint16_t vc) {
|
||
/*
|
||
Multicolor-Bitmap-Modus (ECM/BMM/MCM=0/1/1)
|
||
|
||
Ähnlich wie beim Multicolor-Textmodus bilden auch in diesem Modus jeweils
|
||
zwei benachbarte Bits ein (doppelt so breites) Pixel. Die Auflösung
|
||
reduziert sich damit auf 160×200 Pixel.
|
||
|
||
Genau wie beim Multicolor-Textmodus wird die Bitkombination "01" für die
|
||
Spritepriorität und -kollisionserkennung zum "Hintergrund" gezählt.
|
||
|
||
+----+----+----+----+----+----+----+----+
|
||
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||
+----+----+----+----+----+----+----+----+
|
||
| 4 Pixel (2 Bit/Pixel) |
|
||
| |
|
||
| "00": Hintergrundfarbe 0 ($d021) |
|
||
| "01": Farbe aus Bits 4-7 der c-Daten |
|
||
| "10": Farbe aus Bits 0-3 der c-Daten |
|
||
| "11": Farbe aus Bits 8-11 der c-Daten |
|
||
+---------------------------------------+
|
||
|
||
POKE 53265,PEEK(53625)OR 32: POKE 53270,PEEK(53270)OR 16
|
||
*/
|
||
uint8_t * bP = cpu.vic.bitmapPtr + vc * 8 + cpu.vic.rc;
|
||
uint16_t colors[4];
|
||
uint16_t pixel;
|
||
uint8_t chr, x;
|
||
|
||
x = 0;
|
||
|
||
if (cpu.vic.lineHasSprites) {
|
||
colors[0] = cpu.vic.B0C;
|
||
do {
|
||
|
||
if (cpu.vic.idle) {
|
||
cpu_clock(1);
|
||
colors[1] = colors[2] = colors[3] = 0;
|
||
chr = cpu.RAM[cpu.vic.bank + 0x3fff];
|
||
} else {
|
||
BADLINE(x);
|
||
uint8_t t = cpu.vic.lineMemChr[x];
|
||
colors[1] = t >> 4;//10
|
||
colors[2] = t & 0x0f; //01
|
||
colors[3] = cpu.vic.lineMemCol[x];
|
||
chr = bP[x * 8];
|
||
};
|
||
|
||
x++;
|
||
|
||
for (unsigned i = 0; i < 4; i++) {
|
||
if (p >= pe) break;
|
||
uint32_t c = (chr >> 6) & 0x03;
|
||
chr = chr << 2;
|
||
|
||
int sprite = *spl++;
|
||
|
||
if (sprite) { // Sprite: Ja
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f; //Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
if (c & 0x02) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = colors[c];
|
||
}
|
||
} else { // MDP = 0
|
||
if (c & 0x02) cpu.vic.fgcollision |= spritenum; //Vordergundpixel ist gesetzt
|
||
}
|
||
} else { // Kein Sprite
|
||
pixel = colors[c];
|
||
}
|
||
|
||
*p++ = cpu.vic.palette[pixel];
|
||
if (p >= pe) break;
|
||
|
||
sprite = *spl++;
|
||
|
||
if (sprite) { // Sprite: Ja
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f; //Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
|
||
if (c & 0x02) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = colors[c];
|
||
}
|
||
} else { // MDP = 0
|
||
if (c & 0x02) cpu.vic.fgcollision |= spritenum; //Vordergundpixel ist gesetzt
|
||
}
|
||
} else { // Kein Sprite
|
||
pixel = colors[c];
|
||
}
|
||
|
||
*p++ = cpu.vic.palette[pixel];
|
||
|
||
}
|
||
|
||
} while (p < pe);
|
||
PRINTOVERFLOWS
|
||
|
||
} else { //Keine Sprites
|
||
|
||
while (p < pe - 8) {
|
||
|
||
colors[0] = cpu.vic.colors[1];
|
||
|
||
if (cpu.vic.idle) {
|
||
cpu_clock(1);
|
||
colors[1] = colors[2] = colors[3] = 0;
|
||
chr = cpu.RAM[cpu.vic.bank + 0x3fff];
|
||
} else {
|
||
BADLINE(x);
|
||
|
||
uint8_t t = cpu.vic.lineMemChr[x];
|
||
colors[1] = cpu.vic.palette[t >> 4];//10
|
||
colors[2] = cpu.vic.palette[t & 0x0f]; //01
|
||
colors[3] = cpu.vic.palette[cpu.vic.lineMemCol[x]];
|
||
chr = bP[x * 8];
|
||
}
|
||
|
||
x++;
|
||
pixel = colors[(chr >> 6) & 0x03]; *p++ = pixel; *p++ = pixel;
|
||
pixel = colors[(chr >> 4) & 0x03]; *p++ = pixel; *p++ = pixel;
|
||
pixel = colors[(chr >> 2) & 0x03]; *p++ = pixel; *p++ = pixel;
|
||
pixel = colors[chr & 0x03]; *p++ = pixel; *p++ = pixel;
|
||
|
||
};
|
||
while (p < pe) {
|
||
|
||
colors[0] = cpu.vic.colors[1];
|
||
|
||
if (cpu.vic.idle) {
|
||
cpu_clock(1);
|
||
colors[1] = colors[2] = colors[3] = 0;
|
||
chr = cpu.RAM[cpu.vic.bank + 0x3fff];
|
||
} else {
|
||
BADLINE(x);
|
||
|
||
uint8_t t = cpu.vic.lineMemChr[x];
|
||
colors[1] = cpu.vic.palette[t >> 4];//10
|
||
colors[2] = cpu.vic.palette[t & 0x0f]; //01
|
||
colors[3] = cpu.vic.palette[cpu.vic.lineMemCol[x]];
|
||
chr = bP[x * 8];
|
||
}
|
||
|
||
x++;
|
||
pixel = colors[(chr >> 6) & 0x03]; *p++ = pixel; if (p >= pe) break; *p++ = pixel; if (p >= pe) break;
|
||
pixel = colors[(chr >> 4) & 0x03]; *p++ = pixel; if (p >= pe) break; *p++ = pixel; if (p >= pe) break;
|
||
pixel = colors[(chr >> 2) & 0x03]; *p++ = pixel; if (p >= pe) break; *p++ = pixel; if (p >= pe) break;
|
||
pixel = colors[chr & 0x03]; *p++ = pixel; if (p >= pe) break; *p++ = pixel;
|
||
|
||
};
|
||
PRINTOVERFLOW
|
||
}
|
||
while (x<40) {BADLINE(x); x++;}
|
||
}
|
||
/*****************************************************************************************************/
|
||
void mode4 (tpixel *p, const tpixel *pe, uint16_t *spl, const uint16_t vc) {
|
||
//ECM-Textmodus (ECM/BMM/MCM=1/0/0)
|
||
/*
|
||
Dieser Textmodus entspricht dem Standard-Textmodus, erlaubt es aber, für
|
||
jedes einzelne Zeichen eine von vier Hintergrundfarben auszuwählen. Die
|
||
Auswahl geschieht über die oberen beiden Bits des Zeichenzeigers. Dadurch
|
||
reduziert sich der Zeichenvorrat allerdings von 256 auf 64 Zeichen.
|
||
|
||
+----+----+----+----+----+----+----+----+
|
||
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||
+----+----+----+----+----+----+----+----+
|
||
| 8 Pixel (1 Bit/Pixel) |
|
||
| |
|
||
| "0": Je nach Bits 6/7 der c-Daten |
|
||
| 00: Hintergrundfarbe 0 ($d021) |
|
||
| 01: Hintergrundfarbe 1 ($d022) |
|
||
| 10: Hintergrundfarbe 2 ($d023) |
|
||
| 11: Hintergrundfarbe 3 ($d024) |
|
||
| "1": Farbe aus Bits 8-11 der c-Daten |
|
||
+---------------------------------------+
|
||
*/
|
||
// https://www.c64-wiki.de/wiki/Hintergrundfarbe
|
||
// POKE 53265, PEEK(53265) OR 64:REM CURSOR BLINKT ROT abc
|
||
|
||
uint8_t chr, pixel;
|
||
uint16_t fgcol;
|
||
uint16_t bgcol;
|
||
uint8_t x = 0;
|
||
|
||
CHARSETPTR();
|
||
if (cpu.vic.lineHasSprites) {
|
||
do {
|
||
|
||
BADLINE(x);
|
||
|
||
uint32_t td = cpu.vic.lineMemChr[x];
|
||
bgcol = cpu.vic.R[0x21 + ((td >> 6) & 0x03)];
|
||
chr = cpu.vic.charsetPtr[(td & 0x3f) * 8];
|
||
fgcol = cpu.vic.lineMemCol[x];
|
||
|
||
x++;
|
||
|
||
unsigned m = min(8, pe - p);
|
||
for (unsigned i = 0; i < m; i++) {
|
||
|
||
int sprite = *spl++;
|
||
|
||
if (sprite) { // Sprite: Ja
|
||
int spritenum = SPRITENUM(sprite);
|
||
if (sprite & 0x4000) { // Sprite: Hinter Text
|
||
if (chr & 0x80) {
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = fgcol;
|
||
} else pixel = bgcol;
|
||
} else { // Sprite: Vor Text
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum;
|
||
pixel = sprite & 0x0f;
|
||
}
|
||
} else { // Kein Sprite
|
||
pixel = (chr & 0x80) ? fgcol : bgcol;
|
||
}
|
||
|
||
chr = chr << 1;
|
||
*p++ = cpu.vic.palette[pixel];
|
||
}
|
||
} while (p < pe);
|
||
PRINTOVERFLOWS
|
||
}
|
||
else //Keine Sprites
|
||
while (p < pe - 8) {
|
||
|
||
BADLINE(x);
|
||
|
||
uint32_t td = cpu.vic.lineMemChr[x];
|
||
bgcol = cpu.vic.palette[cpu.vic.R[0x21 + ((td >> 6) & 0x03)]];
|
||
chr = cpu.vic.charsetPtr[(td & 0x3f) * 8];
|
||
fgcol = cpu.vic.palette[cpu.vic.lineMemCol[x]];
|
||
x++;
|
||
|
||
*p++ = (chr & 0x80) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x40) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x20) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x10) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x08) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x04) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x02) ? fgcol : bgcol;
|
||
*p++ = (chr & 0x01) ? fgcol : bgcol;
|
||
|
||
};
|
||
while (p < pe) {
|
||
|
||
BADLINE(x);
|
||
|
||
uint32_t td = cpu.vic.lineMemChr[x];
|
||
bgcol = cpu.vic.palette[cpu.vic.R[0x21 + ((td >> 6) & 0x03)]];
|
||
chr = cpu.vic.charsetPtr[(td & 0x3f) * 8];
|
||
fgcol = cpu.vic.palette[cpu.vic.lineMemCol[x]];
|
||
|
||
x++;
|
||
|
||
*p++ = (chr & 0x80) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x40) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x20) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x10) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x08) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x04) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x02) ? fgcol : bgcol; if (p >= pe) break;
|
||
*p++ = (chr & 0x01) ? fgcol : bgcol;
|
||
|
||
};
|
||
PRINTOVERFLOW
|
||
while (x<40) {BADLINE(x); x++;}
|
||
}
|
||
|
||
/*****************************************************************************************************/
|
||
/* Ungültige Modi ************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
void mode5 (tpixel *p, const tpixel *pe, uint16_t *spl, const uint16_t vc) {
|
||
/*
|
||
Ungültiger Textmodus (ECM/BMM/MCM=1/0/1)
|
||
|
||
Das gleichzeitige Setzen der ECM- und MCM-Bits wählt keinen der
|
||
"offiziellen" Grafikmodi des VIC, sondern erzeugt nur schwarze Pixel.
|
||
Nichtsdestotrotz erzeugt der Grafikdatensequenzer auch in diesem Modus
|
||
intern gültige Grafikdaten, die die Spritekollisionserkennung triggern
|
||
können. Über den Umweg der Spritekollisionen kann man die erzeugten Daten
|
||
auch auslesen (sehen kann man nichts, das Bild ist schwarz). Man kann so
|
||
allerdings nur Vordergrund- und Hintergrundpixel unterscheiden, die
|
||
Farbinformation läßt sich aus den Spritekollisionen nicht gewinnen.
|
||
|
||
Die erzeugte Grafik entspricht der des Multicolor-Textmodus, allerdings ist
|
||
der Zeichenvorrat genau wie im ECM-Modus auf 64 Zeichen eingeschränkt.
|
||
*/
|
||
CHARSETPTR();
|
||
|
||
uint8_t chr, pixel;
|
||
uint16_t fgcol;
|
||
uint8_t x = 0;
|
||
|
||
if (cpu.vic.lineHasSprites) {
|
||
|
||
do {
|
||
|
||
BADLINE(x);
|
||
|
||
chr = cpu.vic.charsetPtr[(cpu.vic.lineMemChr[x] & 0x3F) * 8];
|
||
fgcol = cpu.vic.lineMemCol[x];
|
||
|
||
x++;
|
||
|
||
if ((fgcol & 0x08) == 0) { //Zeichen ist HIRES
|
||
|
||
unsigned m = min(8, pe - p);
|
||
for (unsigned i = 0; i < m; i++) {
|
||
|
||
int sprite = *spl;
|
||
*spl++ = 0;
|
||
|
||
if (sprite) { // Sprite: Ja
|
||
|
||
/*
|
||
Sprite-Prioritäten (Anzeige)
|
||
MDP = 1: Grafikhintergrund, Sprite, Vordergrund
|
||
MDP = 0: Grafikhintergrund, Vordergrund, Sprite
|
||
|
||
Kollision:
|
||
Eine Kollision zwischen Sprites und anderen Grafikdaten wird erkannt,
|
||
sobald beim Bildaufbau ein nicht transparentes Spritepixel und ein Vordergrundpixel ausgegeben wird.
|
||
|
||
*/
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f; //Hintergrundgrafik
|
||
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
if (chr & 0x80) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = 0;
|
||
}
|
||
} else { // MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum; //Vordergrundpixel ist gesetzt
|
||
}
|
||
|
||
} else { // Kein Sprite
|
||
pixel = 0;
|
||
}
|
||
|
||
*p++ = cpu.vic.palette[pixel];
|
||
|
||
chr = chr << 1;
|
||
}
|
||
|
||
} else {//Zeichen ist MULTICOLOR
|
||
|
||
for (unsigned i = 0; i < 4; i++) {
|
||
if (p >= pe) break;
|
||
|
||
chr = chr << 2;
|
||
|
||
int sprite = *spl;
|
||
*spl++ = 0;
|
||
|
||
if (sprite) { // Sprite: Ja
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f; //Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
|
||
if (chr & 0x80) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = 0;
|
||
}
|
||
} else { // MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum; //Vordergrundpixel ist gesetzt
|
||
}
|
||
|
||
} else { // Kein Sprite
|
||
pixel = 0;
|
||
|
||
}
|
||
*p++ = cpu.vic.palette[pixel];
|
||
if (p >= pe) break;
|
||
|
||
sprite = *spl;
|
||
*spl++ = 0;
|
||
//Das gleiche nochmal für das nächste Pixel
|
||
if (sprite) { // Sprite: Ja
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f; //Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
if (chr & 0x80) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = 0;
|
||
}
|
||
} else { // MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum; //Vordergrundpixel ist gesetzt
|
||
}
|
||
} else { // Kein Sprite
|
||
pixel = 0;
|
||
}
|
||
*p++ = cpu.vic.palette[pixel];
|
||
|
||
}
|
||
|
||
}
|
||
|
||
} while (p < pe);
|
||
PRINTOVERFLOWS
|
||
|
||
} else { //Keine Sprites
|
||
//Farbe immer schwarz
|
||
const uint16_t bgcol = palette[0];
|
||
while (p < pe - 8) {
|
||
|
||
BADLINE(x);
|
||
x++;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
|
||
};
|
||
while (p < pe) {
|
||
|
||
BADLINE(x);
|
||
x++;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol; if (p >= pe) break;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol; if (p >= pe) break;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol; if (p >= pe) break;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol;
|
||
|
||
};
|
||
PRINTOVERFLOW
|
||
}
|
||
while (x<40) {BADLINE(x); x++;}
|
||
}
|
||
/*****************************************************************************************************/
|
||
void mode6 (tpixel *p, const tpixel *pe, uint16_t *spl, const uint16_t vc) {
|
||
/*
|
||
Ungültiger Bitmap-Modus 1 (ECM/BMM/MCM=1/1/0)
|
||
|
||
Dieser Modus erzeugt nur ebenfalls nur ein schwarzes Bild, die Pixel lassen
|
||
sich allerdings auch hier mit dem Spritekollisionstrick auslesen.
|
||
|
||
Der Aufbau der Grafik ist im Prinzip wie im Standard-Bitmap-Modus, aber die
|
||
Bits 9 und 10 der g-Adressen sind wegen dem gesetzten ECM-Bit immer Null,
|
||
entsprechend besteht auch die Grafik - grob gesagt - aus vier
|
||
"Abschnitten", die jeweils viermal wiederholt dargestellt werden.
|
||
|
||
*/
|
||
|
||
uint8_t chr, pixel;
|
||
uint8_t x = 0;
|
||
uint8_t * bP = cpu.vic.bitmapPtr + vc * 8 + cpu.vic.rc;
|
||
|
||
if (cpu.vic.lineHasSprites) {
|
||
|
||
do {
|
||
|
||
BADLINE(x);
|
||
|
||
chr = bP[x * 8];
|
||
|
||
x++;
|
||
|
||
unsigned m = min(8, pe - p);
|
||
for (unsigned i = 0; i < m; i++) {
|
||
|
||
int sprite = *spl;
|
||
*spl++ = 0;
|
||
|
||
chr = chr << 1;
|
||
if (sprite) { // Sprite: Ja
|
||
/*
|
||
Sprite-Prioritäten (Anzeige)
|
||
MDP = 1: Grafikhintergrund, Sprite, Vordergrund
|
||
MDP = 0: Grafikhintergung, Vordergrund, Sprite
|
||
|
||
Kollision:
|
||
Eine Kollision zwischen Sprites und anderen Grafikdaten wird erkannt,
|
||
sobald beim Bildaufbau ein nicht transparentes Spritepixel und ein Vordergrundpixel ausgegeben wird.
|
||
|
||
*/
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f; //Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
if (chr & 0x80) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = 0;
|
||
}
|
||
} else { // MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum; //Vordergrundpixel ist gesetzt
|
||
}
|
||
|
||
} else { // Kein Sprite
|
||
pixel = 0;
|
||
}
|
||
|
||
*p++ = cpu.vic.palette[pixel];
|
||
|
||
}
|
||
|
||
} while (p < pe);
|
||
PRINTOVERFLOWS
|
||
|
||
} else { //Keine Sprites
|
||
//Farbe immer schwarz
|
||
const uint16_t bgcol = palette[0];
|
||
while (p < pe - 8) {
|
||
|
||
BADLINE(x);
|
||
x++;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
|
||
};
|
||
while (p < pe) {
|
||
|
||
BADLINE(x);
|
||
x++;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol; if (p >= pe) break;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol; if (p >= pe) break;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol; if (p >= pe) break;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol;
|
||
|
||
};
|
||
PRINTOVERFLOW
|
||
}
|
||
while (x<40) {BADLINE(x); x++;}
|
||
}
|
||
/*****************************************************************************************************/
|
||
void mode7 (tpixel *p, const tpixel *pe, uint16_t *spl, const uint16_t vc) {
|
||
/*
|
||
Ungültiger Bitmap-Modus 2 (ECM/BMM/MCM=1/1/1)
|
||
|
||
Der letzte ungültige Modus liefert auch ein schwarzes Bild, das sich jedoch
|
||
genauso mit Hilfe der Sprite-Grafik-Kollisionen "abtasten" läßt.
|
||
|
||
Der Aufbau der Grafik ist im Prinzip wie im Multicolor-Bitmap-Modus, aber
|
||
die Bits 9 und 10 der g-Adressen sind wegen dem gesetzten ECM-Bit immer
|
||
Null, was sich in der Darstellung genauso wie beim ersten ungültigen
|
||
Bitmap-Modus wiederspiegelt. Die Bitkombination "01" wird wie gewohnt zum
|
||
Hintergrund gezählt.
|
||
|
||
*/
|
||
|
||
uint8_t chr;
|
||
uint8_t x = 0;
|
||
uint16_t pixel;
|
||
uint8_t * bP = cpu.vic.bitmapPtr + vc * 8 + cpu.vic.rc;
|
||
|
||
if (cpu.vic.lineHasSprites) {
|
||
|
||
do {
|
||
|
||
BADLINE(x);
|
||
|
||
chr = bP[x * 8];
|
||
x++;
|
||
|
||
for (unsigned i = 0; i < 4; i++) {
|
||
if (p >= pe) break;
|
||
|
||
chr = chr << 2;
|
||
|
||
int sprite = *spl;
|
||
*spl++ = 0;
|
||
|
||
if (sprite) { // Sprite: Ja
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f;//Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
|
||
if (chr & 0x80) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = 0;
|
||
}
|
||
} else { // MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum; //Vordergundpixel ist gesetzt
|
||
}
|
||
} else { // Kein Sprite
|
||
pixel = 0;
|
||
}
|
||
|
||
*p++ = cpu.vic.palette[pixel];
|
||
if (p >= pe) break;
|
||
|
||
sprite = *spl;
|
||
*spl++ = 0;
|
||
|
||
if (sprite) { // Sprite: Ja
|
||
int spritenum = SPRITENUM(sprite);
|
||
pixel = sprite & 0x0f;//Hintergrundgrafik
|
||
if (sprite & 0x4000) { // MDP = 1
|
||
|
||
if (chr & 0x80) { //Vordergrundpixel ist gesetzt
|
||
cpu.vic.fgcollision |= spritenum;
|
||
pixel = 0;
|
||
}
|
||
} else { // MDP = 0
|
||
if (chr & 0x80) cpu.vic.fgcollision |= spritenum; //Vordergundpixel ist gesetzt
|
||
}
|
||
} else { // Kein Sprite
|
||
pixel = 0;
|
||
}
|
||
|
||
*p++ = cpu.vic.palette[pixel];
|
||
|
||
}
|
||
|
||
} while (p < pe);
|
||
PRINTOVERFLOWS
|
||
|
||
} else { //Keine Sprites
|
||
|
||
const uint16_t bgcol = palette[0];
|
||
while (p < pe - 8) {
|
||
|
||
BADLINE(x);
|
||
x++;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
*p++ = bgcol; *p++ = bgcol;
|
||
|
||
};
|
||
while (p < pe) {
|
||
|
||
BADLINE(x);
|
||
x++;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol; if (p >= pe) break;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol; if (p >= pe) break;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol; if (p >= pe) break;
|
||
*p++ = bgcol; if (p >= pe) break; *p++ = bgcol;
|
||
|
||
};
|
||
PRINTOVERFLOW
|
||
|
||
}
|
||
while (x<40) {BADLINE(x); x++;}
|
||
}
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
typedef void (*modes_t)( tpixel *p, const tpixel *pe, uint16_t *spl, const uint16_t vc ); //Funktionspointer
|
||
const modes_t modes[8] = {mode0, mode1, mode2, mode3, mode4, mode5, mode6, mode7};
|
||
|
||
|
||
static tpixel linebuffer[SCREEN_WIDTH];
|
||
|
||
void vic_do(void) {
|
||
|
||
uint16_t vc;
|
||
uint16_t xscroll;
|
||
tpixel *pe;
|
||
tpixel *p;
|
||
uint16_t *spl;
|
||
uint8_t mode;
|
||
|
||
/*****************************************************************************************************/
|
||
/* Linecounter ***************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*
|
||
?PEEK(678) NTSC =0
|
||
?PEEK(678) PAL = 1
|
||
*/
|
||
|
||
if ( cpu.vic.rasterLine >= LINECNT ) {
|
||
//reSID sound needs much time - too much to keep everything in sync and with stable refreshrate
|
||
//but it is not called very often, so most of the time, we have more time than needed.
|
||
//We can measure the time needed for a frame and calc a correction factor to speed things up.
|
||
unsigned long m = fbmicros();
|
||
cpu.vic.neededTime = (m - cpu.vic.timeStart);
|
||
cpu.vic.timeStart = m;
|
||
cpu.vic.lineClock.setIntervalFast(LINETIMER_DEFAULT_FREQ - ((float)cpu.vic.neededTime / (float)LINECNT - LINETIMER_DEFAULT_FREQ ));
|
||
|
||
cpu.vic.rasterLine = 0;
|
||
cpu.vic.vcbase = 0;
|
||
cpu.vic.denLatch = 0;
|
||
//if (cpu.vic.rasterLine == LINECNT) {
|
||
emu_DrawVsync();
|
||
//}
|
||
|
||
} else cpu.vic.rasterLine++;
|
||
|
||
int r = cpu.vic.rasterLine;
|
||
|
||
if (r == cpu.vic.intRasterLine )//Set Rasterline-Interrupt
|
||
cpu.vic.R[0x19] |= 1 | ((cpu.vic.R[0x1a] & 1) << 7);
|
||
|
||
/*****************************************************************************************************/
|
||
/* Badlines ******************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*
|
||
Ein Bad-Line-Zustand liegt in einem beliebigen Taktzyklus vor, wenn an der
|
||
negativen Flanke von ø0 zu Beginn des Zyklus RASTER >= $30 und RASTER <=
|
||
$f7 und die unteren drei Bits von RASTER mit YSCROLL übereinstimmen und in
|
||
einem beliebigen Zyklus von Rasterzeile $30 das DEN-Bit gesetzt war.
|
||
|
||
(default 3)
|
||
yscroll : POKE 53265, PEEK(53265) AND 248 OR 1:POKE 1024,0
|
||
yscroll : poke 53265, peek(53265) and 248 or 1
|
||
|
||
DEN : POKE 53265, PEEK(53265) AND 224 Bildschirm aus
|
||
|
||
Die einzige Verwendung von YSCROLL ist der Vergleich mit r in der Badline
|
||
|
||
*/
|
||
|
||
if (r == 0x30 ) cpu.vic.denLatch |= cpu.vic.DEN;
|
||
|
||
/* 3.7.2
|
||
2. In der ersten Phase von Zyklus 14 jeder Zeile wird VC mit VCBASE geladen
|
||
(VCBASE->VC) und VMLI gelöscht. Wenn zu diesem Zeitpunkt ein
|
||
Bad-Line-Zustand vorliegt, wird zusätzlich RC auf Null gesetzt.
|
||
*/
|
||
|
||
vc = cpu.vic.vcbase;
|
||
|
||
cpu.vic.badline = (cpu.vic.denLatch && (r >= 0x30) && (r <= 0xf7) && ( (r & 0x07) == cpu.vic.YSCROLL));
|
||
|
||
if (cpu.vic.badline) {
|
||
cpu.vic.idle = 0;
|
||
cpu.vic.rc = 0;
|
||
}
|
||
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
#if 1
|
||
{
|
||
int t = MAXCYCLESSPRITES3_7 - cpu.vic.spriteCycles3_7;
|
||
if (t > 0) cpu_clock(t);
|
||
if (cpu.vic.spriteCycles3_7 > 0) cia_clockt(cpu.vic.spriteCycles3_7);
|
||
}
|
||
#endif
|
||
|
||
//HBlank:
|
||
cpu_clock(10);
|
||
|
||
#ifdef ADDITIONALCYCLES
|
||
cpu_clock(ADDITIONALCYCLES);
|
||
#endif
|
||
|
||
//cpu.vic.videomatrix = cpu.vic.bank + (unsigned)(cpu.vic.R[0x18] & 0xf0) * 64;
|
||
|
||
/* Rand oben /unten **********************************************************************************/
|
||
/*
|
||
RSEL Höhe des Anzeigefensters Erste Zeile Letzte Zeile
|
||
0 24 Textzeilen/192 Pixel 55 ($37) 246 ($f6) = 192 sichtbare Zeilen, der Rest ist Rand oder unsichtbar
|
||
1 25 Textzeilen/200 Pixel 51 ($33) 250 ($fa) = 200 sichtbare Zeilen, der Rest ist Rand oder unsichtbar
|
||
*/
|
||
|
||
if (cpu.vic.borderFlag) {
|
||
int firstLine = (cpu.vic.RSEL) ? 0x33 : 0x37;
|
||
if ((cpu.vic.DEN) && (r == firstLine)) cpu.vic.borderFlag = false;
|
||
} else {
|
||
int lastLine = (cpu.vic.RSEL) ? 0xfb : 0xf7;
|
||
if (r == lastLine) cpu.vic.borderFlag = true;
|
||
}
|
||
|
||
if (r < FIRSTDISPLAYLINE || r > LASTDISPLAYLINE ) {
|
||
if (r == 0)
|
||
cpu_clock(CYCLESPERRASTERLINE - 10 - 2 - MAXCYCLESSPRITES - 1); // (minus hblank l + r)
|
||
else
|
||
cpu_clock(CYCLESPERRASTERLINE - 10 - 2 - MAXCYCLESSPRITES );
|
||
goto noDisplayIncRC;
|
||
}
|
||
|
||
//max_x = (!cpu.vic.CSEL) ? 40:38;
|
||
#ifdef USE_VGA
|
||
p = (tpixel*)emu_LineBuffer((r - FIRSTDISPLAYLINE));
|
||
#else
|
||
p = &linebuffer[0];
|
||
#endif
|
||
pe = p + SCREEN_WIDTH;
|
||
//Left Screenborder: Cycle 10
|
||
spl = &cpu.vic.spriteLine[24];
|
||
cpu_clock(6);
|
||
|
||
|
||
if (cpu.vic.borderFlag) {
|
||
cpu_clock(5);
|
||
fastFillLineNoSprites(p, pe + BORDER_RIGHT, cpu.vic.colors[0]);
|
||
goto noDisplayIncRC ;
|
||
}
|
||
|
||
|
||
/*****************************************************************************************************/
|
||
/* DISPLAY *******************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
|
||
//max_x = (!cpu.vic.CSEL) ? 40:38;
|
||
//X-Scrolling:
|
||
|
||
xscroll = cpu.vic.XSCROLL;
|
||
|
||
if (xscroll > 0) {
|
||
uint16_t col = cpu.vic.colors[0];
|
||
|
||
if (!cpu.vic.CSEL) {
|
||
cpu_clock(1);
|
||
uint16_t sprite;
|
||
for (int i = 0; i < xscroll; i++) {
|
||
SPRITEORFIXEDCOLOR();
|
||
}
|
||
} else {
|
||
spl += xscroll;
|
||
for (unsigned i = 0; i < xscroll; i++) {
|
||
*p++ = col;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
|
||
cpu.vic.fgcollision = 0;
|
||
mode = (cpu.vic.ECM << 2) | (cpu.vic.BMM << 1) | cpu.vic.MCM;
|
||
|
||
if ( !cpu.vic.idle) {
|
||
|
||
#if 0
|
||
static uint8_t omode = 99;
|
||
if (mode != omode) {
|
||
Serial.print("Graphicsmode:");
|
||
Serial.println(mode);
|
||
omode = mode;
|
||
}
|
||
#endif
|
||
|
||
modes[mode](p, pe, spl, vc);
|
||
vc = (vc + 40) & 0x3ff;
|
||
|
||
} else {
|
||
/*
|
||
3.7.3.9. Idle-Zustand
|
||
---------------------
|
||
|
||
Im Idle-Zustand liest der VIC die Grafikdaten von Adresse $3fff (bzw. $39ff
|
||
bei gesetztem ECM-Bit) und stellt sie im ausgewählten Grafikmodus dar,
|
||
wobei aber die Videomatrix-Daten (normalerweise in den c-Zugriffen gelesen)
|
||
nur aus "0"-Bits bestehen. Es wird also immer wiederholt das Byte an
|
||
Adresse $3fff/$39ff ausgegeben.
|
||
|
||
c-Zugriff
|
||
|
||
Es werden keine c-Zugriffe ausgeführt.
|
||
|
||
Daten
|
||
|
||
+----+----+----+----+----+----+----+----+----+----+----+----+
|
||
| 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||
+----+----+----+----+----+----+----+----+----+----+----+----+
|
||
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
|
||
+----+----+----+----+----+----+----+----+----+----+----+----+
|
||
|
||
g-Zugriff
|
||
|
||
Adressen (ECM=0)
|
||
|
||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|
||
| 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|
||
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|
||
|
||
Adressen (ECM=1)
|
||
|
||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|
||
| 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|
||
| 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|
||
|
||
Daten
|
||
|
||
+----+----+----+----+----+----+----+----+
|
||
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||
+----+----+----+----+----+----+----+----+
|
||
| 8 Pixel (1 Bit/Pixel) | Standard-Textmodus/
|
||
| | Multicolor-Textmodus/
|
||
| "0": Hintergrundfarbe 0 ($d021) | ECM-Textmodus
|
||
| "1": Schwarz |
|
||
+---------------------------------------+
|
||
| 8 Pixel (1 Bit/Pixel) | Standard-Bitmap-Modus/
|
||
| | Ungültiger Textmodus/
|
||
| "0": Schwarz (Hintergrund) | Ungültiger Bitmap-Modus 1
|
||
| "1": Schwarz (Vordergrund) |
|
||
+---------------------------------------+
|
||
| 4 Pixel (2 Bit/Pixel) | Multicolor-Bitmap-Modus
|
||
| |
|
||
| "00": Hintergrundfarbe 0 ($d021) |
|
||
| "01": Schwarz (Hintergrund) |
|
||
| "10": Schwarz (Vordergrund) |
|
||
| "11": Schwarz (Vordergrund) |
|
||
+---------------------------------------+
|
||
| 4 Pixel (2 Bit/Pixel) | Ungültiger Bitmap-Modus 2
|
||
| |
|
||
| "00": Schwarz (Hintergrund) |
|
||
| "01": Schwarz (Hintergrund) |
|
||
| "10": Schwarz (Vordergrund) |
|
||
| "11": Schwarz (Vordergrund) |
|
||
+---------------------------------------+
|
||
*/
|
||
//Modes 1 & 3
|
||
if (mode == 1 || mode == 3) {
|
||
modes[mode](p, pe, spl, vc);
|
||
} else {//TODO: all other modes
|
||
fastFillLine(p, pe, cpu.vic.palette[0], spl);
|
||
}
|
||
}
|
||
|
||
/*
|
||
Bei den MBC- und MMC-Interrupts löst jeweils nur die erste Kollision einen
|
||
Interrupt aus (d.h. wenn die Kollisionsregister $d01e bzw. $d01f vor der
|
||
Kollision den Inhalt Null hatten). Um nach einer Kollision weitere
|
||
Interrupts auszulösen, muß das betreffende Register erst durch Auslesen
|
||
gelöscht werden.
|
||
*/
|
||
|
||
if (cpu.vic.fgcollision) {
|
||
if (cpu.vic.MD == 0) {
|
||
cpu.vic.R[0x19] |= 2 | ( (cpu.vic.R[0x1a] & 2) << 6);
|
||
}
|
||
cpu.vic.MD |= cpu.vic.fgcollision;
|
||
}
|
||
|
||
/*****************************************************************************************************/
|
||
|
||
if (!cpu.vic.CSEL) {
|
||
cpu_clock(1);
|
||
uint16_t col = cpu.vic.colors[0];
|
||
#ifdef USE_VGA
|
||
p = (tpixel*)emu_LineBuffer((r - FIRSTDISPLAYLINE)) + BORDER_LEFT;
|
||
#else
|
||
p = &linebuffer[0]; // tft.getLineBuffer((r - FIRSTDISPLAYLINE));
|
||
#endif
|
||
#if 0
|
||
// Sprites im Rand
|
||
uint16_t sprite;
|
||
uint16_t * spl;
|
||
spl = &cpu.vic.spriteLine[24 + xscroll];
|
||
|
||
SPRITEORFIXEDCOLOR()
|
||
SPRITEORFIXEDCOLOR()
|
||
SPRITEORFIXEDCOLOR()
|
||
SPRITEORFIXEDCOLOR()
|
||
SPRITEORFIXEDCOLOR()
|
||
SPRITEORFIXEDCOLOR()
|
||
SPRITEORFIXEDCOLOR() //7
|
||
#else
|
||
//keine Sprites im Rand
|
||
*p++ = col; *p++ = col; *p++ = col; *p++ = col;
|
||
*p++ = col; *p++ = col; *p = col;
|
||
|
||
#endif
|
||
|
||
//Rand rechts:
|
||
#ifdef USE_VGA
|
||
p = (tpixel*)emu_LineBuffer((r - FIRSTDISPLAYLINE)) + SCREEN_WIDTH - 9 + BORDER_LEFT;
|
||
#else
|
||
p = &linebuffer[SCREEN_WIDTH - 9 + BORDER_LEFT]; //tft.getLineBuffer((r - FIRSTDISPLAYLINE)) + SCREEN_WIDTH - 9 + BORDER_LEFT;
|
||
#endif
|
||
pe = p + 9;
|
||
|
||
#if 0
|
||
// Sprites im Rand
|
||
spl = &cpu.vic.spriteLine[24 + SCREEN_WIDTH - 9 + xscroll];
|
||
while (p < pe) {
|
||
SPRITEORFIXEDCOLOR();
|
||
}
|
||
#else
|
||
//keine Sprites im Rand
|
||
//while (p < pe) {
|
||
// *p++ = col;
|
||
//}
|
||
#endif
|
||
|
||
|
||
|
||
}
|
||
|
||
#ifdef USE_VGA
|
||
//emu_DrawLine8(&linebuffer[0], SCREEN_WIDTH, SCREEN_HEIGHT, (r - FIRSTDISPLAYLINE));
|
||
#else
|
||
emu_DrawLine16(&linebuffer[0], SCREEN_WIDTH, SCREEN_HEIGHT, (r - FIRSTDISPLAYLINE));
|
||
//memset(&linebuffer[0],0,320*2);
|
||
#endif
|
||
|
||
|
||
|
||
|
||
//Rechter Rand nach CSEL, im Textbereich
|
||
cpu_clock(5);
|
||
|
||
|
||
noDisplayIncRC:
|
||
/* 3.7.2
|
||
5. In der ersten Phase von Zyklus 58 wird geprüft, ob RC=7 ist. Wenn ja,
|
||
geht die Videologik in den Idle-Zustand und VCBASE wird mit VC geladen
|
||
(VC->VCBASE). Ist die Videologik danach im Display-Zustand (liegt ein
|
||
Bad-Line-Zustand vor, ist dies immer der Fall), wird RC erhöht.
|
||
*/
|
||
|
||
if (cpu.vic.rc == 7) {
|
||
cpu.vic.idle = 1;
|
||
cpu.vic.vcbase = vc;
|
||
}
|
||
//Ist dies richtig ??
|
||
if ((!cpu.vic.idle) || (cpu.vic.denLatch && (r >= 0x30) && (r <= 0xf7) && ( (r & 0x07) == cpu.vic.YSCROLL))) {
|
||
cpu.vic.rc = (cpu.vic.rc + 1) & 0x07;
|
||
}
|
||
|
||
|
||
/*****************************************************************************************************/
|
||
/* Sprites *******************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
cpu.vic.spriteCycles0_2 = 0;
|
||
cpu.vic.spriteCycles3_7 = 0;
|
||
|
||
if (cpu.vic.lineHasSprites) {
|
||
cpu.vic.lineHasSprites = 0;
|
||
memset(cpu.vic.spriteLine, 0, sizeof(cpu.vic.spriteLine) );
|
||
}
|
||
|
||
uint32_t spriteYCheck = cpu.vic.R[0x15]; //Sprite enabled Register
|
||
|
||
if (spriteYCheck) {
|
||
|
||
unsigned short R17 = cpu.vic.R[0x17]; //Sprite-y-expansion
|
||
unsigned char collision = 0;
|
||
short lastSpriteNum = 0;
|
||
|
||
for (unsigned short i = 0; i < 8; i++) {
|
||
if (!spriteYCheck) break;
|
||
|
||
unsigned b = 1 << i;
|
||
|
||
if (spriteYCheck & b ) {
|
||
spriteYCheck &= ~b;
|
||
short y = cpu.vic.R[i * 2 + 1];
|
||
|
||
if ( (r >= y ) && //y-Position > Sprite-y ?
|
||
(((r < y + 21) && (~R17 & b )) || // ohne y-expansion
|
||
((r < y + 2 * 21 ) && (R17 & b ))) ) //mit y-expansion
|
||
{
|
||
|
||
//Sprite Cycles
|
||
if (i < 3) {
|
||
if (!lastSpriteNum) cpu.vic.spriteCycles0_2 += 1;
|
||
cpu.vic.spriteCycles0_2 += 2;
|
||
} else {
|
||
if (!lastSpriteNum) cpu.vic.spriteCycles3_7 += 1;
|
||
cpu.vic.spriteCycles3_7 += 2;
|
||
}
|
||
lastSpriteNum = i;
|
||
//Sprite Cycles END
|
||
|
||
|
||
if (r < FIRSTDISPLAYLINE || r > LASTDISPLAYLINE ) continue;
|
||
|
||
uint16_t x = (((cpu.vic.R[0x10] >> i) & 1) << 8) | cpu.vic.R[i * 2];
|
||
if (x >= SPRITE_MAX_X) continue;
|
||
|
||
unsigned short lineOfSprite = r - y;
|
||
if (R17 & b) lineOfSprite = lineOfSprite / 2; // Y-Expansion
|
||
unsigned short spriteadr = cpu.vic.bank | cpu.RAM[cpu.vic.videomatrix + (1024 - 8) + i] << 6 | (lineOfSprite * 3);
|
||
unsigned spriteData = ((unsigned)cpu.RAM[ spriteadr ] << 16) | ((unsigned)cpu.RAM[ spriteadr + 1 ] << 8) | ((unsigned)cpu.RAM[ spriteadr + 2 ]);
|
||
|
||
if (!spriteData) continue;
|
||
cpu.vic.lineHasSprites = 1;
|
||
|
||
uint16_t * slp = &cpu.vic.spriteLine[x]; //Sprite-Line-Pointer
|
||
unsigned short upperByte = ( 0x80 | ( (cpu.vic.MDP & b) ? 0x40 : 0 ) | i ) << 8; //Bit7 = Sprite "da", Bit 6 = Sprite-Priorität vor Grafik/Text, Bits 3..0 = Spritenummer
|
||
|
||
//Sprite in Spritezeile schreiben:
|
||
if ((cpu.vic.MMC & b) == 0) { // NO MULTICOLOR
|
||
|
||
uint16_t color = upperByte | cpu.vic.R[0x27 + i];
|
||
|
||
if ((cpu.vic.MXE & b) == 0) { // NO MULTICOLOR, NO SPRITE-EXPANSION
|
||
|
||
for (unsigned cnt = 0; (spriteData > 0) && (cnt < 24); cnt++) {
|
||
int c = (spriteData >> 23) & 0x01;
|
||
spriteData = (spriteData << 1);
|
||
|
||
if (c) {
|
||
if (*slp == 0) *slp = color;
|
||
else collision |= b | (1 << ((*slp >> 8) & 0x07));
|
||
}
|
||
slp++;
|
||
|
||
}
|
||
|
||
} else { // NO MULTICOLOR, SPRITE-EXPANSION
|
||
|
||
for (unsigned cnt = 0; (spriteData > 0) && (cnt < 24); cnt++) {
|
||
int c = (spriteData >> 23) & 0x01;
|
||
spriteData = (spriteData << 1);
|
||
//So wie oben, aber zwei gleiche Pixel
|
||
|
||
if (c) {
|
||
if (*slp == 0) *slp = color;
|
||
else collision |= b | (1 << ((*slp >> 8) & 0x07));
|
||
slp++;
|
||
if (*slp == 0) *slp = color;
|
||
else collision |= b | (1 << ((*slp >> 8) & 0x07));
|
||
slp++;
|
||
} else {
|
||
slp += 2;
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
|
||
|
||
} else { // MULTICOLOR
|
||
/* Im Mehrfarbenmodus (Multicolor-Modus) bekommen alle Sprites zwei zusätzliche gemeinsame Farben.
|
||
Die horizontale Auflösung wird von 24 auf 12 halbiert, da bei der Sprite-Definition jeweils zwei Bits zusammengefasst werden.
|
||
*/
|
||
uint16_t colors[4];
|
||
//colors[0] = 1; //dummy, color 0 is transparent
|
||
colors[1] = upperByte | cpu.vic.R[0x25];
|
||
colors[2] = upperByte | cpu.vic.R[0x27 + i];
|
||
colors[3] = upperByte | cpu.vic.R[0x26];
|
||
|
||
if ((cpu.vic.MXE & b) == 0) { // MULTICOLOR, NO SPRITE-EXPANSION
|
||
for (unsigned cnt = 0; (spriteData > 0) && (cnt < 24); cnt++) {
|
||
int c = (spriteData >> 22) & 0x03;
|
||
spriteData = (spriteData << 2);
|
||
|
||
if (c) {
|
||
if (*slp == 0) *slp = colors[c];
|
||
else collision |= b | (1 << ((*slp >> 8) & 0x07));
|
||
slp++;
|
||
if (*slp == 0) *slp = colors[c];
|
||
else collision |= b | (1 << ((*slp >> 8) & 0x07));
|
||
slp++;
|
||
} else {
|
||
slp += 2;
|
||
}
|
||
|
||
}
|
||
|
||
} else { // MULTICOLOR, SPRITE-EXPANSION
|
||
for (unsigned cnt = 0; (spriteData > 0) && (cnt < 24); cnt++) {
|
||
int c = (spriteData >> 22) & 0x03;
|
||
spriteData = (spriteData << 2);
|
||
|
||
//So wie oben, aber vier gleiche Pixel
|
||
if (c) {
|
||
if (*slp == 0) *slp = colors[c];
|
||
else collision |= b | (1 << ((*slp >> 8) & 0x07));
|
||
slp++;
|
||
if (*slp == 0) *slp = colors[c];
|
||
else collision |= b | (1 << ((*slp >> 8) & 0x07));
|
||
slp++;
|
||
if (*slp == 0) *slp = colors[c];
|
||
else collision |= b | (1 << ((*slp >> 8) & 0x07));
|
||
slp++;
|
||
if (*slp == 0) *slp = colors[c];
|
||
else collision |= b | (1 << ((*slp >> 8) & 0x07));
|
||
slp++;
|
||
} else {
|
||
slp += 4;
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
}
|
||
else lastSpriteNum = 0;
|
||
}
|
||
|
||
}
|
||
|
||
if (collision) {
|
||
if (cpu.vic.MM == 0) {
|
||
cpu.vic.R[0x19] |= 4 | ((cpu.vic.R[0x1a] & 4) << 5 );
|
||
}
|
||
cpu.vic.MM |= collision;
|
||
}
|
||
|
||
}
|
||
/*****************************************************************************************************/
|
||
#if 0
|
||
{
|
||
int t = MAXCYCLESSPRITES0_2 - cpu.vic.spriteCycles0_2;
|
||
if (t > 0) cpu_clock(t);
|
||
if (cpu.vic.spriteCycles0_2 > 0) cia_clockt(cpu.vic.spriteCycles0_2);
|
||
}
|
||
#endif
|
||
|
||
//HBlank:
|
||
#if PAL
|
||
cpu_clock(2);
|
||
#else
|
||
cpu_clock(3);
|
||
#endif
|
||
|
||
|
||
#if 0
|
||
if (cpu.vic.idle) {
|
||
Serial.print("Cycles line ");
|
||
Serial.print(r);
|
||
Serial.print(": ");
|
||
Serial.println(cpu.lineCyclesAbs);
|
||
}
|
||
#endif
|
||
|
||
|
||
return;
|
||
}
|
||
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
void fastFillLineNoSprites(tpixel * p, const tpixel * pe, const uint16_t col) {
|
||
int i = 0;
|
||
|
||
while (p < pe) {
|
||
*p++ = col;
|
||
i = (i + 1) & 0x07;
|
||
if (!i) CYCLES(1);
|
||
}
|
||
|
||
|
||
}
|
||
|
||
void fastFillLine(tpixel * p, const tpixel * pe, const uint16_t col, uint16_t * spl) {
|
||
if (spl != NULL && cpu.vic.lineHasSprites) {
|
||
int i = 0;
|
||
uint16_t sprite;
|
||
while ( p < pe ) {
|
||
SPRITEORFIXEDCOLOR();
|
||
i = (i + 1) & 0x07;
|
||
if (!i) CYCLES(1);
|
||
};
|
||
|
||
} else {
|
||
|
||
fastFillLineNoSprites(p, pe, col);
|
||
|
||
}
|
||
}
|
||
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
void vic_displaySimpleModeScreen(void) {
|
||
}
|
||
|
||
|
||
void vic_do_simple(void) {
|
||
uint16_t vc;
|
||
int cycles = 0;
|
||
|
||
if ( cpu.vic.rasterLine >= LINECNT ) {
|
||
|
||
//reSID sound needs much time - too much to keep everything in sync and with stable refreshrate
|
||
//but it is not called very often, so most of the time, we have more time than needed.
|
||
//We can measure the time needed for a frame and calc a correction factor to speed things up.
|
||
unsigned long m = fbmicros();
|
||
cpu.vic.neededTime = (m - cpu.vic.timeStart);
|
||
cpu.vic.timeStart = m;
|
||
cpu.vic.lineClock.setIntervalFast(LINETIMER_DEFAULT_FREQ - ((float)cpu.vic.neededTime / (float)LINECNT - LINETIMER_DEFAULT_FREQ ));
|
||
|
||
cpu.vic.rasterLine = 0;
|
||
cpu.vic.vcbase = 0;
|
||
cpu.vic.denLatch = 0;
|
||
|
||
} else {
|
||
cpu.vic.rasterLine++;
|
||
cpu_clock(1);
|
||
cycles += 1;
|
||
}
|
||
|
||
int r = cpu.vic.rasterLine;
|
||
|
||
if (r == cpu.vic.intRasterLine )//Set Rasterline-Interrupt
|
||
cpu.vic.R[0x19] |= 1 | ((cpu.vic.R[0x1a] & 1) << 7);
|
||
|
||
cpu_clock(9);
|
||
cycles += 9;
|
||
|
||
if (r == 0x30 ) cpu.vic.denLatch |= cpu.vic.DEN;
|
||
|
||
vc = cpu.vic.vcbase;
|
||
|
||
cpu.vic.badline = (cpu.vic.denLatch && (r >= 0x30) && (r <= 0xf7) && ( (r & 0x07) == cpu.vic.YSCROLL));
|
||
|
||
if (cpu.vic.badline) {
|
||
cpu.vic.idle = 0;
|
||
cpu.vic.rc = 0;
|
||
}
|
||
|
||
|
||
/* Rand oben /unten **********************************************************************************/
|
||
/*
|
||
RSEL Höhe des Anzeigefensters Erste Zeile Letzte Zeile
|
||
0 24 Textzeilen/192 Pixel 55 ($37) 246 ($f6) = 192 sichtbare Zeilen, der Rest ist Rand oder unsichtbar
|
||
1 25 Textzeilen/200 Pixel 51 ($33) 250 ($fa) = 200 sichtbare Zeilen, der Rest ist Rand oder unsichtbar
|
||
*/
|
||
|
||
if (cpu.vic.borderFlag) {
|
||
int firstLine = (cpu.vic.RSEL) ? 0x33 : 0x37;
|
||
if ((cpu.vic.DEN) && (r == firstLine)) cpu.vic.borderFlag = false;
|
||
} else {
|
||
int lastLine = (cpu.vic.RSEL) ? 0xfb : 0xf7;
|
||
if (r == lastLine) cpu.vic.borderFlag = true;
|
||
}
|
||
|
||
|
||
//left screenborder
|
||
cpu_clock(6);
|
||
cycles += 6;
|
||
|
||
CYCLES(40);
|
||
cycles += 40;
|
||
vc += 40;
|
||
|
||
//right screenborder
|
||
cpu_clock(4); //1
|
||
cycles += 4;
|
||
|
||
|
||
if (cpu.vic.rc == 7) {
|
||
cpu.vic.idle = 1;
|
||
cpu.vic.vcbase = vc;
|
||
}
|
||
//Ist dies richtig ??
|
||
if ((!cpu.vic.idle) || (cpu.vic.denLatch && (r >= 0x30) && (r <= 0xf7) && ( (r & 0x07) == cpu.vic.YSCROLL))) {
|
||
cpu.vic.rc = (cpu.vic.rc + 1) & 0x07;
|
||
}
|
||
|
||
cpu_clock(3); //1
|
||
cycles += 3;
|
||
|
||
int cyclesleft = CYCLESPERRASTERLINE - cycles;
|
||
if (cyclesleft) cpu_clock(cyclesleft);
|
||
|
||
}
|
||
|
||
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
void installPalette(void) {
|
||
memcpy(cpu.vic.palette, (void*)palette, sizeof(cpu.vic.palette));
|
||
}
|
||
|
||
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
void vic_adrchange(void) {
|
||
uint8_t r18 = cpu.vic.R[0x18];
|
||
cpu.vic.videomatrix = cpu.vic.bank + (unsigned)(r18 & 0xf0) * 64;
|
||
|
||
unsigned charsetAddr = r18 & 0x0e;
|
||
if ((cpu.vic.bank & 0x4000) == 0) {
|
||
if (charsetAddr == 0x04) cpu.vic.charsetPtrBase = ((uint8_t *)&rom_characters);
|
||
else if (charsetAddr == 0x06) cpu.vic.charsetPtrBase = ((uint8_t *)&rom_characters) + 0x800;
|
||
else
|
||
cpu.vic.charsetPtrBase = &cpu.RAM[charsetAddr * 0x400 + cpu.vic.bank] ;
|
||
} else
|
||
cpu.vic.charsetPtrBase = &cpu.RAM[charsetAddr * 0x400 + cpu.vic.bank];
|
||
|
||
cpu.vic.bitmapPtr = (uint8_t*) &cpu.RAM[cpu.vic.bank | ((r18 & 0x08) * 0x400)];
|
||
if ((cpu.vic.R[0x11] & 0x60) == 0x60) cpu.vic.bitmapPtr = (uint8_t*)((uintptr_t)cpu.vic.bitmapPtr & 0xf9ff);
|
||
|
||
}
|
||
/*****************************************************************************************************/
|
||
void vic_write(uint32_t address, uint8_t value) {
|
||
|
||
address &= 0x3F;
|
||
|
||
switch (address) {
|
||
case 0x11 :
|
||
cpu.vic.R[address] = value;
|
||
cpu.vic.intRasterLine = (cpu.vic.intRasterLine & 0xff) | ((((uint16_t) value) << 1) & 0x100);
|
||
if (cpu.vic.rasterLine == 0x30 ) cpu.vic.denLatch |= value & 0x10;
|
||
|
||
cpu.vic.badline = (cpu.vic.denLatch && (cpu.vic.rasterLine >= 0x30) && (cpu.vic.rasterLine <= 0xf7) && ( (cpu.vic.rasterLine & 0x07) == (value & 0x07)));
|
||
|
||
if (cpu.vic.badline) {
|
||
cpu.vic.idle = 0;
|
||
}
|
||
|
||
vic_adrchange();
|
||
|
||
break;
|
||
case 0x12 :
|
||
cpu.vic.intRasterLine = (cpu.vic.intRasterLine & 0x100) | value;
|
||
cpu.vic.R[address] = value;
|
||
break;
|
||
case 0x18 :
|
||
cpu.vic.R[address] = value;
|
||
vic_adrchange();
|
||
break;
|
||
case 0x19 : //IRQs
|
||
cpu.vic.R[0x19] &= (~value & 0x0f);
|
||
break;
|
||
case 0x1A : //IRQ Mask
|
||
cpu.vic.R[address] = value & 0x0f;
|
||
break;
|
||
case 0x1e:
|
||
case 0x1f:
|
||
cpu.vic.R[address] = 0;
|
||
break;
|
||
case 0x20 ... 0x2E:
|
||
cpu.vic.R[address] = value & 0x0f;
|
||
cpu.vic.colors[address - 0x20] = cpu.vic.palette[value & 0x0f];
|
||
break;
|
||
case 0x2F ... 0x3F:
|
||
break;
|
||
default :
|
||
cpu.vic.R[address] = value;
|
||
break;
|
||
}
|
||
|
||
//#if DEBUGVIC
|
||
#if 0
|
||
Serial.print("VIC ");
|
||
Serial.print(address, HEX);
|
||
Serial.print("=");
|
||
Serial.println(value, HEX);
|
||
//logAddr(address, value, 1);
|
||
#endif
|
||
}
|
||
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
uint8_t vic_read(uint32_t address) {
|
||
uint8_t ret;
|
||
|
||
address &= 0x3F;
|
||
switch (address) {
|
||
|
||
case 0x11:
|
||
ret = (cpu.vic.R[address] & 0x7F) | ((cpu.vic.rasterLine & 0x100) >> 1);
|
||
break;
|
||
case 0x12:
|
||
ret = cpu.vic.rasterLine;
|
||
break;
|
||
case 0x16:
|
||
ret = cpu.vic.R[address] | 0xC0;
|
||
break;
|
||
case 0x18:
|
||
ret = cpu.vic.R[address] | 0x01;
|
||
break;
|
||
case 0x19:
|
||
ret = cpu.vic.R[address] | 0x70;
|
||
break;
|
||
case 0x1a:
|
||
ret = cpu.vic.R[address] | 0xF0;
|
||
break;
|
||
case 0x1e:
|
||
case 0x1f:
|
||
ret = cpu.vic.R[address];
|
||
cpu.vic.R[address] = 0;
|
||
break;
|
||
case 0x20 ... 0x2E:
|
||
ret = cpu.vic.R[address] | 0xF0;
|
||
break;
|
||
case 0x2F ... 0x3F:
|
||
ret = 0xFF;
|
||
break;
|
||
default:
|
||
ret = cpu.vic.R[address];
|
||
break;
|
||
}
|
||
|
||
#if DEBUGVIC
|
||
Serial.print("VIC ");
|
||
logAddr(address, ret, 0);
|
||
#endif
|
||
return ret;
|
||
}
|
||
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
/*****************************************************************************************************/
|
||
|
||
void resetVic(void) {
|
||
enableCycleCounter();
|
||
|
||
cpu.vic.intRasterLine = 0;
|
||
cpu.vic.rasterLine = 0;
|
||
cpu.vic.lineHasSprites = 0;
|
||
memset(&cpu.RAM[0x400], 0, 1000);
|
||
memset(&cpu.vic, 0, sizeof(cpu.vic));
|
||
|
||
|
||
|
||
installPalette();
|
||
|
||
//http://dustlayer.com/vic-ii/2013/4/22/when-visibility-matters
|
||
cpu.vic.R[0x11] = 0x9B;
|
||
cpu.vic.R[0x16] = 0x08;
|
||
cpu.vic.R[0x18] = 0x14;
|
||
cpu.vic.R[0x19] = 0x0f;
|
||
|
||
for (unsigned i = 0; i < sizeof(cpu.vic.COLORRAM); i++)
|
||
cpu.vic.COLORRAM[i] = (rand() & 0x0F);
|
||
|
||
cpu.RAM[0x39FF] = 0x0;
|
||
cpu.RAM[0x3FFF] = 0x0;
|
||
cpu.RAM[0x39FF + 16384] = 0x0;
|
||
cpu.RAM[0x3FFF + 16384] = 0x0;
|
||
cpu.RAM[0x39FF + 32768] = 0x0;
|
||
cpu.RAM[0x3FFF + 32768] = 0x0;
|
||
cpu.RAM[0x39FF + 49152] = 0x0;
|
||
cpu.RAM[0x3FFF + 49152] = 0x0;
|
||
|
||
vic_adrchange();
|
||
}
|
||
|
||
|
||
/*
|
||
?PEEK(678) NTSC =0
|
||
?PEEK(678) PAL = 1
|
||
PRINT TIME$
|
||
*/
|
||
/*
|
||
Raster- Takt- sichtb. sichtbare
|
||
VIC-II System zeilen zyklen Zeilen Pixel/Zeile
|
||
-------------------------------------------------------
|
||
6569 PAL 312 63 284 403
|
||
6567R8 NTSC 263 65 235 418
|
||
6567R56A NTSC 262 64 234 411
|
||
*/
|