mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-11-04 00:16:26 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			593 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			593 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
//
 | 
						|
//  ScanTarget.hpp
 | 
						|
//  Clock Signal
 | 
						|
//
 | 
						|
//  Created by Thomas Harte on 30/10/2018.
 | 
						|
//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
						|
//
 | 
						|
 | 
						|
#pragma once
 | 
						|
 | 
						|
#include "ClockReceiver/TimeTypes.hpp"
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
#include <array>
 | 
						|
#include <cstddef>
 | 
						|
#include <cstdint>
 | 
						|
 | 
						|
namespace Outputs::Display {
 | 
						|
 | 
						|
enum class Type {
 | 
						|
	PAL50,
 | 
						|
	PAL60,
 | 
						|
	NTSC60
 | 
						|
};
 | 
						|
 | 
						|
struct Rect {
 | 
						|
	struct Point {
 | 
						|
		float x, y;
 | 
						|
		auto operator <=>(const Point &) const = default;
 | 
						|
	} origin;
 | 
						|
 | 
						|
	struct Size {
 | 
						|
		float width, height;
 | 
						|
		auto operator <=>(const Size &) const = default;
 | 
						|
	} size;
 | 
						|
 | 
						|
	auto operator <=>(const Rect &) const = default;
 | 
						|
 | 
						|
	bool equal(const Rect &rhs, const float tolerance) const {
 | 
						|
		const auto compare = [=](const float left, const float right) {
 | 
						|
			if(left < right - tolerance || left > right + tolerance) return false;
 | 
						|
			return true;
 | 
						|
		};
 | 
						|
		return
 | 
						|
			compare(origin.x, rhs.origin.x) &&
 | 
						|
			compare(origin.y, rhs.origin.y) &&
 | 
						|
			compare(size.width, rhs.size.width) &&
 | 
						|
			compare(size.height, rhs.size.height);
 | 
						|
	}
 | 
						|
 | 
						|
	constexpr Rect() noexcept : origin({0.0f, 0.0f}), size({1.0f, 1.0f}) {}
 | 
						|
	constexpr Rect(const float x, const float y, const float width, const float height) noexcept :
 | 
						|
		origin({x, y}), size({width, height}) {}
 | 
						|
 | 
						|
	bool empty() const {
 | 
						|
		return size.width == 0.0f || size.height == 0.0f;
 | 
						|
	}
 | 
						|
 | 
						|
	void expand(const float min_x, const float max_x, const float min_y, const float max_y) {
 | 
						|
		origin.x = std::min(origin.x, min_x);
 | 
						|
		size.width = std::max(size.width, max_x - origin.x);
 | 
						|
 | 
						|
		origin.y = std::min(origin.y, min_y);
 | 
						|
		size.height = std::max(size.height, max_y - origin.y);
 | 
						|
	}
 | 
						|
 | 
						|
	/// Scales a rectange around its centre.
 | 
						|
	void scale(const float scale_x, const float scale_y) {
 | 
						|
		const float centre[] = {
 | 
						|
			origin.x + size.width * 0.5f,
 | 
						|
			origin.y + size.height * 0.5f,
 | 
						|
		};
 | 
						|
		size.width *= scale_x;
 | 
						|
		size.height *= scale_y;
 | 
						|
		origin.x = centre[0] - size.width * 0.5f;
 | 
						|
		origin.y = centre[1] - size.height * 0.5f;
 | 
						|
	}
 | 
						|
 | 
						|
	float appropriate_zoom(const float aspect_ratio_stretch) const {
 | 
						|
		const float width_zoom = 1.0f / (size.width * aspect_ratio_stretch);
 | 
						|
		const float height_zoom = 1.0f / size.height;
 | 
						|
		return std::min(width_zoom, height_zoom);
 | 
						|
	}
 | 
						|
 | 
						|
	Rect operator *(const float multiplier) const {
 | 
						|
		return Rect(
 | 
						|
			origin.x * multiplier,
 | 
						|
			origin.y * multiplier,
 | 
						|
			size.width * multiplier,
 | 
						|
			size.height * multiplier
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	Rect operator +(const Rect &rhs) const {
 | 
						|
		return Rect(
 | 
						|
			origin.x + rhs.origin.x,
 | 
						|
			origin.y + rhs.origin.y,
 | 
						|
			size.width + rhs.size.width,
 | 
						|
			size.height + rhs.size.height
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	/// Scale towards the origin.
 | 
						|
	Rect operator /(const float multiplier) const {
 | 
						|
		return Rect(
 | 
						|
			origin.x / multiplier,
 | 
						|
			origin.y / multiplier,
 | 
						|
			size.width / multiplier,
 | 
						|
			size.height / multiplier
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	/// Perform the union.
 | 
						|
	Rect operator |(const Rect &rhs) const {
 | 
						|
		const auto left = std::min(origin.x, rhs.origin.x);
 | 
						|
		const auto top = std::min(origin.y, rhs.origin.y);
 | 
						|
 | 
						|
		return Rect(
 | 
						|
			left,
 | 
						|
			top,
 | 
						|
			std::max(origin.x + size.width - left, rhs.origin.x + rhs.size.width - left),
 | 
						|
			std::max(origin.y + size.height - top, rhs.origin.y + rhs.size.height - top)
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	/// Perform the intersection.
 | 
						|
	Rect operator &(const Rect &rhs) const {
 | 
						|
		const auto left = std::max(origin.x, rhs.origin.x);
 | 
						|
		const auto top = std::max(origin.y, rhs.origin.y);
 | 
						|
 | 
						|
		return Rect(
 | 
						|
			left,
 | 
						|
			top,
 | 
						|
			std::min(origin.x + size.width - left, rhs.origin.x + rhs.size.width - left),
 | 
						|
			std::min(origin.y + size.height - top, rhs.origin.y + rhs.size.height - top)
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	void constrain(Rect &rhs, const float max_centre_offset_x, const float max_centre_offset_y) const {
 | 
						|
		// Push left and up if out of bounds on the right or bottom.
 | 
						|
		if(rhs.origin.x + rhs.size.width > origin.x + size.width) {
 | 
						|
			rhs.origin.x -= origin.x + size.width - rhs.size.width;
 | 
						|
		}
 | 
						|
		if(rhs.origin.y + rhs.size.height > origin.y + size.height) {
 | 
						|
			rhs.origin.y -= origin.x + size.height - rhs.size.height;
 | 
						|
		}
 | 
						|
 | 
						|
		// Push down and right if out of bounds on the left or top.
 | 
						|
		rhs.origin.x = std::max(rhs.origin.x, origin.x);
 | 
						|
		rhs.origin.y = std::max(rhs.origin.y, origin.y);
 | 
						|
 | 
						|
		// If the other rectangle is _still_ too large then it's not solveable by
 | 
						|
		// moving it around; just shrink it.
 | 
						|
		if(rhs.origin.x + rhs.size.width > origin.x + size.width) {
 | 
						|
			rhs.size.width = size.width;
 | 
						|
		}
 | 
						|
		if(rhs.origin.y + rhs.size.height > origin.y + size.height) {
 | 
						|
			rhs.size.height = size.height;
 | 
						|
		}
 | 
						|
 | 
						|
		// Expand if necessary to include at least the same visible area but to align centres.
 | 
						|
		const auto apply_centre = [](float &origin, float &size, const float target, const float max) {
 | 
						|
			const auto offset = origin + size*0.5f - target;
 | 
						|
 | 
						|
			if(offset < -max) {
 | 
						|
				size -= (offset + max) * 2.0f;
 | 
						|
			} else if(offset > max) {
 | 
						|
				const auto adjustment = offset - max;
 | 
						|
				size += adjustment * 2.0f;
 | 
						|
				origin -= adjustment * 2.0f;
 | 
						|
			}
 | 
						|
		};
 | 
						|
		apply_centre(rhs.origin.x, rhs.size.width, origin.x + size.width * 0.5f, max_centre_offset_x);
 | 
						|
		apply_centre(rhs.origin.y, rhs.size.height, origin.y + size.height * 0.5f, max_centre_offset_y);
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
enum class ColourSpace {
 | 
						|
	/// YIQ is the NTSC colour space.
 | 
						|
	YIQ,
 | 
						|
 | 
						|
	/// YUV is the PAL colour space.
 | 
						|
	YUV
 | 
						|
};
 | 
						|
 | 
						|
enum class DisplayType {
 | 
						|
	RGB,
 | 
						|
	SVideo,
 | 
						|
	CompositeColour,
 | 
						|
	CompositeMonochrome
 | 
						|
};
 | 
						|
 | 
						|
constexpr bool is_composite(const DisplayType type) {
 | 
						|
	return type == DisplayType::CompositeColour || type == DisplayType::CompositeMonochrome;
 | 
						|
}
 | 
						|
 | 
						|
/*!
 | 
						|
	Enumerates the potential formats of input data.
 | 
						|
 | 
						|
	All types are designed to be 1, 2 or 4 bytes per pixel; this hopefully creates appropriate alignment
 | 
						|
	on all formats.
 | 
						|
*/
 | 
						|
enum class InputDataType {
 | 
						|
 | 
						|
	// The luminance types can be used to feed only two video pipelines:
 | 
						|
	// black and white video, or composite colour.
 | 
						|
 | 
						|
	Luminance1,				// 1 byte/pixel; any bit set => white; no bits set => black.
 | 
						|
	Luminance8,				// 1 byte/pixel; linear scale.
 | 
						|
 | 
						|
	PhaseLinkedLuminance8,	// 4 bytes/pixel; each byte is an individual 8-bit luminance
 | 
						|
							// value and which value is output is a function of
 | 
						|
							// colour subcarrier phase — byte 0 defines the first quarter
 | 
						|
							// of each colour cycle, byte 1 the next quarter, etc. This
 | 
						|
							// format is intended to permit replay of sampled original data.
 | 
						|
 | 
						|
	// The luminance plus phase types describe a luminance and the phase offset
 | 
						|
	// of a colour subcarrier. So they can be used to generate a luminance signal,
 | 
						|
	// or an s-video pipeline.
 | 
						|
 | 
						|
	Luminance8Phase8,		// 2 bytes/pixel; first is luminance, second is phase
 | 
						|
							// of a cosine wave.
 | 
						|
							//
 | 
						|
							// Phase is encoded on a 128-unit circle; anything
 | 
						|
							// greater than 192 implies that the colour part of
 | 
						|
							// the signal should be omitted.
 | 
						|
 | 
						|
	// The RGB types can directly feed an RGB pipeline, naturally, or can be mapped
 | 
						|
	// to phase+luminance, or just to luminance.
 | 
						|
 | 
						|
	Red1Green1Blue1,		// 1 byte/pixel; bit 0 is blue on or off, bit 1 is green, bit 2 is red.
 | 
						|
	Red2Green2Blue2,		// 1 byte/pixel; bits 0 and 1 are blue, bits 2 and 3 are green, bits 4 and 5 are blue.
 | 
						|
	Red4Green4Blue4,		// 2 bytes/pixel; low nibble in first byte is red, high nibble in second is green, low is blue.
 | 
						|
							// i.e. if it were a little endian word, 0xgb0r; or 0x0rgb big endian.
 | 
						|
	Red8Green8Blue8,		// 4 bytes/pixel; first is red, second is green, third is blue, fourth is vacant.
 | 
						|
};
 | 
						|
 | 
						|
/// @returns the number of bytes per sample for data of type @c data_type.
 | 
						|
/// Guaranteed to be 1, 2 or 4 for valid data types.
 | 
						|
constexpr inline size_t size_for_data_type(InputDataType data_type) {
 | 
						|
	switch(data_type) {
 | 
						|
		case InputDataType::Luminance1:
 | 
						|
		case InputDataType::Luminance8:
 | 
						|
		case InputDataType::Red1Green1Blue1:
 | 
						|
		case InputDataType::Red2Green2Blue2:
 | 
						|
			return 1;
 | 
						|
 | 
						|
		case InputDataType::Luminance8Phase8:
 | 
						|
		case InputDataType::Red4Green4Blue4:
 | 
						|
			return 2;
 | 
						|
 | 
						|
		case InputDataType::Red8Green8Blue8:
 | 
						|
		case InputDataType::PhaseLinkedLuminance8:
 | 
						|
			return 4;
 | 
						|
 | 
						|
		default:
 | 
						|
			return 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/// @returns @c true if this data type presents normalised data, i.e. each byte holds a
 | 
						|
/// value in the range [0, 255] representing a real number in the range [0.0, 1.0]; @c false otherwise.
 | 
						|
constexpr inline size_t data_type_is_normalised(InputDataType data_type) {
 | 
						|
	switch(data_type) {
 | 
						|
		case InputDataType::Luminance8:
 | 
						|
		case InputDataType::Luminance8Phase8:
 | 
						|
		case InputDataType::Red8Green8Blue8:
 | 
						|
		case InputDataType::PhaseLinkedLuminance8:
 | 
						|
			return true;
 | 
						|
 | 
						|
		default:
 | 
						|
		case InputDataType::Luminance1:
 | 
						|
		case InputDataType::Red1Green1Blue1:
 | 
						|
		case InputDataType::Red2Green2Blue2:
 | 
						|
		case InputDataType::Red4Green4Blue4:
 | 
						|
			return false;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/// @returns The 'natural' display type for data of type @c data_type. The natural display is whichever would
 | 
						|
/// display it with the least number of conversions. Caveat: a colour display is assumed for pure-composite data types.
 | 
						|
constexpr inline DisplayType natural_display_type_for_data_type(InputDataType data_type) {
 | 
						|
	switch(data_type) {
 | 
						|
		default:
 | 
						|
		case InputDataType::Luminance1:
 | 
						|
		case InputDataType::Luminance8:
 | 
						|
		case InputDataType::PhaseLinkedLuminance8:
 | 
						|
			return DisplayType::CompositeColour;
 | 
						|
 | 
						|
		case InputDataType::Red1Green1Blue1:
 | 
						|
		case InputDataType::Red2Green2Blue2:
 | 
						|
		case InputDataType::Red4Green4Blue4:
 | 
						|
		case InputDataType::Red8Green8Blue8:
 | 
						|
			return DisplayType::RGB;
 | 
						|
 | 
						|
		case InputDataType::Luminance8Phase8:
 | 
						|
			return DisplayType::SVideo;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/// @returns A 3x3 matrix in row-major order to convert from @c colour_space to RGB.
 | 
						|
inline std::array<float, 9> to_rgb_matrix(ColourSpace colour_space) {
 | 
						|
	const std::array<float, 9> yiq_to_rgb = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f};
 | 
						|
	const std::array<float, 9> yuv_to_rgb = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f};
 | 
						|
 | 
						|
	switch(colour_space) {
 | 
						|
		case ColourSpace::YIQ:	return yiq_to_rgb;
 | 
						|
		case ColourSpace::YUV:	return yuv_to_rgb;
 | 
						|
	}
 | 
						|
 | 
						|
	// Should be unreachable.
 | 
						|
	return std::array<float, 9>{};
 | 
						|
}
 | 
						|
 | 
						|
/// @returns A 3x3 matrix in row-major order to convert to @c colour_space to RGB.
 | 
						|
inline std::array<float, 9> from_rgb_matrix(ColourSpace colour_space) {
 | 
						|
	const std::array<float, 9> rgb_to_yiq = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f};
 | 
						|
	const std::array<float, 9> rgb_to_yuv = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f};
 | 
						|
 | 
						|
	switch(colour_space) {
 | 
						|
		case ColourSpace::YIQ:	return rgb_to_yiq;
 | 
						|
		case ColourSpace::YUV:	return rgb_to_yuv;
 | 
						|
	}
 | 
						|
 | 
						|
	// Should be unreachable.
 | 
						|
	return std::array<float, 9>{};
 | 
						|
}
 | 
						|
 | 
						|
/*!
 | 
						|
	Provides an abstract target for 'scans' i.e. continuous sweeps of output data,
 | 
						|
	which are identified by 2d start and end coordinates, and the PCM-sampled data
 | 
						|
	that is output during the sweep.
 | 
						|
 | 
						|
	Additional information is provided to allow decoding (and/or encoding) of a
 | 
						|
	composite colour feed.
 | 
						|
 | 
						|
	Otherwise helpful: the ScanTarget vends all allocated memory. That should allow
 | 
						|
	for use of shared memory where available.
 | 
						|
*/
 | 
						|
struct ScanTarget {
 | 
						|
		virtual ~ScanTarget() = default;
 | 
						|
 | 
						|
 | 
						|
	/*
 | 
						|
		This top section of the interface deals with modal settings. A ScanTarget can
 | 
						|
		assume that the modals change very infrequently.
 | 
						|
	*/
 | 
						|
 | 
						|
		struct Modals {
 | 
						|
			/// Describes the format of input data.
 | 
						|
			InputDataType input_data_type;
 | 
						|
 | 
						|
			struct InputDataTweaks {
 | 
						|
				/// If using the PhaseLinkedLuminance8 data type, this value provides an offset
 | 
						|
				/// to add to phase before indexing the supplied luminances.
 | 
						|
				float phase_linked_luminance_offset = 0.0f;
 | 
						|
 | 
						|
			} input_data_tweaks;
 | 
						|
 | 
						|
			/// Describes the type of display that the data is being shown on.
 | 
						|
			DisplayType display_type = DisplayType::SVideo;
 | 
						|
 | 
						|
			/// If being fed composite data, this defines the colour space in use.
 | 
						|
			ColourSpace composite_colour_space;
 | 
						|
 | 
						|
			/// Provides an integral clock rate for the duration of "a single line", specifically
 | 
						|
			/// for an idealised line. So e.g. in NTSC this will be for the duration of 227.5
 | 
						|
			/// colour clocks, regardless of whether the source actually stretches lines to
 | 
						|
			/// 228 colour cycles, abbreviates them to 227 colour cycles, etc.
 | 
						|
			int cycles_per_line;
 | 
						|
 | 
						|
			/// Sets a GCD for the durations of pixels coming out of this device. This with
 | 
						|
			/// the @c cycles_per_line are offered for sizing of intermediary buffers.
 | 
						|
			int clocks_per_pixel_greatest_common_divisor;
 | 
						|
 | 
						|
			/// Provides the number of colour cycles in a line, as a quotient.
 | 
						|
			int colour_cycle_numerator, colour_cycle_denominator;
 | 
						|
 | 
						|
			/// Provides a pre-estimate of the likely number of left-to-right scans per frame.
 | 
						|
			/// This isn't a guarantee, but it should provide a decent-enough estimate.
 | 
						|
			int expected_vertical_lines;
 | 
						|
 | 
						|
			/// Provides an additional restriction on the section of the display that is expected
 | 
						|
			/// to contain interesting content.
 | 
						|
			Rect visible_area;
 | 
						|
 | 
						|
			/// Describes the usual gamma of the output device these scans would appear on.
 | 
						|
			float intended_gamma = 2.2f;
 | 
						|
 | 
						|
			/// Provides a brightness multiplier for the display output.
 | 
						|
			float brightness = 1.0f;
 | 
						|
 | 
						|
			/// Specifies the range of values that will be output for x and y coordinates.
 | 
						|
			struct {
 | 
						|
				uint16_t x, y;
 | 
						|
			} output_scale;
 | 
						|
 | 
						|
			/// Describes the intended display aspect ratio.
 | 
						|
			float aspect_ratio = 4.0f / 3.0f;
 | 
						|
		};
 | 
						|
 | 
						|
		/// Sets the total format of input data.
 | 
						|
		virtual void set_modals(Modals) = 0;
 | 
						|
 | 
						|
 | 
						|
	/*
 | 
						|
		This second section of the interface allows provision of the streamed data, plus some control
 | 
						|
		over the streaming.
 | 
						|
	*/
 | 
						|
 | 
						|
		/*!
 | 
						|
			Defines a scan in terms of its two endpoints.
 | 
						|
		*/
 | 
						|
		struct Scan {
 | 
						|
			struct EndPoint {
 | 
						|
				/// Provide the coordinate of this endpoint. These are fixed point, purely fractional
 | 
						|
				/// numbers, relative to the scale provided in the Modals.
 | 
						|
				uint16_t x, y;
 | 
						|
 | 
						|
				/// Provides the offset, in samples, into the most recently allocated write area, of data
 | 
						|
				/// at this end point.
 | 
						|
				uint16_t data_offset;
 | 
						|
 | 
						|
				/// For composite video, provides the angle of the colour subcarrier at this endpoint.
 | 
						|
				///
 | 
						|
				/// This is a slightly weird fixed point, being:
 | 
						|
				///
 | 
						|
				///		* a six-bit fractional part;
 | 
						|
				///		* a nine-bit integral part; and
 | 
						|
				///		* a sign.
 | 
						|
				///
 | 
						|
				/// Positive numbers indicate that the colour subcarrier is 'running positively' on this
 | 
						|
				/// line; i.e. it is any NTSC line or an appropriate swing PAL line, encoded as
 | 
						|
				/// x*cos(a) + y*sin(a).
 | 
						|
				///
 | 
						|
				/// Negative numbers indicate a 'negative running' colour subcarrier; i.e. it is one of
 | 
						|
				/// the phase alternated lines of PAL, encoded as x*cos(a) - y*sin(a), or x*cos(-a) + y*sin(-a),
 | 
						|
				/// whichever you prefer.
 | 
						|
				///
 | 
						|
				/// It will produce undefined behaviour if signs differ on a single scan.
 | 
						|
				int16_t composite_angle;
 | 
						|
 | 
						|
				/// Gives the number of cycles since the most recent horizontal retrace ended.
 | 
						|
				uint16_t cycles_since_end_of_horizontal_retrace;
 | 
						|
			} end_points[2];
 | 
						|
 | 
						|
			/// For composite video, dictates the amplitude of the colour subcarrier as a proportion of
 | 
						|
			/// the whole, as determined from the colour burst. Will be 0 if there was no colour burst.
 | 
						|
			union {
 | 
						|
				uint8_t composite_amplitude;
 | 
						|
 | 
						|
				uint32_t padding;
 | 
						|
			};
 | 
						|
		};
 | 
						|
 | 
						|
		/// Requests a new scan to populate.
 | 
						|
		///
 | 
						|
		/// @return A valid pointer, or @c nullptr if insufficient further storage is available.
 | 
						|
		virtual Scan *begin_scan() = 0;
 | 
						|
 | 
						|
		/// Requests a new scan to populate.
 | 
						|
		virtual void end_scan() {}
 | 
						|
 | 
						|
		/// Finds the first available storage of at least @c required_length pixels in size which is
 | 
						|
		/// suitably aligned for writing of @c required_alignment number of samples at a time.
 | 
						|
		///
 | 
						|
		/// Calls will be paired off with calls to @c end_data.
 | 
						|
		///
 | 
						|
		/// @returns a pointer to the allocated space if any was available; @c nullptr otherwise.
 | 
						|
		virtual uint8_t *begin_data(size_t required_length, size_t required_alignment = 1) = 0;
 | 
						|
 | 
						|
		/// Announces that the owner is finished with the region created by the most recent @c begin_data
 | 
						|
		/// and indicates that its actual final size was @c actual_length.
 | 
						|
		///
 | 
						|
		/// It is required that every call to begin_data be paired with a call to end_data.
 | 
						|
		virtual void end_data([[maybe_unused]] size_t actual_length) {}
 | 
						|
 | 
						|
		/// Tells the scan target that its owner is about to change; this is a hint that existing
 | 
						|
		/// data and scan allocations should be invalidated.
 | 
						|
		virtual void will_change_owner() {}
 | 
						|
 | 
						|
		/// Acts as a fence, marking the end of an atomic set of [begin/end]_[scan/data] calls] — all future pieces of
 | 
						|
		/// data will have no relation to scans prior to the submit() and all future scans will similarly have no relation to
 | 
						|
		/// prior runs of data.
 | 
						|
		///
 | 
						|
		/// Drawing is defined to be best effort, so the scan target should either:
 | 
						|
		///
 | 
						|
		///		(i)	output everything received since the previous submit; or
 | 
						|
		///		(ii)	output nothing.
 | 
						|
		///
 | 
						|
		/// If there were any allocation failures — i.e. any nullptr responses to begin_data or
 | 
						|
		/// begin_scan — then (ii) is a required response. But a scan target may also need to opt for (ii)
 | 
						|
		/// for any other reason.
 | 
						|
		///
 | 
						|
		/// The ScanTarget isn't bound to take any drawing action immediately; it may sit on submitted data for
 | 
						|
		/// as long as it feels is appropriate, subject to a @c flush.
 | 
						|
		virtual void submit() {}
 | 
						|
 | 
						|
 | 
						|
	/*
 | 
						|
		ScanTargets also receive notification of certain events that may be helpful in processing, particularly
 | 
						|
		for synchronising internal output to the outside world.
 | 
						|
	*/
 | 
						|
 | 
						|
		enum class Event {
 | 
						|
			BeginHorizontalRetrace,
 | 
						|
			EndHorizontalRetrace,
 | 
						|
 | 
						|
			BeginVerticalRetrace,
 | 
						|
			EndVerticalRetrace,
 | 
						|
		};
 | 
						|
 | 
						|
		/*!
 | 
						|
			Provides a hint that the named event has occurred.
 | 
						|
 | 
						|
			Guarantee:
 | 
						|
			* any announce acts as an implicit fence on data/scans, much as a submit().
 | 
						|
 | 
						|
			Permitted ScanTarget implementation:
 | 
						|
			* ignore all output during retrace periods.
 | 
						|
 | 
						|
			@param event The event.
 | 
						|
			@param is_visible @c true if the output stream is visible immediately after this event; @c false otherwise.
 | 
						|
			@param location The location of the event.
 | 
						|
			@param composite_amplitude The amplitude of the colour burst on this line (0, if no colour burst was found).
 | 
						|
		*/
 | 
						|
		virtual void announce([[maybe_unused]] Event event, [[maybe_unused]] bool is_visible, [[maybe_unused]] const Scan::EndPoint &location, [[maybe_unused]] uint8_t composite_amplitude) {}
 | 
						|
};
 | 
						|
 | 
						|
struct ScanStatus {
 | 
						|
	/// The current (prediced) length of a field (including retrace).
 | 
						|
	Time::Seconds field_duration = 0.0;
 | 
						|
	/// The difference applied to the field_duration estimate during the last field.
 | 
						|
	Time::Seconds field_duration_gradient = 0.0;
 | 
						|
	/// The amount of time this device spends in retrace.
 | 
						|
	Time::Seconds retrace_duration = 0.0;
 | 
						|
	/// The distance into the current field, from a small negative amount (in retrace) through
 | 
						|
	/// 0 (start of visible area field) to 1 (end of field).
 | 
						|
	///
 | 
						|
	/// This will increase monotonically, being a measure
 | 
						|
	/// of the current vertical position — i.e. if current_position = 0.8 then a caller can
 | 
						|
	/// conclude that the top 80% of the visible part of the display has been painted.
 | 
						|
	float current_position = 0.0f;
 | 
						|
	/// The total number of hsyncs so far encountered;
 | 
						|
	int hsync_count = 0;
 | 
						|
	/// @c true if retrace is currently going on; @c false otherwise.
 | 
						|
	bool is_in_retrace = false;
 | 
						|
 | 
						|
	/*!
 | 
						|
		@returns this ScanStatus, with time-relative fields scaled by dividing them by @c dividend.
 | 
						|
	*/
 | 
						|
	ScanStatus operator / (float dividend) {
 | 
						|
		const ScanStatus result = {
 | 
						|
			.field_duration = field_duration / dividend,
 | 
						|
			.field_duration_gradient = field_duration_gradient / dividend,
 | 
						|
			.retrace_duration = retrace_duration / dividend,
 | 
						|
			.current_position = current_position,
 | 
						|
			.hsync_count = hsync_count,
 | 
						|
			.is_in_retrace = is_in_retrace,
 | 
						|
		};
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	/*!
 | 
						|
		@returns this ScanStatus, with time-relative fields scaled by multiplying them by @c multiplier.
 | 
						|
	*/
 | 
						|
	ScanStatus operator * (float multiplier) {
 | 
						|
		const ScanStatus result = {
 | 
						|
			.field_duration = field_duration * multiplier,
 | 
						|
			.field_duration_gradient = field_duration_gradient * multiplier,
 | 
						|
			.retrace_duration = retrace_duration * multiplier,
 | 
						|
			.current_position = current_position,
 | 
						|
			.hsync_count = hsync_count,
 | 
						|
			.is_in_retrace = is_in_retrace,
 | 
						|
		};
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
/*!
 | 
						|
	Provides a null target for scans.
 | 
						|
*/
 | 
						|
struct NullScanTarget: public ScanTarget {
 | 
						|
	void set_modals(Modals) override {}
 | 
						|
	Scan *begin_scan() override { return nullptr; }
 | 
						|
	uint8_t *begin_data(size_t, size_t) override { return nullptr; }
 | 
						|
	void submit() override {}
 | 
						|
 | 
						|
	static NullScanTarget singleton;
 | 
						|
};
 | 
						|
 | 
						|
}
 |