mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-10 11:25:23 +00:00
They all just as the CRT, as all are currently based on the CRT. Which doesn't currently know the total clock rate it would need to in order properly to scale the answer to the question. Further thought coming.
341 lines
11 KiB
C++
341 lines
11 KiB
C++
//
|
|
// ScanTarget.hpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 30/10/2018.
|
|
// Copyright © 2018 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#ifndef Outputs_Display_ScanTarget_h
|
|
#define Outputs_Display_ScanTarget_h
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include "../ClockReceiver/TimeTypes.hpp"
|
|
|
|
namespace Outputs {
|
|
namespace Display {
|
|
|
|
enum class Type {
|
|
PAL50,
|
|
NTSC60
|
|
};
|
|
|
|
struct Rect {
|
|
struct Point {
|
|
float x, y;
|
|
} origin;
|
|
|
|
struct {
|
|
float width, height;
|
|
} size;
|
|
|
|
Rect() : origin({0.0f, 0.0f}), size({1.0f, 1.0f}) {}
|
|
Rect(float x, float y, float width, float height) :
|
|
origin({x, y}), size({width, height}) {}
|
|
};
|
|
|
|
enum class ColourSpace {
|
|
/// YIQ is the NTSC colour space.
|
|
YIQ,
|
|
|
|
/// YUV is the PAL colour space.
|
|
YUV
|
|
};
|
|
|
|
enum class DisplayType {
|
|
RGB,
|
|
SVideo,
|
|
CompositeColour,
|
|
CompositeMonochrome
|
|
};
|
|
|
|
/*!
|
|
Enumerates the potential formats of input data.
|
|
*/
|
|
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.
|
|
// Phase is encoded on a 192-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; first nibble is red, second is green, third is blue.
|
|
Red8Green8Blue8, // 4 bytes/pixel; first is red, second is green, third is blue, fourth is vacant.
|
|
};
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
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 {
|
|
|
|
/*
|
|
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.
|
|
uint8_t composite_amplitude;
|
|
};
|
|
|
|
/// 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(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() {}
|
|
|
|
/// Marks the end of an atomic set of data. Drawing is 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 an @c flush.
|
|
virtual void submit() = 0;
|
|
|
|
|
|
/*
|
|
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.
|
|
|
|
@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(Event event, bool is_visible, const Scan::EndPoint &location, uint8_t composite_amplitude) {}
|
|
};
|
|
|
|
struct ScanStatus {
|
|
/// The current (prediced) length of a field.
|
|
Time::Seconds field_duration;
|
|
/// The difference applied to the field_duration estimate during the last field.
|
|
Time::Seconds field_duration_gradient;
|
|
/// The distance into the current field, from 0 (start of field) to 1 (end of field).
|
|
/// This is unlikely to be linear but should 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;
|
|
};
|
|
|
|
/*!
|
|
Provides a null target for scans.
|
|
*/
|
|
struct NullScanTarget: public ScanTarget {
|
|
void set_modals(Modals) {}
|
|
Scan *begin_scan() { return nullptr; }
|
|
uint8_t *begin_data(size_t required_length, size_t required_alignment = 1) { return nullptr; }
|
|
void submit() {}
|
|
|
|
static NullScanTarget singleton;
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
#endif /* Outputs_Display_ScanTarget_h */
|