From a07c99d778419dcd3a042554e2aefdf10d1c7521 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 17 Apr 2018 22:04:02 -0400 Subject: [PATCH] Completes first draft of Apple II video hardware. --- Machines/AppleII/AppleII.cpp | 5 +- Machines/AppleII/Video.cpp | 120 +++++++++++++++++++----- Machines/AppleII/Video.hpp | 174 ++++++++++++++++++----------------- 3 files changed, 193 insertions(+), 106 deletions(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index f996ffd81..9e51d7fd1 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -104,7 +104,10 @@ class ConcreteMachine: } } else { if(address < sizeof(ram_)) { - update_video(); // TODO: be more selective. + if(address >= 0x400) { + // TODO: be more selective. + update_video(); + } ram_[address] = *value; // printf("%04x <- %02x\n", address, *value); } diff --git a/Machines/AppleII/Video.cpp b/Machines/AppleII/Video.cpp index 1753f8ff2..4d65b5443 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/AppleII/Video.cpp @@ -10,26 +10,100 @@ using namespace AppleII::Video; -//void Video::set_graphics_mode() { -// printf("Graphics mode\n"); -//} -// -//void Video::set_text_mode() { -// printf("Text mode\n"); -//} -// -//void Video::set_mixed_mode(bool mixed_mode) { -// printf("Mixed mode: %s\n", mixed_mode ? "true" : "false"); -//} -// -//void Video::set_video_page(int page) { -// printf("Video page: %d\n", page); -//} -// -//void Video::set_low_resolution() { -// printf("Low resolution\n"); -//} -// -//void Video::set_high_resolution() { -// printf("High resolution\n"); -//} +namespace { + +struct ScaledByteFiller { + ScaledByteFiller() { + VideoBase::setup_tables(); + } +} throwaway; + +} + +VideoBase::VideoBase() : + crt_(new Outputs::CRT::CRT(455, 1, Outputs::CRT::DisplayType::NTSC60, 1)) { + + // Set a composite sampling function that assumes 1bpp input, and uses just 7 bits per byte. + crt_->set_composite_sampling_function( + "float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" + "{" + "uint texValue = texture(sampler, coordinate).r;" + "texValue <<= uint(icoordinate.x * 7.0) % 7u;" + "return float(texValue & 64u);" + "}"); + + // TODO: the above has precision issues. Fix! + + // Show only the centre 75% of the TV frame. + crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); + crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.117f, 0.77f, 0.77f)); +} + +Outputs::CRT::CRT *VideoBase::get_crt() { + return crt_.get(); +} + +uint16_t VideoBase::scaled_byte[256]; +uint16_t VideoBase::low_resolution_patterns[2][16]; + +void VideoBase::setup_tables() { + for(int c = 0; c < 128; ++c) { + const uint16_t value = + ((c & 0x01) ? 0x0003 : 0x0000) | + ((c & 0x02) ? 0x000c : 0x0000) | + ((c & 0x04) ? 0x0030 : 0x0000) | + ((c & 0x08) ? 0x0140 : 0x0000) | + ((c & 0x10) ? 0x0600 : 0x0000) | + ((c & 0x20) ? 0x1800 : 0x0000) | + ((c & 0x40) ? 0x6000 : 0x0000); + + uint8_t *const table_entry = reinterpret_cast(&scaled_byte[c]); + table_entry[1] = static_cast(value & 0xff); + table_entry[0] = static_cast(value >> 8); + } + for(int c = 128; c < 256; ++c) { + uint8_t *const source_table_entry = reinterpret_cast(&scaled_byte[c & 0x7f]); + uint8_t *const destination_table_entry = reinterpret_cast(&scaled_byte[c]); + + destination_table_entry[0] = static_cast(source_table_entry[0] >> 1); + destination_table_entry[1] = static_cast((source_table_entry[1] >> 1) | ((source_table_entry[0]&1) << 6)); + } + + for(int c = 0; c < 16; ++c) { + uint8_t *table_entry = reinterpret_cast(&low_resolution_patterns[0][c]); + table_entry[1] = static_cast(c | (c << 4)); + table_entry[0] = static_cast((c >> 3) | (c << 1) | (c << 5)); + + table_entry = reinterpret_cast(&low_resolution_patterns[1][c]); + table_entry[1] = static_cast((c >> 2) | (c << 2) | (c << 6)); + table_entry[0] = static_cast((c >> 1) | (c << 3)); + } +} + +void VideoBase::set_graphics_mode() { + use_graphics_mode_ = true; +} + +void VideoBase::set_text_mode() { + use_graphics_mode_ = false; +} + +void VideoBase::set_mixed_mode(bool mixed_mode) { + mixed_mode_ = mixed_mode; +} + +void VideoBase::set_video_page(int page) { + video_page_ = page; +} + +void VideoBase::set_low_resolution() { + graphics_mode_ = GraphicsMode::LowRes; +} + +void VideoBase::set_high_resolution() { + graphics_mode_ = GraphicsMode::HighRes; +} + +void VideoBase::set_character_rom(const std::vector &character_rom) { + character_rom_ = character_rom; +} diff --git a/Machines/AppleII/Video.hpp b/Machines/AppleII/Video.hpp index 2394e333c..eb1feb03d 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/AppleII/Video.hpp @@ -24,41 +24,52 @@ class BusHandler { } }; -template class Video { +class VideoBase { + public: + VideoBase(); + static void setup_tables(); + + /// @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(); + + // Setup for text mode. + void set_character_rom(const std::vector &); + + protected: + std::unique_ptr crt_; + + int video_page_ = 0; + int row_ = 0, column_ = 0; + uint16_t *pixel_pointer_ = nullptr; + std::vector character_rom_; + + enum class GraphicsMode { + LowRes, + HighRes, + Text + } graphics_mode_ = GraphicsMode::LowRes; + bool use_graphics_mode_ = false; + bool mixed_mode_ = false; + uint16_t graphics_carry_ = 0; + + static uint16_t scaled_byte[256]; + static uint16_t low_resolution_patterns[2][16]; +}; + +template class Video: public VideoBase { public: /// Constructs an instance of the video feed; a CRT is also created. Video(BusHandler &bus_handler) : - bus_handler_(bus_handler), - crt_(new Outputs::CRT::CRT(455, 1, Outputs::CRT::DisplayType::NTSC60, 1)) { - - // Set a composite sampling function that assumes 1bpp input, and uses just 7 bits per byte. - crt_->set_composite_sampling_function( - "float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" - "{" -// "uint texValue = texture(sampler, coordinate).r;" -// "texValue <<= int(icoordinate.x * 8) & 7;" -// "return float(texValue & 128u);" - - "uint texValue = texture(sampler, coordinate).r;" - "texValue <<= uint(icoordinate.x * 7.0) % 7u;" - "return float(texValue & 64u);" - -// "uint texValue = texture(sampler, coordinate).r;" -// "texValue <<= uint(mod(icoordinate.x, 1.0) * 7.0);" -// "return float(texValue & 64u);" - "}"); - - // TODO: the above has precision issues. Fix! - - // Show only the centre 75% of the TV frame. - crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); - crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.117f, 0.77f, 0.77f)); - } - - /// @returns The CRT this video feed is feeding. - Outputs::CRT::CRT *get_crt() { - return crt_.get(); - } + VideoBase(), + bus_handler_(bus_handler) {} /*! Advances time by @c cycles; expects to be fed by the CPU clock. @@ -83,6 +94,9 @@ template class Video { crt_->output_sync(static_cast(cycles_this_line) * 7); } else { const int ending_column = column_ + cycles_this_line; + GraphicsMode line_mode = graphics_mode_; + if(!use_graphics_mode_ || (mixed_mode_ && row_ >= 160)) + line_mode = GraphicsMode::Text; // 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 @@ -90,27 +104,49 @@ template class Video { if(column_ < 40) { if(row_ < 192) { if(!column_) { - pixel_pointer_ = crt_->allocate_write_area(40); + pixel_pointer_ = reinterpret_cast(crt_->allocate_write_area(80, 2)); } const int pixel_end = std::min(40, ending_column); const int character_row = row_ >> 3; const int pixel_row = row_ & 7; - const uint16_t line_address = static_cast(0x400 + (video_page_ * 0x400) + (character_row >> 3) * 40 + ((character_row&7) << 7)); + const uint16_t row_address = static_cast((character_row >> 3) * 40 + ((character_row&7) << 7)); + const uint16_t text_address = static_cast(((video_page_+1) * 0x400) + row_address); + const uint16_t graphics_address = static_cast(((video_page_+1) * 0x1000) + row_address + ((pixel_row&7) << 9)); + const int row_shift = (row_&4); - for(int c = column_; c < pixel_end; ++c) { - const uint16_t address = static_cast(line_address + c); - const uint8_t character = bus_handler_.perform_read(address); - const int index = (character & 0x7f) << 3; + switch(line_mode) { + case GraphicsMode::Text: + for(int c = column_; c < pixel_end; ++c) { + const uint8_t character = bus_handler_.perform_read(static_cast(text_address + c)); + const std::size_t character_address = static_cast(((character & 0x7f) << 3) + pixel_row); - const std::size_t character_address = static_cast(index + pixel_row); - pixel_pointer_[c] = character_rom_[character_address] ^ ((character & 0x80) ? 0x00 : 0xff); + const uint8_t character_pattern = character_rom_[character_address] ^ ((character & 0x80) ? 0x00 : 0xff); + pixel_pointer_[c] = scaled_byte[character_pattern & 0x7f]; + } + break; + + case GraphicsMode::LowRes: + for(int c = column_; c < pixel_end; ++c) { + const uint8_t character = bus_handler_.perform_read(static_cast(text_address + c)); + pixel_pointer_[c] = low_resolution_patterns[column_&1][(character >> row_shift)&0xf]; + } + break; + + case GraphicsMode::HighRes: + for(int c = column_; c < pixel_end; ++c) { + const uint8_t graphic = bus_handler_.perform_read(static_cast(graphics_address + c)); + pixel_pointer_[c] = scaled_byte[graphic]; + if(graphic & 0x80) { + reinterpret_cast(&pixel_pointer_[c])[0] |= graphics_carry_ << 7; + } + graphics_carry_ = pixel_pointer_[c] & 1; + } + break; } - // TODO: graphics; 0x2000+ for high resolution - if(ending_column >= 40) { - crt_->output_data(280, 40); + crt_->output_data(280, 80); } } else { if(ending_column >= 40) { @@ -130,15 +166,25 @@ template class Video { crt_->output_blank(static_cast(first_blank_end - first_blank_start) * 7); } - // TODO: colour burst. - const int sync_start = std::max(first_sync_column, column_); const int sync_end = std::min(first_sync_column + 4, ending_column); if(sync_end > sync_start) { crt_->output_sync(static_cast(sync_end - sync_start) * 7); } - const int second_blank_start = std::max(first_sync_column + 4, column_); + int second_blank_start; + if(line_mode != GraphicsMode::Text) { + const int colour_burst_start = std::max(first_sync_column + 4, column_); + const int colour_burst_end = std::min(first_sync_column + 7, ending_column); + if(colour_burst_end > colour_burst_start) { + crt_->output_default_colour_burst(static_cast(colour_burst_end - colour_burst_start) * 7); + } + + second_blank_start = std::max(first_sync_column + 7, column_); + } else { + second_blank_start = std::max(first_sync_column + 4, column_); + } + if(ending_column > second_blank_start) { crt_->output_blank(static_cast(ending_column - second_blank_start) * 7); } @@ -156,44 +202,8 @@ template class Video { } } - // Inputs for the various soft switches. - void set_graphics_mode() { - printf("Graphics mode\n"); - } - - void set_text_mode() { - printf("Text mode\n"); - } - - void set_mixed_mode(bool mixed_mode) { - printf("Mixed mode: %s\n", mixed_mode ? "true" : "false"); - } - - void set_video_page(int page) { - video_page_ = page; - printf("Video page: %d\n", page); - } - - void set_low_resolution() { - printf("Low resolution\n"); - } - - void set_high_resolution() { - printf("High resolution\n"); - } - - void set_character_rom(const std::vector &character_rom) { - character_rom_ = character_rom; - } - private: BusHandler &bus_handler_; - std::unique_ptr crt_; - - int video_page_ = 0; - int row_ = 0, column_ = 0; - uint8_t *pixel_pointer_ = nullptr; - std::vector character_rom_; }; }