diff --git a/Machines/ZX8081/Video.cpp b/Machines/ZX8081/Video.cpp index a35a44bf5..76722f23a 100644 --- a/Machines/ZX8081/Video.cpp +++ b/Machines/ZX8081/Video.cpp @@ -17,6 +17,8 @@ Video::Video() : cycles_since_update_(0), sync_(false) { + // Set a composite sampling function that assumes 8bpp input grayscale. + // TODO: lessen this to 1bpp. crt_->set_composite_sampling_function( "float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)" "{" @@ -25,22 +27,33 @@ Video::Video() : } void Video::run_for_cycles(int number_of_cycles) { + // Just keep a running total of the amount of time that remains owed to the CRT. cycles_since_update_ += (unsigned int)number_of_cycles << 1; } 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(cycles_since_update_); } 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. unsigned int data_length = (unsigned int)(line_data_pointer_ - line_data_); - if(data_length < cycles_since_update_) { + if(data_length < cycles_since_update_ || next_sync) { crt_->output_data(data_length, 1); line_data_pointer_ = line_data_ = nullptr; cycles_since_update_ -= data_length; } else return; } + // Any pending pixels being dealt with, pad with the white level. uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1); if(colour_pointer) *colour_pointer = 0xff; crt_->output_level(cycles_since_update_); @@ -50,19 +63,24 @@ void Video::flush() { } void Video::set_sync(bool sync) { + // Do nothing if sync hasn't changed. if(sync_ == sync) return; - flush(); + + // Complete whatever was being drawn, and update sync. + flush(sync); sync_ = sync; - if(cycles_since_update_) flush(); } void Video::output_byte(uint8_t byte) { + // Complete whatever was going on. flush(); + // Grab a buffer if one isn't already available. if(!line_data_) { line_data_pointer_ = line_data_ = crt_->allocate_write_area(320); } + // If a buffer was obtained, serialise the new pixels. if(line_data_) { uint8_t mask = 0x80; for(int c = 0; c < 8; c++) { @@ -71,6 +89,7 @@ void Video::output_byte(uint8_t byte) { } line_data_pointer_ += 8; + // If that fills the buffer, output it now. if(line_data_pointer_ - line_data_ == 320) { crt_->output_data(320, 1); line_data_pointer_ = line_data_ = nullptr; diff --git a/Machines/ZX8081/Video.hpp b/Machines/ZX8081/Video.hpp index dc3a4d699..9c63021ba 100644 --- a/Machines/ZX8081/Video.hpp +++ b/Machines/ZX8081/Video.hpp @@ -13,15 +13,31 @@ namespace ZX8081 { +/*! + Packages a ZX80/81-style video feed into a CRT-compatible waveform. + + While sync is active, this feed will output the sync level. + + While sync is inactive, this feed will output the white level unless it is supplied + with a byte to output. When a byte is supplied for output, it will be interpreted as + a 1-bit graphic and output over the next 4 cycles, picking between the white level + and the black level. +*/ class Video { public: + /// Constructs an instance of the video feed; a CRT is also created. Video(); - + /// @returns The CRT this video feed is feeding. std::shared_ptr get_crt(); + + /// Advances time by @c number_of_cycles cycles. void run_for_cycles(int number_of_cycles); + /// Forces output to catch up to the current output position. void flush(); + /// Sets the current sync output. void set_sync(bool sync); + /// Causes @c byte to be serialised into pixels and output over the next four cycles. void output_byte(uint8_t byte); private: @@ -29,6 +45,8 @@ class Video { uint8_t *line_data_, *line_data_pointer_; unsigned int cycles_since_update_; std::shared_ptr crt_; + + void flush(bool next_sync); }; }