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

Edges further towards implementing the IIe video subsystem.

All video-specific switches are in place, and mostly honoured, and a IIe machine configuration is advertised at least.
This commit is contained in:
Thomas Harte 2018-07-24 22:15:42 -04:00
parent 59b9e39022
commit ede2696a77
4 changed files with 256 additions and 51 deletions

View File

@ -18,7 +18,8 @@ namespace AppleII {
struct Target: public ::Analyser::Static::Target {
enum class Model {
II,
IIplus
IIplus,
IIe
};
enum class DiskController {
None,

View File

@ -43,7 +43,7 @@ std::vector<std::unique_ptr<Configurable::Option>> AppleII::get_options() {
namespace {
class ConcreteMachine:
template <bool is_iie> class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
@ -494,9 +494,36 @@ class ConcreteMachine:
*value |= 0x80;
}
} break;
// The IIe-only state reads follow...
case 0xc018: if(is_iie) *value = (*value & 0x7f) | video_->get_80_store() ? 0x80 : 0x00; break;
case 0xc01a: if(is_iie) *value = (*value & 0x7f) | video_->get_text() ? 0x80 : 0x00; break;
case 0xc01b: if(is_iie) *value = (*value & 0x7f) | video_->get_mixed() ? 0x80 : 0x00; break;
case 0xc01c: if(is_iie) *value = (*value & 0x7f) | video_->get_page2() ? 0x80 : 0x00; break;
case 0xc01d: if(is_iie) *value = (*value & 0x7f) | video_->get_high_resolution() ? 0x80 : 0x00; break;
case 0xc01e: if(is_iie) *value = (*value & 0x7f) | video_->get_alternative_character_set() ? 0x80 : 0x00; break;
case 0xc01f: if(is_iie) *value = (*value & 0x7f) | video_->get_80_columns() ? 0x80 : 0x00; break;
case 0xc07f: if(is_iie) *value = (*value & 0x7f) | video_->get_double_high_resolution() ? 0x80 : 0x00; break;
}
} else {
// Write-only switches.
// Write-only switches. All IIe as currently implemented.
if(is_iie) {
switch(address) {
default: break;
case 0xc00e:
case 0xc00f: video_->set_alternative_character_set(!!(address&1)); break;
case 0xc00c:
case 0xc00d: video_->set_80_columns(!!(address&1)); break;
case 0xc000:
case 0xc001: video_->set_80_store(!!(address&1)); break;
case 0xc05e:
case 0xc05f: video_->set_double_high_resolution(!(address&1)); break;
}
}
}
break;
@ -513,14 +540,14 @@ class ConcreteMachine:
} break;
/* Read-write switches. */
case 0xc050: update_video(); video_->set_graphics_mode(); break;
case 0xc051: update_video(); video_->set_text_mode(); break;
case 0xc052: update_video(); video_->set_mixed_mode(false); break;
case 0xc053: update_video(); video_->set_mixed_mode(true); break;
case 0xc054: update_video(); video_->set_video_page(0); break;
case 0xc055: update_video(); video_->set_video_page(1); break;
case 0xc056: update_video(); video_->set_low_resolution(); break;
case 0xc057: update_video(); video_->set_high_resolution(); break;
case 0xc050: update_video(); video_->set_text(false); break;
case 0xc051: update_video(); video_->set_text(true); break;
case 0xc052: update_video(); video_->set_mixed(false); break;
case 0xc053: update_video(); video_->set_mixed(true); break;
case 0xc054: update_video(); video_->set_page2(false); break;
case 0xc055: update_video(); video_->set_page2(true); break;
case 0xc056: update_video(); video_->set_high_resolution(false); break;
case 0xc057: update_video(); video_->set_high_resolution(true); break;
case 0xc010:
keyboard_input_ &= 0x7f;
@ -646,9 +673,11 @@ class ConcreteMachine:
// If no ASCII value is supplied, look for a few special cases.
if(!value) {
switch(key) {
case Key::Left: value = 8; break;
case Key::Right: value = 21; break;
case Key::Down: value = 10; break;
case Key::Left: value = 0x08; break;
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::BackSpace: value = 0x7f; break;
default: break;
}
}
@ -718,7 +747,11 @@ using namespace AppleII;
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::AppleII::Target;
const Target *const appleii_target = dynamic_cast<const Target *>(target);
return new ConcreteMachine(*appleii_target, rom_fetcher);
if(appleii_target->model == Target::Model::IIe) {
return new ConcreteMachine<true>(*appleii_target, rom_fetcher);
} else {
return new ConcreteMachine<false>(*appleii_target, rom_fetcher);
}
}
Machine::~Machine() {}

