From b15ff6d442466f202c2e7947028d7d3e0470b7ec Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 30 Apr 2024 22:06:08 -0400 Subject: [PATCH 1/3] Support interlaced video timing. --- Machines/Acorn/Archimedes/Video.hpp | 54 ++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/Machines/Acorn/Archimedes/Video.hpp b/Machines/Acorn/Archimedes/Video.hpp index cca4420df..749eec0ab 100644 --- a/Machines/Acorn/Archimedes/Video.hpp +++ b/Machines/Acorn/Archimedes/Video.hpp @@ -79,9 +79,7 @@ struct Video { horizontal_timing_.cursor_start = (value >> 13) & 0x7ff; cursor_shift_ = (value >> 11) & 3; break; - case 0x9c: - logger.error().append("TODO: Video horizontal interlace: %d", (value >> 14) & 0x3ff); - break; + case 0x9c: horizontal_timing_.interlace_sync_position = timing_value(value); break; case 0xa0: vertical_timing_.period = timing_value(value); break; case 0xa4: vertical_timing_.sync_width = timing_value(value); break; @@ -104,6 +102,9 @@ struct Video { // Set colour depth. colour_depth_ = Depth((value >> 2) & 0b11); + + // Crib interlace-enable. + vertical_timing_.is_interlaced = value & (1 << 6); break; // @@ -244,6 +245,9 @@ private: uint32_t display_end = 0; uint32_t cursor_start = 0; uint32_t cursor_end = 0; + + uint32_t interlace_sync_position = 0; + bool is_interlaced = false; }; uint32_t cursor_shift_ = 0; Timing horizontal_timing_, vertical_timing_; @@ -257,7 +261,7 @@ private: // Current video state. enum class Phase { - Sync, Blank, Border, Display, + Sync, Blank, Border, Display, StartInterlacedSync, EndInterlacedSync, }; template struct State { @@ -265,8 +269,20 @@ private: uint32_t display_start = 0; uint32_t display_end = 0; + bool is_odd_iteration_ = false; + void increment_position(const Timing &timing) { - if(position == timing.sync_width) state |= SyncEnded; + const auto previous_override = interlace_override_; + if constexpr (is_vertical) { + interlace_override_ = Phase::Sync; // i.e. no override. + } + if(position == timing.sync_width) { + state |= SyncEnded; + if(is_vertical && timing.is_interlaced && is_odd_iteration_ && previous_override == Phase::Sync) { + --position; + interlace_override_ = Phase::EndInterlacedSync; + } + } if(position == timing.display_start) { state |= DisplayStarted; display_start = position; } if(position == timing.display_end) { state |= DisplayEnded; display_end = position; } if(position == timing.border_start) state |= BorderStarted; @@ -278,10 +294,16 @@ private: if(position == timing.period) { state = DidRestart; position = 0; + is_odd_iteration_ ^= true; // 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::max() >> 1; + + // Possibly label the next as a start-of-interlaced-sync. + if(is_vertical && timing.is_interlaced && is_odd_iteration_) { + interlace_override_ = Phase::StartInterlacedSync; + } } else { ++position; if(position == 1024) position = 0; @@ -310,6 +332,7 @@ private: static constexpr uint8_t DisplayEnded = 0x10; static constexpr uint8_t DidRestart = 0x20; uint8_t state = 0; + Phase interlace_override_ = Phase::Sync; bool cursor_active = false; @@ -324,6 +347,9 @@ private: } Phase phase(Phase horizontal_fallback = Phase::Border) const { + if(is_vertical && interlace_override_ != Phase::Sync) { + return interlace_override_; + } // TODO: turn the following logic into a lookup table. if(!(state & SyncEnded)) { return Phase::Sync; @@ -444,6 +470,24 @@ private: return; } + // Start interlaced sync lines: do blank from horizontal sync up to the programmed + // cutoff, then do sync. + if constexpr (vertical_phase == Phase::StartInterlacedSync) { + if(phase_ == Phase::Sync && horizontal_state_.phase() != Phase::Sync) { + set_phase(Phase::Blank); + } + if(phase_ == Phase::Blank && horizontal_state_.position == horizontal_timing_.interlace_sync_position) { + set_phase(Phase::Sync); + } + } + + // End interlaced sync lines: do sync up to the programmed cutoff, then do blank. + if constexpr (vertical_phase == Phase::EndInterlacedSync) { + if(phase_ == Phase::Sync && horizontal_state_.position == horizontal_timing_.interlace_sync_position) { + set_phase(Phase::Blank); + } + } + // Blank lines: obey only the transition from sync to non-sync. if constexpr (vertical_phase == Phase::Blank) { if(phase_ == Phase::Sync && horizontal_state_.phase() != Phase::Sync) { From 75457864369f05b3398a17a38226ee6b2651f3f1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 30 Apr 2024 22:22:18 -0400 Subject: [PATCH 2/3] Ensure extra line types are used. --- Machines/Acorn/Archimedes/Video.hpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Machines/Acorn/Archimedes/Video.hpp b/Machines/Acorn/Archimedes/Video.hpp index 32922433a..e05051e3c 100644 --- a/Machines/Acorn/Archimedes/Video.hpp +++ b/Machines/Acorn/Archimedes/Video.hpp @@ -202,10 +202,12 @@ struct Video { // Move along line. switch(vertical_state_.phase()) { - case Phase::Sync: tick_horizontal(); break; - case Phase::Blank: tick_horizontal(); break; - case Phase::Border: tick_horizontal(); break; - case Phase::Display: tick_horizontal(); break; + case Phase::Sync: tick_horizontal(); break; + case Phase::Blank: tick_horizontal(); break; + case Phase::Border: tick_horizontal(); break; + case Phase::Display: tick_horizontal(); break; + case Phase::StartInterlacedSync: tick_horizontal(); break; + case Phase::EndInterlacedSync: tick_horizontal(); break; } ++time_in_phase_; } @@ -500,6 +502,7 @@ private: if(phase_ == Phase::Blank && horizontal_state_.position == horizontal_timing_.interlace_sync_position) { set_phase(Phase::Sync); } + return; } // End interlaced sync lines: do sync up to the programmed cutoff, then do blank. @@ -507,6 +510,7 @@ private: if(phase_ == Phase::Sync && horizontal_state_.position == horizontal_timing_.interlace_sync_position) { set_phase(Phase::Blank); } + return; } // Blank lines: obey only the transition from sync to non-sync. From 3d61861737be2f8843da1a39333b962fb2cd200a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 30 Apr 2024 22:26:19 -0400 Subject: [PATCH 3/3] Ensure switch is complete. --- Machines/Acorn/Archimedes/Video.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Acorn/Archimedes/Video.hpp b/Machines/Acorn/Archimedes/Video.hpp index e05051e3c..607f82c0d 100644 --- a/Machines/Acorn/Archimedes/Video.hpp +++ b/Machines/Acorn/Archimedes/Video.hpp @@ -468,7 +468,7 @@ private: void set_phase(Phase phase) { if(time_in_phase_) { switch(phase_) { - case Phase::Sync: crt_.output_sync(time_in_phase_); break; + default: crt_.output_sync(time_in_phase_); break; case Phase::Blank: crt_.output_blank(time_in_phase_); break; case Phase::Border: crt_.output_level(time_in_phase_, phased_border_colour_); break; case Phase::Display: flush_pixels(); break;