2020-11-01 00:39:32 +00:00
|
|
|
|
//
|
|
|
|
|
// Video.hpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 31/10/2020.
|
|
|
|
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
2024-01-17 04:34:46 +00:00
|
|
|
|
#pragma once
|
2020-11-01 00:39:32 +00:00
|
|
|
|
|
|
|
|
|
#include "../AppleII/VideoSwitches.hpp"
|
2020-11-08 01:42:34 +00:00
|
|
|
|
#include "../../../Outputs/CRT/CRT.hpp"
|
2020-11-05 22:56:20 +00:00
|
|
|
|
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
2020-11-01 00:39:32 +00:00
|
|
|
|
|
2023-05-10 21:02:18 +00:00
|
|
|
|
namespace Apple::IIgs::Video {
|
2020-11-01 00:39:32 +00:00
|
|
|
|
|
2020-11-08 00:40:26 +00:00
|
|
|
|
/*!
|
2020-11-26 17:54:20 +00:00
|
|
|
|
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
|
2020-11-08 00:40:26 +00:00
|
|
|
|
stretched cycle.
|
|
|
|
|
*/
|
2020-11-26 17:54:20 +00:00
|
|
|
|
class Video: public Apple::II::VideoSwitches<Cycles> {
|
2024-12-05 03:29:08 +00:00
|
|
|
|
public:
|
|
|
|
|
Video();
|
|
|
|
|
void set_internal_ram(const uint8_t *);
|
2020-11-01 00:39:32 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
bool get_is_vertical_blank(Cycles offset);
|
|
|
|
|
uint8_t get_horizontal_counter(Cycles offset);
|
|
|
|
|
uint8_t get_vertical_counter(Cycles offset);
|
2020-11-05 22:56:20 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
void set_new_video(uint8_t);
|
|
|
|
|
uint8_t get_new_video();
|
2020-11-05 22:56:20 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
void clear_interrupts(uint8_t);
|
|
|
|
|
uint8_t get_interrupt_register();
|
|
|
|
|
void set_interrupt_register(uint8_t);
|
|
|
|
|
bool get_interrupt_line();
|
2020-11-05 22:56:20 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
void notify_clock_tick();
|
2020-11-08 04:14:50 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
void set_border_colour(uint8_t);
|
|
|
|
|
void set_text_colour(uint8_t);
|
|
|
|
|
uint8_t get_text_colour();
|
|
|
|
|
uint8_t get_border_colour();
|
2020-11-05 22:56:20 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
void set_composite_is_colour(bool);
|
|
|
|
|
bool get_composite_is_colour();
|
2020-11-10 02:54:25 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
/// Sets the scan target.
|
|
|
|
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
2020-11-08 01:42:34 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
/// Gets the current scan status.
|
|
|
|
|
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
2020-11-08 01:42:34 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
/// Sets the type of output.
|
|
|
|
|
void set_display_type(Outputs::Display::DisplayType);
|
2020-11-08 01:42:34 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
/// Gets the type of output.
|
|
|
|
|
Outputs::Display::DisplayType get_display_type() const;
|
2020-11-08 01:42:34 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
/// Determines the period until video might autonomously update its interrupt lines.
|
|
|
|
|
Cycles next_sequence_point() const;
|
2020-11-16 19:42:50 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
/// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are
|
|
|
|
|
/// generated here.
|
|
|
|
|
void set_megaii_interrupts_enabled(uint8_t);
|
2020-11-30 02:21:46 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
uint8_t get_megaii_interrupt_status();
|
2020-11-30 02:21:46 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
void clear_megaii_interrupts();
|
2020-11-30 02:21:46 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
private:
|
|
|
|
|
Outputs::CRT::CRT crt_;
|
2020-11-26 17:54:20 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
// This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs.
|
|
|
|
|
enum class GraphicsMode {
|
|
|
|
|
Text = 0,
|
|
|
|
|
DoubleText,
|
|
|
|
|
HighRes,
|
|
|
|
|
DoubleHighRes,
|
|
|
|
|
LowRes,
|
|
|
|
|
DoubleLowRes,
|
|
|
|
|
FatLowRes,
|
2020-11-23 02:29:40 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
// Additions:
|
|
|
|
|
DoubleHighResMono,
|
|
|
|
|
SuperHighRes
|
|
|
|
|
};
|
|
|
|
|
constexpr bool is_colour_ntsc(const GraphicsMode m) {
|
|
|
|
|
return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes;
|
|
|
|
|
}
|
2020-11-23 02:29:40 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
GraphicsMode graphics_mode(int row) const {
|
|
|
|
|
if(new_video_ & 0x80) {
|
|
|
|
|
return GraphicsMode::SuperHighRes;
|
2020-11-23 02:29:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
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]];
|
2020-11-26 17:54:20 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-05 22:56:20 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
void advance(Cycles);
|
2023-08-18 18:30:40 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
uint8_t new_video_ = 0x01;
|
2023-08-18 18:30:40 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
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();
|
|
|
|
|
}
|
2023-08-18 18:30:40 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
void clear(uint8_t value) {
|
|
|
|
|
// Zeroes in bits 5 or 6 clear the respective interrupts.
|
|
|
|
|
value_ &= value | ~0x60;
|
|
|
|
|
test();
|
|
|
|
|
}
|
2023-08-18 18:30:40 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
void set_control(uint8_t value) {
|
|
|
|
|
// Ones in bits 1 or 2 enable the respective interrupts.
|
|
|
|
|
value_ = (value_ & ~0x6) | (value & 0x6);
|
|
|
|
|
test();
|
|
|
|
|
}
|
2023-08-18 18:30:40 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
uint8_t status() const {
|
|
|
|
|
return value_;
|
|
|
|
|
}
|
2023-08-18 18:30:40 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
bool active() const {
|
|
|
|
|
return value_ & 0x80;
|
|
|
|
|
}
|
2023-08-18 18:30:40 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
private:
|
|
|
|
|
void test() {
|
|
|
|
|
value_ &= 0x7f;
|
|
|
|
|
if((value_ >> 4) & value_ & 0x6) {
|
|
|
|
|
value_ |= 0x80;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-21 23:07:51 +00:00
|
|
|
|
|
2024-12-05 03:29:08 +00:00
|
|
|
|
// Overall meaning of value is as per the VGC interrupt register, i.e.
|
2020-11-23 00:10:05 +00:00
|
|
|
|
//
|
2024-12-05 03:29:08 +00:00
|
|
|
|
// 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.
|
2020-11-01 00:39:32 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|