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
Thomas Harte e56b4bd61b Establish that the composition buffer is at least drawable.
The problem must lie in the composition shader, the scans vertex array or the connection between the two.
2026-02-01 16:06:02 -05:00

691 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 "CompositionShader.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;
/// 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;
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_) {
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 float sample_multiplier =
FilterGenerator::suggested_sample_multiplier(227.5f, 1320);
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
) {
// TODO: a temporary vertex array is created here for now, to avoid messing
// with the bindings of the real scan vertex array. Move past that.
VertexArray array(scan_buffer_);
composition_shader_ = OpenGL::composition_shader(
api_,
modals.input_data_type,
modals.display_type,
modals.composite_colour_space,
sample_multiplier,
2048, 2048,
buffer_width, 2048,
array,
GL_TEXTURE0
);
}
if(composition_buffer_.empty()) {
composition_buffer_ = TextureTarget(api_, buffer_width, 2048, GL_TEXTURE4, GL_NEAREST, false);
composition_buffer_.bind_texture();
// The below establishes that the composition buffer texture is ultimately being drawn.
std::vector<uint8_t> image(buffer_width * 2048 * 4);
for(auto &c : image) {
c = rand();
}
test_gl([&]{
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
buffer_width,
2048,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
image.data()
);
});
}
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(int, 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);
// 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 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);
}