1
0
mirror of https://github.com/TomHarte/CLK.git synced 2026-03-13 02:42:08 +00:00
Files
CLK/Outputs/OpenGL/ScanTarget.cpp
2026-02-03 12:50:04 -05:00

716 lines
25 KiB
C++

//
// ScanTarget.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/11/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "ScanTarget.hpp"
#include "OpenGL.hpp"
#include "Outputs/ScanTargets/FilterGenerator.hpp"
#include "Outputs/OpenGL/Shaders/CompositionShader.hpp"
#include "Outputs/OpenGL/Shaders/CopyShader.hpp"
#include <algorithm>
#include <cassert>
#include <cstring>
#include <limits>
using namespace Outputs::Display::OpenGL;
#ifndef NDEBUG
//#define LOG_LINES
//#define LOG_SCANS
#endif
namespace {
/// The texture unit from which to source input data.
constexpr GLenum SourceDataTextureUnit = GL_TEXTURE0;
//
// Old Pipeline.
//
/// The texture unit which contains raw line-by-line composite, S-Video or RGB data.
constexpr GLenum UnprocessedLineBufferTextureUnit = GL_TEXTURE1;
/// The texture unit that contains a pre-lowpass-filtered but fixed-resolution version of the chroma signal;
/// this is used when processing composite video only, and for chroma information only. Luminance is calculated
/// at the fidelity permitted by the output target, but my efforts to separate, demodulate and filter
/// chrominance during output without either massively sampling or else incurring significant high-frequency
/// noise that sampling reduces into a Moire, have proven to be unsuccessful for the time being.
constexpr GLenum QAMChromaTextureUnit = GL_TEXTURE2;
/// The texture unit that contains the current display.
constexpr GLenum AccumulationTextureUnit = GL_TEXTURE3;
//
// New pipeline.
//
/// Contains the initial composition of scans into lines.
constexpr GLenum CompositionTextureUnit = GL_TEXTURE4;
/// The texture unit that contains the current display.
constexpr GLenum OutputTextureUnit = GL_TEXTURE5;
using Logger = Log::Logger<Log::Source::OpenGL>;
constexpr GLint internalFormatForDepth(const std::size_t depth) {
switch(depth) {
default: return GL_FALSE;
case 1: return GL_R8UI;
case 2: return GL_RG8UI;
case 3: return GL_RGB8UI;
case 4: return GL_RGBA8UI;
}
}
constexpr GLenum formatForDepth(const std::size_t depth) {
switch(depth) {
default: return GL_FALSE;
case 1: return GL_RED_INTEGER;
case 2: return GL_RG_INTEGER;
case 3: return GL_RGB_INTEGER;
case 4: return GL_RGBA_INTEGER;
}
}
template <typename T> void allocate_buffer(
const T &array,
GLuint &buffer_name,
GLuint &vertex_array_name
) {
const auto buffer_size = array.size() * sizeof(array[0]);
test_gl([&]{ glGenBuffers(1, &buffer_name); });
test_gl([&]{ glBindBuffer(GL_ARRAY_BUFFER, buffer_name); });
test_gl([&]{ glBufferData(GL_ARRAY_BUFFER, GLsizeiptr(buffer_size), NULL, GL_STREAM_DRAW); });
test_gl([&]{ glGenVertexArrays(1, &vertex_array_name); });
test_gl([&]{ glBindVertexArray(vertex_array_name); });
test_gl([&]{ glBindBuffer(GL_ARRAY_BUFFER, buffer_name); });
}
}
ScanTarget::ScanTarget(const API api, const GLuint target_framebuffer, const float output_gamma) :
api_(api),
target_framebuffer_(target_framebuffer),
output_gamma_(output_gamma),
unprocessed_line_texture_(api, LineBufferWidth, LineBufferHeight, UnprocessedLineBufferTextureUnit, GL_NEAREST, false),
full_display_rectangle_(api, -1.0f, -1.0f, 2.0f, 2.0f),
scans_(scan_buffer_),
dirty_zones_(dirty_zones_buffer_) {
set_scan_buffer(scan_buffer_.data(), scan_buffer_.size());
set_line_buffer(line_buffer_.data(), line_metadata_buffer_.data(), line_buffer_.size());
// Allocate space for the scans and lines.
allocate_buffer(scan_buffer_, scan_buffer_name_, scan_vertex_array_);
allocate_buffer(line_buffer_, line_buffer_name_, line_vertex_array_);
// TODO: if this is OpenGL 4.4 or newer, use glBufferStorage rather than glBufferData
// and specify GL_MAP_PERSISTENT_BIT. Then map the buffer now, and let the client
// write straight into it.
test_gl([&]{ glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); });
test_gl([&]{ glBlendColor(0.4f, 0.4f, 0.4f, 1.0f); });
// Establish initial state for is_drawing_to_accumulation_buffer_.
is_drawing_to_accumulation_buffer_.clear();
// TEST CODE. NOCOMMIT.
// const auto buffer_width = FilterGenerator::SuggestedBufferWidth;
// const float sample_multiplier =
// FilterGenerator::suggested_sample_multiplier(227.5f, 1320);
//
// VertexArray va(scan_buffer_);
// for(auto &pair: {
// std::make_pair(InputDataType::Luminance1, DisplayType::CompositeColour),
// std::make_pair(InputDataType::Luminance8, DisplayType::CompositeColour),
// std::make_pair(InputDataType::PhaseLinkedLuminance8, DisplayType::CompositeColour),
//
// std::make_pair(InputDataType::Luminance8Phase8, DisplayType::SVideo),
// std::make_pair(InputDataType::Luminance8Phase8, DisplayType::CompositeColour),
//
// std::make_pair(InputDataType::Red1Green1Blue1, DisplayType::RGB),
// std::make_pair(InputDataType::Red1Green1Blue1, DisplayType::SVideo),
// std::make_pair(InputDataType::Red1Green1Blue1, DisplayType::CompositeColour),
//
// std::make_pair(InputDataType::Red2Green2Blue2, DisplayType::RGB),
// std::make_pair(InputDataType::Red2Green2Blue2, DisplayType::SVideo),
// std::make_pair(InputDataType::Red2Green2Blue2, DisplayType::CompositeColour),
//
// std::make_pair(InputDataType::Red4Green4Blue4, DisplayType::RGB),
// std::make_pair(InputDataType::Red4Green4Blue4, DisplayType::SVideo),
// std::make_pair(InputDataType::Red4Green4Blue4, DisplayType::CompositeColour),
//
// std::make_pair(InputDataType::Red8Green8Blue8, DisplayType::RGB),
// std::make_pair(InputDataType::Red8Green8Blue8, DisplayType::SVideo),
// std::make_pair(InputDataType::Red8Green8Blue8, DisplayType::CompositeColour),
// }) {
// OpenGL::composition_shader(
// api,
// pair.first,
// pair.second,
// ColourSpace::YIQ,
// sample_multiplier,
// BufferingScanTarget::WriteAreaWidth, BufferingScanTarget::WriteAreaHeight,
// buffer_width, 2048, // TODO: substitute real composition buffer sizes.
// va,
// GL_TEXTURE0
// ).bind();
// }
}
ScanTarget::~ScanTarget() {
perform([&] {
glDeleteBuffers(1, &scan_buffer_name_);
glDeleteVertexArrays(1, &scan_vertex_array_);
});
}
void ScanTarget::set_target_framebuffer(GLuint target_framebuffer) {
perform([&] {
target_framebuffer_ = target_framebuffer;
});
}
void ScanTarget::setup_pipeline() {
const auto modals = BufferingScanTarget::modals();
const auto data_type_size = Outputs::Display::size_for_data_type(modals.input_data_type);
// Possibly create a new source texture.
if(source_texture_.empty() || source_texture_.channels() != data_type_size) {
source_texture_ = Texture(
data_type_size,
SourceDataTextureUnit,
GL_NEAREST,
GL_NEAREST,
WriteAreaWidth,
WriteAreaHeight
);
}
// Resize the texture only if required.
const size_t required_size = WriteAreaWidth*WriteAreaHeight*data_type_size;
if(required_size != write_area_texture_.size()) {
write_area_texture_.resize(required_size);
set_write_area(write_area_texture_.data());
}
// Prepare to bind line shaders.
test_gl([&]{ glBindVertexArray(line_vertex_array_); });
test_gl([&]{ glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); });
// Destroy or create a QAM buffer and shader, if appropriate.
if(!existing_modals_ || existing_modals_->display_type != modals.display_type) {
const bool needs_qam_buffer =
modals.display_type == DisplayType::CompositeColour ||
modals.display_type == DisplayType::SVideo;
if(needs_qam_buffer) {
if(!qam_chroma_texture_) {
qam_chroma_texture_ = std::make_unique<TextureTarget>(
api_,
LineBufferWidth,
LineBufferHeight,
QAMChromaTextureUnit,
GL_NEAREST,
false
);
}
qam_separation_shader_ = qam_separation_shader();
enable_vertex_attributes(ShaderType::QAMSeparation, *qam_separation_shader_);
qam_separation_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0));
} else {
qam_chroma_texture_.reset();
qam_separation_shader_.reset();
}
// Establish an output shader.
output_shader_ = conversion_shader();
enable_vertex_attributes(ShaderType::Conversion, *output_shader_);
set_uniforms(ShaderType::Conversion, *output_shader_);
output_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0));
output_shader_->set_uniform("qamTextureName", GLint(QAMChromaTextureUnit - GL_TEXTURE0));
}
if(qam_separation_shader_) {
set_uniforms(ShaderType::QAMSeparation, *qam_separation_shader_);
}
// Select whichever of a letterbox or pillarbox avoids cropping.
constexpr float output_ratio = 4.0f / 3.0f;
const float aspect_ratio_stretch = modals.aspect_ratio / output_ratio;
auto adjusted_rect = modals.visible_area;
const float letterbox_scale = adjusted_rect.size.height / (adjusted_rect.size.width * aspect_ratio_stretch);
if(letterbox_scale > 1.0f) {
adjusted_rect.scale(letterbox_scale, 1.0f);
} else {
adjusted_rect.scale(1.0f, 1.0f / letterbox_scale);
}
// Provide to shader.
output_shader_->set_uniform("origin", adjusted_rect.origin.x, adjusted_rect.origin.y);
output_shader_->set_uniform("size", 1.0f / adjusted_rect.size.width, 1.0f / adjusted_rect.size.height);
// Establish an input shader.
if(!existing_modals_ || existing_modals_->input_data_type != modals.input_data_type) {
input_shader_ = composition_shader();
test_gl([&]{ glBindVertexArray(scan_vertex_array_); });
test_gl([&]{ glBindBuffer(GL_ARRAY_BUFFER, scan_buffer_name_); });
enable_vertex_attributes(ShaderType::Composition, *input_shader_);
set_uniforms(ShaderType::Composition, *input_shader_);
input_shader_->set_uniform("textureName", GLint(SourceDataTextureUnit - GL_TEXTURE0));
}
//
// New pipeline starts here!
//
const auto buffer_width = FilterGenerator::SuggestedBufferWidth;
const auto subcarrier_frequency = [](const Modals &modals) {
return float(modals.colour_cycle_numerator) / float(modals.colour_cycle_denominator);
};
const float sample_multiplier =
FilterGenerator::suggested_sample_multiplier(
subcarrier_frequency(modals),
modals.cycles_per_line
);
if(copy_shader_.empty()) {
copy_shader_ = copy_shader(api_, OutputTextureUnit, {}, {});
}
if(
!existing_modals_ ||
existing_modals_->input_data_type != modals.input_data_type ||
existing_modals_->display_type != modals.display_type ||
existing_modals_->composite_colour_space != modals.composite_colour_space ||
subcarrier_frequency(*existing_modals_) != subcarrier_frequency(modals)
) {
composition_shader_ = OpenGL::composition_shader(
api_,
modals.input_data_type,
modals.display_type,
modals.composite_colour_space,
sample_multiplier,
2048, 2048,
buffer_width, 2048,
scans_,
GL_TEXTURE0
);
}
if(composition_buffer_.empty()) {
composition_buffer_ = TextureTarget(api_, buffer_width, 2048, CompositionTextureUnit, GL_NEAREST, false);
}
existing_modals_ = modals;
}
bool ScanTarget::is_soft_display_type() {
const auto display_type = modals().display_type;
return display_type == DisplayType::CompositeColour || display_type == DisplayType::CompositeMonochrome;
}
void ScanTarget::update(const int output_width, const int output_height) {
// If the GPU is still busy, don't wait; we'll catch it next time.
if(fence_ != nullptr) {
if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, 0) == GL_TIMEOUT_EXPIRED) {
display_metrics_.announce_draw_status(
lines_submitted_,
std::chrono::high_resolution_clock::now() - line_submission_begin_time_,
false);
return;
}
fence_ = nullptr;
}
// Update the display metrics.
display_metrics_.announce_draw_status(
lines_submitted_,
std::chrono::high_resolution_clock::now() - line_submission_begin_time_,
true);
// Make sure there's a buffer.
const auto output_buffer_width = output_width * 2;
const auto output_buffer_height = output_height * 2;
if(
output_buffer_.empty() ||
output_buffer_.width() != output_buffer_width ||
output_buffer_.height() != output_buffer_height
) {
output_buffer_ = TextureTarget(
api_,
output_buffer_width,
output_buffer_height,
OutputTextureUnit,
GL_NEAREST,
false // TODO: should probably be true, if I'm going to use stencil.
);
}
// Grab the new output list.
perform([&] {
const OutputArea area = get_output_area();
// Establish the pipeline if necessary.
const auto new_modals = BufferingScanTarget::new_modals();
const bool did_setup_pipeline = [&] {
if(bool(new_modals)) {
setup_pipeline();
return true;
}
return false;
} ();
// Determine the start time of this submission group and the number of lines it will contain.
line_submission_begin_time_ = std::chrono::high_resolution_clock::now();
lines_submitted_ = (area.end.line - area.start.line + line_buffer_.size()) % line_buffer_.size();
// Submit texture.
if(area.start.write_area_x != area.end.write_area_x || area.start.write_area_y != area.end.write_area_y) {
source_texture_.bind();
const auto submit = [&](const GLint y_begin, const GLint y_end) {
test_gl([&]{
glTexSubImage2D(
GL_TEXTURE_2D, 0,
0, y_begin,
WriteAreaWidth,
y_end - y_begin,
formatForDepth(write_area_data_size()),
GL_UNSIGNED_BYTE,
&write_area_texture_[size_t(y_begin * WriteAreaWidth) * source_texture_.channels()]
);
});
};
// Both of the following upload to area.end.write_area_y + 1 to include whatever line the write area
// is currently on. It may have partial source areas along it, despite being incomplete.
if(area.end.write_area_y >= area.start.write_area_y) {
// Submit the direct region from the submit pointer to the read pointer.
submit(area.start.write_area_y, area.end.write_area_y + 1);
} else {
// The circular buffer wrapped around; submit the data from the read pointer to the end of
// the buffer and from the start of the buffer to the submit pointer.
submit(area.start.write_area_y, WriteAreaHeight);
submit(0, area.end.write_area_y + 1);
}
}
// Submit scans; only the new ones need to be communicated.
if(area.end.scan != area.start.scan) {
const size_t new_scans = (area.end.scan - area.start.scan + scan_buffer_.size()) % scan_buffer_.size();
//
// OLD PIPELINE: submit new scans.
//
test_gl([&]{ glBindBuffer(GL_ARRAY_BUFFER, scan_buffer_name_); });
// Map only the required portion of the buffer.
const size_t new_scans_size = new_scans * sizeof(Scan);
const auto destination = static_cast<Scan *>(
glMapBufferRange(
GL_ARRAY_BUFFER,
0,
GLsizeiptr(new_scans_size),
GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT
)
);
test_gl_error();
// Copy as a single chunk if possible; otherwise copy in two parts.
if(area.start.scan < area.end.scan) {
std::copy_n(scan_buffer_.begin() + area.start.scan, new_scans, destination);
} else {
const size_t first_portion_count = scan_buffer_.size() - area.start.scan;
std::copy_n(scan_buffer_.begin() + area.start.scan, first_portion_count, destination);
std::copy_n(scan_buffer_.begin(), new_scans - first_portion_count, destination + first_portion_count);
}
// Flush and unmap the buffer.
test_gl([&]{ glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, GLsizeiptr(new_scans_size)); });
test_gl([&]{ glUnmapBuffer(GL_ARRAY_BUFFER); });
//
// OLD PIPELINE: draw new scans.
//
// Push new input to the unprocessed line buffer.
unprocessed_line_texture_.bind_framebuffer();
// Clear newly-touched lines; that is everything from (read+1) to submit.
const auto first_line_to_clear = GLsizei((area.start.line+1)%line_buffer_.size());
const auto final_line_to_clear = GLsizei(area.end.line);
if(first_line_to_clear != final_line_to_clear) {
test_gl([&]{ glEnable(GL_SCISSOR_TEST); });
// Determine the proper clear colour — this needs to be anything that describes black
// in the input colour encoding at use.
if(modals().input_data_type == InputDataType::Luminance8Phase8) {
// Supply both a zero luminance and a colour-subcarrier-disengaging phase.
test_gl([&]{ glClearColor(0.0f, 1.0f, 0.0f, 0.0f); });
} else {
test_gl([&]{ glClearColor(0.0f, 0.0f, 0.0f, 0.0f); });
}
if(first_line_to_clear < final_line_to_clear) {
test_gl([&]{ glScissor(GLint(0), GLint(first_line_to_clear), unprocessed_line_texture_.width(), final_line_to_clear - first_line_to_clear); });
test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT); });
} else {
test_gl([&]{ glScissor(GLint(0), GLint(0), unprocessed_line_texture_.width(), final_line_to_clear); });
test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT); });
test_gl([&]{ glScissor(GLint(0), GLint(first_line_to_clear), unprocessed_line_texture_.width(), unprocessed_line_texture_.height() - first_line_to_clear); });
test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT); });
}
test_gl([&]{ glDisable(GL_SCISSOR_TEST); });
}
// Apply new spans. They definitely always go to the first buffer.
test_gl([&]{ glBindVertexArray(scan_vertex_array_); });
input_shader_->bind();
test_gl([&]{ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(new_scans)); });
//
// NEW PIPELINE.
//
// Submit new scans.
// First implementation: put all new scans at the start of the buffer, for a simple
// glDrawArraysInstanced call below.
scans_.bind_buffer();
size_t buffer_destination = 0;
const auto submit = [&](const size_t begin, const size_t end) {
test_gl([&]{
glBufferSubData(
GL_ARRAY_BUFFER,
buffer_destination,
(end - begin) * sizeof(Scan),
&scan_buffer_[begin]
);
});
buffer_destination += (end - begin) * sizeof(Scan);
};
if(area.start.scan < area.end.scan) {
submit(area.start.scan, area.end.scan);
} else {
submit(area.start.scan, scan_buffer_.size());
submit(0, area.end.scan);
}
// Populate composition buffer.
composition_buffer_.bind_framebuffer();
scans_.bind();
composition_shader_.bind();
test_gl([&]{ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(new_scans)); });
}
// Logic for reducing resolution: start doing so if the metrics object reports that
// it's a good idea. Go up to a quarter of the requested resolution, subject to
// clamping at each stage. If the output resolution changes, or anything else about
// the output pipeline, just start trying the highest size again.
if(display_metrics_.should_lower_resolution() && is_soft_display_type()) {
resolution_reduction_level_ = std::min(resolution_reduction_level_+1, 4);
}
if(output_height_ != output_height || did_setup_pipeline) {
resolution_reduction_level_ = 1;
output_height_ = output_height;
}
// Ensure the accumulation buffer is properly sized, allowing for the metrics object's
// feelings about whether too high a resolution is being used.
const int framebuffer_height = std::max(output_height / resolution_reduction_level_, std::min(540, output_height));
const int proportional_width = (framebuffer_height * 4) / 3;
const bool did_create_accumulation_texture =
!accumulation_texture_ ||
(
accumulation_texture_->width() != proportional_width ||
accumulation_texture_->height() != framebuffer_height
);
// Work with the accumulation_buffer_ potentially starts from here onwards; set its flag.
while(is_drawing_to_accumulation_buffer_.test_and_set());
if(did_create_accumulation_texture) {
Logger::info().append("Changed output resolution to %d by %d", proportional_width, framebuffer_height);
display_metrics_.announce_did_resize();
auto new_framebuffer = std::make_unique<TextureTarget>(
api_,
GLsizei(proportional_width),
GLsizei(framebuffer_height),
AccumulationTextureUnit,
GL_NEAREST,
true
);
if(accumulation_texture_) {
new_framebuffer->bind_framebuffer();
test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); });
test_gl([&]{ glActiveTexture(AccumulationTextureUnit); });
accumulation_texture_->bind_texture();
accumulation_texture_->draw(4.0f / 3.0f);
test_gl([&]{ glClear(GL_STENCIL_BUFFER_BIT); });
new_framebuffer->bind_texture();
}
accumulation_texture_ = std::move(new_framebuffer);
// In the absence of a way to resize a stencil buffer, just mark
// what's currently present as invalid to avoid an improper clear
// for this frame.
stencil_is_valid_ = false;
}
if(did_setup_pipeline || did_create_accumulation_texture) {
set_sampling_window(proportional_width, framebuffer_height, *output_shader_);
}
// Figure out how many new lines are ready.
auto new_lines = (area.end.line - area.start.line + LineBufferHeight) % LineBufferHeight;
if(new_lines) {
// Prepare to output lines.
test_gl([&]{ glBindVertexArray(line_vertex_array_); });
// Bind the accumulation framebuffer, unless there's going to be QAM work first.
if(!qam_separation_shader_ || line_metadata_buffer_[area.start.line].is_first_in_frame) {
accumulation_texture_->bind_framebuffer();
output_shader_->bind();
// Enable blending and stenciling.
test_gl([&]{ glEnable(GL_BLEND); });
test_gl([&]{ glEnable(GL_STENCIL_TEST); });
}
// Set the proper stencil function regardless.
test_gl([&]{ glStencilFunc(GL_EQUAL, 0, GLuint(~0)); });
test_gl([&]{ glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); });
// Prepare to upload data that will consitute lines.
test_gl([&]{ glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); });
// Divide spans by which frame they're in.
auto start_line = area.start.line;
while(new_lines) {
uint16_t end_line = (start_line + 1) % LineBufferHeight;
// Find the limit of spans to draw in this cycle.
size_t lines = 1;
while(end_line != area.end.line && !line_metadata_buffer_[end_line].is_first_in_frame) {
end_line = (end_line + 1) % LineBufferHeight;
++lines;
}
// If this is start-of-frame, clear any untouched pixels and flush the stencil buffer
if(line_metadata_buffer_[start_line].is_first_in_frame) {
if(stencil_is_valid_ && line_metadata_buffer_[start_line].previous_frame_was_complete) {
full_display_rectangle_.draw(0.0f, 0.0f, 0.0f);
}
stencil_is_valid_ = true;
test_gl([&]{ glClear(GL_STENCIL_BUFFER_BIT); });
// Rebind the program for span output.
test_gl([&]{ glBindVertexArray(line_vertex_array_); });
if(!qam_separation_shader_) {
output_shader_->bind();
}
}
// Upload.
const auto buffer_size = lines * sizeof(Line);
if(!end_line || end_line > start_line) {
test_gl([&]{ glBufferSubData(GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size), &line_buffer_[start_line]); });
} else {
auto destination = static_cast<Line *>(
glMapBufferRange(
GL_ARRAY_BUFFER,
0,
GLsizeiptr(buffer_size),
GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT
)
);
assert(destination);
test_gl_error();
std::copy_n(line_buffer_.begin(), end_line, destination + line_buffer_.size() - start_line);
std::copy_n(line_buffer_.begin() + start_line, line_buffer_.size() - start_line, destination);
test_gl([&]{ glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size)); });
test_gl([&]{ glUnmapBuffer(GL_ARRAY_BUFFER); });
}
// Produce colour information, if required.
if(qam_separation_shader_) {
qam_separation_shader_->bind();
qam_chroma_texture_->bind_framebuffer();
test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT); }); // TODO: this is here as a hint that the old framebuffer doesn't need reloading;
// test whether that's a valid optimisation on desktop OpenGL.
test_gl([&]{ glDisable(GL_BLEND); });
test_gl([&]{ glDisable(GL_STENCIL_TEST); });
test_gl([&]{ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(lines)); });
accumulation_texture_->bind_framebuffer();
output_shader_->bind();
test_gl([&]{ glEnable(GL_BLEND); });
test_gl([&]{ glEnable(GL_STENCIL_TEST); });
}
// Render to the output.
test_gl([&]{ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(lines)); });
start_line = end_line;
new_lines -= lines;
}
// Disable blending and the stencil test again.
test_gl([&]{ glDisable(GL_STENCIL_TEST); });
test_gl([&]{ glDisable(GL_BLEND); });
}
// That's it for operations affecting the accumulation buffer.
is_drawing_to_accumulation_buffer_.clear();
// Grab a fence sync object to avoid busy waiting upon the next extry into this
// function, and reset the is_updating_ flag.
fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
complete_output_area(area);
});
}
void ScanTarget::draw(int output_width, int output_height) {
while(is_drawing_to_accumulation_buffer_.test_and_set(std::memory_order_acquire));
// if(accumulation_texture_) {
// // Copy the accumulation texture to the target.
// test_gl([&]{ glBindFramebuffer(GL_FRAMEBUFFER, target_framebuffer_); });
// test_gl([&]{ glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); });
//
// test_gl([&]{ glClearColor(0.0f, 0.0f, 0.0f, 0.0f); });
// test_gl([&]{ glClear(GL_COLOR_BUFFER_BIT); });
// accumulation_texture_->bind_texture();
// accumulation_texture_->draw(float(output_width) / float(output_height), 4.0f / 255.0f);
// }
if(!composition_buffer_.empty()) {
// copy_shader_.bind();
// glDrawElements(GL_TRIANGLE_STRIP, 0, GL_UNSIGNED_BYTE, nullptr);
// Copy the accumulation texture to the target.
test_gl([&]{ glBindFramebuffer(GL_FRAMEBUFFER, target_framebuffer_); });
test_gl([&]{ glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); });
composition_buffer_.draw(float(output_width) / float(output_height), 4.0f / 255.0f);
}
is_drawing_to_accumulation_buffer_.clear(std::memory_order_release);
}