2017-06-06 21:53:23 +00:00
|
|
|
//
|
|
|
|
// Video.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 06/06/2017.
|
2018-05-13 19:19:52 +00:00
|
|
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
2017-06-06 21:53:23 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#include "Video.hpp"
|
|
|
|
|
2018-11-04 01:54:25 +00:00
|
|
|
#include <algorithm>
|
|
|
|
|
2017-06-06 21:53:23 +00:00
|
|
|
using namespace ZX8081;
|
|
|
|
|
2018-04-08 14:35:07 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
/*!
|
|
|
|
The number of bytes of PCM data to allocate at once; if/when more are required,
|
|
|
|
the class will simply allocate another batch.
|
|
|
|
*/
|
2018-06-02 22:25:00 +00:00
|
|
|
const std::size_t StandardAllocationSize = 320;
|
2018-04-08 14:35:07 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-11-15 02:52:57 +00:00
|
|
|
Video::Video() :
|
|
|
|
crt_(207 * 2, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Luminance1) {
|
2017-06-06 21:53:23 +00:00
|
|
|
|
2017-06-11 21:24:32 +00:00
|
|
|
// Show only the centre 80% of the TV frame.
|
2018-11-29 01:53:33 +00:00
|
|
|
crt_.set_display_type(Outputs::Display::DisplayType::CompositeMonochrome);
|
2018-11-15 02:52:57 +00:00
|
|
|
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
2017-06-06 21:53:23 +00:00
|
|
|
}
|
|
|
|
|
2017-07-28 02:05:29 +00:00
|
|
|
void Video::run_for(const HalfCycles half_cycles) {
|
2017-06-06 22:01:33 +00:00
|
|
|
// Just keep a running total of the amount of time that remains owed to the CRT.
|
2018-06-02 22:25:00 +00:00
|
|
|
time_since_update_ += half_cycles;
|
2017-06-06 21:53:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Video::flush() {
|
2017-06-06 22:01:33 +00:00
|
|
|
flush(sync_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Video::flush(bool next_sync) {
|
2017-06-06 21:53:23 +00:00
|
|
|
if(sync_) {
|
2017-06-06 22:01:33 +00:00
|
|
|
// If in sync, that takes priority. Output the proper amount of sync.
|
2019-10-30 02:36:29 +00:00
|
|
|
crt_.output_sync(int(time_since_update_.as_integral()));
|
2017-06-06 21:53:23 +00:00
|
|
|
} else {
|
2017-06-06 22:01:33 +00:00
|
|
|
// If not presently in sync, then...
|
|
|
|
|
2017-06-06 21:53:23 +00:00
|
|
|
if(line_data_) {
|
2017-06-06 22:01:33 +00:00
|
|
|
// If there is output data queued, output it either if it's being interrupted by
|
|
|
|
// sync, or if we're past its end anyway. Otherwise let it be.
|
2020-05-10 03:00:39 +00:00
|
|
|
int data_length = int(line_data_pointer_ - line_data_);
|
2019-10-30 02:36:29 +00:00
|
|
|
if(data_length < int(time_since_update_.as_integral()) || next_sync) {
|
|
|
|
auto output_length = std::min(data_length, int(time_since_update_.as_integral()));
|
2018-11-15 02:52:57 +00:00
|
|
|
crt_.output_data(output_length);
|
2017-06-06 21:53:23 +00:00
|
|
|
line_data_pointer_ = line_data_ = nullptr;
|
2018-06-02 22:25:00 +00:00
|
|
|
time_since_update_ -= HalfCycles(output_length);
|
2017-06-06 21:53:23 +00:00
|
|
|
} else return;
|
|
|
|
}
|
|
|
|
|
2017-06-06 22:01:33 +00:00
|
|
|
// Any pending pixels being dealt with, pad with the white level.
|
2018-11-15 02:52:57 +00:00
|
|
|
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_.begin_data(1));
|
2017-06-06 21:53:23 +00:00
|
|
|
if(colour_pointer) *colour_pointer = 0xff;
|
2019-10-30 02:36:29 +00:00
|
|
|
crt_.output_level(int(time_since_update_.as_integral()));
|
2017-06-06 21:53:23 +00:00
|
|
|
}
|
|
|
|
|
2018-06-02 22:25:00 +00:00
|
|
|
time_since_update_ = 0;
|
2017-06-06 21:53:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Video::set_sync(bool sync) {
|
2017-06-06 22:01:33 +00:00
|
|
|
// Do nothing if sync hasn't changed.
|
2017-06-06 21:53:23 +00:00
|
|
|
if(sync_ == sync) return;
|
2017-06-06 22:01:33 +00:00
|
|
|
|
|
|
|
// Complete whatever was being drawn, and update sync.
|
|
|
|
flush(sync);
|
2017-06-06 21:53:23 +00:00
|
|
|
sync_ = sync;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Video::output_byte(uint8_t byte) {
|
2017-06-06 22:01:33 +00:00
|
|
|
// Complete whatever was going on.
|
2017-07-09 01:01:07 +00:00
|
|
|
if(sync_) return;
|
2017-06-06 21:53:23 +00:00
|
|
|
flush();
|
|
|
|
|
2017-06-06 22:01:33 +00:00
|
|
|
// Grab a buffer if one isn't already available.
|
2017-06-06 21:53:23 +00:00
|
|
|
if(!line_data_) {
|
2018-11-15 02:52:57 +00:00
|
|
|
line_data_pointer_ = line_data_ = crt_.begin_data(StandardAllocationSize);
|
2017-06-06 21:53:23 +00:00
|
|
|
}
|
|
|
|
|
2017-06-06 22:01:33 +00:00
|
|
|
// If a buffer was obtained, serialise the new pixels.
|
2017-06-06 21:53:23 +00:00
|
|
|
if(line_data_) {
|
2017-07-09 23:33:05 +00:00
|
|
|
// If the buffer is full, output it now and obtain a new one
|
2018-04-08 14:35:07 +00:00
|
|
|
if(line_data_pointer_ - line_data_ == StandardAllocationSize) {
|
2018-11-15 02:52:57 +00:00
|
|
|
crt_.output_data(StandardAllocationSize, StandardAllocationSize);
|
2018-06-02 22:25:00 +00:00
|
|
|
time_since_update_ -= StandardAllocationSize;
|
2018-11-15 02:52:57 +00:00
|
|
|
line_data_pointer_ = line_data_ = crt_.begin_data(StandardAllocationSize);
|
2017-07-09 23:33:05 +00:00
|
|
|
if(!line_data_) return;
|
|
|
|
}
|
|
|
|
|
2018-06-02 22:25:00 +00:00
|
|
|
// Convert to one-byte-per-pixel where any non-zero value will act as white.
|
|
|
|
uint8_t mask = 0x80;
|
|
|
|
for(int c = 0; c < 8; c++) {
|
|
|
|
line_data_pointer_[c] = byte & mask;
|
|
|
|
mask >>= 1;
|
|
|
|
}
|
|
|
|
line_data_pointer_ += 8;
|
2017-06-06 21:53:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-15 02:52:57 +00:00
|
|
|
void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
|
|
|
crt_.set_scan_target(scan_target);
|
2017-06-06 21:53:23 +00:00
|
|
|
}
|
2020-01-21 02:45:10 +00:00
|
|
|
|
2020-01-22 03:28:25 +00:00
|
|
|
Outputs::Display::ScanStatus Video::get_scaled_scan_status() const {
|
2020-01-26 04:27:09 +00:00
|
|
|
return crt_.get_scaled_scan_status() / 2.0f;
|
2020-01-21 02:45:10 +00:00
|
|
|
}
|