From a99ebda513828a80f7710a67361a3a81388afb98 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 4 May 2019 22:27:58 -0400 Subject: [PATCH] Takes a first shot at (inverted) Mac video output. --- ClockReceiver/ClockReceiver.hpp | 13 +++ Machines/Apple/Macintosh/Macintosh.cpp | 30 +++---- Machines/Apple/Macintosh/Video.cpp | 115 ++++++++++++++++++++++++- Machines/Apple/Macintosh/Video.hpp | 9 ++ 4 files changed, 149 insertions(+), 18 deletions(-) diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index c6cd23f23..67ccb0fe3 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -90,6 +90,16 @@ template class WrappedInt { return *static_cast(this); } + T &operator *=(const T &rhs) { + length_ *= rhs.length_; + return *static_cast(this); + } + + T &operator /=(const T &rhs) { + length_ /= rhs.length_; + return *static_cast(this); + } + T &operator %=(const T &rhs) { length_ %= rhs.length_; return *static_cast(this); @@ -103,6 +113,9 @@ template class WrappedInt { constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); } constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); } + constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); } + constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); } + constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); } constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); } diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index ce8363aa0..18f1deff7 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -60,6 +60,8 @@ class ConcreteMachine: using Microcycle = CPU::MC68000::Microcycle; HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) { + time_since_video_update_ += cycle.length; + // Assumption here: it's a divide by ten to derive the 6522 clock, i.e. // it runs off the 68000's E clock. via_clock_ += cycle.length; @@ -71,11 +73,12 @@ class ConcreteMachine: if(cycle.operation) { auto word_address = cycle.word_address(); - // Hardware devices begin at 0x800000. + // Hardware devices begin at 0x800000 and accesses to 'them' (i.e. at lest the 6522, + // and the other two are a guess) is via the synchronous bus. mc68000_.set_is_peripheral_address(word_address >= 0x400000); if(word_address >= 0x400000) { if(cycle.data_select_active()) { - printf("IO access to %06x: ", word_address << 1); +// printf("IO access to %06x: ", word_address << 1); const int register_address = word_address >> 8; @@ -83,7 +86,7 @@ class ConcreteMachine: case 0x77f0ff: // VIA accesses are via address 0xefe1fe + register*512, // which at word precision is 0x77f0ff + register*256. - printf("VIA"); +// printf("VIA"); if(cycle.operation & Microcycle::Read) { cycle.value->halves.low = via_.get_register(register_address); if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff; @@ -94,11 +97,11 @@ class ConcreteMachine: case 0x6ff0ff: // IWM - printf("IWM %d", register_address & 0xf); +// printf("IWM %d", register_address & 0xf); break; } - printf("\n"); +// printf("\n"); } } else { if(cycle.data_select_active()) { @@ -136,6 +139,7 @@ class ConcreteMachine: break; case Microcycle::SelectWord: memory_base[word_address] = cycle.value->full; + printf("%04x -> %06x\n", cycle.value->full, word_address << 1); break; case Microcycle::SelectByte: memory_base[word_address] = uint16_t( @@ -151,10 +155,6 @@ class ConcreteMachine: } } - // Any access to the - - // TODO: the entirety of dealing with this cycle. - /* Normal memory map: @@ -164,19 +164,14 @@ class ConcreteMachine: BFFFF8+: SCC write operations DFE1FF+: IWM EFE1FE+: VIA - - Overlay mode: - - ROM replaces RAM at 00000, while also being at 400000 */ return HalfCycles(0); } - /* - Notes to self: accesses to the VIA are via the 68000's - synchronous bus. - */ + void flush() { + video_.run_for(time_since_video_update_.flush()); + } void set_rom_is_overlay(bool rom_is_overlay) { ROM_is_overlay_ = rom_is_overlay; @@ -258,6 +253,7 @@ class ConcreteMachine: MOS::MOS6522::MOS6522 via_; VIAPortHandler via_port_handler_; HalfCycles via_clock_; + HalfCycles time_since_video_update_; bool ROM_is_overlay_ = true; }; diff --git a/Machines/Apple/Macintosh/Video.cpp b/Machines/Apple/Macintosh/Video.cpp index 5468f7eb3..922d6b974 100644 --- a/Machines/Apple/Macintosh/Video.cpp +++ b/Machines/Apple/Macintosh/Video.cpp @@ -8,8 +8,18 @@ #include "Video.hpp" +#include + using namespace Apple::Macintosh; +namespace { + +const HalfCycles line_length(704); +const int number_of_lines = 370; +const HalfCycles frame_length(line_length * HalfCycles(number_of_lines)); + +} + // Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family, // bottom of page 400: // @@ -22,13 +32,116 @@ using namespace Apple::Macintosh; // During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines," // Video::Video(uint16_t *ram) : - crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) { + crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1), + ram_(ram) { + + crt_.set_display_type(Outputs::Display::DisplayType::CompositeMonochrome); + crt_.set_visible_area(Outputs::Display::Rect(0.02f, 0.025f, 0.94f, 0.94f)); } void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); } +void Video::run_for(HalfCycles duration) { + const int sync_start = 36; + const int sync_end = 38; + + // The number of HalfCycles is literally the number of pixel clocks to move through, + // since pixel output occurs at twice the processor clock. So divide by 16 to get + // the number of fetches. + while(duration > HalfCycles(0)) { + auto cycles_left_in_line = std::min(line_length - frame_position_%line_length, duration); + + const int line = (frame_position_ / line_length).as_int(); + const auto pixel_start = frame_position_ % line_length; + + // Line timing, entirely invented as I can find exactly zero words of documentation: + // + // First 342 lines: + // + // First 32 words = pixels; + // next 5 words = right border; + // next 2 words = sync level; + // final 5 words = left border. + // + // Then 12 lines of border, 3 of sync, 11 more of border. + + const int first_word = pixel_start.as_int() >> 4; + const int final_word = (pixel_start + cycles_left_in_line).as_int() >> 4; + + if(first_word != final_word) { + if(line < 342) { + // If there are any pixels left to output, do so. + if(first_word < 32) { + const int final_pixel_word = std::min(final_word, 32); + + if(!first_word) { + pixel_buffer_ = crt_.begin_data(512); + } + + if(pixel_buffer_) { + for(int c = first_word; c < final_pixel_word; ++c) { + uint16_t pixels = ram_[video_address_]; + ++video_address_; + + pixel_buffer_[0] = pixels & 0x01; + pixel_buffer_[1] = pixels & 0x02; + pixel_buffer_[2] = pixels & 0x04; + pixel_buffer_[3] = pixels & 0x08; + pixel_buffer_[4] = pixels & 0x10; + pixel_buffer_[5] = pixels & 0x20; + pixel_buffer_[6] = pixels & 0x40; + pixel_buffer_[7] = pixels & 0x80; + + pixels >>= 8; + pixel_buffer_[8] = pixels & 0x01; + pixel_buffer_[9] = pixels & 0x02; + pixel_buffer_[10] = pixels & 0x04; + pixel_buffer_[11] = pixels & 0x08; + pixel_buffer_[12] = pixels & 0x10; + pixel_buffer_[13] = pixels & 0x20; + pixel_buffer_[14] = pixels & 0x40; + pixel_buffer_[15] = pixels & 0x80; + + pixel_buffer_ += 16; + } + } + + if(final_pixel_word == 32) { + crt_.output_data(512); + } + } + + if(first_word < sync_start && final_word >= sync_start) crt_.output_blank((sync_start - 32) * 16); + if(first_word < sync_end && final_word >= sync_end) crt_.output_sync((sync_end - sync_start) * 16); + if(final_word == 44) crt_.output_blank((44 - sync_end) * 16); + } else if(line >= 353 && line < 356) { + /* Output a sync line. */ + if(final_word == 44) { + crt_.output_sync(sync_start * 16); + crt_.output_blank((sync_end - sync_start) * 16); + crt_.output_sync((44 - sync_end) * 16); + } + } else { + /* Output a blank line. */ + if(final_word == 44) { + crt_.output_blank(sync_start * 16); + crt_.output_sync((sync_end - sync_start) * 16); + crt_.output_blank((44 - sync_end) * 16); + } + } + } + + duration -= cycles_left_in_line; + frame_position_ = frame_position_ + cycles_left_in_line; + if(frame_position_ == frame_length) { + frame_position_ = HalfCycles(0); + video_address_ = 0x1a700 >> 1; + } + } +} + /* Video: $1A700 and the alternate buffer starts at $12700; for a 512K Macintosh, add $60000 to these numbers. */ diff --git a/Machines/Apple/Macintosh/Video.hpp b/Machines/Apple/Macintosh/Video.hpp index 35e60a89c..27fb3e005 100644 --- a/Machines/Apple/Macintosh/Video.hpp +++ b/Machines/Apple/Macintosh/Video.hpp @@ -10,6 +10,7 @@ #define Video_hpp #include "../../../Outputs/CRT/CRT.hpp" +#include "../../../ClockReceiver/ClockReceiver.hpp" namespace Apple { namespace Macintosh { @@ -18,9 +19,17 @@ class Video { public: Video(uint16_t *ram); void set_scan_target(Outputs::Display::ScanTarget *scan_target); + void run_for(HalfCycles duration); + + // TODO: feedback on blanks and syncs. private: Outputs::CRT::CRT crt_; + + HalfCycles frame_position_; + size_t video_address_; + uint16_t *ram_; + uint8_t *pixel_buffer_; }; }