From 2de1a2dd0ddaa8f632dc746b7325f3d473eb4f91 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Mar 2024 20:41:24 -0400 Subject: [PATCH] Install and properly clock a CRT. --- Machines/Acorn/Archimedes/Archimedes.cpp | 8 ++--- .../Archimedes/InputOutputController.hpp | 11 +++--- .../Acorn/Archimedes/MemoryController.hpp | 14 +++----- Machines/Acorn/Archimedes/Video.hpp | 36 ++++++++++++++++--- Outputs/CRT/CRT.cpp | 12 ++++++- Outputs/CRT/CRT.hpp | 14 ++++++++ 6 files changed, 69 insertions(+), 26 deletions(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 11a1484b8..dac6681a5 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -116,10 +116,10 @@ class ConcreteMachine: private: // MARK: - ScanProducer. void set_scan_target(Outputs::Display::ScanTarget *scan_target) override { - (void)scan_target; + executor_.bus.video().crt().set_scan_target(scan_target); } Outputs::Display::ScanStatus get_scaled_scan_status() const override { - return Outputs::Display::ScanStatus(); + return executor_.bus.video().crt().get_scaled_scan_status(); } std::array pc_history; @@ -208,8 +208,8 @@ class ConcreteMachine: } void tick_timers() { executor_.bus.tick_timers(); } - void tick_sound() { executor_.bus.tick_sound(); } - void tick_video() { executor_.bus.tick_video(); } + void tick_sound() { executor_.bus.sound().tick(); } + void tick_video() { executor_.bus.video().tick(); } // MARK: - MediaTarget bool insert_media(const Analyser::Static::Media &) override { diff --git a/Machines/Acorn/Archimedes/InputOutputController.hpp b/Machines/Acorn/Archimedes/InputOutputController.hpp index 5be53b863..3d55520f2 100644 --- a/Machines/Acorn/Archimedes/InputOutputController.hpp +++ b/Machines/Acorn/Archimedes/InputOutputController.hpp @@ -328,13 +328,10 @@ struct InputOutputController { update_interrupts(); } - Sound &sound() { - return sound_; - } - - Video> &video() { - return video_; - } + auto &sound() { return sound_; } + const auto &sound() const { return sound_; } + auto &video() { return video_; } + const auto &video() const { return video_; } void update_interrupts() { if(sound_.interrupt()) { diff --git a/Machines/Acorn/Archimedes/MemoryController.hpp b/Machines/Acorn/Archimedes/MemoryController.hpp index 926878e97..9829ff014 100644 --- a/Machines/Acorn/Archimedes/MemoryController.hpp +++ b/Machines/Acorn/Archimedes/MemoryController.hpp @@ -197,15 +197,11 @@ struct MemoryController { return false; } - void tick_timers() { ioc_.tick_timers(); } - void tick_sound() { - // TODO: does disabling sound DMA pause output, or leave it ticking and merely - // stop allowing it to use the bus? - ioc_.sound().tick(); - } - void tick_video() { - ioc_.video().tick(); - } + void tick_timers() { ioc_.tick_timers(); } + auto &sound() { return ioc_.sound(); } + const auto &sound() const { return ioc_.sound(); } + auto &video() { return ioc_.video(); } + const auto &video() const { return ioc_.video(); } private: Log::Logger logger; diff --git a/Machines/Acorn/Archimedes/Video.hpp b/Machines/Acorn/Archimedes/Video.hpp index 42acc49ed..e227f3f3e 100644 --- a/Machines/Acorn/Archimedes/Video.hpp +++ b/Machines/Acorn/Archimedes/Video.hpp @@ -9,6 +9,7 @@ #pragma once #include "../../../Outputs/Log.hpp" +#include "../../../Outputs/CRT/CRT.hpp" #include @@ -17,7 +18,12 @@ namespace Archimedes { template struct Video { Video(InterruptObserverT &observer, SoundT &sound, const uint8_t *ram) : - observer_(observer), sound_(sound), ram_(ram) {} + observer_(observer), + sound_(sound), + ram_(ram), + crt_(Outputs::Display::InputDataType::Red4Green4Blue4) { + set_clock_divider(2); + } void write(uint32_t value) { const auto target = (value >> 24) & 0xfc; @@ -112,10 +118,10 @@ struct Video { // Set pixel rate. This is the value that a 24Mhz clock should be divided // by to get half the pixel rate. switch(value & 0b11) { - case 0b00: clock_divider_ = 6; break; // i.e. pixel clock = 8Mhz. - case 0b01: clock_divider_ = 4; break; // 12Mhz. - case 0b10: clock_divider_ = 3; break; // 16Mhz. - case 0b11: clock_divider_ = 2; break; // 24Mhz. + case 0b00: set_clock_divider(6); break; // i.e. pixel clock = 8Mhz. + case 0b01: set_clock_divider(4); break; // 12Mhz. + case 0b10: set_clock_divider(3); break; // 16Mhz. + case 0b11: set_clock_divider(2); break; // 24Mhz. } break; @@ -161,6 +167,9 @@ struct Video { void set_buffer_end(uint32_t address) { buffer_end_ = address; } void set_cursor_start(uint32_t address) { cursor_start_ = address; } + Outputs::CRT::CRT &crt() { return crt_; } + const Outputs::CRT::CRT &crt() const { return crt_; } + private: Log::Logger logger; InterruptObserverT &observer_; @@ -169,6 +178,7 @@ private: // In the current version of this code, video DMA occurrs costlessly, // being deferred to the component itself. const uint8_t *ram_ = nullptr; + Outputs::CRT::CRT crt_; // TODO: real video output. uint32_t position_ = 0; @@ -202,6 +212,22 @@ private: // the pixel clock because that's the fidelity at which the programmer // places horizontal events — display start, end, sync period, etc. uint32_t clock_divider_ = 0; + + void set_clock_divider(uint32_t divider) { + if(divider == clock_divider_) { + return; + } + + clock_divider_ = divider; + crt_.set_new_timing( + 24'000'000 / (divider * 312 * 50), /* Cycle per line. */ + 312, /* Height of display. */ + Outputs::Display::ColourSpace::YIQ, /* Composite colour space. */ + Outputs::CRT::PAL::ColourCycleNumerator, + Outputs::CRT::PAL::ColourCycleDenominator, + Outputs::CRT::PAL::VerticalSyncLength, + true); + } }; } diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 85e812bbb..b6b0fb0ba 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -117,7 +117,14 @@ void CRT::set_new_display_type(int cycles_per_line, Outputs::Display::Type displ case Outputs::Display::Type::PAL50: case Outputs::Display::Type::PAL60: scan_target_modals_.intended_gamma = 2.8f; - set_new_timing(cycles_per_line, (displayType == Outputs::Display::Type::PAL50) ? 312 : 262, Outputs::Display::ColourSpace::YUV, 709379, 2500, 5, true); + set_new_timing( + cycles_per_line, + (displayType == Outputs::Display::Type::PAL50) ? 312 : 262, + Outputs::Display::ColourSpace::YUV, + PAL::ColourCycleNumerator, + PAL::ColourCycleDenominator, + PAL::VerticalSyncLength, + true); // i.e. 283.7516 colour cycles per line; 2.5 lines = vertical sync. break; @@ -176,6 +183,9 @@ CRT::CRT(int cycles_per_line, set_new_timing(cycles_per_line, height_of_display, Outputs::Display::ColourSpace::YIQ, 1, 1, vertical_sync_half_lines, false); } +// Use some from-thin-air arbitrary constants for default timing, otherwise passing +// construction off to one of the other constructors. +CRT::CRT(Outputs::Display::InputDataType data_type) : CRT(100, 1, 100, 1, data_type) {} // MARK: - Sync loop diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index d0d8ac7fc..bd1bc1b6c 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -18,6 +18,15 @@ namespace Outputs::CRT { +namespace PAL { + +// PAL: 283.7516 colour cycles per line; 2.5 lines = vertical sync. +static constexpr int ColourCycleNumerator = 709379; +static constexpr int ColourCycleDenominator = 2500; +static constexpr int VerticalSyncLength = 5; + +} + class CRT; class Delegate { @@ -136,6 +145,11 @@ class CRT { Outputs::Display::Type display_type, Outputs::Display::InputDataType data_type); + /*! Constructs a CRT with no guaranteed expectations as to input signal other than data type; + this allows for callers that intend to rely on @c set_new_timing. + */ + CRT(Outputs::Display::InputDataType data_type); + /*! Resets the CRT with new timing information. The CRT then continues as though the new timing had been provided at construction. */ void set_new_timing(