From 91ec4012b926a0a1d2b356634455a4c1d2a4ab21 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 May 2026 14:07:10 -0400 Subject: [PATCH 1/5] Don't impose NTSC colour bursts on PAL. --- Components/6847/6847.cpp | 18 ++++++++++++------ Components/6847/6847.hpp | 2 ++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Components/6847/6847.cpp b/Components/6847/6847.cpp index 6a489a8c2..2571db321 100644 --- a/Components/6847/6847.cpp +++ b/Components/6847/6847.cpp @@ -130,18 +130,24 @@ using namespace Motorola::MC6847; MC6847Base::MC6847Base(const Outputs::Display::Type display_type) : crt_( - 455, - 1, - display_type, - Outputs::Display::InputDataType::Red8Green8Blue8 -) { + 455, + 1, + display_type, + Outputs::Display::InputDataType::Red8Green8Blue8 + ), + display_type_(display_type) +{ crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour); } void MC6847Base::pixel_line(const int line_begin, const int line_end) { if(line_begin < LineLayout::EndOfLeftBorder && line_end >= LineLayout::EndOfLeftBorder) [[unlikely]] { crt_.output_sync(LineLayout::EndOfSync); - crt_.output_colour_burst(LineLayout::EndOfColourBurst - LineLayout::EndOfSync, ColourPhase); + if(display_type_ == Outputs::Display::Type::NTSC60) { + crt_.output_colour_burst(LineLayout::EndOfColourBurst - LineLayout::EndOfSync, ColourPhase); + } else { + crt_.output_default_colour_burst(LineLayout::EndOfColourBurst - LineLayout::EndOfSync); + } crt_.output_blank(LineLayout::EndOfLeftBorder - LineLayout::EndOfColourBurst); pixels_ = reinterpret_cast(crt_.begin_data(256)); diff --git a/Components/6847/6847.hpp b/Components/6847/6847.hpp index b47049bbc..0bfd4b260 100644 --- a/Components/6847/6847.hpp +++ b/Components/6847/6847.hpp @@ -184,6 +184,8 @@ struct MC6847Base { uint16_t address_ = 0; Numeric::SizedInt<4> row_; } address_; + + Outputs::Display::Type display_type_; }; struct NullMapper { From 9ec7e39874a05529ce76a563994481436cc455c3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 May 2026 14:53:52 -0400 Subject: [PATCH 2/5] Add delegate call-outs. --- Components/6847/6847.hpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Components/6847/6847.hpp b/Components/6847/6847.hpp index 0bfd4b260..a788cfa85 100644 --- a/Components/6847/6847.hpp +++ b/Components/6847/6847.hpp @@ -39,7 +39,6 @@ template struct FrameLayout { static_assert(FrameLayout::EndOfField == 262, "NTSC frames should be 262 lines long"); static_assert(FrameLayout::EndOfField == 312, "PAL-padded frames should be 312 lines long"); -// TODO: allow a delegate to be specified, and signal it. struct MC6847Delegate { virtual void set_hsync(bool active) = 0; virtual void set_vsync(bool active) = 0; @@ -205,6 +204,7 @@ public: bool hsync() const; bool vsync() const; using MC6847Base::set_mode; + void set_delegate(MC6847Delegate *); // MARK: - ScanTarget entrypoints. @@ -216,6 +216,7 @@ public: private: MemoryAccessT &memory_; Numeric::DividingAccumulator::EndOfField> position_{2}; + MC6847Delegate *delegate_ = nullptr; }; // MARK: - Implementation. @@ -236,7 +237,11 @@ void MC6847::run_for(const Cycles cycles) { position_.advance( cycles.as(), [&] (const int line, const int begin, const int end) { + if(delegate_ && !begin) delegate_->set_hsync(true); + if(line < FrameLayout::EndOfPixels) { + if(delegate_ && !begin && !address_.row()) delegate_->set_row_preset(true); + Numeric::clamp( begin, end, @@ -253,17 +258,27 @@ void MC6847::run_for(const Cycles cycles) { } ); pixel_line(begin, end); + + if(delegate_ && end >= LineLayout::EndOfSync) delegate_->set_row_preset(false); } else if(line < FrameLayout::EndOfBottomBorder) { border_line(begin, end); } else if(line < FrameLayout::EndOfFrontPorch) { porch_line(begin, end); } else if(line < FrameLayout::EndOfSync) { + if(delegate_ && !begin && line == FrameLayout::EndOfFrontPorch) { + delegate_->set_vsync(true); + } sync_line(begin, end); } else if(line < FrameLayout::EndOfBackPorch) { + if(delegate_ && !begin && line == FrameLayout::EndOfSync) { + delegate_->set_vsync(false); + } porch_line(begin, end); } else { border_line(begin, end); } + + if(delegate_ && end >= LineLayout::EndOfSync) delegate_->set_hsync(false); }, [&] { reset(); @@ -281,6 +296,11 @@ bool MC6847::hsync() const { return MC6847Base::hsync(position_.subsegment()); } +template +void MC6847::set_delegate(MC6847Delegate *const delegate) { + delegate_ = delegate; +} + template bool MC6847::vsync() const { const auto segment = position_.segment(); From 0fb2490af5fe179e089b0fb6d673b4fe8c3df83f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 May 2026 15:03:30 -0400 Subject: [PATCH 3/5] As implemented, the delegate need not be dynamic. --- Components/6847/6847.hpp | 84 ++++++++++++++++++------------------ Machines/Tandy/CoCo/CoCo.cpp | 7 ++- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/Components/6847/6847.hpp b/Components/6847/6847.hpp index a788cfa85..f0dcaacc4 100644 --- a/Components/6847/6847.hpp +++ b/Components/6847/6847.hpp @@ -39,12 +39,6 @@ template struct FrameLayout { static_assert(FrameLayout::EndOfField == 262, "NTSC frames should be 262 lines long"); static_assert(FrameLayout::EndOfField == 312, "PAL-padded frames should be 312 lines long"); -struct MC6847Delegate { - virtual void set_hsync(bool active) = 0; - virtual void set_vsync(bool active) = 0; - virtual void set_row_preset(bool active) = 0; -}; - namespace Mode { using IntT = uint8_t; @@ -193,10 +187,21 @@ struct NullMapper { } }; -template +struct NullDelegate { + void set_hsync(bool) {} + void set_vsync(bool) {} + void set_row_preset(bool) {} +}; + +template < + FrameTiming timing, + typename MemoryAccessT, + typename DelegateT = NullDelegate, + typename ModeMapperT = NullMapper +> class MC6847: private MC6847Base { public: - MC6847(MemoryAccessT &); + MC6847(MemoryAccessT &, DelegateT &delegate); void run_for(const Cycles); Cycles next_sequence_point() const; @@ -204,7 +209,6 @@ public: bool hsync() const; bool vsync() const; using MC6847Base::set_mode; - void set_delegate(MC6847Delegate *); // MARK: - ScanTarget entrypoints. @@ -216,31 +220,32 @@ public: private: MemoryAccessT &memory_; Numeric::DividingAccumulator::EndOfField> position_{2}; - MC6847Delegate *delegate_ = nullptr; + DelegateT &delegate_; }; // MARK: - Implementation. -template -MC6847::MC6847(MemoryAccessT &memory) : +template +MC6847::MC6847(MemoryAccessT &memory, DelegateT &delegate) : MC6847Base(is_ntsc(timing) ? Outputs::Display::Type::NTSC60 : Outputs::Display::Type::PAL50), - memory_(memory) { + memory_(memory), + delegate_(delegate) { crt_.set_fixed_framing([&] { run_for(Cycles(10'000)); }); } -template -void MC6847::run_for(const Cycles cycles) { +template +void MC6847::run_for(const Cycles cycles) { // This template binds the drawing logic to whatever was supplied // as MemoryAccessT; a consequence of that is that frame layout logic is here. position_.advance( cycles.as(), [&] (const int line, const int begin, const int end) { - if(delegate_ && !begin) delegate_->set_hsync(true); + if(!begin) delegate_.set_hsync(true); if(line < FrameLayout::EndOfPixels) { - if(delegate_ && !begin && !address_.row()) delegate_->set_row_preset(true); + if(!begin && !address_.row()) delegate_.set_row_preset(true); Numeric::clamp( begin, @@ -259,26 +264,26 @@ void MC6847::run_for(const Cycles cycles) { ); pixel_line(begin, end); - if(delegate_ && end >= LineLayout::EndOfSync) delegate_->set_row_preset(false); + if(end >= LineLayout::EndOfSync) delegate_.set_row_preset(false); } else if(line < FrameLayout::EndOfBottomBorder) { border_line(begin, end); } else if(line < FrameLayout::EndOfFrontPorch) { porch_line(begin, end); } else if(line < FrameLayout::EndOfSync) { - if(delegate_ && !begin && line == FrameLayout::EndOfFrontPorch) { - delegate_->set_vsync(true); + if(!begin && line == FrameLayout::EndOfFrontPorch) { + delegate_.set_vsync(true); } sync_line(begin, end); } else if(line < FrameLayout::EndOfBackPorch) { - if(delegate_ && !begin && line == FrameLayout::EndOfSync) { - delegate_->set_vsync(false); + if(!begin && line == FrameLayout::EndOfSync) { + delegate_.set_vsync(false); } porch_line(begin, end); } else { border_line(begin, end); } - if(delegate_ && end >= LineLayout::EndOfSync) delegate_->set_hsync(false); + if(end >= LineLayout::EndOfSync) delegate_.set_hsync(false); }, [&] { reset(); @@ -286,44 +291,39 @@ void MC6847::run_for(const Cycles cycles) { ); } -template -Cycles MC6847::next_sequence_point() const { +template +Cycles MC6847::next_sequence_point() const { return MC6847Base::next_sequence_point(position_.subsegment()); } -template -bool MC6847::hsync() const { +template +bool MC6847::hsync() const { return MC6847Base::hsync(position_.subsegment()); } -template -void MC6847::set_delegate(MC6847Delegate *const delegate) { - delegate_ = delegate; -} - -template -bool MC6847::vsync() const { +template +bool MC6847::vsync() const { const auto segment = position_.segment(); return segment >= FrameLayout::EndOfFrontPorch && segment < FrameLayout::EndOfSync; } -template -void MC6847::set_scan_target(Outputs::Display::ScanTarget *const target) { +template +void MC6847::set_scan_target(Outputs::Display::ScanTarget *const target) { crt_.set_scan_target(target); } -template -Outputs::Display::ScanStatus MC6847::get_scaled_scan_status() const { +template +Outputs::Display::ScanStatus MC6847::get_scaled_scan_status() const { return crt_.get_scaled_scan_status(); } -template -void MC6847::set_display_type(const Outputs::Display::DisplayType display_type) { +template +void MC6847::set_display_type(const Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); } -template -Outputs::Display::DisplayType MC6847::get_display_type() const { +template +Outputs::Display::DisplayType MC6847::get_display_type() const { return crt_.get_display_type(); } diff --git a/Machines/Tandy/CoCo/CoCo.cpp b/Machines/Tandy/CoCo/CoCo.cpp index d0317494d..fd72da45c 100644 --- a/Machines/Tandy/CoCo/CoCo.cpp +++ b/Machines/Tandy/CoCo/CoCo.cpp @@ -124,7 +124,7 @@ public: pia1_handler_(*this), pia1_(pia1_handler_), sam_(memory_), - m6847_(sam_), + m6847_(sam_, sam_), tape_player_(int(ClockRate)), audio_(audio_queue_, MaxDACLevel), speaker_(audio_) @@ -669,6 +669,10 @@ private: return graphics_address_; } + void set_hsync(bool) {} + void set_vsync(bool) {} + void set_row_preset(bool) {} + private: MemoryMap &memory_; @@ -694,6 +698,7 @@ private: Motorola::MC6847::MC6847< Motorola::MC6847::FrameTiming::NTSC, SAM, + SAM, ModeMapper >, Cycles, From 8b1c1e9c7a014ca2e141e87a9632178d2b14cf29 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 May 2026 15:09:23 -0400 Subject: [PATCH 4/5] Add TODO. --- Components/6847/6847.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Components/6847/6847.cpp b/Components/6847/6847.cpp index 2571db321..080c0648c 100644 --- a/Components/6847/6847.cpp +++ b/Components/6847/6847.cpp @@ -274,8 +274,13 @@ void MC6847Base::pixel_line(const int line_begin, const int line_end) { pixels_[7] = palette[(pixels >> 0) & 1]; } else { const uint32_t *const palette = mode & Mode::ColourSelect ? Colours::text1 : Colours::text0; - const uint8_t pixels = - font[data & 63][row] ^ (mode & Mode::Invert ? 0xff : 0x00) ^ (data & 64 ? 0xff : 0x00); + uint8_t pixels = (mode & Mode::Invert ? 0xff : 0x00) ^ (data & 64 ? 0xff : 0x00); + + if(mode & Mode::ExternalROM) { + // TODO: external ROM. + } else { + pixels ^= font[data & 63][row]; + } pixels_[0] = palette[(pixels >> 7) & 1]; pixels_[1] = palette[(pixels >> 6) & 1]; From 43bed7406db78ada1d208f24d97da98e06018298 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 May 2026 15:27:10 -0400 Subject: [PATCH 5/5] Field sync runs from end of display to end of sync. --- Components/6847/6847.hpp | 10 +++++----- Machines/Tandy/CoCo/CoCo.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Components/6847/6847.hpp b/Components/6847/6847.hpp index f0dcaacc4..8b2814963 100644 --- a/Components/6847/6847.hpp +++ b/Components/6847/6847.hpp @@ -189,7 +189,7 @@ struct NullMapper { struct NullDelegate { void set_hsync(bool) {} - void set_vsync(bool) {} + void set_field_sync(bool) {} void set_row_preset(bool) {} }; @@ -266,17 +266,17 @@ void MC6847::run_for(const Cycles if(end >= LineLayout::EndOfSync) delegate_.set_row_preset(false); } else if(line < FrameLayout::EndOfBottomBorder) { + if(!begin && line == FrameLayout::EndOfPixels) { + delegate_.set_field_sync(true); + } border_line(begin, end); } else if(line < FrameLayout::EndOfFrontPorch) { porch_line(begin, end); } else if(line < FrameLayout::EndOfSync) { - if(!begin && line == FrameLayout::EndOfFrontPorch) { - delegate_.set_vsync(true); - } sync_line(begin, end); } else if(line < FrameLayout::EndOfBackPorch) { if(!begin && line == FrameLayout::EndOfSync) { - delegate_.set_vsync(false); + delegate_.set_field_sync(false); } porch_line(begin, end); } else { diff --git a/Machines/Tandy/CoCo/CoCo.cpp b/Machines/Tandy/CoCo/CoCo.cpp index fd72da45c..2fa3b9c38 100644 --- a/Machines/Tandy/CoCo/CoCo.cpp +++ b/Machines/Tandy/CoCo/CoCo.cpp @@ -670,7 +670,7 @@ private: } void set_hsync(bool) {} - void set_vsync(bool) {} + void set_field_sync(bool) {} void set_row_preset(bool) {} private: