1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Kills CRTOpenGL.cpp and simplifies shader output very slightly.

This commit is contained in:
Thomas Harte 2018-11-24 17:37:58 -05:00
parent f4764ea680
commit 6b42b92930
4 changed files with 30 additions and 559 deletions

View File

@ -1369,7 +1369,6 @@
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; }; 4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; };
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; }; 4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; }; 4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; };
4BD191DC219113B80042E144 /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = "<group>"; };
4BD191E0219113B80042E144 /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; }; 4BD191E0219113B80042E144 /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; };
4BD191E1219113B80042E144 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; }; 4BD191E1219113B80042E144 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
4BD191E3219113B80042E144 /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; }; 4BD191E3219113B80042E144 /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; };
@ -3027,7 +3026,6 @@
4BD191D5219113B80042E144 /* OpenGL */ = { 4BD191D5219113B80042E144 /* OpenGL */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4BD191DC219113B80042E144 /* CRTOpenGL.cpp */,
4BD191F22191180E0042E144 /* ScanTarget.cpp */, 4BD191F22191180E0042E144 /* ScanTarget.cpp */,
4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */, 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */,
4BD191D9219113B80042E144 /* OpenGL.hpp */, 4BD191D9219113B80042E144 /* OpenGL.hpp */,

View File

@ -1,527 +0,0 @@
// CRTOpenGL.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/02/2016.
// Copyright 2016 Thomas Harte. All rights reserved.
//
#include "../CRT.hpp"
#include <cassert>
#include <cmath>
#include <cstdlib>
#include "CRTOpenGL.hpp"
#include "../../../SignalProcessing/FIRFilter.hpp"
#include "Shaders/OutputShader.hpp"
using namespace Outputs::CRT;
namespace {
static const GLenum source_data_texture_unit = GL_TEXTURE0;
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE1;
static const GLenum composite_texture_unit = GL_TEXTURE2;
static const GLenum separated_texture_unit = GL_TEXTURE3;
static const GLenum filtered_texture_unit = GL_TEXTURE4;
static const GLenum work_texture_unit = GL_TEXTURE2;
}
OpenGLOutputBuilder::OpenGLOutputBuilder(std::size_t bytes_per_pixel) :
visible_area_(Rect(0, 0, 1, 1)),
composite_src_output_y_(0),
last_output_width_(0),
last_output_height_(0),
fence_(nullptr),
texture_builder(bytes_per_pixel, source_data_texture_unit),
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize) {
glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR);
glBlendColor(0.4f, 0.4f, 0.4f, 1.0f);
// create the output vertex array
glGenVertexArrays(1, &output_vertex_array_);
// create the source vertex array
glGenVertexArrays(1, &source_vertex_array_);
// bool supports_texture_barrier = false;
#ifdef GL_NV_texture_barrier
// GLint number_of_extensions;
// glGetIntegerv(GL_NUM_EXTENSIONS, &number_of_extensions);
//
// for(GLuint c = 0; c < (GLuint)number_of_extensions; c++) {
// const char *extension_name = (const char *)glGetStringi(GL_EXTENSIONS, c);
// if(!std::strcmp(extension_name, "GL_NV_texture_barrier")) {
// supports_texture_barrier = true;
// }
// }
#endif
// if(supports_texture_barrier) {
// work_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight*2, work_texture_unit));
// } else {
composite_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit, GL_NEAREST));
separated_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit, GL_NEAREST));
filtered_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit, GL_LINEAR));
// }
}
OpenGLOutputBuilder::~OpenGLOutputBuilder() {
glDeleteVertexArrays(1, &output_vertex_array_);
}
void OpenGLOutputBuilder::set_target_framebuffer(GLint target_framebuffer) {
target_framebuffer_ = target_framebuffer;
}
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();
// establish essentials
if(!output_shader_program_) {
prepare_composite_input_shaders();
prepare_svideo_input_shaders();
prepare_rgb_input_shaders();
prepare_source_vertex_array();
prepare_output_shader();
prepare_output_vertex_array();
set_timing_uniforms();
set_colour_space_uniforms();
set_gamma();
}
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) {
draw_mutex_.unlock();
return;
}
glDeleteSync(fence_);
}
// make sure everything is bound
composite_texture_->bind_texture();
separated_texture_->bind_texture();
filtered_texture_->bind_texture();
if(work_texture_) work_texture_->bind_texture();
// make sure there's a target to draw to
if(!framebuffer_ || static_cast<unsigned int>(framebuffer_->get_height()) != output_height || static_cast<unsigned int>(framebuffer_->get_width()) != output_width) {
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit, GL_LINEAR));
if(framebuffer_) {
new_framebuffer->bind_framebuffer();
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(pixel_accumulation_texture_unit);
framebuffer_->bind_texture();
framebuffer_->draw(static_cast<float>(output_width) / static_cast<float>(output_height));
new_framebuffer->bind_texture();
}
framebuffer_ = std::move(new_framebuffer);
}
// lock out the machine emulation until data is copied
output_mutex_.lock();
// release the mapping, giving up on trying to draw if data has been lost
ArrayBuilder::Submission array_submission = array_builder.submit();
// upload new source pixels, if any
glActiveTexture(source_data_texture_unit);
texture_builder.bind();
texture_builder.submit();
// 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();
struct RenderStage {
OpenGL::Shader *const shader;
OpenGL::TextureTarget *const target;
float clear_colour[3];
};
// for composite video, go through four steps to get to something that can be painted to the output
const RenderStage composite_render_stages[] = {
{composite_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
{composite_separation_filter_program_.get(), separated_texture_.get(), {0.0, 0.5, 0.5}},
{composite_chrominance_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
{nullptr, nullptr}
};
// for s-video, there are two steps: it's like composite but skips separation
const RenderStage svideo_render_stages[] = {
{svideo_input_shader_program_.get(), separated_texture_.get(), {0.0, 0.5, 0.5}},
{composite_chrominance_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
{nullptr, nullptr}
};
// for RGB video, there's also only two steps; a lowpass filter is still applied per physical reality
const RenderStage rgb_render_stages[] = {
{rgb_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
{rgb_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
{nullptr, nullptr}
};
const RenderStage *active_pipeline;
switch(video_signal_) {
default:
case VideoSignal::Composite: active_pipeline = composite_render_stages; break;
case VideoSignal::SVideo: active_pipeline = svideo_render_stages; break;
case VideoSignal::RGB: active_pipeline = rgb_render_stages; break;
}
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_);
glDisable(GL_BLEND);
#ifdef GL_NV_texture_barrier
// if(work_texture_) {
// work_texture_->bind_framebuffer();
// glClear(GL_COLOR_BUFFER_BIT);
// }
#endif
while(active_pipeline->shader) {
// switch to the framebuffer and shader associated with this stage
active_pipeline->shader->bind();
if(!work_texture_) {
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].shader) {
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// }
}
// draw
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.input_size / SourceVertexSize);
active_pipeline++;
#ifdef GL_NV_texture_barrier
// glTextureBarrierNV();
#endif
}
// prepare to transfer to framebuffer
framebuffer_->bind_framebuffer();
// draw from the output array buffer, with blending
glBindVertexArray(output_vertex_array_);
glEnable(GL_BLEND);
// 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_);
last_output_width_ = output_width;
last_output_height_ = output_height;
// Configure right and left gutters to crop the left- and right-hand 1% of the display.
left_overlay_.reset(new OpenGL::Rectangle(output_shader_program_->get_left_extent() * 0.98f, -1.0f, -1.0f, 2.0f));
right_overlay_.reset(new OpenGL::Rectangle(output_shader_program_->get_right_extent() * 0.98f, -1.0f, 1.0f, 2.0f));
}
output_shader_program_->bind();
// draw
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize);
// mask off the gutter
glDisable(GL_BLEND);
left_overlay_->draw(0.0, 0.0, 0.0);
right_overlay_->draw(0.0, 0.0, 0.0);
}
#ifdef GL_NV_texture_barrier
// glTextureBarrierNV();
#endif
// Copy framebuffer to the intended place; apply a threshold so that any persistent errors in
// the lower part of the colour channels are invisible.
glDisable(GL_BLEND);
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(target_framebuffer_));
glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height);
glActiveTexture(pixel_accumulation_texture_unit);
framebuffer_->bind_texture();
framebuffer_->draw(static_cast<float>(output_width) / static_cast<float>(output_height), 4.0f / 255.0f);
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_chrominance_filter_shader_program_ = nullptr;
svideo_input_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();
reset_all_OpenGL_state();
output_mutex_.unlock();
}
void OpenGLOutputBuilder::set_composite_sampling_function(const std::string &shader) {
std::lock_guard<std::mutex> lock_guard(output_mutex_);
composite_shader_ = shader;
reset_all_OpenGL_state();
}
void OpenGLOutputBuilder::set_svideo_sampling_function(const std::string &shader) {
std::lock_guard<std::mutex> lock_guard(output_mutex_);
svideo_shader_ = shader;
reset_all_OpenGL_state();
}
void OpenGLOutputBuilder::set_rgb_sampling_function(const std::string &shader) {
std::lock_guard<std::mutex> lock_guard(output_mutex_);
rgb_shader_ = shader;
reset_all_OpenGL_state();
}
// MARK: - Program compilation
void OpenGLOutputBuilder::prepare_composite_input_shaders() {
composite_input_shader_program_ = OpenGL::IntermediateShader::make_composite_source_shader(composite_shader_, svideo_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(work_texture_ ? work_texture_unit : composite_texture_unit);
composite_separation_filter_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(work_texture_ ? work_texture_unit : separated_texture_unit);
composite_chrominance_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
// TODO: the below is related to texture fencing, which is not yet implemented correctly, so not yet enabled.
if(work_texture_) {
composite_input_shader_program_->set_is_double_height(true, 0.0f, 0.0f);
composite_separation_filter_program_->set_is_double_height(true, 0.0f, 0.5f);
composite_chrominance_filter_shader_program_->set_is_double_height(true, 0.5f, 0.0f);
} else {
composite_input_shader_program_->set_is_double_height(false);
composite_separation_filter_program_->set_is_double_height(false);
composite_chrominance_filter_shader_program_->set_is_double_height(false);
}
}
void OpenGLOutputBuilder::prepare_svideo_input_shaders() {
if(!svideo_shader_.empty() || !rgb_shader_.empty()) {
svideo_input_shader_program_ = OpenGL::IntermediateShader::make_svideo_source_shader(svideo_shader_, rgb_shader_);
svideo_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
svideo_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
// TODO: the below is related to texture fencing, which is not yet implemented correctly, so not yet enabled.
if(work_texture_) {
svideo_input_shader_program_->set_is_double_height(true, 0.0f, 0.0f);
} else {
svideo_input_shader_program_->set_is_double_height(false);
}
}
}
void OpenGLOutputBuilder::prepare_rgb_input_shaders() {
if(!rgb_shader_.empty()) {
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_ || svideo_input_shader_program_) {
glBindVertexArray(source_vertex_array_);
array_builder.bind_input();
}
using Shader = OpenGL::IntermediateShader;
OpenGL::IntermediateShader *const shaders[] = {
composite_input_shader_program_.get(),
svideo_input_shader_program_.get()
};
for(int c = 0; c < 2; ++c) {
if(!shaders[c]) continue;
shaders[c]->enable_vertex_attribute_with_pointer(
Shader::get_input_name(Shader::Input::InputStart),
2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize,
(void *)SourceVertexOffsetOfInputStart, 1);
shaders[c]->enable_vertex_attribute_with_pointer(
Shader::get_input_name(Shader::Input::OutputStart),
2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize,
(void *)SourceVertexOffsetOfOutputStart, 1);
shaders[c]->enable_vertex_attribute_with_pointer(
Shader::get_input_name(Shader::Input::Ends),
2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize,
(void *)SourceVertexOffsetOfEnds, 1);
shaders[c]->enable_vertex_attribute_with_pointer(
Shader::get_input_name(Shader::Input::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(work_texture_ ? work_texture_unit : filtered_texture_unit);
// output_shader_program_->set_source_texture_unit(composite_texture_unit);
output_shader_program_->set_origin_is_double_height(!!work_texture_);
}
void OpenGLOutputBuilder::prepare_output_vertex_array() {
if(output_shader_program_) {
glBindVertexArray(output_vertex_array_);
array_builder.bind_output();
using Shader = OpenGL::OutputShader;
output_shader_program_->enable_vertex_attribute_with_pointer(
Shader::get_input_name(Shader::Input::Horizontal),
2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize,
(void *)OutputVertexOffsetOfHorizontal, 1);
output_shader_program_->enable_vertex_attribute_with_pointer(
Shader::get_input_name(Shader::Input::Vertical),
2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize,
(void *)OutputVertexOffsetOfVertical, 1);
}
}
// MARK: - Public Configuration
void OpenGLOutputBuilder::set_video_signal(VideoSignal video_signal) {
if(video_signal_ != video_signal) {
video_signal_ = video_signal;
composite_src_output_y_ = 0;
last_output_width_ = 0;
last_output_height_ = 0;
set_output_shader_width();
}
}
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) {
std::lock_guard<std::mutex> lock_guard(output_mutex_);
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();
}
// MARK: - Internal Configuration
void OpenGLOutputBuilder::set_colour_space_uniforms() {
GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f};
GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f};
GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f};
GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f};
GLfloat *fromRGB = nullptr, *toRGB = nullptr;
switch(colour_space_) {
case ColourSpace::YIQ:
fromRGB = rgbToYIQ;
toRGB = yiqToRGB;
break;
case ColourSpace::YUV:
fromRGB = rgbToYUV;
toRGB = yuvToRGB;
break;
default: assert(false); break;
}
if(composite_input_shader_program_) composite_input_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
if(composite_separation_filter_program_) composite_separation_filter_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(svideo_input_shader_program_) svideo_input_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
}
void OpenGLOutputBuilder::set_gamma() {
if(output_shader_program_) output_shader_program_->set_gamma_ratio(gamma_);
}
/*!
@returns The multiplier to apply to x positions received at the shader in order to produce locations in the intermediate
texture. Intermediate textures are in phase with the composite signal, so this is a function of (i) composite frequency
(determining how much of the texture adds up to a single line); and (ii) input frequency (determining what the input
positions mean as a fraction of a line).
*/
float OpenGLOutputBuilder::get_composite_output_width() const {
return
(static_cast<float>(colour_cycle_numerator_ * 4) / static_cast<float>(colour_cycle_denominator_ * IntermediateBufferWidth)) *
(static_cast<float>(IntermediateBufferWidth) / static_cast<float>(cycles_per_line_));
}
void OpenGLOutputBuilder::set_output_shader_width() {
if(output_shader_program_) {
// For anything that isn't RGB, scale so that sampling is in-phase with the colour subcarrier.
const float width = (video_signal_ == VideoSignal::RGB) ? 1.0f : get_composite_output_width();
output_shader_program_->set_input_width_scaler(width);
}
}
void OpenGLOutputBuilder::set_timing_uniforms() {
const float colour_subcarrier_frequency = static_cast<float>(colour_cycle_numerator_) / static_cast<float>(colour_cycle_denominator_);
const float output_width = get_composite_output_width();
const float sample_cycles_per_line = cycles_per_line_ / output_width;
if(composite_separation_filter_program_) {
composite_separation_filter_program_->set_width_scalers(output_width, output_width);
composite_separation_filter_program_->set_separation_frequency(sample_cycles_per_line, colour_subcarrier_frequency);
composite_separation_filter_program_->set_extension(6.0f);
}
if(composite_chrominance_filter_shader_program_) {
composite_chrominance_filter_shader_program_->set_width_scalers(output_width, output_width);
composite_chrominance_filter_shader_program_->set_extension(5.0f);
}
if(rgb_filter_shader_program_) {
rgb_filter_shader_program_->set_width_scalers(1.0f, 1.0f);
rgb_filter_shader_program_->set_filter_coefficients(sample_cycles_per_line, static_cast<float>(input_frequency_) * 0.5f);
}
if(output_shader_program_) {
set_output_shader_width();
output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_);
}
if(composite_input_shader_program_) {
composite_input_shader_program_->set_width_scalers(1.0f, output_width);
composite_input_shader_program_->set_extension(0.0f);
}
if(svideo_input_shader_program_) {
svideo_input_shader_program_->set_width_scalers(1.0f, output_width);
svideo_input_shader_program_->set_extension(0.0f);
}
if(rgb_input_shader_program_) {
rgb_input_shader_program_->set_width_scalers(1.0f, 1.0f);
}
}

