2018-11-07 03:23:38 +00:00
//
// ScanTarget.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/11/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
# include "ScanTarget.hpp"
2018-11-08 03:53:46 +00:00
# include "Primitives/Rectangle.hpp"
2018-11-07 03:23:38 +00:00
using namespace Outputs : : Display : : OpenGL ;
namespace {
2018-11-11 21:22:14 +00:00
/// The texture unit from which to source 1bpp input data.
constexpr GLenum SourceData1BppTextureUnit = GL_TEXTURE0 ;
/// The texture unit from which to source 2bpp input data.
constexpr GLenum SourceData2BppTextureUnit = GL_TEXTURE1 ;
/// The texture unit from which to source 4bpp input data.
constexpr GLenum SourceData4BppTextureUnit = GL_TEXTURE2 ;
2018-11-24 03:34:38 +00:00
/// The texture unit which contains raw line-by-line composite, S-Video or RGB data.
2018-11-11 21:22:14 +00:00
constexpr GLenum UnprocessedLineBufferTextureUnit = GL_TEXTURE3 ;
2018-11-24 23:51:07 +00:00
/// The texture unit which contains line-by-line records of luminance and two channels of chrominance, straight after multiplication by the quadrature vector, not yet filtered.
2018-11-24 03:34:38 +00:00
constexpr GLenum SVideoLineBufferTextureUnit = GL_TEXTURE4 ;
2018-11-24 23:51:07 +00:00
/// The texture unit which contains line-by-line records of RGB.
2018-11-24 03:34:38 +00:00
constexpr GLenum RGBLineBufferTextureUnit = GL_TEXTURE5 ;
2018-11-11 21:22:14 +00:00
/// The texture unit that contains the current display.
2018-11-24 03:54:52 +00:00
constexpr GLenum AccumulationTextureUnit = GL_TEXTURE6 ;
2018-11-11 20:11:32 +00:00
2018-11-09 04:02:36 +00:00
# define TextureAddress(x, y) (((y) << 11) | (x))
# define TextureAddressGetY(v) uint16_t((v) >> 11)
# define TextureAddressGetX(v) uint16_t((v) & 0x7ff)
# define TextureSub(a, b) (((a) - (b)) & 0x3fffff)
const GLint internalFormatForDepth ( std : : size_t depth ) {
switch ( depth ) {
default : return GL_FALSE ;
case 1 : return GL_R8UI ;
case 2 : return GL_RG8UI ;
case 3 : return GL_RGB8UI ;
case 4 : return GL_RGBA8UI ;
}
}
const GLenum formatForDepth ( std : : size_t depth ) {
switch ( depth ) {
default : return GL_FALSE ;
case 1 : return GL_RED_INTEGER ;
case 2 : return GL_RG_INTEGER ;
case 3 : return GL_RGB_INTEGER ;
case 4 : return GL_RGBA_INTEGER ;
}
}
2018-11-07 03:23:38 +00:00
}
2018-11-13 00:10:48 +00:00
template < typename T > void ScanTarget : : allocate_buffer ( const T & array , GLuint & buffer_name , GLuint & vertex_array_name ) {
2018-11-12 23:47:55 +00:00
const auto buffer_size = array . size ( ) * sizeof ( array [ 0 ] ) ;
glGenBuffers ( 1 , & buffer_name ) ;
glBindBuffer ( GL_ARRAY_BUFFER , buffer_name ) ;
glBufferData ( GL_ARRAY_BUFFER , GLsizeiptr ( buffer_size ) , NULL , GL_STREAM_DRAW ) ;
2018-11-13 00:10:48 +00:00
glGenVertexArrays ( 1 , & vertex_array_name ) ;
glBindVertexArray ( vertex_array_name ) ;
glBindBuffer ( GL_ARRAY_BUFFER , buffer_name ) ;
2018-11-12 23:47:55 +00:00
}
2018-11-11 20:11:32 +00:00
ScanTarget : : ScanTarget ( ) :
2018-11-24 03:34:38 +00:00
unprocessed_line_texture_ ( LineBufferWidth , LineBufferHeight , UnprocessedLineBufferTextureUnit , GL_LINEAR , false ) ,
// svideo_texture_(LineBufferWidth, LineBufferHeight, SVideoLineBufferTextureUnit, GL_LINEAR, false),
// rgb_texture_(LineBufferWidth, LineBufferHeight, RGBLineBufferTextureUnit, GL_LINEAR, false),
full_display_rectangle_ ( - 1.0f , - 1.0f , 2.0f , 2.0f ) {
2018-11-11 20:11:32 +00:00
2018-11-15 01:49:06 +00:00
// Ensure proper initialisation of the two atomic pointer sets.
read_pointers_ . store ( write_pointers_ ) ;
submit_pointers_ . store ( write_pointers_ ) ;
2018-11-12 23:47:55 +00:00
// Allocate space for the scans and lines.
2018-11-13 00:10:48 +00:00
allocate_buffer ( scan_buffer_ , scan_buffer_name_ , scan_vertex_array_ ) ;
allocate_buffer ( line_buffer_ , line_buffer_name_ , line_vertex_array_ ) ;
2018-11-09 03:21:11 +00:00
// TODO: if this is OpenGL 4.4 or newer, use glBufferStorage rather than glBufferData
// and specify GL_MAP_PERSISTENT_BIT. Then map the buffer now, and let the client
// write straight into it.
2018-11-09 04:02:36 +00:00
glGenTextures ( 1 , & write_area_texture_name_ ) ;
2018-11-12 04:23:42 +00:00
2018-11-16 02:51:27 +00:00
glBlendFunc ( GL_SRC_ALPHA , GL_CONSTANT_COLOR ) ;
glBlendColor ( 0.4f , 0.4f , 0.4f , 1.0f ) ;
2018-11-15 01:10:38 +00:00
is_drawing_ . clear ( ) ;
2018-11-08 03:53:46 +00:00
}
ScanTarget : : ~ ScanTarget ( ) {
2018-11-13 03:52:26 +00:00
while ( is_drawing_ . test_and_set ( ) ) { }
2018-11-08 03:53:46 +00:00
glDeleteBuffers ( 1 , & scan_buffer_name_ ) ;
2018-11-09 04:02:36 +00:00
glDeleteTextures ( 1 , & write_area_texture_name_ ) ;
2018-11-11 20:11:32 +00:00
glDeleteVertexArrays ( 1 , & scan_vertex_array_ ) ;
2018-11-08 03:53:46 +00:00
}
2018-11-07 03:23:38 +00:00
void ScanTarget : : set_modals ( Modals modals ) {
2018-11-25 03:30:39 +00:00
modals . display_type = DisplayType : : CompositeColour ;
2018-11-24 21:06:26 +00:00
2018-11-07 03:23:38 +00:00
modals_ = modals ;
const auto data_type_size = Outputs : : Display : : size_for_data_type ( modals . input_data_type ) ;
if ( data_type_size ! = data_type_size_ ) {
// TODO: flush output.
data_type_size_ = data_type_size ;
write_area_texture_ . resize ( 2048 * 2048 * data_type_size_ ) ;
2018-11-09 02:57:28 +00:00
write_pointers_ . scan_buffer = 0 ;
write_pointers_ . write_area = 0 ;
2018-11-07 03:23:38 +00:00
}
2018-11-12 04:23:42 +00:00
2018-11-13 23:33:44 +00:00
// Pick a processing width; this will be at least four times the
// colour subcarrier, and an integer multiple of the pixel clock and
// at most 2048.
const int colour_cycle_width = ( modals . colour_cycle_numerator * 4 + modals . colour_cycle_denominator - 1 ) / modals . colour_cycle_denominator ;
const int dot_clock = modals . cycles_per_line / modals . clocks_per_pixel_greatest_common_divisor ;
const int overflow = colour_cycle_width % dot_clock ;
processing_width_ = colour_cycle_width + ( overflow ? dot_clock - overflow : 0 ) ;
processing_width_ = std : : min ( processing_width_ , 2048 ) ;
2018-11-24 03:34:38 +00:00
// Establish an output shader. TODO: add gamma correction here.
output_shader_ . reset ( new Shader (
glsl_globals ( ShaderType : : Line ) + glsl_default_vertex_shader ( ShaderType : : Line ) ,
" #version 150 \n "
" out vec4 fragColour; "
" in vec2 textureCoordinate; "
" uniform sampler2D textureName; "
" void main(void) { "
" fragColour = vec4(texture(textureName, textureCoordinate).rgb, 0.64); "
2018-11-24 21:06:26 +00:00
" } " ,
attribute_bindings ( ShaderType : : Line )
2018-11-24 03:34:38 +00:00
) ) ;
glBindVertexArray ( line_vertex_array_ ) ;
glBindBuffer ( GL_ARRAY_BUFFER , line_buffer_name_ ) ;
enable_vertex_attributes ( ShaderType : : Line , * output_shader_ ) ;
set_uniforms ( ShaderType : : Line , * output_shader_ ) ;
output_shader_ - > set_uniform ( " origin " , modals . visible_area . origin . x , modals . visible_area . origin . y ) ;
output_shader_ - > set_uniform ( " size " , modals . visible_area . size . width , modals . visible_area . size . height ) ;
// Establish such intermediary shaders as are required.
pipeline_stages_ . clear ( ) ;
if ( modals_ . display_type = = DisplayType : : CompositeColour ) {
pipeline_stages_ . emplace_back (
composite_to_svideo_shader ( modals_ . colour_cycle_numerator , modals_ . colour_cycle_denominator , processing_width_ ) . release ( ) ,
SVideoLineBufferTextureUnit ) ;
}
if ( modals_ . display_type = = DisplayType : : SVideo | | modals_ . display_type = = DisplayType : : CompositeColour ) {
pipeline_stages_ . emplace_back (
svideo_to_rgb_shader ( modals_ . colour_cycle_numerator , modals_ . colour_cycle_denominator , processing_width_ ) . release ( ) ,
2018-11-24 03:54:52 +00:00
( modals_ . display_type = = DisplayType : : CompositeColour ) ? RGBLineBufferTextureUnit : SVideoLineBufferTextureUnit ) ;
2018-11-22 22:20:31 +00:00
}
2018-11-22 18:22:04 +00:00
2018-11-24 03:54:52 +00:00
glBindVertexArray ( scan_vertex_array_ ) ;
glBindBuffer ( GL_ARRAY_BUFFER , scan_buffer_name_ ) ;
2018-11-24 21:06:26 +00:00
// Establish an input shader.
input_shader_ = input_shader ( modals_ . input_data_type , modals_ . display_type ) ;
2018-11-24 03:54:52 +00:00
enable_vertex_attributes ( ShaderType : : InputScan , * input_shader_ ) ;
set_uniforms ( ShaderType : : InputScan , * input_shader_ ) ;
input_shader_ - > set_uniform ( " textureName " , GLint ( SourceData1BppTextureUnit - GL_TEXTURE0 ) ) ;
2018-11-24 03:34:38 +00:00
// Cascade the texture units in use as per the pipeline stages.
std : : vector < Shader * > input_shaders = { input_shader_ . get ( ) } ;
GLint texture_unit = GLint ( UnprocessedLineBufferTextureUnit - GL_TEXTURE0 ) ;
2018-11-24 22:37:58 +00:00
// output_shader_->set_uniform("textureName", texture_unit);
2018-11-24 03:34:38 +00:00
for ( const auto & stage : pipeline_stages_ ) {
input_shaders . push_back ( stage . shader . get ( ) ) ;
2018-11-24 03:54:52 +00:00
2018-11-24 03:34:38 +00:00
stage . shader - > set_uniform ( " textureName " , texture_unit ) ;
set_uniforms ( ShaderType : : ProcessedScan , * stage . shader ) ;
2018-11-24 03:54:52 +00:00
enable_vertex_attributes ( ShaderType : : ProcessedScan , * stage . shader ) ;
2018-11-24 03:34:38 +00:00
+ + texture_unit ;
}
output_shader_ - > set_uniform ( " textureName " , texture_unit ) ;
// Ensure that all shaders involved in the input pipeline have the proper colour space knowledged.
for ( auto shader : input_shaders ) {
switch ( modals . composite_colour_space ) {
case ColourSpace : : YIQ : {
const GLfloat rgbToYIQ [ ] = { 0.299f , 0.596f , 0.211f , 0.587f , - 0.274f , - 0.523f , 0.114f , - 0.322f , 0.312f } ;
const GLfloat yiqToRGB [ ] = { 1.0f , 1.0f , 1.0f , 0.956f , - 0.272f , - 1.106f , 0.621f , - 0.647f , 1.703f } ;
shader - > set_uniform_matrix ( " lumaChromaToRGB " , 3 , false , yiqToRGB ) ;
shader - > set_uniform_matrix ( " rgbToLumaChroma " , 3 , false , rgbToYIQ ) ;
} break ;
case ColourSpace : : YUV : {
const GLfloat rgbToYUV [ ] = { 0.299f , - 0.14713f , 0.615f , 0.587f , - 0.28886f , - 0.51499f , 0.114f , 0.436f , - 0.10001f } ;
const GLfloat yuvToRGB [ ] = { 1.0f , 1.0f , 1.0f , 0.0f , - 0.39465f , 2.03211f , 1.13983f , - 0.58060f , 0.0f } ;
shader - > set_uniform_matrix ( " lumaChromaToRGB " , 3 , false , yuvToRGB ) ;
shader - > set_uniform_matrix ( " rgbToLumaChroma " , 3 , false , rgbToYUV ) ;
} break ;
}
}
2018-11-14 02:15:33 +00:00
}
void Outputs : : Display : : OpenGL : : ScanTarget : : set_uniforms ( ShaderType type , Shader & target ) {
2018-11-19 04:03:56 +00:00
// Slightly over-amping rowHeight here is a cheap way to make sure that lines
// converge even allowing for the fact that they may not be spaced by exactly
// the expected distance. Cf. the stencil-powered logic for making sure all
// pixels are painted only exactly once per field.
target . set_uniform ( " rowHeight " , GLfloat ( 1.05f / modals_ . expected_vertical_lines ) ) ;
2018-11-14 02:15:33 +00:00
target . set_uniform ( " scale " , GLfloat ( modals_ . output_scale . x ) , GLfloat ( modals_ . output_scale . y ) ) ;
target . set_uniform ( " processingWidth " , GLfloat ( processing_width_ ) / 2048.0f ) ;
2018-11-07 03:23:38 +00:00
}
2018-11-11 00:52:57 +00:00
Outputs : : Display : : ScanTarget : : Scan * ScanTarget : : begin_scan ( ) {
2018-11-07 03:23:38 +00:00
if ( allocation_has_failed_ ) return nullptr ;
2018-11-09 02:57:28 +00:00
const auto result = & scan_buffer_ [ write_pointers_ . scan_buffer ] ;
const auto read_pointers = read_pointers_ . load ( ) ;
2018-11-07 03:23:38 +00:00
// Advance the pointer.
2018-11-09 02:57:28 +00:00
const auto next_write_pointer = decltype ( write_pointers_ . scan_buffer ) ( ( write_pointers_ . scan_buffer + 1 ) % scan_buffer_ . size ( ) ) ;
2018-11-07 03:23:38 +00:00
// Check whether that's too many.
2018-11-09 02:57:28 +00:00
if ( next_write_pointer = = read_pointers . scan_buffer ) {
2018-11-07 03:23:38 +00:00
allocation_has_failed_ = true ;
return nullptr ;
}
2018-11-09 02:57:28 +00:00
write_pointers_ . scan_buffer = next_write_pointer ;
2018-11-16 02:51:27 +00:00
+ + provided_scans_ ;
2018-11-07 03:23:38 +00:00
// Fill in extra OpenGL-specific details.
2018-11-12 02:41:13 +00:00
result - > line = write_pointers_ . line ;
2018-11-07 03:23:38 +00:00
2018-11-11 02:10:33 +00:00
vended_scan_ = result ;
2018-11-12 02:41:13 +00:00
return & result - > scan ;
2018-11-07 03:23:38 +00:00
}
2018-11-11 02:10:33 +00:00
void ScanTarget : : end_scan ( ) {
if ( vended_scan_ ) {
vended_scan_ - > data_y = TextureAddressGetY ( vended_write_area_pointer_ ) ;
2018-11-13 03:52:26 +00:00
vended_scan_ - > line = write_pointers_ . line ;
2018-11-12 02:41:13 +00:00
vended_scan_ - > scan . end_points [ 0 ] . data_offset + = TextureAddressGetX ( vended_write_area_pointer_ ) ;
vended_scan_ - > scan . end_points [ 1 ] . data_offset + = TextureAddressGetX ( vended_write_area_pointer_ ) ;
2018-11-11 02:10:33 +00:00
}
vended_scan_ = nullptr ;
}
2018-11-11 00:52:57 +00:00
uint8_t * ScanTarget : : begin_data ( size_t required_length , size_t required_alignment ) {
2018-11-07 03:23:38 +00:00
if ( allocation_has_failed_ ) return nullptr ;
2018-11-09 02:57:28 +00:00
// Determine where the proposed write area would start and end.
uint16_t output_y = TextureAddressGetY ( write_pointers_ . write_area ) ;
uint16_t aligned_start_x = TextureAddressGetX ( write_pointers_ . write_area & 0xffff ) + 1 ;
aligned_start_x + = uint16_t ( ( required_alignment - aligned_start_x % required_alignment ) % required_alignment ) ;
uint16_t end_x = aligned_start_x + uint16_t ( 1 + required_length ) ;
if ( end_x > WriteAreaWidth ) {
output_y = ( output_y + 1 ) % WriteAreaHeight ;
aligned_start_x = uint16_t ( required_alignment ) ;
end_x = aligned_start_x + uint16_t ( 1 + required_length ) ;
2018-11-07 03:23:38 +00:00
}
2018-11-09 02:57:28 +00:00
// Check whether that steps over the read pointer.
2018-11-09 04:02:36 +00:00
const auto end_address = TextureAddress ( end_x , output_y ) ;
2018-11-09 02:57:28 +00:00
const auto read_pointers = read_pointers_ . load ( ) ;
2018-11-09 04:02:36 +00:00
const auto end_distance = TextureSub ( end_address , read_pointers . write_area ) ;
const auto previous_distance = TextureSub ( write_pointers_ . write_area , read_pointers . write_area ) ;
2018-11-09 02:57:28 +00:00
2018-11-09 04:02:36 +00:00
// If allocating this would somehow make the write pointer back away from the read pointer,
2018-11-09 02:57:28 +00:00
// there must not be enough space left.
2018-11-09 04:02:36 +00:00
if ( end_distance < previous_distance ) {
2018-11-07 03:23:38 +00:00
allocation_has_failed_ = true ;
return nullptr ;
}
2018-11-09 02:57:28 +00:00
// Everything checks out, return the pointer.
2018-11-11 02:10:33 +00:00
vended_write_area_pointer_ = write_pointers_ . write_area = TextureAddress ( aligned_start_x , output_y ) ;
2018-11-09 04:02:36 +00:00
return & write_area_texture_ [ size_t ( write_pointers_ . write_area ) * data_type_size_ ] ;
// Note state at exit:
// write_pointers_.write_area points to the first pixel the client is expected to draw to.
2018-11-07 03:23:38 +00:00
}
2018-11-11 00:52:57 +00:00
void ScanTarget : : end_data ( size_t actual_length ) {
2018-11-07 03:23:38 +00:00
if ( allocation_has_failed_ ) return ;
2018-11-15 01:49:06 +00:00
// Bookend the start of the new data, to safeguard for precision errors in sampling.
memcpy (
& write_area_texture_ [ size_t ( write_pointers_ . write_area - 1 ) * data_type_size_ ] ,
& write_area_texture_ [ size_t ( write_pointers_ . write_area ) * data_type_size_ ] ,
data_type_size_ ) ;
2018-11-13 03:52:26 +00:00
2018-11-09 03:21:11 +00:00
// The write area was allocated in the knowledge that there's sufficient
// distance left on the current line, so there's no need to worry about carry.
2018-11-09 02:57:28 +00:00
write_pointers_ . write_area + = actual_length + 1 ;
2018-11-15 01:49:06 +00:00
// Also bookend the end.
memcpy (
& write_area_texture_ [ size_t ( write_pointers_ . write_area - 1 ) * data_type_size_ ] ,
2018-11-17 22:46:57 +00:00
& write_area_texture_ [ size_t ( write_pointers_ . write_area - 2 ) * data_type_size_ ] ,
2018-11-15 01:49:06 +00:00
data_type_size_ ) ;
2018-11-07 03:23:38 +00:00
}
void ScanTarget : : submit ( ) {
2018-11-08 03:53:46 +00:00
if ( allocation_has_failed_ ) {
2018-11-22 18:22:04 +00:00
// Reset all pointers to where they were; this also means
// the stencil won't be properly populated.
2018-11-09 02:57:28 +00:00
write_pointers_ = submit_pointers_ . load ( ) ;
2018-11-22 18:22:04 +00:00
frame_was_complete_ = false ;
2018-11-08 03:53:46 +00:00
} else {
// Advance submit pointer.
2018-11-09 02:57:28 +00:00
submit_pointers_ . store ( write_pointers_ ) ;
2018-11-08 03:53:46 +00:00
}
allocation_has_failed_ = false ;
}
2018-11-11 20:11:32 +00:00
void ScanTarget : : announce ( Event event , uint16_t x , uint16_t y ) {
2018-11-11 20:20:18 +00:00
switch ( event ) {
default : break ;
case ScanTarget : : Event : : BeginHorizontalRetrace :
2018-11-12 02:41:13 +00:00
if ( active_line_ ) {
active_line_ - > end_points [ 1 ] . x = x ;
active_line_ - > end_points [ 1 ] . y = y ;
2018-11-11 20:20:18 +00:00
}
break ;
case ScanTarget : : Event : : EndHorizontalRetrace : {
2018-11-16 02:51:27 +00:00
// Commit the most recent line only if any scans fell on it.
// Otherwise there's no point outputting it, it'll contribute nothing.
if ( provided_scans_ ) {
2018-11-19 02:39:11 +00:00
// Store metadata if concluding a previous line.
if ( active_line_ ) {
line_metadata_buffer_ [ size_t ( write_pointers_ . line ) ] . is_first_in_frame = is_first_in_frame_ ;
2018-11-22 18:22:04 +00:00
line_metadata_buffer_ [ size_t ( write_pointers_ . line ) ] . previous_frame_was_complete = frame_was_complete_ ;
2018-11-19 02:39:11 +00:00
is_first_in_frame_ = false ;
}
2018-11-16 02:51:27 +00:00
const auto read_pointers = read_pointers_ . load ( ) ;
// Attempt to allocate a new line; note allocation failure if necessary.
const auto next_line = uint16_t ( ( write_pointers_ . line + 1 ) % LineBufferHeight ) ;
if ( next_line = = read_pointers . line ) {
allocation_has_failed_ = true ;
active_line_ = nullptr ;
} else {
write_pointers_ . line = next_line ;
active_line_ = & line_buffer_ [ size_t ( write_pointers_ . line ) ] ;
}
provided_scans_ = 0 ;
}
2018-11-11 20:20:18 +00:00
2018-11-16 02:51:27 +00:00
if ( active_line_ ) {
2018-11-12 02:41:13 +00:00
active_line_ - > end_points [ 0 ] . x = x ;
active_line_ - > end_points [ 0 ] . y = y ;
active_line_ - > line = write_pointers_ . line ;
2018-11-11 20:20:18 +00:00
}
} break ;
2018-11-19 02:39:11 +00:00
case ScanTarget : : Event : : EndVerticalRetrace :
is_first_in_frame_ = true ;
2018-11-22 18:22:04 +00:00
frame_was_complete_ = true ;
2018-11-19 02:39:11 +00:00
break ;
2018-11-11 20:20:18 +00:00
}
2018-11-13 03:52:26 +00:00
// TODO: any lines that include any portion of vertical sync should be hidden.
// (maybe set a flag and zero out the line coordinates?)
2018-11-11 20:11:32 +00:00
}
2018-11-14 02:15:33 +00:00
void ScanTarget : : draw ( bool synchronous , int output_width , int output_height ) {
2018-11-13 01:15:38 +00:00
if ( fence_ ! = nullptr ) {
// if the GPU is still busy, don't wait; we'll catch it next time
if ( glClientWaitSync ( fence_ , GL_SYNC_FLUSH_COMMANDS_BIT , synchronous ? GL_TIMEOUT_IGNORED : 0 ) = = GL_TIMEOUT_EXPIRED ) {
return ;
}
fence_ = nullptr ;
}
2018-11-12 23:47:55 +00:00
2018-11-21 00:51:11 +00:00
// Spin until the is-drawing flag is reset; the wait sync above will deal
// with instances where waiting is inappropriate.
while ( is_drawing_ . test_and_set ( ) ) ;
2018-11-13 03:52:26 +00:00
2018-11-12 23:47:55 +00:00
// Grab the current read and submit pointers.
2018-11-20 04:35:12 +00:00
const auto submit_pointers = submit_pointers_ . load ( ) ;
2018-11-12 23:47:55 +00:00
const auto read_pointers = read_pointers_ . load ( ) ;
2018-11-15 01:10:38 +00:00
// Submit scans; only the new ones need to be communicated.
2018-11-14 02:15:33 +00:00
size_t new_scans = ( submit_pointers . scan_buffer + scan_buffer_ . size ( ) - read_pointers . scan_buffer ) % scan_buffer_ . size ( ) ;
if ( new_scans ) {
glBindBuffer ( GL_ARRAY_BUFFER , scan_buffer_name_ ) ;
// Map only the required portion of the buffer.
const size_t new_scans_size = new_scans * sizeof ( Scan ) ;
uint8_t * const destination = static_cast < uint8_t * > (
glMapBufferRange ( GL_ARRAY_BUFFER , 0 , GLsizeiptr ( new_scans_size ) , GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT )
) ;
if ( read_pointers . scan_buffer < submit_pointers . scan_buffer ) {
memcpy ( destination , & scan_buffer_ [ read_pointers . scan_buffer ] , new_scans_size ) ;
} else {
const size_t first_portion_length = ( scan_buffer_ . size ( ) - read_pointers . scan_buffer ) * sizeof ( Scan ) ;
memcpy ( destination , & scan_buffer_ [ read_pointers . scan_buffer ] , first_portion_length ) ;
memcpy ( & destination [ first_portion_length ] , & scan_buffer_ [ 0 ] , new_scans_size - first_portion_length ) ;
}
2018-11-15 01:10:38 +00:00
// Flush and unmap the buffer.
glFlushMappedBufferRange ( GL_ARRAY_BUFFER , 0 , GLsizeiptr ( new_scans_size ) ) ;
2018-11-14 02:15:33 +00:00
glUnmapBuffer ( GL_ARRAY_BUFFER ) ;
}
2018-11-08 03:53:46 +00:00
2018-11-09 04:02:36 +00:00
// Submit texture.
if ( submit_pointers . write_area ! = read_pointers . write_area ) {
2018-11-13 03:52:26 +00:00
glActiveTexture ( SourceData1BppTextureUnit ) ;
2018-11-09 04:02:36 +00:00
glBindTexture ( GL_TEXTURE_2D , write_area_texture_name_ ) ;
// Create storage for the texture if it doesn't yet exist; this was deferred until here
// because the pixel format wasn't initially known.
if ( ! texture_exists_ ) {
2018-11-13 03:52:26 +00:00
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE ) ;
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE ) ;
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_NEAREST ) ;
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_NEAREST ) ;
2018-11-09 04:02:36 +00:00
glTexImage2D (
GL_TEXTURE_2D ,
0 ,
internalFormatForDepth ( data_type_size_ ) ,
WriteAreaWidth ,
WriteAreaHeight ,
0 ,
formatForDepth ( data_type_size_ ) ,
GL_UNSIGNED_BYTE ,
nullptr ) ;
texture_exists_ = true ;
}
const auto start_y = TextureAddressGetY ( read_pointers . write_area ) ;
const auto end_y = TextureAddressGetY ( submit_pointers . write_area ) ;
if ( end_y > = start_y ) {
// Submit the direct region from the submit pointer to the read pointer.
glTexSubImage2D ( GL_TEXTURE_2D , 0 ,
0 , start_y ,
WriteAreaWidth ,
1 + end_y - start_y ,
formatForDepth ( data_type_size_ ) ,
GL_UNSIGNED_BYTE ,
2018-11-16 02:02:46 +00:00
& write_area_texture_ [ size_t ( TextureAddress ( 0 , start_y ) ) * data_type_size_ ] ) ;
2018-11-09 04:02:36 +00:00
} else {
// The circular buffer wrapped around; submit the data from the read pointer to the end of
// the buffer and from the start of the buffer to the submit pointer.
glTexSubImage2D ( GL_TEXTURE_2D , 0 ,
0 , 0 ,
WriteAreaWidth ,
1 + end_y ,
formatForDepth ( data_type_size_ ) ,
GL_UNSIGNED_BYTE ,
& write_area_texture_ [ 0 ] ) ;
glTexSubImage2D ( GL_TEXTURE_2D , 0 ,
0 , start_y ,
WriteAreaWidth ,
WriteAreaHeight - start_y ,
formatForDepth ( data_type_size_ ) ,
GL_UNSIGNED_BYTE ,
2018-11-16 02:02:46 +00:00
& write_area_texture_ [ size_t ( TextureAddress ( 0 , start_y ) ) * data_type_size_ ] ) ;
2018-11-09 04:02:36 +00:00
}
}
2018-11-14 02:15:33 +00:00
// Push new input to the unprocessed line buffer.
if ( new_scans ) {
2018-11-16 02:51:27 +00:00
glDisable ( GL_BLEND ) ;
2018-11-14 02:15:33 +00:00
unprocessed_line_texture_ . bind_framebuffer ( ) ;
2018-11-15 02:19:14 +00:00
// Clear newly-touched lines; that is everything from (read+1) to submit.
const uint16_t first_line_to_clear = ( read_pointers . line + 1 ) % line_buffer_ . size ( ) ;
const uint16_t final_line_to_clear = submit_pointers . line ;
if ( first_line_to_clear ! = final_line_to_clear ) {
glEnable ( GL_SCISSOR_TEST ) ;
if ( first_line_to_clear < final_line_to_clear ) {
glScissor ( 0 , first_line_to_clear , unprocessed_line_texture_ . get_width ( ) , final_line_to_clear - first_line_to_clear ) ;
glClear ( GL_COLOR_BUFFER_BIT ) ;
} else {
glScissor ( 0 , 0 , unprocessed_line_texture_ . get_width ( ) , final_line_to_clear ) ;
glClear ( GL_COLOR_BUFFER_BIT ) ;
2018-11-21 23:27:04 +00:00
glScissor ( 0 , first_line_to_clear , unprocessed_line_texture_ . get_width ( ) , unprocessed_line_texture_ . get_height ( ) - first_line_to_clear ) ;
2018-11-15 02:19:14 +00:00
glClear ( GL_COLOR_BUFFER_BIT ) ;
}
glDisable ( GL_SCISSOR_TEST ) ;
}
2018-11-24 03:34:38 +00:00
// Apply new spans. They definitely always go to the first buffer.
2018-11-14 02:15:33 +00:00
glBindVertexArray ( scan_vertex_array_ ) ;
input_shader_ - > bind ( ) ;
glDrawArraysInstanced ( GL_TRIANGLE_STRIP , 0 , 4 , GLsizei ( new_scans ) ) ;
2018-11-24 03:34:38 +00:00
// If there are any further pipeline stages, apply them.
for ( auto & stage : pipeline_stages_ ) {
stage . target . bind_framebuffer ( ) ;
stage . shader - > bind ( ) ;
glDrawArraysInstanced ( GL_TRIANGLE_STRIP , 0 , 4 , GLsizei ( new_scans ) ) ;
}
2018-11-14 02:15:33 +00:00
}
2018-11-09 03:21:11 +00:00
2018-11-19 02:39:11 +00:00
// Ensure the accumulation buffer is properly sized.
2018-11-19 03:22:43 +00:00
const int proportional_width = ( output_height * 4 ) / 3 ;
2018-11-21 23:27:04 +00:00
if ( ! accumulation_texture_ | | ( /* !synchronous && */ ( accumulation_texture_ - > get_width ( ) ! = proportional_width | | accumulation_texture_ - > get_height ( ) ! = output_height ) ) ) {
2018-11-19 02:39:11 +00:00
std : : unique_ptr < OpenGL : : TextureTarget > new_framebuffer (
new TextureTarget (
2018-11-19 03:22:43 +00:00
GLsizei ( proportional_width ) ,
2018-11-19 02:39:11 +00:00
GLsizei ( output_height ) ,
AccumulationTextureUnit ,
GL_LINEAR ,
true ) ) ;
if ( accumulation_texture_ ) {
new_framebuffer - > bind_framebuffer ( ) ;
2018-11-21 00:51:11 +00:00
glClear ( GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ) ;
2018-11-19 02:39:11 +00:00
glActiveTexture ( AccumulationTextureUnit ) ;
accumulation_texture_ - > bind_texture ( ) ;
accumulation_texture_ - > draw ( float ( output_width ) / float ( output_height ) ) ;
2018-11-21 00:51:11 +00:00
glClear ( GL_STENCIL_BUFFER_BIT ) ;
2018-11-19 02:39:11 +00:00
new_framebuffer - > bind_texture ( ) ;
}
accumulation_texture_ = std : : move ( new_framebuffer ) ;
2018-11-21 00:51:11 +00:00
// In the absence of a way to resize a stencil buffer, just mark
// what's currently present as invalid to avoid an improper clear
// for this frame.
stencil_is_valid_ = false ;
2018-11-19 02:39:11 +00:00
}
2018-11-20 04:25:26 +00:00
// Figure out how many new spans are ostensible ready; use two less than that.
uint16_t new_spans = ( submit_pointers . line + LineBufferHeight - read_pointers . line ) % LineBufferHeight ;
2018-11-20 04:35:12 +00:00
if ( new_spans ) {
2018-11-20 04:25:26 +00:00
// Bind the accumulation framebuffer.
accumulation_texture_ - > bind_framebuffer ( ) ;
// Enable blending and stenciling, and ensure spans increment the stencil buffer.
glEnable ( GL_BLEND ) ;
glEnable ( GL_STENCIL_TEST ) ;
glStencilFunc ( GL_EQUAL , 0 , GLuint ( - 1 ) ) ;
glStencilOp ( GL_KEEP , GL_KEEP , GL_INCR ) ;
// Prepare to output lines.
glBindVertexArray ( line_vertex_array_ ) ;
output_shader_ - > bind ( ) ;
// Prepare to upload data that will consitute lines.
glBindBuffer ( GL_ARRAY_BUFFER , line_buffer_name_ ) ;
// Divide spans by which frame they're in.
uint16_t start_line = read_pointers . line ;
while ( new_spans ) {
uint16_t end_line = start_line + 1 ;
// Find the limit of spans to draw in this cycle.
size_t spans = 1 ;
while ( end_line ! = submit_pointers . line & & ! line_metadata_buffer_ [ end_line ] . is_first_in_frame ) {
end_line = ( end_line + 1 ) % LineBufferHeight ;
+ + spans ;
}
2018-11-19 03:22:43 +00:00
2018-11-21 23:27:04 +00:00
// If this is start-of-frame, clear any untouched pixels and flush the stencil buffer
if ( line_metadata_buffer_ [ start_line ] . is_first_in_frame ) {
2018-11-22 18:22:04 +00:00
if ( stencil_is_valid_ & & line_metadata_buffer_ [ start_line ] . previous_frame_was_complete ) {
2018-11-22 22:20:31 +00:00
full_display_rectangle_ . draw ( 0.0f , 0.0f , 0.0f ) ;
2018-11-21 23:27:04 +00:00
}
stencil_is_valid_ = true ;
glClear ( GL_STENCIL_BUFFER_BIT ) ;
// Rebind the program for span output.
glBindVertexArray ( line_vertex_array_ ) ;
output_shader_ - > bind ( ) ;
}
2018-11-20 04:25:26 +00:00
// Upload and draw.
const auto buffer_size = spans * sizeof ( Line ) ;
if ( ! end_line | | end_line > start_line ) {
glBufferSubData ( GL_ARRAY_BUFFER , 0 , GLsizeiptr ( buffer_size ) , & line_buffer_ [ start_line ] ) ;
} else {
uint8_t * destination = static_cast < uint8_t * > (
glMapBufferRange ( GL_ARRAY_BUFFER , 0 , GLsizeiptr ( buffer_size ) , GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT )
) ;
assert ( destination ) ;
const size_t buffer_length = line_buffer_ . size ( ) * sizeof ( Line ) ;
const size_t start_position = start_line * sizeof ( Line ) ;
memcpy ( & destination [ 0 ] , & line_buffer_ [ start_line ] , buffer_length - start_position ) ;
memcpy ( & destination [ buffer_length - start_position ] , & line_buffer_ [ 0 ] , end_line * sizeof ( Line ) ) ;
glFlushMappedBufferRange ( GL_ARRAY_BUFFER , 0 , GLsizeiptr ( buffer_size ) ) ;
glUnmapBuffer ( GL_ARRAY_BUFFER ) ;
}
2018-11-14 02:15:33 +00:00
2018-11-20 04:25:26 +00:00
glDrawArraysInstanced ( GL_TRIANGLE_STRIP , 0 , 4 , GLsizei ( spans ) ) ;
start_line = end_line ;
new_spans - = spans ;
}
2018-11-20 04:35:12 +00:00
// Disable blending and the stencil test again.
2018-11-20 04:25:26 +00:00
glDisable ( GL_STENCIL_TEST ) ;
glDisable ( GL_BLEND ) ;
}
2018-11-19 03:22:43 +00:00
2018-11-19 02:39:11 +00:00
// Copy the accumulatiion texture to the target (TODO: don't assume framebuffer 0).
glBindFramebuffer ( GL_FRAMEBUFFER , 0 ) ;
glViewport ( 0 , 0 , ( GLsizei ) output_width , ( GLsizei ) output_height ) ;
glClear ( GL_COLOR_BUFFER_BIT ) ;
2018-11-24 21:06:26 +00:00
// unprocessed_line_texture_.bind_texture();
// unprocessed_line_texture_.draw(float(output_width) / float(output_height), 4.0f / 255.0f);
// pipeline_stages_.front().target.bind_texture();
// pipeline_stages_.front().target.draw(float(output_width) / float(output_height), 4.0f / 255.0f);
2018-11-19 02:39:11 +00:00
accumulation_texture_ - > bind_texture ( ) ;
accumulation_texture_ - > draw ( float ( output_width ) / float ( output_height ) , 4.0f / 255.0f ) ;
2018-11-09 02:57:28 +00:00
// All data now having been spooled to the GPU, update the read pointers to
// the submit pointer location.
read_pointers_ . store ( submit_pointers ) ;
2018-11-14 02:15:33 +00:00
// Grab a fence sync object to avoid busy waiting upon the next extry into this
// function, and reset the is_drawing_ flag.
2018-11-13 01:15:38 +00:00
fence_ = glFenceSync ( GL_SYNC_GPU_COMMANDS_COMPLETE , 0 ) ;
2018-11-13 03:52:26 +00:00
is_drawing_ . clear ( ) ;
2018-11-07 03:23:38 +00:00
}