mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-25 16:31:42 +00:00
Factors out the Apple II/IIe video switches and mode selection logic.
This commit is contained in:
parent
e8943618dc
commit
1b28d929e4
@ -11,9 +11,9 @@
|
|||||||
using namespace Apple::II::Video;
|
using namespace Apple::II::Video;
|
||||||
|
|
||||||
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
|
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),
|
crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
|
||||||
is_iie_(is_iie),
|
is_iie_(is_iie) {
|
||||||
deferrer_(std::move(target)) {
|
|
||||||
|
|
||||||
// Show only the centre 75% of the TV frame.
|
// Show only the centre 75% of the TV frame.
|
||||||
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
|
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();
|
return crt_.get_display_type();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
void VideoBase::did_set_alternative_character_set(bool alternative_character_set) {
|
||||||
Rote setters and getters.
|
alternative_character_set_ = alternative_character_set;
|
||||||
*/
|
if(alternative_character_set) {
|
||||||
void VideoBase::set_alternative_character_set(bool alternative_character_set) {
|
character_zones[1].address_mask = 0xff;
|
||||||
set_alternative_character_set_ = alternative_character_set;
|
character_zones[1].xor_mask = 0;
|
||||||
deferrer_.defer(Cycles(2), [this, alternative_character_set] {
|
} else {
|
||||||
alternative_character_set_ = alternative_character_set;
|
character_zones[1].address_mask = 0x3f;
|
||||||
if(alternative_character_set) {
|
character_zones[1].xor_mask = flash_mask();
|
||||||
character_zones[1].address_mask = 0xff;
|
// The XOR mask is seeded here; it's dynamic, so updated elsewhere.
|
||||||
character_zones[1].xor_mask = 0;
|
}
|
||||||
} else {
|
|
||||||
character_zones[1].address_mask = 0x3f;
|
|
||||||
character_zones[1].xor_mask = flash_mask();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoBase::get_alternative_character_set() {
|
void VideoBase::did_set_annunciator_3(bool annunciator_3) {
|
||||||
return set_alternative_character_set_;
|
high_resolution_mask_ = annunciator_3 ? 0x7f : 0xff;
|
||||||
}
|
|
||||||
|
|
||||||
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::set_character_rom(const std::vector<uint8_t> &character_rom) {
|
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||||
#include "../../../ClockReceiver/DeferredQueue.hpp"
|
#include "../../../ClockReceiver/DeferredQueue.hpp"
|
||||||
|
|
||||||
|
#include "VideoSwitches.hpp"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -33,7 +35,7 @@ class BusHandler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class VideoBase {
|
class VideoBase: public VideoSwitches<Cycles> {
|
||||||
public:
|
public:
|
||||||
VideoBase(bool is_iie, std::function<void(Cycles)> &&target);
|
VideoBase(bool is_iie, std::function<void(Cycles)> &&target);
|
||||||
|
|
||||||
@ -49,109 +51,6 @@ class VideoBase {
|
|||||||
/// Gets the type of output.
|
/// Gets the type of output.
|
||||||
Outputs::Display::DisplayType get_display_type() const;
|
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.
|
// Setup for text mode.
|
||||||
void set_character_rom(const std::vector<uint8_t> &);
|
void set_character_rom(const std::vector<uint8_t> &);
|
||||||
|
|
||||||
@ -167,28 +66,9 @@ class VideoBase {
|
|||||||
return uint8_t((flash_ / flash_length) * 0xff);
|
return uint8_t((flash_ / flash_length) * 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enumerates all Apple II and IIe display modes.
|
bool alternative_character_set_ = false;
|
||||||
enum class GraphicsMode {
|
void did_set_annunciator_3(bool) override;
|
||||||
Text = 0,
|
void did_set_alternative_character_set(bool) override;
|
||||||
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;
|
|
||||||
|
|
||||||
// Graphics carry is the final level output in a fetch window;
|
// Graphics carry is the final level output in a fetch window;
|
||||||
// it carries on into the next if it's high resolution with
|
// 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> base_stream_;
|
||||||
std::array<uint8_t, 40> auxiliary_stream_;
|
std::array<uint8_t, 40> auxiliary_stream_;
|
||||||
|
|
||||||
bool is_iie_ = false;
|
const bool is_iie_ = false;
|
||||||
static constexpr int flash_length = 8406;
|
static constexpr int flash_length = 8406;
|
||||||
|
|
||||||
// Describes the current text mode mapping from in-memory character index
|
// Describes the current text mode mapping from in-memory character index
|
||||||
@ -256,9 +136,6 @@ class VideoBase {
|
|||||||
clock rather than the 14M.
|
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;
|
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 {
|
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); }),
|
VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }),
|
||||||
bus_handler_(bus_handler) {}
|
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.
|
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.
|
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 first_sync_column = 49; // Also a guess.
|
||||||
constexpr int sync_length = 4; // One of the two likely candidates.
|
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_end = std::min(40, ending_column);
|
||||||
const int pixel_row = row_ & 7;
|
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_) {
|
if(!is_double && was_double_ && pixel_pointer_) {
|
||||||
pixel_pointer_[pixel_start*14 + 0] =
|
pixel_pointer_[pixel_start*14 + 0] =
|
||||||
pixel_pointer_[pixel_start*14 + 1] =
|
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_;
|
BusHandler &bus_handler_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
254
Machines/Apple/AppleII/VideoSwitches.hpp
Normal file
254
Machines/Apple/AppleII/VideoSwitches.hpp
Normal 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 */
|
@ -1315,6 +1315,7 @@
|
|||||||
4B8DF4D62546561300F3433C /* MemoryMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryMap.hpp; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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 */,
|
4BCE004C227CE8CA000CA200 /* DiskIICard.hpp */,
|
||||||
4BF40A5525424C770033EA39 /* LanguageCardSwitches.hpp */,
|
4BF40A5525424C770033EA39 /* LanguageCardSwitches.hpp */,
|
||||||
4BCE004F227CE8CA000CA200 /* Video.hpp */,
|
4BCE004F227CE8CA000CA200 /* Video.hpp */,
|
||||||
|
4B8DF4F2254E141700F3433C /* VideoSwitches.hpp */,
|
||||||
);
|
);
|
||||||
path = AppleII;
|
path = AppleII;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
Loading…
Reference in New Issue
Block a user