1
0
mirror of https://github.com/TomHarte/CLK.git synced 2026-04-19 19:16:34 +00:00

Include interlacing information in Field.

This commit is contained in:
Thomas Harte
2026-02-22 23:16:33 -05:00
parent 5531c817a7
commit 486a67586a
5 changed files with 79 additions and 66 deletions
@@ -960,6 +960,18 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
// Hence every pixel is touched every frame, regardless of the machine's output.
//
const auto end_field =
[&](
const bool was_complete,
[[maybe_unused]] const int field_index,
[[maybe_unused]] const bool is_interlaced
) {
if(was_complete && !_dontClearFrameBuffer) {
[self outputFrameCleanerToCommandBuffer:commandBuffer];
}
_dontClearFrameBuffer = NO;
};
switch(_pipeline) {
case Pipeline::DirectToDisplay:
_scanTarget.output_scans(
@@ -967,12 +979,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
[&](const size_t begin, const size_t end) {
[self outputFrom:begin to:end commandBuffer:commandBuffer];
},
[&](const bool was_complete) {
if(was_complete && !_dontClearFrameBuffer) {
[self outputFrameCleanerToCommandBuffer:commandBuffer];
}
_dontClearFrameBuffer = NO;
}
end_field
);
break;
@@ -1095,12 +1102,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
[&](const size_t begin, const size_t end) {
[self outputFrom:begin to:end commandBuffer:commandBuffer];
},
[&](const bool was_complete) {
if(was_complete && !_dontClearFrameBuffer) {
[self outputFrameCleanerToCommandBuffer:commandBuffer];
}
_dontClearFrameBuffer = NO;
}
end_field
);
break;
}
+30 -21
View File
@@ -127,10 +127,10 @@ void ScanTarget::set_alphas() {
static constexpr float OutputAlpha = 0.64f;
if(!scan_output_shader_.empty()) {
scan_output_shader_.set_alpha(is_interlaced() ? std::sqrt(OutputAlpha) : OutputAlpha);
scan_output_shader_.set_alpha(is_interlacing_ ? std::sqrt(OutputAlpha) : OutputAlpha);
}
if(!line_output_shader_.empty()) {
line_output_shader_.set_alpha(is_interlaced() ? std::sqrt(OutputAlpha) : OutputAlpha);
line_output_shader_.set_alpha(is_interlacing_ ? std::sqrt(OutputAlpha) : OutputAlpha);
}
}
@@ -547,16 +547,25 @@ void ScanTarget::process_to_rgb(const OutputArea &area) {
}
void ScanTarget::bind_current_output_buffer() {
output_buffers_[output_buffer_ & (is_interlaced() ? 1 : 0)].bind_framebuffer();
output_buffers_[field_index_].bind_framebuffer();
}
void ScanTarget::toggle_output_buffer() {
output_buffer_ ^= 1;
bind_current_output_buffer();
void ScanTarget::end_field(
const bool was_complete,
const int field_index,
const bool is_interlaced
) {
if(was_complete) {
full_display_rectangle_.draw(0.0, 0.0, 0.0);
}
test_gl([&]{ glClear(GL_STENCIL_BUFFER_BIT); });
field_index_ = field_index;
output_buffers_[field_index].bind_framebuffer();
is_interlacing_ = is_interlaced;
}
void ScanTarget::output_lines(const OutputArea &area) {
bind_current_output_buffer();
output_buffers_[field_index_].bind_framebuffer();
BufferingScanTarget::output_lines(
area,
[&](const size_t begin, const size_t end) {
@@ -568,12 +577,12 @@ void ScanTarget::output_lines(const OutputArea &area) {
line_output_shader_.bind();
test_gl([&]{ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(new_lines)); });
},
[&](const bool was_complete) {
if(was_complete) {
full_display_rectangle_.draw(0.0, 0.0, 0.0);
}
test_gl([&]{ glClear(GL_STENCIL_BUFFER_BIT); });
toggle_output_buffer();
[&](
const bool was_complete,
const int field_index,
const bool is_interlaced
) {
end_field(was_complete, field_index, is_interlaced);
}
);
}
@@ -591,12 +600,12 @@ void ScanTarget::output_scans(const OutputArea &area) {
composition_buffer_.bind_texture();
test_gl([&]{ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(new_scans)); });
},
[&](const bool was_complete) {
if(was_complete) {
full_display_rectangle_.draw(0.0, 0.0, 0.0);
}
test_gl([&]{ glClear(GL_STENCIL_BUFFER_BIT); });
toggle_output_buffer();
[&](
const bool was_complete,
const int field_index,
const bool is_interlaced
) {
end_field(was_complete, field_index, is_interlaced);
}
);
}
@@ -608,7 +617,7 @@ void ScanTarget::draw(const int output_width, const int output_height) {
test_gl([&]{ glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); });
if(!output_buffers_[0].empty()) {
if(is_interlaced()) {
if(is_interlacing_) {
if(!was_interlacing_) {
set_alphas();
output_buffers_[1].bind_framebuffer();
@@ -635,7 +644,7 @@ void ScanTarget::draw(const int output_width, const int output_height) {
output_buffers_[0].bind_texture();
copy_shader_.perform(OutputTextureUnits[0], 1.0f);
}
was_interlacing_ = is_interlaced();
was_interlacing_ = is_interlacing_;
}
is_drawing_to_output_.clear(std::memory_order_release);
+3 -2
View File
@@ -112,8 +112,8 @@ private:
TextureTarget demodulation_buffer_;
std::array<TextureTarget, 2> output_buffers_;
int output_buffer_ = 0;
bool was_interlacing_ = false;
bool is_interlacing_ = false;
Shader composition_shader_;
Shader separation_shader_;
@@ -128,8 +128,9 @@ private:
void output_lines(const OutputArea &);
void output_scans(const OutputArea &);
int field_index_ = 0;
void bind_current_output_buffer();
void toggle_output_buffer();
void end_field(bool was_complete, int field_index, bool is_interlaced);
};
}
+11 -9
View File
@@ -223,9 +223,15 @@ void BufferingScanTarget::announce(
auto write = frame_write_.load(std::memory_order_relaxed);
const auto submit_pointers = submit_pointers_.load(std::memory_order_relaxed);
frames_[write].first_line = submit_pointers.line;
frames_[write].first_scan = submit_pointers.scan;
frames_[write].was_complete = previous_frame_was_complete_;
auto &frame = frames_[write];
frame.first_line = submit_pointers.line;
frame.first_scan = submit_pointers.scan;
frame.was_complete = previous_frame_was_complete_;
frame.is_interlaced = is_interlaced_;
frame.field_index = field_index_ & (frame.is_interlaced ? 1 : 0);
field_index_ ^= 1;
++write;
frame_write_.store(write, std::memory_order_release);
@@ -254,9 +260,9 @@ void BufferingScanTarget::announce(
return true;
};
if(!similar_enough(0, 1) && similar_enough(0, 2) && similar_enough(1, 2)) {
is_interlaced_.store(true, std::memory_order_relaxed);
is_interlaced_ = true;
} else if(similar_enough(0, 1)) {
is_interlaced_.store(false, std::memory_order_relaxed);
is_interlaced_ = false;
}
}
@@ -440,7 +446,3 @@ const Outputs::Display::ScanTarget::Modals &BufferingScanTarget::modals() const
bool BufferingScanTarget::has_new_modals() const {
return modals_are_dirty_.load(std::memory_order_relaxed);
}
bool BufferingScanTarget::is_interlaced() const {
return is_interlaced_.load(std::memory_order_relaxed);
}
+21 -22
View File
@@ -164,13 +164,6 @@ public:
/// @returns the current @c Modals.
const Modals &modals() const;
/// @returns whether the current output being received appears to describe an interlaced signal.
/// This is a hint only, potentially to provide for better deinterlacing of output, being derived locally
/// from line positioning. Specifically: if a scan target pays no heed to this whatsoever it's likely to
/// end up doing the equivalent of a bob. If it so desires, it might prefer to do something closer to
/// a weave if and only if interlaced video is detected.
bool is_interlaced() const;
/// @returns @c true if new modals are available; @c false otherwise.
///
/// Safe to call from any thread.
@@ -179,10 +172,10 @@ public:
template <typename OutputFuncT, typename FrameFuncT, typename CountGetT, typename FrameLimitGetT>
void output(
const OutputArea &area,
const OutputFuncT &&output,
const FrameFuncT &&end_frame,
const CountGetT &&count,
const FrameLimitGetT &&limit
OutputFuncT &&output,
FrameFuncT &&end_frame,
CountGetT &&count,
FrameLimitGetT &&limit
) const {
if(count(area.end) == count(area.begin) && area.end.frame == area.begin.frame) {
return;
@@ -200,8 +193,11 @@ public:
output(output_begin, limit(frames_[frame_begin]));
output_begin = limit(frames_[frame_begin]);
}
end_frame(frames_[frame_begin].was_complete);
end_frame(
frames_[frame_begin].was_complete,
frames_[frame_begin].field_index,
frames_[frame_begin].is_interlaced
);
++frame_begin;
if(frame_begin == frames_.size()) frame_begin = 0;
} while(frame_begin != area.end.frame);
@@ -214,13 +210,13 @@ public:
template <typename ScanFuncT, typename FrameFuncT>
void output_scans(
const OutputArea &area,
const ScanFuncT &&output_scans,
const FrameFuncT &&end_frame
ScanFuncT &&output_scans,
FrameFuncT &&end_frame
) const {
output(
area,
std::move(output_scans),
std::move(end_frame),
output_scans,
end_frame,
[](const auto &endpoint) { return endpoint.scan; },
[](const auto &frame) { return frame.first_scan; }
);
@@ -229,13 +225,13 @@ public:
template <typename LineFuncT, typename FrameFuncT>
void output_lines(
const OutputArea &area,
const LineFuncT &&output_lines,
const FrameFuncT &&end_frame
LineFuncT &&output_lines,
FrameFuncT &&end_frame
) const {
output(
area,
std::move(output_lines),
std::move(end_frame),
output_lines,
end_frame,
[](const auto &endpoint) { return endpoint.line; },
[](const auto &frame) { return frame.first_line; }
);
@@ -318,6 +314,8 @@ private:
size_t first_scan;
size_t first_line;
bool was_complete;
int field_index;
bool is_interlaced;
};
static constexpr uint16_t NumFrames = 60;
std::array<Frame, NumFrames> frames_;
@@ -329,7 +327,8 @@ private:
static constexpr size_t StartHistoryLength = 16;
std::array<Outputs::Display::ScanTarget::Scan::EndPoint, StartHistoryLength> start_history_;
Numeric::CircularCounter<size_t, StartHistoryLength> start_history_pointer_;
std::atomic<bool> is_interlaced_ = false;
bool is_interlaced_ = false;
int field_index_ = 0;
// By convention everything in the PointerSet points to the next instance
// of whatever it is that will be used. So a client should start with whatever