work in progress: video tweaks (and reformat source code)

This commit is contained in:
Christopher A. Mosher committed 2020-09-14 16:50:42 -04:00
1 parent 55a43d19f0
commit 836df9df68
8 files changed
+305 -313

No files matched your search

+14 -3
View File
@@ -455,9 +455,20 @@ void AnalogTV::ntsc_to_yiq(const int isignal, const int siglen, const IQ& iq_fac
int inline AnalogTV::yiq2rgb(const int yiq)
{
double r = (((yiq)&0xFF)-IQINTOFF) + 0.956 * (((yiq>>8)&0xFF)-IQINTOFF) + 0.621 * (((yiq>>16)&0xFF)-IQINTOFF);
double g = (((yiq)&0xFF)-IQINTOFF) - 0.272 * (((yiq>>8)&0xFF)-IQINTOFF) - 0.647 * (((yiq>>16)&0xFF)-IQINTOFF);
double b = (((yiq)&0xFF)-IQINTOFF) - 1.105 * (((yiq>>8)&0xFF)-IQINTOFF) + 1.702 * (((yiq>>16)&0xFF)-IQINTOFF);
/*
* https://en.wikipedia.org/wiki/YIQ (2019-09-19):
* To convert from FCC YIQ to RGB:
* E_R = E_Y + 0.9469*E_I + 0.6236*E_Q
* E_G = E_Y - 0.2748*E_I - 0.6357*E_Q
* E_B = E_Y - 1.1 *E_I + 1.7 *E_Q
*/
const double y = (yiq&0xFF)-IQINTOFF;
const double i = ((yiq>>8)&0xFF)-IQINTOFF;
const double q = ((yiq>>16)&0xFF)-IQINTOFF;
double r = y + 0.9469*i + 0.6236*q;
double g = y - 0.2748*i - 0.6357*q;
double b = y - 1.1000*i + 1.7000*q;
const int rgb =
(calc_color(r) << 16)|
+177 -236
View File
@@ -22,265 +22,215 @@
#include "e2const.h"
PictureGenerator::PictureGenerator(AnalogTV& display, VideoMode& mode, const int& revision):
display(display), mode(mode), itestsig(testsig), itestsiglim(testsig+AppleNTSC::SIGNAL_LEN),
VISIBLE_X_OFFSET(E2Const::BYTES_PER_ROW-E2Const::VISIBLE_BYTES_PER_ROW),
revision(revision)
{
display(display), mode(mode), itestsig(testsig), itestsiglim(testsig+AppleNTSC::SIGNAL_LEN),
VISIBLE_X_OFFSET(E2Const::BYTES_PER_ROW-E2Const::VISIBLE_BYTES_PER_ROW),
revision(revision) {
}
PictureGenerator::~PictureGenerator()
{
PictureGenerator::~PictureGenerator() {
}
void PictureGenerator::powerOn()
{
this->hpos = 0;
this->line = 0;
this->display.signal = this->testsig;
this->itestsig = this->testsig;
void PictureGenerator::powerOn() {
this->hpos = 0;
this->line = 0;
this->display.signal = this->testsig;
this->itestsig = this->testsig;
}
void inline PictureGenerator::shiftLoRes()
{
/*
* For byte ABCDEFGH in register, perform
* the following 4-bit end-around shifts:
*
* +---<----+ +---<----+
* | | | |
* +->ABCD->+ +->EFGH->+
*
* Therefore:
*
* ABCDEFGH --> DABCHEFG
*/
void inline PictureGenerator::shiftLoRes() {
/*
* For byte ABCDEFGH in register, perform
* the following 4-bit end-around shifts:
*
* +---<----+ +---<----+
* | | | |
* +->ABCD->+ +->EFGH->+
*
* Therefore:
*
* ABCDEFGH --> DABCHEFG
*/
unsigned char rot_bits = this->latchGraphics & 0x11;
// 000D000H
rot_bits <<= 3;
// D000H000
unsigned char rot_bits = this->latchGraphics & 0x11;
// 000D000H
rot_bits <<= 3;
// D000H000
this->latchGraphics &= 0xEE;
// ABC0EFG0
this->latchGraphics >>= 1;
// 0ABC0EFG
this->latchGraphics |= rot_bits;
// DABCHEFG
this->latchGraphics &= 0xEE;
// ABC0EFG0
this->latchGraphics >>= 1;
// 0ABC0EFG
this->latchGraphics |= rot_bits;
// DABCHEFG
}
void inline PictureGenerator::shiftHiRes()
{
/*
* For byte ABCDEFGH in register, perform
* the following shift:
*
* +---<----+
* | |
* +->ABCD->+--->EFGH->
*
* Therefore:
*
* ABCDEFGH --> DABCDEFG
*/
void inline PictureGenerator::shiftHiRes() {
/*
* For byte ABCDEFGH in register, perform
* the following shift:
*
* +---<----+
* | |
* +->ABCD->+--->EFGH->
*
* Therefore:
*
* ABCDEFGH --> DABCDEFG
*/
unsigned char rot_bits = this->latchGraphics & 0x10;
// 000D0000
rot_bits <<= 3;
// D0000000
unsigned char rot_bits = this->latchGraphics & 0x10;
// 000D0000
rot_bits <<= 3;
// D0000000
this->latchGraphics >>= 1;
// 0ABCDEFG
this->latchGraphics |= rot_bits;
// DABCDEFG
this->latchGraphics >>= 1;
// 0ABCDEFG
this->latchGraphics |= rot_bits;
// DABCDEFG
}
void inline PictureGenerator::shiftText()
{
this->latchText >>= 1;
void inline PictureGenerator::shiftText() {
this->latchText >>= 1;
}
bool inline PictureGenerator::getTextBit()
{
return this->latchText & 1;
bool inline PictureGenerator::getTextBit() {
return this->latchText & 1;
}
bool inline PictureGenerator::getHiResBit()
{
return this->latchGraphics & 1;
bool inline PictureGenerator::getHiResBit() {
return this->latchGraphics & 1;
}
bool inline PictureGenerator::getLoResBit(const bool odd, const bool vc)
{
const int nibble = (this->latchGraphics >> (vc ? 4 : 0)) & 0x0F;
return (nibble >> (odd ? 2 : 0)) & 1;
bool inline PictureGenerator::getLoResBit(const bool odd, const bool vc) {
const int nibble = (this->latchGraphics >> (vc ? 4 : 0)) & 0x0F;
return (nibble >> (odd ? 2 : 0)) & 1;
}
void inline PictureGenerator::loadGraphics(const unsigned char value)
{
this->latchGraphics = value;
this->d7 = this->latchGraphics & 0x80;
void inline PictureGenerator::loadGraphics(const unsigned char value) {
this->latchGraphics = value;
this->d7 = this->latchGraphics & 0x80;
}
void inline PictureGenerator::loadText(const int value)
{
this->latchText = value;
void inline PictureGenerator::loadText(const unsigned char value) {
this->latchText = value;
}
// TODO can we hand-optimize the main picture generator algorithm any more?
// Note that the innermost loop (which calls writeVideoSignal) has to execute
// at 14MHz, in order to maintain authentic Apple ][ speed.
void PictureGenerator::tick(const int t, const unsigned char rowToPlot)
{
const bool isText(this->mode.isDisplayingText(t));
const bool isHiRes(this->mode.isHiRes());
void PictureGenerator::tick(const int t, const unsigned char c) {
this->mode.tick();
signed char* is = this->itestsig;
const bool isText(this->mode.isDisplayingText(t));
const bool isHiRes(this->mode.isHiRes());
if (isText)
loadText(rowToPlot);
else
loadGraphics(rowToPlot);
signed char* is = this->itestsig;
if (t==0)
{
this->line = 0;
}
if (isText) {
loadText(c);
} else {
loadGraphics(c);
}
int cycles = E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE;
if (this->hpos == E2Const::HORIZ_CYCLES-1)
{
cycles += E2Const::EXTRA_CRYSTAL_CYCLES_PER_CPU_LONG_CYCLE;
}
if (t==0) {
this->line = 0;
}
// hi-res half-pixel shift:
const bool shift = !isText && isHiRes && this->d7 && this->line < E2Const::VISIBLE_ROWS_PER_FIELD && !(this->hpos < VISIBLE_X_OFFSET) && this->revision > 0;
const bool showLastHiRes = shift && this->lasthires;
int cycles = E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE;
if (this->hpos == E2Const::HORIZ_CYCLES-1) {
cycles += E2Const::EXTRA_CRYSTAL_CYCLES_PER_CPU_LONG_CYCLE;
}
int xtra(0);
if (shift)
{
--cycles;
++xtra;
}
const int firstBlankedCycle(E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE-xtra);
// hi-res half-pixel shift:
const bool shift = !isText && isHiRes && this->d7 && this->line < E2Const::VISIBLE_ROWS_PER_FIELD && !(this->hpos < VISIBLE_X_OFFSET) && this->revision > 0;
const bool showLastHiRes = shift && this->lasthires;
int hcycle(this->hpos*E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE);
const bool lineVis(this->line < E2Const::VISIBLE_ROWS_PER_FIELD);
const bool hVis(this->hpos >= VISIBLE_X_OFFSET);
for (int cycle(0); cycle < cycles-1; ++cycle)
{
const bool bit = shiftLatch(t,cycle,isText,isHiRes);
is = writeVideoSignal(shift,showLastHiRes,firstBlankedCycle,cycle,hcycle,bit,lineVis,hVis,is);
++hcycle;
}
// optimization: pull the last iteration of the loop out, so we don't getHiResBit every time
{
this->lasthires = getHiResBit(); // save it for the next plotted byte, just in case we need it
const int cycle = cycles-1;
const bool bit = shiftLatch(t,cycle,isText,isHiRes);
is = writeVideoSignal(shift,showLastHiRes,firstBlankedCycle,cycle,hcycle,bit,lineVis,hVis,is);
}
int xtra(0);
if (shift) {
--cycles;
++xtra;
}
const int firstBlankedCycle(E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE-xtra);
this->itestsig = is;
unsigned int hcycle(this->hpos*E2Const::CRYSTAL_CYCLES_PER_CPU_CYCLE);
const bool lineVis(this->line < E2Const::VISIBLE_ROWS_PER_FIELD);
const bool hVis(this->hpos >= VISIBLE_X_OFFSET);
for (int cycle(0); cycle < cycles; ++cycle) {
this->lasthires = getHiResBit(); // save it for the next plotted byte, just in case we need it
const bool bit = shiftLatch(t,cycle,isText,isHiRes);
is = writeVideoSignal(shift,showLastHiRes,firstBlankedCycle,cycle,hcycle,bit,lineVis,hVis,is);
++hcycle;
}
++this->hpos;
if (this->hpos >= E2Const::HORIZ_CYCLES)
{
this->hpos = 0;
++this->line;
if (this->itestsig >= this->itestsiglim)
{
this->itestsig = this->testsig;
this->display.drawCurrent();
}
}
this->itestsig = is;
++this->hpos;
if (this->hpos >= E2Const::HORIZ_CYCLES) {
this->hpos = 0;
++this->line;
if (this->itestsig >= this->itestsiglim) {
this->itestsig = this->testsig;
this->display.drawCurrent();
}
}
}
bool inline PictureGenerator::shiftLatch(const int t, const int cycle, const bool isText, const bool isHiRes)
{
bool bit;
if (isText)
{
bit = getTextBit();
if (cycle & 1) // @ 7MHz
{
shiftText();
}
}
else if (isHiRes)
{
bit = getHiResBit();
if (cycle & 1) // @ 7MHz
{
shiftHiRes();
}
}
else // LO-RES
{
const int y = t / E2Const::BYTES_PER_ROW;
bit = getLoResBit((t & 1) == (this->line & 1), y & 4);
shiftLoRes();
}
return bit;
bool inline PictureGenerator::shiftLatch(const int t, const int cycle, const bool isText, const bool isHiRes) {
bool bit;
if (isText) {
bit = getTextBit();
if (cycle & 1) { // @ 7MHz
shiftText();
}
} else if (isHiRes) {
bit = getHiResBit();
if (cycle & 1) { // @ 7MHz
shiftHiRes();
}
} else { // LO-RES
const int y = t / E2Const::BYTES_PER_ROW;
bit = getLoResBit((t & 1) == (this->line & 1), y & 4);
shiftLoRes();
}
return bit;
}
inline signed char* PictureGenerator::writeVideoSignal(const bool shift, const bool showLastHiRes, const int firstBlankedCycle, const int cycle, const int hcycle, const bool bit, const bool lineVis, const bool hVis, signed char* is)
{
if (shift && !cycle)
{
*is++ = showLastHiRes ? AppleNTSC::WHITE_LEVEL : AppleNTSC::BLANK_LEVEL;
}
inline signed char* PictureGenerator::writeVideoSignal(const bool shift, const bool showLastHiRes, const int firstBlankedCycle, const int cycle, const unsigned int hcycle, const bool bit, const bool lineVis, const bool hVis, signed char* is) {
if (shift && !cycle) {
*is++ = showLastHiRes ? AppleNTSC::WHITE_LEVEL : AppleNTSC::BLANK_LEVEL;
}
signed char sig;
if (lineVis)
{
if (hVis)
{
if (bit && cycle < firstBlankedCycle)
{
sig = AppleNTSC::WHITE_LEVEL;
}
else
{
sig = AppleNTSC::BLANK_LEVEL;
}
}
else
{
sig = hbl(hcycle);
}
}
else
{
sig = vbl(hcycle);
}
*is++ = sig;
return is;
signed char sig;
if (lineVis) {
if (hVis) {
if (bit && cycle < firstBlankedCycle) {
sig = AppleNTSC::WHITE_LEVEL;
} else {
sig = AppleNTSC::BLANK_LEVEL;
}
} else {
sig = hbl(hcycle);
}
} else {
sig = vbl(hcycle);
}
*is++ = sig;
return is;
}
// TODO Just to be extremely accurate, fix picture signal values during HBL and VBL
// (note that they vary by motherboard revision... there is a whole section in U.A.2)
signed char inline PictureGenerator::vbl(const int hcycle)
{
signed char sig;
if (224 <= this->line && this->line < 240) // VSYNC // TODO symbolize constants
{
sig = AppleNTSC::SYNC_LEVEL;
}
else
{
if (AppleNTSC::SYNC_START <= hcycle && hcycle < AppleNTSC::BP_START)
{
sig = AppleNTSC::SYNC_LEVEL;
}
else
{
sig = AppleNTSC::BLANK_LEVEL;
}
}
return sig;
signed char inline PictureGenerator::vbl(const unsigned int hcycle) {
signed char sig;
if (224 <= this->line && this->line < 240) { // VSYNC // TODO symbolize constants
sig = AppleNTSC::SYNC_LEVEL;
} else {
if (AppleNTSC::SYNC_START <= hcycle && hcycle < AppleNTSC::BP_START) {
sig = AppleNTSC::SYNC_LEVEL;
} else {
sig = AppleNTSC::BLANK_LEVEL;
}
}
return sig;
}
@@ -297,27 +247,18 @@ const signed char PictureGenerator::lutCB[] =
{ 0, -AppleNTSC::CB_LEVEL, 0, +AppleNTSC::CB_LEVEL };
//{ +AppleNTSC::CB_LEVEL/2, -AppleNTSC::CB_LEVEL/2, -AppleNTSC::CB_LEVEL/2, +AppleNTSC::CB_LEVEL/2 };
signed char inline PictureGenerator::hbl(const int hcycle)
{
signed char cb;
if (AppleNTSC::CB_START <= hcycle && hcycle < AppleNTSC::CB_END)
{
if (this->mode.isText() && this->revision > 0)
{
cb = AppleNTSC::BLANK_LEVEL;
}
else
{
cb = lutCB[(hcycle-AppleNTSC::CB_START)%4];
}
}
else if (AppleNTSC::SYNC_START <= hcycle && hcycle < AppleNTSC::BP_START)
{
cb = AppleNTSC::SYNC_LEVEL;
}
else
{
cb = AppleNTSC::BLANK_LEVEL;
}
return cb;
signed char inline PictureGenerator::hbl(const unsigned int hcycle) {
signed char cb;
if (AppleNTSC::CB_START <= hcycle && hcycle < AppleNTSC::CB_END) {
if (this->mode.isText() && this->revision > 0) {
cb = AppleNTSC::BLANK_LEVEL;
} else {
cb = lutCB[(hcycle-AppleNTSC::CB_START)%4];
}
} else if (AppleNTSC::SYNC_START <= hcycle && hcycle < AppleNTSC::BP_START) {
cb = AppleNTSC::SYNC_LEVEL;
} else {
cb = AppleNTSC::BLANK_LEVEL;
}
return cb;
}
+4 -4
View File
@@ -46,11 +46,11 @@ private:
bool getHiResBit();
bool getLoResBit(const bool odd, const bool vc);
void loadGraphics(const unsigned char value);
void loadText(const int value);
void loadText(const unsigned char value);
bool shiftLatch(const int t, const int cycle, const bool isText, const bool isHiRes);
signed char* writeVideoSignal(const bool shift, const bool showLastHiRes, const int firstBlankedCycle, const int cycle, const int hcycle, const bool bit, const bool lineVis, const bool hVis, signed char* is);
signed char vbl(const int hcycle);
signed char hbl(const int hcycle);
signed char* writeVideoSignal(const bool shift, const bool showLastHiRes, const int firstBlankedCycle, const int cycle, const unsigned int hcycle, const bool bit, const bool lineVis, const bool hVis, signed char* is);
signed char vbl(const unsigned int hcycle);
signed char hbl(const unsigned int hcycle);
const unsigned int VISIBLE_X_OFFSET;
+12 -22
View File
@@ -18,35 +18,25 @@
#include "powerupreset.h"
#include "apple2.h"
#include "e2const.h"
#include <cmath>
PowerUpReset::PowerUpReset(Apple2& apple):
apple(apple)
{
apple(apple) {
}
PowerUpReset::~PowerUpReset()
{
PowerUpReset::~PowerUpReset() {
}
void PowerUpReset::tick()
{
if (this->pendingTicks > 0)
{
--this->pendingTicks;
if (this->pendingTicks == 0)
{
this->apple.reset();
}
}
void PowerUpReset::tick() {
if (this->pendingTicks > 0) {
--this->pendingTicks;
if (this->pendingTicks == 0) {
this->apple.reset();
}
}
}
void PowerUpReset::powerOn()
{
#ifdef USE_EMU
this->pendingTicks = 99; // TODO REMOVE THIS
#else
this->pendingTicks = (int)(E2Const::AVG_CPU_HZ*.3); // U.A.II, p. 7-15
#endif
void PowerUpReset::powerOn() {
this->pendingTicks = std::lround(E2Const::AVG_CPU_HZ*.3); // U.A.II, p. 7-15
}
+7 -8
View File
@@ -20,17 +20,16 @@
class Apple2;
class PowerUpReset
{
class PowerUpReset {
private:
Apple2& apple;
int pendingTicks;
Apple2& apple;
long pendingTicks;
public:
PowerUpReset(Apple2& apple);
~PowerUpReset();
void powerOn();
void tick();
PowerUpReset(Apple2& apple);
~PowerUpReset();
void powerOn();
void tick();
};
#endif
+2 -2
View File
@@ -39,8 +39,8 @@ private:
std::vector<unsigned short> lutTEXT[2]; // [0] is page 1, [1] is page 2
std::vector<unsigned short> lutHRES[2]; // [0] is page 1, [1] is page 2
VideoMode& mode;
AddressBus& addressBus;
VideoMode& mode;
AddressBus& addressBus;
PictureGenerator& picgen;
TextCharacters& textRows;
+66 -24
View File
@@ -17,32 +17,74 @@
*/
#include "videomode.h"
VideoMode::VideoMode()
{
VideoMode::VideoMode():
pendText(0),
pendMixed(0),
pendHiRes(0) {
}
unsigned char VideoMode::io(const unsigned short addr, const unsigned char b)
{
const int sw = (addr & 0xE) >> 1;
const bool on = addr & 0x1;
switch (sw)
{
case 0:
this->swText = on; break;
case 1:
this->swMixed = on; break;
case 2:
this->swPage2 = on ? 1 : 0; break;
case 3:
this->swHiRes = on; break;
}
return b;
unsigned char VideoMode::io(const unsigned short addr, const unsigned char b) {
const int sw = (addr & 0x6) >> 1;
const bool on = addr & 0x1;
// TODO verify all soft-switch change timings
switch (sw) {
case 0:
if (this->swText != on) {
this->pendText = 1;
}
break;
case 1:
if (this->swMixed != on) {
this->pendMixed = 1;
}
break;
case 2:
this->pendPage2 = on;
// if (this->swPage2 != on) {
// this->pendPage2 = 1;
// }
break;
case 3:
this->swHiRes = on;
// if (this->swHiRes != on) {
// if (on) {
// this->pendHiRes = 1;
// } else {
// this->pendHiRes = 2;
// }
// }
break;
}
return b;
}
void VideoMode::powerOn()
{
this->swText = false;
this->swMixed = false;
this->swPage2 = 0;
this->swHiRes = false;
void VideoMode::tick() {
if (this->pendText) {
if (--this->pendText <= 0) {
this->swText = !this->swText;
}
}
if (this->pendMixed) {
if (--this->pendMixed <= 0) {
this->swMixed = !this->swMixed;
}
}
if (this->pendPage2) {
if (--this->pendPage2 <= 0) {
this->swPage2 = !this->swPage2;
}
}
if (this->pendHiRes) {
if (--this->pendHiRes <= 0) {
this->swHiRes = !this->swHiRes;
}
}
}
void VideoMode::powerOn() {
this->swText = false;
this->swMixed = false;
this->swPage2 = 0;
this->swHiRes = false;
}
+23 -14
View File
@@ -21,24 +21,33 @@
#include "videoaddressing.h"
#include "e2const.h"
class VideoMode
{
class VideoMode {
private:
bool swText;
bool swMixed;
int swPage2;
bool swHiRes;
bool swText;
int pendText;
bool swMixed;
int pendMixed;
bool swPage2;
int pendPage2;
bool swHiRes;
int pendHiRes;
public:
VideoMode();
unsigned char io(const unsigned short addr, const unsigned char b);
void powerOn();
VideoMode();
unsigned char io(const unsigned short addr, const unsigned char b);
void powerOn();
bool isText() const { return this->swText; }
bool isHiRes() const { return this->swHiRes; }
bool isMixed() const { return this->swMixed; }
int getPage() const { return this->swPage2; }
bool isDisplayingText(const int atTickInField) const { return this->swText || (this->swMixed && atTickInField >= E2Const::MIXED_TEXT_CYCLE); }
void tick();
bool isText() const { return this->swText; }
bool isHiRes() const { return this->swHiRes; }
bool isMixed() const { return this->swMixed; }
int getPage() const { return this->swPage2 ? 1 : 0; }
bool isDisplayingText(const int atTickInField) const { return this->swText || (this->swMixed && atTickInField >= E2Const::MIXED_TEXT_CYCLE); }
};
#endif