// // Video.hpp // Clock Signal // // Created by Thomas Harte on 04/10/2019. // Copyright © 2019 Thomas Harte. All rights reserved. // #ifndef Atari_ST_Video_hpp #define Atari_ST_Video_hpp #include "../../../Outputs/CRT/CRT.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" #include "../../../ClockReceiver/DeferredQueue.hpp" #include // Testing hook; not for any other user. class VideoTester; namespace Atari { namespace ST { struct LineLength { int length = 1024; int hsync_start = 1024; int hsync_end = 1024; }; /*! Models a combination of the parts of the GLUE, MMU and Shifter that in net form the video subsystem of the Atari ST. So not accurate to a real chip, but (hopefully) to a subsystem. */ class Video { public: Video(); /*! Sets the memory pool that provides video, and its size. */ void set_ram(uint16_t *, size_t size); /*! Sets the target device for video data. */ void set_scan_target(Outputs::Display::ScanTarget *scan_target); /// Gets the current scan status. Outputs::Display::ScanStatus get_scaled_scan_status() const; /*! Sets the type of output. */ void set_display_type(Outputs::Display::DisplayType); /*! Gets the type of output. */ Outputs::Display::DisplayType get_display_type(); /*! Produces the next @c duration period of pixels. */ void run_for(HalfCycles duration); /*! @returns the number of cycles until there is next a change in the hsync, vsync or display_enable outputs. */ HalfCycles get_next_sequence_point(); /*! @returns @c true if the horizontal sync output is currently active; @c false otherwise. @discussion On an Atari ST, this generates a VPA-style interrupt, which is often erroneously documented as being triggered by horizontal blank. */ bool hsync(); /*! @returns @c true if the vertical sync output is currently active; @c false otherwise. @discussion On an Atari ST, this generates a VPA-style interrupt, which is often erroneously documented as being triggered by vertical blank. */ bool vsync(); /*! @returns @c true if the display enabled output is currently active; @c false otherwise. @discussion On an Atari ST this is fed to the MFP. The documentation that I've been able to find implies a total 28-cycle delay between the real delay enabled signal changing and its effect on the 68000 interrupt input via the MFP. As I have yet to determine how much delay is caused by the MFP a full 28-cycle delay is applied by this class. This should be dialled down when the MFP's responsibility is clarified. */ bool display_enabled(); /// @returns the effect of reading from @c address; only the low 6 bits are decoded. uint16_t read(int address); /// Writes @c value to @c address, of which only the low 6 bits are decoded. void write(int address, uint16_t value); /// Used internally to track state. enum class FieldFrequency { Fifty = 0, Sixty = 1, SeventyTwo = 2 }; struct RangeObserver { /// Indicates to the observer that the memory access range has changed. virtual void video_did_change_access_range(Video *) = 0; }; /// Sets a range observer, which is an actor that will be notified if the memory access range changes. void set_range_observer(RangeObserver *); struct Range { uint32_t low_address, high_address; }; /*! @returns the range of addresses that the video might read from. */ Range get_memory_access_range(); private: DeferredQueue deferrer_; Outputs::CRT::CRT crt_; RangeObserver *range_observer_ = nullptr; uint16_t raw_palette_[16]; uint16_t palette_[16]; int base_address_ = 0; int previous_base_address_ = 0; int current_address_ = 0; uint16_t *ram_ = nullptr; int x_ = 0, y_ = 0, next_y_ = 0; bool load_ = false; int load_base_ = 0; uint16_t video_mode_ = 0; uint16_t sync_mode_ = 0; FieldFrequency field_frequency_ = FieldFrequency::Fifty; enum class OutputBpp { One, Two, Four } output_bpp_ = OutputBpp::Four; void update_output_mode(); struct HorizontalState { bool enable = false; bool blank = false; bool sync = false; } horizontal_; struct VerticalState { bool enable = false; bool blank = false; enum class SyncSchedule { /// No sync events this line. None, /// Sync should begin during this horizontal line. Begin, /// Sync should end during this horizontal line. End, } sync_schedule = SyncSchedule::None; bool sync = false; } vertical_, next_vertical_; LineLength line_length_; int data_latch_position_ = 0; int data_latch_read_position_ = 0; uint16_t data_latch_[128]; void push_latched_data(); void reset_fifo(); /*! Provides a target for control over the output video stream, which is considered to be a permanently shifting shifter, that you need to reload when appropriate, which can be overridden by the blank and sync levels. This stream will automatically insert a colour burst. */ class VideoStream { public: VideoStream(Outputs::CRT::CRT &crt, uint16_t *palette) : crt_(crt), palette_(palette) {} enum class OutputMode { Sync, Blank, ColourBurst, Pixels, }; /// Sets the current data format for the shifter. Changes in output BPP flush the shifter. void set_bpp(OutputBpp bpp); /// Outputs signal of type @c mode for @c duration. void output(int duration, OutputMode mode); /// Warns the video stream that the border colour, included in the palette that it holds a pointer to, /// will change momentarily. This should be called after the relevant @c output() updates, and /// is used to help elide border-regio output. void will_change_border_colour(); /// Loads 64 bits into the Shifter. The shifter shifts continuously. If you also declare /// a pixels region then whatever is being shifted will reach the display, in a form that /// depends on the current output BPP. void load(uint64_t value); private: // The target CRT and the palette to use. Outputs::CRT::CRT &crt_; uint16_t *palette_ = nullptr; // Internal stateful processes. void generate(int duration, OutputMode mode, bool is_terminal); void flush_border(); void flush_pixels(); void shift(int duration); void output_pixels(int duration); // Internal state that is a function of output intent. int duration_ = 0; OutputMode output_mode_ = OutputMode::Sync; OutputBpp bpp_ = OutputBpp::Four; union { uint64_t output_shifter_; uint32_t shifter_halves_[2]; }; // Internal state for handling output serialisation. uint16_t *pixel_buffer_ = nullptr; int pixel_pointer_ = 0; } video_stream_; /// Contains copies of the various observeable fields, after the relevant propagation delay. struct PublicState { bool display_enable = false; bool hsync = false; bool vsync = false; } public_state_; friend class ::VideoTester; }; } } #endif /* Atari_ST_Video_hpp */