Respect the four most-obvious modes.

This commit is contained in:
Thomas Harte committed 2026-04-13 15:05:10 -04:00
1 parent ea36337549
commit 9408aff2e6
3 files changed
+235 -34

No files matched your search

+168 -28
View File
@@ -8,6 +8,7 @@
#include "Video.hpp"
#include <algorithm>
#include <bit>
using namespace Thomson::MO5;
@@ -28,6 +29,147 @@ constexpr uint16_t mo5_palette[] = {
rgb(0xaaa), rgb(0xfaa), rgb(0xafa), rgb(0xffa), rgb(0x5af), rgb(0xfaf), rgb(0xaff), rgb(0xfa5),
};
// MARK: - Pixel generators.
// TODO: all of the below ignore the bit ordering. Don't.
template <PixelMode, DotClock, BitOrdering>
struct PixelGenerator {
static void output(
[[maybe_unused]] uint8_t bank0,
[[maybe_unused]] uint8_t bank1,
[[maybe_unused]] uint16_t *target,
[[maybe_unused]] const std::array<uint16_t, 16> &palette
) {}
};
//
// 40-column mode.
//
template <DotClock clock, BitOrdering ordering>
struct PixelGenerator<PixelMode::Columns40, clock, ordering> {
static void output(
const uint8_t bank0,
const uint8_t bank1,
uint16_t *const target,
const std::array<uint16_t, 16> &palette
) {
const uint16_t colours[] = {
palette[bank1 & 0xf],
palette[bank1 >> 4],
};
target[0] = colours[(bank0 >> 7) & 1];
target[1] = colours[(bank0 >> 6) & 1];
target[2] = colours[(bank0 >> 5) & 1];
target[3] = colours[(bank0 >> 4) & 1];
if constexpr (pixels_per_column(clock) == 4) {
return;
}
target[4] = colours[(bank0 >> 3) & 1];
target[5] = colours[(bank0 >> 2) & 1];
target[6] = colours[(bank0 >> 1) & 1];
target[7] = colours[(bank0 >> 0) & 1];
if constexpr (pixels_per_column(clock) == 8) {
return;
}
std::fill_n(&target[8], 8, colours[0]);
}
};
//
// 80-column mode.
//
template <DotClock clock, BitOrdering ordering>
struct PixelGenerator<PixelMode::Columns80, clock, ordering> {
static void output(
const uint8_t bank0,
const uint8_t bank1,
uint16_t *const target,
const std::array<uint16_t, 16> &palette
) {
target[0] = palette[(bank0 >> 7) & 1];
target[1] = palette[(bank1 >> 7) & 1];
target[2] = palette[(bank0 >> 6) & 1];
target[3] = palette[(bank1 >> 6) & 1];
if constexpr (pixels_per_column(clock) == 4) {
return;
}
target[4] = palette[(bank0 >> 5) & 1];
target[5] = palette[(bank1 >> 5) & 1];
target[6] = palette[(bank0 >> 4) & 1];
target[7] = palette[(bank1 >> 4) & 1];
if constexpr (pixels_per_column(clock) == 8) {
return;
}
target[8] = palette[(bank0 >> 3) & 1];
target[9] = palette[(bank1 >> 3) & 1];
target[10] = palette[(bank0 >> 2) & 1];
target[11] = palette[(bank1 >> 2) & 1];
target[12] = palette[(bank0 >> 1) & 1];
target[13] = palette[(bank1 >> 1) & 1];
target[14] = palette[(bank0 >> 0) & 1];
target[15] = palette[(bank1 >> 0) & 1];
}
};
//
// 2bpp mode.
//
template <DotClock clock, BitOrdering ordering>
struct PixelGenerator<PixelMode::Bitmap4, clock, ordering> {
static void output(
const uint8_t bank0,
const uint8_t bank1,
uint16_t *const target,
const std::array<uint16_t, 16> &palette
) {
target[0] = palette[(bank0 >> 6) & 0x03];
target[1] = palette[(bank0 >> 4) & 0x03];
target[2] = palette[(bank0 >> 2) & 0x03];
target[3] = palette[(bank0 >> 0) & 0x03];
if constexpr (pixels_per_column(clock) == 4) {
return;
}
target[4] = palette[(bank1 >> 6) & 0x03];
target[5] = palette[(bank1 >> 4) & 0x03];
target[6] = palette[(bank1 >> 2) & 0x03];
target[7] = palette[(bank1 >> 0) & 0x03];
if constexpr (pixels_per_column(clock) == 8) {
return;
}
std::fill_n(&target[8], 8, palette[0]);
}
};
//
// 4bpp mode.
//
template <DotClock clock, BitOrdering ordering>
struct PixelGenerator<PixelMode::Bitmap16, clock, ordering> {
static void output(
const uint8_t bank0,
const uint8_t bank1,
uint16_t *const target,
const std::array<uint16_t, 16> &palette
) {
target[0] = palette[(bank0 >> 4) & 0x0f];
target[1] = palette[(bank0 >> 0) & 0x0f];
target[2] = palette[(bank1 >> 4) & 0x0f];
target[3] = palette[(bank1 >> 0) & 0x0f];
if constexpr (pixels_per_column(clock) > 4) {
std::fill_n(&target[4], pixels_per_column(clock) - 4, palette[0]);
}
}
};
}
Video::Video(const uint8_t *const pixels, const uint8_t *const attributes) :
@@ -141,44 +283,42 @@ void Video::pixel_line(const int line_begin, const int line_end) {
crt_.output_level(end - begin, mapped_border_);
});
Numeric::clamp<Line::EndOfLeftBorder, Line::EndOfPixels>(line_begin, line_end, [&](const int begin, const int end) {
// TODO: evaluate mode for:
//
// b6, b5: video data organisation
// b4, b3: data frequency
// b2, b1, b0: display mode
const auto flush_pixels = [&] {
crt_.output_data(Line::EndOfPixels - output_begin_column_, size_t(output_ - output_begin_));
output_ = output_begin_ = nullptr;
};
if(begin == Line::EndOfLeftBorder) {
output_ = reinterpret_cast<uint16_t *>(crt_.begin_data(Line::TotalPixels));
// Flush existing pixels if the pixel rate has changed.
static constexpr auto pixel_rate = pixels_per_column(mode);
if(output_ && pixel_rate != output_pixel_rate_) {
flush_pixels();
}
// Try to grab a buffer if without one.
if(!output_) {
output_ = output_begin_ = reinterpret_cast<uint16_t *>(crt_.begin_data(size_t(pixel_rate * 40)));
output_begin_column_ = begin;
output_pixel_rate_ = pixel_rate;
}
if(output_) {
for(int c = begin; c < end; c++) {
const uint8_t pixels = pixels_[source_address_];
const uint8_t attributes = attributes_[source_address_];
PixelGenerator<pixel_mode(mode), dot_clock(mode), bit_ordering(mode)>::output(
pixels_[source_address_],
attributes_[source_address_],
output_,
mapped_palette_
);
++source_address_;
output_ += pixel_rate;
}
const uint16_t colours[] = {
mapped_palette_[attributes & 0xf],
mapped_palette_[attributes >> 4],
};
output_[0] = colours[(pixels >> 7) & 1];
output_[1] = colours[(pixels >> 6) & 1];
output_[2] = colours[(pixels >> 5) & 1];
output_[3] = colours[(pixels >> 4) & 1];
output_[4] = colours[(pixels >> 3) & 1];
output_[5] = colours[(pixels >> 2) & 1];
output_[6] = colours[(pixels >> 1) & 1];
output_[7] = colours[(pixels >> 0) & 1];
output_ += 8;
if(end == Line::EndOfPixels) {
flush_pixels();
}
} else {
source_address_ += end - begin;
}
if(end == Line::EndOfPixels) {
crt_.output_data(Line::EndOfPixels - Line::EndOfLeftBorder, Line::TotalPixels);
output_ = nullptr;
crt_.output_data(end - begin, 1);
}
});
Numeric::clamp<Line::EndOfPixels, Line::TotalCycles>(line_begin, line_end, [&](const int begin, const int end) {
+60 -5
View File
@@ -14,8 +14,42 @@
#include "Numeric/SizedInt.hpp"
#include "Outputs/CRT/CRT.hpp"
#include <array>
namespace Thomson::MO5 {
enum class BitOrdering {
Legacy = 0b00,
Direct = 0b01,
Special = 0b10,
Transposed = 0b11,
};
enum class DotClock {
EightMhz1 = 0b00,
EightMhz2 = 0b10,
FourMhz = 0b11,
SixteenMhz = 0b01,
};
constexpr int pixels_per_column(const DotClock clock) {
switch(clock) {
default: __builtin_unreachable();
case DotClock::FourMhz: return 4;
case DotClock::EightMhz1:
case DotClock::EightMhz2: return 8;
case DotClock::SixteenMhz: return 16;
}
}
enum class PixelMode {
Columns40 = 0b000,
Bitmap4 = 0b001,
Columns80 = 0b010,
Bitmap16 = 0b011,
Page1 = 0b100,
Page2 = 0b101,
Overprint = 0b110,
TripleOverprint = 0b11,
};
struct Video {
public:
Video(const uint8_t *pixels, const uint8_t *attributes);
@@ -59,7 +93,7 @@ public:
void write(const uint8_t value) {
switch(address) {
case 0xa7e5:
// TODO: enable (0) or disable (1) video mode access at 0xa7dc.
enable_mode_access_ = !(value & 0x80);
break;
case 0xa7e7:
// TODO: b5 = 1 => 525-line output; 0 = 625-line output.
@@ -67,7 +101,11 @@ public:
case 0xa7da: set_palette(value); break;
case 0xa7db: set_palette_index(value); break;
case 0xa7dc: set_mode(value); break;
case 0xa7dc:
// if(enable_mode_access_) {
set_mode(value);
// }
break;
case 0xa7dd: set_border_colour(value); break;
default:
@@ -99,9 +137,11 @@ private:
Outputs::CRT::CRT crt_;
uint16_t source_address_ = 0;
uint16_t *output_ = nullptr;
uint16_t *output_ = nullptr, *output_begin_ = nullptr;
int output_begin_column_ = 0;
int output_pixel_rate_ = 0;
uint16_t mapped_palette_[16];
std::array<uint16_t, 16> mapped_palette_;
uint16_t mapped_border_ = 0;
void update_mapped_border() {
mapped_border_ = mapped_palette_[border_];
@@ -114,11 +154,26 @@ private:
void pixel_line(int, int);
// For gate array support: palette and mode.
uint8_t palette_[32];
std::array<uint8_t, 32> palette_;
uint8_t border_;
Numeric::SizedInt<5> palette_index_ = 0;
bool enable_mode_access_ = false;
uint8_t mode_ = 0b0'01'00'000; // Direct mode byte translation, 8 Mhz clock, 40-column output.
static constexpr BitOrdering bit_ordering(const uint8_t mode) {
return BitOrdering((mode >> 5) & 0b11);
}
static constexpr DotClock dot_clock(const uint8_t mode) {
return DotClock((mode >> 3) & 0b11);
}
static constexpr int pixels_per_column(const uint8_t mode) {
return Thomson::MO5::pixels_per_column(dot_clock(mode));
}
static constexpr PixelMode pixel_mode(const uint8_t mode) {
return PixelMode(mode & 0b111);
}
// Line layout: [sync][border][pixels][border].
struct Line {
static constexpr int EndOfSync = 4;
+7 -1
View File
@@ -137,9 +137,15 @@ void CRT::set_dynamic_framing(
void CRT::set_fixed_framing(const std::function<void()> &advance) {
framing_ = Framing::CalibratingAutomaticFixed;
while(framing_ == Framing::CalibratingAutomaticFixed) {
static constexpr int MaxSpins = 1000;
int spin = 0;
while(framing_ == Framing::CalibratingAutomaticFixed && ++spin < MaxSpins) {
advance();
}
if(spin == MaxSpins) {
Logger::info().append("CRT failed to calibrate after %d spins", spin);
}
}
void CRT::set_fixed_framing(const Display::Rect frame) {