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
/*!
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 .
*/
2018-11-07 03:23:38 +00:00
class ScanTarget : public Outputs : : Display : : ScanTarget {
public :
2019-01-16 02:33:30 +00:00
ScanTarget ( GLuint target_framebuffer = 0 , float output_gamma = 2.2f ) ;
2018-11-08 03:53:46 +00:00
~ ScanTarget ( ) ;
2019-01-18 03:28:02 +00:00
void set_target_framebuffer ( GLuint ) ;
2019-03-03 00:33:28 +00:00
/*! 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 ) ;
2018-11-08 03:53:46 +00:00
2019-03-05 02:54:50 +00:00
/*! @returns The DisplayMetrics object that this ScanTarget has been providing with announcements and draw overages. */
Metrics & display_metrics ( ) ;
2018-11-11 20:11:32 +00:00
private :
2019-02-21 01:21:17 +00:00
# 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
2018-11-14 04:08:51 +00:00
static constexpr int WriteAreaWidth = 2048 ;
static constexpr int WriteAreaHeight = 2048 ;
static constexpr int LineBufferWidth = 2048 ;
static constexpr int LineBufferHeight = 2048 ;
2019-01-18 03:28:02 +00:00
GLuint target_framebuffer_ ;
2019-01-16 02:33:30 +00:00
const float output_gamma_ ;
2020-01-24 03:57:51 +00:00
// Outputs::Display::ScanTarget finals.
void set_modals ( Modals ) final ;
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 ;
void will_change_owner ( ) final ;
2018-11-07 03:23:38 +00:00
2019-01-06 18:37:34 +00:00
bool output_is_visible_ = false ;
2019-03-05 02:54:50 +00:00
Metrics display_metrics_ ;
2019-03-06 02:41:20 +00:00
int resolution_reduction_level_ = 1 ;
int output_height_ = 0 ;
2019-03-05 02:54:50 +00:00
size_t lines_submitted_ = 0 ;
std : : chrono : : high_resolution_clock : : time_point line_submission_begin_time_ ;
2018-11-07 03:23:38 +00:00
// Extends the definition of a Scan to include two extra fields,
// relevant to the way that this scan target processes video.
2018-11-12 02:41:13 +00:00
struct Scan {
Outputs : : Display : : ScanTarget : : Scan scan ;
2018-11-07 03:23:38 +00:00
/// Stores the y coordinate that this scan's data is at, within the write area texture.
uint16_t data_y ;
2018-11-12 02:41:13 +00:00
/// Stores the y coordinate of this scan within the line buffer.
uint16_t line ;
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
2018-11-09 02:57:28 +00:00
// The sizes below might be less hassle as something more natural like ints,
// but squeezing this struct into 64 bits makes the std::atomics more likely
// 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_ ;
2020-02-19 03:41: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.
2020-02-19 01:41:51 +00:00
std : : mutex write_pointers_mutex_ ;
2018-11-09 02:57:28 +00:00
/// 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_ ;
2019-10-20 21:22:41 +00:00
/// Maintains a buffer of the most recent scans.
std : : array < Scan , 16384 > scan_buffer_ ;
2018-11-11 20:11:32 +00:00
2018-11-19 02:39:11 +00:00
// Maintains a list of composite scan buffer coordinates; the Line struct
// is transported to the GPU in its entirety; the LineMetadatas live in CPU
// space only.
2018-11-12 02:41:13 +00:00
struct Line {
2018-11-11 20:11:32 +00:00
struct EndPoint {
uint16_t x , y ;
2019-01-06 23:47:01 +00:00
uint16_t cycles_since_end_of_horizontal_retrace ;
2019-01-12 03:02:15 +00:00
int16_t composite_angle ;
2018-11-11 20:11:32 +00:00
} end_points [ 2 ] ;
2018-11-12 02:41:13 +00:00
uint16_t line ;
2019-01-12 03:02:15 +00:00
uint8_t composite_amplitude ;
2018-11-11 20:11:32 +00:00
} ;
2018-11-19 02:39:11 +00:00
struct LineMetadata {
bool is_first_in_frame ;
2018-11-22 18:22:04 +00:00
bool previous_frame_was_complete ;
2018-11-19 02:39:11 +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_ ;
// 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
// Ephemeral state that helps in line composition.
2018-11-12 02:41:13 +00:00
Line * active_line_ = nullptr ;
2018-11-16 02:51:27 +00:00
int provided_scans_ = 0 ;
2018-11-19 02:39:11 +00:00
bool is_first_in_frame_ = true ;
2019-01-15 02:42:45 +00:00
bool frame_is_complete_ = true ;
bool previous_frame_was_complete_ = true ;
2018-11-07 03:23:38 +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-07 03:23:38 +00:00
// Uses a texture to vend write areas.
std : : vector < uint8_t > write_area_texture_ ;
size_t data_type_size_ = 0 ;
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
2018-11-11 02:10:33 +00:00
// Ephemeral information for the begin/end functions.
Scan * vended_scan_ = nullptr ;
int vended_write_area_pointer_ = 0 ;
2018-11-07 03:23:38 +00:00
// Track allocation failures.
2019-06-04 01:56:53 +00:00
bool data_is_allocated_ = false ;
2018-11-07 03:23:38 +00:00
bool allocation_has_failed_ = false ;
// Receives scan target modals.
Modals modals_ ;
2018-11-30 03:41:54 +00:00
bool modals_are_dirty_ = false ;
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_updating_ ;
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_ ;
2018-11-16 02:02:46 +00:00
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 */