286 lines
11 KiB
C++
286 lines
11 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, },
|
|
};
|
|
|
|
// 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::Red4Green4Blue4 // TODO.
|
|
) {
|
|
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);
|
|
crt_.output_colour_burst(LineLayout::EndOfColourBurst - LineLayout::EndOfSync, ColourPhase);
|
|
crt_.output_blank(LineLayout::EndOfLeftBorder - LineLayout::EndOfColourBurst);
|
|
|
|
pixels_ = reinterpret_cast<uint16_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;
|
|
|
|
// TODO: properly obey graphics mode here, use colours.
|
|
if(mode_ <= GraphicsMode::ResolutionGraphics6) {
|
|
for(int c = column_begin; c < column_end; c++) {
|
|
const int pixels = line_.data[c];
|
|
pixels_[0] = (pixels & 0x80) ? 0xffff : 0x0000;
|
|
pixels_[1] = (pixels & 0x40) ? 0xffff : 0x0000;
|
|
pixels_[2] = (pixels & 0x20) ? 0xffff : 0x0000;
|
|
pixels_[3] = (pixels & 0x10) ? 0xffff : 0x0000;
|
|
pixels_[4] = (pixels & 0x08) ? 0xffff : 0x0000;
|
|
pixels_[5] = (pixels & 0x04) ? 0xffff : 0x0000;
|
|
pixels_[6] = (pixels & 0x02) ? 0xffff : 0x0000;
|
|
pixels_[7] = (pixels & 0x01) ? 0xffff : 0x0000;
|
|
pixels_ += 8;
|
|
}
|
|
} else {
|
|
for(int c = column_begin; c < column_end; c++) {
|
|
const int pixels = font[line_.data[c] & 63][row];
|
|
pixels_[0] = (pixels & 0x80) ? 0xffff : 0x0000;
|
|
pixels_[1] = (pixels & 0x40) ? 0xffff : 0x0000;
|
|
pixels_[2] = (pixels & 0x20) ? 0xffff : 0x0000;
|
|
pixels_[3] = (pixels & 0x10) ? 0xffff : 0x0000;
|
|
pixels_[4] = (pixels & 0x08) ? 0xffff : 0x0000;
|
|
pixels_[5] = (pixels & 0x04) ? 0xffff : 0x0000;
|
|
pixels_[6] = (pixels & 0x02) ? 0xffff : 0x0000;
|
|
pixels_[7] = (pixels & 0x01) ? 0xffff : 0x0000;
|
|
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,
|
|
[[maybe_unused]] const bool colour_select
|
|
) {
|
|
mode_ = [&]() {
|
|
if(graphics) {
|
|
return GraphicsMode(graphics_mode);
|
|
}
|
|
|
|
if(semigraphics) {
|
|
return external_rom ? GraphicsMode::Semigraphics6 : GraphicsMode::Semigraphics4;
|
|
}
|
|
|
|
return external_rom ? GraphicsMode::ExternalAlphanumerics : GraphicsMode::InternalAlphanumerics;
|
|
} ();
|
|
alphanumerics_inversion_ = invert ? 0xff : 0x00;
|
|
|
|
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);
|
|
};
|
|
|
|
switch(mode_) {
|
|
case InternalAlphanumerics:
|
|
case ExternalAlphanumerics:
|
|
case Semigraphics4:
|
|
case Semigraphics6:
|
|
set_32bytes();
|
|
address_.target_row_ = 12;
|
|
break;
|
|
|
|
case ResolutionGraphics1: // 128x64;
|
|
case ColourGraphics1: // 64x64; 1kb required.
|
|
set_16bytes();
|
|
address_.target_row_ = 3;
|
|
break;
|
|
|
|
case ResolutionGraphics2: // 128x96; 2kb required.
|
|
set_16bytes();
|
|
address_.target_row_ = 2;
|
|
break;
|
|
|
|
case ColourGraphics2: // 128x64: 2kb required.
|
|
set_32bytes();
|
|
address_.target_row_ = 3;
|
|
break;
|
|
|
|
case ColourGraphics3: // 128x96: 3kb required.
|
|
set_32bytes();
|
|
address_.target_row_ = 2;
|
|
break;
|
|
|
|
case ColourGraphics6: // 128x192: 6kb required.
|
|
case ResolutionGraphics6: // 256x192, 6kb required.
|
|
set_32bytes();
|
|
address_.target_row_ = 1;
|
|
break;
|
|
|
|
case ResolutionGraphics3: // 128x192; 3 kb required.
|
|
set_16bytes();
|
|
address_.target_row_ = 2;
|
|
break;
|
|
|
|
default: __builtin_unreachable();
|
|
}
|
|
|
|
// 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.
|
|
}
|