diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 40047e56f..1b6c32084 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -45,7 +45,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di 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)); - _openGL_output_builder->set_timing(_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, _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) @@ -133,7 +133,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi uint8_t *next_run = nullptr; if(is_output_segment) { - next_run = (_openGL_output_builder->get_output_device() == Monitor) ? _openGL_output_builder->get_next_output_run() : _openGL_output_builder->get_next_source_run(); + next_run = _openGL_output_builder->get_next_source_run(); } // Vertex output is arranged for triangle strips, as: @@ -143,30 +143,15 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // [0/1] 3 if(next_run) { - if(_openGL_output_builder->get_output_device() == Monitor) - { - // set the type, initial raster position and type of this run - output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = tex_x; - - // these things are constants across the line so just throw them out now - output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = output_tex_y(3) = output_tex_y(4) = output_tex_y(5) = tex_y; - output_lateral(0) = output_lateral(1) = output_lateral(3) = 0; - output_lateral(2) = output_lateral(4) = output_lateral(5) = 1; - } - else - { - source_input_position_x(0) = tex_x; - source_input_position_y(0) = source_input_position_y(1) = tex_y; - source_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - source_output_position_y(0) = source_output_position_y(1) = _openGL_output_builder->get_composite_output_y(); - source_phase(0) = source_phase(1) = _colour_burst_phase; - source_amplitude(0) = source_amplitude(1) = _colour_burst_amplitude; - source_phase_time(0) = source_phase_time(1) = _colour_burst_time; - source_offset(0) = 0; - source_offset(1) = 255; - } + source_input_position_x(0) = tex_x; + source_input_position_y(0) = source_input_position_y(1) = tex_y; + source_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + source_output_position_y(0) = source_output_position_y(1) = _openGL_output_builder->get_composite_output_y(); + source_phase(0) = source_phase(1) = _colour_burst_phase; + source_amplitude(0) = source_amplitude(1) = _colour_burst_amplitude; + source_phase_time(0) = source_phase_time(1) = _colour_burst_time; + source_offset(0) = 0; + source_offset(1) = 255; } // decrement the number of cycles left to run for and increment the @@ -188,54 +173,39 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // 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(_openGL_output_builder->get_output_device() == Monitor) - { - // store the final raster position - output_position_x(3) = output_position_x(4) = output_position_x(5) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - output_position_y(3) = output_position_y(4) = output_position_y(5) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_tex_x(3) = output_tex_x(4) = output_tex_x(5) = tex_x; + source_input_position_x(1) = tex_x; + source_output_position_x(1) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - _openGL_output_builder->complete_output_run(6); - } - else - { - source_input_position_x(1) = tex_x; - source_output_position_x(1) = (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 - if(_openGL_output_builder->get_output_device() == Television) + Flywheel::SyncEvent honoured_event = Flywheel::SyncEvent::None; + 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()); + + if(needs_endpoint) { - Flywheel::SyncEvent honoured_event = Flywheel::SyncEvent::None; - 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()); + uint8_t *next_run = _openGL_output_builder->get_next_output_run(); - if(needs_endpoint) - { - uint8_t *next_run = _openGL_output_builder->get_next_output_run(); + output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); + output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = _openGL_output_builder->get_composite_output_y(); + output_lateral(0) = 0; + output_lateral(1) = _is_writing_composite_run ? 1 : 0; + output_lateral(2) = 1; - output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = _openGL_output_builder->get_composite_output_y(); - output_lateral(0) = 0; - output_lateral(1) = _is_writing_composite_run ? 1 : 0; - output_lateral(2) = 1; + _openGL_output_builder->complete_output_run(3); + _is_writing_composite_run ^= true; + } - _openGL_output_builder->complete_output_run(3); - _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(); - } + if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) + { + _openGL_output_builder->increment_composite_output_y(); } // if this is vertical retrace then adcance a field diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 1616a4d8b..79f8b3916 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -167,13 +167,13 @@ OpenGLOutputBuilder::~OpenGLOutputBuilder() void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { // establish essentials - if(!composite_input_shader_program && !rgb_shader_program) + if(!output_shader_program) { - prepare_composite_input_shader(); + prepare_composite_input_shaders(); + prepare_rgb_input_shaders(); prepare_source_vertex_array(); - prepare_composite_output_shader(); - prepare_rgb_output_shader(); + prepare_output_shader(); prepare_output_vertex_array(); set_timing_uniforms(); @@ -241,80 +241,122 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; } - // for television, update intermediate buffers and then draw; for a monitor, just draw - if(_output_device == Television || !rgb_shader_program) + struct RenderStage { + OpenGL::TextureTarget *const target; + OpenGL::Shader *const shader; + float clear_colour[3]; + }; + + RenderStage composite_render_stages[] = { - // decide how much to draw - if(_drawn_source_buffer_data_pointer != _source_buffer_data_pointer) + {compositeTexture.get(), composite_input_shader_program.get(), {0.0, 0.0, 0.0}}, + {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}}, + {nullptr} + }; + + 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}}, + {nullptr} + }; + + 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(_drawn_source_buffer_data_pointer != _source_buffer_data_pointer) + { + // determine how many lines are newly reclaimed; they'll need to be cleared + GLsizei clearing_zones[4], drawing_zones[4]; + int number_of_clearing_zones = getCircularRanges(_cleared_composite_output_y+1, _composite_src_output_y+1, IntermediateBufferHeight, 1, clearing_zones); + int number_of_drawing_zones = getCircularRanges(_drawn_source_buffer_data_pointer, _source_buffer_data_pointer, SourceVertexBufferDataSize, 2*SourceVertexSize, drawing_zones); + + _composite_src_output_y %= IntermediateBufferHeight; + _cleared_composite_output_y = _composite_src_output_y; + _source_buffer_data_pointer %= SourceVertexBufferDataSize; + _drawn_source_buffer_data_pointer = _source_buffer_data_pointer; + + // all drawing will be from the source vertex array and without blending + glBindVertexArray(source_vertex_array); + glDisable(GL_BLEND); + + // flush the source data + glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); + for(int c = 0; c < number_of_drawing_zones; c++) { - // determine how many lines are newly reclaimed; they'll need to be cleared - GLsizei clearing_zones[4], drawing_zones[4]; - int number_of_clearing_zones = getCircularRanges(_cleared_composite_output_y+1, _composite_src_output_y+1, IntermediateBufferHeight, 1, clearing_zones); - int number_of_drawing_zones = getCircularRanges(_drawn_source_buffer_data_pointer, _source_buffer_data_pointer, SourceVertexBufferDataSize, 2*SourceVertexSize, drawing_zones); - - _composite_src_output_y %= IntermediateBufferHeight; - _cleared_composite_output_y = _composite_src_output_y; - _source_buffer_data_pointer %= SourceVertexBufferDataSize; - _drawn_source_buffer_data_pointer = _source_buffer_data_pointer; - - // all drawing will be from the source vertex array and without blending - glBindVertexArray(source_vertex_array); - glDisable(GL_BLEND); - - // flush the source data - glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); - for(int c = 0; c < number_of_drawing_zones; c++) - { - glFlushMappedBufferRange(GL_ARRAY_BUFFER, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); - } - - OpenGL::TextureTarget *targets[] = { - compositeTexture.get(), - filteredYTexture.get(), - filteredTexture.get() - }; - OpenGL::Shader *shaders[] = { - composite_input_shader_program.get(), - composite_y_filter_shader_program.get(), - composite_chrominance_filter_shader_program.get() - }; - float clear_colours[][3] = { - {0.0, 0.0, 0.0}, - {0.0, 0.5, 0.5}, - {0.0, 0.0, 0.0} - }; - for(int stage = 0; stage < 3; stage++) - { - // switch to the initial texture - targets[stage]->bind_framebuffer(); - shaders[stage]->bind(); - - // clear as desired - if(number_of_clearing_zones) - { - glEnable(GL_SCISSOR_TEST); - glClearColor(clear_colours[stage][0], clear_colours[stage][1], clear_colours[stage][2], 1.0); - for(int c = 0; c < number_of_clearing_zones; c++) - { - glScissor(0, clearing_zones[c*2], IntermediateBufferWidth, clearing_zones[c*2 + 1]); - glClear(GL_COLOR_BUFFER_BIT); - } - glDisable(GL_SCISSOR_TEST); - } - - // draw as desired - for(int c = 0; c < number_of_drawing_zones; c++) - { - glDrawArrays(GL_LINES, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); - } - } + glFlushMappedBufferRange(GL_ARRAY_BUFFER, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); } - // transfer to screen - perform_output_stage(output_width, output_height, composite_output_shader_program.get()); + while(active_pipeline->target) + { + // switch to the initial texture + active_pipeline->target->bind_framebuffer(); + 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*2], IntermediateBufferWidth, clearing_zones[c*2 + 1]); + glClear(GL_COLOR_BUFFER_BIT); + } + glDisable(GL_SCISSOR_TEST); + } + + // draw as desired + for(int c = 0; c < number_of_drawing_zones; c++) + { + glDrawArrays(GL_LINES, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); + } + + active_pipeline++; + } + } + + // transfer to framebuffer + framebuffer->bind_framebuffer(); + + // draw all pending lines + GLsizei drawing_zones[4]; + int number_of_drawing_zones = getCircularRanges(_drawn_output_buffer_data_pointer, _output_buffer_data_pointer, OutputVertexBufferDataSize, 6*OutputVertexSize, drawing_zones); + + // flush the buffer data + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); + for(int c = 0; c < number_of_drawing_zones; c++) + { + glFlushMappedBufferRange(GL_ARRAY_BUFFER, drawing_zones[c*2] / OutputVertexSize, drawing_zones[c*2 + 1] / OutputVertexSize); + } + + _output_buffer_data_pointer %= SourceVertexBufferDataSize; + _output_buffer_data_pointer -= (_output_buffer_data_pointer%(6*OutputVertexSize)); + _drawn_output_buffer_data_pointer = _output_buffer_data_pointer; + + if(number_of_drawing_zones > 0) + { + 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) + 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->bind(); + + // draw + for(int c = 0; c < number_of_drawing_zones; c++) + { + glDrawArrays(GL_TRIANGLE_STRIP, drawing_zones[c*2] / OutputVertexSize, drawing_zones[c*2 + 1] / OutputVertexSize); + } } - else - perform_output_stage(output_width, output_height, rgb_shader_program.get()); // copy framebuffer to the intended place glDisable(GL_BLEND); @@ -339,46 +381,6 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign { if(shader) { - // bind the target - framebuffer->bind_framebuffer(); - - // draw all pending lines - GLsizei drawing_zones[4]; - int number_of_drawing_zones = getCircularRanges(_drawn_output_buffer_data_pointer, _output_buffer_data_pointer, OutputVertexBufferDataSize, 6*OutputVertexSize, drawing_zones); - - // flush the buffer data - glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); - for(int c = 0; c < number_of_drawing_zones; c++) - { - glFlushMappedBufferRange(GL_ARRAY_BUFFER, drawing_zones[c*2] / OutputVertexSize, drawing_zones[c*2 + 1] / OutputVertexSize); - } - - _output_buffer_data_pointer %= SourceVertexBufferDataSize; - _output_buffer_data_pointer -= (_output_buffer_data_pointer%(6*OutputVertexSize)); - _drawn_output_buffer_data_pointer = _output_buffer_data_pointer; - - if(number_of_drawing_zones > 0) - { - 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) - if(_last_output_width != output_width || _last_output_height != output_height) - { - shader->set_output_size(output_width, output_height, _visible_area); - _last_output_width = output_width; - _last_output_height = output_height; - } - shader->bind(); - - // draw - for(int c = 0; c < number_of_drawing_zones; c++) - { - glDrawArrays(GL_TRIANGLE_STRIP, drawing_zones[c*2] / OutputVertexSize, drawing_zones[c*2 + 1] / OutputVertexSize); - } - } } } @@ -398,7 +400,7 @@ void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader) #pragma mark - Program compilation -void OpenGLOutputBuilder::prepare_composite_input_shader() +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); @@ -413,6 +415,20 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() composite_chrominance_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); } +void OpenGLOutputBuilder::prepare_rgb_input_shaders() +{ + 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_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) @@ -438,35 +454,19 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() } } -void OpenGLOutputBuilder::prepare_rgb_output_shader() +void OpenGLOutputBuilder::prepare_output_shader() { - const char *rgb_shader = _rgb_shader; - if(!_rgb_shader) - { - rgb_shader = - "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" - "{" - "return texture(sampler, coordinate).rgb / vec3(255.0);" - "}"; - } - - rgb_shader_program = OpenGL::OutputShader::make_shader(rgb_shader, "rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying)", true); - rgb_shader_program->set_source_texture_unit(source_data_texture_unit); -} - -void OpenGLOutputBuilder::prepare_composite_output_shader() -{ - composite_output_shader_program = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false); - composite_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(rgb_shader_program) + if(output_shader_program) { - GLint positionAttribute = rgb_shader_program->get_attrib_location("position"); - GLint textureCoordinatesAttribute = rgb_shader_program->get_attrib_location("srcCoordinates"); - GLint lateralAttribute = rgb_shader_program->get_attrib_location("lateral"); + GLint positionAttribute = output_shader_program->get_attrib_location("position"); + GLint textureCoordinatesAttribute = output_shader_program->get_attrib_location("srcCoordinates"); + GLint lateralAttribute = output_shader_program->get_attrib_location("lateral"); glBindVertexArray(output_vertex_array); @@ -495,8 +495,9 @@ void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) } } -void OpenGLOutputBuilder::set_timing(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) +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) { + _input_frequency = input_frequency; _cycles_per_line = cycles_per_line; _height_of_display = height_of_display; _horizontal_scan_period = horizontal_scan_period; @@ -540,6 +541,7 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() void OpenGLOutputBuilder::set_timing_uniforms() { _output_mutex->lock(); + OpenGL::IntermediateShader *intermediate_shaders[] = { composite_input_shader_program.get(), composite_y_filter_shader_program.get(), @@ -553,17 +555,12 @@ void OpenGLOutputBuilder::set_timing_uniforms() extends = true; } - OpenGL::OutputShader *output_shaders[] = { - rgb_shader_program.get(), - composite_output_shader_program.get() - }; - for(int c = 0; c < 2; c++) - { - if(output_shaders[c]) output_shaders[c]->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_y_filter_shader_program) composite_y_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency - 90.0f); - if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.5f); + if(composite_y_filter_shader_program) composite_y_filter_shader_program->set_separation_frequency(_cycles_per_line, colour_subcarrier_frequency); + 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); + _output_mutex->unlock(); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index b3ae9939b..7014d9c07 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -33,6 +33,7 @@ class OpenGLOutputBuilder { 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; @@ -47,9 +48,9 @@ class OpenGLOutputBuilder { char *_rgb_shader; // Methods used by the OpenGL code - void prepare_rgb_output_shader(); - void prepare_composite_output_shader(); - void prepare_composite_input_shader(); + void prepare_output_shader(); + void prepare_rgb_input_shaders(); + void prepare_composite_input_shaders(); void prepare_output_vertex_array(); void prepare_source_vertex_array(); @@ -61,8 +62,9 @@ class OpenGLOutputBuilder { // transient buffers indicating composite data not yet decoded uint16_t _composite_src_output_y, _cleared_composite_output_y; - std::unique_ptr rgb_shader_program, composite_output_shader_program; + std::unique_ptr output_shader_program; std::unique_ptr composite_input_shader_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program; + std::unique_ptr rgb_input_shader_program, rgb_filter_shader_program; GLuint output_array_buffer, output_vertex_array; GLuint source_array_buffer, source_vertex_array; @@ -168,7 +170,7 @@ class OpenGLOutputBuilder { void set_composite_sampling_function(const char *shader); void set_rgb_sampling_function(const char *shader); void set_output_device(OutputDevice output_device); - void set_timing(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); + 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); uint8_t *_input_texture_data; GLuint _input_texture_array; diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp index 51deed3e1..faaa8a37c 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp @@ -141,6 +141,34 @@ std::unique_ptr IntermediateShader::make_source_conversion_s return shader; } +std::unique_ptr IntermediateShader::make_rgb_source_shader(const char *rgb_shader) +{ + char *fragment_shader; + asprintf(&fragment_shader, + "#version 150\n" + + "in vec2 inputPositionsVarying[11];" + "in vec2 iInputPositionVarying;" + "in vec2 phaseAndAmplitudeVarying;" + + "out vec3 fragColour;" + + "uniform usampler2D texID;" + + "\n%s\n" + + "void main(void)" + "{" + "fragColour = rgb_sample(texID, inputPositionsVarying[5], iInputPositionVarying);" + "}" + , rgb_shader); + + std::unique_ptr shader = make_shader(fragment_shader, true, true); + free(fragment_shader); + + return shader; +} + std::unique_ptr IntermediateShader::make_chroma_luma_separation_shader() { return make_shader( @@ -250,6 +278,70 @@ std::unique_ptr IntermediateShader::make_chroma_filter_shade "}", false, false); } +std::unique_ptr IntermediateShader::make_rgb_filter_shader() +{ + return make_shader( + "#version 150\n" + + "in vec2 inputPositionsVarying[11];" + "uniform vec4 weights[3];" + + "out vec3 fragColour;" + + "uniform sampler2D texID;" + + "void main(void)" + "{" + "vec3 samples[] = vec3[](" + "texture(texID, inputPositionsVarying[0]).rgb," + "texture(texID, inputPositionsVarying[1]).rgb," + "texture(texID, inputPositionsVarying[2]).rgb," + "texture(texID, inputPositionsVarying[3]).rgb," + "texture(texID, inputPositionsVarying[4]).rgb," + "texture(texID, inputPositionsVarying[5]).rgb," + "texture(texID, inputPositionsVarying[6]).rgb," + "texture(texID, inputPositionsVarying[7]).rgb," + "texture(texID, inputPositionsVarying[8]).rgb," + "texture(texID, inputPositionsVarying[9]).rgb," + "texture(texID, inputPositionsVarying[10]).rgb" + ");" + + "vec4 channel1[] = vec4[](" + "vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r)," + "vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r)," + "vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)" + ");" + "vec4 channel2[] = vec4[](" + "vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g)," + "vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g)," + "vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)" + ");" + "vec4 channel3[] = vec4[](" + "vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b)," + "vec4(samples[4].b, samples[5].b, samples[6].b, samples[7].b)," + "vec4(samples[8].b, samples[9].b, samples[10].b, 0.0)" + ");" + + "fragColour = vec3(" + "dot(vec3(" + "dot(channel1[0], weights[0])," + "dot(channel1[1], weights[1])," + "dot(channel1[2], weights[2])" + "), vec3(1.0))," + "dot(vec3(" + "dot(channel2[0], weights[0])," + "dot(channel2[1], weights[1])," + "dot(channel2[2], weights[2])" + "), vec3(1.0))," + "dot(vec3(" + "dot(channel3[0], weights[0])," + "dot(channel3[1], weights[1])," + "dot(channel3[2], weights[2])" + "), vec3(1.0))" + ");" + "}", false, false); +} + void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height) { bind(); @@ -319,6 +411,12 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto glUniform1fv(offsetsUniform, 5, offsets); } +void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency) +{ + // TODO: apply separately-formed filters for luminance and chrominance + set_filter_coefficients(sampling_rate, colour_burst_frequency - 50.0f); +} + void IntermediateShader::set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle) { bind(); diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp index 8b75a51df..db4f76199 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp @@ -27,6 +27,12 @@ public: */ static std::unique_ptr make_source_conversion_shader(const char *composite_shader, const char *rgb_shader); + /*! + Constructs and returns an intermediate shader that will take runs from the inputPositions, + converting them to RGB values using @c rgb_shader. + */ + static std::unique_ptr make_rgb_source_shader(const char *rgb_shader); + /*! Constructs and returns an intermediate shader that will read composite samples from the R channel, filter then to obtain luminance, stored to R, and to separate out unfiltered chrominance, store to G and B. @@ -38,6 +44,11 @@ public: */ static std::unique_ptr make_chroma_filter_shader(); + /*! + Constructs and returns an intermediate shader that will filter R, G and B. + */ + static std::unique_ptr make_rgb_filter_shader(); + /*! Binds this shader and configures it for output to an area of `output_width` and `output_height` pixels. */ @@ -53,6 +64,12 @@ public: */ void set_filter_coefficients(float sampling_rate, float cutoff_frequency); + /*! + Binds this shader and configures filtering to separate luminance and chrominance based on a colour + subcarrier of the given frequency. + */ + void set_separation_frequency(float sampling_rate, float colour_burst_frequency); + /*! Binds this shader and sets the number of colour phase cycles per sample, indicating whether output geometry should be extended so that a complete colour cycle is included at both the beginning and end.