From 1b28d929e4d470e7bc3b53fd77988d3d3235d4ff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 31 Oct 2020 20:02:50 -0400 Subject: [PATCH] Factors out the Apple II/IIe video switches and mode selection logic. --- Machines/Apple/AppleII/Video.cpp | 105 +------- Machines/Apple/AppleII/Video.hpp | 177 +----------- Machines/Apple/AppleII/VideoSwitches.hpp | 254 ++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 2 + 4 files changed, 279 insertions(+), 259 deletions(-) create mode 100644 Machines/Apple/AppleII/VideoSwitches.hpp diff --git a/Machines/Apple/AppleII/Video.cpp b/Machines/Apple/AppleII/Video.cpp index 887b1a3bb..3ddbbe8b5 100644 --- a/Machines/Apple/AppleII/Video.cpp +++ b/Machines/Apple/AppleII/Video.cpp @@ -11,9 +11,9 @@ using namespace Apple::II::Video; VideoBase::VideoBase(bool is_iie, std::function &&target) : + VideoSwitches(Cycles(2), std::move(target)), crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1), - is_iie_(is_iie), - deferrer_(std::move(target)) { + is_iie_(is_iie) { // Show only the centre 75% of the TV frame. crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour); @@ -59,97 +59,20 @@ Outputs::Display::DisplayType VideoBase::get_display_type() const { return crt_.get_display_type(); } -/* - Rote setters and getters. -*/ -void VideoBase::set_alternative_character_set(bool alternative_character_set) { - set_alternative_character_set_ = alternative_character_set; - deferrer_.defer(Cycles(2), [this, alternative_character_set] { - alternative_character_set_ = alternative_character_set; - if(alternative_character_set) { - character_zones[1].address_mask = 0xff; - character_zones[1].xor_mask = 0; - } else { - character_zones[1].address_mask = 0x3f; - character_zones[1].xor_mask = flash_mask(); - } - }); +void VideoBase::did_set_alternative_character_set(bool alternative_character_set) { + alternative_character_set_ = alternative_character_set; + if(alternative_character_set) { + character_zones[1].address_mask = 0xff; + character_zones[1].xor_mask = 0; + } else { + character_zones[1].address_mask = 0x3f; + character_zones[1].xor_mask = flash_mask(); + // The XOR mask is seeded here; it's dynamic, so updated elsewhere. + } } -bool VideoBase::get_alternative_character_set() { - return set_alternative_character_set_; -} - -void VideoBase::set_80_columns(bool columns_80) { - set_columns_80_ = columns_80; - deferrer_.defer(Cycles(2), [this, columns_80] { - columns_80_ = columns_80; - }); -} - -bool VideoBase::get_80_columns() { - return set_columns_80_; -} - -void VideoBase::set_80_store(bool store_80) { - set_store_80_ = store_80_ = store_80; -} - -bool VideoBase::get_80_store() { - return set_store_80_; -} - -void VideoBase::set_page2(bool page2) { - set_page2_ = page2_ = page2; -} - -bool VideoBase::get_page2() { - return set_page2_; -} - -void VideoBase::set_text(bool text) { - set_text_ = text; - deferrer_.defer(Cycles(2), [this, text] { - text_ = text; - }); -} - -bool VideoBase::get_text() { - return set_text_; -} - -void VideoBase::set_mixed(bool mixed) { - set_mixed_ = mixed; - deferrer_.defer(Cycles(2), [this, mixed] { - mixed_ = mixed; - }); -} - -bool VideoBase::get_mixed() { - return set_mixed_; -} - -void VideoBase::set_high_resolution(bool high_resolution) { - set_high_resolution_ = high_resolution; - deferrer_.defer(Cycles(2), [this, high_resolution] { - high_resolution_ = high_resolution; - }); -} - -bool VideoBase::get_high_resolution() { - return set_high_resolution_; -} - -void VideoBase::set_annunciator_3(bool annunciator_3) { - set_annunciator_3_ = annunciator_3; - deferrer_.defer(Cycles(2), [this, annunciator_3] { - annunciator_3_ = annunciator_3; - high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff; - }); -} - -bool VideoBase::get_annunciator_3() { - return set_annunciator_3_; +void VideoBase::did_set_annunciator_3(bool annunciator_3) { + high_resolution_mask_ = annunciator_3 ? 0x7f : 0xff; } void VideoBase::set_character_rom(const std::vector &character_rom) { diff --git a/Machines/Apple/AppleII/Video.hpp b/Machines/Apple/AppleII/Video.hpp index 67124fae4..fa23ff713 100644 --- a/Machines/Apple/AppleII/Video.hpp +++ b/Machines/Apple/AppleII/Video.hpp @@ -13,6 +13,8 @@ #include "../../../ClockReceiver/ClockReceiver.hpp" #include "../../../ClockReceiver/DeferredQueue.hpp" +#include "VideoSwitches.hpp" + #include #include @@ -33,7 +35,7 @@ class BusHandler { } }; -class VideoBase { +class VideoBase: public VideoSwitches { public: VideoBase(bool is_iie, std::function &&target); @@ -49,109 +51,6 @@ class VideoBase { /// Gets the type of output. Outputs::Display::DisplayType get_display_type() const; - /* - Descriptions for the setters below are taken verbatim from - the Apple IIe Technical Reference. Addresses are the conventional - locations within the Apple II memory map. Only those which affect - video output are implemented here. - - Those registers which don't exist on a II/II+ are marked. - */ - - /*! - Setter for ALTCHAR ($C00E/$C00F; triggers on write only): - - * Off: display text using primary character set. - * On: display text using alternate character set. - - Doesn't exist on a II/II+. - */ - void set_alternative_character_set(bool); - bool get_alternative_character_set(); - - /*! - Setter for 80COL ($C00C/$C00D; triggers on write only). - - * Off: display 40 columns. - * On: display 80 columns. - - Doesn't exist on a II/II+. - */ - void set_80_columns(bool); - bool get_80_columns(); - - /*! - Setter for 80STORE ($C000/$C001; triggers on write only). - - * Off: cause PAGE2 to select auxiliary RAM. - * On: cause PAGE2 to switch main RAM areas. - - Doesn't exist on a II/II+. - */ - void set_80_store(bool); - bool get_80_store(); - - /*! - Setter for PAGE2 ($C054/$C055; triggers on read or write). - - * Off: select Page 1. - * On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory. - - 80STORE doesn't exist on a II/II+; therefore this always selects - either Page 1 or Page 2 on those machines. - */ - void set_page2(bool); - bool get_page2(); - - /*! - Setter for TEXT ($C050/$C051; triggers on read or write). - - * Off: display graphics or, if MIXED on, mixed. - * On: display text. - */ - void set_text(bool); - bool get_text(); - - /*! - Setter for MIXED ($C052/$C053; triggers on read or write). - - * Off: display only text or only graphics. - * On: if TEXT off, display text and graphics. - */ - void set_mixed(bool); - bool get_mixed(); - - /*! - Setter for HIRES ($C056/$C057; triggers on read or write). - - * Off: if TEXT off, display low-resolution graphics. - * On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics. - - DHIRES doesn't exist on a II/II+; therefore this always selects - either high- or low-resolution graphics on those machines. - - Despite Apple's documentation, the IIe also supports double low-resolution - graphics, which are the 80-column analogue to ordinary low-resolution 40-column - low-resolution graphics. - */ - void set_high_resolution(bool); - bool get_high_resolution(); - - /*! - Setter for annunciator 3. - - * On: turn on annunciator 3. - * Off: turn off annunciator 3. - - This exists on both the II/II+ and the IIe, but has no effect on - video on the older machines. It's intended to be used on the IIe - to confirm double-high resolution mode but has side effects in - selecting mixed mode output and discarding high-resolution - delay bits. - */ - void set_annunciator_3(bool); - bool get_annunciator_3(); - // Setup for text mode. void set_character_rom(const std::vector &); @@ -167,28 +66,9 @@ class VideoBase { return uint8_t((flash_ / flash_length) * 0xff); } - // Enumerates all Apple II and IIe display modes. - enum class GraphicsMode { - Text = 0, - DoubleText, - HighRes, - DoubleHighRes, - LowRes, - DoubleLowRes, - FatLowRes - }; - bool is_text_mode(GraphicsMode m) { return m <= GraphicsMode::DoubleText; } - bool is_double_mode(GraphicsMode m) { return !!(int(m)&1); } - - // Various soft-switch values. - bool alternative_character_set_ = false, set_alternative_character_set_ = false; - bool columns_80_ = false, set_columns_80_ = false; - bool store_80_ = false, set_store_80_ = false; - bool page2_ = false, set_page2_ = false; - bool text_ = true, set_text_ = true; - bool mixed_ = false, set_mixed_ = false; - bool high_resolution_ = false, set_high_resolution_ = false; - bool annunciator_3_ = false, set_annunciator_3_ = false; + bool alternative_character_set_ = false; + void did_set_annunciator_3(bool) override; + void did_set_alternative_character_set(bool) override; // Graphics carry is the final level output in a fetch window; // it carries on into the next if it's high resolution with @@ -208,7 +88,7 @@ class VideoBase { std::array base_stream_; std::array auxiliary_stream_; - bool is_iie_ = false; + const bool is_iie_ = false; static constexpr int flash_length = 8406; // Describes the current text mode mapping from in-memory character index @@ -256,9 +136,6 @@ class VideoBase { clock rather than the 14M. */ void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const; - - // Maintain a DeferredQueue for delayed mode switches. - DeferredQueuePerformer deferrer_; }; template class Video: public VideoBase { @@ -268,13 +145,6 @@ template class Video: public VideoBase { VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }), bus_handler_(bus_handler) {} - /*! - Runs video for @c cycles. - */ - void run_for(Cycles cycles) { - deferrer_.run_for(cycles); - } - /*! Obtains the last value the video read prior to time now+offset. */ @@ -345,7 +215,7 @@ template class Video: public VideoBase { A frame is oriented around 65 cycles across, 262 lines down. */ - constexpr int first_sync_line = 220; // A complete guess. Information needed. + constexpr int first_sync_line = 220; // A complete guess. Information needed. constexpr int first_sync_column = 49; // Also a guess. constexpr int sync_length = 4; // One of the two likely candidates. @@ -422,7 +292,7 @@ template class Video: public VideoBase { const int pixel_end = std::min(40, ending_column); const int pixel_row = row_ & 7; - const bool is_double = Video::is_double_mode(line_mode); + const bool is_double = is_double_mode(line_mode); if(!is_double && was_double_ && pixel_pointer_) { pixel_pointer_[pixel_start*14 + 0] = pixel_pointer_[pixel_start*14 + 1] = @@ -585,35 +455,6 @@ template class Video: public VideoBase { } } - GraphicsMode graphics_mode(int row) { - if( - text_ || - (mixed_ && row >= 160 && row < 192) - ) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text; - if(high_resolution_) { - return (annunciator_3_ && columns_80_) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes; - } else { - if(columns_80_) return GraphicsMode::DoubleLowRes; - if(annunciator_3_) return GraphicsMode::FatLowRes; - return GraphicsMode::LowRes; - } - } - - int video_page() { - return (store_80_ || !page2_) ? 0 : 1; - } - - uint16_t get_row_address(int row) { - const int character_row = row >> 3; - const int pixel_row = row & 7; - const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7)); - - const GraphicsMode pixel_mode = graphics_mode(row); - return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ? - uint16_t(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : - uint16_t(((video_page()+1) * 0x400) + row_address); - } - BusHandler &bus_handler_; }; diff --git a/Machines/Apple/AppleII/VideoSwitches.hpp b/Machines/Apple/AppleII/VideoSwitches.hpp new file mode 100644 index 000000000..ff506fdbe --- /dev/null +++ b/Machines/Apple/AppleII/VideoSwitches.hpp @@ -0,0 +1,254 @@ +// +// VideoSwitches.hpp +// Clock Signal +// +// Created by Thomas Harte on 31/10/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef VideoSwitches_h +#define VideoSwitches_h + +#include "../../../ClockReceiver/ClockReceiver.hpp" +#include "../../../ClockReceiver/DeferredQueue.hpp" + +namespace Apple { +namespace II { + +// Enumerates all Apple II, IIe and IIgs display modes. +enum class GraphicsMode { + Text = 0, + DoubleText, + HighRes, + DoubleHighRes, + LowRes, + DoubleLowRes, + FatLowRes +}; +constexpr bool is_text_mode(GraphicsMode m) { return m <= GraphicsMode::DoubleText; } +constexpr bool is_double_mode(GraphicsMode m) { return int(m) & 1; } + +template class VideoSwitches { + public: + /*! + Constructs a new instance of VideoSwitches in which changes to the switch + affect the video mode only after @c delay cycles. + */ + VideoSwitches(TimeUnit delay, std::function &&target) : delay_(delay), deferrer_(std::move(target)) {} + + /*! + Advances @c cycles. + */ + void run_for(TimeUnit cycles) { + deferrer_.run_for(cycles); + } + + /* + Descriptions for the setters below are taken verbatim from + the Apple IIe Technical Reference. Addresses are the conventional + locations within the Apple II memory map. Only those which affect + video output are implemented here. + + Those registers which don't exist on a II/II+ are marked. + */ + + /*! + Setter for ALTCHAR ($C00E/$C00F; triggers on write only): + + * Off: display text using primary character set. + * On: display text using alternate character set. + + Doesn't exist on a II/II+. + */ + void set_alternative_character_set(bool alternative_character_set) { + external_.alternative_character_set = alternative_character_set; + deferrer_.defer(delay_, [this, alternative_character_set] { + internal_.alternative_character_set = alternative_character_set; + did_set_annunciator_3(alternative_character_set); + }); + } + bool get_alternative_character_set() const { + return external_.alternative_character_set; + } + + /*! + Setter for 80COL ($C00C/$C00D; triggers on write only). + + * Off: display 40 columns. + * On: display 80 columns. + + Doesn't exist on a II/II+. + */ + void set_80_columns(bool columns_80) { + external_.columns_80 = columns_80; + deferrer_.defer(delay_, [this, columns_80] { + internal_.columns_80 = columns_80; + }); + } + bool get_80_columns() const { + return external_.columns_80; + } + + /*! + Setter for 80STORE ($C000/$C001; triggers on write only). + + * Off: cause PAGE2 to select auxiliary RAM. + * On: cause PAGE2 to switch main RAM areas. + + Doesn't exist on a II/II+. + */ + void set_80_store(bool store_80) { + external_.store_80 = internal_.store_80 = store_80; + } + bool get_80_store() const { + return external_.store_80; + } + + /*! + Setter for PAGE2 ($C054/$C055; triggers on read or write). + + * Off: select Page 1. + * On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory. + + 80STORE doesn't exist on a II/II+; therefore this always selects + either Page 1 or Page 2 on those machines. + */ + void set_page2(bool page2) { + external_.page2 = internal_.page2 = page2; + } + bool get_page2() const { + return external_.page2; + } + + /*! + Setter for TEXT ($C050/$C051; triggers on read or write). + + * Off: display graphics or, if MIXED on, mixed. + * On: display text. + */ + void set_text(bool text) { + external_.text = text; + deferrer_.defer(delay_, [this, text] { + internal_.text = text; + }); + } + bool get_text() const { + return external_.text; + } + + /*! + Setter for MIXED ($C052/$C053; triggers on read or write). + + * Off: display only text or only graphics. + * On: if TEXT off, display text and graphics. + */ + void set_mixed(bool mixed) { + external_.mixed = mixed; + deferrer_.defer(delay_, [this, mixed] { + internal_.mixed = mixed; + }); + } + bool get_mixed() const { + return external_.mixed; + } + + /*! + Setter for HIRES ($C056/$C057; triggers on read or write). + + * Off: if TEXT off, display low-resolution graphics. + * On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics. + + DHIRES doesn't exist on a II/II+; therefore this always selects + either high- or low-resolution graphics on those machines. + + Despite Apple's documentation, the IIe also supports double low-resolution + graphics, which are the 80-column analogue to ordinary low-resolution 40-column + low-resolution graphics. + */ + void set_high_resolution(bool high_resolution) { + external_.high_resolution = high_resolution; + deferrer_.defer(delay_, [this, high_resolution] { + internal_.high_resolution = high_resolution; + }); + } + bool get_high_resolution() const { + return external_.high_resolution; + } + + /*! + Setter for annunciator 3. + + * On: turn on annunciator 3. + * Off: turn off annunciator 3. + + This exists on both the II/II+ and the IIe, but has no effect on + video on the older machines. It's intended to be used on the IIe + to confirm double-high resolution mode but has side effects in + selecting mixed mode output and discarding high-resolution + delay bits. + */ + void set_annunciator_3(bool annunciator_3) { + external_.annunciator_3 = annunciator_3; + deferrer_.defer(delay_, [this, annunciator_3] { + internal_.annunciator_3 = annunciator_3; + did_set_alternative_character_set(annunciator_3); + }); + } + bool get_annunciator_3() const { + return external_.annunciator_3; + } + + protected: + GraphicsMode graphics_mode(int row) const { + if( + internal_.text || + (internal_.mixed && row >= 160 && row < 192) + ) return internal_.columns_80 ? GraphicsMode::DoubleText : GraphicsMode::Text; + if(internal_.high_resolution) { + return (internal_.annunciator_3 && internal_.columns_80) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes; + } else { + if(internal_.columns_80) return GraphicsMode::DoubleLowRes; + if(internal_.annunciator_3) return GraphicsMode::FatLowRes; + return GraphicsMode::LowRes; + } + } + + int video_page() const { + return (internal_.store_80 || !internal_.page2) ? 0 : 1; + } + + uint16_t get_row_address(int row) const { + const int character_row = row >> 3; + const int pixel_row = row & 7; + const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7)); + + const GraphicsMode pixel_mode = graphics_mode(row); + return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ? + uint16_t(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) : + uint16_t(((video_page()+1) * 0x400) + row_address); + } + + virtual void did_set_annunciator_3(bool) = 0; + virtual void did_set_alternative_character_set(bool) = 0; + + private: + // Maintain a DeferredQueue for delayed mode switches. + const TimeUnit delay_; + DeferredQueuePerformer deferrer_; + + struct Switches { + bool alternative_character_set = false; + bool columns_80 = false; + bool store_80 = false; + bool page2 = false; + bool text = true; + bool mixed = false; + bool high_resolution = false; + bool annunciator_3 = false; + } external_, internal_; +}; + +} +} + +#endif /* VideoSwitches_h */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 9fc455db9..d29a0661b 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1315,6 +1315,7 @@ 4B8DF4D62546561300F3433C /* MemoryMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryMap.hpp; sourceTree = ""; }; 4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = IIgsMemoryMapTests.mm; sourceTree = ""; }; 4B8DF4ED254B840B00F3433C /* AppleClock.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleClock.hpp; sourceTree = ""; }; + 4B8DF4F2254E141700F3433C /* VideoSwitches.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = VideoSwitches.hpp; sourceTree = ""; }; 4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = ""; }; 4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = ""; }; 4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; }; @@ -3761,6 +3762,7 @@ 4BCE004C227CE8CA000CA200 /* DiskIICard.hpp */, 4BF40A5525424C770033EA39 /* LanguageCardSwitches.hpp */, 4BCE004F227CE8CA000CA200 /* Video.hpp */, + 4B8DF4F2254E141700F3433C /* VideoSwitches.hpp */, ); path = AppleII; sourceTree = "";