View File

@ -31,28 +31,71 @@ Outputs::CRT::CRT *VideoBase::get_crt() {
return crt_.get();
}
void VideoBase::set_graphics_mode() {
use_graphics_mode_ = true;
/*
Rote setters and getters.
*/
void VideoBase::set_alternative_character_set(bool alternative_character_set) {
alternative_character_set_ = alternative_character_set;
}
void VideoBase::set_text_mode() {
use_graphics_mode_ = false;
bool VideoBase::get_alternative_character_set() {
return alternative_character_set_;
}
void VideoBase::set_mixed_mode(bool mixed_mode) {
mixed_mode_ = mixed_mode;
void VideoBase::set_80_columns(bool columns_80) {
columns_80_ = columns_80;
}
void VideoBase::set_video_page(int page) {
video_page_ = page;
bool VideoBase::get_80_columns() {
return columns_80_;
}
void VideoBase::set_low_resolution() {
graphics_mode_ = GraphicsMode::LowRes;
void VideoBase::set_80_store(bool store_80) {
store_80_ = store_80;
}
void VideoBase::set_high_resolution() {
graphics_mode_ = GraphicsMode::HighRes;
bool VideoBase::get_80_store() {
return store_80_;
}
void VideoBase::set_page2(bool page2) {
page2_ = page2;;
}
bool VideoBase::get_page2() {
return page2_;
}
void VideoBase::set_text(bool text) {
text_ = text;
}
bool VideoBase::get_text() {
return text_;
}
void VideoBase::set_mixed(bool mixed) {
mixed_ = mixed;
}
bool VideoBase::get_mixed() {
return mixed_;
}
void VideoBase::set_high_resolution(bool high_resolution) {
high_resolution_ = high_resolution;
}
bool VideoBase::get_high_resolution() {
return high_resolution_;
}
void VideoBase::set_double_high_resolution(bool double_high_resolution) {
double_high_resolution_ = double_high_resolution;
}
bool VideoBase::get_double_high_resolution() {
return double_high_resolution_;
}
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {

View File

@ -43,13 +43,107 @@ class VideoBase {
/// @returns The CRT this video feed is feeding.
Outputs::CRT::CRT *get_crt();
// Inputs for the various soft switches.
void set_graphics_mode();
void set_text_mode();
void set_mixed_mode(bool);
void set_video_page(int);
void set_low_resolution();
void set_high_resolution();
/*
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 DHIRES ($C05E/$C05F; triggers on write only).
* On: turn on double-high resolution.
* Off: turn off double-high resolution.
DHIRES doesn't exist on a II/II+. On the IIe there is another
register usually grouped with the graphics setters called IOUDIS
that affects visibility of this switch. But it has no effect on
video, so it's not modelled by this class.
*/
void set_double_high_resolution(bool);
bool get_double_high_resolution();
// Setup for text mode.
void set_character_rom(const std::vector<uint8_t> &);
@ -57,14 +151,15 @@ class VideoBase {
protected:
std::unique_ptr<Outputs::CRT::CRT> crt_;
// State affecting output video stream generation.
uint8_t *pixel_pointer_ = nullptr;
int pixel_pointer_column_ = 0;
bool pixels_are_high_density_ = false;
int video_page_ = 0;
// State affecting logical state.
int row_ = 0, column_ = 0, flash_ = 0;
std::vector<uint8_t> character_rom_;
// An
enum class GraphicsMode {
LowRes,
DoubleLowRes,
@ -72,10 +167,28 @@ class VideoBase {
DoubleHighRes,
Text,
DoubleText
} graphics_mode_ = GraphicsMode::LowRes;
bool use_graphics_mode_ = false;
bool mixed_mode_ = false;
};
bool is_text_mode(GraphicsMode m) { return m >= GraphicsMode::Text; }
// Various soft-switch values.
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 double_high_resolution_ = false;
// Graphics carry is the final level output in a fetch window;
// it carries on into the next if it's high resolution with
// the delay bit set.
uint8_t graphics_carry_ = 0;
// This holds a copy of the character ROM. The regular character
// set is assumed to be in the first 64*8 bytes; the alternative
// is in the 128*8 bytes after that.
std::vector<uint8_t> character_rom_;
};
template <class BusHandler> class Video: public VideoBase {
@ -123,15 +236,14 @@ template <class BusHandler> class Video: public VideoBase {
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14);
}
} else {
const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text;
const GraphicsMode line_mode = graphics_mode(row_);
// The first 40 columns are submitted to the CRT only upon completion;
// they'll be either graphics or blank, depending on which side we are
// of line 192.
if(column_ < 40) {
if(row_ < 192) {
GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
bool requires_high_density = pixel_mode != GraphicsMode::Text;
const bool requires_high_density = line_mode != GraphicsMode::Text;
if(!column_ || requires_high_density != pixels_are_high_density_) {
if(column_) output_data_to_column(column_);
pixel_pointer_ = crt_->allocate_write_area(561);
@ -144,9 +256,9 @@ template <class BusHandler> class Video: public VideoBase {
const int character_row = row_ >> 3;
const int pixel_row = row_ & 7;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
const uint16_t text_address = static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
switch(pixel_mode) {
switch(line_mode) {
case GraphicsMode::Text: {
const uint8_t inverses[] = {
0xff,
@ -239,7 +351,7 @@ template <class BusHandler> class Video: public VideoBase {
} break;
case GraphicsMode::HighRes: {
const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
const uint16_t graphics_address = static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
for(int c = column_; c < pixel_end; ++c) {
const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c));
@ -269,7 +381,7 @@ template <class BusHandler> class Video: public VideoBase {
} break;
case GraphicsMode::DoubleHighRes: {
const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
const uint16_t graphics_address = static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
for(int c = column_; c < pixel_end; ++c) {
const uint16_t graphic = bus_handler_.perform_aux_read(static_cast<uint16_t>(graphics_address + c));
@ -321,7 +433,7 @@ template <class BusHandler> class Video: public VideoBase {
}
int second_blank_start;
if(line_mode != GraphicsMode::Text && (!mixed_mode_ || row_ < 159 || row_ >= 192)) {
if(!is_text_mode(graphics_mode(row_+1))) {
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
if(colour_burst_end > colour_burst_start) {
@ -390,15 +502,31 @@ template <class BusHandler> class Video: public VideoBase {
}
private:
GraphicsMode graphics_mode(int row) {
if(text_) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text;
if(mixed_ && row >= 160 && row < 192) {
return (columns_80_ || double_high_resolution_) ? GraphicsMode::DoubleText : GraphicsMode::Text;
}
if(high_resolution_) {
return double_high_resolution_ ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
} else {
return double_high_resolution_ ? GraphicsMode::DoubleLowRes : 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 = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
GraphicsMode pixel_mode = ((!mixed_mode_ || row < 160) && use_graphics_mode_) ? graphics_mode_ : GraphicsMode::Text;
return (pixel_mode == GraphicsMode::HighRes) ?
static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
const GraphicsMode pixel_mode = graphics_mode(row);
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
}
static const int flash_length = 8406;