// // Video.cpp // Clock Signal // // Created by Thomas Harte on 10/12/2016. // Copyright 2016 Thomas Harte. All rights reserved. // #include "Video.hpp" #include using namespace Electron; #define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) #define graphics_column(v) ((((v) & 127) - first_graphics_cycle + 128) & 127) namespace { constexpr int cycles_per_line = 128; constexpr int lines_per_frame = 625; constexpr int cycles_per_frame = lines_per_frame * cycles_per_line; constexpr int crt_cycles_multiplier = 8; constexpr int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; constexpr int field_divider_line = 312; // i.e. the line, simultaneous with which, the first field's sync ends. So if // the first line with pixels in field 1 is the 20th in the frame, the first line // with pixels in field 2 will be 20+field_divider_line constexpr int first_graphics_line = 31; constexpr int first_graphics_cycle = 33; constexpr int display_end_interrupt_line = 256; constexpr int real_time_clock_interrupt_1 = 16704; constexpr int real_time_clock_interrupt_2 = 56704; constexpr int display_end_interrupt_1 = (first_graphics_line + display_end_interrupt_line)*cycles_per_line; constexpr int display_end_interrupt_2 = (first_graphics_line + field_divider_line + display_end_interrupt_line)*cycles_per_line; } // MARK: - Lifecycle VideoOutput::VideoOutput(const uint8_t *memory) : ram_(memory), crt_(crt_cycles_per_line, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red1Green1Blue1) { memset(palette_, 0xf, sizeof(palette_)); // TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. crt_.set_visible_area(crt_.get_rect_for_area(first_graphics_line - 1, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); } void VideoOutput::set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); } Outputs::Display::ScanStatus VideoOutput::get_scaled_scan_status() const { return crt_.get_scaled_scan_status() / float(crt_cycles_multiplier); } void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); } Outputs::Display::DisplayType VideoOutput::get_display_type() const { return crt_.get_display_type(); } // MARK: - Display update methods //void VideoOutput::start_pixel_line() { // current_pixel_line_ = (current_pixel_line_+1)&255; // if(!current_pixel_line_) { // start_line_address_ = start_screen_address_; // current_character_row_ = 0; // is_blank_line_ = false; // } else { // bool mode_has_blank_lines = (screen_mode_ == 6) || (screen_mode_ == 3); // is_blank_line_ = (mode_has_blank_lines && ((current_character_row_ > 7 && current_character_row_ < 10) || (current_pixel_line_ > 249))); // // if(!is_blank_line_) { // start_line_address_++; // // if(current_character_row_ > 7) { // start_line_address_ += ((screen_mode_ < 4) ? 80 : 40) * 8 - 8; // current_character_row_ = 0; // } // } // } // current_screen_address_ = start_line_address_; // current_pixel_column_ = 0; // initial_output_target_ = current_output_target_ = nullptr; //} // //void VideoOutput::end_pixel_line() { // const int data_length = int(current_output_target_ - initial_output_target_); // if(data_length) { // crt_.output_data(data_length * current_output_divider_, size_t(data_length)); // } // current_character_row_++; //} // //void VideoOutput::output_pixels(int number_of_cycles) { // if(!number_of_cycles) return; // // if(is_blank_line_) { // crt_.output_blank(number_of_cycles * crt_cycles_multiplier); // } else { // int divider = 1; // switch(screen_mode_) { // case 0: case 3: divider = 1; break; // case 1: case 4: case 6: divider = 2; break; // case 2: case 5: divider = 4; break; // } // // if(!initial_output_target_ || divider != current_output_divider_) { // const int data_length = int(current_output_target_ - initial_output_target_); // if(data_length) { // crt_.output_data(data_length * current_output_divider_, size_t(data_length)); // } // current_output_divider_ = divider; // initial_output_target_ = current_output_target_ = crt_.begin_data(size_t(640 / current_output_divider_), size_t(8 / divider)); // } // //#define get_pixel() \ // if(current_screen_address_&32768) {\ // current_screen_address_ = (screen_mode_base_address_ + current_screen_address_)&32767;\ // }\ // last_pixel_byte_ = ram_[current_screen_address_];\ // current_screen_address_ = current_screen_address_+8 // // switch(screen_mode_) { // case 0: case 3: // if(initial_output_target_) { // while(number_of_cycles--) { // get_pixel(); // *reinterpret_cast(current_output_target_) = palette_tables_.eighty1bpp[last_pixel_byte_]; // current_output_target_ += 8; // current_pixel_column_++; // } // } else current_output_target_ += 8*number_of_cycles; // break; // // case 1: // if(initial_output_target_) { // while(number_of_cycles--) { // get_pixel(); // *reinterpret_cast(current_output_target_) = palette_tables_.eighty2bpp[last_pixel_byte_]; // current_output_target_ += 4; // current_pixel_column_++; // } // } else current_output_target_ += 4*number_of_cycles; // break; // // case 2: // if(initial_output_target_) { // while(number_of_cycles--) { // get_pixel(); // *reinterpret_cast(current_output_target_) = palette_tables_.eighty4bpp[last_pixel_byte_]; // current_output_target_ += 2; // current_pixel_column_++; // } // } else current_output_target_ += 2*number_of_cycles; // break; // // case 4: case 6: // if(initial_output_target_) { // if(current_pixel_column_&1) { // last_pixel_byte_ <<= 4; // *reinterpret_cast(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_]; // current_output_target_ += 4; // // number_of_cycles--; // current_pixel_column_++; // } // while(number_of_cycles > 1) { // get_pixel(); // *reinterpret_cast(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_]; // current_output_target_ += 4; // // last_pixel_byte_ <<= 4; // *reinterpret_cast(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_]; // current_output_target_ += 4; // // number_of_cycles -= 2; // current_pixel_column_+=2; // } // if(number_of_cycles) { // get_pixel(); // *reinterpret_cast(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_]; // current_output_target_ += 4; // current_pixel_column_++; // } // } else current_output_target_ += 4 * number_of_cycles; // break; // // case 5: // if(initial_output_target_) { // if(current_pixel_column_&1) { // last_pixel_byte_ <<= 2; // *reinterpret_cast(current_output_target_) = palette_tables_.forty2bpp[last_pixel_byte_]; // current_output_target_ += 2; // // number_of_cycles--; // current_pixel_column_++; // } // while(number_of_cycles > 1) { // get_pixel(); // *reinterpret_cast(current_output_target_) = palette_tables_.forty2bpp[last_pixel_byte_]; // current_output_target_ += 2; // // last_pixel_byte_ <<= 2; // *reinterpret_cast(current_output_target_) = palette_tables_.forty2bpp[last_pixel_byte_]; // current_output_target_ += 2; // // number_of_cycles -= 2; // current_pixel_column_+=2; // } // if(number_of_cycles) { // get_pixel(); // *reinterpret_cast(current_output_target_) = palette_tables_.forty2bpp[last_pixel_byte_]; // current_output_target_ += 2; // current_pixel_column_++; // } // } else current_output_target_ += 2*number_of_cycles; // break; // } // //#undef get_pixel // } //} Electron::Interrupt VideoOutput::run_for(const Cycles cycles) { Electron::Interrupt interrupts{}; // int number_of_cycles = int(cycles.as_integral()); // const auto start_position = output_position_; // output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame; // // const auto test_range = [&](int start, int end) { // if( // (start < real_time_clock_interrupt_1 && end >= real_time_clock_interrupt_1) || // (start < real_time_clock_interrupt_2 && end >= real_time_clock_interrupt_2) // ) { // interrupts_ = Electron::Interrupt(interrupts_ | Electron::Interrupt::RealTimeClock); // } // // if( // (start < display_end_interrupt_1 && end >= display_end_interrupt_1) || // (start < display_end_interrupt_2 && end >= display_end_interrupt_2) // ) { // interrupts_ = Electron::Interrupt(interrupts_ | Electron::Interrupt::DisplayEnd); // } // }; // // if(output_position_ >= start_position) { // test_range(start_position, output_position_); // } else { // test_range(start_position, cycles_per_frame); // test_range(0, output_position_); // } // // while(number_of_cycles) { // int draw_action_length = screen_map_[screen_map_pointer_].length; // int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_); // if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels(time_left_in_action); // // number_of_cycles -= time_left_in_action; // cycles_into_draw_action_ += time_left_in_action; // if(cycles_into_draw_action_ == draw_action_length) { // switch(screen_map_[screen_map_pointer_].type) { // case DrawAction::Sync: crt_.output_sync(draw_action_length * crt_cycles_multiplier); break; // case DrawAction::ColourBurst: crt_.output_default_colour_burst(draw_action_length * crt_cycles_multiplier); break; // case DrawAction::Blank: crt_.output_blank(draw_action_length * crt_cycles_multiplier); break; // case DrawAction::Pixels: end_pixel_line(); break; // } // screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size(); // cycles_into_draw_action_ = 0; // if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) start_pixel_line(); // } // } return interrupts; } // MARK: - Register hub void VideoOutput::write(int address, uint8_t value) { switch(address & 0xf) { case 0x02: screen_base = (screen_base & 0b0111'1110'0000'0000) | ((value << 1) & 0b0000'0001'1100'0000); break; case 0x03: screen_base = (screen_base & 0b0111'1110'0000'0000) | ((value << 1) & 0b0000'0001'1100'0000); break; case 0x07: { uint8_t mode = (value >> 3)&7; mode_40 = mode >= 4; mode_text = mode == 3 || mode == 6; switch(mode) { case 0: case 1: case 2: mode_base = 0x3000; break; case 3: mode_base = 0x4000; break; case 6: mode_base = 0x6000; break; default: mode_base = 0x5800; break; } switch(mode) { default: mode_bpp = Bpp::One; break; case 1: case 5: mode_bpp = Bpp::Two; break; case 2: mode_bpp = Bpp::Four; break; } } break; case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: { // constexpr int registers[4][4] = { // {10, 8, 2, 0}, // {14, 12, 6, 4}, // {15, 13, 7, 5}, // {11, 9, 3, 1}, // }; // const int index = (address >> 1)&3; // const uint8_t colour = ~value; // if(address&1) { // palette_[registers[index][0]] = (palette_[registers[index][0]]&3) | ((colour >> 1)&4); // palette_[registers[index][1]] = (palette_[registers[index][1]]&3) | ((colour >> 0)&4); // palette_[registers[index][2]] = (palette_[registers[index][2]]&3) | ((colour << 1)&4); // palette_[registers[index][3]] = (palette_[registers[index][3]]&3) | ((colour << 2)&4); // // palette_[registers[index][2]] = (palette_[registers[index][2]]&5) | ((colour >> 4)&2); // palette_[registers[index][3]] = (palette_[registers[index][3]]&5) | ((colour >> 3)&2); // } else { // palette_[registers[index][0]] = (palette_[registers[index][0]]&6) | ((colour >> 7)&1); // palette_[registers[index][1]] = (palette_[registers[index][1]]&6) | ((colour >> 6)&1); // palette_[registers[index][2]] = (palette_[registers[index][2]]&6) | ((colour >> 5)&1); // palette_[registers[index][3]] = (palette_[registers[index][3]]&6) | ((colour >> 4)&1); // // palette_[registers[index][0]] = (palette_[registers[index][0]]&5) | ((colour >> 2)&2); // palette_[registers[index][1]] = (palette_[registers[index][1]]&5) | ((colour >> 1)&2); // } // // // regenerate all palette tables for now // for(int byte = 0; byte < 256; byte++) { // uint8_t *target = reinterpret_cast(&palette_tables_.forty1bpp[byte]); // target[0] = palette_[(byte&0x80) >> 4]; // target[1] = palette_[(byte&0x40) >> 3]; // target[2] = palette_[(byte&0x20) >> 2]; // target[3] = palette_[(byte&0x10) >> 1]; // // target = reinterpret_cast(&palette_tables_.eighty2bpp[byte]); // target[0] = palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)]; // target[1] = palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]; // target[2] = palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)]; // target[3] = palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]; // // target = reinterpret_cast(&palette_tables_.eighty1bpp[byte]); // target[0] = palette_[(byte&0x80) >> 4]; // target[1] = palette_[(byte&0x40) >> 3]; // target[2] = palette_[(byte&0x20) >> 2]; // target[3] = palette_[(byte&0x10) >> 1]; // target[4] = palette_[(byte&0x08) >> 0]; // target[5] = palette_[(byte&0x04) << 1]; // target[6] = palette_[(byte&0x02) << 2]; // target[7] = palette_[(byte&0x01) << 3]; // // target = reinterpret_cast(&palette_tables_.forty2bpp[byte]); // target[0] = palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)]; // target[1] = palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]; // // target = reinterpret_cast(&palette_tables_.eighty4bpp[byte]); // target[0] = palette_[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)]; // target[1] = palette_[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]; // } } break; } }