2021-03-18 16:47:48 +00:00
|
|
|
|
//
|
|
|
|
|
// Video.hpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 18/03/2021.
|
|
|
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#ifndef Video_hpp
|
|
|
|
|
#define Video_hpp
|
|
|
|
|
|
2021-03-19 01:54:42 +00:00
|
|
|
|
#include "../../../Outputs/CRT/CRT.hpp"
|
2021-03-19 01:29:52 +00:00
|
|
|
|
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
|
|
|
|
|
2021-03-19 02:29:24 +00:00
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
2021-03-18 16:47:48 +00:00
|
|
|
|
namespace Sinclair {
|
|
|
|
|
namespace ZXSpectrum {
|
|
|
|
|
|
|
|
|
|
enum class VideoTiming {
|
2021-04-15 01:37:10 +00:00
|
|
|
|
FortyEightK,
|
|
|
|
|
OneTwoEightK,
|
|
|
|
|
Plus3,
|
2021-03-18 16:47:48 +00:00
|
|
|
|
};
|
|
|
|
|
|
2021-03-19 01:29:52 +00:00
|
|
|
|
/*
|
|
|
|
|
Timing notes:
|
|
|
|
|
|
|
|
|
|
As of the +2a/+3:
|
|
|
|
|
|
|
|
|
|
311 lines, 228 cycles/line
|
|
|
|
|
Delays begin at 14361, follow the pattern 1, 0, 7, 6, 5, 4, 3, 2; run for 129 cycles/line.
|
|
|
|
|
Possibly delays only affect actual reads and writes; documentation is unclear.
|
|
|
|
|
|
|
|
|
|
Unknowns, to me, presently:
|
|
|
|
|
|
|
|
|
|
How long the interrupt line held for.
|
|
|
|
|
|
|
|
|
|
So...
|
|
|
|
|
|
|
|
|
|
Probably two bytes of video and attribute are fetched in each 8-cycle block,
|
|
|
|
|
with 16 such blocks therefore providing the whole visible display, an island
|
|
|
|
|
within 28.5 blocks horizontally.
|
|
|
|
|
|
|
|
|
|
14364 is 228*63, so I I guess almost 63 lines run from the start of vertical
|
|
|
|
|
blank through to the top of the display, implying 56 lines on to vertical blank.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
2021-03-18 16:47:48 +00:00
|
|
|
|
template <VideoTiming timing> class Video {
|
2021-03-19 01:29:52 +00:00
|
|
|
|
private:
|
|
|
|
|
struct Timings {
|
2021-03-23 02:18:38 +00:00
|
|
|
|
// Number of cycles per line. Will be 224 or 228.
|
2021-03-19 01:29:52 +00:00
|
|
|
|
int cycles_per_line;
|
2021-03-23 02:18:38 +00:00
|
|
|
|
// Number of lines comprising a whole frame. Will be 311 or 312.
|
2021-03-19 01:29:52 +00:00
|
|
|
|
int lines_per_frame;
|
2021-03-23 02:18:38 +00:00
|
|
|
|
|
|
|
|
|
// Number of cycles before first pixel fetch that contention starts to be applied.
|
|
|
|
|
int contention_leadin;
|
|
|
|
|
// Period in a line for which contention is applied.
|
|
|
|
|
int contention_duration;
|
|
|
|
|
|
2021-04-01 16:44:00 +00:00
|
|
|
|
// Number of cycles after first pixel fetch at which interrupt is first signalled.
|
|
|
|
|
int interrupt_time;
|
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
// Contention to apply, in half-cycles, as a function of number of half cycles since
|
|
|
|
|
// contention began.
|
2021-03-19 01:29:52 +00:00
|
|
|
|
int delays[16];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static constexpr Timings get_timings() {
|
2021-04-15 02:23:27 +00:00
|
|
|
|
if constexpr (timing == VideoTiming::Plus3) {
|
|
|
|
|
// Amstrad gate array timings, classic statement:
|
|
|
|
|
//
|
|
|
|
|
// Contention begins 14361 cycles "after interrupt" and follows the pattern [1, 0, 7, 6 5 4, 3, 2].
|
|
|
|
|
// The first four bytes of video are fetched at 14365–14368 cycles, in the order [pixels, attribute, pixels, attribute].
|
|
|
|
|
//
|
|
|
|
|
// For my purposes:
|
|
|
|
|
//
|
|
|
|
|
// Video fetching always begins at 0. Since there are 311*228 = 70908 cycles per frame, and the interrupt
|
|
|
|
|
// should "occur" (I assume: begin) 14365 before that, it should actually begin at 70908 - 14365 = 56543.
|
|
|
|
|
//
|
|
|
|
|
// Contention begins four cycles before the first video fetch, so it begins at 70904. I don't currently
|
|
|
|
|
// know whether the four cycles is true across all models, so it's given here as convention_leadin.
|
|
|
|
|
//
|
|
|
|
|
// ... except that empirically that all seems to be two cycles off. So maybe I misunderstand what the
|
|
|
|
|
// contention patterns are supposed to indicate relative to MREQ? It's frustrating that all documentation
|
|
|
|
|
// I can find is vaguely in terms of contention patterns, and what they mean isn't well-defined in terms
|
|
|
|
|
// of regular Z80 signalling.
|
|
|
|
|
constexpr Timings result = {
|
|
|
|
|
.cycles_per_line = 228 * 2,
|
|
|
|
|
.lines_per_frame = 311,
|
|
|
|
|
|
|
|
|
|
// i.e. video fetching begins five cycles after the start of the
|
|
|
|
|
// contended memory pattern below; that should put a clear two
|
|
|
|
|
// cycles between a Z80 access and the first video fetch.
|
|
|
|
|
.contention_leadin = 5 * 2,
|
|
|
|
|
.contention_duration = 129 * 2,
|
|
|
|
|
|
|
|
|
|
// i.e. interrupt is first signalled 14368 cycles before the first video fetch.
|
|
|
|
|
.interrupt_time = (228*311 - 14368) * 2,
|
|
|
|
|
|
|
|
|
|
.delays = {
|
|
|
|
|
2, 1,
|
|
|
|
|
0, 0,
|
|
|
|
|
14, 13,
|
|
|
|
|
12, 11,
|
|
|
|
|
10, 9,
|
|
|
|
|
8, 7,
|
|
|
|
|
6, 5,
|
|
|
|
|
4, 3,
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: fix 48kb and 128kb timings, below.
|
|
|
|
|
|
|
|
|
|
if constexpr (timing == VideoTiming::OneTwoEightK) {
|
|
|
|
|
constexpr Timings result = {
|
|
|
|
|
.cycles_per_line = 228 * 2,
|
|
|
|
|
.lines_per_frame = 311,
|
|
|
|
|
|
|
|
|
|
.contention_leadin = 5 * 2,
|
|
|
|
|
.contention_duration = 128 * 2,
|
|
|
|
|
|
|
|
|
|
.interrupt_time = (228*311 - 14361) * 2,
|
|
|
|
|
|
|
|
|
|
.delays = {
|
|
|
|
|
12, 11,
|
|
|
|
|
10, 9,
|
|
|
|
|
8, 7,
|
|
|
|
|
6, 5,
|
|
|
|
|
4, 3,
|
|
|
|
|
2, 1,
|
|
|
|
|
0, 0,
|
|
|
|
|
0, 0,
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if constexpr (timing == VideoTiming::FortyEightK) {
|
|
|
|
|
constexpr Timings result = {
|
|
|
|
|
.cycles_per_line = 224 * 2,
|
|
|
|
|
.lines_per_frame = 312,
|
|
|
|
|
|
|
|
|
|
.contention_leadin = 5 * 2,
|
|
|
|
|
.contention_duration = 128 * 2,
|
|
|
|
|
|
2021-04-15 23:17:11 +00:00
|
|
|
|
.interrupt_time = (224*312 - 14330) * 2,
|
2021-04-15 02:23:27 +00:00
|
|
|
|
|
|
|
|
|
.delays = {
|
|
|
|
|
12, 11,
|
|
|
|
|
10, 9,
|
|
|
|
|
8, 7,
|
|
|
|
|
6, 5,
|
|
|
|
|
4, 3,
|
|
|
|
|
2, 1,
|
|
|
|
|
0, 0,
|
|
|
|
|
0, 0,
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2021-03-19 01:29:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
// TODO: how long is the interrupt line held for?
|
|
|
|
|
static constexpr int interrupt_duration = 48;
|
|
|
|
|
|
2021-03-19 01:29:52 +00:00
|
|
|
|
public:
|
|
|
|
|
void run_for(HalfCycles duration) {
|
|
|
|
|
constexpr auto timings = get_timings();
|
2021-03-23 02:18:38 +00:00
|
|
|
|
|
|
|
|
|
constexpr int sync_line = (timings.interrupt_time / timings.cycles_per_line) + 1;
|
|
|
|
|
|
2021-03-19 02:41:20 +00:00
|
|
|
|
constexpr int sync_position = 166 * 2;
|
2021-03-20 14:38:21 +00:00
|
|
|
|
constexpr int sync_length = 17 * 2;
|
2021-03-20 02:25:37 +00:00
|
|
|
|
constexpr int burst_position = sync_position + 40;
|
|
|
|
|
constexpr int burst_length = 17;
|
2021-03-19 02:29:24 +00:00
|
|
|
|
|
|
|
|
|
int cycles_remaining = duration.as<int>();
|
|
|
|
|
while(cycles_remaining) {
|
2021-03-23 02:18:38 +00:00
|
|
|
|
int line = time_into_frame_ / timings.cycles_per_line;
|
|
|
|
|
int offset = time_into_frame_ % timings.cycles_per_line;
|
2021-03-19 02:29:24 +00:00
|
|
|
|
const int cycles_this_line = std::min(cycles_remaining, timings.cycles_per_line - offset);
|
|
|
|
|
const int end_offset = offset + cycles_this_line;
|
|
|
|
|
|
2021-03-22 00:22:27 +00:00
|
|
|
|
if(!offset) {
|
|
|
|
|
is_alternate_line_ ^= true;
|
|
|
|
|
|
|
|
|
|
if(!line) {
|
|
|
|
|
flash_counter_ = (flash_counter_ + 1) & 31;
|
|
|
|
|
flash_mask_ = uint8_t(flash_counter_ >> 4);
|
|
|
|
|
}
|
2021-03-19 03:07:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
if(line >= sync_line && line < sync_line + 3) {
|
2021-03-19 02:29:24 +00:00
|
|
|
|
// Output sync line.
|
|
|
|
|
crt_.output_sync(cycles_this_line);
|
2021-03-19 02:41:20 +00:00
|
|
|
|
} else {
|
2021-03-23 02:18:38 +00:00
|
|
|
|
if(line >= 192) {
|
2021-03-22 00:34:58 +00:00
|
|
|
|
// Output plain border line.
|
|
|
|
|
if(offset < sync_position) {
|
|
|
|
|
const int border_duration = std::min(sync_position, end_offset) - offset;
|
|
|
|
|
output_border(border_duration);
|
|
|
|
|
offset += border_duration;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Output pixel line.
|
|
|
|
|
if(offset < 256) {
|
|
|
|
|
const int pixel_duration = std::min(256, end_offset) - offset;
|
2021-03-19 02:57:10 +00:00
|
|
|
|
|
2021-03-22 00:34:58 +00:00
|
|
|
|
if(!offset) {
|
|
|
|
|
pixel_target_ = crt_.begin_data(256);
|
2021-03-23 02:18:38 +00:00
|
|
|
|
attribute_address_ = ((line >> 3) << 5) + 6144;
|
|
|
|
|
pixel_address_ = ((line & 0x07) << 8) | ((line & 0x38) << 2) | ((line & 0xc0) << 5);
|
2021-03-22 00:34:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(pixel_target_) {
|
2021-03-22 19:59:03 +00:00
|
|
|
|
const int start_column = offset >> 4;
|
|
|
|
|
const int end_column = (offset + pixel_duration) >> 4;
|
2021-03-22 00:34:58 +00:00
|
|
|
|
for(int column = start_column; column < end_column; column++) {
|
2021-03-23 02:34:28 +00:00
|
|
|
|
last_fetches_[0] = memory_[pixel_address_];
|
|
|
|
|
last_fetches_[1] = memory_[attribute_address_];
|
|
|
|
|
last_fetches_[2] = memory_[pixel_address_+1];
|
|
|
|
|
last_fetches_[3] = memory_[attribute_address_+1];
|
2021-03-25 00:23:33 +00:00
|
|
|
|
set_last_contended_area_access(last_fetches_[3]);
|
|
|
|
|
|
2021-03-23 02:34:28 +00:00
|
|
|
|
pixel_address_ += 2;
|
|
|
|
|
attribute_address_ += 2;
|
2021-03-22 00:34:58 +00:00
|
|
|
|
|
|
|
|
|
constexpr uint8_t masks[] = {0, 0xff};
|
2021-03-22 19:59:03 +00:00
|
|
|
|
|
2021-03-23 02:34:28 +00:00
|
|
|
|
#define Output(n) \
|
|
|
|
|
{ \
|
|
|
|
|
const uint8_t pixels = \
|
|
|
|
|
uint8_t(last_fetches_[n] ^ masks[flash_mask_ & (last_fetches_[n+1] >> 7)]); \
|
|
|
|
|
\
|
|
|
|
|
const uint8_t colours[2] = { \
|
|
|
|
|
palette[(last_fetches_[n+1] & 0x78) >> 3], \
|
|
|
|
|
palette[((last_fetches_[n+1] & 0x40) >> 3) | (last_fetches_[n+1] & 0x07)], \
|
|
|
|
|
}; \
|
|
|
|
|
\
|
|
|
|
|
pixel_target_[0] = colours[(pixels >> 7) & 1]; \
|
|
|
|
|
pixel_target_[1] = colours[(pixels >> 6) & 1]; \
|
|
|
|
|
pixel_target_[2] = colours[(pixels >> 5) & 1]; \
|
|
|
|
|
pixel_target_[3] = colours[(pixels >> 4) & 1]; \
|
|
|
|
|
pixel_target_[4] = colours[(pixels >> 3) & 1]; \
|
|
|
|
|
pixel_target_[5] = colours[(pixels >> 2) & 1]; \
|
|
|
|
|
pixel_target_[6] = colours[(pixels >> 1) & 1]; \
|
|
|
|
|
pixel_target_[7] = colours[(pixels >> 0) & 1]; \
|
|
|
|
|
pixel_target_ += 8; \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Output(0);
|
|
|
|
|
Output(2);
|
|
|
|
|
|
|
|
|
|
#undef Output
|
2021-03-22 00:34:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-19 02:57:10 +00:00
|
|
|
|
|
2021-03-22 00:34:58 +00:00
|
|
|
|
offset += pixel_duration;
|
|
|
|
|
if(offset == 256) {
|
|
|
|
|
crt_.output_data(256);
|
|
|
|
|
pixel_target_ = nullptr;
|
2021-03-19 02:57:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-22 00:34:58 +00:00
|
|
|
|
if(offset >= 256 && offset < sync_position && end_offset > offset) {
|
|
|
|
|
const int border_duration = std::min(sync_position, end_offset) - offset;
|
|
|
|
|
output_border(border_duration);
|
|
|
|
|
offset += border_duration;
|
2021-03-19 02:57:10 +00:00
|
|
|
|
}
|
2021-03-19 02:41:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-22 00:34:58 +00:00
|
|
|
|
// Output the common tail to border and pixel lines: sync, blank, colour burst, border.
|
2021-03-19 02:41:20 +00:00
|
|
|
|
|
2021-03-22 00:34:58 +00:00
|
|
|
|
if(offset >= sync_position && offset < sync_position + sync_length && end_offset > offset) {
|
2021-03-19 02:41:20 +00:00
|
|
|
|
const int sync_duration = std::min(sync_position + sync_length, end_offset) - offset;
|
|
|
|
|
crt_.output_sync(sync_duration);
|
|
|
|
|
offset += sync_duration;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-20 02:25:37 +00:00
|
|
|
|
if(offset >= sync_position + sync_length && offset < burst_position && end_offset > offset) {
|
|
|
|
|
const int blank_duration = std::min(burst_position, end_offset) - offset;
|
|
|
|
|
crt_.output_blank(blank_duration);
|
|
|
|
|
offset += blank_duration;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) {
|
|
|
|
|
const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset;
|
2021-04-15 02:23:27 +00:00
|
|
|
|
|
|
|
|
|
if constexpr (timing >= VideoTiming::OneTwoEightK) {
|
|
|
|
|
crt_.output_colour_burst(burst_duration, 116, is_alternate_line_);
|
|
|
|
|
// The colour burst phase above is an empirical guess. I need to research further.
|
|
|
|
|
} else {
|
|
|
|
|
crt_.output_default_colour_burst(burst_duration);
|
|
|
|
|
}
|
2021-03-20 02:25:37 +00:00
|
|
|
|
offset += burst_duration;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-22 00:34:58 +00:00
|
|
|
|
if(offset >= burst_position+burst_length && end_offset > offset) {
|
2021-03-19 02:41:20 +00:00
|
|
|
|
const int border_duration = end_offset - offset;
|
|
|
|
|
output_border(border_duration);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-19 02:29:24 +00:00
|
|
|
|
|
|
|
|
|
cycles_remaining -= cycles_this_line;
|
2021-03-23 02:18:38 +00:00
|
|
|
|
time_into_frame_ = (time_into_frame_ + cycles_this_line) % (timings.cycles_per_line * timings.lines_per_frame);
|
2021-03-19 02:29:24 +00:00
|
|
|
|
}
|
2021-03-19 01:29:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2021-03-19 02:29:24 +00:00
|
|
|
|
void output_border(int duration) {
|
|
|
|
|
uint8_t *const colour_pointer = crt_.begin_data(1);
|
|
|
|
|
if(colour_pointer) *colour_pointer = border_colour_;
|
|
|
|
|
crt_.output_level(duration);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-15 02:23:27 +00:00
|
|
|
|
static constexpr int half_cycles_per_line() {
|
|
|
|
|
if constexpr (timing == VideoTiming::FortyEightK) {
|
|
|
|
|
// TODO: determine real figure here, if one exists.
|
|
|
|
|
// The source I'm looking at now suggests that the theoretical
|
|
|
|
|
// ideal of 224*2 ignores the real-life effects of separate
|
|
|
|
|
// crystals, so I've nudged this experimentally.
|
|
|
|
|
return 224*2 - 1;
|
|
|
|
|
} else {
|
|
|
|
|
return 227*2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-19 01:29:52 +00:00
|
|
|
|
public:
|
2021-03-19 01:54:42 +00:00
|
|
|
|
Video() :
|
2021-04-15 02:23:27 +00:00
|
|
|
|
crt_(half_cycles_per_line(), 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2)
|
2021-03-19 01:54:42 +00:00
|
|
|
|
{
|
|
|
|
|
// Show only the centre 80% of the TV frame.
|
|
|
|
|
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
|
|
|
|
|
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
|
|
|
|
|
|
|
|
|
}
|
2021-03-19 01:29:52 +00:00
|
|
|
|
|
2021-03-19 02:04:41 +00:00
|
|
|
|
void set_video_source(const uint8_t *source) {
|
|
|
|
|
memory_ = source;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
/*!
|
|
|
|
|
@returns The amount of time until the next change in the interrupt line, that being the only internally-observeable output.
|
|
|
|
|
*/
|
2021-03-19 01:29:52 +00:00
|
|
|
|
HalfCycles get_next_sequence_point() {
|
2021-03-23 02:18:38 +00:00
|
|
|
|
constexpr auto timings = get_timings();
|
|
|
|
|
|
|
|
|
|
// Is the frame still ahead of this interrupt?
|
|
|
|
|
if(time_into_frame_ < timings.interrupt_time) {
|
|
|
|
|
return HalfCycles(timings.interrupt_time - time_into_frame_);
|
2021-03-19 01:29:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
// If not, is it within this interrupt?
|
|
|
|
|
if(time_into_frame_ < timings.interrupt_time + interrupt_duration) {
|
|
|
|
|
return HalfCycles(timings.interrupt_time + interrupt_duration - time_into_frame_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If not, it'll be in the next batch.
|
|
|
|
|
return timings.interrupt_time + timings.cycles_per_line * timings.lines_per_frame - time_into_frame_;
|
2021-03-19 01:29:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
/*!
|
|
|
|
|
@returns The current state of the interrupt output.
|
|
|
|
|
*/
|
2021-03-19 01:29:52 +00:00
|
|
|
|
bool get_interrupt_line() const {
|
2021-03-23 02:18:38 +00:00
|
|
|
|
constexpr auto timings = get_timings();
|
|
|
|
|
return time_into_frame_ >= timings.interrupt_time && time_into_frame_ < timings.interrupt_time + interrupt_duration;
|
2021-03-19 01:29:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
/*!
|
|
|
|
|
@returns How many cycles the [ULA/gate array] would delay the CPU for if it were to recognise that contention
|
|
|
|
|
needs to be applied in @c offset half-cycles from now.
|
|
|
|
|
*/
|
2021-03-19 12:49:56 +00:00
|
|
|
|
int access_delay(HalfCycles offset) const {
|
2021-03-19 01:29:52 +00:00
|
|
|
|
constexpr auto timings = get_timings();
|
2021-03-23 02:18:38 +00:00
|
|
|
|
const int delay_time = (time_into_frame_ + offset.as<int>() + timings.contention_leadin) % (timings.cycles_per_line * timings.lines_per_frame);
|
2021-03-19 01:29:52 +00:00
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
// Check for a time within the no-contention window.
|
|
|
|
|
if(delay_time >= (191*timings.cycles_per_line + timings.contention_duration)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2021-03-19 01:29:52 +00:00
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
const int time_into_line = delay_time % timings.cycles_per_line;
|
2021-04-01 02:51:20 +00:00
|
|
|
|
if(time_into_line >= timings.contention_duration) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2021-03-19 01:29:52 +00:00
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
return timings.delays[time_into_line & 15];
|
2021-03-19 01:29:52 +00:00
|
|
|
|
}
|
2021-03-18 16:47:48 +00:00
|
|
|
|
|
2021-03-23 02:34:28 +00:00
|
|
|
|
/*!
|
2021-03-25 00:23:33 +00:00
|
|
|
|
@returns Whatever the ULA or gate array would expose via the floating bus, this cycle.
|
2021-03-23 02:34:28 +00:00
|
|
|
|
*/
|
2021-03-25 00:23:33 +00:00
|
|
|
|
uint8_t get_floating_value() const {
|
2021-03-23 02:34:28 +00:00
|
|
|
|
constexpr auto timings = get_timings();
|
2021-04-16 01:13:36 +00:00
|
|
|
|
const uint8_t out_of_bounds = (timing == VideoTiming::Plus3) ? last_contended_access_ : 0xff;
|
|
|
|
|
|
2021-03-23 02:34:28 +00:00
|
|
|
|
const int line = time_into_frame_ / timings.cycles_per_line;
|
2021-04-16 01:13:36 +00:00
|
|
|
|
if(line >= 192) {
|
|
|
|
|
return out_of_bounds;
|
|
|
|
|
}
|
2021-03-23 02:34:28 +00:00
|
|
|
|
|
|
|
|
|
const int time_into_line = time_into_frame_ % timings.cycles_per_line;
|
2021-03-23 21:09:42 +00:00
|
|
|
|
if(time_into_line >= 256 || (time_into_line&8)) {
|
2021-04-16 01:13:36 +00:00
|
|
|
|
return out_of_bounds;
|
2021-03-25 00:23:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The +2a and +3 always return the low bit as set.
|
2021-04-16 01:13:36 +00:00
|
|
|
|
const uint8_t value = last_fetches_[(time_into_line >> 1) & 3];
|
2021-03-25 00:23:33 +00:00
|
|
|
|
if constexpr (timing == VideoTiming::Plus3) {
|
2021-04-16 01:13:36 +00:00
|
|
|
|
return value | 1;
|
2021-03-23 02:34:28 +00:00
|
|
|
|
}
|
2021-04-16 01:13:36 +00:00
|
|
|
|
return value;
|
2021-03-23 02:34:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-25 00:23:33 +00:00
|
|
|
|
/*!
|
|
|
|
|
Relevant to the +2a and +3 only, sets the most recent value read from or
|
|
|
|
|
written to contended memory. This is what will be returned if the floating
|
|
|
|
|
bus is accessed when the gate array isn't currently reading.
|
|
|
|
|
*/
|
|
|
|
|
void set_last_contended_area_access([[maybe_unused]] uint8_t value) {
|
|
|
|
|
if constexpr (timing == VideoTiming::Plus3) {
|
|
|
|
|
last_contended_access_ = value | 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 02:18:38 +00:00
|
|
|
|
/*!
|
|
|
|
|
Sets the current border colour.
|
|
|
|
|
*/
|
2021-03-19 02:29:24 +00:00
|
|
|
|
void set_border_colour(uint8_t colour) {
|
|
|
|
|
border_colour_ = palette[colour];
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-19 01:54:42 +00:00
|
|
|
|
/// Sets the scan target.
|
|
|
|
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
|
|
|
|
crt_.set_scan_target(scan_target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the current scan status.
|
|
|
|
|
Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
|
|
|
|
return crt_.get_scaled_scan_status();
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-20 02:25:37 +00:00
|
|
|
|
/*! Sets the type of display the CRT will request. */
|
|
|
|
|
void set_display_type(Outputs::Display::DisplayType type) {
|
|
|
|
|
crt_.set_display_type(type);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-19 01:29:52 +00:00
|
|
|
|
private:
|
2021-03-23 02:18:38 +00:00
|
|
|
|
int time_into_frame_ = 0;
|
2021-03-19 01:54:42 +00:00
|
|
|
|
Outputs::CRT::CRT crt_;
|
2021-03-19 02:04:41 +00:00
|
|
|
|
const uint8_t *memory_ = nullptr;
|
2021-03-19 02:29:24 +00:00
|
|
|
|
uint8_t border_colour_ = 0;
|
|
|
|
|
|
2021-03-19 02:57:10 +00:00
|
|
|
|
uint8_t *pixel_target_ = nullptr;
|
|
|
|
|
int attribute_address_ = 0;
|
|
|
|
|
int pixel_address_ = 0;
|
|
|
|
|
|
2021-03-19 03:07:51 +00:00
|
|
|
|
uint8_t flash_mask_ = 0;
|
|
|
|
|
int flash_counter_ = 0;
|
2021-03-22 00:22:27 +00:00
|
|
|
|
bool is_alternate_line_ = false;
|
2021-03-19 03:07:51 +00:00
|
|
|
|
|
2021-03-23 02:34:28 +00:00
|
|
|
|
uint8_t last_fetches_[4] = {0xff, 0xff, 0xff, 0xff};
|
2021-03-25 00:23:33 +00:00
|
|
|
|
uint8_t last_contended_access_ = 0xff;
|
2021-03-23 02:34:28 +00:00
|
|
|
|
|
2021-03-19 02:29:24 +00:00
|
|
|
|
#define RGB(r, g, b) (r << 4) | (g << 2) | b
|
|
|
|
|
static constexpr uint8_t palette[] = {
|
|
|
|
|
RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2),
|
|
|
|
|
RGB(0, 2, 0), RGB(0, 2, 2), RGB(2, 2, 0), RGB(2, 2, 2),
|
|
|
|
|
RGB(0, 0, 0), RGB(0, 0, 3), RGB(3, 0, 0), RGB(3, 0, 3),
|
|
|
|
|
RGB(0, 3, 0), RGB(0, 3, 3), RGB(3, 3, 0), RGB(3, 3, 3),
|
|
|
|
|
};
|
|
|
|
|
#undef RGB
|
2021-03-18 16:47:48 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif /* Video_hpp */
|