1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

Factors out the Apple II/IIe video switches and mode selection logic.

This commit is contained in:
Thomas Harte 2020-10-31 20:02:50 -04:00
parent e8943618dc
commit 1b28d929e4
4 changed files with 279 additions and 259 deletions

View File

@ -11,9 +11,9 @@
using namespace Apple::II::Video;
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
VideoSwitches<Cycles>(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<uint8_t> &character_rom) {

View File

@ -13,6 +13,8 @@
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../ClockReceiver/DeferredQueue.hpp"
#include "VideoSwitches.hpp"
#include <array>
#include <vector>
@ -33,7 +35,7 @@ class BusHandler {
}
};
class VideoBase {
class VideoBase: public VideoSwitches<Cycles> {
public:
VideoBase(bool is_iie, std::function<void(Cycles)> &&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<uint8_t> &);
@ -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<uint8_t, 40> base_stream_;
std::array<uint8_t, 40> 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<Cycles> deferrer_;
};
template <class BusHandler, bool is_iie> class Video: public VideoBase {
@ -268,13 +145,6 @@ template <class BusHandler, bool is_iie> 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 BusHandler, bool is_iie> 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 BusHandler, bool is_iie> 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 BusHandler, bool is_iie> 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_;
};

View File

@ -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 <typename TimeUnit> 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<void(TimeUnit)> &&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<TimeUnit> 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 */

View File

@ -1315,6 +1315,7 @@
4B8DF4D62546561300F3433C /* MemoryMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryMap.hpp; sourceTree = "<group>"; };
4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = IIgsMemoryMapTests.mm; sourceTree = "<group>"; };
4B8DF4ED254B840B00F3433C /* AppleClock.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleClock.hpp; sourceTree = "<group>"; };
4B8DF4F2254E141700F3433C /* VideoSwitches.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = VideoSwitches.hpp; sourceTree = "<group>"; };
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; };
4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = "<group>"; };
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 = "<group>";