From a34f294ba8999c4e64791290d20f19ae570e0bdd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 22 Nov 2020 21:29:40 -0500 Subject: [PATCH] Pulls out commonalities re: NTSC colour, ensures mixed modes on a line works. --- Machines/Apple/AppleII/VideoSwitches.hpp | 2 +- Machines/Apple/AppleIIgs/Video.cpp | 112 ++++++++++++----------- Machines/Apple/AppleIIgs/Video.hpp | 54 ++++++++++- 3 files changed, 112 insertions(+), 56 deletions(-) diff --git a/Machines/Apple/AppleII/VideoSwitches.hpp b/Machines/Apple/AppleII/VideoSwitches.hpp index 0736b0472..2db487bd3 100644 --- a/Machines/Apple/AppleII/VideoSwitches.hpp +++ b/Machines/Apple/AppleII/VideoSwitches.hpp @@ -16,7 +16,7 @@ namespace Apple { namespace II { -// Enumerates all Apple II, IIe and IIgs display modes. +// Enumerates all Apple II and IIe display modes. enum class GraphicsMode { Text = 0, DoubleText, diff --git a/Machines/Apple/AppleIIgs/Video.cpp b/Machines/Apple/AppleIIgs/Video.cpp index aee6a8f8f..78692bf3e 100644 --- a/Machines/Apple/AppleIIgs/Video.cpp +++ b/Machines/Apple/AppleIIgs/Video.cpp @@ -80,6 +80,8 @@ 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; +// TODO: above completely forgets about double high-res, etc, starting half a column earler. Hmmm. + } VideoBase::VideoBase() : @@ -212,12 +214,9 @@ void VideoBase::output_row(int row, int start, int end) { // Fetch and output such pixels as it is time for. if(start >= start_of_pixels && start < start_of_right_border) { const int end_of_period = std::min(start_of_right_border, end); + const auto mode = graphics_mode(row); if(start == start_of_pixels) { -// printf("Begin\n"); - // 640 is the absolute most number of pixels that might be generated - next_pixel_ = pixels_ = reinterpret_cast(crt_.begin_data(640, 2)); - // YUCKY HACK. I do not know when the IIgs fetches its super high-res palette // and control byte. Since I do not know, any guess is equally likely negatively // to affect software. Therefore this hack is as good as any other guess: @@ -236,43 +235,60 @@ void VideoBase::output_row(int row, int start, int end) { set_interrupts(0x20); } - // Reset NTSC decoding. + // Reset NTSC decoding and total line buffering. ntsc_delay_ = 4; + pixels_start_column_ = start; + } + + if(!next_pixel_ || pixels_format_ != format_for_mode(mode)) { + // Flush anything already in a buffer. + if(pixels_start_column_ < start) { + crt_.output_data((start - pixels_start_column_) * CyclesPerTick, next_pixel_ ? size_t(next_pixel_ - pixels_) : 1); + next_pixel_ = pixels_ = nullptr; + } + + // Allocate a new buffer; 640 is as bad as it gets. + // TODO: make proper size estimate? + next_pixel_ = pixels_ = reinterpret_cast(crt_.begin_data(640, 2)); + pixels_start_column_ = start; + pixels_format_ = format_for_mode(mode); } if(next_pixel_) { const int window_start = start - start_of_pixels; const int window_end = end_of_period - start_of_pixels; - if(new_video_ & 0x80) { - next_pixel_ = output_super_high_res(next_pixel_, window_start, window_end, row); - } else { - switch(graphics_mode(row)) { - case Apple::II::GraphicsMode::Text: - next_pixel_ = output_text(next_pixel_, window_start, window_end, row); - break; - case Apple::II::GraphicsMode::DoubleText: - next_pixel_ = output_double_text(next_pixel_, window_start, window_end, row); - break; - case Apple::II::GraphicsMode::LowRes: - next_pixel_ = output_low_resolution(next_pixel_, window_start, window_end, row); - break; - case Apple::II::GraphicsMode::HighRes: - next_pixel_ = output_high_resolution(next_pixel_, window_start, window_end, row); - break; + switch(mode) { + case GraphicsMode::SuperHighRes: + next_pixel_ = output_super_high_res(next_pixel_, window_start, window_end, row); + break; + case GraphicsMode::Text: + next_pixel_ = output_text(next_pixel_, window_start, window_end, row); + break; + case GraphicsMode::DoubleText: + next_pixel_ = output_double_text(next_pixel_, window_start, window_end, row); + break; + case GraphicsMode::LowRes: + next_pixel_ = output_low_resolution(next_pixel_, window_start, window_end, row); + break; + case GraphicsMode::HighRes: + next_pixel_ = output_high_resolution(next_pixel_, window_start, window_end, row); + break; - default: - assert(false); - } - - // TODO: support modes other than 40-column text. -// if(graphics_mode(row) != Apple::II::GraphicsMode::Text) printf("Outputting incorrect graphics mode!\n"); + default: + assert(false); // i.e. other modes yet to do. } } if(end_of_period == start_of_right_border) { -// printf("End\n"); - crt_.output_data((start_of_right_border - start_of_pixels) * CyclesPerTick, next_pixel_ ? size_t(next_pixel_ - pixels_) : 1); + // Flush what remains in the NTSC queue, if applicable. + // TODO: with real NTSC test, why not? + if(next_pixel_ && is_colour_ntsc(mode)) { + ntsc_shift_ >>= 14; + next_pixel_ = output_shift(next_pixel_, 81); + } + + crt_.output_data((start_of_right_border - pixels_start_column_) * CyclesPerTick, next_pixel_ ? size_t(next_pixel_ - pixels_) : 1); next_pixel_ = pixels_ = nullptr; } @@ -452,17 +468,7 @@ uint16_t *VideoBase::output_low_resolution(uint16_t *target, int start, int end, } ntsc_shift_ = (long_source << 18) | (ntsc_shift_ >> 14); - - if(c) { - target = output_shift(target, 2 + (c * 14)); - } else { - ntsc_shift_ |= ntsc_shift_ >> 14; - } - } - - if(end == 40) { - ntsc_shift_ >>= 14; - target = output_shift(target, 2 + (40 * 14)); + target = output_shift(target, 1 + c*2); } return target; @@ -492,24 +498,24 @@ uint16_t *VideoBase::output_high_resolution(uint16_t *target, int start, int end ntsc_shift_ = (doubled_source << 18) | (ntsc_shift_ >> 14); } - // Make sure that at least two columns are enqueued before output begins; - // the top bits can't be understood without reference to bits that come afterwards. - if(c) { - target = output_shift(target, 2 + (c * 14)); - } else { - ntsc_shift_ |= ntsc_shift_ >> 14; - } - } - - if(end == 40) { - ntsc_shift_ >>= 14; - target = output_shift(target, 2 + (40 * 14)); + target = output_shift(target, 1 + c*2); } return target; } -uint16_t *VideoBase::output_shift(uint16_t *target, int phase) { +uint16_t *VideoBase::output_shift(uint16_t *target, int column) { + // Make sure that at least two columns are enqueued before output begins; + // the top bits can't be understood without reference to bits that come afterwards. + if(!column) { + ntsc_shift_ |= ntsc_shift_ >> 14; + return target; + } + + // Phase here is kind of arbitrary; it pairs off with the order + // I've picked for my rolls table and with my decision to count + // columns as aligned with double-mode. + const int phase = column * 7 + 3; constexpr uint8_t rolls[4][16] = { { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf diff --git a/Machines/Apple/AppleIIgs/Video.hpp b/Machines/Apple/AppleIIgs/Video.hpp index e15cff7a5..d4c4be5fb 100644 --- a/Machines/Apple/AppleIIgs/Video.hpp +++ b/Machines/Apple/AppleIIgs/Video.hpp @@ -17,6 +17,35 @@ namespace Apple { namespace IIgs { namespace Video { +// This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs. +enum class GraphicsMode { + Text = 0, + DoubleText, + HighRes, + DoubleHighRes, + LowRes, + DoubleLowRes, + FatLowRes, + + // Additions: + DoubleHighResMono, + SuperHighRes +}; +constexpr bool is_colour_ntsc(GraphicsMode m) { return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes; } + +enum class PixelBufferFormat { + Text, DoubleText, NTSC, NTSCMono, SuperHighRes +}; +constexpr PixelBufferFormat format_for_mode(GraphicsMode m) { + switch(m) { + case GraphicsMode::Text: return PixelBufferFormat::Text; + case GraphicsMode::DoubleText: return PixelBufferFormat::DoubleText; + default: return PixelBufferFormat::NTSC; + case GraphicsMode::DoubleHighResMono: return PixelBufferFormat::NTSCMono; + case GraphicsMode::SuperHighRes: return PixelBufferFormat::SuperHighRes; + } +} + /*! Provides IIgs video output; assumed clocking here is twice the usual Apple II clock. So it'll produce a single line of video every 131 cycles — 65*2 + 1, allowing for the @@ -62,6 +91,24 @@ class VideoBase: public Apple::II::VideoSwitches { private: Outputs::CRT::CRT crt_; + GraphicsMode graphics_mode(int row) const { + if(new_video_ & 0x80) { + return GraphicsMode::SuperHighRes; + } + + const auto ii_mode = Apple::II::VideoSwitches::graphics_mode(row); + switch(ii_mode) { + // Coupling very much assumed here. + case Apple::II::GraphicsMode::DoubleHighRes: + if(new_video_ & 0x20) { + return GraphicsMode::DoubleHighResMono; + } + [[fallthrough]]; + + default: return GraphicsMode(int(ii_mode)); break; + } + } + void advance(Cycles); uint8_t new_video_ = 0x01; @@ -76,8 +123,10 @@ class VideoBase: public Apple::II::VideoSwitches { uint16_t text_colour_ = 0xffff; uint16_t background_colour_ = 0; - // Current pixel output buffer. + // Current pixel output buffer and conceptual format. + PixelBufferFormat pixels_format_; uint16_t *pixels_ = nullptr, *next_pixel_ = nullptr; + int pixels_start_column_; void output_row(int row, int start, int end); @@ -118,7 +167,8 @@ class VideoBase: public Apple::II::VideoSwitches { int ntsc_delay_ = 0; /// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB. - uint16_t *output_shift(uint16_t *target, int phase); + /// Phase is derived from @c column. + uint16_t *output_shift(uint16_t *target, int column); }; class Video: public VideoBase {