// // Video.cpp // Clock Signal // // Created by Thomas Harte on 06/06/2017. // Copyright 2017 Thomas Harte. All rights reserved. // #include "Video.hpp" using namespace ZX8081; namespace { /*! The number of bytes of PCM data to allocate at once; if/when more are required, the class will simply allocate another batch. */ const std::size_t StandardAllocationSize = 320; } Video::Video() : crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)) { // Set a composite sampling function that assumes two-level input; either a byte is 0, which is black, // or it is non-zero, which is white. crt_->set_composite_sampling_function( "float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" "{" "return texture(sampler, coordinate).r;" "}"); // Show only the centre 80% of the TV frame. crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite); crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f)); } void Video::run_for(const HalfCycles half_cycles) { // Just keep a running total of the amount of time that remains owed to the CRT. time_since_update_ += half_cycles; } void Video::flush() { flush(sync_); } void Video::flush(bool next_sync) { if(sync_) { // If in sync, that takes priority. Output the proper amount of sync. crt_->output_sync(static_cast(time_since_update_.as_int())); } else { // If not presently in sync, then... if(line_data_) { // 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. int data_length = static_cast(line_data_pointer_ - line_data_); if(data_length < time_since_update_.as_int() || next_sync) { auto output_length = std::min(data_length, time_since_update_.as_int()); crt_->output_data(static_cast(output_length), static_cast(output_length)); line_data_pointer_ = line_data_ = nullptr; time_since_update_ -= HalfCycles(output_length); } else return; } // Any pending pixels being dealt with, pad with the white level. uint8_t *colour_pointer = static_cast(crt_->allocate_write_area(1)); if(colour_pointer) *colour_pointer = 0xff; crt_->output_level(static_cast(time_since_update_.as_int())); } time_since_update_ = 0; } void Video::set_sync(bool sync) { // Do nothing if sync hasn't changed. if(sync_ == sync) return; // Complete whatever was being drawn, and update sync. flush(sync); sync_ = sync; } void Video::output_byte(uint8_t byte) { // Complete whatever was going on. if(sync_) return; flush(); // Grab a buffer if one isn't already available. if(!line_data_) { line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize); } // If a buffer was obtained, serialise the new pixels. if(line_data_) { // If the buffer is full, output it now and obtain a new one if(line_data_pointer_ - line_data_ == StandardAllocationSize) { crt_->output_data(StandardAllocationSize, StandardAllocationSize); time_since_update_ -= StandardAllocationSize; line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize); if(!line_data_) return; } // 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; } } Outputs::CRT::CRT *Video::get_crt() { return crt_.get(); }