mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-11 08:30:55 +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:
parent
59b9e39022
commit
ede2696a77
@ -18,7 +18,8 @@ namespace AppleII {
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class Model {
|
||||
II,
|
||||
IIplus
|
||||
IIplus,
|
||||
IIe
|
||||
};
|
||||
enum class DiskController {
|
||||
None,
|
||||
|
@ -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() {}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user