diff --git a/Machines/Thomson/MO5/Video.cpp b/Machines/Thomson/MO5/Video.cpp index 021ca206a..82729bfea 100644 --- a/Machines/Thomson/MO5/Video.cpp +++ b/Machines/Thomson/MO5/Video.cpp @@ -8,6 +8,7 @@ #include "Video.hpp" +#include #include 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 +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 &palette + ) {} +}; + +// +// 40-column mode. +// +template +struct PixelGenerator { + static void output( + const uint8_t bank0, + const uint8_t bank1, + uint16_t *const target, + const std::array &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 +struct PixelGenerator { + static void output( + const uint8_t bank0, + const uint8_t bank1, + uint16_t *const target, + const std::array &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 +struct PixelGenerator { + static void output( + const uint8_t bank0, + const uint8_t bank1, + uint16_t *const target, + const std::array &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 +struct PixelGenerator { + static void output( + const uint8_t bank0, + const uint8_t bank1, + uint16_t *const target, + const std::array &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_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(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(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::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_begin, line_end, [&](const int begin, const int end) { diff --git a/Machines/Thomson/MO5/Video.hpp b/Machines/Thomson/MO5/Video.hpp index ca60b4815..9c7bd3ef0 100644 --- a/Machines/Thomson/MO5/Video.hpp +++ b/Machines/Thomson/MO5/Video.hpp @@ -14,8 +14,42 @@ #include "Numeric/SizedInt.hpp" #include "Outputs/CRT/CRT.hpp" +#include + 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 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 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; diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 8336a7e55..3f78b0109 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -137,9 +137,15 @@ void CRT::set_dynamic_framing( void CRT::set_fixed_framing(const std::function &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) {