1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-30 04:50:08 +00:00
CLK/Machines/Apple/AppleIIgs/Video.hpp

258 lines
7.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Video.hpp
// Clock Signal
//
// Created by Thomas Harte on 31/10/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef Apple_IIgs_Video_hpp
#define Apple_IIgs_Video_hpp
#include "../AppleII/VideoSwitches.hpp"
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
namespace Apple::IIgs::Video {
/*!
Provides IIgs video output; assumed clocking here is seven times the usual Apple II clock.
So it'll produce a single line of video every 456 cycles — 65*7 + 1, allowing for the
stretched cycle.
*/
class Video: public Apple::II::VideoSwitches<Cycles> {
public:
Video();
void set_internal_ram(const uint8_t *);
bool get_is_vertical_blank(Cycles offset);
uint8_t get_horizontal_counter(Cycles offset);
uint8_t get_vertical_counter(Cycles offset);
void set_new_video(uint8_t);
uint8_t get_new_video();
void clear_interrupts(uint8_t);
uint8_t get_interrupt_register();
void set_interrupt_register(uint8_t);
bool get_interrupt_line();
void notify_clock_tick();
void set_border_colour(uint8_t);
void set_text_colour(uint8_t);
uint8_t get_text_colour();
uint8_t get_border_colour();
void set_composite_is_colour(bool);
bool get_composite_is_colour();
/// 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;
/// Determines the period until video might autonomously update its interrupt lines.
Cycles next_sequence_point() const;
/// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are
/// generated here.
void set_megaii_interrupts_enabled(uint8_t);
uint8_t get_megaii_interrupt_status();
void clear_megaii_interrupts();
private:
Outputs::CRT::CRT crt_;
// 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; }
GraphicsMode graphics_mode(int row) const {
if(new_video_ & 0x80) {
return GraphicsMode::SuperHighRes;
}
const auto ii_mode = Apple::II::VideoSwitches<Cycles>::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;
}
}
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;
}
}
void advance(Cycles);
uint8_t new_video_ = 0x01;
class Interrupts {
public:
void add(uint8_t value) {
// Taken literally, status accumulates regardless of being enabled,
// potentially to be polled, it simply doesn't trigger an interrupt.
value_ |= value;
test();
}
void clear(uint8_t value) {
// Zeroes in bits 5 or 6 clear the respective interrupts.
value_ &= value | ~0x60;
test();
}
void set_control(uint8_t value) {
// Ones in bits 1 or 2 enable the respective interrupts.
value_ = (value_ & ~0x6) | (value & 0x6);
test();
}
uint8_t status() const {
return value_;
}
bool active() const {
return value_ & 0x80;
}
private:
void test() {
value_ &= 0x7f;
if((value_ >> 4) & value_ & 0x6) {
value_ |= 0x80;
}
}
// Overall meaning of value is as per the VGC interrupt register, i.e.
//
// b7: interrupt status;
// b6: 1-second interrupt status;
// b5: scan-line interrupt status;
// b4: reserved;
// b3: reserved;
// b2: 1-second interrupt enable;
// b1: scan-line interrupt enable;
// b0: reserved.
uint8_t value_ = 0x00;
} interrupts_;
int cycles_into_frame_ = 0;
const uint8_t *ram_ = nullptr;
// The modal colours.
uint16_t border_colour_ = 0;
uint8_t border_colour_entry_ = 0;
uint8_t text_colour_entry_ = 0xf0;
uint16_t text_colour_ = 0xffff;
uint16_t background_colour_ = 0;
// 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);
uint16_t *output_super_high_res(uint16_t *target, int start, int end, int row) const;
uint16_t *output_text(uint16_t *target, int start, int end, int row) const;
uint16_t *output_double_text(uint16_t *target, int start, int end, int row) const;
uint16_t *output_char(uint16_t *target, uint8_t source, int row) const;
uint16_t *output_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_fat_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_high_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_high_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_high_resolution_mono(uint16_t *target, int start, int end, int row);
// Super high-res per-line state.
uint8_t line_control_;
uint16_t palette_[16];
// Storage used for fill mode.
uint16_t *palette_zero_[4] = {nullptr, nullptr, nullptr, nullptr}, palette_throwaway_;
// Lookup tables and state to assist in the IIgs' mapping from NTSC to RGB.
//
// My understanding of the real-life algorithm is: maintain a four-bit buffer.
// Fill it in a circular fashion. Ordinarily, output the result of looking
// up the RGB mapping of those four bits of Apple II output (which outputs four
// bits per NTSC colour cycle), commuted as per current phase. But if the bit
// being inserted differs from that currently in its position in the shift
// register, hold the existing output for three shifts.
//
// From there I am using the following:
// Maps from:
//
// b0 = b0 of the shift register
// b1 = b4 of the shift register
// b2 = current delay count
//
// to a new delay count.
uint8_t ntsc_delay_lookup_[20];
uint32_t ntsc_shift_ = 0; // Assumption here: logical shifts will ensue, rather than arithmetic.
int ntsc_delay_ = 0;
/// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB.
/// Phase is derived from @c column.
uint16_t *output_shift(uint16_t *target, int column);
// Common getter for the two counters.
struct Counters {
Counters(int v, int h) : vertical(v), horizontal(h) {}
const int vertical, horizontal;
};
Counters get_counters(Cycles offset);
// Marshalls the Mega II-style interrupt state.
uint8_t megaii_interrupt_mask_ = 0;
uint8_t megaii_interrupt_state_ = 0;
int megaii_frame_counter_ = 0; // To count up to quarter-second interrupts.
};
}
#endif /* Video_hpp */