diff --git a/Machines/AppleII/Video.cpp b/Machines/AppleII/Video.cpp index 9cbfc7302..443444708 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/AppleII/Video.cpp @@ -10,8 +10,9 @@ using namespace AppleII::Video; -VideoBase::VideoBase() : - crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)) { +VideoBase::VideoBase(bool is_iie) : + crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)), + is_iie_(is_iie) { // Set a composite sampling function that assumes one byte per pixel input, and // accepts any non-zero value as being fully on, zero being fully off. @@ -115,3 +116,180 @@ void VideoBase::set_character_rom(const std::vector &character_rom) { } } } + +uint8_t *VideoBase::output_text(uint8_t *target, uint8_t *source, size_t length, size_t pixel_row) { + const uint8_t inverses[] = { + 0xff, + is_iie_ ? static_cast(0xff) : static_cast((flash_ / flash_length) * 0xff), + is_iie_ ? static_cast(0xff) : static_cast(0x00), + is_iie_ ? static_cast(0xff) : static_cast(0x00) + }; + const int or_mask = alternative_character_set_ ? 0x100 : 0x000; + const int and_mask = is_iie_ ? ~0 : 0x3f; + + for(size_t c = 0; c < length; ++c) { + const int character = (source[c] | or_mask) & and_mask; + const uint8_t xor_mask = inverses[character >> 6]; + const std::size_t character_address = static_cast(character << 3) + pixel_row; + const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask; + + // The character ROM is output MSB to LSB rather than LSB to MSB. + target[0] = character_pattern & 0x40; + target[1] = character_pattern & 0x20; + target[2] = character_pattern & 0x10; + target[3] = character_pattern & 0x08; + target[4] = character_pattern & 0x04; + target[5] = character_pattern & 0x02; + target[6] = character_pattern & 0x01; + graphics_carry_ = character_pattern & 0x01; + target += 7; + } + + return target; +} + +uint8_t *VideoBase::output_double_text(uint8_t *target, uint8_t *source, uint8_t *auxiliary_source, size_t length, size_t pixel_row) { + for(size_t c = 0; c < length; ++c) { + const std::size_t character_addresses[2] = { + static_cast( + auxiliary_source[c] << 3 + ) + pixel_row, + static_cast( + source[c] << 3 + ) + pixel_row, + }; + + const size_t pattern_offset = alternative_character_set_ ? (256*8) : 0; + const uint8_t character_patterns[2] = { + character_rom_[character_addresses[0] + pattern_offset], + character_rom_[character_addresses[1] + pattern_offset], + }; + + // The character ROM is output MSB to LSB rather than LSB to MSB. + target[0] = character_patterns[0] & 0x40; + target[1] = character_patterns[0] & 0x20; + target[2] = character_patterns[0] & 0x10; + target[3] = character_patterns[0] & 0x08; + target[4] = character_patterns[0] & 0x04; + target[5] = character_patterns[0] & 0x02; + target[6] = character_patterns[0] & 0x01; + target[7] = character_patterns[1] & 0x40; + target[8] = character_patterns[1] & 0x20; + target[9] = character_patterns[1] & 0x10; + target[10] = character_patterns[1] & 0x08; + target[11] = character_patterns[1] & 0x04; + target[12] = character_patterns[1] & 0x02; + target[13] = character_patterns[1] & 0x01; + graphics_carry_ = character_patterns[1] & 0x01; + target += 14; + } + + return target; +} + +uint8_t *VideoBase::output_low_resolution(uint8_t *target, uint8_t *source, size_t length, int row) { + const int row_shift = row&4; + for(size_t c = 0; c < length; ++c) { + // Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this + // 14-sample output window is starting at the beginning of a colour cycle or halfway through. + if(c&1) { + target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 4; + target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 8; + target[2] = target[6] = target[10] = (source[c] >> row_shift) & 1; + target[3] = target[7] = target[11] = (source[c] >> row_shift) & 2; + graphics_carry_ = (source[c] >> row_shift) & 8; + } else { + target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 1; + target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 2; + target[2] = target[6] = target[10] = (source[c] >> row_shift) & 4; + target[3] = target[7] = target[11] = (source[c] >> row_shift) & 8; + graphics_carry_ = (source[c] >> row_shift) & 2; + } + target += 14; + } + + return target; +} + +uint8_t *VideoBase::output_double_low_resolution(uint8_t *target, uint8_t *source, uint8_t *auxiliary_source, size_t length, int row) { + const int row_shift = row&4; + for(size_t c = 0; c < length; ++c) { + if(c&1) { + target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 2; + target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 4; + target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 8; + target[3] = (auxiliary_source[c] >> row_shift) & 1; + + target[8] = target[12] = (source[c] >> row_shift) & 4; + target[9] = target[13] = (source[c] >> row_shift) & 8; + target[10] = (source[c] >> row_shift) & 1; + target[7] = target[11] = (source[c] >> row_shift) & 2; + graphics_carry_ = (source[c] >> row_shift) & 8; + } else { + target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 8; + target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 1; + target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 2; + target[3] = (auxiliary_source[c] >> row_shift) & 4; + + target[8] = target[12] = (source[c] >> row_shift) & 1; + target[9] = target[13] = (source[c] >> row_shift) & 2; + target[10] = (source[c] >> row_shift) & 4; + target[7] = target[11] = (source[c] >> row_shift) & 8; + graphics_carry_ = (source[c] >> row_shift) & 2; + } + target += 14; + } + + return target; +} + +uint8_t *VideoBase::output_high_resolution(uint8_t *target, uint8_t *source, size_t length) { + for(size_t c = 0; c < length; ++c) { + // High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel. + // If there is a delay, the previous output level is held to bridge the gap. + if(base_stream_[c] & 0x80) { + target[0] = graphics_carry_; + target[1] = target[2] = source[c] & 0x01; + target[3] = target[4] = source[c] & 0x02; + target[5] = target[6] = source[c] & 0x04; + target[7] = target[8] = source[c] & 0x08; + target[9] = target[10] = source[c] & 0x10; + target[11] = target[12] = source[c] & 0x20; + target[13] = source[c] & 0x40; + } else { + target[0] = target[1] = source[c] & 0x01; + target[2] = target[3] = source[c] & 0x02; + target[4] = target[5] = source[c] & 0x04; + target[6] = target[7] = source[c] & 0x08; + target[8] = target[9] = source[c] & 0x10; + target[10] = target[11] = source[c] & 0x20; + target[12] = target[13] = source[c] & 0x40; + } + graphics_carry_ = source[c] & 0x40; + target += 14; + } + return target; +} + +uint8_t *VideoBase::output_double_high_resolution(uint8_t *target, uint8_t *source, uint8_t *auxiliary_source, size_t length) { + for(size_t c = 0; c < length; ++c) { + target[0] = graphics_carry_; + target[1] = auxiliary_source[c] & 0x01; + target[2] = auxiliary_source[c] & 0x02; + target[3] = auxiliary_source[c] & 0x04; + target[4] = auxiliary_source[c] & 0x08; + target[5] = auxiliary_source[c] & 0x10; + target[6] = auxiliary_source[c] & 0x20; + target[7] = auxiliary_source[c] & 0x40; + target[8] = auxiliary_source[c] & 0x01; + target[9] = auxiliary_source[c] & 0x02; + target[10] = auxiliary_source[c] & 0x04; + target[11] = auxiliary_source[c] & 0x08; + target[12] = auxiliary_source[c] & 0x10; + target[13] = auxiliary_source[c] & 0x20; + graphics_carry_ = auxiliary_source[c] & 0x40; + pixel_pointer_ += 14; + } + + return target; +} diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index d9f300d19..daacb656a 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -33,7 +33,7 @@ class BusHandler { class VideoBase { public: - VideoBase(); + VideoBase(bool is_iie); /// @returns The CRT this video feed is feeding. Outputs::CRT::CRT *get_crt(); @@ -190,13 +190,52 @@ class VideoBase { // without having to worry about a rolling buffer. std::array base_stream_; std::array auxiliary_stream_; + + bool is_iie_ = false; + static const int flash_length = 8406; + + /*! + Outputs 40-column text to @c target, using @c length bytes from @c source. + @return One byte after the final value written to @c target. + */ + uint8_t *output_text(uint8_t *target, uint8_t *source, size_t length, size_t pixel_row); + + /*! + Outputs 80-column text to @c target, drawing @c length columns from @c source and @c auxiliary_source. + @return One byte after the final value written to @c target. + */ + uint8_t *output_double_text(uint8_t *target, uint8_t *source, uint8_t *auxiliary_source, size_t length, size_t pixel_row); + + /*! + Outputs 40-column low-resolution graphics to @c target, drawing @c length columns from @c source. + @return One byte after the final value written to @c target. + */ + uint8_t *output_low_resolution(uint8_t *target, uint8_t *source, size_t length, int row); + + /*! + Outputs 80-column low-resolution graphics to @c target, drawing @c length columns from @c source and @c auxiliary_source. + @return One byte after the final value written to @c target. + */ + uint8_t *output_double_low_resolution(uint8_t *target, uint8_t *source, uint8_t *auxiliary_source, size_t length, int row); + + /*! + Outputs 40-column high-resolution graphics to @c target, drawing @c length columns from @c source. + @return One byte after the final value written to @c target. + */ + uint8_t *output_high_resolution(uint8_t *target, uint8_t *source, size_t length); + + /*! + Outputs 80-column double-high-resolution graphics to @c target, drawing @c length columns from @c source. + @return One byte after the final value written to @c target. + */ + uint8_t *output_double_high_resolution(uint8_t *target, uint8_t *source, uint8_t *auxiliary_source, size_t length); }; template class Video: public VideoBase { public: /// Constructs an instance of the video feed; a CRT is also created. Video(BusHandler &bus_handler) : - VideoBase(), + VideoBase(is_iie), bus_handler_(bus_handler) {} /*! @@ -276,173 +315,54 @@ template class Video: public VideoBase { } switch(line_mode) { - case GraphicsMode::Text: { - const uint8_t inverses[] = { - 0xff, - static_cast((flash_ / flash_length) * 0xff), - 0x00, - 0x00 - }; - for(size_t c = column_; c < pixel_end; ++c) { - int character = base_stream_[c]; - if(is_iie) { - character |= alternative_character_set_ ? 0x100 : 0; - } else { - character &= 0x3f; - } - const uint8_t xor_mask = is_iie ? 0xff : inverses[character >> 6]; - const std::size_t character_address = static_cast((character << 3) + pixel_row); - const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask; + case GraphicsMode::Text: + pixel_pointer_ = output_text( + pixel_pointer_, + &base_stream_[static_cast(column_)], + static_cast(pixel_end - column_), + static_cast(pixel_row)); + break; - // The character ROM is output MSB to LSB rather than LSB to MSB. - pixel_pointer_[0] = character_pattern & 0x40; - pixel_pointer_[1] = character_pattern & 0x20; - pixel_pointer_[2] = character_pattern & 0x10; - pixel_pointer_[3] = character_pattern & 0x08; - pixel_pointer_[4] = character_pattern & 0x04; - pixel_pointer_[5] = character_pattern & 0x02; - pixel_pointer_[6] = character_pattern & 0x01; - graphics_carry_ = character_pattern & 0x01; - pixel_pointer_ += 7; - } - } break; + case GraphicsMode::DoubleText: + pixel_pointer_ = output_double_text( + pixel_pointer_, + &base_stream_[static_cast(column_)], + &auxiliary_stream_[static_cast(column_)], + static_cast(pixel_end - column_), + static_cast(pixel_row)); + break; - case GraphicsMode::DoubleText: { - for(size_t c = column_; c < pixel_end; ++c) { - const std::size_t character_addresses[2] = { - static_cast( - (auxiliary_stream_[c] << 3) + pixel_row - ), - static_cast( - (base_stream_[c] << 3) + pixel_row - ), - }; + case GraphicsMode::LowRes: + pixel_pointer_ = output_low_resolution( + pixel_pointer_, + &base_stream_[static_cast(column_)], + static_cast(pixel_end - column_), + pixel_row); + break; - const size_t pattern_offset = alternative_character_set_ ? (256*8) : 0; - const uint8_t character_patterns[2] = { - character_rom_[character_addresses[0] + pattern_offset], - character_rom_[character_addresses[1] + pattern_offset], - }; + case GraphicsMode::DoubleLowRes: + pixel_pointer_ = output_double_low_resolution( + pixel_pointer_, + &base_stream_[static_cast(column_)], + &auxiliary_stream_[static_cast(column_)], + static_cast(pixel_end - column_), + pixel_row); + break; - // The character ROM is output MSB to LSB rather than LSB to MSB. - pixel_pointer_[0] = character_patterns[0] & 0x40; - pixel_pointer_[1] = character_patterns[0] & 0x20; - pixel_pointer_[2] = character_patterns[0] & 0x10; - pixel_pointer_[3] = character_patterns[0] & 0x08; - pixel_pointer_[4] = character_patterns[0] & 0x04; - pixel_pointer_[5] = character_patterns[0] & 0x02; - pixel_pointer_[6] = character_patterns[0] & 0x01; - pixel_pointer_[7] = character_patterns[1] & 0x40; - pixel_pointer_[8] = character_patterns[1] & 0x20; - pixel_pointer_[9] = character_patterns[1] & 0x10; - pixel_pointer_[10] = character_patterns[1] & 0x08; - pixel_pointer_[11] = character_patterns[1] & 0x04; - pixel_pointer_[12] = character_patterns[1] & 0x02; - pixel_pointer_[13] = character_patterns[1] & 0x01; - graphics_carry_ = character_patterns[1] & 0x01; - pixel_pointer_ += 14; - } - } break; + case GraphicsMode::HighRes: + pixel_pointer_ = output_high_resolution( + pixel_pointer_, + &base_stream_[static_cast(column_)], + static_cast(pixel_end - column_)); + break; - case GraphicsMode::DoubleLowRes: { - const int row_shift = (row_&4); - for(size_t c = column_; c < pixel_end; ++c) { - if(c&1) { - pixel_pointer_[0] = pixel_pointer_[4] = (auxiliary_stream_[c] >> row_shift) & 2; - pixel_pointer_[1] = pixel_pointer_[5] = (auxiliary_stream_[c] >> row_shift) & 4; - pixel_pointer_[2] = pixel_pointer_[6] = (auxiliary_stream_[c] >> row_shift) & 8; - pixel_pointer_[3] = (auxiliary_stream_[c] >> row_shift) & 1; - - pixel_pointer_[8] = pixel_pointer_[12] = (base_stream_[c] >> row_shift) & 4; - pixel_pointer_[9] = pixel_pointer_[13] = (base_stream_[c] >> row_shift) & 8; - pixel_pointer_[10] = (base_stream_[c] >> row_shift) & 1; - pixel_pointer_[7] = pixel_pointer_[11] = (base_stream_[c] >> row_shift) & 2; - graphics_carry_ = (base_stream_[c] >> row_shift) & 8; - } else { - pixel_pointer_[0] = pixel_pointer_[4] = (auxiliary_stream_[c] >> row_shift) & 8; - pixel_pointer_[1] = pixel_pointer_[5] = (auxiliary_stream_[c] >> row_shift) & 1; - pixel_pointer_[2] = pixel_pointer_[6] = (auxiliary_stream_[c] >> row_shift) & 2; - pixel_pointer_[3] = (auxiliary_stream_[c] >> row_shift) & 4; - - pixel_pointer_[8] = pixel_pointer_[12] = (base_stream_[c] >> row_shift) & 1; - pixel_pointer_[9] = pixel_pointer_[13] = (base_stream_[c] >> row_shift) & 2; - pixel_pointer_[10] = (base_stream_[c] >> row_shift) & 4; - pixel_pointer_[7] = pixel_pointer_[11] = (base_stream_[c] >> row_shift) & 8; - graphics_carry_ = (base_stream_[c] >> row_shift) & 2; - } - pixel_pointer_ += 14; - } - } break; - - case GraphicsMode::LowRes: { - const int row_shift = (row_&4); - for(size_t c = column_; c < pixel_end; ++c) { - // Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this - // 14-sample output window is starting at the beginning of a colour cycle or halfway through. - if(c&1) { - pixel_pointer_[0] = pixel_pointer_[4] = pixel_pointer_[8] = pixel_pointer_[12] = (base_stream_[c] >> row_shift) & 4; - pixel_pointer_[1] = pixel_pointer_[5] = pixel_pointer_[9] = pixel_pointer_[13] = (base_stream_[c] >> row_shift) & 8; - pixel_pointer_[2] = pixel_pointer_[6] = pixel_pointer_[10] = (base_stream_[c] >> row_shift) & 1; - pixel_pointer_[3] = pixel_pointer_[7] = pixel_pointer_[11] = (base_stream_[c] >> row_shift) & 2; - graphics_carry_ = (base_stream_[c] >> row_shift) & 8; - } else { - pixel_pointer_[0] = pixel_pointer_[4] = pixel_pointer_[8] = pixel_pointer_[12] = (base_stream_[c] >> row_shift) & 1; - pixel_pointer_[1] = pixel_pointer_[5] = pixel_pointer_[9] = pixel_pointer_[13] = (base_stream_[c] >> row_shift) & 2; - pixel_pointer_[2] = pixel_pointer_[6] = pixel_pointer_[10] = (base_stream_[c] >> row_shift) & 4; - pixel_pointer_[3] = pixel_pointer_[7] = pixel_pointer_[11] = (base_stream_[c] >> row_shift) & 8; - graphics_carry_ = (base_stream_[c] >> row_shift) & 2; - } - pixel_pointer_ += 14; - } - } break; - - case GraphicsMode::HighRes: { - for(size_t c = column_; c < pixel_end; ++c) { - // High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel. - // If there is a delay, the previous output level is held to bridge the gap. - if(base_stream_[c] & 0x80) { - pixel_pointer_[0] = graphics_carry_; - pixel_pointer_[1] = pixel_pointer_[2] = base_stream_[c] & 0x01; - pixel_pointer_[3] = pixel_pointer_[4] = base_stream_[c] & 0x02; - pixel_pointer_[5] = pixel_pointer_[6] = base_stream_[c] & 0x04; - pixel_pointer_[7] = pixel_pointer_[8] = base_stream_[c] & 0x08; - pixel_pointer_[9] = pixel_pointer_[10] = base_stream_[c] & 0x10; - pixel_pointer_[11] = pixel_pointer_[12] = base_stream_[c] & 0x20; - pixel_pointer_[13] = base_stream_[c] & 0x40; - } else { - pixel_pointer_[0] = pixel_pointer_[1] = base_stream_[c] & 0x01; - pixel_pointer_[2] = pixel_pointer_[3] = base_stream_[c] & 0x02; - pixel_pointer_[4] = pixel_pointer_[5] = base_stream_[c] & 0x04; - pixel_pointer_[6] = pixel_pointer_[7] = base_stream_[c] & 0x08; - pixel_pointer_[8] = pixel_pointer_[9] = base_stream_[c] & 0x10; - pixel_pointer_[10] = pixel_pointer_[11] = base_stream_[c] & 0x20; - pixel_pointer_[12] = pixel_pointer_[13] = base_stream_[c] & 0x40; - } - graphics_carry_ = base_stream_[c] & 0x40; - pixel_pointer_ += 14; - } - } break; - - case GraphicsMode::DoubleHighRes: { - for(size_t c = column_; c < pixel_end; ++c) { - pixel_pointer_[0] = graphics_carry_; - pixel_pointer_[1] = auxiliary_stream_[c] & 0x01; - pixel_pointer_[2] = auxiliary_stream_[c] & 0x02; - pixel_pointer_[3] = auxiliary_stream_[c] & 0x04; - pixel_pointer_[4] = auxiliary_stream_[c] & 0x08; - pixel_pointer_[5] = auxiliary_stream_[c] & 0x10; - pixel_pointer_[6] = auxiliary_stream_[c] & 0x20; - pixel_pointer_[7] = auxiliary_stream_[c] & 0x40; - pixel_pointer_[8] = base_stream_[c] & 0x01; - pixel_pointer_[9] = base_stream_[c] & 0x02; - pixel_pointer_[10] = base_stream_[c] & 0x04; - pixel_pointer_[11] = base_stream_[c] & 0x08; - pixel_pointer_[12] = base_stream_[c] & 0x10; - pixel_pointer_[13] = base_stream_[c] & 0x20; - graphics_carry_ = base_stream_[c] & 0x40; - pixel_pointer_ += 14; - } - } break; + case GraphicsMode::DoubleHighRes: + pixel_pointer_ = output_double_high_resolution( + pixel_pointer_, + &base_stream_[static_cast(column_)], + &auxiliary_stream_[static_cast(column_)], + static_cast(pixel_end - column_)); + break; } if(ending_column >= 40) { @@ -588,7 +508,6 @@ template class Video: public VideoBase { static_cast(((video_page()+1) * 0x400) + row_address); } - static const int flash_length = 8406; BusHandler &bus_handler_; void output_data_to_column(int column) { int length = column - pixel_pointer_column_;