Files
CLK/Components/6847/6847.cpp
T
2026-05-12 15:09:23 -04:00

408 lines
14 KiB
C++

//
// 6847.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/05/2026.
// Copyright © 2026 Thomas Harte. All rights reserved.
//
#include "6847.hpp"
#include <cstdint>
namespace {
static constexpr uint8_t font[64][12] = {
{ 0x00, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x1a, 0x2a, 0x2a, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3c, 0x12, 0x12, 0x1c, 0x12, 0x12, 0x3c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x1c, 0x22, 0x20, 0x20, 0x20, 0x22, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3c, 0x12, 0x12, 0x12, 0x12, 0x12, 0x3c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x20, 0x20, 0x3e, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x20, 0x20, 0x20, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x1e, 0x20, 0x20, 0x26, 0x22, 0x22, 0x1e, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x22, 0x22, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x22, 0x24, 0x28, 0x30, 0x28, 0x24, 0x22, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3e, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x22, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x22, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x22, 0x22, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3e, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3e, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x20, 0x20, 0x20, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x2a, 0x24, 0x1a, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x28, 0x24, 0x22, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x1c, 0x22, 0x10, 0x08, 0x04, 0x22, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x36, 0x22, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3e, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3e, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x38, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x20, 0x20, 0x10, 0x08, 0x04, 0x02, 0x02, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x0e, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0e, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x08, 0x1c, 0x2a, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x3e, 0x10, 0x08, 0x00, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x14, 0x14, 0x36, 0x00, 0x36, 0x14, 0x14, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x08, 0x1e, 0x20, 0x1c, 0x02, 0x3c, 0x08, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x32, 0x32, 0x04, 0x08, 0x10, 0x26, 0x26, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x10, 0x28, 0x28, 0x10, 0x2a, 0x24, 0x1a, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x00, 0x08, 0x1c, 0x3e, 0x1c, 0x08, 0x00, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x10, 0x20, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x18, 0x24, 0x24, 0x24, 0x24, 0x24, 0x18, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x1c, 0x20, 0x20, 0x3e, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x0c, 0x02, 0x22, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x04, 0x0c, 0x14, 0x3e, 0x04, 0x04, 0x04, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3e, 0x20, 0x3c, 0x02, 0x02, 0x22, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x1c, 0x20, 0x20, 0x3c, 0x22, 0x22, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x3e, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x1c, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, },
{ 0x00, 0x00, 0x00, 0x18, 0x24, 0x04, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00, },
};
namespace Colours {
constexpr uint32_t rgb(const uint8_t r, const uint8_t g, const uint8_t b) {
uint8_t value[] = {
r, g, b, 0
};
return std::bit_cast<uint32_t>(value);
}
//
// RGB values below sourced from http://www.hcvgm.org/VDG_Colours.html
//
constexpr uint32_t text0[] = { rgb(0x28, 0xe0, 0x28), rgb(0x10, 0x60, 0x10) };
constexpr uint32_t text1[] = { rgb(0x80, 0x30, 0x10) , rgb(0xf0, 0xb0, 0x40) };
constexpr uint32_t resolution0[] = { rgb(0x28, 0xe0, 0x28), rgb(0x10, 0x60, 0x10) };
constexpr uint32_t resolution1[] = { rgb(0x00, 0x00, 0x00), rgb(0xf0, 0xf0, 0xf0) };
constexpr uint32_t colour0[] = {
rgb(0x28, 0xe0, 0x28),
rgb(0xf0, 0xf0, 0x70),
rgb(0x20, 0x20, 0xd8),
rgb(0xa8, 0x20, 0x20),
};
constexpr uint32_t colour1[] = {
rgb(0xf0, 0xf0, 0xf0),
rgb(0x28, 0xa8, 0xa8),
rgb(0xd3, 0x61, 0xfa),
rgb(0xf0, 0x88, 0x28),
};
constexpr uint32_t semigraphics[][2] = {
{ rgb(0x00, 0x00, 0x00), rgb(0xf0, 0x88, 0x28), },
{ rgb(0x00, 0x00, 0x00), rgb(0xd3, 0x61, 0xfa), },
{ rgb(0x00, 0x00, 0x00), rgb(0x28, 0xa8, 0xa8), },
{ rgb(0x00, 0x00, 0x00), rgb(0xf0, 0xf0, 0xf0), },
{ rgb(0x00, 0x00, 0x00), rgb(0xa8, 0x20, 0x20), },
{ rgb(0x00, 0x00, 0x00), rgb(0x20, 0x20, 0xd8), },
{ rgb(0x00, 0x00, 0x00), rgb(0xf0, 0xf0, 0x70), },
{ rgb(0x00, 0x00, 0x00), rgb(0x28, 0xe0, 0x28), },
};
}
// Empirical, by eye.
static constexpr uint8_t ColourPhase = 215;
}
using namespace Motorola::MC6847;
MC6847Base::MC6847Base(const Outputs::Display::Type display_type) :
crt_(
455,
1,
display_type,
Outputs::Display::InputDataType::Red8Green8Blue8
),
display_type_(display_type)
{
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
}
void MC6847Base::pixel_line(const int line_begin, const int line_end) {
if(line_begin < LineLayout::EndOfLeftBorder && line_end >= LineLayout::EndOfLeftBorder) [[unlikely]] {
crt_.output_sync(LineLayout::EndOfSync);
if(display_type_ == Outputs::Display::Type::NTSC60) {
crt_.output_colour_burst(LineLayout::EndOfColourBurst - LineLayout::EndOfSync, ColourPhase);
} else {
crt_.output_default_colour_burst(LineLayout::EndOfColourBurst - LineLayout::EndOfSync);
}
crt_.output_blank(LineLayout::EndOfLeftBorder - LineLayout::EndOfColourBurst);
pixels_ = reinterpret_cast<uint32_t *>(crt_.begin_data(256));
}
if(pixels_) [[likely]] {
Numeric::clamp<LineLayout::EndOfLeftBorder, LineLayout::EndOfPixels>(
line_begin,
line_end,
[&](const int begin, const int end) {
const int row = address_.row();
const int column_begin = (begin - LineLayout::EndOfLeftBorder) >> 3;
const int column_end = (end - LineLayout::EndOfLeftBorder) >> 3;
for(int c = column_begin; c < column_end; c++) {
const auto mode = line_.columns[c].mode;
const auto data = line_.columns[c & ~(mode & Mode::Columns16)].data;
if(mode & Mode::Graphics) {
switch(mode & (Mode::BPP2 | Mode::Columns16)) {
case 0: { // 1bpp, 32-column.
const uint32_t *const palette =
mode & Mode::ColourSelect ? Colours::resolution1 : Colours::resolution0;
pixels_[0] = palette[(data >> 7) & 1];
pixels_[1] = palette[(data >> 6) & 1];
pixels_[2] = palette[(data >> 5) & 1];
pixels_[3] = palette[(data >> 4) & 1];
pixels_[4] = palette[(data >> 3) & 1];
pixels_[5] = palette[(data >> 2) & 1];
pixels_[6] = palette[(data >> 1) & 1];
pixels_[7] = palette[(data >> 0) & 1];
} break;
case Mode::Columns16: { // 1bpp, 16-column.
const uint32_t *const palette =
mode & Mode::ColourSelect ? Colours::resolution1 : Colours::resolution0;
if(c & 1) {
pixels_[0] =
pixels_[1] = palette[(data >> 3) & 1];
pixels_[2] =
pixels_[3] = palette[(data >> 2) & 1];
pixels_[4] =
pixels_[5] = palette[(data >> 1) & 1];
pixels_[6] =
pixels_[7] = palette[(data >> 0) & 1];
} else {
pixels_[0] =
pixels_[1] = palette[(data >> 7) & 1];
pixels_[2] =
pixels_[3] = palette[(data >> 6) & 1];
pixels_[4] =
pixels_[5] = palette[(data >> 5) & 1];
pixels_[6] =
pixels_[7] = palette[(data >> 4) & 1];
}
} break;
case Mode::BPP2: { // 2bpp, 32-column.
const uint32_t *const palette =
mode & Mode::ColourSelect ? Colours::colour0 : Colours::colour1;
pixels_[0] =
pixels_[1] = palette[(data >> 6) & 3];
pixels_[2] =
pixels_[3] = palette[(data >> 4) & 3];
pixels_[4] =
pixels_[5] = palette[(data >> 2) & 3];
pixels_[6] =
pixels_[7] = palette[(data >> 0) & 3];
} break;
case Mode::BPP2 | Mode::Columns16: { // 2bpp, 16-column.
const uint32_t *const palette =
mode & Mode::ColourSelect ? Colours::colour0 : Colours::colour1;
if(c & 1) {
pixels_[0] =
pixels_[1] =
pixels_[2] =
pixels_[3] = palette[(data >> 2) & 3];
pixels_[4] =
pixels_[5] =
pixels_[6] =
pixels_[7] = palette[(data >> 0) & 3];
} else {
pixels_[0] =
pixels_[1] =
pixels_[2] =
pixels_[3] = palette[(data >> 6) & 3];
pixels_[4] =
pixels_[5] =
pixels_[6] =
pixels_[7] = palette[(data >> 4) & 3];
}
} break;
default: __builtin_unreachable();
}
} else {
if(mode & Mode::Semigraphics) {
const uint32_t *palette;
uint8_t pixels;
if(mode & Mode::ExternalROM) {
palette = Colours::semigraphics[data >> 6];
pixels = [&]() -> uint8_t {
if(row < 4) return data >> 4;
if(row < 8) return data >> 2;
return data;
} ();
} else {
palette = Colours::semigraphics[(data >> 4) & 7];
pixels = row < 6 ? data >> 2 : data;
}
pixels_[0] =
pixels_[1] =
pixels_[2] =
pixels_[3] = palette[(pixels >> 1) & 1];
pixels_[4] =
pixels_[5] =
pixels_[6] =
pixels_[7] = palette[(pixels >> 0) & 1];
} else {
const uint32_t *const palette = mode & Mode::ColourSelect ? Colours::text1 : Colours::text0;
uint8_t pixels = (mode & Mode::Invert ? 0xff : 0x00) ^ (data & 64 ? 0xff : 0x00);
if(mode & Mode::ExternalROM) {
// TODO: external ROM.
} else {
pixels ^= font[data & 63][row];
}
pixels_[0] = palette[(pixels >> 7) & 1];
pixels_[1] = palette[(pixels >> 6) & 1];
pixels_[2] = palette[(pixels >> 5) & 1];
pixels_[3] = palette[(pixels >> 4) & 1];
pixels_[4] = palette[(pixels >> 3) & 1];
pixels_[5] = palette[(pixels >> 2) & 1];
pixels_[6] = palette[(pixels >> 1) & 1];
pixels_[7] = palette[(pixels >> 0) & 1];
}
}
pixels_ += 8;
}
}
);
}
if(line_end == LineLayout::EndOfLine) [[unlikely]] {
crt_.output_data(256);
address_.apply_hsync();
crt_.output_blank(LineLayout::EndOfLine - LineLayout::EndOfPixels);
}
}
void MC6847Base::border_line(const int, const int end) {
if(end == LineLayout::EndOfLine) {
crt_.output_sync(LineLayout::EndOfSync);
crt_.output_blank(LineLayout::EndOfLine - LineLayout::EndOfSync);
}
}
void MC6847Base::porch_line(const int begin, const int end) {
border_line(begin, end);
}
void MC6847Base::sync_line(const int, const int end) {
if(end == LineLayout::EndOfLine) {
crt_.output_sync(LineLayout::EndOfLine);
}
}
bool MC6847Base::hsync(const int column) const {
return column <= LineLayout::EndOfSync;
}
Cycles MC6847Base::next_sequence_point(const int column) const {
if(column < LineLayout::EndOfSync) return LineLayout::EndOfSync - column;
return LineLayout::EndOfLine - column;
}
void MC6847Base::reset() {
address_.apply_vertical_preload();
}
void MC6847Base::Address::apply_vertical_preload() {
row_ = 0;
address_ = 0;
}
void MC6847Base::Address::advance(const int column) {
address_ += (column & 1) | increment_mask_;
}
void MC6847Base::Address::apply_hsync() {
++row_;
if(row_ == target_row_) {
row_ = 0;
} else {
--address_;
}
address_ &= line_mask_;
}
void MC6847Base::set_mode(
const bool graphics,
const bool semigraphics,
const bool external_rom,
const bool invert,
const uint8_t graphics_mode,
const bool colour_select
) {
mode_ =
(graphics ? Mode::Graphics : 0) |
(semigraphics ? Mode::Semigraphics : 0) |
(external_rom ? Mode::ExternalROM : 0) |
(invert ? Mode::Invert : 0) |
(colour_select ? Mode::ColourSelect : 0);;
const auto set_32bytes = [&] {
address_.increment_mask_ = 1;
address_.line_mask_ = uint16_t(~31);
};
const auto set_16bytes = [&] {
address_.increment_mask_ = 0;
address_.line_mask_ = uint16_t(~15);
mode_ |= Mode::Columns16;
};
if(graphics) {
const auto mode = Mode::GraphicsMode(graphics_mode);
if(Mode::is_16column(mode)) {
set_16bytes();
} else {
set_32bytes();
}
address_.target_row_ = repeated_rows(mode);
if(is_2bpp(mode)) {
mode_ |= Mode::BPP2;
}
} else {
set_32bytes();
address_.target_row_ = 12;
}
// TODO: apply my net understanding, which is now this:
//
// Alphanumeric modes works pretty much as already implemented with an access to the character ROM,
// though it obeys the top two bits;
//
// Semigraphic modes fetch much like the alphanumeric modes but use the fetched
// byte as colour + telectext-style pixel data;
//
// The graphics modes break down into either 1bpp or 2bpp output, at either 16 or 32 bytes per row.
}