Respect the four most-obvious modes.
This commit is contained in:
1 parent
ea36337549
commit
9408aff2e6
3 files changed
+235
-34
No files matched your search
+168
-28
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user