1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-01 22:41:32 +00:00

Reinstate all missing video modes.

This commit is contained in:
Thomas Harte 2024-04-28 21:49:04 -04:00
parent 4f1aef90b8
commit d480f9eae2

View File

@ -178,18 +178,16 @@ struct Video {
if(address_ == buffer_end_ + 16) { if(address_ == buffer_end_ + 16) {
address_ = buffer_start_; address_ = buffer_start_;
} }
bitmap_queue_[bitmap_queue_write_pointer_ & 7] = next; bitmap_queue_[bitmap_queue_pointer_ & 7] = next;
++bitmap_queue_write_pointer_; ++bitmap_queue_pointer_;
}; };
switch(colour_depth_) { switch(colour_depth_) {
case Depth::EightBPP: next_byte(); next_byte(); break; case Depth::EightBPP: next_byte(); next_byte(); break;
case Depth::FourBPP: next_byte(); break; case Depth::FourBPP: next_byte(); break;
case Depth::TwoBPP: if(!(pixel_count_&1)) next_byte(); break; case Depth::TwoBPP: if(!(pixel_count_&3)) next_byte(); break;
case Depth::OneBPP: if(!(pixel_count_&3)) next_byte(); break; case Depth::OneBPP: if(!(pixel_count_&7)) next_byte(); break;
} }
++display_count_;
} }
// Move along line. // Move along line.
@ -251,6 +249,13 @@ private:
uint32_t cursor_shift_ = 0; uint32_t cursor_shift_ = 0;
Timing horizontal_timing_, vertical_timing_; Timing horizontal_timing_, vertical_timing_;
enum class Depth {
OneBPP = 0b00,
TwoBPP = 0b01,
FourBPP = 0b10,
EightBPP = 0b11,
};
// Current video state. // Current video state.
enum class Phase { enum class Phase {
Sync, Blank, Border, Display, Sync, Blank, Border, Display,
@ -258,11 +263,13 @@ private:
template <bool is_vertical> template <bool is_vertical>
struct State { struct State {
uint32_t position = 0; uint32_t position = 0;
uint32_t display_start = 0;
uint32_t display_end = 0;
void increment_position(const Timing &timing) { void increment_position(const Timing &timing) {
if(position == timing.sync_width) state |= SyncEnded; if(position == timing.sync_width) state |= SyncEnded;
if(position == timing.display_start) state |= DisplayStarted; if(position == timing.display_start) { state |= DisplayStarted; display_start = position; }
if(position == timing.display_end) state |= DisplayEnded; if(position == timing.display_end) { state |= DisplayEnded; display_end = position; }
if(position == timing.border_start) state |= BorderStarted; if(position == timing.border_start) state |= BorderStarted;
if(position == timing.border_end) state |= BorderEnded; if(position == timing.border_end) state |= BorderEnded;
@ -272,12 +279,31 @@ private:
if(position == timing.period) { if(position == timing.period) {
state = DidRestart; state = DidRestart;
position = 0; position = 0;
// Both display start and end need to be seeded as bigger than can be reached,
// while having some overhead for addition.
display_end = display_start = std::numeric_limits<uint32_t>::max() >> 1;
} else { } else {
++position; ++position;
if(position == 1024) position = 0; if(position == 1024) position = 0;
} }
} }
bool is_outputting(Depth depth) const {
return position >= display_start + output_latencies[static_cast<uint32_t>(depth)] && position < display_end + output_latencies[static_cast<uint32_t>(depth)];
}
uint32_t output_cycle(Depth depth) const {
return position - display_start - output_latencies[static_cast<uint32_t>(depth)];
}
static constexpr uint32_t output_latencies[] = {
19 >> 1, // 1 bpp.
11 >> 1, // 2 bpp.
7 >> 1, // 4 bpp.
5 >> 1 // 8 bpp.
};
static constexpr uint8_t SyncEnded = 0x1; static constexpr uint8_t SyncEnded = 0x1;
static constexpr uint8_t BorderStarted = 0x2; static constexpr uint8_t BorderStarted = 0x2;
static constexpr uint8_t BorderEnded = 0x4; static constexpr uint8_t BorderEnded = 0x4;
@ -298,14 +324,18 @@ private:
return (state & DisplayStarted) && !(state & DisplayEnded); return (state & DisplayStarted) && !(state & DisplayEnded);
} }
Phase phase() const { Phase phase(Phase horizontal_fallback = Phase::Border) const {
// TODO: turn the following logic into a 32-entry lookup table. // TODO: turn the following logic into a lookup table.
if(!(state & SyncEnded)) { if(!(state & SyncEnded)) {
return Phase::Sync; return Phase::Sync;
} }
if(!(state & BorderStarted) || (state & BorderEnded)) { if(!(state & BorderStarted) || (state & BorderEnded)) {
return Phase::Blank; return Phase::Blank;
} }
if constexpr (!is_vertical) {
return horizontal_fallback;
}
if(!(state & DisplayStarted) || (state & DisplayEnded)) { if(!(state & DisplayStarted) || (state & DisplayEnded)) {
return Phase::Border; return Phase::Border;
} }
@ -355,13 +385,7 @@ private:
// the pixel clock because that's the fidelity at which the programmer // the pixel clock because that's the fidelity at which the programmer
// places horizontal events — display start, end, sync period, etc. // places horizontal events — display start, end, sync period, etc.
uint32_t clock_divider_ = 0; uint32_t clock_divider_ = 0;
Depth colour_depth_;
enum class Depth {
OneBPP = 0b00,
TwoBPP = 0b01,
FourBPP = 0b10,
EightBPP = 0b11,
} colour_depth_;
void set_clock_divider(uint32_t divider) { void set_clock_divider(uint32_t divider) {
if(divider == clock_divider_) { if(divider == clock_divider_) {
@ -407,7 +431,7 @@ private:
void end_horizontal() { void end_horizontal() {
set_phase(Phase::Sync); set_phase(Phase::Sync);
display_area_start_ = -1; display_area_start_ = -1;
bitmap_queue_write_pointer_ = bitmap_queue_read_pointer_ = display_count_ = 0; bitmap_queue_pointer_ = 0;
} }
template <Phase vertical_phase> void tick_horizontal() { template <Phase vertical_phase> void tick_horizontal() {
@ -425,31 +449,24 @@ private:
} }
// Border lines: ignore display phases; also reset the border phase if the colour changes. // Border lines: ignore display phases; also reset the border phase if the colour changes.
const auto horizontal_phase = horizontal_state_.phase(); const auto phase = horizontal_state_.phase(Phase::Border);
const auto phase = horizontal_phase != Phase::Display ? horizontal_phase : Phase::Border;
if(phase != phase_ || (phase_ == Phase::Border && border_colour_ != phased_border_colour_)) { if(phase != phase_ || (phase_ == Phase::Border && border_colour_ != phased_border_colour_)) {
set_phase(phase); set_phase(phase);
} }
} }
uint8_t bitmap_queue_[8]; uint8_t bitmap_queue_[8];
int bitmap_queue_write_pointer_ = 0; int bitmap_queue_pointer_ = 0;
int bitmap_queue_read_pointer_ = 0;
int display_count_ = 0;
template <> template <>
void tick_horizontal<Phase::Display>() { void tick_horizontal<Phase::Display>() {
// Some timing facts, to explain what would otherwise be magic constants. // Some timing facts, to explain what would otherwise be magic constants.
static constexpr int CursorDelay = 6; // The cursor will appear six pixels after its programmed trigger point. static constexpr int CursorDelay = 5; // The cursor will appear six pixels after its programmed trigger point.
// static constexpr int Delay1bpp = 19; // ... BUT! Border and display are currently a pixel early. So move the
// static constexpr int Delay2bpp = 11; // cursor for alignment.
// static constexpr int Delay4bpp = 7;
// static constexpr int Delay8bpp = 5;
// Deal with sync and blank via set_phase(); collapse display and border into Phase::Display. // Deal with sync and blank via set_phase(); collapse display and border into Phase::Display.
const auto horizontal_phase = horizontal_state_.phase(); const auto phase = horizontal_state_.phase(Phase::Display);
const auto phase = horizontal_phase == Phase::Border ? Phase::Display : horizontal_phase;
if(phase != phase_) set_phase(phase); if(phase != phase_) set_phase(phase);
// Update cursor pixel counter if applicable; this might mean triggering it // Update cursor pixel counter if applicable; this might mean triggering it
@ -470,47 +487,41 @@ private:
if(!pixel_count_) pixels_ = reinterpret_cast<uint16_t *>(crt_.begin_data(PixelBufferSize)); if(!pixel_count_) pixels_ = reinterpret_cast<uint16_t *>(crt_.begin_data(PixelBufferSize));
// Output. // Output.
// TODO: below isn't correct because the read pointers don't advance unless pixels are making it all
// the way to the display. I also don't think it quite correctly models any reasonable interpretation of
// what might happen if colour depth were changed during the line. Hmmm.
if(pixels_) { if(pixels_) {
// Paint the border colour for potential painting over. // Paint the border colour for potential painting over.
pixels_[0] = pixels_[1] = border_colour_;
switch(colour_depth_) { if(horizontal_state_.is_outputting(colour_depth_)) {
case Depth::EightBPP: const auto source = horizontal_state_.output_cycle(colour_depth_);
// A five pixel delay applies. So that means at least three display counts must have happened.
// pixels_[0] = (colours_[pixel_data_[0] & 0xf] & colour(0b0111'0011'0111)) | high_spread[pixel_data_[0] >> 4];
// pixels_[1] = (colours_[pixel_data_[1] & 0xf] & colour(0b0111'0011'0111)) | high_spread[pixel_data_[1] >> 4];
break;
case Depth::FourBPP: // TODO: all below should be delayed an extra pixel. As should the border, actually. Fix up externally?
// A 7-pixel delay applies. switch(colour_depth_) {
// So that means at least three display counts must have happened, accumating 8 pixels. case Depth::EightBPP: {
if(display_count_ < 3 || bitmap_queue_read_pointer_ == bitmap_queue_write_pointer_+1) { const uint8_t *bitmap = &bitmap_queue_[(source << 1) & 7];
break; pixels_[0] = (colours_[bitmap[0] & 0xf] & colour(0b0111'0011'0111)) | high_spread[bitmap[0] >> 4];
} pixels_[1] = (colours_[bitmap[1] & 0xf] & colour(0b0111'0011'0111)) | high_spread[bitmap[1] >> 4];
} break;
pixels_[0] = case Depth::FourBPP:
bitmap_queue_read_pointer_ ? pixels_[0] = colours_[bitmap_queue_[source & 7] & 0xf];
colours_[bitmap_queue_[(bitmap_queue_read_pointer_ - 1) & 7] >> 4] : border_colour_; pixels_[1] = colours_[bitmap_queue_[source & 7] >> 4];
pixels_[1] = break;
bitmap_queue_read_pointer_ != bitmap_queue_write_pointer_ ?
colours_[bitmap_queue_[bitmap_queue_read_pointer_ & 7] & 0xf] : border_colour_;
++bitmap_queue_read_pointer_;
break;
case Depth::TwoBPP: case Depth::TwoBPP: {
// pixels_[0] = colours_[pixel_data_[0] & 3]; uint8_t &bitmap = bitmap_queue_[(source >> 1) & 7];
// pixels_[1] = colours_[(pixel_data_[0] >> 2) & 3]; pixels_[0] = colours_[bitmap & 3];
// pixel_data_[0] >>= 4; pixels_[1] = colours_[(bitmap >> 2) & 3];
break; bitmap >>= 4;
} break;
case Depth::OneBPP: case Depth::OneBPP: {
// pixels_[0] = colours_[pixel_data_[0] & 1]; uint8_t &bitmap = bitmap_queue_[(source >> 2) & 7];
// pixels_[1] = colours_[(pixel_data_[0] >> 1) & 1]; pixels_[0] = colours_[bitmap & 1];
// pixel_data_[0] >>= 2; pixels_[1] = colours_[(bitmap >> 1) & 1];
break; bitmap >>= 2;
} break;
}
} else {
pixels_[0] = pixels_[1] = border_colour_;
} }
// Overlay cursor if applicable. // Overlay cursor if applicable.
@ -534,30 +545,6 @@ private:
pixel_count_ += 2; pixel_count_ += 2;
} }
// switch(colour_depth_) {
// case Depth::EightBPP:
// pixels_[0] = (colours_[pixel_data_[0] & 0xf] & colour(0b0111'0011'0111)) | high_spread[pixel_data_[0] >> 4];
// pixels_[1] = (colours_[pixel_data_[1] & 0xf] & colour(0b0111'0011'0111)) | high_spread[pixel_data_[1] >> 4];
// break;
//
// case Depth::FourBPP:
// pixels_[0] = colours_[pixel_data_[0] & 0xf];
// pixels_[1] = colours_[pixel_data_[0] >> 4];
// break;
//
// case Depth::TwoBPP:
// pixels_[0] = colours_[pixel_data_[0] & 3];
// pixels_[1] = colours_[(pixel_data_[0] >> 2) & 3];
// pixel_data_[0] >>= 4;
// break;
//
// case Depth::OneBPP:
// pixels_[0] = colours_[pixel_data_[0] & 1];
// pixels_[1] = colours_[(pixel_data_[0] >> 1) & 1];
// pixel_data_[0] >>= 2;
// break;
// }
}; };
} }