2018-11-07 03:23:38 +00:00
//
// ScanTarget.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/11/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
# ifndef ScanTarget_hpp
# define ScanTarget_hpp
2019-02-21 01:21:17 +00:00
# include "../Log.hpp"
2019-03-05 02:54:50 +00:00
# include "../DisplayMetrics.hpp"
# include "../ScanTarget.hpp"
2019-02-21 01:21:17 +00:00
2018-11-08 03:53:46 +00:00
# include "OpenGL.hpp"
2018-11-11 20:11:32 +00:00
# include "Primitives/TextureTarget.hpp"
2018-11-19 02:39:11 +00:00
# include "Primitives/Rectangle.hpp"
2018-11-07 03:23:38 +00:00
2018-11-27 03:34:31 +00:00
# include "../../SignalProcessing/FIRFilter.hpp"
2018-11-07 03:23:38 +00:00
# include <array>
# include <atomic>
# include <cstdint>
2019-03-05 02:54:50 +00:00
# include <chrono>
2018-11-24 03:34:38 +00:00
# include <list>
# include <memory>
2018-11-12 02:41:13 +00:00
# include <string>
2018-11-07 03:23:38 +00:00
# include <vector>
namespace Outputs {
namespace Display {
namespace OpenGL {
2018-11-20 04:25:26 +00:00
/*!
2020-07-22 02:49:46 +00:00
Provides basic thread - safe ( hopefully ) circular queues for any scan target that :
2019-01-18 03:28:02 +00:00
2020-07-22 02:49:46 +00:00
* will store incoming Scans into a linear circular buffer and pack regions of
incoming pixel data into a 2 d texture ;
* will compose whole lines of content by partioning the Scans based on sync
placement and then pasting together their content ;
* will process those lines as necessary to map from input format to whatever
suits the display ; and
* will then output the lines .
2018-11-08 03:53:46 +00:00
2020-07-22 02:49:46 +00:00
This buffer rejects new data when full .
*/
class BufferingScanTarget : public Outputs : : Display : : ScanTarget {
public :
2019-03-05 02:54:50 +00:00
/*! @returns The DisplayMetrics object that this ScanTarget has been providing with announcements and draw overages. */
2020-07-22 02:49:46 +00:00
const Metrics & display_metrics ( ) ;
2019-02-21 01:21:17 +00:00
2020-07-22 02:49:46 +00:00
protected :
// Extends the definition of a Scan to include two extra fields,
// completing this scan's source data and destination locations.
struct Scan {
Outputs : : Display : : ScanTarget : : Scan scan ;
2018-11-14 04:08:51 +00:00
2020-07-22 02:49:46 +00:00
/// Stores the y coordinate for this scan's data within the write area texture.
/// Use this plus the scan's endpoints' data_offsets to locate this data in 2d.
uint16_t data_y ;
/// Stores the y coordinate assigned to this scan within the intermediate buffers.
/// Use this plus this scan's endpoints' x locations to determine where to composite
/// this data for intermediate processing.
uint16_t line ;
} ;
2018-11-14 04:08:51 +00:00
2020-07-22 02:49:46 +00:00
/// Defines the boundaries of a complete line of video — a 2d start and end location,
/// composite phase and amplitude (if relevant), the source line in the intermediate buffer
/// plus the start and end offsets of the area that is visible from the intermediate buffer.
struct Line {
struct EndPoint {
uint16_t x , y ;
uint16_t cycles_since_end_of_horizontal_retrace ;
int16_t composite_angle ;
} end_points [ 2 ] ;
uint16_t line ;
uint8_t composite_amplitude ;
} ;
2019-01-16 02:33:30 +00:00
2020-07-22 02:49:46 +00:00
/// Provides additional metadata about lines; this is separate because it's unlikely to be of
/// interest to the GPU, unlike the fields in Line.
struct LineMetadata {
/// @c true if this line was the first drawn after vertical sync; @c false otherwise.
bool is_first_in_frame ;
/// @c true if this line is the first in the frame and if every single piece of output
/// from the previous frame was recorded; @c false otherwise. Data can be dropped
/// from a frame if performance problems mean that the emulated machine is running
/// more quickly than complete frames can be generated.
bool previous_frame_was_complete ;
} ;
2018-11-07 03:23:38 +00:00
2020-07-22 02:49:46 +00:00
// TODO: put this behind accessors.
std : : atomic_flag is_updating_ ;
2019-01-06 18:37:34 +00:00
2020-07-22 02:49:46 +00:00
// These are safe to read if you have is_updating_.
Modals modals_ ;
bool modals_are_dirty_ = false ;
2019-03-05 02:54:50 +00:00
2020-07-22 02:49:46 +00:00
// Track allocation failures.
bool data_is_allocated_ = false ;
bool allocation_has_failed_ = false ;
2019-03-05 02:54:50 +00:00
2020-07-22 02:49:46 +00:00
/// Maintains a buffer of the most recent scans.
// TODO: have the owner supply a buffer and its size.
// That'll allow owners to place this in shared video memory if possible.
std : : array < Scan , 16384 > scan_buffer_ ;
2018-11-12 02:41:13 +00:00
2020-07-22 02:49:46 +00:00
/// A mutex for gettng access to write_pointers_; access to write_pointers_,
/// data_type_size_ or write_area_texture_ is almost never contended, so this
/// is cheap for the main use case.
std : : mutex write_pointers_mutex_ ;
2018-11-07 03:23:38 +00:00
2018-11-09 02:57:28 +00:00
struct PointerSet {
2019-01-26 00:30:39 +00:00
// This constructor is here to appease GCC's interpretation of
// an ambiguity in the C++ standard; cf. https://stackoverflow.com/questions/17430377
2019-01-26 01:19:50 +00:00
PointerSet ( ) noexcept { }
2019-01-26 00:30:39 +00:00
2020-07-22 02:49:46 +00:00
// Squeezing this struct into 64 bits makes the std::atomics more likely
2018-11-09 02:57:28 +00:00
// to be lock free; they are under LLVM x86-64.
2020-02-10 00:13:21 +00:00
int write_area = 1 ; // By convention this points to the vended area. Which is preceded by a guard pixel. So a sensible default construction is write_area = 1.
2018-11-09 04:02:36 +00:00
uint16_t scan_buffer = 0 ;
2018-11-12 02:41:13 +00:00
uint16_t line = 0 ;
2018-11-07 03:23:38 +00:00
} ;
2018-11-09 02:57:28 +00:00
/// A pointer to the next thing that should be provided to the caller for data.
PointerSet write_pointers_ ;
/// A pointer to the final thing currently cleared for submission.
std : : atomic < PointerSet > submit_pointers_ ;
/// A pointer to the first thing not yet submitted for display.
std : : atomic < PointerSet > read_pointers_ ;
2020-07-22 02:49:46 +00:00
// Ephemeral state that helps in line composition.
Line * active_line_ = nullptr ;
int provided_scans_ = 0 ;
bool is_first_in_frame_ = true ;
bool frame_is_complete_ = true ;
bool previous_frame_was_complete_ = true ;
// Ephemeral information for the begin/end functions.
Scan * vended_scan_ = nullptr ;
int vended_write_area_pointer_ = 0 ;
static constexpr int WriteAreaWidth = 2048 ;
static constexpr int WriteAreaHeight = 2048 ;
static constexpr int LineBufferWidth = 2048 ;
static constexpr int LineBufferHeight = 2048 ;
Metrics display_metrics_ ;
// Uses a texture to vend write areas.
std : : vector < uint8_t > write_area_texture_ ;
size_t data_type_size_ = 0 ;
bool output_is_visible_ = false ;
2018-11-11 20:11:32 +00:00
2018-11-14 04:08:51 +00:00
std : : array < Line , LineBufferHeight > line_buffer_ ;
2018-11-19 02:39:11 +00:00
std : : array < LineMetadata , LineBufferHeight > line_metadata_buffer_ ;
2020-07-22 02:49:46 +00:00
private :
// ScanTarget overrides.
void set_modals ( Modals ) final ;
Outputs : : Display : : ScanTarget : : Scan * begin_scan ( ) final ;
void end_scan ( ) final ;
uint8_t * begin_data ( size_t required_length , size_t required_alignment ) final ;
void end_data ( size_t actual_length ) final ;
void announce ( Event event , bool is_visible , const Outputs : : Display : : ScanTarget : : Scan : : EndPoint & location , uint8_t colour_burst_amplitude ) final ;
} ;
/*!
Provides a ScanTarget that uses OpenGL to render its output ;
this uses various internal buffers so that the only geometry
drawn to the target framebuffer is a quad .
*/
class ScanTarget : public BufferingScanTarget {
public :
ScanTarget ( GLuint target_framebuffer = 0 , float output_gamma = 2.2f ) ;
~ ScanTarget ( ) ;
void set_target_framebuffer ( GLuint ) ;
/*! Pushes the current state of output to the target framebuffer. */
void draw ( int output_width , int output_height ) ;
/*! Processes all the latest input, at a resolution suitable for later output to a framebuffer of the specified size. */
void update ( int output_width , int output_height ) ;
private :
# ifndef NDEBUG
struct OpenGLVersionDumper {
OpenGLVersionDumper ( ) {
// Note the OpenGL version, as the first thing this class does prior to construction.
LOG ( " Constructing scan target with OpenGL " < < glGetString ( GL_VERSION ) < < " ; shading language version " < < glGetString ( GL_SHADING_LANGUAGE_VERSION ) ) ;
}
} dumper_ ;
# endif
GLuint target_framebuffer_ ;
const float output_gamma_ ;
// Outputs::Display::ScanTarget finals.
void will_change_owner ( ) final ;
int resolution_reduction_level_ = 1 ;
int output_height_ = 0 ;
size_t lines_submitted_ = 0 ;
std : : chrono : : high_resolution_clock : : time_point line_submission_begin_time_ ;
2018-11-19 02:39:11 +00:00
// Contains the first composition of scans into lines;
// they're accumulated prior to output to allow for continuous
2019-01-02 02:02:21 +00:00
// application of any necessary conversions — e.g. composite processing.
2018-11-11 21:22:14 +00:00
TextureTarget unprocessed_line_texture_ ;
2018-11-19 02:39:11 +00:00
2019-02-09 21:54:31 +00:00
// Contains pre-lowpass-filtered chrominance information that is
// part-QAM-demoduled, if dealing with a QAM data source.
std : : unique_ptr < TextureTarget > qam_chroma_texture_ ;
2018-11-19 02:39:11 +00:00
// Scans are accumulated to the accumulation texture; the full-display
// rectangle is used to ensure untouched pixels properly decay.
std : : unique_ptr < TextureTarget > accumulation_texture_ ;
Rectangle full_display_rectangle_ ;
2018-11-21 00:51:11 +00:00
bool stencil_is_valid_ = false ;
2018-11-19 02:39:11 +00:00
2018-11-12 23:47:55 +00:00
// OpenGL storage handles for buffer data.
GLuint scan_buffer_name_ = 0 , scan_vertex_array_ = 0 ;
GLuint line_buffer_name_ = 0 , line_vertex_array_ = 0 ;
2018-11-13 00:10:48 +00:00
template < typename T > void allocate_buffer ( const T & array , GLuint & buffer_name , GLuint & vertex_array_name ) ;
2018-11-14 02:15:33 +00:00
template < typename T > void patch_buffer ( const T & array , GLuint target , uint16_t submit_pointer , uint16_t read_pointer ) ;
2018-11-12 23:47:55 +00:00
2018-11-09 04:02:36 +00:00
GLuint write_area_texture_name_ = 0 ;
bool texture_exists_ = false ;
2018-11-07 03:23:38 +00:00
// Receives scan target modals.
2018-11-30 03:41:54 +00:00
void setup_pipeline ( ) ;
2018-11-12 02:41:13 +00:00
enum class ShaderType {
2019-01-14 04:07:50 +00:00
Composition ,
2019-02-09 21:54:31 +00:00
Conversion ,
QAMSeparation
2018-11-12 02:41:13 +00:00
} ;
/*!
Calls @ c taret . enable_vertex_attribute_with_pointer to attach all
globals for shaders of @ c type to @ c target .
*/
2018-11-24 21:06:26 +00:00
static void enable_vertex_attributes ( ShaderType type , Shader & target ) ;
2019-02-11 03:39:24 +00:00
void set_uniforms ( ShaderType type , Shader & target ) const ;
std : : vector < std : : string > bindings ( ShaderType type ) const ;
2018-11-12 04:23:42 +00:00
2018-11-13 01:15:38 +00:00
GLsync fence_ = nullptr ;
2019-03-08 00:28:32 +00:00
std : : atomic_flag is_drawing_to_accumulation_buffer_ ;
2018-11-13 23:33:44 +00:00
2018-11-14 02:15:33 +00:00
std : : unique_ptr < Shader > input_shader_ ;
std : : unique_ptr < Shader > output_shader_ ;
2019-02-09 21:54:31 +00:00
std : : unique_ptr < Shader > qam_separation_shader_ ;
2019-01-14 03:49:01 +00:00
/*!
Produces a shader that composes fragment of the input stream to a single buffer ,
normalising the data into one of four forms : RGB , 8 - bit luminance ,
phase - linked luminance or luminance + phase offset .
*/
2019-01-23 03:20:12 +00:00
std : : unique_ptr < Shader > composition_shader ( ) const ;
2019-01-14 03:49:01 +00:00
/*!
Produces a shader that reads from a composition buffer and converts to host
output RGB , decoding composite or S - Video as necessary .
*/
2019-01-23 03:20:12 +00:00
std : : unique_ptr < Shader > conversion_shader ( ) const ;
2019-02-09 21:54:31 +00:00
/*!
Produces a shader that writes separated but not - yet filtered QAM components
from the unprocessed line texture to the QAM chroma texture , at a fixed
size of four samples per colour clock , point sampled .
*/
std : : unique_ptr < Shader > qam_separation_shader ( ) const ;
2019-02-27 03:21:49 +00:00
void set_sampling_window ( int output_Width , int output_height , Shader & target ) ;
2019-02-09 21:54:31 +00:00
std : : string sampling_function ( ) const ;
2019-06-03 19:57:31 +00:00
/*!
@ returns true if the current display type is a ' soft ' one , i . e . one in which
contrast tends to be low , such as a composite colour display .
*/
bool is_soft_display_type ( ) ;
2018-11-07 03:23:38 +00:00
} ;
}
}
}
# endif /* ScanTarget_hpp */