mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-25 16:31:42 +00:00
Kills CRTOpenGL.cpp and simplifies shader output very slightly.
This commit is contained in:
parent
f4764ea680
commit
6b42b92930
@ -1369,7 +1369,6 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -3027,7 +3026,6 @@
|
||||
4BD191D5219113B80042E144 /* OpenGL */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BD191DC219113B80042E144 /* CRTOpenGL.cpp */,
|
||||
4BD191F22191180E0042E144 /* ScanTarget.cpp */,
|
||||
4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */,
|
||||
4BD191D9219113B80042E144 /* OpenGL.hpp */,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ using namespace Outputs::Display::OpenGL;
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
/// The texture unit from which to source 1bpp input data.
|
||||
constexpr GLenum SourceData1BppTextureUnit = GL_TEXTURE0;
|
||||
/// 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.
|
||||
std::vector<Shader *> input_shaders = {input_shader_.get()};
|
||||
GLint texture_unit = GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0);
|
||||
// output_shader_->set_uniform("textureName", texture_unit);
|
||||
for(const auto &stage: pipeline_stages_) {
|
||||
input_shaders.push_back(stage.shader.get());
|
||||
|
||||
@ -186,8 +186,6 @@ void ScanTarget::set_modals(Modals modals) {
|
||||
}
|
||||
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.
|
||||
for(auto shader: input_shaders) {
|
||||
switch(modals.composite_colour_space) {
|
||||
|
@ -110,23 +110,25 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) {
|
||||
|
||||
if(type == ShaderType::InputScan) {
|
||||
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);";
|
||||
} else {
|
||||
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[1] = eyePosition + vec2(-4.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[2] = eyePosition + vec2(-3.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[3] = eyePosition + vec2(-2.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[4] = eyePosition + vec2(-1.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[5] = eyePosition;"
|
||||
"textureCoordinates[6] = eyePosition + vec2(1.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[7] = eyePosition + vec2(2.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[8] = eyePosition + vec2(3.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[9] = eyePosition + vec2(4.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[10] = eyePosition + vec2(5.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[0] = sourcePosition + vec2(-5.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[1] = sourcePosition + vec2(-4.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[2] = sourcePosition + vec2(-3.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[3] = sourcePosition + vec2(-2.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[4] = sourcePosition + vec2(-1.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[5] = sourcePosition;"
|
||||
"textureCoordinates[6] = sourcePosition + vec2(1.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[7] = sourcePosition + vec2(2.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[8] = sourcePosition + vec2(3.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[9] = sourcePosition + vec2(4.0, 0.0) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[10] = sourcePosition + vec2(5.0, 0.0) / textureSize(textureName, 0);"
|
||||
|
||||
"eyePosition = eyePosition;";
|
||||
}
|
||||
@ -155,7 +157,6 @@ std::string ScanTarget::glsl_default_vertex_shader(ShaderType type) {
|
||||
}
|
||||
|
||||
void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) {
|
||||
target.bind();
|
||||
switch(type) {
|
||||
case ShaderType::InputScan:
|
||||
case ShaderType::ProcessedScan:
|
||||
@ -228,7 +229,7 @@ std::unique_ptr<Shader> ScanTarget::input_shader(InputDataType input_data_type,
|
||||
std::string fragment_shader =
|
||||
"#version 150\n"
|
||||
|
||||
"out vec4 fragColour;"
|
||||
"out vec3 fragColour;"
|
||||
"in vec2 textureCoordinate;"
|
||||
"in float compositeAngle;"
|
||||
"in float compositeAmplitudeOut;"
|
||||
@ -243,12 +244,12 @@ std::unique_ptr<Shader> ScanTarget::input_shader(InputDataType input_data_type,
|
||||
switch(input_data_type) {
|
||||
case InputDataType::Luminance1:
|
||||
computed_display_type = DisplayType::CompositeMonochrome;
|
||||
fragment_shader += "fragColour = vec4(vec3(texture(textureName, textureCoordinate).r), 1.0);";
|
||||
fragment_shader += "fragColour = texture(textureName, textureCoordinate).rrr;";
|
||||
break;
|
||||
|
||||
case InputDataType::Luminance8:
|
||||
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;
|
||||
|
||||
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 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;
|
||||
|
||||
case InputDataType::Red1Green1Blue1:
|
||||
computed_display_type = DisplayType::RGB;
|
||||
fragment_shader +=
|
||||
"uint textureValue = texture(textureName, textureCoordinate).r;"
|
||||
"fragColour = vec4(uvec3(textureValue) & uvec3(4u, 2u, 1u), 1.0);";
|
||||
"fragColour = uvec3(textureValue) & uvec3(4u, 2u, 1u);";
|
||||
break;
|
||||
|
||||
case InputDataType::Red2Green2Blue2:
|
||||
computed_display_type = DisplayType::RGB;
|
||||
fragment_shader +=
|
||||
"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;
|
||||
|
||||
case InputDataType::Red4Green4Blue4:
|
||||
computed_display_type = DisplayType::RGB;
|
||||
fragment_shader +=
|
||||
"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;
|
||||
|
||||
case InputDataType::Red8Green8Blue8:
|
||||
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;
|
||||
}
|
||||
|
||||
@ -293,14 +294,14 @@ std::unique_ptr<Shader> ScanTarget::input_shader(InputDataType input_data_type,
|
||||
// there'll definitely be an RGB to SVideo step.
|
||||
if(computed_display_type == DisplayType::RGB) {
|
||||
fragment_shader +=
|
||||
"vec3 composite_colour = rgbToLumaChroma * vec3(fragColour);"
|
||||
"vec3 composite_colour = rgbToLumaChroma * fragColour;"
|
||||
"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(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 usampler2D textureName;"
|
||||
|
||||
"out vec4 fragColour;"
|
||||
"out vec3 fragColour;"
|
||||
"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)
|
||||
));
|
||||
|
Loading…
Reference in New Issue
Block a user