2023-12-06 03:37:33 +00:00
|
|
|
|
//
|
|
|
|
|
// CGA.hpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 05/12/2023.
|
|
|
|
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
2024-01-17 04:34:46 +00:00
|
|
|
|
#pragma once
|
2023-12-06 03:37:33 +00:00
|
|
|
|
|
|
|
|
|
#include "../../Components/6845/CRTC6845.hpp"
|
|
|
|
|
#include "../../Outputs/CRT/CRT.hpp"
|
|
|
|
|
#include "../../Machines/Utility/ROMCatalogue.hpp"
|
|
|
|
|
|
|
|
|
|
namespace PCCompatible {
|
|
|
|
|
|
|
|
|
|
class CGA {
|
|
|
|
|
public:
|
2023-12-27 21:15:52 +00:00
|
|
|
|
CGA() : crtc_(outputter_) {}
|
2023-12-06 03:37:33 +00:00
|
|
|
|
|
2023-12-06 04:01:08 +00:00
|
|
|
|
static constexpr uint32_t BaseAddress = 0xb'8000;
|
|
|
|
|
static constexpr auto FontROM = ROM::Name::PCCompatibleCGAFont;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
|
|
|
|
|
void set_source(const uint8_t *ram, std::vector<uint8_t> font) {
|
|
|
|
|
outputter_.ram = ram;
|
|
|
|
|
outputter_.font = font;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void run_for(Cycles cycles) {
|
|
|
|
|
// Input rate is the PIT rate of 1,193,182 Hz.
|
2023-12-07 17:05:15 +00:00
|
|
|
|
// CGA is clocked at the real oscillator rate of 12 times that.
|
|
|
|
|
// But there's also an internal divide by 8 to align to the 80-cfetch clock.
|
|
|
|
|
// ... and 12/8 = 3/2.
|
|
|
|
|
full_clock_ += 3 * cycles.as<int>();
|
2023-12-06 14:59:14 +00:00
|
|
|
|
|
2023-12-07 17:05:15 +00:00
|
|
|
|
const int modulo = 2 * outputter_.clock_divider;
|
2023-12-06 14:59:14 +00:00
|
|
|
|
crtc_.run_for(Cycles(full_clock_ / modulo));
|
|
|
|
|
full_clock_ %= modulo;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <int address>
|
|
|
|
|
void write(uint8_t value) {
|
2023-12-06 16:19:04 +00:00
|
|
|
|
switch(address) {
|
|
|
|
|
case 0: case 2: case 4: case 6:
|
2023-12-06 03:37:33 +00:00
|
|
|
|
crtc_.select_register(value);
|
2023-12-06 16:19:04 +00:00
|
|
|
|
break;
|
|
|
|
|
case 1: case 3: case 5: case 7:
|
|
|
|
|
crtc_.set_register(value);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x8: outputter_.set_mode(value); break;
|
|
|
|
|
case 0x9: outputter_.set_colours(value); break;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <int address>
|
|
|
|
|
uint8_t read() {
|
2023-12-06 04:01:08 +00:00
|
|
|
|
switch(address) {
|
2023-12-06 16:19:04 +00:00
|
|
|
|
case 1: case 3: case 5: case 7:
|
|
|
|
|
return crtc_.get_register();
|
|
|
|
|
|
2023-12-06 04:01:08 +00:00
|
|
|
|
case 0xa:
|
|
|
|
|
return
|
|
|
|
|
// b3: 1 => in vsync; 0 => not;
|
|
|
|
|
// b2: 1 => light pen switch is off;
|
|
|
|
|
// b1: 1 => positive edge from light pen has set trigger;
|
|
|
|
|
// b0: 1 => safe to write to VRAM now without causing snow.
|
|
|
|
|
(crtc_.get_bus_state().vsync ? 0b1001 : 0b0000) |
|
2023-12-08 16:02:41 +00:00
|
|
|
|
(crtc_.get_bus_state().display_enable ? 0b0000 : 0b0001) |
|
2023-12-06 04:01:08 +00:00
|
|
|
|
0b0100;
|
2023-12-06 16:19:04 +00:00
|
|
|
|
|
|
|
|
|
default: return 0xff;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 19:44:59 +00:00
|
|
|
|
// MARK: - Display type configuration.
|
|
|
|
|
|
2023-12-06 18:55:41 +00:00
|
|
|
|
void set_display_type(Outputs::Display::DisplayType display_type) {
|
|
|
|
|
outputter_.crt.set_display_type(display_type);
|
2023-12-07 19:21:09 +00:00
|
|
|
|
outputter_.set_is_composite(Outputs::Display::is_composite(display_type));
|
2023-12-06 18:55:41 +00:00
|
|
|
|
}
|
|
|
|
|
Outputs::Display::DisplayType get_display_type() const {
|
|
|
|
|
return outputter_.crt.get_display_type();
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 03:37:33 +00:00
|
|
|
|
// MARK: - Call-ins for ScanProducer.
|
|
|
|
|
|
|
|
|
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
|
|
|
|
outputter_.crt.set_scan_target(scan_target);
|
|
|
|
|
}
|
|
|
|
|
Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
2023-12-07 17:17:19 +00:00
|
|
|
|
// The CRT is always handed data at the full CGA pixel clock rate, so just
|
|
|
|
|
// divide by 12 to get back to the rate that run_for is being called at.
|
|
|
|
|
return outputter_.crt.get_scaled_scan_status() / 12.0f;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
struct CRTCOutputter {
|
2023-12-06 18:10:28 +00:00
|
|
|
|
enum class OutputState {
|
|
|
|
|
Sync, Pixels, Border, ColourBurst
|
|
|
|
|
};
|
|
|
|
|
|
2023-12-06 03:37:33 +00:00
|
|
|
|
CRTCOutputter() :
|
2023-12-06 18:12:58 +00:00
|
|
|
|
crt(910, 8, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red2Green2Blue2)
|
2023-12-06 03:37:33 +00:00
|
|
|
|
{
|
2023-12-06 18:55:41 +00:00
|
|
|
|
crt.set_visible_area(Outputs::Display::Rect(0.097f, 0.095f, 0.82f, 0.82f));
|
2023-12-07 15:03:52 +00:00
|
|
|
|
crt.set_display_type(Outputs::Display::DisplayType::RGB);
|
2023-12-06 03:37:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 04:01:08 +00:00
|
|
|
|
void set_mode(uint8_t control) {
|
2023-12-06 03:37:33 +00:00
|
|
|
|
// b5: enable blink
|
2023-12-06 04:01:08 +00:00
|
|
|
|
// b4: 1 => 640x200 graphics
|
|
|
|
|
// b3: video enable
|
|
|
|
|
// b2: 1 => monochrome
|
|
|
|
|
// b1: 1 => 320x200 graphics; 0 => text
|
|
|
|
|
// b0: 1 => 80-column text; 0 => 40
|
2023-12-06 18:10:28 +00:00
|
|
|
|
|
|
|
|
|
control_ = control; // To capture blink, monochrome and video enable bits.
|
2023-12-06 04:01:08 +00:00
|
|
|
|
|
|
|
|
|
if(control & 0x2) {
|
2023-12-06 15:30:30 +00:00
|
|
|
|
mode_ = (control & 0x10) ? Mode::Pixels640 : Mode::Pixels320;
|
2023-12-06 15:41:20 +00:00
|
|
|
|
pixels_per_tick = (mode_ == Mode::Pixels640) ? 16 : 8;
|
2023-12-06 04:01:08 +00:00
|
|
|
|
} else {
|
2023-12-06 14:59:14 +00:00
|
|
|
|
mode_ = Mode::Text;
|
2023-12-06 15:41:20 +00:00
|
|
|
|
pixels_per_tick = 8;
|
2023-12-06 04:01:08 +00:00
|
|
|
|
}
|
2023-12-06 15:30:30 +00:00
|
|
|
|
clock_divider = 1 + !(control & 0x01);
|
2023-12-07 18:11:20 +00:00
|
|
|
|
|
|
|
|
|
// Both graphics mode and monochrome/colour may have changed, so update the palette.
|
|
|
|
|
update_palette();
|
2023-12-06 03:37:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-07 19:21:09 +00:00
|
|
|
|
void set_is_composite(bool is_composite) {
|
|
|
|
|
is_composite_ = is_composite;
|
|
|
|
|
update_palette();
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 16:19:04 +00:00
|
|
|
|
void set_colours(uint8_t value) {
|
2023-12-07 18:11:20 +00:00
|
|
|
|
colours_ = value;
|
|
|
|
|
update_palette();
|
2023-12-06 16:19:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 03:37:33 +00:00
|
|
|
|
uint8_t control() {
|
|
|
|
|
return control_;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 18:10:28 +00:00
|
|
|
|
void update_hsync(bool new_hsync) {
|
|
|
|
|
if(new_hsync == previous_hsync) {
|
|
|
|
|
cycles_since_hsync += clock_divider;
|
|
|
|
|
} else {
|
|
|
|
|
cycles_since_hsync = 0;
|
|
|
|
|
previous_hsync = new_hsync;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OutputState implied_state(const Motorola::CRTC::BusState &state) const {
|
|
|
|
|
OutputState new_state;
|
|
|
|
|
|
|
|
|
|
if(state.hsync || state.vsync) {
|
|
|
|
|
new_state = OutputState::Sync;
|
|
|
|
|
} else if(!state.display_enable || !(control_&0x08)) {
|
|
|
|
|
new_state = OutputState::Border;
|
|
|
|
|
|
2023-12-07 18:11:20 +00:00
|
|
|
|
// TODO: this isn't correct for colour burst positioning, though
|
2023-12-06 18:15:17 +00:00
|
|
|
|
// it happens to fool the particular CRT I've implemented.
|
2023-12-07 18:11:20 +00:00
|
|
|
|
if(!(control_&4) && cycles_since_hsync <= 6) {
|
2023-12-06 18:10:28 +00:00
|
|
|
|
new_state = OutputState::ColourBurst;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
new_state = OutputState::Pixels;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new_state;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 03:37:33 +00:00
|
|
|
|
void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) {
|
|
|
|
|
// Determine new output state.
|
2023-12-06 18:10:28 +00:00
|
|
|
|
update_hsync(state.hsync);
|
|
|
|
|
const OutputState new_state = implied_state(state);
|
2023-12-08 16:55:00 +00:00
|
|
|
|
static constexpr uint8_t colour_phase = 200;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
|
|
|
|
|
// Upon either a state change or just having accumulated too much local time...
|
2023-12-07 18:11:20 +00:00
|
|
|
|
if(
|
|
|
|
|
new_state != output_state ||
|
|
|
|
|
active_pixels_per_tick != pixels_per_tick ||
|
|
|
|
|
active_clock_divider != clock_divider ||
|
|
|
|
|
active_border_colour != border_colour ||
|
|
|
|
|
count > 912
|
|
|
|
|
) {
|
2023-12-06 03:37:33 +00:00
|
|
|
|
// (1) flush preexisting state.
|
|
|
|
|
if(count) {
|
|
|
|
|
switch(output_state) {
|
2023-12-08 16:55:00 +00:00
|
|
|
|
case OutputState::Sync: crt.output_sync(count * active_clock_divider); break;
|
2023-12-07 18:11:20 +00:00
|
|
|
|
case OutputState::Border:
|
|
|
|
|
if(active_border_colour) {
|
|
|
|
|
crt.output_blank(count * active_clock_divider);
|
|
|
|
|
} else {
|
|
|
|
|
crt.output_level<uint8_t>(count * active_clock_divider, active_border_colour);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2023-12-08 16:55:00 +00:00
|
|
|
|
case OutputState::ColourBurst: crt.output_colour_burst(count * active_clock_divider, colour_phase); break;
|
|
|
|
|
case OutputState::Pixels: flush_pixels(); break;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// (2) adopt new state.
|
|
|
|
|
output_state = new_state;
|
2023-12-06 15:41:20 +00:00
|
|
|
|
active_pixels_per_tick = pixels_per_tick;
|
|
|
|
|
active_clock_divider = clock_divider;
|
2023-12-07 18:11:20 +00:00
|
|
|
|
active_border_colour = border_colour;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect pixels if applicable.
|
|
|
|
|
if(output_state == OutputState::Pixels) {
|
|
|
|
|
if(!pixels) {
|
|
|
|
|
pixel_pointer = pixels = crt.begin_data(DefaultAllocationSize);
|
|
|
|
|
|
|
|
|
|
// Flush any period where pixels weren't recorded due to back pressure.
|
|
|
|
|
if(pixels && count) {
|
2023-12-06 15:41:20 +00:00
|
|
|
|
crt.output_blank(count * active_clock_divider);
|
2023-12-06 03:37:33 +00:00
|
|
|
|
count = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(pixels) {
|
|
|
|
|
if(state.cursor) {
|
2023-12-06 15:41:20 +00:00
|
|
|
|
std::fill(pixel_pointer, pixel_pointer + pixels_per_tick, 0x3f); // i.e. white.
|
2023-12-06 03:37:33 +00:00
|
|
|
|
} else {
|
2023-12-06 15:30:30 +00:00
|
|
|
|
if(mode_ == Mode::Text) {
|
|
|
|
|
serialise_text(state);
|
2023-12-06 03:37:33 +00:00
|
|
|
|
} else {
|
2023-12-06 15:30:30 +00:00
|
|
|
|
serialise_pixels(state);
|
2023-12-06 03:37:33 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-06 16:50:16 +00:00
|
|
|
|
pixel_pointer += active_pixels_per_tick;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Advance.
|
2023-12-06 14:13:47 +00:00
|
|
|
|
count += 8;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
|
|
|
|
|
// Output pixel row prematurely if storage is exhausted.
|
|
|
|
|
if(output_state == OutputState::Pixels && pixel_pointer == pixels + DefaultAllocationSize) {
|
2023-12-06 14:59:14 +00:00
|
|
|
|
flush_pixels();
|
2023-12-06 03:37:33 +00:00
|
|
|
|
count = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &) {}
|
|
|
|
|
|
2023-12-06 14:59:14 +00:00
|
|
|
|
void flush_pixels() {
|
2023-12-06 16:50:16 +00:00
|
|
|
|
crt.output_data(count * active_clock_divider, size_t((count * active_pixels_per_tick) / 8));
|
2023-12-06 14:59:14 +00:00
|
|
|
|
pixels = pixel_pointer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 15:30:30 +00:00
|
|
|
|
void serialise_pixels(const Motorola::CRTC::BusState &state) {
|
|
|
|
|
// This is what I think is happenings:
|
|
|
|
|
//
|
|
|
|
|
// Refresh address is still shifted left one and two bytes are fetched, just as if it were
|
|
|
|
|
// character code + attributes except that these are two bytes worth of graphics.
|
|
|
|
|
//
|
|
|
|
|
// Meanwhile, row address is used to invent a 15th address line.
|
2023-12-07 03:03:24 +00:00
|
|
|
|
const auto base_address =
|
|
|
|
|
((state.refresh_address & 0xfff) << 1) +
|
|
|
|
|
((state.row_address & 1) << 13);
|
2023-12-06 15:41:20 +00:00
|
|
|
|
const uint8_t bitmap[] = {
|
|
|
|
|
ram[base_address],
|
|
|
|
|
ram[base_address + 1],
|
|
|
|
|
};
|
2023-12-06 15:30:30 +00:00
|
|
|
|
|
2023-12-06 16:19:04 +00:00
|
|
|
|
if(mode_ == Mode::Pixels320) {
|
2023-12-06 19:38:43 +00:00
|
|
|
|
pixel_pointer[0] = palette320[(bitmap[0] & 0xc0) >> 6];
|
|
|
|
|
pixel_pointer[1] = palette320[(bitmap[0] & 0x30) >> 4];
|
|
|
|
|
pixel_pointer[2] = palette320[(bitmap[0] & 0x0c) >> 2];
|
|
|
|
|
pixel_pointer[3] = palette320[(bitmap[0] & 0x03) >> 0];
|
|
|
|
|
pixel_pointer[4] = palette320[(bitmap[1] & 0xc0) >> 6];
|
|
|
|
|
pixel_pointer[5] = palette320[(bitmap[1] & 0x30) >> 4];
|
|
|
|
|
pixel_pointer[6] = palette320[(bitmap[1] & 0x0c) >> 2];
|
|
|
|
|
pixel_pointer[7] = palette320[(bitmap[1] & 0x03) >> 0];
|
2023-12-06 16:19:04 +00:00
|
|
|
|
} else {
|
2023-12-06 19:38:43 +00:00
|
|
|
|
pixel_pointer[0x0] = palette640[(bitmap[0] & 0x80) >> 7];
|
|
|
|
|
pixel_pointer[0x1] = palette640[(bitmap[0] & 0x40) >> 6];
|
|
|
|
|
pixel_pointer[0x2] = palette640[(bitmap[0] & 0x20) >> 5];
|
|
|
|
|
pixel_pointer[0x3] = palette640[(bitmap[0] & 0x10) >> 4];
|
|
|
|
|
pixel_pointer[0x4] = palette640[(bitmap[0] & 0x08) >> 3];
|
|
|
|
|
pixel_pointer[0x5] = palette640[(bitmap[0] & 0x04) >> 2];
|
|
|
|
|
pixel_pointer[0x6] = palette640[(bitmap[0] & 0x02) >> 1];
|
|
|
|
|
pixel_pointer[0x7] = palette640[(bitmap[0] & 0x01) >> 0];
|
|
|
|
|
pixel_pointer[0x8] = palette640[(bitmap[1] & 0x80) >> 7];
|
|
|
|
|
pixel_pointer[0x9] = palette640[(bitmap[1] & 0x40) >> 6];
|
|
|
|
|
pixel_pointer[0xa] = palette640[(bitmap[1] & 0x20) >> 5];
|
|
|
|
|
pixel_pointer[0xb] = palette640[(bitmap[1] & 0x10) >> 4];
|
|
|
|
|
pixel_pointer[0xc] = palette640[(bitmap[1] & 0x08) >> 3];
|
|
|
|
|
pixel_pointer[0xd] = palette640[(bitmap[1] & 0x04) >> 2];
|
|
|
|
|
pixel_pointer[0xe] = palette640[(bitmap[1] & 0x02) >> 1];
|
|
|
|
|
pixel_pointer[0xf] = palette640[(bitmap[1] & 0x01) >> 0];
|
2023-12-06 16:19:04 +00:00
|
|
|
|
}
|
2023-12-06 15:30:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void serialise_text(const Motorola::CRTC::BusState &state) {
|
2023-12-07 03:03:24 +00:00
|
|
|
|
const uint8_t attributes = ram[((state.refresh_address << 1) + 1) & 0x3fff];
|
|
|
|
|
const uint8_t glyph = ram[((state.refresh_address << 1) + 0) & 0x3fff];
|
2023-12-06 15:30:30 +00:00
|
|
|
|
const uint8_t row = font[(glyph * 8) + state.row_address];
|
|
|
|
|
|
2023-12-07 18:11:20 +00:00
|
|
|
|
uint8_t colours[2] = { rgb(attributes >> 4), rgbi(attributes) };
|
2023-12-06 15:30:30 +00:00
|
|
|
|
|
|
|
|
|
// Apply blink or background intensity.
|
|
|
|
|
if(control_ & 0x20) {
|
2023-12-08 16:55:00 +00:00
|
|
|
|
// Set both colours to black if within a blink; otherwise consider a yellow-to-brown conversion.
|
2023-12-06 15:30:30 +00:00
|
|
|
|
if((attributes & 0x80) && (state.field_count & 16)) {
|
2023-12-08 16:55:00 +00:00
|
|
|
|
colours[0] = colours[1] = 0;
|
|
|
|
|
} else {
|
|
|
|
|
colours[0] = yellow_to_brown(colours[0]);
|
2023-12-06 15:30:30 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if(attributes & 0x80) {
|
2023-12-07 18:11:20 +00:00
|
|
|
|
colours[0] = bright(colours[0]);
|
2023-12-08 16:55:00 +00:00
|
|
|
|
} else {
|
|
|
|
|
// Yellow to brown definitely doesn't apply if the colour has been brightened.
|
|
|
|
|
colours[0] = yellow_to_brown(colours[0]);
|
2023-12-06 15:30:30 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw according to ROM contents.
|
|
|
|
|
pixel_pointer[0] = (row & 0x80) ? colours[1] : colours[0];
|
|
|
|
|
pixel_pointer[1] = (row & 0x40) ? colours[1] : colours[0];
|
|
|
|
|
pixel_pointer[2] = (row & 0x20) ? colours[1] : colours[0];
|
|
|
|
|
pixel_pointer[3] = (row & 0x10) ? colours[1] : colours[0];
|
|
|
|
|
pixel_pointer[4] = (row & 0x08) ? colours[1] : colours[0];
|
|
|
|
|
pixel_pointer[5] = (row & 0x04) ? colours[1] : colours[0];
|
|
|
|
|
pixel_pointer[6] = (row & 0x02) ? colours[1] : colours[0];
|
|
|
|
|
pixel_pointer[7] = (row & 0x01) ? colours[1] : colours[0];
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-06 03:37:33 +00:00
|
|
|
|
Outputs::CRT::CRT crt;
|
2023-12-06 15:41:20 +00:00
|
|
|
|
static constexpr size_t DefaultAllocationSize = 320;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
|
2023-12-06 15:41:20 +00:00
|
|
|
|
// Current output stream.
|
2023-12-06 03:37:33 +00:00
|
|
|
|
uint8_t *pixels = nullptr;
|
|
|
|
|
uint8_t *pixel_pointer = nullptr;
|
2023-12-06 15:41:20 +00:00
|
|
|
|
int active_pixels_per_tick = 8;
|
|
|
|
|
int active_clock_divider = 1;
|
2023-12-07 18:11:20 +00:00
|
|
|
|
uint8_t active_border_colour = 0;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
|
2023-12-06 15:41:20 +00:00
|
|
|
|
// Source data.
|
2023-12-06 03:37:33 +00:00
|
|
|
|
const uint8_t *ram = nullptr;
|
|
|
|
|
std::vector<uint8_t> font;
|
|
|
|
|
|
2023-12-06 15:41:20 +00:00
|
|
|
|
// CRTC state tracking, for CRT serialisation.
|
2023-12-06 18:10:28 +00:00
|
|
|
|
OutputState output_state = OutputState::Sync;
|
2023-12-06 15:41:20 +00:00
|
|
|
|
int count = 0;
|
2023-12-06 04:01:08 +00:00
|
|
|
|
|
2023-12-06 18:10:28 +00:00
|
|
|
|
bool previous_hsync = false;
|
|
|
|
|
int cycles_since_hsync = 0;
|
|
|
|
|
|
2023-12-06 15:41:20 +00:00
|
|
|
|
// Current Programmer-set parameters.
|
|
|
|
|
int clock_divider = 1;
|
|
|
|
|
int pixels_per_tick = 8;
|
2023-12-07 18:11:20 +00:00
|
|
|
|
uint8_t colours_ = 0;
|
2023-12-06 15:41:20 +00:00
|
|
|
|
uint8_t control_ = 0;
|
2023-12-07 19:21:09 +00:00
|
|
|
|
bool is_composite_ = false;
|
2023-12-06 04:01:08 +00:00
|
|
|
|
enum class Mode {
|
2023-12-06 15:30:30 +00:00
|
|
|
|
Pixels640, Pixels320, Text,
|
2023-12-06 14:59:14 +00:00
|
|
|
|
} mode_ = Mode::Text;
|
2023-12-06 15:41:20 +00:00
|
|
|
|
|
2023-12-06 19:38:43 +00:00
|
|
|
|
uint8_t palette320[4]{};
|
|
|
|
|
uint8_t palette640[2]{};
|
2023-12-07 18:11:20 +00:00
|
|
|
|
uint8_t border_colour;
|
|
|
|
|
|
|
|
|
|
void update_palette() {
|
|
|
|
|
// b5: 320x200 palette, unless in monochrome mode.
|
|
|
|
|
if(control_ & 0x04) {
|
|
|
|
|
palette320[1] = DarkCyan;
|
|
|
|
|
palette320[2] = DarkRed;
|
|
|
|
|
palette320[3] = DarkGrey;
|
|
|
|
|
} else {
|
|
|
|
|
if(colours_ & 0x20) {
|
|
|
|
|
palette320[1] = DarkCyan;
|
|
|
|
|
palette320[2] = DarkMagenta;
|
|
|
|
|
palette320[3] = DarkGrey;
|
|
|
|
|
} else {
|
|
|
|
|
palette320[1] = DarkGreen;
|
|
|
|
|
palette320[2] = DarkRed;
|
|
|
|
|
palette320[3] = DarkYellow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// b4: set 320x200 palette into high intensity.
|
|
|
|
|
if(colours_ & 0x10) {
|
|
|
|
|
palette320[1] = bright(palette320[1]);
|
|
|
|
|
palette320[2] = bright(palette320[2]);
|
|
|
|
|
palette320[3] = bright(palette320[3]);
|
|
|
|
|
} else {
|
|
|
|
|
// Remap dark yellow to brown if applicable.
|
|
|
|
|
palette320[3] = yellow_to_brown(palette320[3]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// b3–b0: set background, border, monochrome colour.
|
|
|
|
|
palette640[1] = palette320[0] = rgbi(colours_);
|
|
|
|
|
border_colour = (mode_ != Mode::Pixels640) ? palette320[0] : 0;
|
|
|
|
|
}
|
2023-12-06 16:19:04 +00:00
|
|
|
|
|
2023-12-07 19:21:09 +00:00
|
|
|
|
//
|
|
|
|
|
// Named colours and mapping logic.
|
|
|
|
|
//
|
|
|
|
|
static constexpr uint8_t DarkCyan = 0b00'10'10;
|
|
|
|
|
static constexpr uint8_t DarkMagenta = 0b10'00'10;
|
|
|
|
|
static constexpr uint8_t DarkGrey = 0b10'10'10;
|
|
|
|
|
|
|
|
|
|
static constexpr uint8_t DarkGreen = 0b00'10'00;
|
|
|
|
|
static constexpr uint8_t DarkRed = 0b10'00'00;
|
|
|
|
|
static constexpr uint8_t DarkYellow = 0b10'10'00;
|
|
|
|
|
|
|
|
|
|
static constexpr uint8_t Brown = 0b10'01'00;
|
|
|
|
|
|
|
|
|
|
/// @returns @c Brown if @c source is @c DarkYellow and composite output is not enabled; @c source otherwise.
|
|
|
|
|
constexpr uint8_t yellow_to_brown(uint8_t source) {
|
|
|
|
|
return (source == DarkYellow && !is_composite_) ? Brown : source;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// @returns The brightened (i.e. high intensity) version of @c source.
|
|
|
|
|
constexpr uint8_t bright(uint8_t source) {
|
|
|
|
|
return source | (source >> 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Maps the RGB TTL triplet @c source to an appropriate output colour.
|
|
|
|
|
constexpr uint8_t rgb(uint8_t source) {
|
|
|
|
|
return uint8_t(
|
|
|
|
|
((source & 0x01) << 1) |
|
|
|
|
|
((source & 0x02) << 2) |
|
|
|
|
|
((source & 0x04) << 3)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Maps the RGBI value in @c source to an appropriate output colour, including potential yellow-to-brown conversion.
|
|
|
|
|
constexpr uint8_t rgbi(uint8_t source) {
|
|
|
|
|
const uint8_t result = rgb(source);
|
|
|
|
|
return (source & 0x10) ? bright(result) : yellow_to_brown(result);
|
|
|
|
|
}
|
2023-12-06 03:37:33 +00:00
|
|
|
|
} outputter_;
|
2023-12-27 21:15:52 +00:00
|
|
|
|
Motorola::CRTC::CRTC6845<
|
|
|
|
|
CRTCOutputter,
|
|
|
|
|
Motorola::CRTC::Personality::HD6845S,
|
|
|
|
|
Motorola::CRTC::CursorType::MDA> crtc_;
|
2023-12-06 03:37:33 +00:00
|
|
|
|
|
2023-12-06 18:55:41 +00:00
|
|
|
|
int full_clock_ = 0;
|
2023-12-07 19:21:09 +00:00
|
|
|
|
|
2023-12-06 03:37:33 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|