From ba2adf8bb11118cccddcd80c6b92854ad01c9ac2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 10:34:54 +0800 Subject: [PATCH 01/40] Normalised on `std::vector` rather than `std::unique_ptr` as (probably?) more idiomatic. Improved structure slightly. Introduced a container for line data, albeit one that isn't yet used. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 15 ++++---- Outputs/CRT/Internals/CRTOpenGL.hpp | 57 +++++++++++++++-------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 21e606fd9..a7bc9c96e 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -80,9 +80,9 @@ static int getCircularRanges(GLsizei *start_pointer, GLsizei *end_pointer, GLsiz } } -static GLsizei submitArrayData(GLuint buffer, uint8_t *source, GLsizei *length_pointer) +static GLsizei submitArrayData(GLuint buffer, uint8_t *source, size_t *length_pointer) { - GLsizei length = *length_pointer; + GLsizei length = (GLsizei)*length_pointer; glBindBuffer(GL_ARRAY_BUFFER, buffer); uint8_t *data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); @@ -114,16 +114,15 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _cleared_composite_output_y(0), _composite_shader(nullptr), _rgb_shader(nullptr), - _output_buffer_data(new uint8_t[OutputVertexBufferDataSize]), - _source_buffer_data(new uint8_t[SourceVertexBufferDataSize]), - _output_buffer_data_pointer(0), - _source_buffer_data_pointer(0), _last_output_width(0), _last_output_height(0), _fence(nullptr) { _buffer_builder.reset(new CRTInputBufferBuilder(buffer_depth)); + _output_buffer.data.resize(OutputVertexBufferDataSize); + _source_buffer.data.resize(OutputVertexBufferDataSize); + glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); glBlendColor(0.6f, 0.6f, 0.6f, 1.0f); @@ -224,10 +223,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _output_mutex->lock(); // release the mapping, giving up on trying to draw if data has been lost - GLsizei submitted_output_data = submitArrayData(output_array_buffer, _output_buffer_data.get(), &_output_buffer_data_pointer); + GLsizei submitted_output_data = submitArrayData(output_array_buffer, _output_buffer.data.data(), &_output_buffer.pointer); // bind and flush the source array buffer - GLsizei submitted_source_data = submitArrayData(source_array_buffer, _source_buffer_data.get(), &_source_buffer_data_pointer); + GLsizei submitted_source_data = submitArrayData(source_array_buffer, _source_buffer.data.data(), &_source_buffer.pointer); // determine how many lines are newly reclaimed; they'll need to be cleared Range clearing_zones[2]; diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 4f0a79a32..65a0b7423 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -20,6 +20,7 @@ #include "Shaders/IntermediateShader.hpp" #include +#include namespace Outputs { namespace CRT { @@ -93,6 +94,28 @@ class OpenGLOutputBuilder { OpenGLOutputBuilder(unsigned int buffer_depth); ~OpenGLOutputBuilder(); + inline uint8_t *get_next_source_run() + { + if(_source_buffer.pointer == SourceVertexBufferDataSize) return nullptr; + return &_source_buffer.data[_source_buffer.pointer]; + } + + inline void complete_source_run() + { + _source_buffer.pointer += SourceVertexSize; + } + + inline uint8_t *get_next_output_run() + { + if(_output_buffer.pointer == OutputVertexBufferDataSize) return nullptr; + return &_output_buffer.data[_output_buffer.pointer]; + } + + inline void complete_output_run() + { + _output_buffer.pointer += OutputVertexSize; + } + inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) { _output_mutex->lock(); @@ -108,31 +131,9 @@ class OpenGLOutputBuilder { _visible_area = visible_area; } - inline uint8_t *get_next_source_run() - { - if(_source_buffer_data_pointer == SourceVertexBufferDataSize) return nullptr; - return &_source_buffer_data.get()[_source_buffer_data_pointer]; - } - - inline void complete_source_run() - { - _source_buffer_data_pointer += SourceVertexSize; - } - inline bool composite_output_run_has_room_for_vertex() { - return _output_buffer_data_pointer < OutputVertexBufferDataSize; - } - - inline uint8_t *get_next_output_run() - { - if(_output_buffer_data_pointer == OutputVertexBufferDataSize) return nullptr; - return &_output_buffer_data.get()[_output_buffer_data_pointer]; - } - - inline void complete_output_run() - { - _output_buffer_data_pointer += OutputVertexSize; + return _output_buffer.pointer < OutputVertexBufferDataSize; } inline void lock_output() @@ -199,11 +200,11 @@ class OpenGLOutputBuilder { void set_output_device(OutputDevice output_device); void set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider); - std::unique_ptr _source_buffer_data; - GLsizei _source_buffer_data_pointer; - - std::unique_ptr _output_buffer_data; - GLsizei _output_buffer_data_pointer; + struct Buffer { + std::vector data; + size_t pointer; + Buffer() : pointer(0) {} + } _line_buffer, _source_buffer, _output_buffer; GLsync _fence; }; From ccedb6bea6ea5b4527ff79186b207a6e5884364b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 10:49:18 +0800 Subject: [PATCH 02/40] Introduced an intermediate buffer that collects lines before flushing them to the output buffer. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 2 +- Outputs/CRT/Internals/CRTOpenGL.hpp | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index a7bc9c96e..68fbc3d46 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -121,7 +121,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _buffer_builder.reset(new CRTInputBufferBuilder(buffer_depth)); _output_buffer.data.resize(OutputVertexBufferDataSize); - _source_buffer.data.resize(OutputVertexBufferDataSize); + _source_buffer.data.resize(SourceVertexBufferDataSize); glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); glBlendColor(0.6f, 0.6f, 0.6f, 1.0f); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 65a0b7423..295d74fa7 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -96,13 +96,13 @@ class OpenGLOutputBuilder { inline uint8_t *get_next_source_run() { - if(_source_buffer.pointer == SourceVertexBufferDataSize) return nullptr; - return &_source_buffer.data[_source_buffer.pointer]; + _line_buffer.data.resize(_line_buffer.pointer + SourceVertexSize); + return &_line_buffer.data[_line_buffer.pointer]; } inline void complete_source_run() { - _source_buffer.pointer += SourceVertexSize; + _line_buffer.pointer += SourceVertexSize; } inline uint8_t *get_next_output_run() @@ -113,7 +113,15 @@ class OpenGLOutputBuilder { inline void complete_output_run() { - _output_buffer.pointer += OutputVertexSize; + size_t line_buffer_size = _line_buffer.data.size(); + if(_source_buffer.pointer + line_buffer_size < SourceVertexBufferDataSize) + { + _output_buffer.pointer += OutputVertexSize; + memcpy(&_source_buffer.data[_source_buffer.pointer], _line_buffer.data.data(), _line_buffer.data.size()); + _source_buffer.pointer += _line_buffer.data.size(); + _line_buffer.data.resize(0); + _line_buffer.pointer = 0; + } } inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) From 294adde344910ee5e5dc30dcd015655def1306b8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 10:59:12 +0800 Subject: [PATCH 03/40] Eliminated zoned clearing, in favour of a complete buffer clear on each load and attempted to reduce locking. But imperfectly. --- Outputs/CRT/CRT.cpp | 16 ++-------------- Outputs/CRT/Internals/CRTOpenGL.cpp | 15 ++------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 9d1b9436f..05ca75550 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -194,12 +194,14 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi } else { + _openGL_output_builder->lock_output(); uint8_t *next_run = _openGL_output_builder->get_next_output_run(); output_x1() = _output_run.x1; output_position_y() = _output_run.y; output_tex_y() = _output_run.tex_y; output_x2() = (uint16_t)_horizontal_flywheel->get_current_output_position(); _openGL_output_builder->complete_output_run(); + _openGL_output_builder->unlock_output(); } _is_writing_composite_run ^= true; } @@ -218,11 +220,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi _frames_since_last_delegate_call++; if(_frames_since_last_delegate_call == 20) { - // Yuck: to deal with the permitted ability of the delegate to make CRT changes that require the lock to be - // asserted during the delegate call, temporarily release the lock. TODO: find a less blunt instrument. - _openGL_output_builder->unlock_output(); _delegate->crt_did_end_batch_of_frames(this, _frames_since_last_delegate_call, _vertical_flywheel->get_and_reset_number_of_surprises()); - _openGL_output_builder->lock_output(); _frames_since_last_delegate_call = 0; } } @@ -283,29 +281,24 @@ void CRT::output_scan(const Scan *const scan) */ void CRT::output_sync(unsigned int number_of_cycles) { - _openGL_output_builder->lock_output(); Scan scan{ .type = Scan::Type::Sync, .number_of_cycles = number_of_cycles }; output_scan(&scan); - _openGL_output_builder->unlock_output(); } void CRT::output_blank(unsigned int number_of_cycles) { - _openGL_output_builder->lock_output(); Scan scan { .type = Scan::Type::Blank, .number_of_cycles = number_of_cycles }; output_scan(&scan); - _openGL_output_builder->unlock_output(); } void CRT::output_level(unsigned int number_of_cycles) { - _openGL_output_builder->lock_output(); if(!_openGL_output_builder->input_buffer_is_full()) { Scan scan { @@ -324,12 +317,10 @@ void CRT::output_level(unsigned int number_of_cycles) }; output_scan(&scan); } - _openGL_output_builder->unlock_output(); } void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) { - _openGL_output_builder->lock_output(); Scan scan { .type = Scan::Type::ColourBurst, .number_of_cycles = number_of_cycles, @@ -337,12 +328,10 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint .amplitude = amplitude }; output_scan(&scan); - _openGL_output_builder->unlock_output(); } void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { - _openGL_output_builder->lock_output(); if(!_openGL_output_builder->input_buffer_is_full()) { _openGL_output_builder->reduce_previous_allocation_to(number_of_cycles / source_divider); @@ -363,7 +352,6 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider }; output_scan(&scan); } - _openGL_output_builder->unlock_output(); } Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 68fbc3d46..beadc565c 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -281,26 +281,15 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // all drawing will be from the source vertex array and without blending glBindVertexArray(source_vertex_array); glDisable(GL_BLEND); + glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0); while(active_pipeline->target) { // switch to the initial texture active_pipeline->target->bind_framebuffer(); + glClear(GL_COLOR_BUFFER_BIT); active_pipeline->shader->bind(); - // clear as desired - if(number_of_clearing_zones) - { - glEnable(GL_SCISSOR_TEST); - glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0); - for(int c = 0; c < number_of_clearing_zones; c++) - { - glScissor(0, clearing_zones[c].location, IntermediateBufferWidth, clearing_zones[c].length); - glClear(GL_COLOR_BUFFER_BIT); - } - glDisable(GL_SCISSOR_TEST); - } - // draw as desired glDrawArraysInstanced(GL_LINES, 0, 2, submitted_source_data / SourceVertexSize); From 311f8c0b47a1a3b2cfa3aa7253b137d91aa10fff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 11:10:07 +0800 Subject: [PATCH 04/40] Restored audio. --- Machines/Electron/Electron.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 805700752..636c3faab 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -485,6 +485,7 @@ void Machine::synchronise() { update_display(); update_audio(); + _speaker->flush(); } void Machine::configure_as_target(const StaticAnalyser::Target &target) From f63e8490929892650d60f407521be05d61bd92e1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 11:10:34 +0800 Subject: [PATCH 05/40] Sought completely to eliminate the outgoing concept of 'clearing zones'. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 14 +++----------- Outputs/CRT/Internals/CRTOpenGL.hpp | 4 ++-- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index beadc565c..8781b6fc7 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -111,7 +111,6 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _draw_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), _composite_src_output_y(0), - _cleared_composite_output_y(0), _composite_shader(nullptr), _rgb_shader(nullptr), _last_output_width(0), @@ -228,16 +227,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // bind and flush the source array buffer GLsizei submitted_source_data = submitArrayData(source_array_buffer, _source_buffer.data.data(), &_source_buffer.pointer); - // determine how many lines are newly reclaimed; they'll need to be cleared - Range clearing_zones[2]; - - // the clearing zones for the composite output Y are calculated with a fixed offset of '1' which has the effect of clearing - // one ahead of the expected drawing area this frame; that's because the current _composite_src_output_y may or may not have been - // written to during the last update, so we want it to have been cleared during the last update. - int number_of_clearing_zones = getCircularRanges(&_cleared_composite_output_y, &_composite_src_output_y, IntermediateBufferHeight, 1, 1, clearing_zones); - uint16_t completed_texture_y = _buffer_builder->get_and_finalise_current_line(); - - // upload new source pixels + // upload new source pixels, if any + uint16_t completed_texture_y = _buffer_builder->get_and_finalise_current_line(); if(completed_texture_y) { glActiveTexture(source_data_texture_unit); @@ -296,6 +287,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out active_pipeline++; } } + _composite_src_output_y = 0; // transfer to framebuffer framebuffer->bind_framebuffer(); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 295d74fa7..fca1e6532 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -62,7 +62,7 @@ class OpenGLOutputBuilder { std::unique_ptr _draw_mutex; // transient buffers indicating composite data not yet decoded - GLsizei _composite_src_output_y, _cleared_composite_output_y; + GLsizei _composite_src_output_y; std::unique_ptr output_shader_program; std::unique_ptr composite_input_shader_program, composite_separation_filter_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program; @@ -166,7 +166,7 @@ class OpenGLOutputBuilder { inline bool composite_output_buffer_is_full() { - return _composite_src_output_y == _cleared_composite_output_y + IntermediateBufferHeight; + return _composite_src_output_y == IntermediateBufferHeight; } inline void increment_composite_output_y() From 7a737e0790f3e7e04f119c237fbfa9cf8ceeefbf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 11:12:08 +0800 Subject: [PATCH 06/40] Both pathways start with a buffer that clears to black. So no need to keep going on about it. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 8781b6fc7..cc9497f26 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -272,7 +272,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // all drawing will be from the source vertex array and without blending glBindVertexArray(source_vertex_array); glDisable(GL_BLEND); - glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0); while(active_pipeline->target) { From 44d3fd6d5bcfe5868ca9d708ea8be8c701323dfc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 11:22:12 +0800 Subject: [PATCH 07/40] Fixed mistimed reset of the target output line. Now all that's left is occasional noise. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index cc9497f26..e9e801896 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -239,6 +239,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _buffer_builder->get_image_pointer()); } + // buffer usage restart from 0 for the next time around + _composite_src_output_y = 0; + // data having been grabbed, allow the machine to continue _output_mutex->unlock(); @@ -285,14 +288,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out active_pipeline++; } - } - _composite_src_output_y = 0; - // transfer to framebuffer - framebuffer->bind_framebuffer(); + // transfer to framebuffer + framebuffer->bind_framebuffer(); - if(submitted_output_data) - { glEnable(GL_BLEND); // Ensure we're back on the output framebuffer, drawing from the output array buffer From 9ee11d7765c81ac6c949796a4df0dba39d5198d6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 11:34:05 +0800 Subject: [PATCH 08/40] At the expense of API simplicity, at least for now, resolved the most glaring cause of dirty lines. --- Outputs/CRT/CRT.cpp | 18 +++++++++++++++--- Outputs/CRT/CRT.hpp | 2 +- Outputs/CRT/Internals/CRTOpenGL.hpp | 8 +++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 05ca75550..b722fab26 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -141,7 +141,8 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi source_input_position_x1() = tex_x; source_input_position_y() = tex_y; source_output_position_x1() = (uint16_t)_horizontal_flywheel->get_current_output_position(); - source_output_position_y() = _openGL_output_builder->get_composite_output_y(); + // Don't write output_y now, write it later; we won't necessarily know what it is outside of the locked region +// source_output_position_y() = _openGL_output_builder->get_composite_output_y(); source_phase() = _colour_burst_phase; source_amplitude() = _colour_burst_amplitude; source_phase_time() = (uint8_t)_colour_burst_time; // assumption: burst was within the first 1/16 of the line @@ -190,17 +191,28 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi { _output_run.x1 = (uint16_t)_horizontal_flywheel->get_current_output_position(); _output_run.y = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - _output_run.tex_y = _openGL_output_builder->get_composite_output_y(); } else { _openGL_output_builder->lock_output(); + + // Get and write all those previously unwritten output ys + uint16_t output_y = _openGL_output_builder->get_composite_output_y(); + size_t size; + uint8_t *buffered_lines = _openGL_output_builder->get_buffered_source_runs(size); + for(size_t position = 0; position < size; position += SourceVertexSize) + { + (*(uint16_t *)&buffered_lines[position + SourceVertexOffsetOfOutputStart + 2]) = output_y; + } + + // Construct the output run uint8_t *next_run = _openGL_output_builder->get_next_output_run(); output_x1() = _output_run.x1; output_position_y() = _output_run.y; - output_tex_y() = _output_run.tex_y; + output_tex_y() = output_y; output_x2() = (uint16_t)_horizontal_flywheel->get_current_output_position(); _openGL_output_builder->complete_output_run(); + _openGL_output_builder->unlock_output(); } _is_writing_composite_run ^= true; diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 59d4ca962..78bc4ec1a 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -83,7 +83,7 @@ class CRT { // temporary storage used during the construction of output runs struct { - uint16_t x1, y, tex_y; + uint16_t x1, y; } _output_run; // The delegate diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index fca1e6532..baab3a33e 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -105,6 +105,12 @@ class OpenGLOutputBuilder { _line_buffer.pointer += SourceVertexSize; } + inline uint8_t *get_buffered_source_runs(size_t &size) + { + size = _line_buffer.pointer; + return _line_buffer.data.data(); + } + inline uint8_t *get_next_output_run() { if(_output_buffer.pointer == OutputVertexBufferDataSize) return nullptr; @@ -161,7 +167,7 @@ class OpenGLOutputBuilder { inline uint16_t get_composite_output_y() { - return _composite_src_output_y % IntermediateBufferHeight; + return (uint16_t)_composite_src_output_y; } inline bool composite_output_buffer_is_full() From 6cb4950db4a9b9b9edba927bb6a22fe8d1d16fb9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 11:53:15 +0800 Subject: [PATCH 09/40] Improved run-off area. --- Outputs/CRT/CRT.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index b722fab26..4b453b143 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -370,7 +370,9 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_ { first_cycle_after_sync *= _time_multiplier; number_of_cycles *= _time_multiplier; - number_of_lines++; + + first_line_after_sync -= 2; + number_of_lines += 4; // determine prima facie x extent unsigned int horizontal_period = _horizontal_flywheel->get_standard_period(); From 4ee4400801df2000c96263c3fdb22dc9245ccefd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 11:57:22 +0800 Subject: [PATCH 10/40] Removed dead code. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 39 ----------------------------- 1 file changed, 39 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index e9e801896..065f588f8 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -41,45 +41,6 @@ struct Range { GLsizei location, length; }; -static int getCircularRanges(GLsizei *start_pointer, GLsizei *end_pointer, GLsizei buffer_length, GLsizei granularity, GLsizei offset, Range *ranges) -{ - GLsizei start = *start_pointer; - GLsizei end = *end_pointer; - - *end_pointer %= buffer_length; - *start_pointer = *end_pointer; - - start += offset; - end += offset; - start -= start%granularity; - end -= end%granularity; - - GLsizei length = end - start; - if(!length) return 0; - if(length >= buffer_length) - { - ranges[0].location = 0; - ranges[0].length = buffer_length; - return 1; - } - else - { - ranges[0].location = start % buffer_length; - if(ranges[0].location + length <= buffer_length) - { - ranges[0].length = length; - return 1; - } - else - { - ranges[0].length = buffer_length - ranges[0].location; - ranges[1].location = 0; - ranges[1].length = length - ranges[0].length; - return 2; - } - } -} - static GLsizei submitArrayData(GLuint buffer, uint8_t *source, size_t *length_pointer) { GLsizei length = (GLsizei)*length_pointer; From 4d0d5eb9193eb31d392dac0250c974ed050bb1fa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 12:31:32 +0800 Subject: [PATCH 11/40] Renamed the 'input buffer builder' to the 'input texture builder' to be explicit about what sort of buffer, and killed the prefix since it's namespaced. Also switched to `std::vector`. --- .../Clock Signal.xcodeproj/project.pbxproj | 12 +-- .../CRT/Internals/CRTInputBufferBuilder.hpp | 62 --------------- Outputs/CRT/Internals/CRTOpenGL.cpp | 12 +-- Outputs/CRT/Internals/CRTOpenGL.hpp | 16 ++-- ...ferBuilder.cpp => InputTextureBuilder.cpp} | 37 ++++----- Outputs/CRT/Internals/InputTextureBuilder.hpp | 78 +++++++++++++++++++ 6 files changed, 118 insertions(+), 99 deletions(-) delete mode 100644 Outputs/CRT/Internals/CRTInputBufferBuilder.hpp rename Outputs/CRT/Internals/{CRTInputBufferBuilder.cpp => InputTextureBuilder.cpp} (72%) create mode 100644 Outputs/CRT/Internals/InputTextureBuilder.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f7941d5bc..fd3a1442f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -352,7 +352,7 @@ 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; }; 4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; 4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; }; - 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */; }; + 4BBF99141C8FBA6F0075DAFB /* InputTextureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* InputTextureBuilder.cpp */; }; 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; }; 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; }; @@ -828,8 +828,8 @@ 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = ""; }; 4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = ""; }; 4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = ""; }; - 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTInputBufferBuilder.cpp; sourceTree = ""; }; - 4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTInputBufferBuilder.hpp; sourceTree = ""; }; + 4BBF99081C8FBA6F0075DAFB /* InputTextureBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InputTextureBuilder.cpp; sourceTree = ""; }; + 4BBF99091C8FBA6F0075DAFB /* InputTextureBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = InputTextureBuilder.hpp; sourceTree = ""; }; 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = ""; }; 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTOpenGL.hpp; sourceTree = ""; }; 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = ""; }; @@ -1688,8 +1688,8 @@ isa = PBXGroup; children = ( 4BC3B74C1CD194CC00F86E85 /* Shaders */, - 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */, - 4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */, + 4BBF99081C8FBA6F0075DAFB /* InputTextureBuilder.cpp */, + 4BBF99091C8FBA6F0075DAFB /* InputTextureBuilder.hpp */, 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, @@ -2320,7 +2320,7 @@ 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */, 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, - 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, + 4BBF99141C8FBA6F0075DAFB /* InputTextureBuilder.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */, 4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */, diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp deleted file mode 100644 index 39afba9d0..000000000 --- a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp +++ /dev/null @@ -1,62 +0,0 @@ -// -// CRTInputBufferBuilder.hpp -// Clock Signal -// -// Created by Thomas Harte on 08/03/2016. -// Copyright © 2016 Thomas Harte. All rights reserved. -// - -#ifndef CRTInputBufferBuilder_hpp -#define CRTInputBufferBuilder_hpp - -#include -#include -#include -#include "CRTConstants.hpp" -#include "OpenGL.hpp" -#include - -namespace Outputs { -namespace CRT { - -struct CRTInputBufferBuilder { - CRTInputBufferBuilder(size_t bytes_per_pixel); - - void allocate_write_area(size_t required_length); - void reduce_previous_allocation_to(size_t actual_length); - - uint16_t get_and_finalise_current_line(); - uint8_t *get_image_pointer(); - - uint8_t *get_write_target(); - - uint16_t get_last_write_x_position(); - - uint16_t get_last_write_y_position(); - - size_t get_bytes_per_pixel(); - - bool is_full(); - - private: - // where pixel data will be put to the next time a write is requested - uint16_t _next_write_x_position, _next_write_y_position; - - // the most recent position returned for pixel data writing - uint16_t _write_x_position, _write_y_position; - - // details of the most recent allocation - size_t _write_target_pointer; - size_t _last_allocation_amount; - - // the buffer size - size_t _bytes_per_pixel; - - // the buffer - std::unique_ptr _image; -}; - -} -} - -#endif /* CRTInputBufferBuilder_hpp */ diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 065f588f8..ec772821e 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -45,6 +45,8 @@ static GLsizei submitArrayData(GLuint buffer, uint8_t *source, size_t *length_po { GLsizei length = (GLsizei)*length_pointer; + // The development machine is a Mac; Apple seemingly having given up on OpenGL (?), GL_MAP_PERSISTENT_BIT is not + // available. Which possibly means I'm doing no better here than a traditional buffer submit, but there it is. glBindBuffer(GL_ARRAY_BUFFER, buffer); uint8_t *data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); memcpy(data, source, (size_t)length); @@ -78,7 +80,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _last_output_height(0), _fence(nullptr) { - _buffer_builder.reset(new CRTInputBufferBuilder(buffer_depth)); + _texture_builder.reset(new InputTextureBuilder(buffer_depth)); _output_buffer.data.resize(OutputVertexBufferDataSize); _source_buffer.data.resize(SourceVertexBufferDataSize); @@ -100,7 +102,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : 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); - glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(_buffer_builder->get_bytes_per_pixel()), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(_buffer_builder->get_bytes_per_pixel()), GL_UNSIGNED_BYTE, nullptr); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(_texture_builder->get_bytes_per_pixel()), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(_texture_builder->get_bytes_per_pixel()), GL_UNSIGNED_BYTE, nullptr); // create the output vertex array glGenVertexArrays(1, &output_vertex_array); @@ -189,15 +191,15 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out GLsizei submitted_source_data = submitArrayData(source_array_buffer, _source_buffer.data.data(), &_source_buffer.pointer); // upload new source pixels, if any - uint16_t completed_texture_y = _buffer_builder->get_and_finalise_current_line(); + uint16_t completed_texture_y = _texture_builder->get_and_finalise_current_line(); if(completed_texture_y) { glActiveTexture(source_data_texture_unit); glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, InputBufferBuilderWidth, completed_texture_y, - formatForDepth(_buffer_builder->get_bytes_per_pixel()), GL_UNSIGNED_BYTE, - _buffer_builder->get_image_pointer()); + formatForDepth(_texture_builder->get_bytes_per_pixel()), GL_UNSIGNED_BYTE, + _texture_builder->get_image_pointer()); } // buffer usage restart from 0 for the next time around diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index baab3a33e..b7a8b86e7 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -14,7 +14,7 @@ #include "OpenGL.hpp" #include "TextureTarget.hpp" #include "Shader.hpp" -#include "CRTInputBufferBuilder.hpp" +#include "InputTextureBuilder.hpp" #include "Shaders/OutputShader.hpp" #include "Shaders/IntermediateShader.hpp" @@ -57,7 +57,7 @@ class OpenGLOutputBuilder { void prepare_source_vertex_array(); // the run and input data buffers - std::unique_ptr _buffer_builder; + std::unique_ptr _texture_builder; std::unique_ptr _output_mutex; std::unique_ptr _draw_mutex; @@ -183,28 +183,28 @@ class OpenGLOutputBuilder { inline uint8_t *allocate_write_area(size_t required_length) { - _buffer_builder->allocate_write_area(required_length); - return _buffer_builder->get_write_target(); + _texture_builder->allocate_write_area(required_length); + return _texture_builder->get_write_target(); } inline void reduce_previous_allocation_to(size_t actual_length) { - _buffer_builder->reduce_previous_allocation_to(actual_length); + _texture_builder->reduce_previous_allocation_to(actual_length); } inline bool input_buffer_is_full() { - return _buffer_builder->is_full(); + return _texture_builder->is_full(); } inline uint16_t get_last_write_x_posititon() { - return _buffer_builder->get_last_write_x_position(); + return _texture_builder->get_last_write_x_position(); } inline uint16_t get_last_write_y_posititon() { - return _buffer_builder->get_last_write_y_position(); + return _texture_builder->get_last_write_y_position(); } void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty); diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp b/Outputs/CRT/Internals/InputTextureBuilder.cpp similarity index 72% rename from Outputs/CRT/Internals/CRTInputBufferBuilder.cpp rename to Outputs/CRT/Internals/InputTextureBuilder.cpp index 983b1f1ee..8b3d6103b 100644 --- a/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp +++ b/Outputs/CRT/Internals/InputTextureBuilder.cpp @@ -1,25 +1,26 @@ // -// CRTInputBufferBuilder.cpp +// InputTextureBuilder.cpp // Clock Signal // // Created by Thomas Harte on 08/03/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#include "CRTInputBufferBuilder.hpp" +#include "InputTextureBuilder.hpp" #include "CRTOpenGL.hpp" #include using namespace Outputs::CRT; -CRTInputBufferBuilder::CRTInputBufferBuilder(size_t bytes_per_pixel) : +InputTextureBuilder::InputTextureBuilder(size_t bytes_per_pixel) : _bytes_per_pixel(bytes_per_pixel), _next_write_x_position(0), - _next_write_y_position(0), - _image(new uint8_t[bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight]) -{} + _next_write_y_position(0) +{ + _image.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight); +} -void CRTInputBufferBuilder::allocate_write_area(size_t required_length) +void InputTextureBuilder::allocate_write_area(size_t required_length) { if(_next_write_y_position != InputBufferBuilderHeight) { @@ -41,16 +42,16 @@ void CRTInputBufferBuilder::allocate_write_area(size_t required_length) } } -bool CRTInputBufferBuilder::is_full() +bool InputTextureBuilder::is_full() { return (_next_write_y_position == InputBufferBuilderHeight); } -void CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length) +void InputTextureBuilder::reduce_previous_allocation_to(size_t actual_length) { if(_next_write_y_position == InputBufferBuilderHeight) return; - uint8_t *const image_pointer = _image.get(); + uint8_t *const image_pointer = _image.data(); // correct if the writing cursor was reset while a client was writing if(_next_write_x_position == 0 && _next_write_y_position == 0) @@ -77,34 +78,34 @@ void CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length) _next_write_x_position -= (_last_allocation_amount - actual_length); } -uint8_t *CRTInputBufferBuilder::get_image_pointer() +uint8_t *InputTextureBuilder::get_image_pointer() { - return _image.get(); + return _image.data(); } -uint16_t CRTInputBufferBuilder::get_and_finalise_current_line() +uint16_t InputTextureBuilder::get_and_finalise_current_line() { uint16_t result = _write_y_position + (_next_write_x_position ? 1 : 0); _next_write_x_position = _next_write_y_position = 0; return result; } -uint8_t *CRTInputBufferBuilder::get_write_target() +uint8_t *InputTextureBuilder::get_write_target() { - return (_next_write_y_position == InputBufferBuilderHeight) ? nullptr : &_image.get()[_write_target_pointer * _bytes_per_pixel]; + return (_next_write_y_position == InputBufferBuilderHeight) ? nullptr : &_image[_write_target_pointer * _bytes_per_pixel]; } -uint16_t CRTInputBufferBuilder::get_last_write_x_position() +uint16_t InputTextureBuilder::get_last_write_x_position() { return _write_x_position; } -uint16_t CRTInputBufferBuilder::get_last_write_y_position() +uint16_t InputTextureBuilder::get_last_write_y_position() { return _write_y_position; } -size_t CRTInputBufferBuilder::get_bytes_per_pixel() +size_t InputTextureBuilder::get_bytes_per_pixel() { return _bytes_per_pixel; } diff --git a/Outputs/CRT/Internals/InputTextureBuilder.hpp b/Outputs/CRT/Internals/InputTextureBuilder.hpp new file mode 100644 index 000000000..a42081b9a --- /dev/null +++ b/Outputs/CRT/Internals/InputTextureBuilder.hpp @@ -0,0 +1,78 @@ +// +// InputTextureBuilder.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/03/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Outputs_CRT_Internals_InputBufferBuilder_hpp +#define Outputs_CRT_Internals_InputBufferBuilder_hpp + +#include +#include +#include + +#include "OpenGL.hpp" +#include "CRTConstants.hpp" + +namespace Outputs { +namespace CRT { + +/*! + Owns an OpenGL texture resource and provides mechanisms to fill it from top left to bottom right + with runs of data, ensuring each run is neighboured immediately to the left and right by copies of its + first and last pixels. +*/ +class InputTextureBuilder { + public: + /// Constructs an instance of InputTextureBuilder that contains a texture of colour depth @c bytes_per_pixel. + InputTextureBuilder(size_t bytes_per_pixel); + + /// Finds the first available space of at least @c required_length pixels in size. Calls must be paired off + /// with calls to @c reduce_previous_allocation_to. + void allocate_write_area(size_t required_length); + + /// Announces that the owner is finished with the region created by the most recent @c allocate_write_area + /// and indicates that its actual final size was @c actual_length. + void reduce_previous_allocation_to(size_t actual_length); + + /// @returns the row that was the final one to receive data; also resets the builder to restart filling of + /// the texture from row 0. + uint16_t get_and_finalise_current_line(); + + /// @returns a pointer to the image data for this texture. + uint8_t *get_image_pointer(); + + uint8_t *get_write_target(); + + uint16_t get_last_write_x_position(); + + uint16_t get_last_write_y_position(); + + size_t get_bytes_per_pixel(); + + bool is_full(); + + private: + // where pixel data will be put to the next time a write is requested + uint16_t _next_write_x_position, _next_write_y_position; + + // the most recent position returned for pixel data writing + uint16_t _write_x_position, _write_y_position; + + // details of the most recent allocation + size_t _write_target_pointer; + size_t _last_allocation_amount; + + // the buffer size + size_t _bytes_per_pixel; + + // the buffer + std::vector _image; +}; + +} +} + +#endif /* CRTInputBufferBuilder_hpp */ From c1a509910dc2499cf6b4aba69f2f5de28cd52b73 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 13:15:50 +0800 Subject: [PATCH 12/40] Documented this interface, albeit that the English could do with a second pass, and very sightly simplified inline with current usage. --- Outputs/CRT/Internals/CRTOpenGL.hpp | 3 +-- Outputs/CRT/Internals/InputTextureBuilder.cpp | 12 +++++------- Outputs/CRT/Internals/InputTextureBuilder.hpp | 10 +++++++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index b7a8b86e7..3a7035e36 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -183,8 +183,7 @@ class OpenGLOutputBuilder { inline uint8_t *allocate_write_area(size_t required_length) { - _texture_builder->allocate_write_area(required_length); - return _texture_builder->get_write_target(); + return _texture_builder->allocate_write_area(required_length); } inline void reduce_previous_allocation_to(size_t actual_length) diff --git a/Outputs/CRT/Internals/InputTextureBuilder.cpp b/Outputs/CRT/Internals/InputTextureBuilder.cpp index 8b3d6103b..2a027ffaa 100644 --- a/Outputs/CRT/Internals/InputTextureBuilder.cpp +++ b/Outputs/CRT/Internals/InputTextureBuilder.cpp @@ -20,7 +20,7 @@ InputTextureBuilder::InputTextureBuilder(size_t bytes_per_pixel) : _image.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight); } -void InputTextureBuilder::allocate_write_area(size_t required_length) +uint8_t *InputTextureBuilder::allocate_write_area(size_t required_length) { if(_next_write_y_position != InputBufferBuilderHeight) { @@ -32,7 +32,7 @@ void InputTextureBuilder::allocate_write_area(size_t required_length) _next_write_y_position++; if(_next_write_y_position == InputBufferBuilderHeight) - return; + return nullptr; } _write_x_position = _next_write_x_position + 1; @@ -40,6 +40,9 @@ void InputTextureBuilder::allocate_write_area(size_t required_length) _write_target_pointer = (_write_y_position * InputBufferBuilderWidth) + _write_x_position; _next_write_x_position += required_length + 2; } + else return nullptr; + + return &_image[_write_target_pointer * _bytes_per_pixel]; } bool InputTextureBuilder::is_full() @@ -90,11 +93,6 @@ uint16_t InputTextureBuilder::get_and_finalise_current_line() return result; } -uint8_t *InputTextureBuilder::get_write_target() -{ - return (_next_write_y_position == InputBufferBuilderHeight) ? nullptr : &_image[_write_target_pointer * _bytes_per_pixel]; -} - uint16_t InputTextureBuilder::get_last_write_x_position() { return _write_x_position; diff --git a/Outputs/CRT/Internals/InputTextureBuilder.hpp b/Outputs/CRT/Internals/InputTextureBuilder.hpp index a42081b9a..5dc930dd5 100644 --- a/Outputs/CRT/Internals/InputTextureBuilder.hpp +++ b/Outputs/CRT/Internals/InputTextureBuilder.hpp @@ -31,7 +31,8 @@ class InputTextureBuilder { /// Finds the first available space of at least @c required_length pixels in size. Calls must be paired off /// with calls to @c reduce_previous_allocation_to. - void allocate_write_area(size_t required_length); + /// @returns a pointer to the allocated space if any was available; @c nullptr otherwise. + uint8_t *allocate_write_area(size_t required_length); /// Announces that the owner is finished with the region created by the most recent @c allocate_write_area /// and indicates that its actual final size was @c actual_length. @@ -44,14 +45,17 @@ class InputTextureBuilder { /// @returns a pointer to the image data for this texture. uint8_t *get_image_pointer(); - uint8_t *get_write_target(); - + /// @returns the start column for the most recent allocated write area. uint16_t get_last_write_x_position(); + /// @returns the row of the most recent allocated write area. uint16_t get_last_write_y_position(); + /// @returns the number of bytes per pixel as supplied to the constructor. size_t get_bytes_per_pixel(); + /// @returns @c true if all future calls to @c allocate_write_area will fail on account of the input texture + /// being full; @c false if calls may succeed. bool is_full(); private: From 04b26886830a561516c0b26cfb3fed266bfc10e8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 13:25:50 +0800 Subject: [PATCH 13/40] Attempted to reduce allocations. --- Outputs/CRT/Internals/CRTOpenGL.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 3a7035e36..ac05966f6 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -96,7 +96,8 @@ class OpenGLOutputBuilder { inline uint8_t *get_next_source_run() { - _line_buffer.data.resize(_line_buffer.pointer + SourceVertexSize); + if(_line_buffer.data.size() < _line_buffer.pointer + SourceVertexSize) + _line_buffer.data.resize(_line_buffer.pointer + SourceVertexSize); return &_line_buffer.data[_line_buffer.pointer]; } @@ -125,7 +126,6 @@ class OpenGLOutputBuilder { _output_buffer.pointer += OutputVertexSize; memcpy(&_source_buffer.data[_source_buffer.pointer], _line_buffer.data.data(), _line_buffer.data.size()); _source_buffer.pointer += _line_buffer.data.size(); - _line_buffer.data.resize(0); _line_buffer.pointer = 0; } } From 5c5e44874f2d510b61e9cbc3c5c5be7bb911b7ea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 22:57:17 +0800 Subject: [PATCH 14/40] Even better: why include the 'Input' prefix when there's only one? --- .../Clock Signal.xcodeproj/project.pbxproj | 12 +++++----- Outputs/CRT/Internals/CRTOpenGL.cpp | 5 ++--- Outputs/CRT/Internals/CRTOpenGL.hpp | 4 ++-- Outputs/CRT/Internals/Shaders/Shader.hpp | 4 ++-- ...tTextureBuilder.cpp => TextureBuilder.cpp} | 22 +++++++++---------- ...tTextureBuilder.hpp => TextureBuilder.hpp} | 10 ++++----- 6 files changed, 28 insertions(+), 29 deletions(-) rename Outputs/CRT/Internals/{InputTextureBuilder.cpp => TextureBuilder.cpp} (81%) rename Outputs/CRT/Internals/{InputTextureBuilder.hpp => TextureBuilder.hpp} (92%) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index fd3a1442f..1aafde12b 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -352,7 +352,7 @@ 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; }; 4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; 4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; }; - 4BBF99141C8FBA6F0075DAFB /* InputTextureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* InputTextureBuilder.cpp */; }; + 4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */; }; 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; }; 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; }; @@ -828,8 +828,8 @@ 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = ""; }; 4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = ""; }; 4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = ""; }; - 4BBF99081C8FBA6F0075DAFB /* InputTextureBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InputTextureBuilder.cpp; sourceTree = ""; }; - 4BBF99091C8FBA6F0075DAFB /* InputTextureBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = InputTextureBuilder.hpp; sourceTree = ""; }; + 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureBuilder.cpp; sourceTree = ""; }; + 4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureBuilder.hpp; sourceTree = ""; }; 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = ""; }; 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTOpenGL.hpp; sourceTree = ""; }; 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = ""; }; @@ -1688,8 +1688,8 @@ isa = PBXGroup; children = ( 4BC3B74C1CD194CC00F86E85 /* Shaders */, - 4BBF99081C8FBA6F0075DAFB /* InputTextureBuilder.cpp */, - 4BBF99091C8FBA6F0075DAFB /* InputTextureBuilder.hpp */, + 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */, + 4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */, 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, @@ -2320,7 +2320,7 @@ 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */, 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, - 4BBF99141C8FBA6F0075DAFB /* InputTextureBuilder.cpp in Sources */, + 4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */, 4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */, diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index ec772821e..683eb8f32 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -78,10 +78,9 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _rgb_shader(nullptr), _last_output_width(0), _last_output_height(0), - _fence(nullptr) + _fence(nullptr), + _texture_builder(new TextureBuilder(buffer_depth)) { - _texture_builder.reset(new InputTextureBuilder(buffer_depth)); - _output_buffer.data.resize(OutputVertexBufferDataSize); _source_buffer.data.resize(SourceVertexBufferDataSize); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index ac05966f6..590e6bf18 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -14,7 +14,7 @@ #include "OpenGL.hpp" #include "TextureTarget.hpp" #include "Shader.hpp" -#include "InputTextureBuilder.hpp" +#include "TextureBuilder.hpp" #include "Shaders/OutputShader.hpp" #include "Shaders/IntermediateShader.hpp" @@ -57,7 +57,7 @@ class OpenGLOutputBuilder { void prepare_source_vertex_array(); // the run and input data buffers - std::unique_ptr _texture_builder; + std::unique_ptr _texture_builder; std::unique_ptr _output_mutex; std::unique_ptr _draw_mutex; diff --git a/Outputs/CRT/Internals/Shaders/Shader.hpp b/Outputs/CRT/Internals/Shaders/Shader.hpp index b28335032..947b47326 100644 --- a/Outputs/CRT/Internals/Shaders/Shader.hpp +++ b/Outputs/CRT/Internals/Shaders/Shader.hpp @@ -12,7 +12,7 @@ #include "OpenGL.hpp" #include #include -#include +#include #include namespace OpenGL { @@ -110,7 +110,7 @@ private: GLuint _shader_program; void flush_functions(); - std::list> _enqueued_functions; + std::vector> _enqueued_functions; std::mutex _function_mutex; protected: diff --git a/Outputs/CRT/Internals/InputTextureBuilder.cpp b/Outputs/CRT/Internals/TextureBuilder.cpp similarity index 81% rename from Outputs/CRT/Internals/InputTextureBuilder.cpp rename to Outputs/CRT/Internals/TextureBuilder.cpp index 2a027ffaa..c55df69d8 100644 --- a/Outputs/CRT/Internals/InputTextureBuilder.cpp +++ b/Outputs/CRT/Internals/TextureBuilder.cpp @@ -1,18 +1,18 @@ // -// InputTextureBuilder.cpp +// TextureBuilder.cpp // Clock Signal // // Created by Thomas Harte on 08/03/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#include "InputTextureBuilder.hpp" +#include "TextureBuilder.hpp" #include "CRTOpenGL.hpp" #include using namespace Outputs::CRT; -InputTextureBuilder::InputTextureBuilder(size_t bytes_per_pixel) : +TextureBuilder::TextureBuilder(size_t bytes_per_pixel) : _bytes_per_pixel(bytes_per_pixel), _next_write_x_position(0), _next_write_y_position(0) @@ -20,7 +20,7 @@ InputTextureBuilder::InputTextureBuilder(size_t bytes_per_pixel) : _image.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight); } -uint8_t *InputTextureBuilder::allocate_write_area(size_t required_length) +uint8_t *TextureBuilder::allocate_write_area(size_t required_length) { if(_next_write_y_position != InputBufferBuilderHeight) { @@ -45,12 +45,12 @@ uint8_t *InputTextureBuilder::allocate_write_area(size_t required_length) return &_image[_write_target_pointer * _bytes_per_pixel]; } -bool InputTextureBuilder::is_full() +bool TextureBuilder::is_full() { return (_next_write_y_position == InputBufferBuilderHeight); } -void InputTextureBuilder::reduce_previous_allocation_to(size_t actual_length) +void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) { if(_next_write_y_position == InputBufferBuilderHeight) return; @@ -81,29 +81,29 @@ void InputTextureBuilder::reduce_previous_allocation_to(size_t actual_length) _next_write_x_position -= (_last_allocation_amount - actual_length); } -uint8_t *InputTextureBuilder::get_image_pointer() +uint8_t *TextureBuilder::get_image_pointer() { return _image.data(); } -uint16_t InputTextureBuilder::get_and_finalise_current_line() +uint16_t TextureBuilder::get_and_finalise_current_line() { uint16_t result = _write_y_position + (_next_write_x_position ? 1 : 0); _next_write_x_position = _next_write_y_position = 0; return result; } -uint16_t InputTextureBuilder::get_last_write_x_position() +uint16_t TextureBuilder::get_last_write_x_position() { return _write_x_position; } -uint16_t InputTextureBuilder::get_last_write_y_position() +uint16_t TextureBuilder::get_last_write_y_position() { return _write_y_position; } -size_t InputTextureBuilder::get_bytes_per_pixel() +size_t TextureBuilder::get_bytes_per_pixel() { return _bytes_per_pixel; } diff --git a/Outputs/CRT/Internals/InputTextureBuilder.hpp b/Outputs/CRT/Internals/TextureBuilder.hpp similarity index 92% rename from Outputs/CRT/Internals/InputTextureBuilder.hpp rename to Outputs/CRT/Internals/TextureBuilder.hpp index 5dc930dd5..26ad25b51 100644 --- a/Outputs/CRT/Internals/InputTextureBuilder.hpp +++ b/Outputs/CRT/Internals/TextureBuilder.hpp @@ -1,13 +1,13 @@ // -// InputTextureBuilder.hpp +// TextureBuilder.hpp // Clock Signal // // Created by Thomas Harte on 08/03/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#ifndef Outputs_CRT_Internals_InputBufferBuilder_hpp -#define Outputs_CRT_Internals_InputBufferBuilder_hpp +#ifndef Outputs_CRT_Internals_TextureBuilder_hpp +#define Outputs_CRT_Internals_TextureBuilder_hpp #include #include @@ -24,10 +24,10 @@ namespace CRT { with runs of data, ensuring each run is neighboured immediately to the left and right by copies of its first and last pixels. */ -class InputTextureBuilder { +class TextureBuilder { public: /// Constructs an instance of InputTextureBuilder that contains a texture of colour depth @c bytes_per_pixel. - InputTextureBuilder(size_t bytes_per_pixel); + TextureBuilder(size_t bytes_per_pixel); /// Finds the first available space of at least @c required_length pixels in size. Calls must be paired off /// with calls to @c reduce_previous_allocation_to. From 6ac20e0066c54c0316e0649a2dc66d5cd5de35a8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 23:13:06 +0800 Subject: [PATCH 15/40] Pushed responsibility for submitting texture contents up to the texture builder, simplifying the interface. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 48 +-------- Outputs/CRT/Internals/CRTOpenGL.hpp | 2 +- Outputs/CRT/Internals/TextureBuilder.cpp | 130 ++++++++++++++--------- Outputs/CRT/Internals/TextureBuilder.hpp | 30 +++--- 4 files changed, 99 insertions(+), 111 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 683eb8f32..cc6fa648f 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -13,30 +13,6 @@ #include "../../../SignalProcessing/FIRFilter.hpp" #include "Shaders/OutputShader.hpp" -static const GLint internalFormatForDepth(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; - } -} - -static const GLenum formatForDepth(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; - } -} - struct Range { GLsizei location, length; }; @@ -78,8 +54,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _rgb_shader(nullptr), _last_output_width(0), _last_output_height(0), - _fence(nullptr), - _texture_builder(new TextureBuilder(buffer_depth)) + _fence(nullptr) { _output_buffer.data.resize(OutputVertexBufferDataSize); _source_buffer.data.resize(SourceVertexBufferDataSize); @@ -94,14 +69,8 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : filteredTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit)); // create the surce texture - glGenTextures(1, &textureName); glActiveTexture(source_data_texture_unit); - glBindTexture(GL_TEXTURE_2D, textureName); - 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); - glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(_texture_builder->get_bytes_per_pixel()), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(_texture_builder->get_bytes_per_pixel()), GL_UNSIGNED_BYTE, nullptr); + _texture_builder.reset(new TextureBuilder(buffer_depth)); // create the output vertex array glGenVertexArrays(1, &output_vertex_array); @@ -122,7 +91,6 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : OpenGLOutputBuilder::~OpenGLOutputBuilder() { - glDeleteTextures(1, &textureName); glDeleteBuffers(1, &output_array_buffer); glDeleteBuffers(1, &source_array_buffer); glDeleteVertexArrays(1, &output_vertex_array); @@ -190,16 +158,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out GLsizei submitted_source_data = submitArrayData(source_array_buffer, _source_buffer.data.data(), &_source_buffer.pointer); // upload new source pixels, if any - uint16_t completed_texture_y = _texture_builder->get_and_finalise_current_line(); - if(completed_texture_y) - { - glActiveTexture(source_data_texture_unit); - glTexSubImage2D( GL_TEXTURE_2D, 0, - 0, 0, - InputBufferBuilderWidth, completed_texture_y, - formatForDepth(_texture_builder->get_bytes_per_pixel()), GL_UNSIGNED_BYTE, - _texture_builder->get_image_pointer()); - } + glActiveTexture(source_data_texture_unit); + _texture_builder->submit(); // buffer usage restart from 0 for the next time around _composite_src_output_y = 0; diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 590e6bf18..6f4a7fa00 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -80,7 +80,7 @@ class OpenGLOutputBuilder { unsigned int _last_output_width, _last_output_height; - GLuint textureName, shadowMaskTextureName; + GLuint shadowMaskTextureName; GLuint defaultFramebuffer; diff --git a/Outputs/CRT/Internals/TextureBuilder.cpp b/Outputs/CRT/Internals/TextureBuilder.cpp index c55df69d8..a728bccc1 100644 --- a/Outputs/CRT/Internals/TextureBuilder.cpp +++ b/Outputs/CRT/Internals/TextureBuilder.cpp @@ -8,102 +8,134 @@ #include "TextureBuilder.hpp" #include "CRTOpenGL.hpp" +#include "OpenGL.hpp" #include using namespace Outputs::CRT; -TextureBuilder::TextureBuilder(size_t bytes_per_pixel) : - _bytes_per_pixel(bytes_per_pixel), - _next_write_x_position(0), - _next_write_y_position(0) +static const GLint internalFormatForDepth(size_t depth) { - _image.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight); + 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; + } +} + +static const GLenum formatForDepth(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; + } +} + +TextureBuilder::TextureBuilder(size_t bytes_per_pixel) : + bytes_per_pixel_(bytes_per_pixel), + next_write_x_position_(0), + next_write_y_position_(0) +{ + image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight); + glGenTextures(1, &texture_name_); + glBindTexture(GL_TEXTURE_2D, texture_name_); + 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); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(bytes_per_pixel), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(bytes_per_pixel), GL_UNSIGNED_BYTE, nullptr); +} + +TextureBuilder::~TextureBuilder() +{ + glDeleteTextures(1, &texture_name_); } uint8_t *TextureBuilder::allocate_write_area(size_t required_length) { - if(_next_write_y_position != InputBufferBuilderHeight) + if(next_write_y_position_ != InputBufferBuilderHeight) { - _last_allocation_amount = required_length; + last_allocation_amount_ = required_length; - if(_next_write_x_position + required_length + 2 > InputBufferBuilderWidth) + if(next_write_x_position_ + required_length + 2 > InputBufferBuilderWidth) { - _next_write_x_position = 0; - _next_write_y_position++; + next_write_x_position_ = 0; + next_write_y_position_++; - if(_next_write_y_position == InputBufferBuilderHeight) + if(next_write_y_position_ == InputBufferBuilderHeight) return nullptr; } - _write_x_position = _next_write_x_position + 1; - _write_y_position = _next_write_y_position; - _write_target_pointer = (_write_y_position * InputBufferBuilderWidth) + _write_x_position; - _next_write_x_position += required_length + 2; + write_x_position_ = next_write_x_position_ + 1; + write_y_position_ = next_write_y_position_; + write_target_pointer_ = (write_y_position_ * InputBufferBuilderWidth) + write_x_position_; + next_write_x_position_ += required_length + 2; } else return nullptr; - return &_image[_write_target_pointer * _bytes_per_pixel]; + return &image_[write_target_pointer_ * bytes_per_pixel_]; } bool TextureBuilder::is_full() { - return (_next_write_y_position == InputBufferBuilderHeight); + return (next_write_y_position_ == InputBufferBuilderHeight); } void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) { - if(_next_write_y_position == InputBufferBuilderHeight) return; + if(next_write_y_position_ == InputBufferBuilderHeight) return; - uint8_t *const image_pointer = _image.data(); + uint8_t *const image_pointer = image_.data(); // correct if the writing cursor was reset while a client was writing - if(_next_write_x_position == 0 && _next_write_y_position == 0) + if(next_write_x_position_ == 0 && next_write_y_position_ == 0) { - memmove(&image_pointer[_bytes_per_pixel], &image_pointer[_write_target_pointer * _bytes_per_pixel], actual_length * _bytes_per_pixel); - _write_target_pointer = 1; - _last_allocation_amount = actual_length; - _next_write_x_position = (uint16_t)(actual_length + 2); - _write_x_position = 1; - _write_y_position = 0; + memmove(&image_pointer[bytes_per_pixel_], &image_pointer[write_target_pointer_ * bytes_per_pixel_], actual_length * bytes_per_pixel_); + write_target_pointer_ = 1; + last_allocation_amount_ = actual_length; + next_write_x_position_ = (uint16_t)(actual_length + 2); + write_x_position_ = 1; + write_y_position_ = 0; } // book end the allocation with duplicates of the first and last pixel, to protect // against rounding errors when this run is drawn - memcpy( &image_pointer[(_write_target_pointer - 1) * _bytes_per_pixel], - &image_pointer[_write_target_pointer * _bytes_per_pixel], - _bytes_per_pixel); + memcpy( &image_pointer[(write_target_pointer_ - 1) * bytes_per_pixel_], + &image_pointer[write_target_pointer_ * bytes_per_pixel_], + bytes_per_pixel_); - memcpy( &image_pointer[(_write_target_pointer + actual_length) * _bytes_per_pixel], - &image_pointer[(_write_target_pointer + actual_length - 1) * _bytes_per_pixel], - _bytes_per_pixel); + memcpy( &image_pointer[(write_target_pointer_ + actual_length) * bytes_per_pixel_], + &image_pointer[(write_target_pointer_ + actual_length - 1) * bytes_per_pixel_], + bytes_per_pixel_); // return any allocated length that wasn't actually used to the available pool - _next_write_x_position -= (_last_allocation_amount - actual_length); -} - -uint8_t *TextureBuilder::get_image_pointer() -{ - return _image.data(); -} - -uint16_t TextureBuilder::get_and_finalise_current_line() -{ - uint16_t result = _write_y_position + (_next_write_x_position ? 1 : 0); - _next_write_x_position = _next_write_y_position = 0; - return result; + next_write_x_position_ -= (last_allocation_amount_ - actual_length); } uint16_t TextureBuilder::get_last_write_x_position() { - return _write_x_position; + return write_x_position_; } uint16_t TextureBuilder::get_last_write_y_position() { - return _write_y_position; + return write_y_position_; } -size_t TextureBuilder::get_bytes_per_pixel() +void TextureBuilder::submit() { - return _bytes_per_pixel; + uint16_t height = write_y_position_ + (next_write_x_position_ ? 1 : 0); + next_write_x_position_ = next_write_y_position_ = 0; + + glTexSubImage2D( GL_TEXTURE_2D, 0, + 0, 0, + InputBufferBuilderWidth, height, + formatForDepth(bytes_per_pixel_), GL_UNSIGNED_BYTE, + image_.data()); } diff --git a/Outputs/CRT/Internals/TextureBuilder.hpp b/Outputs/CRT/Internals/TextureBuilder.hpp index 26ad25b51..d00f648ab 100644 --- a/Outputs/CRT/Internals/TextureBuilder.hpp +++ b/Outputs/CRT/Internals/TextureBuilder.hpp @@ -26,8 +26,10 @@ namespace CRT { */ class TextureBuilder { public: - /// Constructs an instance of InputTextureBuilder that contains a texture of colour depth @c bytes_per_pixel. + /// Constructs an instance of InputTextureBuilder that contains a texture of colour depth @c bytes_per_pixel; + /// this creates a new texture and binds it to the current active texture unit. TextureBuilder(size_t bytes_per_pixel); + virtual ~TextureBuilder(); /// Finds the first available space of at least @c required_length pixels in size. Calls must be paired off /// with calls to @c reduce_previous_allocation_to. @@ -38,42 +40,36 @@ class TextureBuilder { /// and indicates that its actual final size was @c actual_length. void reduce_previous_allocation_to(size_t actual_length); - /// @returns the row that was the final one to receive data; also resets the builder to restart filling of - /// the texture from row 0. - uint16_t get_and_finalise_current_line(); - - /// @returns a pointer to the image data for this texture. - uint8_t *get_image_pointer(); - /// @returns the start column for the most recent allocated write area. uint16_t get_last_write_x_position(); /// @returns the row of the most recent allocated write area. uint16_t get_last_write_y_position(); - /// @returns the number of bytes per pixel as supplied to the constructor. - size_t get_bytes_per_pixel(); - /// @returns @c true if all future calls to @c allocate_write_area will fail on account of the input texture /// being full; @c false if calls may succeed. bool is_full(); + /// Updates the currently-bound texture with all new data provided since the last @c submit. + void submit(); + private: // where pixel data will be put to the next time a write is requested - uint16_t _next_write_x_position, _next_write_y_position; + uint16_t next_write_x_position_, next_write_y_position_; // the most recent position returned for pixel data writing - uint16_t _write_x_position, _write_y_position; + uint16_t write_x_position_, write_y_position_; // details of the most recent allocation - size_t _write_target_pointer; - size_t _last_allocation_amount; + size_t write_target_pointer_; + size_t last_allocation_amount_; // the buffer size - size_t _bytes_per_pixel; + size_t bytes_per_pixel_; // the buffer - std::vector _image; + std::vector image_; + GLuint texture_name_; }; } From edeafd4d949af4eb3591df284e8e8afd040cfed6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 23:14:15 +0800 Subject: [PATCH 16/40] Wait, OpenGL textures go the other way. --- Outputs/CRT/Internals/TextureBuilder.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/TextureBuilder.hpp b/Outputs/CRT/Internals/TextureBuilder.hpp index d00f648ab..3cb633c34 100644 --- a/Outputs/CRT/Internals/TextureBuilder.hpp +++ b/Outputs/CRT/Internals/TextureBuilder.hpp @@ -20,7 +20,7 @@ namespace Outputs { namespace CRT { /*! - Owns an OpenGL texture resource and provides mechanisms to fill it from top left to bottom right + Owns an OpenGL texture resource and provides mechanisms to fill it from bottom left to top right with runs of data, ensuring each run is neighboured immediately to the left and right by copies of its first and last pixels. */ From c04a116a05a8c074ef1b3eecdd7958d96d16849c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Nov 2016 23:14:56 +0800 Subject: [PATCH 17/40] Fixed comment. --- Outputs/CRT/Internals/TextureBuilder.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/TextureBuilder.hpp b/Outputs/CRT/Internals/TextureBuilder.hpp index 3cb633c34..1dbb4b892 100644 --- a/Outputs/CRT/Internals/TextureBuilder.hpp +++ b/Outputs/CRT/Internals/TextureBuilder.hpp @@ -75,4 +75,4 @@ class TextureBuilder { } } -#endif /* CRTInputBufferBuilder_hpp */ +#endif /* Outputs_CRT_Internals_TextureBuilder_hpp */ From 324a1de43dd98d3e76134f5dfe322c2e389f24d9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 17 Nov 2016 09:20:49 +0800 Subject: [PATCH 18/40] Started pulling out array construction as a separate task. --- .../Clock Signal.xcodeproj/project.pbxproj | 24 ++++++---- Outputs/CRT/Internals/ArrayBuilder.cpp | 10 +++++ Outputs/CRT/Internals/ArrayBuilder.hpp | 45 +++++++++++++++++++ 3 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 Outputs/CRT/Internals/ArrayBuilder.cpp create mode 100644 Outputs/CRT/Internals/ArrayBuilder.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 1aafde12b..4e3de67d9 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; }; 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8261D2C2470003C5BF8 /* C1540.cpp */; }; 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; }; + 4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */; }; 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; }; 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; }; @@ -486,6 +487,8 @@ 4B4DC8271D2C2470003C5BF8 /* C1540.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = C1540.hpp; sourceTree = ""; }; 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerialBus.cpp; sourceTree = ""; }; 4B4DC82A1D2C27A4003C5BF8 /* SerialBus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialBus.hpp; sourceTree = ""; }; + 4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ArrayBuilder.cpp; sourceTree = ""; }; + 4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ArrayBuilder.hpp; sourceTree = ""; }; 4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = ""; }; 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = ""; }; 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = ""; }; @@ -1687,16 +1690,18 @@ 4BBF99071C8FBA6F0075DAFB /* Internals */ = { isa = PBXGroup; children = ( - 4BC3B74C1CD194CC00F86E85 /* Shaders */, - 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */, - 4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */, + 4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */, 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, + 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */, + 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */, + 4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */, + 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */, 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, 4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */, - 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */, + 4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */, 4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */, - 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */, + 4BC3B74C1CD194CC00F86E85 /* Shaders */, ); path = Internals; sourceTree = ""; @@ -1704,12 +1709,12 @@ 4BC3B74C1CD194CC00F86E85 /* Shaders */ = { isa = PBXGroup; children = ( - 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */, - 4BC3B74E1CD194CC00F86E85 /* Shader.hpp */, - 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */, - 4BC3B7511CD1956900F86E85 /* OutputShader.hpp */, 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */, + 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */, + 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */, 4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */, + 4BC3B7511CD1956900F86E85 /* OutputShader.hpp */, + 4BC3B74E1CD194CC00F86E85 /* Shader.hpp */, ); path = Shaders; sourceTree = ""; @@ -2353,6 +2358,7 @@ 4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */, 4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */, 4B4C83701D4F623200CD541F /* D64.cpp in Sources */, + 4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */, 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */, 4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */, 4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */, diff --git a/Outputs/CRT/Internals/ArrayBuilder.cpp b/Outputs/CRT/Internals/ArrayBuilder.cpp new file mode 100644 index 000000000..dd7057e63 --- /dev/null +++ b/Outputs/CRT/Internals/ArrayBuilder.cpp @@ -0,0 +1,10 @@ +// +// ArrayBuilder.cpp +// Clock Signal +// +// Created by Thomas Harte on 17/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "ArrayBuilder.hpp" + diff --git a/Outputs/CRT/Internals/ArrayBuilder.hpp b/Outputs/CRT/Internals/ArrayBuilder.hpp new file mode 100644 index 000000000..97a7b2b63 --- /dev/null +++ b/Outputs/CRT/Internals/ArrayBuilder.hpp @@ -0,0 +1,45 @@ +// +// ArrayBuilder.hpp +// Clock Signal +// +// Created by Thomas Harte on 17/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef ArrayBuilder_hpp +#define ArrayBuilder_hpp + +#include +#include + +#include "OpenGL.hpp" + +namespace Outputs { +namespace CRT { + +class ArrayBuilder { + public: + ArrayBuilder(); + virtual ~ArrayBuilder(); + + uint8_t *get_input_storage(size_t size); + uint8_t *get_output_storage(size_t size); + void flush_storage(); + + void bind_input(); + void bind_output(); + void submit(); + + private: + struct { + std::vector data; + size_t allocated_data, completed_data; + GLuint buffer; + } output_, input_; + std::mutex buffer_mutex_; +}; + +} +} + +#endif /* ArrayBuilder_hpp */ From 57f064874215251fafb1e599e8ab85d62595083c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 17 Nov 2016 10:39:30 +0800 Subject: [PATCH 19/40] Fleshed out first implementation of `ArrayBuilder`, albeit that I need to implement exhaustion properly, as soon as I think of a sensible way to handle synchronisation. --- Outputs/CRT/Internals/ArrayBuilder.cpp | 112 +++++++++++++++++++++++++ Outputs/CRT/Internals/ArrayBuilder.hpp | 37 ++++++-- 2 files changed, 143 insertions(+), 6 deletions(-) diff --git a/Outputs/CRT/Internals/ArrayBuilder.cpp b/Outputs/CRT/Internals/ArrayBuilder.cpp index dd7057e63..ddc89a6ed 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.cpp +++ b/Outputs/CRT/Internals/ArrayBuilder.cpp @@ -8,3 +8,115 @@ #include "ArrayBuilder.hpp" +using namespace Outputs::CRT; + +ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) : + output_(output_size), + input_(input_size) +{} + +ArrayBuilder::Buffer::Buffer(size_t size) : + allocated_data(0), completed_data(0) +{ + glGenBuffers(1, &buffer); + glBindBuffer(GL_ARRAY_BUFFER, buffer); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW); + data.resize(size); +} + +ArrayBuilder::Buffer::~Buffer() +{ + glDeleteBuffers(1, &buffer); +} + +uint8_t *ArrayBuilder::get_input_storage(size_t size) +{ + return input_.get_storage(size); +} + +uint8_t *ArrayBuilder::get_output_storage(size_t size) +{ + return output_.get_storage(size); +} + +void ArrayBuilder::flush() +{ + // TODO: don't flush anything if either buffer was exhausted + buffer_mutex_.lock(); + input_.flush(); + output_.flush(); + buffer_mutex_.unlock(); +} + +void ArrayBuilder::bind_input() +{ + input_.bind(); +} + +void ArrayBuilder::bind_output() +{ + output_.bind(); +} + +ArrayBuilder::Submission ArrayBuilder::submit() +{ + ArrayBuilder::Submission submission; + + buffer_mutex_.lock(); + submission.input_size = input_.submit(); + submission.output_size = output_.submit(); + buffer_mutex_.unlock(); + // TODO: if either buffer was exhausted, reset both + + return submission; +} + +uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) +{ + if(allocated_data + size > data.size()) return nullptr; + uint8_t *pointer = &data[allocated_data]; + vended_pointer = allocated_data; + allocated_data += size; + return pointer; +} + +void ArrayBuilder::Buffer::flush() +{ + // Ordinarily this just requires the completed data count to be bumped up + // to the current allocated data value. However if the amount of allocated + // data is now less than the completed data then that implies a submission + // occurred while the pointer previously vended by get_storage was in use. + // So copy whatever is danging back to the start and make amends. + if(completed_data < allocated_data) + completed_data = allocated_data; + else + { + completed_data = allocated_data - vended_pointer; + allocated_data = completed_data; + memcpy(data.data(), &data[vended_pointer], completed_data); + } +} + +size_t ArrayBuilder::Buffer::submit() +{ + size_t length = completed_data; + + glBindBuffer(GL_ARRAY_BUFFER, buffer); + uint8_t *destination = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); + memcpy(destination, data.data(), length); + glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length); + glUnmapBuffer(GL_ARRAY_BUFFER); + + completed_data = 0; + return length; +} + +void ArrayBuilder::Buffer::bind() +{ + glBindBuffer(GL_ARRAY_BUFFER, buffer); +} + +void ArrayBuilder::Buffer::reset() +{ + completed_data = allocated_data = 0; +} diff --git a/Outputs/CRT/Internals/ArrayBuilder.hpp b/Outputs/CRT/Internals/ArrayBuilder.hpp index 97a7b2b63..e318e9dfe 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.hpp +++ b/Outputs/CRT/Internals/ArrayBuilder.hpp @@ -11,32 +11,57 @@ #include #include +#include #include "OpenGL.hpp" namespace Outputs { namespace CRT { +/*! + Owns two array buffers, an 'input' and an 'output' and vends pointers to allow an owner to write provisional data into those + plus a flush function to lock provisional data into place. Also supplies a submit method to transfer all currently locked + data to the GPU and bind_input/output methods to bind the internal buffers. + + It is safe for one thread to communicate via the get_*_storage and flush inputs asynchronously from another that is making + use of the bind and submit outputs. +*/ class ArrayBuilder { public: - ArrayBuilder(); - virtual ~ArrayBuilder(); + /// Creates an instance of ArrayBuilder with @c output_size bytes of storage for the output buffer and + /// @c input_size bytes of storage for the input buffer. + ArrayBuilder(size_t input_size, size_t output_size); + /// Attempts to add @c size bytes uint8_t *get_input_storage(size_t size); uint8_t *get_output_storage(size_t size); - void flush_storage(); + void flush(); void bind_input(); void bind_output(); - void submit(); + + struct Submission { + size_t input_size, output_size; + }; + Submission submit(); private: - struct { + struct Buffer { + Buffer(size_t size); + ~Buffer(); + std::vector data; - size_t allocated_data, completed_data; + size_t allocated_data, completed_data, vended_pointer; GLuint buffer; + + uint8_t *get_storage(size_t size); + void flush(); + size_t submit(); + void bind(); + void reset(); } output_, input_; std::mutex buffer_mutex_; + bool is_exhausted_; }; } From 0f3b02edb7ff65c0de840e438441b92f04aa93b8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 17 Nov 2016 11:00:11 +0800 Subject: [PATCH 20/40] Switched to postfix underscores and gave this class ownership of a texture builder and an array builder, though it presently uses neither. --- Outputs/CRT/CRT.cpp | 176 ++++++++++++++++++++++---------------------- Outputs/CRT/CRT.hpp | 58 +++++++-------- 2 files changed, 117 insertions(+), 117 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 4b453b143..505d7fbc3 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -16,7 +16,7 @@ using namespace Outputs::CRT; void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) { - _openGL_output_builder->set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator); + openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator); const unsigned int syncCapacityLineChargeThreshold = 2; const unsigned int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234 @@ -28,24 +28,21 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di // a TV picture tube or camera tube to the starting point of a line or field. It is about 7 µs // for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV." - _time_multiplier = IntermediateBufferWidth / cycles_per_line; - - // store fundamental display configuration properties - _height_of_display = height_of_display; - _cycles_per_line = cycles_per_line * _time_multiplier; + time_multiplier_ = IntermediateBufferWidth / cycles_per_line; + unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_; // generate timing values implied by the given arbuments - _sync_capacitor_charge_threshold = (int)(syncCapacityLineChargeThreshold * _cycles_per_line); + sync_capacitor_charge_threshold_ = (int)(syncCapacityLineChargeThreshold * multiplied_cycles_per_line); // create the two flywheels - _horizontal_flywheel.reset(new Flywheel(_cycles_per_line, (millisecondsHorizontalRetraceTime * _cycles_per_line) >> 6, _cycles_per_line >> 6)); - _vertical_flywheel.reset(new Flywheel(_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * _cycles_per_line, (_cycles_per_line * height_of_display) >> 3)); + horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 6)); + vertical_flywheel_.reset(new Flywheel(multiplied_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * multiplied_cycles_per_line, (multiplied_cycles_per_line * height_of_display) >> 3)); // figure out the divisor necessary to get the horizontal flywheel into a 16-bit range - unsigned int real_clock_scan_period = (_cycles_per_line * height_of_display) / (_time_multiplier * _common_output_divisor); - _vertical_flywheel_output_divider = (uint16_t)(ceilf(real_clock_scan_period / 65536.0f) * (_time_multiplier * _common_output_divisor)); + unsigned int real_clock_scan_period = (multiplied_cycles_per_line * height_of_display) / (time_multiplier_ * common_output_divisor_); + vertical_flywheel_output_divider_ = (uint16_t)(ceilf(real_clock_scan_period / 65536.0f) * (time_multiplier_ * common_output_divisor_)); - _openGL_output_builder->set_timing(cycles_per_line, _cycles_per_line, _height_of_display, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period(), _vertical_flywheel_output_divider); + openGL_output_builder_.set_timing(cycles_per_line, multiplied_cycles_per_line, height_of_display, horizontal_flywheel_->get_scan_period(), vertical_flywheel_->get_scan_period(), vertical_flywheel_output_divider_); } void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) @@ -62,24 +59,27 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display } } -CRT::CRT(unsigned int common_output_divisor) : - _sync_capacitor_charge_level(0), - _is_receiving_sync(false), - _sync_period(0), - _common_output_divisor(common_output_divisor), - _is_writing_composite_run(false), - _delegate(nullptr), - _frames_since_last_delegate_call(0) {} +CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) : + sync_capacitor_charge_level_(0), + is_receiving_sync_(false), + sync_period_(0), + common_output_divisor_(common_output_divisor), + is_writing_composite_run_(false), + delegate_(nullptr), + frames_since_last_delegate_call_(0), + openGL_output_builder_(buffer_depth), + array_builder_(SourceVertexBufferDataSize, OutputVertexBufferDataSize), + texture_builder_(buffer_depth) {} -CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int buffer_depth) : CRT(common_output_divisor) +CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int buffer_depth) : + CRT(common_output_divisor, buffer_depth) { - _openGL_output_builder.reset(new OpenGLOutputBuilder(buffer_depth)); set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator); } -CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) : CRT(common_output_divisor) +CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) : + CRT(common_output_divisor, buffer_depth) { - _openGL_output_builder.reset(new OpenGLOutputBuilder(buffer_depth)); set_new_display_type(cycles_per_line, displayType); } @@ -87,12 +87,12 @@ CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, Displ Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) { - return _vertical_flywheel->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced); + return vertical_flywheel_->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced); } Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) { - return _horizontal_flywheel->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced); + return horizontal_flywheel_->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced); } #define output_x1() (*(uint16_t *)&next_run[OutputVertexOffsetOfHorizontal + 0]) @@ -112,7 +112,7 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type, uint16_t tex_x, uint16_t tex_y) { - number_of_cycles *= _time_multiplier; + number_of_cycles *= time_multiplier_; bool is_output_run = ((type == Scan::Type::Level) || (type == Scan::Type::Data)); @@ -129,23 +129,23 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi hsync_requested = false; vsync_requested = false; - bool is_output_segment = ((is_output_run && next_run_length) && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()); + bool is_output_segment = ((is_output_run && next_run_length) && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace()); uint8_t *next_run = nullptr; - if(is_output_segment && !_openGL_output_builder->composite_output_buffer_is_full()) + if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full()) { - next_run = _openGL_output_builder->get_next_source_run(); + next_run = openGL_output_builder_.get_next_source_run(); } if(next_run) { source_input_position_x1() = tex_x; source_input_position_y() = tex_y; - source_output_position_x1() = (uint16_t)_horizontal_flywheel->get_current_output_position(); + source_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position(); // Don't write output_y now, write it later; we won't necessarily know what it is outside of the locked region -// source_output_position_y() = _openGL_output_builder->get_composite_output_y(); - source_phase() = _colour_burst_phase; - source_amplitude() = _colour_burst_amplitude; - source_phase_time() = (uint8_t)_colour_burst_time; // assumption: burst was within the first 1/16 of the line +// source_output_position_y() = openGL_output_builder_->get_composite_output_y(); + source_phase() = colour_burst_phase_; + source_amplitude() = colour_burst_amplitude_; + source_phase_time() = (uint8_t)colour_burst_time_; // assumption: burst was within the first 1/16 of the line } // decrement the number of cycles left to run for and increment the @@ -154,23 +154,23 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // either charge or deplete the vertical retrace capacitor (making sure it stops at 0) if(vsync_charging) - _sync_capacitor_charge_level += next_run_length; + sync_capacitor_charge_level_ += next_run_length; else - _sync_capacitor_charge_level = std::max(_sync_capacitor_charge_level - (int)next_run_length, 0); + sync_capacitor_charge_level_ = std::max(sync_capacitor_charge_level_ - (int)next_run_length, 0); // react to the incoming event... - _horizontal_flywheel->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None); - _vertical_flywheel->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None); + horizontal_flywheel_->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None); + vertical_flywheel_->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None); if(next_run) { // if this is a data run then advance the buffer pointer - if(type == Scan::Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider); + if(type == Scan::Type::Data && source_divider) tex_x += next_run_length / (time_multiplier_ * source_divider); source_input_position_x2() = tex_x; - source_output_position_x2() = (uint16_t)_horizontal_flywheel->get_current_output_position(); + source_output_position_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position(); - _openGL_output_builder->complete_source_run(); + openGL_output_builder_.complete_source_run(); } // if this is horizontal retrace then advance the output line counter and bookend an output run @@ -178,62 +178,62 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event != Flywheel::SyncEvent::None) honoured_event = next_vertical_sync_event; if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) honoured_event = next_horizontal_sync_event; bool needs_endpoint = - (honoured_event == Flywheel::SyncEvent::StartRetrace && _is_writing_composite_run) || - (honoured_event == Flywheel::SyncEvent::EndRetrace && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()); + (honoured_event == Flywheel::SyncEvent::StartRetrace && is_writing_composite_run_) || + (honoured_event == Flywheel::SyncEvent::EndRetrace && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace()); if(needs_endpoint) { if( - _openGL_output_builder->composite_output_run_has_room_for_vertex() && - !_openGL_output_builder->composite_output_buffer_is_full()) + openGL_output_builder_.composite_output_run_has_room_for_vertex() && + !openGL_output_builder_.composite_output_buffer_is_full()) { - if(!_is_writing_composite_run) + if(!is_writing_composite_run_) { - _output_run.x1 = (uint16_t)_horizontal_flywheel->get_current_output_position(); - _output_run.y = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); + output_run_.x1 = (uint16_t)horizontal_flywheel_->get_current_output_position(); + output_run_.y = (uint16_t)(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_); } else { - _openGL_output_builder->lock_output(); + openGL_output_builder_.lock_output(); // Get and write all those previously unwritten output ys - uint16_t output_y = _openGL_output_builder->get_composite_output_y(); + uint16_t output_y = openGL_output_builder_.get_composite_output_y(); size_t size; - uint8_t *buffered_lines = _openGL_output_builder->get_buffered_source_runs(size); + uint8_t *buffered_lines = openGL_output_builder_.get_buffered_source_runs(size); for(size_t position = 0; position < size; position += SourceVertexSize) { (*(uint16_t *)&buffered_lines[position + SourceVertexOffsetOfOutputStart + 2]) = output_y; } // Construct the output run - uint8_t *next_run = _openGL_output_builder->get_next_output_run(); - output_x1() = _output_run.x1; - output_position_y() = _output_run.y; + uint8_t *next_run = openGL_output_builder_.get_next_output_run(); + output_x1() = output_run_.x1; + output_position_y() = output_run_.y; output_tex_y() = output_y; - output_x2() = (uint16_t)_horizontal_flywheel->get_current_output_position(); - _openGL_output_builder->complete_output_run(); + output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position(); + openGL_output_builder_.complete_output_run(); - _openGL_output_builder->unlock_output(); + openGL_output_builder_.unlock_output(); } - _is_writing_composite_run ^= true; + is_writing_composite_run_ ^= true; } } if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) { - _openGL_output_builder->increment_composite_output_y(); + openGL_output_builder_.increment_composite_output_y(); } // if this is vertical retrace then adcance a field if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace) { - if(_delegate) + if(delegate_) { - _frames_since_last_delegate_call++; - if(_frames_since_last_delegate_call == 20) + frames_since_last_delegate_call_++; + if(frames_since_last_delegate_call_ == 20) { - _delegate->crt_did_end_batch_of_frames(this, _frames_since_last_delegate_call, _vertical_flywheel->get_and_reset_number_of_surprises()); - _frames_since_last_delegate_call = 0; + delegate_->crt_did_end_batch_of_frames(this, frames_since_last_delegate_call_, vertical_flywheel_->get_and_reset_number_of_surprises()); + frames_since_last_delegate_call_ = 0; } } } @@ -260,31 +260,31 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi void CRT::output_scan(const Scan *const scan) { const bool this_is_sync = (scan->type == Scan::Type::Sync); - const bool is_trailing_edge = (_is_receiving_sync && !this_is_sync); - const bool is_leading_edge = (!_is_receiving_sync && this_is_sync); - _is_receiving_sync = this_is_sync; + const bool is_trailing_edge = (is_receiving_sync_ && !this_is_sync); + const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync); + is_receiving_sync_ = this_is_sync; // This introduces a blackout period close to the expected vertical sync point in which horizontal syncs are not // recognised, effectively causing the horizontal flywheel to freewheel during that period. This attempts to seek // the problem that vertical sync otherwise often starts halfway through a scanline, which confuses the horizontal // flywheel. I'm currently unclear whether this is an accurate solution to this problem. - const bool hsync_requested = is_leading_edge && !_vertical_flywheel->is_near_expected_sync(); - const bool vsync_requested = is_trailing_edge && (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold); + const bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync(); + const bool vsync_requested = is_trailing_edge && (sync_capacitor_charge_level_ >= sync_capacitor_charge_threshold_); // simplified colour burst logic: if it's within the back porch we'll take it if(scan->type == Scan::Type::ColourBurst) { - if(_horizontal_flywheel->get_current_time() < (_horizontal_flywheel->get_standard_period() * 12) >> 6) + if(horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) { - _colour_burst_time = (uint16_t)_horizontal_flywheel->get_current_time(); - _colour_burst_phase = scan->phase; - _colour_burst_amplitude = scan->amplitude; + colour_burst_time_ = (uint16_t)horizontal_flywheel_->get_current_time(); + colour_burst_phase_ = scan->phase; + colour_burst_amplitude_ = scan->amplitude; } } // TODO: inspect raw data for potential colour burst if required - _sync_period = _is_receiving_sync ? (_sync_period + scan->number_of_cycles) : 0; + sync_period_ = is_receiving_sync_ ? (sync_period_ + scan->number_of_cycles) : 0; advance_cycles(scan->number_of_cycles, scan->source_divider, hsync_requested, vsync_requested, this_is_sync, scan->type, scan->tex_x, scan->tex_y); } @@ -311,13 +311,13 @@ void CRT::output_blank(unsigned int number_of_cycles) void CRT::output_level(unsigned int number_of_cycles) { - if(!_openGL_output_builder->input_buffer_is_full()) + if(!openGL_output_builder_.input_buffer_is_full()) { Scan scan { .type = Scan::Type::Level, .number_of_cycles = number_of_cycles, - .tex_x = _openGL_output_builder->get_last_write_x_posititon(), - .tex_y = _openGL_output_builder->get_last_write_y_posititon() + .tex_x = openGL_output_builder_.get_last_write_x_posititon(), + .tex_y = openGL_output_builder_.get_last_write_y_posititon() }; output_scan(&scan); } @@ -344,14 +344,14 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { - if(!_openGL_output_builder->input_buffer_is_full()) + if(!openGL_output_builder_.input_buffer_is_full()) { - _openGL_output_builder->reduce_previous_allocation_to(number_of_cycles / source_divider); + openGL_output_builder_.reduce_previous_allocation_to(number_of_cycles / source_divider); Scan scan { .type = Scan::Type::Data, .number_of_cycles = number_of_cycles, - .tex_x = _openGL_output_builder->get_last_write_x_posititon(), - .tex_y = _openGL_output_builder->get_last_write_y_posititon(), + .tex_x = openGL_output_builder_.get_last_write_x_posititon(), + .tex_y = openGL_output_builder_.get_last_write_y_posititon(), .source_divider = source_divider }; output_scan(&scan); @@ -368,15 +368,15 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio) { - first_cycle_after_sync *= _time_multiplier; - number_of_cycles *= _time_multiplier; + first_cycle_after_sync *= time_multiplier_; + number_of_cycles *= time_multiplier_; first_line_after_sync -= 2; number_of_lines += 4; // determine prima facie x extent - unsigned int horizontal_period = _horizontal_flywheel->get_standard_period(); - unsigned int horizontal_scan_period = _horizontal_flywheel->get_scan_period(); + unsigned int horizontal_period = horizontal_flywheel_->get_standard_period(); + unsigned int horizontal_scan_period = horizontal_flywheel_->get_scan_period(); unsigned int horizontal_retrace_period = horizontal_period - horizontal_scan_period; // make sure that the requested range is visible @@ -387,8 +387,8 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_ float width = (float)number_of_cycles / (float)horizontal_scan_period; // determine prima facie y extent - unsigned int vertical_period = _vertical_flywheel->get_standard_period(); - unsigned int vertical_scan_period = _vertical_flywheel->get_scan_period(); + unsigned int vertical_period = vertical_flywheel_->get_standard_period(); + unsigned int vertical_scan_period = vertical_flywheel_->get_scan_period(); unsigned int vertical_retrace_period = vertical_period - vertical_scan_period; // make sure that the requested range is visible diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 78bc4ec1a..46fc6e7fc 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -14,6 +14,8 @@ #include "CRTTypes.hpp" #include "Internals/Flywheel.hpp" #include "Internals/CRTOpenGL.hpp" +#include "Internals/ArrayBuilder.hpp" +#include "Internals/TextureBuilder.hpp" namespace Outputs { namespace CRT { @@ -27,26 +29,22 @@ class Delegate { class CRT { private: - CRT(unsigned int common_output_divisor); + CRT(unsigned int common_output_divisor, unsigned int buffer_depth); // the incoming clock lengths will be multiplied by something to give at least 1000 // sample points per line - unsigned int _time_multiplier; - const unsigned int _common_output_divisor; - - // fundamental creator-specified properties - unsigned int _cycles_per_line; - unsigned int _height_of_display; + unsigned int time_multiplier_; + const unsigned int common_output_divisor_; // the two flywheels regulating scanning - std::unique_ptr _horizontal_flywheel, _vertical_flywheel; - uint16_t _vertical_flywheel_output_divider; + std::unique_ptr horizontal_flywheel_, vertical_flywheel_; + uint16_t vertical_flywheel_output_divider_; // elements of sync separation - bool _is_receiving_sync; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync) - int _sync_capacitor_charge_level; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync - int _sync_capacitor_charge_threshold; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync - unsigned int _sync_period; + bool is_receiving_sync_; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync) + int sync_capacitor_charge_level_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync + int sync_capacitor_charge_threshold_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync + unsigned int sync_period_; // each call to output_* generates a scan. A two-slot queue for scans allows edge extensions. struct Scan { @@ -66,9 +64,9 @@ class CRT { }; void output_scan(const Scan *scan); - uint8_t _colour_burst_phase, _colour_burst_amplitude; - uint16_t _colour_burst_time; - bool _is_writing_composite_run; + uint8_t colour_burst_phase_, colour_burst_amplitude_; + uint16_t colour_burst_time_; + bool is_writing_composite_run_; // the outer entry point for dispatching output_sync, output_blank, output_level and output_data void advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type, uint16_t tex_x, uint16_t tex_y); @@ -78,17 +76,19 @@ class CRT { Flywheel::SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced); Flywheel::SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced); - // OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here. - std::unique_ptr _openGL_output_builder; + // OpenGL state + OpenGLOutputBuilder openGL_output_builder_; + ArrayBuilder array_builder_; + TextureBuilder texture_builder_; // temporary storage used during the construction of output runs struct { uint16_t x1, y; - } _output_run; + } output_run_; // The delegate - Delegate *_delegate; - unsigned int _frames_since_last_delegate_call; + Delegate *delegate_; + unsigned int frames_since_last_delegate_call_; public: /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. @@ -194,7 +194,7 @@ class CRT { */ inline uint8_t *allocate_write_area(size_t required_length) { - return _openGL_output_builder->allocate_write_area(required_length); + return openGL_output_builder_.allocate_write_area(required_length); } /*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state. @@ -202,7 +202,7 @@ class CRT { */ inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { - _openGL_output_builder->draw_frame(output_width, output_height, only_if_dirty); + openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty); } /*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than @@ -214,7 +214,7 @@ class CRT { */ inline void set_openGL_context_will_change(bool should_delete_resources) { - _openGL_output_builder->set_openGL_context_will_change(should_delete_resources); + openGL_output_builder_.set_openGL_context_will_change(should_delete_resources); } /*! Sets a function that will map from whatever data the machine provided to a composite signal. @@ -226,7 +226,7 @@ class CRT { */ inline void set_composite_sampling_function(const char *shader) { - _openGL_output_builder->set_composite_sampling_function(shader); + openGL_output_builder_.set_composite_sampling_function(shader); } /*! Sets a function that will map from whatever data the machine provided to an RGB signal. @@ -244,24 +244,24 @@ class CRT { */ inline void set_rgb_sampling_function(const char *shader) { - _openGL_output_builder->set_rgb_sampling_function(shader); + openGL_output_builder_.set_rgb_sampling_function(shader); } inline void set_output_device(OutputDevice output_device) { - _openGL_output_builder->set_output_device(output_device); + openGL_output_builder_.set_output_device(output_device); } inline void set_visible_area(Rect visible_area) { - _openGL_output_builder->set_visible_area(visible_area); + openGL_output_builder_.set_visible_area(visible_area); } Rect get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio); inline void set_delegate(Delegate *delegate) { - _delegate = delegate; + delegate_ = delegate; } }; From 1f91d29434fce042a94e65726b12963e2995426c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 17 Nov 2016 12:26:04 +0800 Subject: [PATCH 21/40] Progressed to usage of an array builder and a texture builder up to the point of a successful build. --- Outputs/CRT/CRT.cpp | 30 +++--- Outputs/CRT/CRT.hpp | 4 +- Outputs/CRT/Internals/ArrayBuilder.cpp | 115 ++++++++++++++++------- Outputs/CRT/Internals/ArrayBuilder.hpp | 16 +++- Outputs/CRT/Internals/CRTOpenGL.cpp | 63 +++---------- Outputs/CRT/Internals/CRTOpenGL.hpp | 84 ++--------------- Outputs/CRT/Internals/TextureBuilder.cpp | 4 +- Outputs/CRT/Internals/TextureBuilder.hpp | 2 +- 8 files changed, 134 insertions(+), 184 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 505d7fbc3..61616e70a 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -67,9 +67,7 @@ CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) : is_writing_composite_run_(false), delegate_(nullptr), frames_since_last_delegate_call_(0), - openGL_output_builder_(buffer_depth), - array_builder_(SourceVertexBufferDataSize, OutputVertexBufferDataSize), - texture_builder_(buffer_depth) {} + openGL_output_builder_(buffer_depth) {} CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int buffer_depth) : CRT(common_output_divisor, buffer_depth) @@ -133,7 +131,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi uint8_t *next_run = nullptr; if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full()) { - next_run = openGL_output_builder_.get_next_source_run(); + next_run = openGL_output_builder_.array_builder.get_input_storage(SourceVertexSize); } if(next_run) @@ -169,8 +167,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi source_input_position_x2() = tex_x; source_output_position_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position(); - - openGL_output_builder_.complete_source_run(); } // if this is horizontal retrace then advance the output line counter and bookend an output run @@ -184,7 +180,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(needs_endpoint) { if( - openGL_output_builder_.composite_output_run_has_room_for_vertex() && + !openGL_output_builder_.array_builder.is_full() && !openGL_output_builder_.composite_output_buffer_is_full()) { if(!is_writing_composite_run_) @@ -199,19 +195,19 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // Get and write all those previously unwritten output ys uint16_t output_y = openGL_output_builder_.get_composite_output_y(); size_t size; - uint8_t *buffered_lines = openGL_output_builder_.get_buffered_source_runs(size); + uint8_t *buffered_lines = openGL_output_builder_.array_builder.reget_input_storage(size); for(size_t position = 0; position < size; position += SourceVertexSize) { (*(uint16_t *)&buffered_lines[position + SourceVertexOffsetOfOutputStart + 2]) = output_y; } // Construct the output run - uint8_t *next_run = openGL_output_builder_.get_next_output_run(); + uint8_t *next_run = openGL_output_builder_.array_builder.get_output_storage(OutputVertexSize); output_x1() = output_run_.x1; output_position_y() = output_run_.y; output_tex_y() = output_y; output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position(); - openGL_output_builder_.complete_output_run(); + openGL_output_builder_.array_builder.flush(); openGL_output_builder_.unlock_output(); } @@ -311,13 +307,13 @@ void CRT::output_blank(unsigned int number_of_cycles) void CRT::output_level(unsigned int number_of_cycles) { - if(!openGL_output_builder_.input_buffer_is_full()) + if(!openGL_output_builder_.array_builder.is_full()) { Scan scan { .type = Scan::Type::Level, .number_of_cycles = number_of_cycles, - .tex_x = openGL_output_builder_.get_last_write_x_posititon(), - .tex_y = openGL_output_builder_.get_last_write_y_posititon() + .tex_x = openGL_output_builder_.texture_builder.get_last_write_x_position(), + .tex_y = openGL_output_builder_.texture_builder.get_last_write_y_position() }; output_scan(&scan); } @@ -344,14 +340,14 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { - if(!openGL_output_builder_.input_buffer_is_full()) + if(!openGL_output_builder_.array_builder.is_full()) { - openGL_output_builder_.reduce_previous_allocation_to(number_of_cycles / source_divider); + openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_cycles / source_divider); Scan scan { .type = Scan::Type::Data, .number_of_cycles = number_of_cycles, - .tex_x = openGL_output_builder_.get_last_write_x_posititon(), - .tex_y = openGL_output_builder_.get_last_write_y_posititon(), + .tex_x = openGL_output_builder_.texture_builder.get_last_write_x_position(), + .tex_y = openGL_output_builder_.texture_builder.get_last_write_y_position(), .source_divider = source_divider }; output_scan(&scan); diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 46fc6e7fc..4baa2a84f 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -78,8 +78,6 @@ class CRT { // OpenGL state OpenGLOutputBuilder openGL_output_builder_; - ArrayBuilder array_builder_; - TextureBuilder texture_builder_; // temporary storage used during the construction of output runs struct { @@ -194,7 +192,7 @@ class CRT { */ inline uint8_t *allocate_write_area(size_t required_length) { - return openGL_output_builder_.allocate_write_area(required_length); + return openGL_output_builder_.texture_builder.allocate_write_area(required_length); } /*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state. diff --git a/Outputs/CRT/Internals/ArrayBuilder.cpp b/Outputs/CRT/Internals/ArrayBuilder.cpp index ddc89a6ed..43eff9698 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.cpp +++ b/Outputs/CRT/Internals/ArrayBuilder.cpp @@ -15,36 +15,43 @@ ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) : input_(input_size) {} -ArrayBuilder::Buffer::Buffer(size_t size) : - allocated_data(0), completed_data(0) +bool ArrayBuilder::is_full() { - glGenBuffers(1, &buffer); - glBindBuffer(GL_ARRAY_BUFFER, buffer); - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW); - data.resize(size); -} - -ArrayBuilder::Buffer::~Buffer() -{ - glDeleteBuffers(1, &buffer); + bool was_full; + buffer_mutex_.lock(); + was_full = is_full_; + buffer_mutex_.unlock(); + return was_full; } uint8_t *ArrayBuilder::get_input_storage(size_t size) { - return input_.get_storage(size); + return get_storage(size, input_); +} + +uint8_t *ArrayBuilder::reget_input_storage(size_t &size) +{ + return input_.reget_storage(size); } uint8_t *ArrayBuilder::get_output_storage(size_t size) { - return output_.get_storage(size); + return get_storage(size, output_); +} + +uint8_t *ArrayBuilder::reget_output_storage(size_t &size) +{ + return output_.reget_storage(size); } void ArrayBuilder::flush() { - // TODO: don't flush anything if either buffer was exhausted buffer_mutex_.lock(); - input_.flush(); - output_.flush(); + if(!is_full_) + { + input_.flush(); + output_.flush(); + } buffer_mutex_.unlock(); } @@ -65,41 +72,82 @@ ArrayBuilder::Submission ArrayBuilder::submit() buffer_mutex_.lock(); submission.input_size = input_.submit(); submission.output_size = output_.submit(); + if(is_full_) + { + is_full_ = false; + input_.reset(); + output_.reset(); + } buffer_mutex_.unlock(); - // TODO: if either buffer was exhausted, reset both return submission; } +ArrayBuilder::Buffer::Buffer(size_t size) : + allocated_data(0), flushed_data(0), submitted_data(0), is_full(false) +{ + glGenBuffers(1, &buffer); + glBindBuffer(GL_ARRAY_BUFFER, buffer); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW); + data.resize(size); +} + +ArrayBuilder::Buffer::~Buffer() +{ + glDeleteBuffers(1, &buffer); +} + +uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer) +{ + buffer_mutex_.lock(); + uint8_t *pointer = buffer.get_storage(size); + if(!pointer) is_full_ = true; + buffer_mutex_.unlock(); + return pointer; +} + uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) { - if(allocated_data + size > data.size()) return nullptr; + if(is_full || allocated_data + size > data.size()) + { + is_full = true; + return nullptr; + } uint8_t *pointer = &data[allocated_data]; - vended_pointer = allocated_data; allocated_data += size; return pointer; } +uint8_t *ArrayBuilder::Buffer::reget_storage(size_t &size) +{ + if(is_full) + { + return nullptr; + } + size = allocated_data - flushed_data; + return &data[flushed_data]; +} + void ArrayBuilder::Buffer::flush() { - // Ordinarily this just requires the completed data count to be bumped up - // to the current allocated data value. However if the amount of allocated - // data is now less than the completed data then that implies a submission - // occurred while the pointer previously vended by get_storage was in use. - // So copy whatever is danging back to the start and make amends. - if(completed_data < allocated_data) - completed_data = allocated_data; + if(submitted_data && !is_full && allocated_data > submitted_data) + { + memcpy(data.data(), &data[flushed_data], allocated_data - flushed_data); + allocated_data -= flushed_data; + flushed_data = allocated_data; + submitted_data = 0; + } else { - completed_data = allocated_data - vended_pointer; - allocated_data = completed_data; - memcpy(data.data(), &data[vended_pointer], completed_data); + allocated_data = 0; + flushed_data = 0; + submitted_data = 0; } } size_t ArrayBuilder::Buffer::submit() { - size_t length = completed_data; + size_t length = flushed_data; glBindBuffer(GL_ARRAY_BUFFER, buffer); uint8_t *destination = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); @@ -107,7 +155,7 @@ size_t ArrayBuilder::Buffer::submit() glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length); glUnmapBuffer(GL_ARRAY_BUFFER); - completed_data = 0; + submitted_data = flushed_data; return length; } @@ -118,5 +166,8 @@ void ArrayBuilder::Buffer::bind() void ArrayBuilder::Buffer::reset() { - completed_data = allocated_data = 0; + allocated_data = 0; + flushed_data = 0; + submitted_data = 0; + is_full = false; } diff --git a/Outputs/CRT/Internals/ArrayBuilder.hpp b/Outputs/CRT/Internals/ArrayBuilder.hpp index e318e9dfe..2f00a64bb 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.hpp +++ b/Outputs/CRT/Internals/ArrayBuilder.hpp @@ -34,7 +34,12 @@ class ArrayBuilder { /// Attempts to add @c size bytes uint8_t *get_input_storage(size_t size); + uint8_t *reget_input_storage(size_t &size); + uint8_t *get_output_storage(size_t size); + uint8_t *reget_output_storage(size_t &size); + + bool is_full(); void flush(); void bind_input(); @@ -51,17 +56,24 @@ class ArrayBuilder { ~Buffer(); std::vector data; - size_t allocated_data, completed_data, vended_pointer; + size_t allocated_data; + size_t flushed_data; + size_t submitted_data; + bool is_full; GLuint buffer; uint8_t *get_storage(size_t size); + uint8_t *reget_storage(size_t &size); + void flush(); size_t submit(); void bind(); void reset(); } output_, input_; + uint8_t *get_storage(size_t size, Buffer &buffer); + std::mutex buffer_mutex_; - bool is_exhausted_; + bool is_full_; }; } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index cc6fa648f..8825fba6b 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -13,27 +13,6 @@ #include "../../../SignalProcessing/FIRFilter.hpp" #include "Shaders/OutputShader.hpp" -struct Range { - GLsizei location, length; -}; - -static GLsizei submitArrayData(GLuint buffer, uint8_t *source, size_t *length_pointer) -{ - GLsizei length = (GLsizei)*length_pointer; - - // The development machine is a Mac; Apple seemingly having given up on OpenGL (?), GL_MAP_PERSISTENT_BIT is not - // available. Which possibly means I'm doing no better here than a traditional buffer submit, but there it is. - glBindBuffer(GL_ARRAY_BUFFER, buffer); - uint8_t *data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); - memcpy(data, source, (size_t)length); - glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, length); - glUnmapBuffer(GL_ARRAY_BUFFER); - - *length_pointer = 0; - - return length; -} - using namespace Outputs::CRT; namespace { @@ -45,7 +24,7 @@ namespace { static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE5; } -OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : +OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) : _output_mutex(new std::mutex), _draw_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), @@ -54,11 +33,10 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _rgb_shader(nullptr), _last_output_width(0), _last_output_height(0), - _fence(nullptr) + _fence(nullptr), + texture_builder(bytes_per_pixel, source_data_texture_unit), + array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize) { - _output_buffer.data.resize(OutputVertexBufferDataSize); - _source_buffer.data.resize(SourceVertexBufferDataSize); - glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); glBlendColor(0.6f, 0.6f, 0.6f, 1.0f); @@ -68,31 +46,15 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : filteredYTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit)); filteredTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit)); - // create the surce texture - glActiveTexture(source_data_texture_unit); - _texture_builder.reset(new TextureBuilder(buffer_depth)); - // create the output vertex array glGenVertexArrays(1, &output_vertex_array); - // create a buffer for output vertex attributes - glGenBuffers(1, &output_array_buffer); - glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); - glBufferData(GL_ARRAY_BUFFER, OutputVertexBufferDataSize, NULL, GL_STREAM_DRAW); - // create the source vertex array glGenVertexArrays(1, &source_vertex_array); - - // create a buffer for source vertex attributes - glGenBuffers(1, &source_array_buffer); - glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); - glBufferData(GL_ARRAY_BUFFER, SourceVertexBufferDataSize, NULL, GL_STREAM_DRAW); } OpenGLOutputBuilder::~OpenGLOutputBuilder() { - glDeleteBuffers(1, &output_array_buffer); - glDeleteBuffers(1, &source_array_buffer); glDeleteVertexArrays(1, &output_vertex_array); free(_composite_shader); @@ -152,14 +114,11 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _output_mutex->lock(); // release the mapping, giving up on trying to draw if data has been lost - GLsizei submitted_output_data = submitArrayData(output_array_buffer, _output_buffer.data.data(), &_output_buffer.pointer); - - // bind and flush the source array buffer - GLsizei submitted_source_data = submitArrayData(source_array_buffer, _source_buffer.data.data(), &_source_buffer.pointer); + ArrayBuilder::Submission array_submission = array_builder.submit(); // upload new source pixels, if any glActiveTexture(source_data_texture_unit); - _texture_builder->submit(); + texture_builder.submit(); // buffer usage restart from 0 for the next time around _composite_src_output_y = 0; @@ -192,7 +151,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out RenderStage *active_pipeline = (_output_device == Television || !rgb_input_shader_program) ? composite_render_stages : rgb_render_stages; // for television, update intermediate buffers and then draw; for a monitor, just draw - if(submitted_source_data) + if(array_submission.input_size || array_submission.output_size) { // all drawing will be from the source vertex array and without blending glBindVertexArray(source_vertex_array); @@ -206,7 +165,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out active_pipeline->shader->bind(); // draw as desired - glDrawArraysInstanced(GL_LINES, 0, 2, submitted_source_data / SourceVertexSize); + glDrawArraysInstanced(GL_LINES, 0, 2, (GLsizei)array_submission.input_size / SourceVertexSize); active_pipeline++; } @@ -229,7 +188,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out output_shader_program->bind(); // draw - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, submitted_output_data / OutputVertexSize); + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize); } // copy framebuffer to the intended place @@ -325,7 +284,7 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() if(composite_input_shader_program) { glBindVertexArray(source_vertex_array); - glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); + array_builder.bind_input(); composite_input_shader_program->enable_vertex_attribute_with_pointer("inputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfInputStart, 1); composite_input_shader_program->enable_vertex_attribute_with_pointer("outputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfOutputStart, 1); @@ -345,7 +304,7 @@ void OpenGLOutputBuilder::prepare_output_vertex_array() if(output_shader_program) { glBindVertexArray(output_vertex_array); - glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); + array_builder.bind_output(); output_shader_program->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1); output_shader_program->enable_vertex_attribute_with_pointer("vertical", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfVertical, 1); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 6f4a7fa00..a97d5b055 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -14,6 +14,8 @@ #include "OpenGL.hpp" #include "TextureTarget.hpp" #include "Shader.hpp" + +#include "ArrayBuilder.hpp" #include "TextureBuilder.hpp" #include "Shaders/OutputShader.hpp" @@ -57,7 +59,6 @@ class OpenGLOutputBuilder { void prepare_source_vertex_array(); // the run and input data buffers - std::unique_ptr _texture_builder; std::unique_ptr _output_mutex; std::unique_ptr _draw_mutex; @@ -75,8 +76,8 @@ class OpenGLOutputBuilder { std::unique_ptr framebuffer; // the current pixel output - GLuint output_array_buffer, output_vertex_array; - GLuint source_array_buffer, source_vertex_array; + GLuint output_vertex_array; + GLuint source_vertex_array; unsigned int _last_output_width, _last_output_height; @@ -91,45 +92,12 @@ class OpenGLOutputBuilder { void reset_all_OpenGL_state(); public: - OpenGLOutputBuilder(unsigned int buffer_depth); + TextureBuilder texture_builder; + ArrayBuilder array_builder; + + OpenGLOutputBuilder(size_t bytes_per_pixel); ~OpenGLOutputBuilder(); - inline uint8_t *get_next_source_run() - { - if(_line_buffer.data.size() < _line_buffer.pointer + SourceVertexSize) - _line_buffer.data.resize(_line_buffer.pointer + SourceVertexSize); - return &_line_buffer.data[_line_buffer.pointer]; - } - - inline void complete_source_run() - { - _line_buffer.pointer += SourceVertexSize; - } - - inline uint8_t *get_buffered_source_runs(size_t &size) - { - size = _line_buffer.pointer; - return _line_buffer.data.data(); - } - - inline uint8_t *get_next_output_run() - { - if(_output_buffer.pointer == OutputVertexBufferDataSize) return nullptr; - return &_output_buffer.data[_output_buffer.pointer]; - } - - inline void complete_output_run() - { - size_t line_buffer_size = _line_buffer.data.size(); - if(_source_buffer.pointer + line_buffer_size < SourceVertexBufferDataSize) - { - _output_buffer.pointer += OutputVertexSize; - memcpy(&_source_buffer.data[_source_buffer.pointer], _line_buffer.data.data(), _line_buffer.data.size()); - _source_buffer.pointer += _line_buffer.data.size(); - _line_buffer.pointer = 0; - } - } - inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) { _output_mutex->lock(); @@ -145,11 +113,6 @@ class OpenGLOutputBuilder { _visible_area = visible_area; } - inline bool composite_output_run_has_room_for_vertex() - { - return _output_buffer.pointer < OutputVertexBufferDataSize; - } - inline void lock_output() { _output_mutex->lock(); @@ -181,31 +144,6 @@ class OpenGLOutputBuilder { _composite_src_output_y++; } - inline uint8_t *allocate_write_area(size_t required_length) - { - return _texture_builder->allocate_write_area(required_length); - } - - inline void reduce_previous_allocation_to(size_t actual_length) - { - _texture_builder->reduce_previous_allocation_to(actual_length); - } - - inline bool input_buffer_is_full() - { - return _texture_builder->is_full(); - } - - inline uint16_t get_last_write_x_posititon() - { - return _texture_builder->get_last_write_x_position(); - } - - inline uint16_t get_last_write_y_posititon() - { - return _texture_builder->get_last_write_y_position(); - } - void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty); void set_openGL_context_will_change(bool should_delete_resources); void set_composite_sampling_function(const char *shader); @@ -213,12 +151,6 @@ class OpenGLOutputBuilder { void set_output_device(OutputDevice output_device); void set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider); - struct Buffer { - std::vector data; - size_t pointer; - Buffer() : pointer(0) {} - } _line_buffer, _source_buffer, _output_buffer; - GLsync _fence; }; diff --git a/Outputs/CRT/Internals/TextureBuilder.cpp b/Outputs/CRT/Internals/TextureBuilder.cpp index a728bccc1..1c7bf8f1f 100644 --- a/Outputs/CRT/Internals/TextureBuilder.cpp +++ b/Outputs/CRT/Internals/TextureBuilder.cpp @@ -37,13 +37,15 @@ static const GLenum formatForDepth(size_t depth) } } -TextureBuilder::TextureBuilder(size_t bytes_per_pixel) : +TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) : bytes_per_pixel_(bytes_per_pixel), next_write_x_position_(0), next_write_y_position_(0) { image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight); glGenTextures(1, &texture_name_); + + glActiveTexture(texture_unit); glBindTexture(GL_TEXTURE_2D, texture_name_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); diff --git a/Outputs/CRT/Internals/TextureBuilder.hpp b/Outputs/CRT/Internals/TextureBuilder.hpp index 1dbb4b892..b346ab66c 100644 --- a/Outputs/CRT/Internals/TextureBuilder.hpp +++ b/Outputs/CRT/Internals/TextureBuilder.hpp @@ -28,7 +28,7 @@ class TextureBuilder { public: /// Constructs an instance of InputTextureBuilder that contains a texture of colour depth @c bytes_per_pixel; /// this creates a new texture and binds it to the current active texture unit. - TextureBuilder(size_t bytes_per_pixel); + TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit); virtual ~TextureBuilder(); /// Finds the first available space of at least @c required_length pixels in size. Calls must be paired off From aca1fa0577c8877c4427987eb8f7bc8f46e95cd2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 17 Nov 2016 13:37:53 +0800 Subject: [PATCH 22/40] Returned some video output. Enough significantly to reduce my paranoia. --- Outputs/CRT/CRT.cpp | 11 +++++++---- Outputs/CRT/Internals/ArrayBuilder.cpp | 8 +++++++- Outputs/CRT/Internals/ArrayBuilder.hpp | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 61616e70a..3f4823229 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -203,10 +203,13 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // Construct the output run uint8_t *next_run = openGL_output_builder_.array_builder.get_output_storage(OutputVertexSize); - output_x1() = output_run_.x1; - output_position_y() = output_run_.y; - output_tex_y() = output_y; - output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position(); + if(next_run) + { + output_x1() = output_run_.x1; + output_position_y() = output_run_.y; + output_tex_y() = output_y; + output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position(); + } openGL_output_builder_.array_builder.flush(); openGL_output_builder_.unlock_output(); diff --git a/Outputs/CRT/Internals/ArrayBuilder.cpp b/Outputs/CRT/Internals/ArrayBuilder.cpp index 43eff9698..d3666fb5c 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.cpp +++ b/Outputs/CRT/Internals/ArrayBuilder.cpp @@ -130,7 +130,13 @@ uint8_t *ArrayBuilder::Buffer::reget_storage(size_t &size) void ArrayBuilder::Buffer::flush() { - if(submitted_data && !is_full && allocated_data > submitted_data) + if(allocated_data > submitted_data) + { + flushed_data = allocated_data; + return; + } + + if(submitted_data) { memcpy(data.data(), &data[flushed_data], allocated_data - flushed_data); allocated_data -= flushed_data; diff --git a/Outputs/CRT/Internals/ArrayBuilder.hpp b/Outputs/CRT/Internals/ArrayBuilder.hpp index 2f00a64bb..6d9a0587a 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.hpp +++ b/Outputs/CRT/Internals/ArrayBuilder.hpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include "OpenGL.hpp" From f59537bce926892a16e3bf7f205a019ae146b7c0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 17 Nov 2016 14:06:16 +0800 Subject: [PATCH 23/40] Added a testing hook, but as of yet no tests. --- Outputs/CRT/Internals/ArrayBuilder.cpp | 48 ++++++++++++++++---------- Outputs/CRT/Internals/ArrayBuilder.hpp | 11 ++++-- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/Outputs/CRT/Internals/ArrayBuilder.cpp b/Outputs/CRT/Internals/ArrayBuilder.cpp index d3666fb5c..1989f6e68 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.cpp +++ b/Outputs/CRT/Internals/ArrayBuilder.cpp @@ -11,8 +11,13 @@ using namespace Outputs::CRT; ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) : - output_(output_size), - input_(input_size) + output_(output_size, nullptr), + input_(input_size, nullptr) +{} + +ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size, std::function submission_function) : + output_(output_size, submission_function), + input_(input_size, submission_function) {} bool ArrayBuilder::is_full() @@ -70,8 +75,8 @@ ArrayBuilder::Submission ArrayBuilder::submit() ArrayBuilder::Submission submission; buffer_mutex_.lock(); - submission.input_size = input_.submit(); - submission.output_size = output_.submit(); + submission.input_size = input_.submit(true); + submission.output_size = output_.submit(false); if(is_full_) { is_full_ = false; @@ -83,18 +88,22 @@ ArrayBuilder::Submission ArrayBuilder::submit() return submission; } -ArrayBuilder::Buffer::Buffer(size_t size) : - allocated_data(0), flushed_data(0), submitted_data(0), is_full(false) +ArrayBuilder::Buffer::Buffer(size_t size, std::function submission_function) : + allocated_data(0), flushed_data(0), submitted_data(0), is_full(false), submission_function_(submission_function) { - glGenBuffers(1, &buffer); - glBindBuffer(GL_ARRAY_BUFFER, buffer); - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW); + if(!submission_function_) + { + glGenBuffers(1, &buffer); + glBindBuffer(GL_ARRAY_BUFFER, buffer); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW); + } data.resize(size); } ArrayBuilder::Buffer::~Buffer() { - glDeleteBuffers(1, &buffer); + if(!submission_function_) + glDeleteBuffers(1, &buffer); } uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer) @@ -151,16 +160,19 @@ void ArrayBuilder::Buffer::flush() } } -size_t ArrayBuilder::Buffer::submit() +size_t ArrayBuilder::Buffer::submit(bool is_input) { size_t length = flushed_data; - - glBindBuffer(GL_ARRAY_BUFFER, buffer); - uint8_t *destination = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); - memcpy(destination, data.data(), length); - glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length); - glUnmapBuffer(GL_ARRAY_BUFFER); - + if(submission_function_) + submission_function_(is_input, data.data(), length); + else + { + glBindBuffer(GL_ARRAY_BUFFER, buffer); + uint8_t *destination = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); + memcpy(destination, data.data(), length); + glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length); + glUnmapBuffer(GL_ARRAY_BUFFER); + } submitted_data = flushed_data; return length; } diff --git a/Outputs/CRT/Internals/ArrayBuilder.hpp b/Outputs/CRT/Internals/ArrayBuilder.hpp index 6d9a0587a..dd3e26497 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.hpp +++ b/Outputs/CRT/Internals/ArrayBuilder.hpp @@ -32,6 +32,11 @@ class ArrayBuilder { /// @c input_size bytes of storage for the input buffer. ArrayBuilder(size_t input_size, size_t output_size); + /// Creates an instance of ArrayBuilder with @c output_size bytes of storage for the output buffer and + /// @c input_size bytes of storage for the input buffer that, rather than using OpenGL, will submit data + /// to the @c submission_function. [Teleological: this is provided as a testing hook.] + ArrayBuilder(size_t input_size, size_t output_size, std::function submission_function); + /// Attempts to add @c size bytes uint8_t *get_input_storage(size_t size); uint8_t *reget_input_storage(size_t &size); @@ -52,7 +57,7 @@ class ArrayBuilder { private: struct Buffer { - Buffer(size_t size); + Buffer(size_t size, std::function submission_function); ~Buffer(); std::vector data; @@ -66,14 +71,16 @@ class ArrayBuilder { uint8_t *reget_storage(size_t &size); void flush(); - size_t submit(); + size_t submit(bool is_input); void bind(); void reset(); + std::function submission_function_; } output_, input_; uint8_t *get_storage(size_t size, Buffer &buffer); std::mutex buffer_mutex_; bool is_full_; + ; }; } From 22cb8ecd753b0b11de9cfdb5551068416d725ad2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Nov 2016 08:27:08 +0800 Subject: [PATCH 24/40] Started building some tests of the array builder. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 ++ .../Mac/Clock SignalTests/ArrayBuilderTests.h | 13 +++ .../Clock SignalTests/ArrayBuilderTests.mm | 83 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.h create mode 100644 OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 4e3de67d9..0f69dbae3 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8261D2C2470003C5BF8 /* C1540.cpp */; }; 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; }; 4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */; }; + 4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */; }; 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; }; 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; }; @@ -489,6 +490,8 @@ 4B4DC82A1D2C27A4003C5BF8 /* SerialBus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialBus.hpp; sourceTree = ""; }; 4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ArrayBuilder.cpp; sourceTree = ""; }; 4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ArrayBuilder.hpp; sourceTree = ""; }; + 4B5073081DDFCFDF00C48FBD /* ArrayBuilderTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayBuilderTests.h; sourceTree = ""; }; + 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArrayBuilderTests.mm; sourceTree = ""; }; 4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = ""; }; 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = ""; }; 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = ""; }; @@ -1633,6 +1636,8 @@ 4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = { isa = PBXGroup; children = ( + 4B5073081DDFCFDF00C48FBD /* ArrayBuilderTests.h */, + 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */, 4BB73EB81B587A5100552FC2 /* Info.plist */, 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */, 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */, @@ -2383,6 +2388,7 @@ 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */, 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */, 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */, + 4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */, 4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */, 4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */, 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */, diff --git a/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.h b/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.h new file mode 100644 index 000000000..ff1800b4d --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.h @@ -0,0 +1,13 @@ +// +// ArrayBuilderTests.h +// Clock Signal +// +// Created by Thomas Harte on 19/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import + +@interface ArrayBuilderTests : XCTestCase + +@end diff --git a/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm b/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm new file mode 100644 index 000000000..983feca4b --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm @@ -0,0 +1,83 @@ +// +// ArrayBuilderTests.m +// Clock Signal +// +// Created by Thomas Harte on 19/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "ArrayBuilderTests.h" +#include "ArrayBuilder.hpp" + +static NSData *inputData, *outputData; + +static void setData(bool is_input, uint8_t *data, size_t size) +{ + NSData *dataObject = [NSData dataWithBytes:data length:size]; + if(is_input) inputData = dataObject; else outputData = dataObject; +} + +@implementation ArrayBuilderTests + ++ (void)setUp +{ + inputData = nil; + outputData = nil; +} + +- (void)assertMonotonicForInputSize:(size_t)inputSize outputSize:(size_t)outputSize +{ + XCTAssert(inputData != nil, @"Should have received some input data"); + XCTAssert(outputData != nil, @"Should have received some output data"); + + XCTAssert(inputData.length == inputSize, @"Input data should be 5 bytes long, was %d", inputData.length); + XCTAssert(outputData.length == outputSize, @"Output data should be 3 bytes long, was %d", outputData.length); + + uint8_t *input = (uint8_t *)inputData.bytes; + uint8_t *output = (uint8_t *)outputData.bytes; + + for(int c = 0; c < inputSize; c++) XCTAssert(input[c] == c, @"Input item %d should be %d, was %d", c, c, input[c]); + for(int c = 0; c < outputSize; c++) XCTAssert(output[c] == c + 0x80, @"Output item %d should be %d, was %d", c, c+0x80, output[c]); +} + +- (void)testSingleWriteSingleFlush +{ + Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); + + uint8_t *input = arrayBuilder.get_input_storage(5); + uint8_t *output = arrayBuilder.get_output_storage(3); + + for(int c = 0; c < 5; c++) input[c] = c; + for(int c = 0; c < 3; c++) output[c] = c + 0x80; + + arrayBuilder.flush(); + arrayBuilder.submit(); + + [self assertMonotonicForInputSize:5 outputSize:3]; +} + +- (void)testDoubleWriteSingleFlush +{ + Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); + uint8_t *input; + uint8_t *output; + + input = arrayBuilder.get_input_storage(2); + output = arrayBuilder.get_output_storage(2); + + for(int c = 0; c < 2; c++) input[c] = c; + for(int c = 0; c < 2; c++) output[c] = c + 0x80; + + input = arrayBuilder.get_input_storage(2); + output = arrayBuilder.get_output_storage(2); + + for(int c = 0; c < 2; c++) input[c] = c+2; + for(int c = 0; c < 2; c++) output[c] = c+2 + 0x80; + + arrayBuilder.flush(); + arrayBuilder.submit(); + + [self assertMonotonicForInputSize:4 outputSize:4]; +} + +@end From 274ec9efb85777704582d34940bbe30eab0a2049 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Nov 2016 08:59:21 +0800 Subject: [PATCH 25/40] Added a test for interceding submit. --- .../Clock SignalTests/ArrayBuilderTests.mm | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm b/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm index 983feca4b..ce7ce159c 100644 --- a/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm +++ b/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm @@ -30,8 +30,8 @@ static void setData(bool is_input, uint8_t *data, size_t size) XCTAssert(inputData != nil, @"Should have received some input data"); XCTAssert(outputData != nil, @"Should have received some output data"); - XCTAssert(inputData.length == inputSize, @"Input data should be 5 bytes long, was %d", inputData.length); - XCTAssert(outputData.length == outputSize, @"Output data should be 3 bytes long, was %d", outputData.length); + XCTAssert(inputData.length == inputSize, @"Input data should be 5 bytes long, was %lu", (unsigned long)inputData.length); + XCTAssert(outputData.length == outputSize, @"Output data should be 3 bytes long, was %lu", (unsigned long)outputData.length); uint8_t *input = (uint8_t *)inputData.bytes; uint8_t *output = (uint8_t *)outputData.bytes; @@ -80,4 +80,26 @@ static void setData(bool is_input, uint8_t *data, size_t size) [self assertMonotonicForInputSize:4 outputSize:4]; } +- (void)testSubmitWithoutFlush +{ + Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); + + arrayBuilder.get_input_storage(5); + arrayBuilder.get_input_storage(8); + arrayBuilder.get_output_storage(6); + arrayBuilder.get_input_storage(12); + arrayBuilder.get_output_storage(3); + + arrayBuilder.submit(); + + XCTAssert(inputData.length == 0, @"No input data should have been received; %lu bytes were received", (unsigned long)inputData.length); + XCTAssert(outputData.length == 0, @"No output data should have been received; %lu bytes were received", (unsigned long)outputData.length); + + arrayBuilder.flush(); + arrayBuilder.submit(); + + XCTAssert(inputData.length == 25, @"All input data should have been received; %lu bytes were received", (unsigned long)inputData.length); + XCTAssert(outputData.length == 9, @"All output data should have been received; %lu bytes were received", (unsigned long)outputData.length); +} + @end From be60eaa12035269e0fc8ba723c8a65b969d34113 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Nov 2016 19:48:16 +0800 Subject: [PATCH 26/40] Added a test for pointer continuity over a submit. Which fails. --- .../Clock SignalTests/ArrayBuilderTests.mm | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm b/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm index ce7ce159c..a2ff011e4 100644 --- a/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm +++ b/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm @@ -30,8 +30,8 @@ static void setData(bool is_input, uint8_t *data, size_t size) XCTAssert(inputData != nil, @"Should have received some input data"); XCTAssert(outputData != nil, @"Should have received some output data"); - XCTAssert(inputData.length == inputSize, @"Input data should be 5 bytes long, was %lu", (unsigned long)inputData.length); - XCTAssert(outputData.length == outputSize, @"Output data should be 3 bytes long, was %lu", (unsigned long)outputData.length); + XCTAssert(inputData.length == inputSize, @"Input data should be %lu bytes long, was %lu", inputSize, (unsigned long)inputData.length); + XCTAssert(outputData.length == outputSize, @"Output data should be %lu bytes long, was %lu", outputSize, (unsigned long)outputData.length); uint8_t *input = (uint8_t *)inputData.bytes; uint8_t *output = (uint8_t *)outputData.bytes; @@ -102,4 +102,27 @@ static void setData(bool is_input, uint8_t *data, size_t size) XCTAssert(outputData.length == 9, @"All output data should have been received; %lu bytes were received", (unsigned long)outputData.length); } +- (void)testSubmitContinuity +{ + Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); + + arrayBuilder.get_input_storage(5); + arrayBuilder.get_output_storage(5); + + arrayBuilder.flush(); + + uint8_t *input = arrayBuilder.get_input_storage(5); + uint8_t *output = arrayBuilder.get_output_storage(5); + + arrayBuilder.submit(); + + for(int c = 0; c < 5; c++) input[c] = c; + for(int c = 0; c < 5; c++) output[c] = c + 0x80; + + arrayBuilder.flush(); + arrayBuilder.submit(); + + [self assertMonotonicForInputSize:5 outputSize:5]; +} + @end From a4c7b00ecd3d6ef30778dbc65b780034a5753555 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Nov 2016 19:55:30 +0800 Subject: [PATCH 27/40] Fixed code that was causing the failing test. --- Outputs/CRT/Internals/ArrayBuilder.cpp | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/Outputs/CRT/Internals/ArrayBuilder.cpp b/Outputs/CRT/Internals/ArrayBuilder.cpp index 1989f6e68..67ba2edcc 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.cpp +++ b/Outputs/CRT/Internals/ArrayBuilder.cpp @@ -139,25 +139,15 @@ uint8_t *ArrayBuilder::Buffer::reget_storage(size_t &size) void ArrayBuilder::Buffer::flush() { - if(allocated_data > submitted_data) - { - flushed_data = allocated_data; - return; - } - if(submitted_data) { - memcpy(data.data(), &data[flushed_data], allocated_data - flushed_data); - allocated_data -= flushed_data; - flushed_data = allocated_data; - submitted_data = 0; - } - else - { - allocated_data = 0; - flushed_data = 0; + memcpy(data.data(), &data[submitted_data], allocated_data - submitted_data); + allocated_data -= submitted_data; + flushed_data -= submitted_data; submitted_data = 0; } + + flushed_data = allocated_data; } size_t ArrayBuilder::Buffer::submit(bool is_input) From 7857ef774f8b380c6979922092b7ad290088739e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Nov 2016 20:09:38 +0800 Subject: [PATCH 28/40] Ensured resets genuinely kill outstanding data. --- Outputs/CRT/Internals/ArrayBuilder.cpp | 12 +++++++++--- Outputs/CRT/Internals/ArrayBuilder.hpp | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Outputs/CRT/Internals/ArrayBuilder.cpp b/Outputs/CRT/Internals/ArrayBuilder.cpp index 67ba2edcc..514f827d8 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.cpp +++ b/Outputs/CRT/Internals/ArrayBuilder.cpp @@ -148,6 +148,14 @@ void ArrayBuilder::Buffer::flush() } flushed_data = allocated_data; + + if(was_reset) + { + allocated_data = 0; + flushed_data = 0; + submitted_data = 0; + was_reset = false; + } } size_t ArrayBuilder::Buffer::submit(bool is_input) @@ -174,8 +182,6 @@ void ArrayBuilder::Buffer::bind() void ArrayBuilder::Buffer::reset() { - allocated_data = 0; - flushed_data = 0; - submitted_data = 0; + was_reset = true; is_full = false; } diff --git a/Outputs/CRT/Internals/ArrayBuilder.hpp b/Outputs/CRT/Internals/ArrayBuilder.hpp index dd3e26497..cf1b6c8a1 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.hpp +++ b/Outputs/CRT/Internals/ArrayBuilder.hpp @@ -64,7 +64,7 @@ class ArrayBuilder { size_t allocated_data; size_t flushed_data; size_t submitted_data; - bool is_full; + bool is_full, was_reset; GLuint buffer; uint8_t *get_storage(size_t size); From c2349ee3f40f7316a6006909759577fdc860d48b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Nov 2016 20:12:40 +0800 Subject: [PATCH 29/40] This thing has clearly becoma a real class. --- Outputs/CRT/Internals/ArrayBuilder.hpp | 34 ++++++++++++++------------ 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Outputs/CRT/Internals/ArrayBuilder.hpp b/Outputs/CRT/Internals/ArrayBuilder.hpp index cf1b6c8a1..b9d84b28a 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.hpp +++ b/Outputs/CRT/Internals/ArrayBuilder.hpp @@ -56,25 +56,27 @@ class ArrayBuilder { Submission submit(); private: - struct Buffer { - Buffer(size_t size, std::function submission_function); - ~Buffer(); + class Buffer { + public: + Buffer(size_t size, std::function submission_function); + ~Buffer(); - std::vector data; - size_t allocated_data; - size_t flushed_data; - size_t submitted_data; - bool is_full, was_reset; - GLuint buffer; + uint8_t *get_storage(size_t size); + uint8_t *reget_storage(size_t &size); - uint8_t *get_storage(size_t size); - uint8_t *reget_storage(size_t &size); + void flush(); + size_t submit(bool is_input); + void bind(); + void reset(); - void flush(); - size_t submit(bool is_input); - void bind(); - void reset(); - std::function submission_function_; + private: + bool is_full, was_reset; + GLuint buffer; + std::function submission_function_; + std::vector data; + size_t allocated_data; + size_t flushed_data; + size_t submitted_data; } output_, input_; uint8_t *get_storage(size_t size, Buffer &buffer); From fda90c5aef7efb9f8a290de45ba5ab9834defe28 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Nov 2016 09:11:24 +0800 Subject: [PATCH 30/40] Documented. --- Outputs/CRT/Internals/ArrayBuilder.cpp | 10 ++++----- Outputs/CRT/Internals/ArrayBuilder.hpp | 30 +++++++++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Outputs/CRT/Internals/ArrayBuilder.cpp b/Outputs/CRT/Internals/ArrayBuilder.cpp index 514f827d8..d04e394cc 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.cpp +++ b/Outputs/CRT/Internals/ArrayBuilder.cpp @@ -34,9 +34,9 @@ uint8_t *ArrayBuilder::get_input_storage(size_t size) return get_storage(size, input_); } -uint8_t *ArrayBuilder::reget_input_storage(size_t &size) +uint8_t *ArrayBuilder::get_unflushed_input(size_t &size) { - return input_.reget_storage(size); + return input_.get_unflushed(size); } uint8_t *ArrayBuilder::get_output_storage(size_t size) @@ -44,9 +44,9 @@ uint8_t *ArrayBuilder::get_output_storage(size_t size) return get_storage(size, output_); } -uint8_t *ArrayBuilder::reget_output_storage(size_t &size) +uint8_t *ArrayBuilder::get_unflushed_input(size_t &size) { - return output_.reget_storage(size); + return output_.get_unflushed(size); } void ArrayBuilder::flush() @@ -127,7 +127,7 @@ uint8_t *ArrayBuilder::Buffer::get_storage(size_t size) return pointer; } -uint8_t *ArrayBuilder::Buffer::reget_storage(size_t &size) +uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size) { if(is_full) { diff --git a/Outputs/CRT/Internals/ArrayBuilder.hpp b/Outputs/CRT/Internals/ArrayBuilder.hpp index b9d84b28a..ed72b98af 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.hpp +++ b/Outputs/CRT/Internals/ArrayBuilder.hpp @@ -37,22 +37,43 @@ class ArrayBuilder { /// to the @c submission_function. [Teleological: this is provided as a testing hook.] ArrayBuilder(size_t input_size, size_t output_size, std::function submission_function); - /// Attempts to add @c size bytes + /// Attempts to add @c size bytes to the input set. + /// @returns a pointer to the allocated area if allocation was possible; @c nullptr otherwise. uint8_t *get_input_storage(size_t size); - uint8_t *reget_input_storage(size_t &size); + /// Gets the size of and a pointer to all data so far added to the input set but not yet flushed. + /// @returns a pointer from which it is safe to access @c size elements, which contains all regions returned via + /// @c get_input_storage in FIFO order. + uint8_t *get_unflushed_input(size_t &size); + + /// Attempts to add @c size bytes to the output set. + /// @returns a pointer to the allocated area if allocation was possible; @c nullptr otherwise. uint8_t *get_output_storage(size_t size); - uint8_t *reget_output_storage(size_t &size); + /// Gets the size of and a pointer to all data so far added to the output set but not yet flushed. + /// @returns a pointer from which it is safe to access @c size elements, which contains all regions returned via + /// @c get_input_storage in FIFO order. + uint8_t *get_unflushed_output(size_t &size); + + /// @returns @c true if either of the input or output storage areas is currently exhausted; @c false otherwise. bool is_full(); + + /// If neither input nor output was exhausted since the last flush, atomically commits both input and output + /// up to the currently allocated size for use upon the next @c submit. Otherwise acts as a no-op. void flush(); + /// Binds the input array to GL_ARRAY_BUFFER. void bind_input(); + + /// Binds the output array to GL_ARRAY_BUFFER. void bind_output(); struct Submission { size_t input_size, output_size; }; + + /// Submits all flushed input and output data to the corresponding arrays. + /// @returns A @c Submission record, indicating how much data of each type was submitted. Submission submit(); private: @@ -62,7 +83,7 @@ class ArrayBuilder { ~Buffer(); uint8_t *get_storage(size_t size); - uint8_t *reget_storage(size_t &size); + uint8_t *get_unflushed(size_t &size); void flush(); size_t submit(bool is_input); @@ -82,7 +103,6 @@ class ArrayBuilder { std::mutex buffer_mutex_; bool is_full_; - ; }; } From 5bc165960a269b49cc61360eab0d1fe27382c685 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Nov 2016 09:12:06 +0800 Subject: [PATCH 31/40] Completed refactoring. --- Outputs/CRT/CRT.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 3f4823229..3f68cddfe 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -195,7 +195,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // Get and write all those previously unwritten output ys uint16_t output_y = openGL_output_builder_.get_composite_output_y(); size_t size; - uint8_t *buffered_lines = openGL_output_builder_.array_builder.reget_input_storage(size); + uint8_t *buffered_lines = openGL_output_builder_.array_builder.get_unflushed_input(size); for(size_t position = 0; position < size; position += SourceVertexSize) { (*(uint16_t *)&buffered_lines[position + SourceVertexOffsetOfOutputStart + 2]) = output_y; From ef0367d4a4a3c6d7bcfe0f2e4fff7597c16011a1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Nov 2016 09:12:43 +0800 Subject: [PATCH 32/40] Corrected typo. --- Outputs/CRT/Internals/ArrayBuilder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/ArrayBuilder.cpp b/Outputs/CRT/Internals/ArrayBuilder.cpp index d04e394cc..09df1463e 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.cpp +++ b/Outputs/CRT/Internals/ArrayBuilder.cpp @@ -44,7 +44,7 @@ uint8_t *ArrayBuilder::get_output_storage(size_t size) return get_storage(size, output_); } -uint8_t *ArrayBuilder::get_unflushed_input(size_t &size) +uint8_t *ArrayBuilder::get_unflushed_output(size_t &size) { return output_.get_unflushed(size); } From 32dbfe947d3e47079c9566060b78e39a47ca70fc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Nov 2016 10:53:35 +0800 Subject: [PATCH 33/40] With hindsight, the elimination of this might have been overzealous. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 8825fba6b..a2e11a1d9 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -161,6 +161,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out { // switch to the initial texture active_pipeline->target->bind_framebuffer(); + glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f); glClear(GL_COLOR_BUFFER_BIT); active_pipeline->shader->bind(); @@ -173,12 +174,11 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // transfer to framebuffer framebuffer->bind_framebuffer(); + // draw from from the output array buffer with blending + glBindVertexArray(output_vertex_array); glEnable(GL_BLEND); - // Ensure we're back on the output framebuffer, drawing from the output array buffer - glBindVertexArray(output_vertex_array); - - // update uniforms (implicitly binding the shader) + // update uniforms, then bind the thing if(_last_output_width != output_width || _last_output_height != output_height) { output_shader_program->set_output_size(output_width, output_height, _visible_area); From c89345c6396b20675b7f2b0b7eccf173ae4b0508 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 21 Nov 2016 11:21:14 +0800 Subject: [PATCH 34/40] Reduced buffer size. --- Outputs/CRT/Internals/CRTConstants.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index 0951d65f3..54f14edbe 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -37,7 +37,7 @@ const GLsizei InputBufferBuilderHeight = 512; // This is the size of the intermediate buffers used during composite to RGB conversion const GLsizei IntermediateBufferWidth = 2048; -const GLsizei IntermediateBufferHeight = 1024; +const GLsizei IntermediateBufferHeight = 512; // Some internal buffer sizes const GLsizeiptr OutputVertexBufferDataSize = OutputVertexSize * IntermediateBufferHeight; // i.e. the maximum number of scans of output that can be created between draws From d7d0ed378a1e233a7b7eaa90138466d7c0d4a687 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 21 Nov 2016 11:26:07 +0800 Subject: [PATCH 35/40] Only the final intermediate buffer needs clearing, on reflection. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index a2e11a1d9..20c9a7a9a 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -161,8 +161,11 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out { // switch to the initial texture active_pipeline->target->bind_framebuffer(); - glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + if(!active_pipeline[1].target) + { + glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + } active_pipeline->shader->bind(); // draw as desired From 7b38247ab3b2ff73bb69e83b7bb6e3744d31e28b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 21 Nov 2016 11:40:13 +0800 Subject: [PATCH 36/40] Updated outdated comments. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 20c9a7a9a..55c5d4e15 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -132,6 +132,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out float clear_colour[3]; }; + // for composite video, go through four steps to get to something that can be painted to the output RenderStage composite_render_stages[] = { {compositeTexture.get(), composite_input_shader_program.get(), {0.0, 0.0, 0.0}}, @@ -141,6 +142,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out {nullptr} }; + // for RGB video, there's only two steps RenderStage rgb_render_stages[] = { {compositeTexture.get(), rgb_input_shader_program.get(), {0.0, 0.0, 0.0}}, @@ -150,7 +152,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out RenderStage *active_pipeline = (_output_device == Television || !rgb_input_shader_program) ? composite_render_stages : rgb_render_stages; - // for television, update intermediate buffers and then draw; for a monitor, just draw if(array_submission.input_size || array_submission.output_size) { // all drawing will be from the source vertex array and without blending @@ -159,29 +160,32 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out while(active_pipeline->target) { - // switch to the initial texture + // switch to the framebuffer and shader associated with this stage + active_pipeline->shader->bind(); active_pipeline->target->bind_framebuffer(); + + // if this is the final stage before painting to the CRT, clear the framebuffer before drawing in order to blank out + // those portions for which no input was provided if(!active_pipeline[1].target) { glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f); glClear(GL_COLOR_BUFFER_BIT); } - active_pipeline->shader->bind(); - // draw as desired + // draw glDrawArraysInstanced(GL_LINES, 0, 2, (GLsizei)array_submission.input_size / SourceVertexSize); active_pipeline++; } - // transfer to framebuffer + // prepare to transfer to framebuffer framebuffer->bind_framebuffer(); - // draw from from the output array buffer with blending + // draw from the output array buffer, with blending glBindVertexArray(output_vertex_array); glEnable(GL_BLEND); - // update uniforms, then bind the thing + // update uniforms, then bind the target if(_last_output_width != output_width || _last_output_height != output_height) { output_shader_program->set_output_size(output_width, output_height, _visible_area); From e1285028aa6ce346e1ec89c9006a701062e20f04 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 21 Nov 2016 11:42:45 +0800 Subject: [PATCH 37/40] Removed a redundant clear and some dead code. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 55c5d4e15..4211bdb4d 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -202,14 +202,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glDisable(GL_BLEND); glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); - glClear(GL_COLOR_BUFFER_BIT); glActiveTexture(pixel_accumulation_texture_unit); framebuffer->bind_texture(); framebuffer->draw((float)output_width / (float)output_height); -// glViewport(0, 0, (GLsizei)output_width / 4, (GLsizei)output_height / 4); -// compositeTexture->bind_texture(); -// compositeTexture->draw((float)output_width / (float)output_height); _fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); _draw_mutex->unlock(); From 340607e13ec6bfa7c2c6d24d1b75330b80d203d1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 21 Nov 2016 11:48:31 +0800 Subject: [PATCH 38/40] Switched the flywheel to suffix underscores. --- Outputs/CRT/Internals/Flywheel.hpp | 80 +++++++++++++++--------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/Outputs/CRT/Internals/Flywheel.hpp b/Outputs/CRT/Internals/Flywheel.hpp index f52f949d8..9b357baa0 100644 --- a/Outputs/CRT/Internals/Flywheel.hpp +++ b/Outputs/CRT/Internals/Flywheel.hpp @@ -31,13 +31,13 @@ struct Flywheel @param sync_error_window The permitted deviation of sync timings from the norm. */ Flywheel(unsigned int standard_period, unsigned int retrace_time, unsigned int sync_error_window) : - _standard_period(standard_period), - _retrace_time(retrace_time), - _sync_error_window(sync_error_window), - _counter(0), - _expected_next_sync(standard_period), - _counter_before_retrace(standard_period - retrace_time), - _number_of_surprises(0) {} + standard_period_(standard_period), + retrace_time_(retrace_time), + sync_error_window_(sync_error_window), + counter_(0), + expected_next_sync_(standard_period), + counter_before_retrace_(standard_period - retrace_time), + number_of_surprises_(0) {} enum SyncEvent { /// Indicates that no synchronisation events will occur in the queried window. @@ -66,22 +66,22 @@ struct Flywheel // do we recognise this hsync, thereby adjusting future time expectations? if(sync_is_requested) { - if(_counter < _sync_error_window || _counter > _expected_next_sync - _sync_error_window) + if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_) { - unsigned int time_now = (_counter < _sync_error_window) ? _expected_next_sync + _counter : _counter; - _expected_next_sync = (3*_expected_next_sync + time_now) >> 2; + unsigned int time_now = (counter_ < sync_error_window_) ? expected_next_sync_ + counter_ : counter_; + expected_next_sync_ = (3*expected_next_sync_ + time_now) >> 2; } else { - _number_of_surprises++; + number_of_surprises_++; - if(_counter < _retrace_time + (_expected_next_sync >> 1)) + if(counter_ < retrace_time_ + (expected_next_sync_ >> 1)) { - _expected_next_sync = (3*_expected_next_sync + _standard_period + _sync_error_window) >> 2; + expected_next_sync_ = (3*expected_next_sync_ + standard_period_ + sync_error_window_) >> 2; } else { - _expected_next_sync = (3*_expected_next_sync + _standard_period - _sync_error_window) >> 2; + expected_next_sync_ = (3*expected_next_sync_ + standard_period_ - sync_error_window_) >> 2; } } } @@ -90,16 +90,16 @@ struct Flywheel unsigned int proposed_sync_time = cycles_to_run_for; // will we end an ongoing retrace? - if(_counter < _retrace_time && _counter + proposed_sync_time >= _retrace_time) + if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_) { - proposed_sync_time = _retrace_time - _counter; + proposed_sync_time = retrace_time_ - counter_; proposed_event = SyncEvent::EndRetrace; } // will we start a retrace? - if(_counter + proposed_sync_time >= _expected_next_sync) + if(counter_ + proposed_sync_time >= expected_next_sync_) { - proposed_sync_time = _expected_next_sync - _counter; + proposed_sync_time = expected_next_sync_ - counter_; proposed_event = SyncEvent::StartRetrace; } @@ -117,14 +117,14 @@ struct Flywheel */ inline void apply_event(unsigned int cycles_advanced, SyncEvent event) { - _counter += cycles_advanced; + counter_ += cycles_advanced; switch(event) { default: return; case StartRetrace: - _counter_before_retrace = _counter - _retrace_time; - _counter = 0; + counter_before_retrace_ = counter_ - retrace_time_; + counter_ = 0; return; } } @@ -137,14 +137,14 @@ struct Flywheel */ inline unsigned int get_current_output_position() { - if(_counter < _retrace_time) + if(counter_ < retrace_time_) { - unsigned int retrace_distance = (_counter * _standard_period) / _retrace_time; - if(retrace_distance > _counter_before_retrace) return 0; - return _counter_before_retrace - retrace_distance; + unsigned int retrace_distance = (counter_ * standard_period_) / retrace_time_; + if(retrace_distance > counter_before_retrace_) return 0; + return counter_before_retrace_ - retrace_distance; } - return _counter - _retrace_time; + return counter_ - retrace_time_; } /*! @@ -152,7 +152,7 @@ struct Flywheel */ inline unsigned int get_current_time() { - return _counter; + return counter_; } /*! @@ -160,7 +160,7 @@ struct Flywheel */ inline bool is_in_retrace() { - return _counter < _retrace_time; + return counter_ < retrace_time_; } /*! @@ -168,7 +168,7 @@ struct Flywheel */ inline unsigned int get_scan_period() { - return _standard_period - _retrace_time; + return standard_period_ - retrace_time_; } /*! @@ -176,7 +176,7 @@ struct Flywheel */ inline unsigned int get_standard_period() { - return _standard_period; + return standard_period_; } /*! @@ -185,8 +185,8 @@ struct Flywheel */ inline unsigned int get_and_reset_number_of_surprises() { - unsigned int result = _number_of_surprises; - _number_of_surprises = 0; + unsigned int result = number_of_surprises_; + number_of_surprises_ = 0; return result; } @@ -195,19 +195,19 @@ struct Flywheel */ inline bool is_near_expected_sync() { - return abs((int)_counter - (int)_expected_next_sync) < (int)_standard_period / 50; + return abs((int)counter_ - (int)expected_next_sync_) < (int)standard_period_ / 50; } private: - unsigned int _standard_period; // the normal length of time between syncs - const unsigned int _retrace_time; // a constant indicating the amount of time it takes to perform a retrace - const unsigned int _sync_error_window; // a constant indicating the window either side of the next expected sync in which we'll accept other syncs + unsigned int standard_period_; // the normal length of time between syncs + const unsigned int retrace_time_; // a constant indicating the amount of time it takes to perform a retrace + const unsigned int sync_error_window_; // a constant indicating the window either side of the next expected sync in which we'll accept other syncs - unsigned int _counter; // time since the _start_ of the last sync - unsigned int _counter_before_retrace; // the value of _counter immediately before retrace began - unsigned int _expected_next_sync; // our current expection of when the next sync will be encountered (which implies velocity) + unsigned int counter_; // time since the _start_ of the last sync + unsigned int counter_before_retrace_; // the value of _counter immediately before retrace began + unsigned int expected_next_sync_; // our current expection of when the next sync will be encountered (which implies velocity) - unsigned int _number_of_surprises; // a count of the surprising syncs + unsigned int number_of_surprises_; // a count of the surprising syncs /* Implementation notes: From bc03e12dc52df3344d3fae555d2e61c7ad1c0d53 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 21 Nov 2016 11:57:45 +0800 Subject: [PATCH 39/40] Switched to suffix underscores. --- Outputs/CRT/Internals/Shaders/Shader.cpp | 40 ++++++++++++------------ Outputs/CRT/Internals/Shaders/Shader.hpp | 10 +++--- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Outputs/CRT/Internals/Shaders/Shader.cpp b/Outputs/CRT/Internals/Shaders/Shader.cpp index f0e2fbb26..df96c14ab 100644 --- a/Outputs/CRT/Internals/Shaders/Shader.cpp +++ b/Outputs/CRT/Internals/Shaders/Shader.cpp @@ -46,34 +46,34 @@ GLuint Shader::compile_shader(const char *source, GLenum type) Shader::Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings) { - _shader_program = glCreateProgram(); + shader_program_ = glCreateProgram(); GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER); GLuint fragment = compile_shader(fragment_shader, GL_FRAGMENT_SHADER); - glAttachShader(_shader_program, vertex); - glAttachShader(_shader_program, fragment); + glAttachShader(shader_program_, vertex); + glAttachShader(shader_program_, fragment); if(attribute_bindings) { while(attribute_bindings->name) { - glBindAttribLocation(_shader_program, attribute_bindings->index, attribute_bindings->name); + glBindAttribLocation(shader_program_, attribute_bindings->index, attribute_bindings->name); attribute_bindings++; } } - glLinkProgram(_shader_program); + glLinkProgram(shader_program_); #if defined(DEBUG) GLint didLink = 0; - glGetProgramiv(_shader_program, GL_LINK_STATUS, &didLink); + glGetProgramiv(shader_program_, GL_LINK_STATUS, &didLink); if(didLink == GL_FALSE) { GLint logLength; - glGetProgramiv(_shader_program, GL_INFO_LOG_LENGTH, &logLength); + glGetProgramiv(shader_program_, GL_INFO_LOG_LENGTH, &logLength); if(logLength > 0) { GLchar *log = (GLchar *)malloc((size_t)logLength); - glGetProgramInfoLog(_shader_program, logLength, &logLength, log); + glGetProgramInfoLog(shader_program_, logLength, &logLength, log); printf("Link log:\n%s\n", log); free(log); } @@ -85,14 +85,14 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att Shader::~Shader() { if(bound_shader == this) Shader::unbind(); - glDeleteProgram(_shader_program); + glDeleteProgram(shader_program_); } void Shader::bind() { if(bound_shader != this) { - glUseProgram(_shader_program); + glUseProgram(shader_program_); bound_shader = this; } flush_functions(); @@ -106,12 +106,12 @@ void Shader::unbind() GLint Shader::get_attrib_location(const GLchar *name) { - return glGetAttribLocation(_shader_program, name); + return glGetAttribLocation(shader_program_, name); } GLint Shader::get_uniform_location(const GLchar *name) { - return glGetUniformLocation(_shader_program, name); + return glGetUniformLocation(shader_program_, name); } void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor) @@ -123,7 +123,7 @@ void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size, } // The various set_uniforms... -#define location() glGetUniformLocation(_shader_program, name.c_str()) +#define location() glGetUniformLocation(shader_program_, name.c_str()) void Shader::set_uniform(const std::string &name, GLint value) { enqueue_function([name, value, this] { @@ -291,18 +291,18 @@ void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei cou void Shader::enqueue_function(std::function function) { - _function_mutex.lock(); - _enqueued_functions.push_back(function); - _function_mutex.unlock(); + function_mutex_.lock(); + enqueued_functions_.push_back(function); + function_mutex_.unlock(); } void Shader::flush_functions() { - _function_mutex.lock(); - for(std::function function : _enqueued_functions) + function_mutex_.lock(); + for(std::function function : enqueued_functions_) { function(); } - _enqueued_functions.clear(); - _function_mutex.unlock(); + enqueued_functions_.clear(); + function_mutex_.unlock(); } diff --git a/Outputs/CRT/Internals/Shaders/Shader.hpp b/Outputs/CRT/Internals/Shaders/Shader.hpp index 947b47326..ba95daf7c 100644 --- a/Outputs/CRT/Internals/Shaders/Shader.hpp +++ b/Outputs/CRT/Internals/Shaders/Shader.hpp @@ -31,8 +31,8 @@ public: }; struct AttributeBinding { - const GLchar *name; - GLuint index; + const GLchar *const name; + const GLuint index; }; /*! @@ -107,11 +107,11 @@ public: private: GLuint compile_shader(const char *source, GLenum type); - GLuint _shader_program; + GLuint shader_program_; void flush_functions(); - std::vector> _enqueued_functions; - std::mutex _function_mutex; + std::vector> enqueued_functions_; + std::mutex function_mutex_; protected: void enqueue_function(std::function function); From 7c85cb62e4a67847f38265a2a1bc06b2c65a5a23 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 21 Nov 2016 12:14:52 +0800 Subject: [PATCH 40/40] Moved underscores, removed indirections where they're not necessary, converted those names that were still looking very Objective-C and moved the GL fence variable into the private area, where it should always have been. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 254 ++++++++++++++-------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 86 +++++----- 2 files changed, 166 insertions(+), 174 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 4211bdb4d..5cb634ca8 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -25,49 +25,45 @@ namespace { } OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) : - _output_mutex(new std::mutex), - _draw_mutex(new std::mutex), - _visible_area(Rect(0, 0, 1, 1)), - _composite_src_output_y(0), - _composite_shader(nullptr), - _rgb_shader(nullptr), - _last_output_width(0), - _last_output_height(0), - _fence(nullptr), + visible_area_(Rect(0, 0, 1, 1)), + composite_src_output_y_(0), + composite_shader_(nullptr), + rgb_shader_(nullptr), + last_output_width_(0), + last_output_height_(0), + fence_(nullptr), texture_builder(bytes_per_pixel, source_data_texture_unit), - array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize) + array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize), + composite_texture_(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit), + separated_texture_(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit), + filtered_y_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit), + filtered_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit) { glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); glBlendColor(0.6f, 0.6f, 0.6f, 1.0f); - // Create intermediate textures and bind to slots 0, 1 and 2 - compositeTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit)); - separatedTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit)); - filteredYTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit)); - filteredTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit)); - // create the output vertex array - glGenVertexArrays(1, &output_vertex_array); + glGenVertexArrays(1, &output_vertex_array_); // create the source vertex array - glGenVertexArrays(1, &source_vertex_array); + glGenVertexArrays(1, &source_vertex_array_); } OpenGLOutputBuilder::~OpenGLOutputBuilder() { - glDeleteVertexArrays(1, &output_vertex_array); + glDeleteVertexArrays(1, &output_vertex_array_); - free(_composite_shader); - free(_rgb_shader); + free(composite_shader_); + free(rgb_shader_); } void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { // lock down any other draw_frames - _draw_mutex->lock(); + draw_mutex_.lock(); // establish essentials - if(!output_shader_program) + if(!output_shader_program_) { prepare_composite_input_shaders(); prepare_rgb_input_shaders(); @@ -80,38 +76,38 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out set_colour_space_uniforms(); } - if(_fence != nullptr) + 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, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED) + if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED) { - _draw_mutex->unlock(); + draw_mutex_.unlock(); return; } - glDeleteSync(_fence); + glDeleteSync(fence_); } // make sure there's a target to draw to - if(!framebuffer || framebuffer->get_height() != output_height || framebuffer->get_width() != output_width) + if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width) { std::unique_ptr new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit)); - if(framebuffer) + if(framebuffer_) { new_framebuffer->bind_framebuffer(); glClear(GL_COLOR_BUFFER_BIT); glActiveTexture(pixel_accumulation_texture_unit); - framebuffer->bind_texture(); - framebuffer->draw((float)output_width / (float)output_height); + framebuffer_->bind_texture(); + framebuffer_->draw((float)output_width / (float)output_height); new_framebuffer->bind_texture(); } - framebuffer = std::move(new_framebuffer); + framebuffer_ = std::move(new_framebuffer); } // lock out the machine emulation until data is copied - _output_mutex->lock(); + output_mutex_.lock(); // release the mapping, giving up on trying to draw if data has been lost ArrayBuilder::Submission array_submission = array_builder.submit(); @@ -121,10 +117,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out texture_builder.submit(); // buffer usage restart from 0 for the next time around - _composite_src_output_y = 0; + composite_src_output_y_ = 0; // data having been grabbed, allow the machine to continue - _output_mutex->unlock(); + output_mutex_.unlock(); struct RenderStage { OpenGL::TextureTarget *const target; @@ -135,27 +131,27 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // for composite video, go through four steps to get to something that can be painted to the output RenderStage composite_render_stages[] = { - {compositeTexture.get(), composite_input_shader_program.get(), {0.0, 0.0, 0.0}}, - {separatedTexture.get(), composite_separation_filter_program.get(), {0.0, 0.5, 0.5}}, - {filteredYTexture.get(), composite_y_filter_shader_program.get(), {0.0, 0.5, 0.5}}, - {filteredTexture.get(), composite_chrominance_filter_shader_program.get(), {0.0, 0.0, 0.0}}, + {&composite_texture_, composite_input_shader_program_.get(), {0.0, 0.0, 0.0}}, + {&separated_texture_, composite_separation_filter_program_.get(), {0.0, 0.5, 0.5}}, + {&filtered_y_texture_, composite_y_filter_shader_program_.get(), {0.0, 0.5, 0.5}}, + {&filtered_texture_, composite_chrominance_filter_shader_program_.get(), {0.0, 0.0, 0.0}}, {nullptr} }; // for RGB video, there's only two steps RenderStage rgb_render_stages[] = { - {compositeTexture.get(), rgb_input_shader_program.get(), {0.0, 0.0, 0.0}}, - {filteredTexture.get(), rgb_filter_shader_program.get(), {0.0, 0.0, 0.0}}, + {&composite_texture_, rgb_input_shader_program_.get(), {0.0, 0.0, 0.0}}, + {&filtered_texture_, rgb_filter_shader_program_.get(), {0.0, 0.0, 0.0}}, {nullptr} }; - RenderStage *active_pipeline = (_output_device == Television || !rgb_input_shader_program) ? composite_render_stages : rgb_render_stages; + RenderStage *active_pipeline = (output_device_ == Television || !rgb_input_shader_program_) ? composite_render_stages : rgb_render_stages; if(array_submission.input_size || array_submission.output_size) { // all drawing will be from the source vertex array and without blending - glBindVertexArray(source_vertex_array); + glBindVertexArray(source_vertex_array_); glDisable(GL_BLEND); while(active_pipeline->target) @@ -179,20 +175,20 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } // prepare to transfer to framebuffer - framebuffer->bind_framebuffer(); + framebuffer_->bind_framebuffer(); // draw from the output array buffer, with blending - glBindVertexArray(output_vertex_array); + glBindVertexArray(output_vertex_array_); glEnable(GL_BLEND); // update uniforms, then bind the target - if(_last_output_width != output_width || _last_output_height != output_height) + if(last_output_width_ != output_width || last_output_height_ != output_height) { - output_shader_program->set_output_size(output_width, output_height, _visible_area); - _last_output_width = output_width; - _last_output_height = output_height; + output_shader_program_->set_output_size(output_width, output_height, visible_area_); + last_output_width_ = output_width; + last_output_height_ = output_height; } - output_shader_program->bind(); + output_shader_program_->bind(); // draw glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize); @@ -204,112 +200,112 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); glActiveTexture(pixel_accumulation_texture_unit); - framebuffer->bind_texture(); - framebuffer->draw((float)output_width / (float)output_height); + framebuffer_->bind_texture(); + framebuffer_->draw((float)output_width / (float)output_height); - _fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - _draw_mutex->unlock(); + fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + draw_mutex_.unlock(); } void OpenGLOutputBuilder::reset_all_OpenGL_state() { - composite_input_shader_program = nullptr; - composite_separation_filter_program = nullptr; - composite_y_filter_shader_program = nullptr; - composite_chrominance_filter_shader_program = nullptr; - rgb_input_shader_program = nullptr; - rgb_filter_shader_program = nullptr; - output_shader_program = nullptr; - framebuffer = nullptr; - _last_output_width = _last_output_height = 0; + composite_input_shader_program_ = nullptr; + composite_separation_filter_program_ = nullptr; + composite_y_filter_shader_program_ = nullptr; + composite_chrominance_filter_shader_program_ = nullptr; + rgb_input_shader_program_ = nullptr; + rgb_filter_shader_program_ = nullptr; + output_shader_program_ = nullptr; + framebuffer_ = nullptr; + last_output_width_ = last_output_height_ = 0; } void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources) { - _output_mutex->lock(); + output_mutex_.lock(); reset_all_OpenGL_state(); - _output_mutex->unlock(); + output_mutex_.unlock(); } void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader) { - _output_mutex->lock(); - _composite_shader = strdup(shader); + output_mutex_.lock(); + composite_shader_ = strdup(shader); reset_all_OpenGL_state(); - _output_mutex->unlock(); + output_mutex_.unlock(); } void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader) { - _output_mutex->lock(); - _rgb_shader = strdup(shader); + output_mutex_.lock(); + rgb_shader_ = strdup(shader); reset_all_OpenGL_state(); - _output_mutex->unlock(); + output_mutex_.unlock(); } #pragma mark - Program compilation void OpenGLOutputBuilder::prepare_composite_input_shaders() { - composite_input_shader_program = OpenGL::IntermediateShader::make_source_conversion_shader(_composite_shader, _rgb_shader); - composite_input_shader_program->set_source_texture_unit(source_data_texture_unit); - composite_input_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); + composite_input_shader_program_ = OpenGL::IntermediateShader::make_source_conversion_shader(composite_shader_, rgb_shader_); + composite_input_shader_program_->set_source_texture_unit(source_data_texture_unit); + composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); - composite_separation_filter_program = OpenGL::IntermediateShader::make_chroma_luma_separation_shader(); - composite_separation_filter_program->set_source_texture_unit(composite_texture_unit); - composite_separation_filter_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); + composite_separation_filter_program_ = OpenGL::IntermediateShader::make_chroma_luma_separation_shader(); + composite_separation_filter_program_->set_source_texture_unit(composite_texture_unit); + composite_separation_filter_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); - composite_y_filter_shader_program = OpenGL::IntermediateShader::make_luma_filter_shader(); - composite_y_filter_shader_program->set_source_texture_unit(separated_texture_unit); - composite_y_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); + composite_y_filter_shader_program_ = OpenGL::IntermediateShader::make_luma_filter_shader(); + composite_y_filter_shader_program_->set_source_texture_unit(separated_texture_unit); + composite_y_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); - composite_chrominance_filter_shader_program = OpenGL::IntermediateShader::make_chroma_filter_shader(); - composite_chrominance_filter_shader_program->set_source_texture_unit(filtered_y_texture_unit); - composite_chrominance_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); + composite_chrominance_filter_shader_program_ = OpenGL::IntermediateShader::make_chroma_filter_shader(); + composite_chrominance_filter_shader_program_->set_source_texture_unit(filtered_y_texture_unit); + composite_chrominance_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); } void OpenGLOutputBuilder::prepare_rgb_input_shaders() { - if(_rgb_shader) + if(rgb_shader_) { - rgb_input_shader_program = OpenGL::IntermediateShader::make_rgb_source_shader(_rgb_shader); - rgb_input_shader_program->set_source_texture_unit(source_data_texture_unit); - rgb_input_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); + rgb_input_shader_program_ = OpenGL::IntermediateShader::make_rgb_source_shader(rgb_shader_); + rgb_input_shader_program_->set_source_texture_unit(source_data_texture_unit); + rgb_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); - rgb_filter_shader_program = OpenGL::IntermediateShader::make_rgb_filter_shader(); - rgb_filter_shader_program->set_source_texture_unit(composite_texture_unit); - rgb_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); + rgb_filter_shader_program_ = OpenGL::IntermediateShader::make_rgb_filter_shader(); + rgb_filter_shader_program_->set_source_texture_unit(composite_texture_unit); + rgb_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); } } void OpenGLOutputBuilder::prepare_source_vertex_array() { - if(composite_input_shader_program) + if(composite_input_shader_program_) { - glBindVertexArray(source_vertex_array); + glBindVertexArray(source_vertex_array_); array_builder.bind_input(); - composite_input_shader_program->enable_vertex_attribute_with_pointer("inputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfInputStart, 1); - composite_input_shader_program->enable_vertex_attribute_with_pointer("outputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfOutputStart, 1); - composite_input_shader_program->enable_vertex_attribute_with_pointer("ends", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfEnds, 1); - composite_input_shader_program->enable_vertex_attribute_with_pointer("phaseTimeAndAmplitude", 3, GL_UNSIGNED_BYTE, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfPhaseTimeAndAmplitude, 1); + composite_input_shader_program_->enable_vertex_attribute_with_pointer("inputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfInputStart, 1); + composite_input_shader_program_->enable_vertex_attribute_with_pointer("outputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfOutputStart, 1); + composite_input_shader_program_->enable_vertex_attribute_with_pointer("ends", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfEnds, 1); + composite_input_shader_program_->enable_vertex_attribute_with_pointer("phaseTimeAndAmplitude", 3, GL_UNSIGNED_BYTE, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfPhaseTimeAndAmplitude, 1); } } void OpenGLOutputBuilder::prepare_output_shader() { - output_shader_program = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false); - output_shader_program->set_source_texture_unit(filtered_texture_unit); + output_shader_program_ = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false); + output_shader_program_->set_source_texture_unit(filtered_texture_unit); } void OpenGLOutputBuilder::prepare_output_vertex_array() { - if(output_shader_program) + if(output_shader_program_) { - glBindVertexArray(output_vertex_array); + glBindVertexArray(output_vertex_array_); array_builder.bind_output(); - output_shader_program->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1); - output_shader_program->enable_vertex_attribute_with_pointer("vertical", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfVertical, 1); + output_shader_program_->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1); + output_shader_program_->enable_vertex_attribute_with_pointer("vertical", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfVertical, 1); } } @@ -317,27 +313,27 @@ void OpenGLOutputBuilder::prepare_output_vertex_array() void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) { - if(_output_device != output_device) + if(output_device_ != output_device) { - _output_device = output_device; - _composite_src_output_y = 0; - _last_output_width = 0; - _last_output_height = 0; + output_device_ = output_device; + composite_src_output_y_ = 0; + last_output_width_ = 0; + last_output_height_ = 0; } } void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) { - _output_mutex->lock(); - _input_frequency = input_frequency; - _cycles_per_line = cycles_per_line; - _height_of_display = height_of_display; - _horizontal_scan_period = horizontal_scan_period; - _vertical_scan_period = vertical_scan_period; - _vertical_period_divider = vertical_period_divider; + output_mutex_.lock(); + input_frequency_ = input_frequency; + cycles_per_line_ = cycles_per_line; + height_of_display_ = height_of_display; + horizontal_scan_period_ = horizontal_scan_period; + vertical_scan_period_ = vertical_scan_period; + vertical_period_divider_ = vertical_period_divider; set_timing_uniforms(); - _output_mutex->unlock(); + output_mutex_.unlock(); } #pragma mark - Internal Configuration @@ -352,7 +348,7 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() GLfloat *fromRGB, *toRGB; - switch(_colour_space) + switch(colour_space_) { case ColourSpace::YIQ: fromRGB = rgbToYIQ; @@ -365,31 +361,31 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() break; } - if(composite_input_shader_program) composite_input_shader_program->set_colour_conversion_matrices(fromRGB, toRGB); - if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_colour_conversion_matrices(fromRGB, toRGB); + if(composite_input_shader_program_) composite_input_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB); + if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB); } void OpenGLOutputBuilder::set_timing_uniforms() { OpenGL::IntermediateShader *intermediate_shaders[] = { - composite_input_shader_program.get(), - composite_separation_filter_program.get(), - composite_y_filter_shader_program.get(), - composite_chrominance_filter_shader_program.get() + composite_input_shader_program_.get(), + composite_separation_filter_program_.get(), + composite_y_filter_shader_program_.get(), + composite_chrominance_filter_shader_program_.get() }; bool extends = false; - float phaseCyclesPerTick = (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line); + float phaseCyclesPerTick = (float)colour_cycle_numerator_ / (float)(colour_cycle_denominator_ * cycles_per_line_); for(int c = 0; c < 3; c++) { if(intermediate_shaders[c]) intermediate_shaders[c]->set_phase_cycles_per_sample(phaseCyclesPerTick, extends); extends = true; } - if(output_shader_program) output_shader_program->set_timing(_height_of_display, _cycles_per_line, _horizontal_scan_period, _vertical_scan_period, _vertical_period_divider); + if(output_shader_program_) output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_); - float colour_subcarrier_frequency = (float)_colour_cycle_numerator / (float)_colour_cycle_denominator; - if(composite_separation_filter_program) composite_separation_filter_program->set_separation_frequency(_cycles_per_line, colour_subcarrier_frequency); - if(composite_y_filter_shader_program) composite_y_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.66f); - if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.5f); - if(rgb_filter_shader_program) rgb_filter_shader_program->set_filter_coefficients(_cycles_per_line, (float)_input_frequency * 0.5f); + float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_; + if(composite_separation_filter_program_) composite_separation_filter_program_->set_separation_frequency(cycles_per_line_, colour_subcarrier_frequency); + if(composite_y_filter_shader_program_) composite_y_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.66f); + if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.5f); + if(rgb_filter_shader_program_) rgb_filter_shader_program_->set_filter_coefficients(cycles_per_line_, (float)input_frequency_ * 0.5f); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index a97d5b055..d8a921b92 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -30,25 +30,25 @@ namespace CRT { class OpenGLOutputBuilder { private: // colour information - ColourSpace _colour_space; - unsigned int _colour_cycle_numerator; - unsigned int _colour_cycle_denominator; - OutputDevice _output_device; + ColourSpace colour_space_; + unsigned int colour_cycle_numerator_; + unsigned int colour_cycle_denominator_; + OutputDevice output_device_; // timing information to allow reasoning about input information - unsigned int _input_frequency; - unsigned int _cycles_per_line; - unsigned int _height_of_display; - unsigned int _horizontal_scan_period; - unsigned int _vertical_scan_period; - unsigned int _vertical_period_divider; + unsigned int input_frequency_; + unsigned int cycles_per_line_; + unsigned int height_of_display_; + unsigned int horizontal_scan_period_; + unsigned int vertical_scan_period_; + unsigned int vertical_period_divider_; // The user-supplied visible area - Rect _visible_area; + Rect visible_area_; // Other things the caller may have provided. - char *_composite_shader; - char *_rgb_shader; + char *composite_shader_; + char *rgb_shader_; // Methods used by the OpenGL code void prepare_output_shader(); @@ -59,31 +59,27 @@ class OpenGLOutputBuilder { void prepare_source_vertex_array(); // the run and input data buffers - std::unique_ptr _output_mutex; - std::unique_ptr _draw_mutex; + std::mutex output_mutex_; + std::mutex draw_mutex_; // transient buffers indicating composite data not yet decoded - GLsizei _composite_src_output_y; + GLsizei composite_src_output_y_; - std::unique_ptr output_shader_program; - std::unique_ptr composite_input_shader_program, composite_separation_filter_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program; - std::unique_ptr rgb_input_shader_program, rgb_filter_shader_program; + std::unique_ptr output_shader_program_; + std::unique_ptr composite_input_shader_program_, composite_separation_filter_program_, composite_y_filter_shader_program_, composite_chrominance_filter_shader_program_; + std::unique_ptr rgb_input_shader_program_, rgb_filter_shader_program_; - std::unique_ptr compositeTexture; // receives raw composite levels - std::unique_ptr separatedTexture; // receives unfiltered Y in the R channel plus unfiltered but demodulated chrominance in G and B - std::unique_ptr filteredYTexture; // receives filtered Y in the R channel plus unfiltered chrominance in G and B - std::unique_ptr filteredTexture; // receives filtered YIQ or YUV + OpenGL::TextureTarget composite_texture_; // receives raw composite levels + OpenGL::TextureTarget separated_texture_; // receives unfiltered Y in the R channel plus unfiltered but demodulated chrominance in G and B + OpenGL::TextureTarget filtered_y_texture_; // receives filtered Y in the R channel plus unfiltered chrominance in G and B + OpenGL::TextureTarget filtered_texture_; // receives filtered YIQ or YUV - std::unique_ptr framebuffer; // the current pixel output + std::unique_ptr framebuffer_; // the current pixel output - GLuint output_vertex_array; - GLuint source_vertex_array; + GLuint output_vertex_array_; + GLuint source_vertex_array_; - unsigned int _last_output_width, _last_output_height; - - GLuint shadowMaskTextureName; - - GLuint defaultFramebuffer; + unsigned int last_output_width_, last_output_height_; void set_timing_uniforms(); void set_colour_space_uniforms(); @@ -91,6 +87,8 @@ class OpenGLOutputBuilder { void establish_OpenGL_state(); void reset_all_OpenGL_state(); + GLsync fence_; + public: TextureBuilder texture_builder; ArrayBuilder array_builder; @@ -100,48 +98,48 @@ class OpenGLOutputBuilder { inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) { - _output_mutex->lock(); - _colour_space = colour_space; - _colour_cycle_numerator = colour_cycle_numerator; - _colour_cycle_denominator = colour_cycle_denominator; + output_mutex_.lock(); + colour_space_ = colour_space; + colour_cycle_numerator_ = colour_cycle_numerator; + colour_cycle_denominator_ = colour_cycle_denominator; set_colour_space_uniforms(); - _output_mutex->unlock(); + output_mutex_.unlock(); } inline void set_visible_area(Rect visible_area) { - _visible_area = visible_area; + visible_area_ = visible_area; } inline void lock_output() { - _output_mutex->lock(); + output_mutex_.lock(); } inline void unlock_output() { - _output_mutex->unlock(); + output_mutex_.unlock(); } inline OutputDevice get_output_device() { - return _output_device; + return output_device_; } inline uint16_t get_composite_output_y() { - return (uint16_t)_composite_src_output_y; + return (uint16_t)composite_src_output_y_; } inline bool composite_output_buffer_is_full() { - return _composite_src_output_y == IntermediateBufferHeight; + return composite_src_output_y_ == IntermediateBufferHeight; } inline void increment_composite_output_y() { if(!composite_output_buffer_is_full()) - _composite_src_output_y++; + composite_src_output_y_++; } void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty); @@ -150,8 +148,6 @@ class OpenGLOutputBuilder { void set_rgb_sampling_function(const char *shader); void set_output_device(OutputDevice output_device); void set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider); - - GLsync _fence; }; }