mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-18 16:30:29 +00:00
Adjust more dangling indentation changes.
This commit is contained in:
parent
3d2eefc7e7
commit
ce5aae3f7d
@ -70,7 +70,7 @@ private:
|
||||
uint8_t track_;
|
||||
std::shared_ptr<Sector> sector_cache_[65536];
|
||||
|
||||
void process_input_bit(const int value) {
|
||||
void process_input_bit(const int value) override {
|
||||
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
|
||||
bit_count_++;
|
||||
}
|
||||
@ -107,7 +107,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void process_index_hole() {
|
||||
void process_index_hole() override {
|
||||
index_count_++;
|
||||
}
|
||||
|
||||
|
@ -24,261 +24,261 @@ namespace Apple::II {
|
||||
Implementation observation: as implemented on the IIe, the zero page setting also affects what happens in the language card area.
|
||||
*/
|
||||
template <typename Machine> class AuxiliaryMemorySwitches {
|
||||
public:
|
||||
static constexpr bool Auxiliary = true;
|
||||
static constexpr bool Main = false;
|
||||
static constexpr bool ROM = true;
|
||||
static constexpr bool Card = false;
|
||||
public:
|
||||
static constexpr bool Auxiliary = true;
|
||||
static constexpr bool Main = false;
|
||||
static constexpr bool ROM = true;
|
||||
static constexpr bool Card = false;
|
||||
|
||||
/// Describes banking state between $0200 and $BFFF.
|
||||
struct MainState {
|
||||
struct Region {
|
||||
/// @c true indicates auxiliary memory should be read from; @c false indicates main.
|
||||
bool read = false;
|
||||
/// @c true indicates auxiliary memory should be written to; @c false indicates main.
|
||||
bool write = false;
|
||||
};
|
||||
|
||||
/// Describes banking state in the ranges $0200–$03FF, $0800–$1FFF and $4000–$BFFF.
|
||||
Region base;
|
||||
/// Describes banking state in the range $0400–$07FF.
|
||||
Region region_04_08;
|
||||
/// Describes banking state in the range $2000–$3FFF.
|
||||
Region region_20_40;
|
||||
|
||||
bool operator != (const MainState &rhs) const {
|
||||
return
|
||||
base.read != rhs.base.read || base.write != rhs.base.write ||
|
||||
region_04_08.read != rhs.region_04_08.read || region_04_08.write != rhs.region_04_08.write ||
|
||||
region_20_40.read != rhs.region_20_40.read || region_20_40.write != rhs.region_20_40.write;
|
||||
}
|
||||
/// Describes banking state between $0200 and $BFFF.
|
||||
struct MainState {
|
||||
struct Region {
|
||||
/// @c true indicates auxiliary memory should be read from; @c false indicates main.
|
||||
bool read = false;
|
||||
/// @c true indicates auxiliary memory should be written to; @c false indicates main.
|
||||
bool write = false;
|
||||
};
|
||||
|
||||
/// Describes banking state between $C100 and $Cfff.
|
||||
struct CardState {
|
||||
/// @c true indicates that the built-in ROM should appear from $C100 to $C2FF @c false indicates that cards should service those accesses.
|
||||
bool region_C1_C3 = false;
|
||||
/// @c true indicates that the built-in ROM should appear from $C300 to $C3FF; @c false indicates that cards should service those accesses.
|
||||
bool region_C3 = false;
|
||||
/// @c true indicates that the built-in ROM should appear from $C400 to $C7FF; @c false indicates that cards should service those accesses.
|
||||
bool region_C4_C8 = false;
|
||||
/// @c true indicates that the built-in ROM should appear from $C800 to $CFFF; @c false indicates that cards should service those accesses.
|
||||
bool region_C8_D0 = false;
|
||||
/// Describes banking state in the ranges $0200–$03FF, $0800–$1FFF and $4000–$BFFF.
|
||||
Region base;
|
||||
/// Describes banking state in the range $0400–$07FF.
|
||||
Region region_04_08;
|
||||
/// Describes banking state in the range $2000–$3FFF.
|
||||
Region region_20_40;
|
||||
|
||||
bool operator != (const CardState &rhs) const {
|
||||
return
|
||||
region_C1_C3 != rhs.region_C1_C3 ||
|
||||
region_C3 != rhs.region_C3 ||
|
||||
region_C4_C8 != rhs.region_C4_C8 ||
|
||||
region_C8_D0 != rhs.region_C8_D0;
|
||||
}
|
||||
};
|
||||
|
||||
/// Descibes banking state between $0000 and $01ff; @c true indicates that auxiliary memory should be used; @c false indicates main memory.
|
||||
using ZeroState = bool;
|
||||
|
||||
/// Returns raw switch state for all switches that affect banking, even if they're logically video switches.
|
||||
struct SwitchState {
|
||||
bool read_auxiliary_memory = false;
|
||||
bool write_auxiliary_memory = false;
|
||||
|
||||
bool internal_CX_rom = false;
|
||||
bool slot_C3_rom = false;
|
||||
bool internal_C8_rom = false;
|
||||
|
||||
bool store_80 = false;
|
||||
bool alternative_zero_page = false;
|
||||
bool video_page_2 = false;
|
||||
bool high_resolution = false;
|
||||
|
||||
void reset() {
|
||||
*this = SwitchState();
|
||||
}
|
||||
};
|
||||
|
||||
AuxiliaryMemorySwitches(Machine &machine) : machine_(machine) {}
|
||||
|
||||
/// Used by an owner to forward, at least, any access in the range $C000 to $C00B,
|
||||
/// in $C054 to $C058, or in the range $C300 to $CFFF. Safe to call for any [16-bit] address.
|
||||
void access(uint16_t address, bool is_read) {
|
||||
if(address >= 0xc300 && address < 0xd000) {
|
||||
switches_.internal_C8_rom |= ((address >> 8) == 0xc3) && !switches_.slot_C3_rom;
|
||||
switches_.internal_C8_rom &= (address != 0xcfff);
|
||||
set_card_paging();
|
||||
return;
|
||||
}
|
||||
|
||||
if(address < 0xc000 || address >= 0xc058) return;
|
||||
|
||||
switch(address) {
|
||||
default: break;
|
||||
|
||||
case 0xc000: case 0xc001:
|
||||
if(!is_read) {
|
||||
switches_.store_80 = address & 1;
|
||||
set_main_paging();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc002: case 0xc003:
|
||||
if(!is_read) {
|
||||
switches_.read_auxiliary_memory = address & 1;
|
||||
set_main_paging();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc004: case 0xc005:
|
||||
if(!is_read) {
|
||||
switches_.write_auxiliary_memory = address & 1;
|
||||
set_main_paging();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc006: case 0xc007:
|
||||
if(!is_read) {
|
||||
switches_.internal_CX_rom = address & 1;
|
||||
set_card_paging();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc008: case 0xc009: {
|
||||
const bool alternative_zero_page = address & 1;
|
||||
if(!is_read && switches_.alternative_zero_page != alternative_zero_page) {
|
||||
switches_.alternative_zero_page = alternative_zero_page;
|
||||
set_zero_page_paging();
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0xc00a: case 0xc00b:
|
||||
if(!is_read) {
|
||||
switches_.slot_C3_rom = address & 1;
|
||||
set_card_paging();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc054: case 0xc055:
|
||||
switches_.video_page_2 = address & 1;
|
||||
set_main_paging();
|
||||
break;
|
||||
|
||||
case 0xc056: case 0xc057:
|
||||
switches_.high_resolution = address & 1;
|
||||
set_main_paging();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides part of the IIgs interface.
|
||||
void set_state(uint8_t value) {
|
||||
// b7: 1 => use auxiliary memory for zero page; 0 => use main. [I think the Hardware Reference gets this the wrong way around]
|
||||
// b6: 1 => text page 2 is selected; 0 => text page 1.
|
||||
// b5: 1 => auxiliary RAM bank is selected for reads; 0 => main.
|
||||
// b4: 1 => auxiliary RAM bank is selected for writes; 0 => main.
|
||||
// b0: 1 => the internal ROM is selected for C800+; 0 => card ROM.
|
||||
switches_.alternative_zero_page = value & 0x80;
|
||||
switches_.video_page_2 = value & 0x40;
|
||||
switches_.read_auxiliary_memory = value & 0x20;
|
||||
switches_.write_auxiliary_memory = value & 0x10;
|
||||
switches_.internal_CX_rom = value & 0x01;
|
||||
|
||||
set_main_paging();
|
||||
set_zero_page_paging();
|
||||
set_card_paging();
|
||||
}
|
||||
|
||||
uint8_t get_state() const {
|
||||
bool operator != (const MainState &rhs) const {
|
||||
return
|
||||
(switches_.alternative_zero_page ? 0x80 : 0x00) |
|
||||
(switches_.video_page_2 ? 0x40 : 0x00) |
|
||||
(switches_.read_auxiliary_memory ? 0x20 : 0x00) |
|
||||
(switches_.write_auxiliary_memory ? 0x10 : 0x00) |
|
||||
(switches_.internal_CX_rom ? 0x01 : 0x00);
|
||||
base.read != rhs.base.read || base.write != rhs.base.write ||
|
||||
region_04_08.read != rhs.region_04_08.read || region_04_08.write != rhs.region_04_08.write ||
|
||||
region_20_40.read != rhs.region_20_40.read || region_20_40.write != rhs.region_20_40.write;
|
||||
}
|
||||
};
|
||||
|
||||
const MainState &main_state() const {
|
||||
return main_state_;
|
||||
}
|
||||
/// Describes banking state between $C100 and $Cfff.
|
||||
struct CardState {
|
||||
/// @c true indicates that the built-in ROM should appear from $C100 to $C2FF @c false indicates that cards should service those accesses.
|
||||
bool region_C1_C3 = false;
|
||||
/// @c true indicates that the built-in ROM should appear from $C300 to $C3FF; @c false indicates that cards should service those accesses.
|
||||
bool region_C3 = false;
|
||||
/// @c true indicates that the built-in ROM should appear from $C400 to $C7FF; @c false indicates that cards should service those accesses.
|
||||
bool region_C4_C8 = false;
|
||||
/// @c true indicates that the built-in ROM should appear from $C800 to $CFFF; @c false indicates that cards should service those accesses.
|
||||
bool region_C8_D0 = false;
|
||||
|
||||
const CardState &card_state() const {
|
||||
return card_state_;
|
||||
bool operator != (const CardState &rhs) const {
|
||||
return
|
||||
region_C1_C3 != rhs.region_C1_C3 ||
|
||||
region_C3 != rhs.region_C3 ||
|
||||
region_C4_C8 != rhs.region_C4_C8 ||
|
||||
region_C8_D0 != rhs.region_C8_D0;
|
||||
}
|
||||
};
|
||||
|
||||
/// @returns @c true if the alternative zero page should be used; @c false otherwise.
|
||||
ZeroState zero_state() const {
|
||||
return switches_.alternative_zero_page;
|
||||
}
|
||||
/// Descibes banking state between $0000 and $01ff; @c true indicates that auxiliary memory should be used; @c false indicates main memory.
|
||||
using ZeroState = bool;
|
||||
|
||||
const SwitchState switches() const {
|
||||
return switches_;
|
||||
}
|
||||
/// Returns raw switch state for all switches that affect banking, even if they're logically video switches.
|
||||
struct SwitchState {
|
||||
bool read_auxiliary_memory = false;
|
||||
bool write_auxiliary_memory = false;
|
||||
|
||||
bool internal_CX_rom = false;
|
||||
bool slot_C3_rom = false;
|
||||
bool internal_C8_rom = false;
|
||||
|
||||
bool store_80 = false;
|
||||
bool alternative_zero_page = false;
|
||||
bool video_page_2 = false;
|
||||
bool high_resolution = false;
|
||||
|
||||
void reset() {
|
||||
switches_.reset();
|
||||
*this = SwitchState();
|
||||
}
|
||||
};
|
||||
|
||||
set_main_paging();
|
||||
set_zero_page_paging();
|
||||
AuxiliaryMemorySwitches(Machine &machine) : machine_(machine) {}
|
||||
|
||||
/// Used by an owner to forward, at least, any access in the range $C000 to $C00B,
|
||||
/// in $C054 to $C058, or in the range $C300 to $CFFF. Safe to call for any [16-bit] address.
|
||||
void access(uint16_t address, bool is_read) {
|
||||
if(address >= 0xc300 && address < 0xd000) {
|
||||
switches_.internal_C8_rom |= ((address >> 8) == 0xc3) && !switches_.slot_C3_rom;
|
||||
switches_.internal_C8_rom &= (address != 0xcfff);
|
||||
set_card_paging();
|
||||
return;
|
||||
}
|
||||
|
||||
private:
|
||||
Machine &machine_;
|
||||
SwitchState switches_;
|
||||
if(address < 0xc000 || address >= 0xc058) return;
|
||||
|
||||
MainState main_state_;
|
||||
void set_main_paging() {
|
||||
const auto previous_state = main_state_;
|
||||
switch(address) {
|
||||
default: break;
|
||||
|
||||
// The two appropriately named switches provide the base case.
|
||||
main_state_.base.read = switches_.read_auxiliary_memory;
|
||||
main_state_.base.write = switches_.write_auxiliary_memory;
|
||||
|
||||
if(switches_.store_80) {
|
||||
// If store 80 is set, use the page 2 flag for the lower carve out;
|
||||
// if both store 80 and high resolution are set, use the page 2 flag for both carve outs.
|
||||
main_state_.region_04_08.read = main_state_.region_04_08.write = switches_.video_page_2;
|
||||
|
||||
if(switches_.high_resolution) {
|
||||
main_state_.region_20_40.read = main_state_.region_20_40.write = switches_.video_page_2;
|
||||
} else {
|
||||
main_state_.region_20_40 = main_state_.base;
|
||||
case 0xc000: case 0xc001:
|
||||
if(!is_read) {
|
||||
switches_.store_80 = address & 1;
|
||||
set_main_paging();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc002: case 0xc003:
|
||||
if(!is_read) {
|
||||
switches_.read_auxiliary_memory = address & 1;
|
||||
set_main_paging();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc004: case 0xc005:
|
||||
if(!is_read) {
|
||||
switches_.write_auxiliary_memory = address & 1;
|
||||
set_main_paging();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc006: case 0xc007:
|
||||
if(!is_read) {
|
||||
switches_.internal_CX_rom = address & 1;
|
||||
set_card_paging();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc008: case 0xc009: {
|
||||
const bool alternative_zero_page = address & 1;
|
||||
if(!is_read && switches_.alternative_zero_page != alternative_zero_page) {
|
||||
switches_.alternative_zero_page = alternative_zero_page;
|
||||
set_zero_page_paging();
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0xc00a: case 0xc00b:
|
||||
if(!is_read) {
|
||||
switches_.slot_C3_rom = address & 1;
|
||||
set_card_paging();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc054: case 0xc055:
|
||||
switches_.video_page_2 = address & 1;
|
||||
set_main_paging();
|
||||
break;
|
||||
|
||||
case 0xc056: case 0xc057:
|
||||
switches_.high_resolution = address & 1;
|
||||
set_main_paging();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides part of the IIgs interface.
|
||||
void set_state(uint8_t value) {
|
||||
// b7: 1 => use auxiliary memory for zero page; 0 => use main. [I think the Hardware Reference gets this the wrong way around]
|
||||
// b6: 1 => text page 2 is selected; 0 => text page 1.
|
||||
// b5: 1 => auxiliary RAM bank is selected for reads; 0 => main.
|
||||
// b4: 1 => auxiliary RAM bank is selected for writes; 0 => main.
|
||||
// b0: 1 => the internal ROM is selected for C800+; 0 => card ROM.
|
||||
switches_.alternative_zero_page = value & 0x80;
|
||||
switches_.video_page_2 = value & 0x40;
|
||||
switches_.read_auxiliary_memory = value & 0x20;
|
||||
switches_.write_auxiliary_memory = value & 0x10;
|
||||
switches_.internal_CX_rom = value & 0x01;
|
||||
|
||||
set_main_paging();
|
||||
set_zero_page_paging();
|
||||
set_card_paging();
|
||||
}
|
||||
|
||||
uint8_t get_state() const {
|
||||
return
|
||||
(switches_.alternative_zero_page ? 0x80 : 0x00) |
|
||||
(switches_.video_page_2 ? 0x40 : 0x00) |
|
||||
(switches_.read_auxiliary_memory ? 0x20 : 0x00) |
|
||||
(switches_.write_auxiliary_memory ? 0x10 : 0x00) |
|
||||
(switches_.internal_CX_rom ? 0x01 : 0x00);
|
||||
}
|
||||
|
||||
const MainState &main_state() const {
|
||||
return main_state_;
|
||||
}
|
||||
|
||||
const CardState &card_state() const {
|
||||
return card_state_;
|
||||
}
|
||||
|
||||
/// @returns @c true if the alternative zero page should be used; @c false otherwise.
|
||||
ZeroState zero_state() const {
|
||||
return switches_.alternative_zero_page;
|
||||
}
|
||||
|
||||
const SwitchState switches() const {
|
||||
return switches_;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
switches_.reset();
|
||||
|
||||
set_main_paging();
|
||||
set_zero_page_paging();
|
||||
set_card_paging();
|
||||
}
|
||||
|
||||
private:
|
||||
Machine &machine_;
|
||||
SwitchState switches_;
|
||||
|
||||
MainState main_state_;
|
||||
void set_main_paging() {
|
||||
const auto previous_state = main_state_;
|
||||
|
||||
// The two appropriately named switches provide the base case.
|
||||
main_state_.base.read = switches_.read_auxiliary_memory;
|
||||
main_state_.base.write = switches_.write_auxiliary_memory;
|
||||
|
||||
if(switches_.store_80) {
|
||||
// If store 80 is set, use the page 2 flag for the lower carve out;
|
||||
// if both store 80 and high resolution are set, use the page 2 flag for both carve outs.
|
||||
main_state_.region_04_08.read = main_state_.region_04_08.write = switches_.video_page_2;
|
||||
|
||||
if(switches_.high_resolution) {
|
||||
main_state_.region_20_40.read = main_state_.region_20_40.write = switches_.video_page_2;
|
||||
} else {
|
||||
main_state_.region_04_08 = main_state_.region_20_40 = main_state_.base;
|
||||
}
|
||||
|
||||
if(previous_state != main_state_) {
|
||||
machine_.template set_paging<PagingType::Main>();
|
||||
main_state_.region_20_40 = main_state_.base;
|
||||
}
|
||||
} else {
|
||||
main_state_.region_04_08 = main_state_.region_20_40 = main_state_.base;
|
||||
}
|
||||
|
||||
CardState card_state_;
|
||||
void set_card_paging() {
|
||||
const auto previous_state = card_state_;
|
||||
if(previous_state != main_state_) {
|
||||
machine_.template set_paging<PagingType::Main>();
|
||||
}
|
||||
}
|
||||
|
||||
// By default apply the CX switch through to $C7FF.
|
||||
card_state_.region_C1_C3 = card_state_.region_C4_C8 = switches_.internal_CX_rom;
|
||||
CardState card_state_;
|
||||
void set_card_paging() {
|
||||
const auto previous_state = card_state_;
|
||||
|
||||
// Allow the C3 region to be switched to internal ROM in isolation even if the rest of the
|
||||
// first half of the CX region is diabled, if its specific switch is also disabled.
|
||||
if(!switches_.internal_CX_rom && !switches_.slot_C3_rom) {
|
||||
card_state_.region_C3 = true;
|
||||
} else {
|
||||
card_state_.region_C3 = card_state_.region_C1_C3;
|
||||
}
|
||||
// By default apply the CX switch through to $C7FF.
|
||||
card_state_.region_C1_C3 = card_state_.region_C4_C8 = switches_.internal_CX_rom;
|
||||
|
||||
// Apply the CX switch to $C800+, but also allow the C8 switch to select that region in isolation.
|
||||
card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom;
|
||||
|
||||
if(previous_state != card_state_) {
|
||||
machine_.template set_paging<PagingType::CardArea>();
|
||||
}
|
||||
// Allow the C3 region to be switched to internal ROM in isolation even if the rest of the
|
||||
// first half of the CX region is diabled, if its specific switch is also disabled.
|
||||
if(!switches_.internal_CX_rom && !switches_.slot_C3_rom) {
|
||||
card_state_.region_C3 = true;
|
||||
} else {
|
||||
card_state_.region_C3 = card_state_.region_C1_C3;
|
||||
}
|
||||
|
||||
void set_zero_page_paging() {
|
||||
// Believe it or not, the zero page is just set or cleared by a single flag.
|
||||
// As though life were rational.
|
||||
machine_.template set_paging<PagingType::ZeroPage>();
|
||||
// Apply the CX switch to $C800+, but also allow the C8 switch to select that region in isolation.
|
||||
card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom;
|
||||
|
||||
if(previous_state != card_state_) {
|
||||
machine_.template set_paging<PagingType::CardArea>();
|
||||
}
|
||||
}
|
||||
|
||||
void set_zero_page_paging() {
|
||||
// Believe it or not, the zero page is just set or cleared by a single flag.
|
||||
// As though life were rational.
|
||||
machine_.template set_paging<PagingType::ZeroPage>();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -19,67 +19,67 @@
|
||||
namespace Apple::IIgs::ADB {
|
||||
|
||||
class GLU: public InstructionSet::M50740::PortHandler {
|
||||
public:
|
||||
GLU();
|
||||
public:
|
||||
GLU();
|
||||
|
||||
uint8_t get_keyboard_data();
|
||||
uint8_t get_mouse_data();
|
||||
uint8_t get_modifier_status();
|
||||
uint8_t get_any_key_down();
|
||||
uint8_t get_keyboard_data();
|
||||
uint8_t get_mouse_data();
|
||||
uint8_t get_modifier_status();
|
||||
uint8_t get_any_key_down();
|
||||
|
||||
uint8_t get_data();
|
||||
uint8_t get_status();
|
||||
uint8_t get_data();
|
||||
uint8_t get_status();
|
||||
|
||||
void set_command(uint8_t);
|
||||
void set_status(uint8_t);
|
||||
void clear_key_strobe();
|
||||
void set_command(uint8_t);
|
||||
void set_status(uint8_t);
|
||||
void clear_key_strobe();
|
||||
|
||||
void set_microcontroller_rom(const std::vector<uint8_t> &rom);
|
||||
void set_microcontroller_rom(const std::vector<uint8_t> &rom);
|
||||
|
||||
void run_for(Cycles cycles);
|
||||
void run_for(Cycles cycles);
|
||||
|
||||
bool get_command_button() const;
|
||||
bool get_option_button() const;
|
||||
bool get_command_button() const;
|
||||
bool get_option_button() const;
|
||||
|
||||
void set_vertical_blank(bool);
|
||||
bool get_vertical_blank() {
|
||||
return vertical_blank_;
|
||||
}
|
||||
void set_vertical_blank(bool);
|
||||
bool get_vertical_blank() {
|
||||
return vertical_blank_;
|
||||
}
|
||||
|
||||
Apple::ADB::Keyboard &keyboard() {
|
||||
return keyboard_;
|
||||
}
|
||||
Inputs::Mouse &get_mouse() {
|
||||
return mouse_;
|
||||
}
|
||||
Apple::ADB::Keyboard &keyboard() {
|
||||
return keyboard_;
|
||||
}
|
||||
Inputs::Mouse &get_mouse() {
|
||||
return mouse_;
|
||||
}
|
||||
|
||||
private:
|
||||
InstructionSet::M50740::Executor executor_;
|
||||
private:
|
||||
InstructionSet::M50740::Executor executor_;
|
||||
|
||||
void run_ports_for(Cycles) override;
|
||||
void set_port_output(int port, uint8_t value) override;
|
||||
uint8_t get_port_input(int port) override;
|
||||
void run_ports_for(Cycles) override;
|
||||
void set_port_output(int port, uint8_t value) override;
|
||||
uint8_t get_port_input(int port) override;
|
||||
|
||||
uint8_t registers_[16]{};
|
||||
uint8_t registers_[16]{};
|
||||
|
||||
uint8_t register_address_;
|
||||
uint8_t register_latch_ = 0xff;
|
||||
bool register_strobe_ = false;
|
||||
uint8_t register_address_;
|
||||
uint8_t register_latch_ = 0xff;
|
||||
bool register_strobe_ = false;
|
||||
|
||||
uint8_t status_ = 0x00;
|
||||
uint8_t status_ = 0x00;
|
||||
|
||||
Apple::ADB::Bus bus_;
|
||||
size_t controller_id_;
|
||||
Apple::ADB::Bus bus_;
|
||||
size_t controller_id_;
|
||||
|
||||
uint8_t modifier_state_ = 0;
|
||||
uint8_t modifier_state_ = 0;
|
||||
|
||||
bool vertical_blank_ = false;
|
||||
bool vertical_blank_ = false;
|
||||
|
||||
int visible_mouse_register_ = 2;
|
||||
int visible_mouse_register_ = 2;
|
||||
|
||||
// For now, attach only a keyboard and mouse.
|
||||
Apple::ADB::Mouse mouse_;
|
||||
Apple::ADB::Keyboard keyboard_;
|
||||
// For now, attach only a keyboard and mouse.
|
||||
Apple::ADB::Mouse mouse_;
|
||||
Apple::ADB::Keyboard keyboard_;
|
||||
};
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -20,144 +20,144 @@
|
||||
namespace Apple::IIgs {
|
||||
|
||||
class MemoryMap {
|
||||
public:
|
||||
// MARK: - Initial construction and configuration.
|
||||
public:
|
||||
// MARK: - Initial construction and configuration.
|
||||
|
||||
MemoryMap(bool is_rom03) : auxiliary_switches_(*this), language_card_(*this) {
|
||||
setup_shadow_maps(is_rom03);
|
||||
}
|
||||
MemoryMap(bool is_rom03) : auxiliary_switches_(*this), language_card_(*this) {
|
||||
setup_shadow_maps(is_rom03);
|
||||
}
|
||||
|
||||
/// Sets the ROM and RAM storage underlying this MemoryMap.
|
||||
void set_storage(std::vector<uint8_t> &ram, std::vector<uint8_t> &rom);
|
||||
/// Sets the ROM and RAM storage underlying this MemoryMap.
|
||||
void set_storage(std::vector<uint8_t> &ram, std::vector<uint8_t> &rom);
|
||||
|
||||
// MARK: - Live bus access notifications and register access.
|
||||
// MARK: - Live bus access notifications and register access.
|
||||
|
||||
void set_shadow_register(uint8_t value);
|
||||
uint8_t get_shadow_register() const;
|
||||
void set_shadow_register(uint8_t value);
|
||||
uint8_t get_shadow_register() const;
|
||||
|
||||
void set_speed_register(uint8_t value);
|
||||
void set_speed_register(uint8_t value);
|
||||
|
||||
void set_state_register(uint8_t value);
|
||||
uint8_t get_state_register() const;
|
||||
void set_state_register(uint8_t value);
|
||||
uint8_t get_state_register() const;
|
||||
|
||||
void access(uint16_t address, bool is_read);
|
||||
void access(uint16_t address, bool is_read);
|
||||
|
||||
using AuxiliaryMemorySwitches = Apple::II::AuxiliaryMemorySwitches<MemoryMap>;
|
||||
const AuxiliaryMemorySwitches &auxiliary_switches() const {
|
||||
return auxiliary_switches_;
|
||||
}
|
||||
using AuxiliaryMemorySwitches = Apple::II::AuxiliaryMemorySwitches<MemoryMap>;
|
||||
const AuxiliaryMemorySwitches &auxiliary_switches() const {
|
||||
return auxiliary_switches_;
|
||||
}
|
||||
|
||||
using LanguageCardSwitches = Apple::II::LanguageCardSwitches<MemoryMap>;
|
||||
const LanguageCardSwitches &language_card_switches() const {
|
||||
return language_card_;
|
||||
}
|
||||
using LanguageCardSwitches = Apple::II::LanguageCardSwitches<MemoryMap>;
|
||||
const LanguageCardSwitches &language_card_switches() const {
|
||||
return language_card_;
|
||||
}
|
||||
|
||||
// MARK: - Accessors for reading and writing RAM.
|
||||
// MARK: - Accessors for reading and writing RAM.
|
||||
|
||||
struct Region {
|
||||
uint8_t *write = nullptr;
|
||||
const uint8_t *read = nullptr;
|
||||
uint8_t flags = 0;
|
||||
struct Region {
|
||||
uint8_t *write = nullptr;
|
||||
const uint8_t *read = nullptr;
|
||||
uint8_t flags = 0;
|
||||
|
||||
enum Flag: uint8_t {
|
||||
Is1Mhz = 1 << 0, // Both reads and writes should be synchronised with the 1Mhz clock.
|
||||
IsIO = 1 << 1, // Indicates that this region should be checked for soft switches, registers, etc.
|
||||
};
|
||||
enum Flag: uint8_t {
|
||||
Is1Mhz = 1 << 0, // Both reads and writes should be synchronised with the 1Mhz clock.
|
||||
IsIO = 1 << 1, // Indicates that this region should be checked for soft switches, registers, etc.
|
||||
};
|
||||
};
|
||||
|
||||
const Region ®ion(uint32_t address) const { return regions_[region_map_[address >> 8]]; }
|
||||
uint8_t read(const Region ®ion, uint32_t address) const {
|
||||
return region.read ? region.read[address] : 0xff;
|
||||
const Region ®ion(uint32_t address) const { return regions_[region_map_[address >> 8]]; }
|
||||
uint8_t read(const Region ®ion, uint32_t address) const {
|
||||
return region.read ? region.read[address] : 0xff;
|
||||
}
|
||||
|
||||
bool is_shadowed(const Region ®ion, uint32_t address) const {
|
||||
// ROM is never shadowed.
|
||||
if(!region.write) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_shadowed(const Region ®ion, uint32_t address) const {
|
||||
// ROM is never shadowed.
|
||||
if(!region.write) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto physical = physical_address(region, address);
|
||||
assert(physical <= 0xff'ffff);
|
||||
return shadow_pages_[(physical >> 10) & 127] && shadow_banks_[physical >> 17];
|
||||
}
|
||||
void write(const Region ®ion, uint32_t address, uint8_t value) {
|
||||
if(!region.write) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write once.
|
||||
region.write[address] = value;
|
||||
|
||||
// Write again, either to the same place (if unshadowed) or to the shadow destination.
|
||||
static constexpr std::size_t shadow_mask[2] = {0xff'ffff, 0x01'ffff};
|
||||
const bool shadowed = is_shadowed(region, address);
|
||||
shadow_base_[shadowed][physical_address(region, address) & shadow_mask[shadowed]] = value;
|
||||
const auto physical = physical_address(region, address);
|
||||
assert(physical <= 0xff'ffff);
|
||||
return shadow_pages_[(physical >> 10) & 127] && shadow_banks_[physical >> 17];
|
||||
}
|
||||
void write(const Region ®ion, uint32_t address, uint8_t value) {
|
||||
if(!region.write) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The objective is to support shadowing:
|
||||
// 1. without storing a whole extra pointer, and such that the shadowing flags
|
||||
// are orthogonal to the current auxiliary memory settings;
|
||||
// 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and
|
||||
// 3. to do so without introducing too much in the way of branching.
|
||||
//
|
||||
// Hence the implemented solution: if shadowing is enabled then use the distance from the start of
|
||||
// physical RAM modulo 128k indexed into the bank $e0/$e1 RAM.
|
||||
//
|
||||
// With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch
|
||||
// even on that.
|
||||
// Write once.
|
||||
region.write[address] = value;
|
||||
|
||||
private:
|
||||
AuxiliaryMemorySwitches auxiliary_switches_;
|
||||
LanguageCardSwitches language_card_;
|
||||
friend AuxiliaryMemorySwitches;
|
||||
friend LanguageCardSwitches;
|
||||
// Write again, either to the same place (if unshadowed) or to the shadow destination.
|
||||
static constexpr std::size_t shadow_mask[2] = {0xff'ffff, 0x01'ffff};
|
||||
const bool shadowed = is_shadowed(region, address);
|
||||
shadow_base_[shadowed][physical_address(region, address) & shadow_mask[shadowed]] = value;
|
||||
}
|
||||
|
||||
uint8_t shadow_register_ = 0x00;
|
||||
uint8_t speed_register_ = 0x00;
|
||||
// The objective is to support shadowing:
|
||||
// 1. without storing a whole extra pointer, and such that the shadowing flags
|
||||
// are orthogonal to the current auxiliary memory settings;
|
||||
// 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and
|
||||
// 3. to do so without introducing too much in the way of branching.
|
||||
//
|
||||
// Hence the implemented solution: if shadowing is enabled then use the distance from the start of
|
||||
// physical RAM modulo 128k indexed into the bank $e0/$e1 RAM.
|
||||
//
|
||||
// With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch
|
||||
// even on that.
|
||||
|
||||
// MARK: - Banking.
|
||||
private:
|
||||
AuxiliaryMemorySwitches auxiliary_switches_;
|
||||
LanguageCardSwitches language_card_;
|
||||
friend AuxiliaryMemorySwitches;
|
||||
friend LanguageCardSwitches;
|
||||
|
||||
void assert_is_region(uint8_t start, uint8_t end);
|
||||
template <int type> void set_paging();
|
||||
uint8_t shadow_register_ = 0x00;
|
||||
uint8_t speed_register_ = 0x00;
|
||||
|
||||
uint8_t *ram_base_ = nullptr;
|
||||
// MARK: - Banking.
|
||||
|
||||
// Memory layout here is done via double indirection; the main loop should:
|
||||
// (i) use the top two bytes of the address to get an index from region_map; and
|
||||
// (ii) use that to index the memory_regions table.
|
||||
//
|
||||
// Pointers are eight bytes at the time of writing, so the extra level of indirection
|
||||
// reduces what would otherwise be a 1.25mb table down to not a great deal more than 64kb.
|
||||
std::array<uint8_t, 65536> region_map_{};
|
||||
std::array<Region, 40> regions_; // An assert above ensures that this is large enough; there's no
|
||||
// doctrinal reason for it to be whatever size it is now, just
|
||||
// adjust as required.
|
||||
void assert_is_region(uint8_t start, uint8_t end);
|
||||
template <int type> void set_paging();
|
||||
|
||||
std::size_t physical_address(const Region ®ion, uint32_t address) const {
|
||||
return std::size_t(®ion.write[address] - ram_base_);
|
||||
}
|
||||
uint8_t *ram_base_ = nullptr;
|
||||
|
||||
// MARK: - Shadowing
|
||||
// Memory layout here is done via double indirection; the main loop should:
|
||||
// (i) use the top two bytes of the address to get an index from region_map; and
|
||||
// (ii) use that to index the memory_regions table.
|
||||
//
|
||||
// Pointers are eight bytes at the time of writing, so the extra level of indirection
|
||||
// reduces what would otherwise be a 1.25mb table down to not a great deal more than 64kb.
|
||||
std::array<uint8_t, 65536> region_map_{};
|
||||
std::array<Region, 40> regions_; // An assert above ensures that this is large enough; there's no
|
||||
// doctrinal reason for it to be whatever size it is now, just
|
||||
// adjust as required.
|
||||
|
||||
// Various precomputed bitsets describing key regions; std::bitset doesn't support constexpr instantiation
|
||||
// beyond the first 64 bits at the time of writing, alas, so these are generated at runtime.
|
||||
std::bitset<128> shadow_text1_;
|
||||
std::bitset<128> shadow_text2_;
|
||||
std::bitset<128> shadow_highres1_, shadow_highres1_aux_;
|
||||
std::bitset<128> shadow_highres2_, shadow_highres2_aux_;
|
||||
std::bitset<128> shadow_superhighres_;
|
||||
void setup_shadow_maps(bool is_rom03);
|
||||
void set_shadowing();
|
||||
std::size_t physical_address(const Region ®ion, uint32_t address) const {
|
||||
return std::size_t(®ion.write[address] - ram_base_);
|
||||
}
|
||||
|
||||
uint8_t *shadow_base_[2] = {nullptr, nullptr};
|
||||
// MARK: - Shadowing
|
||||
|
||||
// Divide the final 128kb of memory into 1kb chunks and flag to indicate whether
|
||||
// each is a potential destination for shadowing.
|
||||
std::bitset<128> shadow_pages_{};
|
||||
// Various precomputed bitsets describing key regions; std::bitset doesn't support constexpr instantiation
|
||||
// beyond the first 64 bits at the time of writing, alas, so these are generated at runtime.
|
||||
std::bitset<128> shadow_text1_;
|
||||
std::bitset<128> shadow_text2_;
|
||||
std::bitset<128> shadow_highres1_, shadow_highres1_aux_;
|
||||
std::bitset<128> shadow_highres2_, shadow_highres2_aux_;
|
||||
std::bitset<128> shadow_superhighres_;
|
||||
void setup_shadow_maps(bool is_rom03);
|
||||
void set_shadowing();
|
||||
|
||||
// Divide the whole 16mb of memory into 128kb chunks and flag to indicate whether
|
||||
// each is a potential source of shadowing.
|
||||
std::bitset<128> shadow_banks_{};
|
||||
uint8_t *shadow_base_[2] = {nullptr, nullptr};
|
||||
|
||||
// Divide the final 128kb of memory into 1kb chunks and flag to indicate whether
|
||||
// each is a potential destination for shadowing.
|
||||
std::bitset<128> shadow_pages_{};
|
||||
|
||||
// Divide the whole 16mb of memory into 128kb chunks and flag to indicate whether
|
||||
// each is a potential source of shadowing.
|
||||
std::bitset<128> shadow_banks_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -17,90 +17,90 @@
|
||||
namespace Apple::IIgs::Sound {
|
||||
|
||||
class GLU: public Outputs::Speaker::BufferSource<GLU, false> { // TODO: isn't this stereo?
|
||||
public:
|
||||
GLU(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
public:
|
||||
GLU(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
void set_control(uint8_t);
|
||||
uint8_t get_control();
|
||||
void set_data(uint8_t);
|
||||
uint8_t get_data();
|
||||
void set_address_low(uint8_t);
|
||||
uint8_t get_address_low();
|
||||
void set_address_high(uint8_t);
|
||||
uint8_t get_address_high();
|
||||
void set_control(uint8_t);
|
||||
uint8_t get_control();
|
||||
void set_data(uint8_t);
|
||||
uint8_t get_data();
|
||||
void set_address_low(uint8_t);
|
||||
uint8_t get_address_low();
|
||||
void set_address_high(uint8_t);
|
||||
uint8_t get_address_high();
|
||||
|
||||
void run_for(Cycles);
|
||||
Cycles next_sequence_point() const;
|
||||
bool get_interrupt_line();
|
||||
void run_for(Cycles);
|
||||
Cycles next_sequence_point() const;
|
||||
bool get_interrupt_line();
|
||||
|
||||
// SampleSource.
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
bool is_zero_level() const { return false; } // TODO.
|
||||
// SampleSource.
|
||||
template <Outputs::Speaker::Action action>
|
||||
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
bool is_zero_level() const { return false; } // TODO.
|
||||
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
private:
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
|
||||
uint16_t address_ = 0;
|
||||
uint16_t address_ = 0;
|
||||
|
||||
// Use a circular buffer for piping memory alterations onto the audio
|
||||
// thread; it would be prohibitive to defer every write individually.
|
||||
//
|
||||
// Assumed: on most modern architectures, an atomic 64-bit read or
|
||||
// write can be achieved locklessly.
|
||||
struct MemoryWrite {
|
||||
uint32_t time;
|
||||
uint16_t address;
|
||||
uint8_t value;
|
||||
bool enabled;
|
||||
};
|
||||
static_assert(sizeof(MemoryWrite) == 8);
|
||||
constexpr static int StoreBufferSize = 16384;
|
||||
// Use a circular buffer for piping memory alterations onto the audio
|
||||
// thread; it would be prohibitive to defer every write individually.
|
||||
//
|
||||
// Assumed: on most modern architectures, an atomic 64-bit read or
|
||||
// write can be achieved locklessly.
|
||||
struct MemoryWrite {
|
||||
uint32_t time;
|
||||
uint16_t address;
|
||||
uint8_t value;
|
||||
bool enabled;
|
||||
};
|
||||
static_assert(sizeof(MemoryWrite) == 8);
|
||||
constexpr static int StoreBufferSize = 16384;
|
||||
|
||||
std::atomic<MemoryWrite> pending_stores_[StoreBufferSize];
|
||||
uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0;
|
||||
uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0;
|
||||
std::atomic<MemoryWrite> pending_stores_[StoreBufferSize];
|
||||
uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0;
|
||||
uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0;
|
||||
|
||||
// Maintain state both 'locally' (i.e. on the emulation thread) and
|
||||
// 'remotely' (i.e. on the audio thread).
|
||||
struct EnsoniqState {
|
||||
uint8_t ram_[65536];
|
||||
struct Oscillator {
|
||||
uint32_t position;
|
||||
// Maintain state both 'locally' (i.e. on the emulation thread) and
|
||||
// 'remotely' (i.e. on the audio thread).
|
||||
struct EnsoniqState {
|
||||
uint8_t ram_[65536];
|
||||
struct Oscillator {
|
||||
uint32_t position;
|
||||
|
||||
// Programmer-set values.
|
||||
uint16_t velocity;
|
||||
uint8_t volume;
|
||||
uint8_t address;
|
||||
uint8_t control;
|
||||
uint8_t table_size;
|
||||
// Programmer-set values.
|
||||
uint16_t velocity;
|
||||
uint8_t volume;
|
||||
uint8_t address;
|
||||
uint8_t control;
|
||||
uint8_t table_size;
|
||||
|
||||
// Derived state.
|
||||
uint32_t overflow_mask; // If a non-zero bit gets anywhere into the overflow mask, this channel
|
||||
// has wrapped around. It's a function of table_size.
|
||||
bool interrupt_request = false; // Will be non-zero if this channel would request an interrupt, were
|
||||
// it currently enabled to do so.
|
||||
// Derived state.
|
||||
uint32_t overflow_mask; // If a non-zero bit gets anywhere into the overflow mask, this channel
|
||||
// has wrapped around. It's a function of table_size.
|
||||
bool interrupt_request = false; // Will be non-zero if this channel would request an interrupt, were
|
||||
// it currently enabled to do so.
|
||||
|
||||
uint8_t sample(uint8_t *ram);
|
||||
int16_t output(uint8_t *ram);
|
||||
} oscillators[32];
|
||||
uint8_t sample(uint8_t *ram);
|
||||
int16_t output(uint8_t *ram);
|
||||
} oscillators[32];
|
||||
|
||||
// Some of these aren't actually needed on both threads.
|
||||
uint8_t control = 0;
|
||||
int oscillator_count = 1;
|
||||
// Some of these aren't actually needed on both threads.
|
||||
uint8_t control = 0;
|
||||
int oscillator_count = 1;
|
||||
|
||||
void set_register(uint16_t address, uint8_t value);
|
||||
} local_, remote_;
|
||||
void set_register(uint16_t address, uint8_t value);
|
||||
} local_, remote_;
|
||||
|
||||
// Functions to update an EnsoniqState; these don't belong to the state itself
|
||||
// because they also access the pending stores (inter alia).
|
||||
template <Outputs::Speaker::Action action>
|
||||
void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
||||
void skip_audio(EnsoniqState &state, size_t number_of_samples);
|
||||
// Functions to update an EnsoniqState; these don't belong to the state itself
|
||||
// because they also access the pending stores (inter alia).
|
||||
template <Outputs::Speaker::Action action>
|
||||
void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
||||
void skip_audio(EnsoniqState &state, size_t number_of_samples);
|
||||
|
||||
// Audio-thread state.
|
||||
int16_t output_range_ = 0;
|
||||
// Audio-thread state.
|
||||
int16_t output_range_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -20,236 +20,236 @@ namespace Apple::IIgs::Video {
|
||||
stretched cycle.
|
||||
*/
|
||||
class Video: public Apple::II::VideoSwitches<Cycles> {
|
||||
public:
|
||||
Video();
|
||||
void set_internal_ram(const uint8_t *);
|
||||
|
||||
bool get_is_vertical_blank(Cycles offset);
|
||||
uint8_t get_horizontal_counter(Cycles offset);
|
||||
uint8_t get_vertical_counter(Cycles offset);
|
||||
|
||||
void set_new_video(uint8_t);
|
||||
uint8_t get_new_video();
|
||||
|
||||
void clear_interrupts(uint8_t);
|
||||
uint8_t get_interrupt_register();
|
||||
void set_interrupt_register(uint8_t);
|
||||
bool get_interrupt_line();
|
||||
|
||||
void notify_clock_tick();
|
||||
|
||||
void set_border_colour(uint8_t);
|
||||
void set_text_colour(uint8_t);
|
||||
uint8_t get_text_colour();
|
||||
uint8_t get_border_colour();
|
||||
|
||||
void set_composite_is_colour(bool);
|
||||
bool get_composite_is_colour();
|
||||
|
||||
/// Sets the scan target.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/// Sets the type of output.
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
/// Gets the type of output.
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/// Determines the period until video might autonomously update its interrupt lines.
|
||||
Cycles next_sequence_point() const;
|
||||
|
||||
/// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are
|
||||
/// generated here.
|
||||
void set_megaii_interrupts_enabled(uint8_t);
|
||||
|
||||
uint8_t get_megaii_interrupt_status();
|
||||
|
||||
void clear_megaii_interrupts();
|
||||
|
||||
private:
|
||||
Outputs::CRT::CRT crt_;
|
||||
|
||||
// This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs.
|
||||
enum class GraphicsMode {
|
||||
Text = 0,
|
||||
DoubleText,
|
||||
HighRes,
|
||||
DoubleHighRes,
|
||||
LowRes,
|
||||
DoubleLowRes,
|
||||
FatLowRes,
|
||||
|
||||
// Additions:
|
||||
DoubleHighResMono,
|
||||
SuperHighRes
|
||||
};
|
||||
constexpr bool is_colour_ntsc(const GraphicsMode m) {
|
||||
return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes;
|
||||
}
|
||||
|
||||
GraphicsMode graphics_mode(int row) const {
|
||||
if(new_video_ & 0x80) {
|
||||
return GraphicsMode::SuperHighRes;
|
||||
}
|
||||
|
||||
const auto ii_mode = Apple::II::VideoSwitches<Cycles>::graphics_mode(row);
|
||||
switch(ii_mode) {
|
||||
// Coupling very much assumed here.
|
||||
case Apple::II::GraphicsMode::DoubleHighRes:
|
||||
if(new_video_ & 0x20) {
|
||||
return GraphicsMode::DoubleHighResMono;
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
default: return GraphicsMode(int(ii_mode)); break;
|
||||
}
|
||||
}
|
||||
|
||||
enum class PixelBufferFormat {
|
||||
Text, DoubleText, NTSC, NTSCMono, SuperHighRes
|
||||
};
|
||||
constexpr PixelBufferFormat format_for_mode(GraphicsMode m) {
|
||||
switch(m) {
|
||||
case GraphicsMode::Text: return PixelBufferFormat::Text;
|
||||
case GraphicsMode::DoubleText: return PixelBufferFormat::DoubleText;
|
||||
default: return PixelBufferFormat::NTSC;
|
||||
case GraphicsMode::DoubleHighResMono: return PixelBufferFormat::NTSCMono;
|
||||
case GraphicsMode::SuperHighRes: return PixelBufferFormat::SuperHighRes;
|
||||
}
|
||||
}
|
||||
|
||||
void advance(Cycles);
|
||||
|
||||
uint8_t new_video_ = 0x01;
|
||||
|
||||
class Interrupts {
|
||||
public:
|
||||
Video();
|
||||
void set_internal_ram(const uint8_t *);
|
||||
void add(uint8_t value) {
|
||||
// Taken literally, status accumulates regardless of being enabled,
|
||||
// potentially to be polled, it simply doesn't trigger an interrupt.
|
||||
value_ |= value;
|
||||
test();
|
||||
}
|
||||
|
||||
bool get_is_vertical_blank(Cycles offset);
|
||||
uint8_t get_horizontal_counter(Cycles offset);
|
||||
uint8_t get_vertical_counter(Cycles offset);
|
||||
void clear(uint8_t value) {
|
||||
// Zeroes in bits 5 or 6 clear the respective interrupts.
|
||||
value_ &= value | ~0x60;
|
||||
test();
|
||||
}
|
||||
|
||||
void set_new_video(uint8_t);
|
||||
uint8_t get_new_video();
|
||||
void set_control(uint8_t value) {
|
||||
// Ones in bits 1 or 2 enable the respective interrupts.
|
||||
value_ = (value_ & ~0x6) | (value & 0x6);
|
||||
test();
|
||||
}
|
||||
|
||||
void clear_interrupts(uint8_t);
|
||||
uint8_t get_interrupt_register();
|
||||
void set_interrupt_register(uint8_t);
|
||||
bool get_interrupt_line();
|
||||
uint8_t status() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
void notify_clock_tick();
|
||||
|
||||
void set_border_colour(uint8_t);
|
||||
void set_text_colour(uint8_t);
|
||||
uint8_t get_text_colour();
|
||||
uint8_t get_border_colour();
|
||||
|
||||
void set_composite_is_colour(bool);
|
||||
bool get_composite_is_colour();
|
||||
|
||||
/// Sets the scan target.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/// Sets the type of output.
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
/// Gets the type of output.
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/// Determines the period until video might autonomously update its interrupt lines.
|
||||
Cycles next_sequence_point() const;
|
||||
|
||||
/// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are
|
||||
/// generated here.
|
||||
void set_megaii_interrupts_enabled(uint8_t);
|
||||
|
||||
uint8_t get_megaii_interrupt_status();
|
||||
|
||||
void clear_megaii_interrupts();
|
||||
bool active() const {
|
||||
return value_ & 0x80;
|
||||
}
|
||||
|
||||
private:
|
||||
Outputs::CRT::CRT crt_;
|
||||
|
||||
// This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs.
|
||||
enum class GraphicsMode {
|
||||
Text = 0,
|
||||
DoubleText,
|
||||
HighRes,
|
||||
DoubleHighRes,
|
||||
LowRes,
|
||||
DoubleLowRes,
|
||||
FatLowRes,
|
||||
|
||||
// Additions:
|
||||
DoubleHighResMono,
|
||||
SuperHighRes
|
||||
};
|
||||
constexpr bool is_colour_ntsc(const GraphicsMode m) {
|
||||
return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes;
|
||||
}
|
||||
|
||||
GraphicsMode graphics_mode(int row) const {
|
||||
if(new_video_ & 0x80) {
|
||||
return GraphicsMode::SuperHighRes;
|
||||
}
|
||||
|
||||
const auto ii_mode = Apple::II::VideoSwitches<Cycles>::graphics_mode(row);
|
||||
switch(ii_mode) {
|
||||
// Coupling very much assumed here.
|
||||
case Apple::II::GraphicsMode::DoubleHighRes:
|
||||
if(new_video_ & 0x20) {
|
||||
return GraphicsMode::DoubleHighResMono;
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
default: return GraphicsMode(int(ii_mode)); break;
|
||||
void test() {
|
||||
value_ &= 0x7f;
|
||||
if((value_ >> 4) & value_ & 0x6) {
|
||||
value_ |= 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
enum class PixelBufferFormat {
|
||||
Text, DoubleText, NTSC, NTSCMono, SuperHighRes
|
||||
};
|
||||
constexpr PixelBufferFormat format_for_mode(GraphicsMode m) {
|
||||
switch(m) {
|
||||
case GraphicsMode::Text: return PixelBufferFormat::Text;
|
||||
case GraphicsMode::DoubleText: return PixelBufferFormat::DoubleText;
|
||||
default: return PixelBufferFormat::NTSC;
|
||||
case GraphicsMode::DoubleHighResMono: return PixelBufferFormat::NTSCMono;
|
||||
case GraphicsMode::SuperHighRes: return PixelBufferFormat::SuperHighRes;
|
||||
}
|
||||
}
|
||||
|
||||
void advance(Cycles);
|
||||
|
||||
uint8_t new_video_ = 0x01;
|
||||
|
||||
class Interrupts {
|
||||
public:
|
||||
void add(uint8_t value) {
|
||||
// Taken literally, status accumulates regardless of being enabled,
|
||||
// potentially to be polled, it simply doesn't trigger an interrupt.
|
||||
value_ |= value;
|
||||
test();
|
||||
}
|
||||
|
||||
void clear(uint8_t value) {
|
||||
// Zeroes in bits 5 or 6 clear the respective interrupts.
|
||||
value_ &= value | ~0x60;
|
||||
test();
|
||||
}
|
||||
|
||||
void set_control(uint8_t value) {
|
||||
// Ones in bits 1 or 2 enable the respective interrupts.
|
||||
value_ = (value_ & ~0x6) | (value & 0x6);
|
||||
test();
|
||||
}
|
||||
|
||||
uint8_t status() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
bool active() const {
|
||||
return value_ & 0x80;
|
||||
}
|
||||
|
||||
private:
|
||||
void test() {
|
||||
value_ &= 0x7f;
|
||||
if((value_ >> 4) & value_ & 0x6) {
|
||||
value_ |= 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
// Overall meaning of value is as per the VGC interrupt register, i.e.
|
||||
//
|
||||
// b7: interrupt status;
|
||||
// b6: 1-second interrupt status;
|
||||
// b5: scan-line interrupt status;
|
||||
// b4: reserved;
|
||||
// b3: reserved;
|
||||
// b2: 1-second interrupt enable;
|
||||
// b1: scan-line interrupt enable;
|
||||
// b0: reserved.
|
||||
uint8_t value_ = 0x00;
|
||||
} interrupts_;
|
||||
|
||||
int cycles_into_frame_ = 0;
|
||||
const uint8_t *ram_ = nullptr;
|
||||
|
||||
// The modal colours.
|
||||
uint16_t border_colour_ = 0;
|
||||
uint8_t border_colour_entry_ = 0;
|
||||
uint8_t text_colour_entry_ = 0xf0;
|
||||
uint16_t text_colour_ = 0xffff;
|
||||
uint16_t background_colour_ = 0;
|
||||
|
||||
// Current pixel output buffer and conceptual format.
|
||||
PixelBufferFormat pixels_format_;
|
||||
uint16_t *pixels_ = nullptr, *next_pixel_ = nullptr;
|
||||
int pixels_start_column_;
|
||||
|
||||
void output_row(int row, int start, int end);
|
||||
|
||||
uint16_t *output_super_high_res(uint16_t *target, int start, int end, int row) const;
|
||||
|
||||
uint16_t *output_text(uint16_t *target, int start, int end, int row) const;
|
||||
uint16_t *output_double_text(uint16_t *target, int start, int end, int row) const;
|
||||
uint16_t *output_char(uint16_t *target, uint8_t source, int row) const;
|
||||
|
||||
uint16_t *output_low_resolution(uint16_t *target, int start, int end, int row);
|
||||
uint16_t *output_fat_low_resolution(uint16_t *target, int start, int end, int row);
|
||||
uint16_t *output_double_low_resolution(uint16_t *target, int start, int end, int row);
|
||||
|
||||
uint16_t *output_high_resolution(uint16_t *target, int start, int end, int row);
|
||||
uint16_t *output_double_high_resolution(uint16_t *target, int start, int end, int row);
|
||||
uint16_t *output_double_high_resolution_mono(uint16_t *target, int start, int end, int row);
|
||||
|
||||
// Super high-res per-line state.
|
||||
uint8_t line_control_;
|
||||
uint16_t palette_[16];
|
||||
|
||||
// Storage used for fill mode.
|
||||
uint16_t *palette_zero_[4] = {nullptr, nullptr, nullptr, nullptr}, palette_throwaway_;
|
||||
|
||||
// Lookup tables and state to assist in the IIgs' mapping from NTSC to RGB.
|
||||
// Overall meaning of value is as per the VGC interrupt register, i.e.
|
||||
//
|
||||
// My understanding of the real-life algorithm is: maintain a four-bit buffer.
|
||||
// Fill it in a circular fashion. Ordinarily, output the result of looking
|
||||
// up the RGB mapping of those four bits of Apple II output (which outputs four
|
||||
// bits per NTSC colour cycle), commuted as per current phase. But if the bit
|
||||
// being inserted differs from that currently in its position in the shift
|
||||
// register, hold the existing output for three shifts.
|
||||
//
|
||||
// From there I am using the following:
|
||||
// b7: interrupt status;
|
||||
// b6: 1-second interrupt status;
|
||||
// b5: scan-line interrupt status;
|
||||
// b4: reserved;
|
||||
// b3: reserved;
|
||||
// b2: 1-second interrupt enable;
|
||||
// b1: scan-line interrupt enable;
|
||||
// b0: reserved.
|
||||
uint8_t value_ = 0x00;
|
||||
} interrupts_;
|
||||
|
||||
// Maps from:
|
||||
//
|
||||
// b0 = b0 of the shift register
|
||||
// b1 = b4 of the shift register
|
||||
// b2– = current delay count
|
||||
//
|
||||
// to a new delay count.
|
||||
uint8_t ntsc_delay_lookup_[20];
|
||||
uint32_t ntsc_shift_ = 0; // Assumption here: logical shifts will ensue, rather than arithmetic.
|
||||
int ntsc_delay_ = 0;
|
||||
int cycles_into_frame_ = 0;
|
||||
const uint8_t *ram_ = nullptr;
|
||||
|
||||
/// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB.
|
||||
/// Phase is derived from @c column.
|
||||
uint16_t *output_shift(uint16_t *target, int column);
|
||||
// The modal colours.
|
||||
uint16_t border_colour_ = 0;
|
||||
uint8_t border_colour_entry_ = 0;
|
||||
uint8_t text_colour_entry_ = 0xf0;
|
||||
uint16_t text_colour_ = 0xffff;
|
||||
uint16_t background_colour_ = 0;
|
||||
|
||||
// Common getter for the two counters.
|
||||
struct Counters {
|
||||
Counters(int v, int h) : vertical(v), horizontal(h) {}
|
||||
const int vertical, horizontal;
|
||||
};
|
||||
Counters get_counters(Cycles offset);
|
||||
// Current pixel output buffer and conceptual format.
|
||||
PixelBufferFormat pixels_format_;
|
||||
uint16_t *pixels_ = nullptr, *next_pixel_ = nullptr;
|
||||
int pixels_start_column_;
|
||||
|
||||
// Marshalls the Mega II-style interrupt state.
|
||||
uint8_t megaii_interrupt_mask_ = 0;
|
||||
uint8_t megaii_interrupt_state_ = 0;
|
||||
int megaii_frame_counter_ = 0; // To count up to quarter-second interrupts.
|
||||
void output_row(int row, int start, int end);
|
||||
|
||||
uint16_t *output_super_high_res(uint16_t *target, int start, int end, int row) const;
|
||||
|
||||
uint16_t *output_text(uint16_t *target, int start, int end, int row) const;
|
||||
uint16_t *output_double_text(uint16_t *target, int start, int end, int row) const;
|
||||
uint16_t *output_char(uint16_t *target, uint8_t source, int row) const;
|
||||
|
||||
uint16_t *output_low_resolution(uint16_t *target, int start, int end, int row);
|
||||
uint16_t *output_fat_low_resolution(uint16_t *target, int start, int end, int row);
|
||||
uint16_t *output_double_low_resolution(uint16_t *target, int start, int end, int row);
|
||||
|
||||
uint16_t *output_high_resolution(uint16_t *target, int start, int end, int row);
|
||||
uint16_t *output_double_high_resolution(uint16_t *target, int start, int end, int row);
|
||||
uint16_t *output_double_high_resolution_mono(uint16_t *target, int start, int end, int row);
|
||||
|
||||
// Super high-res per-line state.
|
||||
uint8_t line_control_;
|
||||
uint16_t palette_[16];
|
||||
|
||||
// Storage used for fill mode.
|
||||
uint16_t *palette_zero_[4] = {nullptr, nullptr, nullptr, nullptr}, palette_throwaway_;
|
||||
|
||||
// Lookup tables and state to assist in the IIgs' mapping from NTSC to RGB.
|
||||
//
|
||||
// My understanding of the real-life algorithm is: maintain a four-bit buffer.
|
||||
// Fill it in a circular fashion. Ordinarily, output the result of looking
|
||||
// up the RGB mapping of those four bits of Apple II output (which outputs four
|
||||
// bits per NTSC colour cycle), commuted as per current phase. But if the bit
|
||||
// being inserted differs from that currently in its position in the shift
|
||||
// register, hold the existing output for three shifts.
|
||||
//
|
||||
// From there I am using the following:
|
||||
|
||||
// Maps from:
|
||||
//
|
||||
// b0 = b0 of the shift register
|
||||
// b1 = b4 of the shift register
|
||||
// b2– = current delay count
|
||||
//
|
||||
// to a new delay count.
|
||||
uint8_t ntsc_delay_lookup_[20];
|
||||
uint32_t ntsc_shift_ = 0; // Assumption here: logical shifts will ensue, rather than arithmetic.
|
||||
int ntsc_delay_ = 0;
|
||||
|
||||
/// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB.
|
||||
/// Phase is derived from @c column.
|
||||
uint16_t *output_shift(uint16_t *target, int column);
|
||||
|
||||
// Common getter for the two counters.
|
||||
struct Counters {
|
||||
Counters(int v, int h) : vertical(v), horizontal(h) {}
|
||||
const int vertical, horizontal;
|
||||
};
|
||||
Counters get_counters(Cycles offset);
|
||||
|
||||
// Marshalls the Mega II-style interrupt state.
|
||||
uint8_t megaii_interrupt_mask_ = 0;
|
||||
uint8_t megaii_interrupt_state_ = 0;
|
||||