View File

@ -13,7 +13,6 @@ using namespace Outputs::Display::OpenGL;
namespace { namespace {
/// The texture unit from which to source 1bpp input data. /// The texture unit from which to source 1bpp input data.
constexpr GLenum SourceData1BppTextureUnit = GL_TEXTURE0; constexpr GLenum SourceData1BppTextureUnit = GL_TEXTURE0;
/// The texture unit from which to source 2bpp input data. /// The texture unit from which to source 2bpp input data.
@ -175,6 +174,7 @@ void ScanTarget::set_modals(Modals modals) {
// Cascade the texture units in use as per the pipeline stages. // Cascade the texture units in use as per the pipeline stages.
std::vector<Shader *> input_shaders = {input_shader_.get()}; std::vector<Shader *> input_shaders = {input_shader_.get()};
GLint texture_unit = GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0); GLint texture_unit = GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0);
// output_shader_->set_uniform("textureName", texture_unit);
for(const auto &stage: pipeline_stages_) { for(const auto &stage: pipeline_stages_) {
input_shaders.push_back(stage.shader.get()); input_shaders.push_back(stage.shader.get());
@ -186,8 +186,6 @@ void ScanTarget::set_modals(Modals modals) {
} }
output_shader_->set_uniform("textureName", texture_unit); output_shader_->set_uniform("textureName", texture_unit);
// enable_vertex_attributes(ShaderType::InputScan, *input_shader_);
// Ensure that all shaders involved in the input pipeline have the proper colour space knowledged. // Ensure that all shaders involved in the input pipeline have the proper colour space knowledged.
for(auto shader: input_shaders) { for(auto shader: input_shaders) {
switch(modals.composite_colour_space) { switch(modals.composite_colour_space) {

View File

@ -110,23 +110,25 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) {
if(type == ShaderType::InputScan) { if(type == ShaderType::InputScan) {
result += result +=
"textureCoordinate = vec2(mix(startDataX, endDataX, lateral), dataY) / textureSize(textureName, 0);" // TODO: dataY + 0.5 "textureCoordinate = vec2(mix(startDataX, endDataX, lateral), dataY + 0.5) / textureSize(textureName, 0);"
"vec2 eyePosition = vec2(mix(startPoint.x, endPoint.x, lateral) * processingWidth, lineY + longitudinal) / vec2(scale.x, 2048.0);"; "vec2 eyePosition = vec2(mix(startPoint.x, endPoint.x, lateral) * processingWidth, lineY + longitudinal) / vec2(scale.x, 2048.0);";
} else { } else {
result += result +=
"vec2 eyePosition = vec2(mix(startPoint.x, endPoint.x, lateral) * processingWidth, lineY + longitudinal) / vec2(scale.x, 2048.0);" "vec2 sourcePosition = vec2(mix(startPoint.x, endPoint.x, lateral) * processingWidth, lineY + 0.5);"
"vec2 eyePosition = (sourcePosition + vec2(0.0, longitudinal - 0.5)) / vec2(scale.x, 2048.0);"
"sourcePosition /= vec2(scale.x, 2048.0);"
"textureCoordinates[0] = eyePosition + vec2(-5.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[0] = sourcePosition + vec2(-5.0, 0.0) / textureSize(textureName, 0);"
"textureCoordinates[1] = eyePosition + vec2(-4.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[1] = sourcePosition + vec2(-4.0, 0.0) / textureSize(textureName, 0);"
"textureCoordinates[2] = eyePosition + vec2(-3.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[2] = sourcePosition + vec2(-3.0, 0.0) / textureSize(textureName, 0);"
"textureCoordinates[3] = eyePosition + vec2(-2.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[3] = sourcePosition + vec2(-2.0, 0.0) / textureSize(textureName, 0);"
"textureCoordinates[4] = eyePosition + vec2(-1.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[4] = sourcePosition + vec2(-1.0, 0.0) / textureSize(textureName, 0);"
"textureCoordinates[5] = eyePosition;" "textureCoordinates[5] = sourcePosition;"
"textureCoordinates[6] = eyePosition + vec2(1.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[6] = sourcePosition + vec2(1.0, 0.0) / textureSize(textureName, 0);"
"textureCoordinates[7] = eyePosition + vec2(2.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[7] = sourcePosition + vec2(2.0, 0.0) / textureSize(textureName, 0);"
"textureCoordinates[8] = eyePosition + vec2(3.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[8] = sourcePosition + vec2(3.0, 0.0) / textureSize(textureName, 0);"
"textureCoordinates[9] = eyePosition + vec2(4.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[9] = sourcePosition + vec2(4.0, 0.0) / textureSize(textureName, 0);"
"textureCoordinates[10] = eyePosition + vec2(5.0, 0.0) / textureSize(textureName, 0);" "textureCoordinates[10] = sourcePosition + vec2(5.0, 0.0) / textureSize(textureName, 0);"
"eyePosition = eyePosition;"; "eyePosition = eyePosition;";
} }
@ -155,7 +157,6 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) {
} }
void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) { void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) {
target.bind();
switch(type) { switch(type) {
case ShaderType::InputScan: case ShaderType::InputScan:
case ShaderType::ProcessedScan: case ShaderType::ProcessedScan:
@ -228,7 +229,7 @@ std::unique_ptr<Shader> ScanTarget::input_shader(InputDataType input_data_type,
std::string fragment_shader = std::string fragment_shader =
"#version 150\n" "#version 150\n"
"out vec4 fragColour;" "out vec3 fragColour;"
"in vec2 textureCoordinate;" "in vec2 textureCoordinate;"
"in float compositeAngle;" "in float compositeAngle;"
"in float compositeAmplitudeOut;" "in float compositeAmplitudeOut;"
@ -243,12 +244,12 @@ std::unique_ptr<Shader> ScanTarget::input_shader(InputDataType input_data_type,
switch(input_data_type) { switch(input_data_type) {
case InputDataType::Luminance1: case InputDataType::Luminance1:
computed_display_type = DisplayType::CompositeMonochrome; computed_display_type = DisplayType::CompositeMonochrome;
fragment_shader += "fragColour = vec4(vec3(texture(textureName, textureCoordinate).r), 1.0);"; fragment_shader += "fragColour = texture(textureName, textureCoordinate).rrr;";
break; break;
case InputDataType::Luminance8: case InputDataType::Luminance8:
computed_display_type = DisplayType::CompositeMonochrome; computed_display_type = DisplayType::CompositeMonochrome;
fragment_shader += "fragColour = vec4(vec3(texture(textureName, textureCoordinate).r / 255.0), 1.0);"; fragment_shader += "fragColour = vec3(texture(textureName, textureCoordinate).r / 255.0);";
break; break;
case InputDataType::Luminance8Phase8: case InputDataType::Luminance8Phase8:
@ -258,33 +259,33 @@ std::unique_ptr<Shader> ScanTarget::input_shader(InputDataType input_data_type,
"float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;" "float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;"
"float chroma = step(yc.y, 0.75) * cos(compositeAngle + phaseOffset);" "float chroma = step(yc.y, 0.75) * cos(compositeAngle + phaseOffset);"
"fragColour = vec4(yc.x, chroma, 0.0, 1.0);"; "fragColour = vec3(yc.x, chroma, 0.0);";
break; break;
case InputDataType::Red1Green1Blue1: case InputDataType::Red1Green1Blue1:
computed_display_type = DisplayType::RGB; computed_display_type = DisplayType::RGB;
fragment_shader += fragment_shader +=
"uint textureValue = texture(textureName, textureCoordinate).r;" "uint textureValue = texture(textureName, textureCoordinate).r;"
"fragColour = vec4(uvec3(textureValue) & uvec3(4u, 2u, 1u), 1.0);"; "fragColour = uvec3(textureValue) & uvec3(4u, 2u, 1u);";
break; break;
case InputDataType::Red2Green2Blue2: case InputDataType::Red2Green2Blue2:
computed_display_type = DisplayType::RGB; computed_display_type = DisplayType::RGB;
fragment_shader += fragment_shader +=
"uint textureValue = texture(textureName, textureCoordinate).r;" "uint textureValue = texture(textureName, textureCoordinate).r;"
"fragColour = vec4(vec3(float((textureValue >> 4) & 3u), float((textureValue >> 2) & 3u), float(textureValue & 3u)) / 3.0, 1.0);"; "fragColour = vec3(float((textureValue >> 4) & 3u), float((textureValue >> 2) & 3u), float(textureValue & 3u)) / 3.0;";
break; break;
case InputDataType::Red4Green4Blue4: case InputDataType::Red4Green4Blue4:
computed_display_type = DisplayType::RGB; computed_display_type = DisplayType::RGB;
fragment_shader += fragment_shader +=
"uvec2 textureValue = texture(textureName, textureCoordinate).rg;" "uvec2 textureValue = texture(textureName, textureCoordinate).rg;"
"fragColour = vec4(float(textureValue.r) / 15.0, float(textureValue.g & 240u) / 240.0, float(textureValue.g & 15u) / 15.0, 1.0);"; "fragColour = vec3(float(textureValue.r) / 15.0, float(textureValue.g & 240u) / 240.0, float(textureValue.g & 15u) / 15.0);";
break; break;
case InputDataType::Red8Green8Blue8: case InputDataType::Red8Green8Blue8:
computed_display_type = DisplayType::RGB; computed_display_type = DisplayType::RGB;
fragment_shader += "fragColour = vec4(texture(textureName, textureCoordinate).rgb / vec3(255.0), 1.0);"; fragment_shader += "fragColour = texture(textureName, textureCoordinate).rgb / vec3(255.0);";
break; break;
} }
@ -293,14 +294,14 @@ std::unique_ptr<Shader> ScanTarget::input_shader(InputDataType input_data_type,
// there'll definitely be an RGB to SVideo step. // there'll definitely be an RGB to SVideo step.
if(computed_display_type == DisplayType::RGB) { if(computed_display_type == DisplayType::RGB) {
fragment_shader += fragment_shader +=
"vec3 composite_colour = rgbToLumaChroma * vec3(fragColour);" "vec3 composite_colour = rgbToLumaChroma * fragColour;"
"vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));" "vec2 quadrature = vec2(cos(compositeAngle), sin(compositeAngle));"
"fragColour = vec4(composite_colour.r, 0.5 + dot(quadrature, composite_colour.gb)*0.5, 0.0, 1.0);"; "fragColour = vec3(composite_colour.r, 0.5 + dot(quadrature, composite_colour.gb)*0.5, 0.0);";
} }
// If the output type isn't SVideo, add an SVideo to composite step. // If the output type isn't SVideo, add an SVideo to composite step.
if(display_type != DisplayType::SVideo) { if(display_type != DisplayType::SVideo) {
fragment_shader += "fragColour = vec4(vec3(mix(fragColour.r, 2.0*(fragColour.g - 0.5), compositeAmplitudeOut)), 1.0);"; fragment_shader += "fragColour = vec3(mix(fragColour.r, 2.0*(fragColour.g - 0.5), compositeAmplitudeOut));";
} }
} }
@ -331,9 +332,10 @@ std::unique_ptr<Shader> ScanTarget::svideo_to_rgb_shader(int colour_cycle_numera
"uniform float textureWeights[11];" "uniform float textureWeights[11];"
"uniform usampler2D textureName;" "uniform usampler2D textureName;"
"out vec4 fragColour;" "out vec3 fragColour;"
"void main(void) {" "void main(void) {"
"fragColour = texture(textureName, textureCoordinates[5]);" "vec3 textureSample = vec3(texture(textureName, textureCoordinates[5]).rgb) / vec3(65536.0 * 16384.0);"
"fragColour = textureSample;"
"}", "}",
attribute_bindings(ShaderType::ProcessedScan) attribute_bindings(ShaderType::ProcessedScan)
)); ));