Skip to content

Commit

Permalink
Merge pull request #1371 from TomHarte/Interlacing
Browse files Browse the repository at this point in the history
Support Archimedes interlaced video.
  • Loading branch information
TomHarte authored May 1, 2024
2 parents cb70967 + 3d61861 commit c3ad215
Showing 1 changed file with 58 additions and 10 deletions.
68 changes: 58 additions & 10 deletions Machines/Acorn/Archimedes/Video.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

//
Expand Down Expand Up @@ -201,10 +202,12 @@ struct Video {

// Move along line.
switch(vertical_state_.phase()) {
case Phase::Sync: tick_horizontal<Phase::Sync>(); break;
case Phase::Blank: tick_horizontal<Phase::Blank>(); break;
case Phase::Border: tick_horizontal<Phase::Border>(); break;
case Phase::Display: tick_horizontal<Phase::Display>(); break;
case Phase::Sync: tick_horizontal<Phase::Sync>(); break;
case Phase::Blank: tick_horizontal<Phase::Blank>(); break;
case Phase::Border: tick_horizontal<Phase::Border>(); break;
case Phase::Display: tick_horizontal<Phase::Display>(); break;
case Phase::StartInterlacedSync: tick_horizontal<Phase::StartInterlacedSync>(); break;
case Phase::EndInterlacedSync: tick_horizontal<Phase::EndInterlacedSync>(); break;
}
++time_in_phase_;
}
Expand Down Expand Up @@ -261,6 +264,9 @@ struct Video {
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_;
Expand All @@ -274,16 +280,28 @@ struct Video {

// Current video state.
enum class Phase {
Sync, Blank, Border, Display,
Sync, Blank, Border, Display, StartInterlacedSync, EndInterlacedSync,
};
template <bool is_vertical>
struct State {
uint32_t position = 0;
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;
Expand All @@ -295,10 +313,16 @@ struct Video {
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<uint32_t>::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;
Expand Down Expand Up @@ -327,6 +351,7 @@ struct Video {
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;

Expand All @@ -341,6 +366,9 @@ struct Video {
}

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;
Expand Down Expand Up @@ -440,7 +468,7 @@ struct Video {
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<uint16_t>(time_in_phase_, phased_border_colour_); break;
case Phase::Display: flush_pixels(); break;
Expand All @@ -465,6 +493,26 @@ struct Video {
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);
}
return;
}

// 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);
}
return;
}

// 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) {
Expand Down

0 comments on commit c3ad215

Please sign in to comment.