diff --git a/Machines/Apple/AppleIIgs/AppleIIgs.cpp b/Machines/Apple/AppleIIgs/AppleIIgs.cpp index 63088369f..039a94cd4 100644 --- a/Machines/Apple/AppleIIgs/AppleIIgs.cpp +++ b/Machines/Apple/AppleIIgs/AppleIIgs.cpp @@ -102,11 +102,24 @@ class ConcreteMachine: m65816_.run_for(cycles); } - void set_scan_target(Outputs::Display::ScanTarget *) override { + void flush() { + video_.flush(); + } + + void set_scan_target(Outputs::Display::ScanTarget *target) override { + video_->set_scan_target(target); } Outputs::Display::ScanStatus get_scaled_scan_status() const override { - return Outputs::Display::ScanStatus(); + return video_->get_scaled_scan_status() * 2.0f; // TODO: expose multiplier and divider via the JustInTime template? + } + + void set_display_type(Outputs::Display::DisplayType display_type) final { + video_->set_display_type(display_type); + } + + Outputs::Display::DisplayType get_display_type() const final { + return video_->get_display_type(); } forceinline Cycles perform_bus_operation(const CPU::WDC65816::BusOperation operation, const uint32_t address, uint8_t *const value) { @@ -544,7 +557,7 @@ class ConcreteMachine: // MARK: - Other components. Apple::Clock::ParallelClock clock_; - JustInTimeActor video_; // i.e. run video at twice the 1Mhz clock. + JustInTimeActor video_; // i.e. run video at twice the 1Mhz clock. Apple::IIgs::ADB::GLU adb_glu_; Apple::IIgs::Sound::GLU sound_glu_; Zilog::SCC::z8530 scc_; diff --git a/Machines/Apple/AppleIIgs/Video.cpp b/Machines/Apple/AppleIIgs/Video.cpp index 5a6eec246..687bbd2c5 100644 --- a/Machines/Apple/AppleIIgs/Video.cpp +++ b/Machines/Apple/AppleIIgs/Video.cpp @@ -12,14 +12,35 @@ using namespace Apple::IIgs::Video; namespace { -constexpr int CyclesPerLine = 910; +constexpr int CyclesPerTick = 7; // One 'tick' being the non-stretched length of a cycle on the old Apple II 1Mhz clock. +constexpr int CyclesPerLine = 456; // Each of the Mega II's cycles lasts 7 cycles, making 455/line except for the + // final on on a line which lasts an additional 1 (i.e. is 1/7th longer). constexpr int Lines = 263; constexpr int FinalPixelLine = 192; +constexpr auto FinalColumn = CyclesPerLine / CyclesPerTick; + } VideoBase::VideoBase() : - VideoSwitches(true, Cycles(2), [this] (Cycles cycles) { advance(cycles); }) { + VideoSwitches(true, Cycles(2), [this] (Cycles cycles) { advance(cycles); }), + crt_(130, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red4Green4Blue4) { +} + +void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); +} + +Outputs::Display::ScanStatus VideoBase::get_scaled_scan_status() const { + return crt_.get_scaled_scan_status(); +} + +void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) { + crt_.set_display_type(display_type); +} + +Outputs::Display::DisplayType VideoBase::get_display_type() const { + return crt_.get_display_type(); } void VideoBase::set_internal_ram(const uint8_t *ram) { @@ -27,25 +48,111 @@ void VideoBase::set_internal_ram(const uint8_t *ram) { } void VideoBase::advance(Cycles cycles) { - // TODO: everything else! - const auto old = cycles_into_frame_; + const int column_start = (cycles_into_frame_ % CyclesPerLine) / CyclesPerTick; + const int row_start = cycles_into_frame_ / CyclesPerLine; + cycles_into_frame_ = (cycles_into_frame_ + cycles.as()) % (CyclesPerLine * Lines); - // DEBUGGING HACK!! - // Scan the output buffer, assuming this is 40-column text mode, and print anything found. - if(cycles_into_frame_ < old) { - for(int line = 0; line < 192; line += 8) { - const uint16_t address = get_row_address(line); + const int column_end = (cycles_into_frame_ % CyclesPerLine) / CyclesPerTick; + const int row_end = cycles_into_frame_ / CyclesPerLine; - bool did_print_line = false; - for(int column = 0; column < 40; column++) { - const char c = char(ram_[address + column]); - if(c > 0) { - printf("%c", c); - did_print_line = true; - } - } - if(did_print_line) printf("\n"); + if(row_end == row_start) { + output_row(row_start, column_start, column_end); + } else { + output_row(row_start, column_start, FinalColumn); + for(int row = row_start+1; row < row_end; row++) { + output_row(row, 0, FinalColumn); + } + if(column_end) { + output_row(row_end, 0, column_end); + } + } +} + +void VideoBase::output_row(int row, int start, int end) { + // Reasoned guesswork ahoy! + // + // The IIgs VGC can fetch four bytes per column — I'm unclear physically how, but that's definitely true + // since the IIgs modes packs 160 bytes work of graphics into the Apple II's usual 40-cycle fetch area; + // it's possible that if I understood the meaning of the linear video bit in the new video flag I'd know more. + // + // Super Hi-Res also fetches 16*2 = 32 bytes of palette and a control byte sometime before each row. + // So it needs five windows for that. + // + // Guessing four cycles of sync, I've chosen to arrange one output row for this emulator as: + // + // 5 cycles of back porch; + // 8 windows left border, the final five of which fetch palette and control if in IIgs mode; + // 40 windows of pixel output; + // 8 cycles of right border; + // 4 cycles of sync (including the extra 1/7th window, as it has to go _somewhere_). + // + // Otherwise, the first 200 rows may be pixels and the 192 in the middle of those are the II set. + constexpr int first_sync_line = 220; // A complete guess. Information needed. + + constexpr int blank_ticks = 5; + constexpr int left_border_ticks = 8; + constexpr int pixel_ticks = 40; + constexpr int right_border_ticks = 8; + + constexpr int start_of_left_border = blank_ticks; + constexpr int start_of_pixels = start_of_left_border + left_border_ticks; + constexpr int start_of_right_border = start_of_pixels + pixel_ticks; + constexpr int start_of_sync = start_of_right_border + right_border_ticks; + constexpr int sync_period = CyclesPerLine - start_of_sync*CyclesPerTick; + + // Deal with vertical sync. + if(row >= first_sync_line && row < first_sync_line + 3) { + // Simplification: just output the whole line at line's end. + if(end == FinalColumn) { + crt_.output_sync(CyclesPerLine - sync_period); + crt_.output_blank(sync_period); + } + + return; + } + + // Deal with the pixel area. + if(row < Lines) { // TODO: use real test here. + + // Output blank only at the end of its window. + if(start < blank_ticks && end >= blank_ticks) { + crt_.output_blank(blank_ticks * CyclesPerTick); + start = blank_ticks; + } + + // Output left border as far as currently known. + if(start >= start_of_left_border && start < start_of_pixels) { + const int duration = std::max(left_border_ticks, end - start_of_left_border); + start += duration; + + // TODO: output real border colour. + crt_.output_blank(duration * CyclesPerTick); + } + + // Output left border as far as currently known. + if(start >= start_of_pixels && start < start_of_right_border) { + const int duration = std::max(pixel_ticks, end - start_of_pixels); + start += duration; + + // TODO: output real pixels. + uint16_t *const pixel = reinterpret_cast(crt_.begin_data(2, 2)); + if(pixel) *pixel = 0xffff; + crt_.output_data(duration * CyclesPerTick, 1); + } + + // Output left border as far as currently known. + if(start >= start_of_right_border && start < start_of_sync) { + const int duration = std::max(right_border_ticks, end - start_of_right_border); + start += duration; + + // TODO: output real border colour. + crt_.output_blank(duration * CyclesPerTick); + } + + // Output sync if the moment has arrived. + if(end == FinalColumn) { + crt_.output_sync(sync_period); } } } diff --git a/Machines/Apple/AppleIIgs/Video.hpp b/Machines/Apple/AppleIIgs/Video.hpp index c4cc72bf8..0c57b238a 100644 --- a/Machines/Apple/AppleIIgs/Video.hpp +++ b/Machines/Apple/AppleIIgs/Video.hpp @@ -10,6 +10,7 @@ #define Apple_IIgs_Video_hpp #include "../AppleII/VideoSwitches.hpp" +#include "../../../Outputs/CRT/CRT.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" namespace Apple { @@ -37,7 +38,21 @@ class VideoBase: public Apple::II::VideoSwitches { void notify_clock_tick(); + /// 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; + private: + Outputs::CRT::CRT crt_; + void advance(Cycles); uint8_t new_video_ = 0x01; @@ -46,6 +61,8 @@ class VideoBase: public Apple::II::VideoSwitches { int cycles_into_frame_ = 0; const uint8_t *ram_ = nullptr; + + void output_row(int row, int start, int end); }; class Video: public VideoBase {