1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-23 03:32:32 +00:00

Merge pull request #588 from TomHarte/SeparateChromaBuffer

Reintroduces a separate chrominance buffer
This commit is contained in:
Thomas Harte 2019-02-12 19:52:54 -05:00 committed by GitHub
commit 5d68a5bdd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 617 additions and 367 deletions

View File

@ -28,12 +28,19 @@
#include "../../Analyser/Static/AppleII/Target.hpp" #include "../../Analyser/Static/AppleII/Target.hpp"
#include "../../ClockReceiver/ForceInline.hpp" #include "../../ClockReceiver/ForceInline.hpp"
#include "../../Configurable/StandardOptions.hpp"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <memory> #include <memory>
namespace { namespace AppleII {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayCompositeMonochrome | Configurable::DisplayCompositeColour)
);
}
#define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe)) #define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe))
@ -43,6 +50,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
public KeyboardMachine::MappedMachine, public KeyboardMachine::MappedMachine,
public CPU::MOS6502::BusHandler, public CPU::MOS6502::BusHandler,
public Inputs::Keyboard, public Inputs::Keyboard,
public Configurable::Device,
public AppleII::Machine, public AppleII::Machine,
public Activity::Source, public Activity::Source,
public JoystickMachine::Machine, public JoystickMachine::Machine,
@ -84,7 +92,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
uint8_t ram_[65536], aux_ram_[65536]; uint8_t ram_[65536], aux_ram_[65536];
std::vector<uint8_t> rom_; std::vector<uint8_t> rom_;
// std::vector<uint8_t> character_rom_;
uint8_t keyboard_input_ = 0x00; uint8_t keyboard_input_ = 0x00;
bool key_is_down_ = false; bool key_is_down_ = false;
@ -401,6 +408,11 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
video_.set_scan_target(scan_target); video_.set_scan_target(scan_target);
} }
/// Sets the type of display.
void set_display_type(Outputs::Display::DisplayType display_type) override {
video_.set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override { Outputs::Speaker::Speaker *get_speaker() override {
return &speaker_; return &speaker_;
} }
@ -804,6 +816,28 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
string_serialiser_.reset(new Utility::StringSerialiser(string, true)); string_serialiser_.reset(new Utility::StringSerialiser(string, true));
} }
// MARK:: Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
return AppleII::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
return get_accurate_selections();
}
// MARK: MediaTarget // MARK: MediaTarget
bool insert_media(const Analyser::Static::Media &media) override { bool insert_media(const Analyser::Static::Media &media) override {
if(!media.disks.empty()) { if(!media.disks.empty()) {

View File

@ -18,6 +18,9 @@
namespace AppleII { namespace AppleII {
/// @returns The options available for an Apple II.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine { class Machine {
public: public:
virtual ~Machine(); virtual ~Machine();

View File

@ -42,6 +42,10 @@ void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target); crt_.set_scan_target(scan_target);
} }
void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
/* /*
Rote setters and getters. Rote setters and getters.
*/ */

View File

@ -39,6 +39,9 @@ class VideoBase {
/// Sets the scan target. /// Sets the scan target.
void set_scan_target(Outputs::Display::ScanTarget *scan_target); void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
/* /*
Descriptions for the setters below are taken verbatim from Descriptions for the setters below are taken verbatim from
the Apple IIe Technical Reference. Addresses are the conventional the Apple IIe Technical Reference. Addresses are the conventional

View File

@ -132,6 +132,7 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options; std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options()));

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -13,37 +13,43 @@
</customObject> </customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/> <customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target"> <window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="80" y="150" width="200" height="54"/> <rect key="contentRect" x="80" y="150" width="200" height="61"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" id="tpZ-0B-QQu"> <view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/> <rect key="frame" x="0.0" y="0.0" width="200" height="61"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="e1J-pw-zGw"> <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ex3-VM-58z">
<rect key="frame" x="18" y="18" width="164" height="18"/> <rect key="frame" x="18" y="17" width="165" height="25"/>
<buttonCell key="cell" type="check" title="Accelerate DOS 3.3" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="tD6-UB-ESB"> <popUpButtonCell key="cell" type="push" title="Colour" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="gOu-dv-tre" id="u3N-Je-c2L">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="menu"/>
</buttonCell> <menu key="menu" id="BUS-Pl-jBm">
<items>
<menuItem title="Colour" state="on" tag="1" id="gOu-dv-tre"/>
<menuItem title="Monochrome" tag="3" id="qhQ-ab-jRo"/>
</items>
</menu>
</popUpButtonCell>
<connections> <connections>
<action selector="setFastLoading:" target="ZW7-Bw-4RP" id="JmG-Ks-jSh"/> <action selector="setDisplayType:" target="ZW7-Bw-4RP" id="f7A-2O-wR8"/>
</connections> </connections>
</button> </popUpButton>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="5ce-DO-a4T"/> <constraint firstAttribute="bottom" secondItem="ex3-VM-58z" secondAttribute="bottom" constant="20" id="4ZS-q5-TJL"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/> <constraint firstItem="ex3-VM-58z" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="8Pj-Ns-TrJ"/>
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/> <constraint firstAttribute="trailing" secondItem="ex3-VM-58z" secondAttribute="trailing" constant="20" id="QWA-lY-ugz"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/> <constraint firstItem="ex3-VM-58z" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="szw-WO-3tS"/>
</constraints> </constraints>
</view> </view>
<connections> <connections>
<outlet property="fastLoadingButton" destination="e1J-pw-zGw" id="jj7-OZ-mOH"/> <outlet property="displayTypeButton" destination="ex3-VM-58z" id="lmZ-aN-lcj"/>
</connections> </connections>
<point key="canvasLocation" x="175" y="30"/> <point key="canvasLocation" x="-161" y="38.5"/>
</window> </window>
</objects> </objects>
</document> </document>

View File

@ -32,6 +32,7 @@ class MachinePanel: NSPanel {
switch tag { switch tag {
case 1: return .composite case 1: return .composite
case 2: return .sVideo case 2: return .sVideo
case 3: return .monochromeComposite
default: break default: break
} }
return .RGB return .RGB

View File

@ -24,7 +24,8 @@
typedef NS_ENUM(NSInteger, CSMachineVideoSignal) { typedef NS_ENUM(NSInteger, CSMachineVideoSignal) {
CSMachineVideoSignalComposite, CSMachineVideoSignalComposite,
CSMachineVideoSignalSVideo, CSMachineVideoSignalSVideo,
CSMachineVideoSignalRGB CSMachineVideoSignalRGB,
CSMachineVideoSignalMonochromeComposite
}; };
typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {

View File

@ -464,6 +464,7 @@ struct ActivityObserver: public Activity::Observer {
case CSMachineVideoSignalRGB: display = Configurable::Display::RGB; break; case CSMachineVideoSignalRGB: display = Configurable::Display::RGB; break;
case CSMachineVideoSignalSVideo: display = Configurable::Display::SVideo; break; case CSMachineVideoSignalSVideo: display = Configurable::Display::SVideo; break;
case CSMachineVideoSignalComposite: display = Configurable::Display::CompositeColour; break; case CSMachineVideoSignalComposite: display = Configurable::Display::CompositeColour; break;
case CSMachineVideoSignalMonochromeComposite: display = Configurable::Display::CompositeMonochrome; break;
} }
append_display_selection(selection_set, display); append_display_selection(selection_set, display);
configurable_device->set_selections(selection_set); configurable_device->set_selections(selection_set);
@ -486,6 +487,7 @@ struct ActivityObserver: public Activity::Observer {
case CSMachineVideoSignalRGB: option = Configurable::DisplayRGB; break; case CSMachineVideoSignalRGB: option = Configurable::DisplayRGB; break;
case CSMachineVideoSignalSVideo: option = Configurable::DisplaySVideo; break; case CSMachineVideoSignalSVideo: option = Configurable::DisplaySVideo; break;
case CSMachineVideoSignalComposite: option = Configurable::DisplayCompositeColour; break; case CSMachineVideoSignalComposite: option = Configurable::DisplayCompositeColour; break;
case CSMachineVideoSignalMonochromeComposite: option = Configurable::DisplayCompositeMonochrome; break;
} }
std::unique_ptr<Configurable::Option> display_option = std::move(standard_options(option).front()); std::unique_ptr<Configurable::Option> display_option = std::move(standard_options(option).front());
Configurable::ListOption *display_list_option = dynamic_cast<Configurable::ListOption *>(display_option.get()); Configurable::ListOption *display_list_option = dynamic_cast<Configurable::ListOption *>(display_option.get());

View File

@ -189,7 +189,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
- (NSString *)optionsPanelNibName { - (NSString *)optionsPanelNibName {
switch(_targets.front()->machine) { switch(_targets.front()->machine) {
case Analyser::Machine::AmstradCPC: return @"CompositeOptions"; case Analyser::Machine::AmstradCPC: return @"CompositeOptions";
// case Analyser::Machine::AppleII: return @"AppleIIOptions"; case Analyser::Machine::AppleII: return @"AppleIIOptions";
case Analyser::Machine::Atari2600: return @"Atari2600Options"; case Analyser::Machine::Atari2600: return @"Atari2600Options";
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions"; case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
case Analyser::Machine::MasterSystem: return @"CompositeOptions"; case Analyser::Machine::MasterSystem: return @"CompositeOptions";

View File

@ -53,7 +53,7 @@ Shader::Shader(const std::string &vertex_shader, const std::string &fragment_sha
GLuint index = 0; GLuint index = 0;
for(const auto &name: binding_names) { for(const auto &name: binding_names) {
bindings.emplace_back(name, index); bindings.emplace_back(name, index);
index += 4; ++index;
} }
init(vertex_shader, fragment_shader, bindings); init(vertex_shader, fragment_shader, bindings);
} }
@ -68,6 +68,21 @@ void Shader::init(const std::string &vertex_shader, const std::string &fragment_
for(const auto &binding : attribute_bindings) { for(const auto &binding : attribute_bindings) {
glBindAttribLocation(shader_program_, binding.index, binding.name.c_str()); glBindAttribLocation(shader_program_, binding.index, binding.name.c_str());
#ifndef NDEBUG
const auto error = glGetError();
switch(error) {
case 0: break;
case GL_INVALID_VALUE:
LOG("GL_INVALID_VALUE when attempting to bind " << binding.name << " to index " << binding.index << " (i.e. index is greater than or equal to GL_MAX_VERTEX_ATTRIBS)");
break;
case GL_INVALID_OPERATION:
LOG("GL_INVALID_OPERATION when attempting to bind " << binding.name << " to index " << binding.index << " (i.e. name begins with gl_)");
break;
default:
LOG("Error " << error << " when attempting to bind " << binding.name << " to index " << binding.index);
break;
}
#endif
} }
glLinkProgram(shader_program_); glLinkProgram(shader_program_);

View File

@ -50,7 +50,7 @@ public:
Attempts to compile a shader, throwing @c VertexShaderCompilationError, @c FragmentShaderCompilationError or @c ProgramLinkageError upon failure. Attempts to compile a shader, throwing @c VertexShaderCompilationError, @c FragmentShaderCompilationError or @c ProgramLinkageError upon failure.
@param vertex_shader The vertex shader source code. @param vertex_shader The vertex shader source code.
@param fragment_shader The fragment shader source code. @param fragment_shader The fragment shader source code.
@param binding_names A list of attributes to generate bindings for; these will be given indices 0, 4, 8 ... 4(n-1). @param binding_names A list of attributes to generate bindings for; these will be given indices 0, 1, 2 ... n-1.
*/ */
Shader(const std::string &vertex_shader, const std::string &fragment_shader, const std::vector<std::string> &binding_names); Shader(const std::string &vertex_shader, const std::string &fragment_shader, const std::vector<std::string> &binding_names);
~Shader(); ~Shader();

View File

@ -23,8 +23,15 @@ constexpr GLenum SourceDataTextureUnit = GL_TEXTURE0;
/// The texture unit which contains raw line-by-line composite, S-Video or RGB data. /// The texture unit which contains raw line-by-line composite, S-Video or RGB data.
constexpr GLenum UnprocessedLineBufferTextureUnit = GL_TEXTURE1; 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. /// The texture unit that contains the current display.
constexpr GLenum AccumulationTextureUnit = GL_TEXTURE2; constexpr GLenum AccumulationTextureUnit = GL_TEXTURE3;
#define TextureAddress(x, y) (((y) << 11) | (x)) #define TextureAddress(x, y) (((y) << 11) | (x))
#define TextureAddressGetY(v) uint16_t((v) >> 11) #define TextureAddressGetY(v) uint16_t((v) >> 11)
@ -299,19 +306,34 @@ void ScanTarget::setup_pipeline() {
write_pointers_.write_area = 0; write_pointers_.write_area = 0;
} }
// Pick a processing width; this will be the minimum necessary not to // Prepare to bind line shaders.
// lose any detail when combining the input. glBindVertexArray(line_vertex_array_);
processing_width_ = modals_.cycles_per_line / modals_.clocks_per_pixel_greatest_common_divisor; glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_);
// Destroy or create a QAM buffer and shader, if appropriate.
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_.reset(new TextureTarget(LineBufferWidth, LineBufferHeight, QAMChromaTextureUnit, GL_NEAREST, false));
}
qam_separation_shader_ = qam_separation_shader();
enable_vertex_attributes(ShaderType::QAMSeparation, *qam_separation_shader_);
set_uniforms(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. // Establish an output shader.
output_shader_ = conversion_shader(); output_shader_ = conversion_shader();
glBindVertexArray(line_vertex_array_);
glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_);
enable_vertex_attributes(ShaderType::Conversion, *output_shader_); enable_vertex_attributes(ShaderType::Conversion, *output_shader_);
set_uniforms(ShaderType::Conversion, *output_shader_); set_uniforms(ShaderType::Conversion, *output_shader_);
output_shader_->set_uniform("origin", modals_.visible_area.origin.x, modals_.visible_area.origin.y); output_shader_->set_uniform("origin", modals_.visible_area.origin.x, modals_.visible_area.origin.y);
output_shader_->set_uniform("size", modals_.visible_area.size.width, modals_.visible_area.size.height); output_shader_->set_uniform("size", modals_.visible_area.size.width, modals_.visible_area.size.height);
output_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0)); output_shader_->set_uniform("textureName", GLint(UnprocessedLineBufferTextureUnit - GL_TEXTURE0));
output_shader_->set_uniform("qamTextureName", GLint(QAMChromaTextureUnit - GL_TEXTURE0));
// Establish an input shader. // Establish an input shader.
input_shader_ = composition_shader(); input_shader_ = composition_shader();
@ -484,35 +506,37 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) {
stencil_is_valid_ = false; stencil_is_valid_ = false;
} }
// Figure out how many new spans are ostensible ready; use two less than that. // Figure out how many new lines are ready.
uint16_t new_spans = (submit_pointers.line + LineBufferHeight - read_pointers.line) % LineBufferHeight; uint16_t new_lines = (submit_pointers.line + LineBufferHeight - read_pointers.line) % LineBufferHeight;
if(new_spans) { if(new_lines) {
// Bind the accumulation framebuffer. // Prepare to output lines.
glBindVertexArray(line_vertex_array_);
// Bind the accumulation framebuffer, unless there's going to be QAM work.
if(!qam_separation_shader_) {
accumulation_texture_->bind_framebuffer(); accumulation_texture_->bind_framebuffer();
output_shader_->bind();
// Enable blending and stenciling, and ensure spans increment the stencil buffer. // Enable blending and stenciling, and ensure spans increment the stencil buffer.
glEnable(GL_BLEND); glEnable(GL_BLEND);
glEnable(GL_STENCIL_TEST); glEnable(GL_STENCIL_TEST);
}
glStencilFunc(GL_EQUAL, 0, GLuint(~0)); glStencilFunc(GL_EQUAL, 0, GLuint(~0));
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
// Prepare to output lines.
glBindVertexArray(line_vertex_array_);
output_shader_->bind();
// Prepare to upload data that will consitute lines. // Prepare to upload data that will consitute lines.
glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_); glBindBuffer(GL_ARRAY_BUFFER, line_buffer_name_);
// Divide spans by which frame they're in. // Divide spans by which frame they're in.
uint16_t start_line = read_pointers.line; uint16_t start_line = read_pointers.line;
while(new_spans) { while(new_lines) {
uint16_t end_line = start_line+1; uint16_t end_line = start_line+1;
// Find the limit of spans to draw in this cycle. // Find the limit of spans to draw in this cycle.
size_t spans = 1; size_t lines = 1;
while(end_line != submit_pointers.line && !line_metadata_buffer_[end_line].is_first_in_frame) { while(end_line != submit_pointers.line && !line_metadata_buffer_[end_line].is_first_in_frame) {
end_line = (end_line + 1) % LineBufferHeight; end_line = (end_line + 1) % LineBufferHeight;
++spans; ++lines;
} }
// If this is start-of-frame, clear any untouched pixels and flush the stencil buffer // If this is start-of-frame, clear any untouched pixels and flush the stencil buffer
@ -525,11 +549,13 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) {
// Rebind the program for span output. // Rebind the program for span output.
glBindVertexArray(line_vertex_array_); glBindVertexArray(line_vertex_array_);
if(!qam_separation_shader_) {
output_shader_->bind(); output_shader_->bind();
} }
}
// Upload and draw. // Upload.
const auto buffer_size = spans * sizeof(Line); const auto buffer_size = lines * sizeof(Line);
if(!end_line || end_line > start_line) { if(!end_line || end_line > start_line) {
glBufferSubData(GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size), &line_buffer_[start_line]); glBufferSubData(GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size), &line_buffer_[start_line]);
} else { } else {
@ -547,10 +573,28 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) {
glUnmapBuffer(GL_ARRAY_BUFFER); glUnmapBuffer(GL_ARRAY_BUFFER);
} }
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(spans)); // Produce colour information, if required.
if(qam_separation_shader_) {
qam_separation_shader_->bind();
qam_chroma_texture_->bind_framebuffer();
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.
glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(lines));
accumulation_texture_->bind_framebuffer();
output_shader_->bind();
glEnable(GL_BLEND);
glEnable(GL_STENCIL_TEST);
}
// Render to the output.
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, GLsizei(lines));
start_line = end_line; start_line = end_line;
new_spans -= spans; new_lines -= lines;
} }
// Disable blending and the stencil test again. // Disable blending and the stencil test again.

View File

@ -124,6 +124,10 @@ class ScanTarget: public Outputs::Display::ScanTarget {
// application of any necessary conversions — e.g. composite processing. // application of any necessary conversions — e.g. composite processing.
TextureTarget unprocessed_line_texture_; TextureTarget unprocessed_line_texture_;
// Contains pre-lowpass-filtered chrominance information that is
// part-QAM-demoduled, if dealing with a QAM data source.
std::unique_ptr<TextureTarget> qam_chroma_texture_;
// Scans are accumulated to the accumulation texture; the full-display // Scans are accumulated to the accumulation texture; the full-display
// rectangle is used to ensure untouched pixels properly decay. // rectangle is used to ensure untouched pixels properly decay.
std::unique_ptr<TextureTarget> accumulation_texture_; std::unique_ptr<TextureTarget> accumulation_texture_;
@ -165,7 +169,8 @@ class ScanTarget: public Outputs::Display::ScanTarget {
enum class ShaderType { enum class ShaderType {
Composition, Composition,
Conversion Conversion,
QAMSeparation
}; };
/*! /*!
@ -173,14 +178,16 @@ class ScanTarget: public Outputs::Display::ScanTarget {
globals for shaders of @c type to @c target. globals for shaders of @c type to @c target.
*/ */
static void enable_vertex_attributes(ShaderType type, Shader &target); static void enable_vertex_attributes(ShaderType type, Shader &target);
void set_uniforms(ShaderType type, Shader &target); void set_uniforms(ShaderType type, Shader &target) const;
std::vector<std::string> bindings(ShaderType type) const;
GLsync fence_ = nullptr; GLsync fence_ = nullptr;
std::atomic_flag is_drawing_; std::atomic_flag is_drawing_;
int processing_width_ = 0;
std::unique_ptr<Shader> input_shader_; std::unique_ptr<Shader> input_shader_;
std::unique_ptr<Shader> output_shader_; std::unique_ptr<Shader> output_shader_;
std::unique_ptr<Shader> qam_separation_shader_;
/*! /*!
Produces a shader that composes fragment of the input stream to a single buffer, Produces a shader that composes fragment of the input stream to a single buffer,
@ -193,6 +200,14 @@ class ScanTarget: public Outputs::Display::ScanTarget {
output RGB, decoding composite or S-Video as necessary. output RGB, decoding composite or S-Video as necessary.
*/ */
std::unique_ptr<Shader> conversion_shader() const; std::unique_ptr<Shader> conversion_shader() const;
/*!
Produces a shader that writes separated but not-yet filtered QAM components
from the unprocessed line texture to the QAM chroma texture, at a fixed
size of four samples per colour clock, point sampled.
*/
std::unique_ptr<Shader> qam_separation_shader() const;
std::string sampling_function() const;
}; };
} }

View File

@ -12,17 +12,46 @@
using namespace Outputs::Display::OpenGL; using namespace Outputs::Display::OpenGL;
void Outputs::Display::OpenGL::ScanTarget::set_uniforms(ShaderType type, Shader &target) { // MARK: - State setup for compiled shaders.
void Outputs::Display::OpenGL::ScanTarget::set_uniforms(ShaderType type, Shader &target) const {
// Slightly over-amping rowHeight here is a cheap way to make sure that lines // Slightly over-amping rowHeight here is a cheap way to make sure that lines
// converge even allowing for the fact that they may not be spaced by exactly // converge even allowing for the fact that they may not be spaced by exactly
// the expected distance. Cf. the stencil-powered logic for making sure all // the expected distance. Cf. the stencil-powered logic for making sure all
// pixels are painted only exactly once per field. // pixels are painted only exactly once per field.
switch(type) { switch(type) {
default: break; case ShaderType::Composition: break;
case ShaderType::Conversion: default:
target.set_uniform("rowHeight", GLfloat(1.05f / modals_.expected_vertical_lines)); target.set_uniform("rowHeight", GLfloat(1.05f / modals_.expected_vertical_lines));
target.set_uniform("scale", GLfloat(modals_.output_scale.x), GLfloat(modals_.output_scale.y)); target.set_uniform("scale", GLfloat(modals_.output_scale.x), GLfloat(modals_.output_scale.y));
target.set_uniform("phaseOffset", GLfloat(modals_.input_data_tweaks.phase_linked_luminance_offset)); target.set_uniform("phaseOffset", GLfloat(modals_.input_data_tweaks.phase_linked_luminance_offset));
const float clocks_per_angle = float(modals_.cycles_per_line) * float(modals_.colour_cycle_denominator) / float(modals_.colour_cycle_numerator);
GLfloat texture_offsets[4];
GLfloat angles[4];
for(int c = 0; c < 4; ++c) {
GLfloat angle = (GLfloat(c) - 1.5f) / 4.0f;
texture_offsets[c] = angle * clocks_per_angle;
angles[c] = GLfloat(angle * 2.0f * M_PI);
}
target.set_uniform("textureCoordinateOffsets", 1, 4, texture_offsets);
target.set_uniform("compositeAngleOffsets", 4, 1, angles);
switch(modals_.composite_colour_space) {
case ColourSpace::YIQ: {
const GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f};
const GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f};
target.set_uniform_matrix("lumaChromaToRGB", 3, false, yiqToRGB);
target.set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYIQ);
} break;
case ColourSpace::YUV: {
const GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f};
const GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f};
target.set_uniform_matrix("lumaChromaToRGB", 3, false, yuvToRGB);
target.set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYUV);
} break;
}
break; break;
} }
} }
@ -69,16 +98,18 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) {
1); 1);
break; break;
case ShaderType::Conversion: default:
for(int c = 0; c < 2; ++c) { for(int c = 0; c < 2; ++c) {
const std::string prefix = c ? "end" : "start"; const std::string prefix = c ? "end" : "start";
if(type == ShaderType::Conversion) {
target.enable_vertex_attribute_with_pointer( target.enable_vertex_attribute_with_pointer(
prefix + "Point", prefix + "Point",
2, GL_UNSIGNED_SHORT, GL_FALSE, 2, GL_UNSIGNED_SHORT, GL_FALSE,
sizeof(Line), sizeof(Line),
reinterpret_cast<void *>(rt_offset_of(end_points[c].x, test_line)), reinterpret_cast<void *>(rt_offset_of(end_points[c].x, test_line)),
1); 1);
}
target.enable_vertex_attribute_with_pointer( target.enable_vertex_attribute_with_pointer(
prefix + "Clock", prefix + "Clock",
@ -113,6 +144,331 @@ void ScanTarget::enable_vertex_attributes(ShaderType type, Shader &target) {
#undef rt_offset_of #undef rt_offset_of
} }
std::vector<std::string> ScanTarget::bindings(ShaderType type) const {
switch(type) {
case ShaderType::Composition: return {
"startDataX",
"startClock",
"endDataX",
"endClock",
"dataY",
"lineY"
};
default: return {
"startPoint",
"endPoint",
"startClock",
"endClock",
"lineY",
"lineCompositeAmplitude",
"startCompositeAngle",
"endCompositeAngle"
};
}
}
// MARK: - Shader code.
std::string ScanTarget::sampling_function() const {
std::string fragment_shader;
if(modals_.display_type == DisplayType::SVideo) {
fragment_shader +=
"vec2 svideo_sample(vec2 coordinate, float angle) {";
} else {
fragment_shader +=
"float composite_sample(vec2 coordinate, float angle) {";
}
const bool is_svideo = modals_.display_type == DisplayType::SVideo;
switch(modals_.input_data_type) {
case InputDataType::Luminance1:
case InputDataType::Luminance8:
// Easy, just copy across.
fragment_shader +=
is_svideo ?
"return vec2(textureLod(textureName, coordinate, 0).r, 0.0);" :
"return textureLod(textureName, coordinate, 0).r;";
break;
case InputDataType::PhaseLinkedLuminance8:
fragment_shader +=
"uint iPhase = uint((angle * 2.0 / 3.141592654) ) & 3u;";
fragment_shader +=
is_svideo ?
"return vec2(textureLod(textureName, coordinate, 0)[iPhase], 0.0);" :
"return textureLod(textureName, coordinate, 0)[iPhase];";
break;
case InputDataType::Luminance8Phase8:
fragment_shader +=
"vec2 yc = textureLod(textureName, coordinate, 0).rg;"
"float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;"
"float rawChroma = step(yc.y, 0.75) * cos(angle + phaseOffset);";
fragment_shader +=
is_svideo ?
"return vec2(yc.x, rawChroma);" :
"return mix(yc.x, rawChroma, compositeAmplitude);";
break;
case InputDataType::Red1Green1Blue1:
case InputDataType::Red2Green2Blue2:
case InputDataType::Red4Green4Blue4:
case InputDataType::Red8Green8Blue8:
fragment_shader +=
"vec3 colour = rgbToLumaChroma * textureLod(textureName, coordinate, 0).rgb;"
"vec2 quadrature = vec2(cos(angle), sin(angle));";
fragment_shader +=
is_svideo ?
"return vec2(colour.r, dot(quadrature, colour.gb));" :
"return mix(colour.r, dot(quadrature, colour.gb), compositeAmplitude);";
break;
}
fragment_shader += "}";
return fragment_shader;
}
std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
// Compose a vertex shader. If the display type is RGB, generate just the proper
// geometry position, plus a solitary textureCoordinate.
//
// If the display type is anything other than RGB, also produce composite
// angle and 1/composite amplitude as outputs.
//
// If the display type is composite colour, generate four textureCoordinates,
// spanning a range of -135, -45, +45, +135 degrees.
//
// If the display type is S-Video, generate three textureCoordinates, at
// -45, 0, +45.
std::string vertex_shader =
"#version 150\n"
"uniform vec2 scale;"
"uniform float rowHeight;"
"in vec2 startPoint;"
"in vec2 endPoint;"
"in float startClock;"
"in float startCompositeAngle;"
"in float endClock;"
"in float endCompositeAngle;"
"in float lineY;"
"in float lineCompositeAmplitude;"
"uniform sampler2D textureName;"
"uniform sampler2D qamTextureName;"
"uniform vec2 origin;"
"uniform vec2 size;";
std::string fragment_shader =
"#version 150\n"
"uniform sampler2D textureName;"
"uniform sampler2D qamTextureName;"
"out vec4 fragColour;";
if(modals_.display_type != DisplayType::RGB) {
vertex_shader +=
"out float compositeAngle;"
"out float compositeAmplitude;"
"out float oneOverCompositeAmplitude;"
"uniform float textureCoordinateOffsets[4];"
"uniform float angleOffsets[4];";
fragment_shader +=
"in float compositeAngle;"
"in float compositeAmplitude;"
"in float oneOverCompositeAmplitude;"
"uniform vec4 compositeAngleOffsets;";
}
switch(modals_.display_type){
case DisplayType::RGB:
case DisplayType::CompositeMonochrome:
vertex_shader += "out vec2 textureCoordinate;";
fragment_shader += "in vec2 textureCoordinate;";
break;
case DisplayType::SVideo:
vertex_shader +=
"out vec2 textureCoordinate;"
"out vec2 qamTextureCoordinates[4];";
fragment_shader +=
"in vec2 textureCoordinate;"
"in vec2 qamTextureCoordinates[4];";
break;
case DisplayType::CompositeColour:
vertex_shader +=
"out vec2 textureCoordinates[4];"
"out vec2 qamTextureCoordinates[4];";
fragment_shader +=
"in vec2 textureCoordinates[4];"
"in vec2 qamTextureCoordinates[4];";
break;
}
// Add the code to generate a proper output position; this applies to all display types.
vertex_shader +=
"void main(void) {"
"float lateral = float(gl_VertexID & 1);"
"float longitudinal = float((gl_VertexID & 2) >> 1);"
"vec2 centrePoint = mix(startPoint, vec2(endPoint.x, startPoint.y), lateral) / scale;"
"vec2 height = normalize(vec2(endPoint.x, startPoint.y) - startPoint).yx * (longitudinal - 0.5) * rowHeight;"
"vec2 eyePosition = vec2(-1.0, 1.0) + vec2(2.0, -2.0) * (((centrePoint + height) - origin) / size);"
"gl_Position = vec4(eyePosition, 0.0, 1.0);";
// For everything other than RGB, calculate the two composite outputs.
if(modals_.display_type != DisplayType::RGB) {
vertex_shader +=
"compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;"
"compositeAmplitude = lineCompositeAmplitude / 255.0;"
"oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));";
}
// For RGB and monochrome composite, generate the single texture coordinate; otherwise generate either three
// or four depending on the type of decoding to apply.
switch(modals_.display_type) {
case DisplayType::RGB:
case DisplayType::CompositeMonochrome:
case DisplayType::SVideo:
vertex_shader +=
"textureCoordinate = vec2(mix(startClock, endClock, lateral), lineY + 0.5) / textureSize(textureName, 0);";
break;
case DisplayType::CompositeColour:
vertex_shader +=
"float centreClock = mix(startClock, endClock, lateral);"
"textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0);"
"textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / textureSize(textureName, 0);"
"textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / textureSize(textureName, 0);"
"textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / textureSize(textureName, 0);";
break;
}
if((modals_.display_type == DisplayType::SVideo) || (modals_.display_type == DisplayType::CompositeColour)) {
vertex_shader +=
"float centreCompositeAngle = abs(mix(startCompositeAngle, endCompositeAngle, lateral)) * 4.0 / 64.0;"
"qamTextureCoordinates[0] = vec2(centreCompositeAngle - 1.5, lineY + 0.5) / textureSize(textureName, 0);"
"qamTextureCoordinates[1] = vec2(centreCompositeAngle - 0.5, lineY + 0.5) / textureSize(textureName, 0);"
"qamTextureCoordinates[2] = vec2(centreCompositeAngle + 0.5, lineY + 0.5) / textureSize(textureName, 0);"
"qamTextureCoordinates[3] = vec2(centreCompositeAngle + 1.5, lineY + 0.5) / textureSize(textureName, 0);";
}
vertex_shader += "}";
// Compose a fragment shader.
if(modals_.display_type != DisplayType::RGB) {
fragment_shader +=
"uniform mat3 lumaChromaToRGB;"
"uniform mat3 rgbToLumaChroma;";
fragment_shader += sampling_function();
}
fragment_shader +=
"void main(void) {"
"vec3 fragColour3;";
switch(modals_.display_type) {
case DisplayType::RGB:
fragment_shader += "fragColour3 = textureLod(textureName, textureCoordinate, 0).rgb;";
break;
case DisplayType::SVideo:
fragment_shader +=
// Sample the S-Video stream once, to obtain luminance.
"vec2 sample = svideo_sample(textureCoordinate, compositeAngle);"
// Split and average chrominance.
"vec2 chrominances[4] = vec2[4]("
"textureLod(qamTextureName, qamTextureCoordinates[0], 0).gb,"
"textureLod(qamTextureName, qamTextureCoordinates[1], 0).gb,"
"textureLod(qamTextureName, qamTextureCoordinates[2], 0).gb,"
"textureLod(qamTextureName, qamTextureCoordinates[3], 0).gb"
");"
"vec2 channels = (chrominances[0] + chrominances[1] + chrominances[2] + chrominances[3])*0.5 - vec2(1.0);"
// Apply a colour space conversion to get RGB.
"fragColour3 = lumaChromaToRGB * vec3(sample.r, channels);";
break;
case DisplayType::CompositeColour:
fragment_shader +=
"vec4 angles = compositeAngle + compositeAngleOffsets;"
// Sample four times over, at proper angle offsets.
"vec4 samples = vec4("
"composite_sample(textureCoordinates[0], angles.x),"
"composite_sample(textureCoordinates[1], angles.y),"
"composite_sample(textureCoordinates[2], angles.z),"
"composite_sample(textureCoordinates[3], angles.w)"
");"
// Compute a luminance for use if there's no colour information, now, before
// modifying samples.
"float mono_luminance = dot(samples, vec4(0.15, 0.35, 0.35, 0.15));" // TODO: figure out proper coefficients.
// Take the average to calculate luminance, then subtract that from all four samples to
// give chrominance.
"float luminance = dot(samples, vec4(0.25));"
// Split and average chrominance.
"vec2 chrominances[4] = vec2[4]("
"textureLod(qamTextureName, qamTextureCoordinates[0], 0).gb,"
"textureLod(qamTextureName, qamTextureCoordinates[1], 0).gb,"
"textureLod(qamTextureName, qamTextureCoordinates[2], 0).gb,"
"textureLod(qamTextureName, qamTextureCoordinates[3], 0).gb"
");"
"vec2 channels = (chrominances[0] + chrominances[1] + chrominances[2] + chrominances[3])*0.5 - vec2(1.0);"
// Apply a colour space conversion to get RGB.
"fragColour3 = mix("
"lumaChromaToRGB * vec3(luminance / (1.0 - compositeAmplitude), channels),"
"vec3(mono_luminance),"
"step(oneOverCompositeAmplitude, 0.01)"
");";
break;
case DisplayType::CompositeMonochrome:
fragment_shader += "fragColour3 = vec3(composite_sample(textureCoordinate, compositeAngle));";
break;
}
// Apply a brightness adjustment if requested.
if(fabs(modals_.brightness - 1.0f) > 0.05f) {
fragment_shader += "fragColour3 = fragColour3 * " + std::to_string(modals_.brightness) + ";";
}
// Apply a gamma correction if required.
if(fabs(output_gamma_ - modals_.intended_gamma) > 0.05f) {
const float gamma_ratio = output_gamma_ / modals_.intended_gamma;
fragment_shader += "fragColour3 = pow(fragColour3, vec3(" + std::to_string(gamma_ratio) + "));";
}
fragment_shader +=
"fragColour = vec4(fragColour3, 0.64);"
"}";
return std::unique_ptr<Shader>(new Shader(
vertex_shader,
fragment_shader,
bindings(ShaderType::Conversion)
));
}
std::unique_ptr<Shader> ScanTarget::composition_shader() const { std::unique_ptr<Shader> ScanTarget::composition_shader() const {
const std::string vertex_shader = const std::string vertex_shader =
"#version 150\n" "#version 150\n"
@ -183,38 +539,18 @@ std::unique_ptr<Shader> ScanTarget::composition_shader() const {
return std::unique_ptr<Shader>(new Shader( return std::unique_ptr<Shader>(new Shader(
vertex_shader, vertex_shader,
fragment_shader + "}", fragment_shader + "}",
{ bindings(ShaderType::Composition)
"startDataX",
"startClock",
"endDataX",
"endClock",
"dataY",
"lineY",
}
)); ));
} }
std::unique_ptr<Shader> ScanTarget::conversion_shader() const { std::unique_ptr<Shader> ScanTarget::qam_separation_shader() const {
// Compose a vertex shader. If the display type is RGB, generate just the proper const bool is_svideo = modals_.display_type == DisplayType::SVideo;
// geometry position, plus a solitary textureCoordinate.
// // Sets up texture coordinates to run between startClock and endClock, mapping to
// If the display type is anything other than RGB, also produce composite // coordinates that correlate with four times the absolute value of the composite angle.
// angle and 1/composite amplitude as outputs.
//
// If the display type is composite colour, generate four textureCoordinates,
// spanning a range of -135, -45, +45, +135 degrees.
//
// If the display type is S-Video, generate three textureCoordinates, at
// -45, 0, +45.
std::string vertex_shader = std::string vertex_shader =
"#version 150\n" "#version 150\n"
"uniform vec2 scale;"
"uniform float rowHeight;"
"in vec2 startPoint;"
"in vec2 endPoint;"
"in float startClock;" "in float startClock;"
"in float startCompositeAngle;" "in float startCompositeAngle;"
"in float endClock;" "in float endClock;"
@ -224,311 +560,96 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
"in float lineCompositeAmplitude;" "in float lineCompositeAmplitude;"
"uniform sampler2D textureName;" "uniform sampler2D textureName;"
"uniform vec2 origin;" "uniform float textureCoordinateOffsets[4];"
"uniform vec2 size;";
"out float compositeAngle;"
"out float compositeAmplitude;"
"out float oneOverCompositeAmplitude;";
std::string fragment_shader = std::string fragment_shader =
"#version 150\n" "#version 150\n"
"uniform sampler2D textureName;" "uniform sampler2D textureName;"
"uniform mat3 rgbToLumaChroma;"
"out vec4 fragColour;";
if(modals_.display_type != DisplayType::RGB) {
vertex_shader +=
"out float compositeAngle;"
"out float compositeAmplitude;"
"out float oneOverCompositeAmplitude;"
"uniform float textureCoordinateOffsets[7];"
"uniform float angleOffsets[4];";
fragment_shader +=
"in float compositeAngle;" "in float compositeAngle;"
"in float compositeAmplitude;" "in float compositeAmplitude;"
"in float oneOverCompositeAmplitude;" "in float oneOverCompositeAmplitude;"
"uniform vec4 compositeAngleOffsets[2];"; "out vec4 fragColour;"
} "uniform vec4 compositeAngleOffsets;";
switch(modals_.display_type){ if(is_svideo) {
case DisplayType::RGB:
case DisplayType::CompositeMonochrome:
vertex_shader += "out vec2 textureCoordinate;"; vertex_shader += "out vec2 textureCoordinate;";
fragment_shader += "in vec2 textureCoordinate;"; fragment_shader += "in vec2 textureCoordinate;";
break; } else {
vertex_shader += "out vec2 textureCoordinates[4];";
case DisplayType::CompositeColour: fragment_shader += "in vec2 textureCoordinates[4];";
case DisplayType::SVideo:
vertex_shader +=
"out vec2 textureCoordinates[7];";
fragment_shader +=
"in vec2 textureCoordinates[7];";
break;
} }
// Add the code to generate a proper output position; this applies to all display types.
vertex_shader += vertex_shader +=
"void main(void) {" "void main(void) {"
"float lateral = float(gl_VertexID & 1);" "float lateral = float(gl_VertexID & 1);"
"float longitudinal = float((gl_VertexID & 2) >> 1);" "float longitudinal = float((gl_VertexID & 2) >> 1);"
"vec2 centrePoint = mix(startPoint, vec2(endPoint.x, startPoint.y), lateral) / scale;" "float centreClock = mix(startClock, endClock, lateral);"
"vec2 height = normalize(vec2(endPoint.x, startPoint.y) - startPoint).yx * (longitudinal - 0.5) * rowHeight;"
"vec2 eyePosition = vec2(-1.0, 1.0) + vec2(2.0, -2.0) * (((centrePoint + height) - origin) / size);"
"gl_Position = vec4(eyePosition, 0.0, 1.0);";
// For everything other than RGB, calculate the two composite outputs. "compositeAngle = mix(startCompositeAngle, endCompositeAngle, lateral) / 64.0;"
if(modals_.display_type != DisplayType::RGB) {
vertex_shader += "vec2 eyePosition = vec2(abs(compositeAngle) * 4.0, lineY + longitudinal) / vec2(2048.0, 2048.0);"
"compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;" "gl_Position = vec4(eyePosition*2.0 - vec2(1.0), 0.0, 1.0);"
"compositeAngle = compositeAngle * 2.0 * 3.141592654;"
"compositeAmplitude = lineCompositeAmplitude / 255.0;" "compositeAmplitude = lineCompositeAmplitude / 255.0;"
"oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));"; "oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));";
}
// For RGB and monochrome composite, generate the single texture coordinate; otherwise generate either three if(is_svideo) {
// or four depending on the type of decoding to apply.
switch(modals_.display_type){
case DisplayType::RGB:
case DisplayType::CompositeMonochrome:
vertex_shader += vertex_shader +=
"textureCoordinate = vec2(mix(startClock, endClock, lateral), lineY + 0.5) / textureSize(textureName, 0);"; "textureCoordinate = vec2(centreClock, lineY + 0.5) / textureSize(textureName, 0);";
break; } else {
case DisplayType::CompositeColour:
case DisplayType::SVideo:
vertex_shader += vertex_shader +=
"float centreClock = mix(startClock, endClock, lateral);"
"textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0);" "textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0);"
"textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / textureSize(textureName, 0);" "textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / textureSize(textureName, 0);"
"textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / textureSize(textureName, 0);" "textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / textureSize(textureName, 0);"
"textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / textureSize(textureName, 0);" "textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / textureSize(textureName, 0);";
"textureCoordinates[4] = vec2(centreClock + textureCoordinateOffsets[4], lineY + 0.5) / textureSize(textureName, 0);"
"textureCoordinates[5] = vec2(centreClock + textureCoordinateOffsets[5], lineY + 0.5) / textureSize(textureName, 0);"
"textureCoordinates[6] = vec2(centreClock + textureCoordinateOffsets[6], lineY + 0.5) / textureSize(textureName, 0);";
break;
} }
vertex_shader += "}"; vertex_shader += "}";
// Compose a fragment shader.
if(modals_.display_type != DisplayType::RGB) {
fragment_shader += fragment_shader +=
"uniform mat3 lumaChromaToRGB;" sampling_function() +
"uniform mat3 rgbToLumaChroma;"; "void main(void) {";
if(modals_.display_type == DisplayType::SVideo) { if(modals_.display_type == DisplayType::SVideo) {
fragment_shader += fragment_shader +=
"vec2 svideo_sample(vec2 coordinate, float angle) {"; "fragColour = vec4(svideo_sample(textureCoordinate, compositeAngle).rgg * vec3(1.0, cos(compositeAngle), sin(compositeAngle)), 1.0);";
} else { } else {
fragment_shader += fragment_shader +=
"float composite_sample(vec2 coordinate, float angle) {"; "vec4 angles = compositeAngle + compositeAngleOffsets;"
}
const bool is_svideo = modals_.display_type == DisplayType::SVideo;
switch(modals_.input_data_type) {
case InputDataType::Luminance1:
case InputDataType::Luminance8:
// Easy, just copy across.
fragment_shader +=
is_svideo ?
"return vec2(textureLod(textureName, coordinate, 0).r, 0.0);" :
"return textureLod(textureName, coordinate, 0).r;";
break;
case InputDataType::PhaseLinkedLuminance8:
fragment_shader +=
"uint iPhase = uint((angle * 2.0 / 3.141592654) ) & 3u;";
fragment_shader +=
is_svideo ?
"return vec2(textureLod(textureName, coordinate, 0)[iPhase], 0.0);" :
"return textureLod(textureName, coordinate, 0)[iPhase];";
break;
case InputDataType::Luminance8Phase8:
fragment_shader +=
"vec2 yc = textureLod(textureName, coordinate, 0).rg;"
"float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;"
"float rawChroma = step(yc.y, 0.75) * cos(angle + phaseOffset);";
fragment_shader +=
is_svideo ?
"return vec2(yc.x, rawChroma);" :
"return mix(yc.x, rawChroma, compositeAmplitude);";
break;
case InputDataType::Red1Green1Blue1:
case InputDataType::Red2Green2Blue2:
case InputDataType::Red4Green4Blue4:
case InputDataType::Red8Green8Blue8:
fragment_shader +=
"vec3 colour = rgbToLumaChroma * textureLod(textureName, coordinate, 0).rgb;"
"vec2 quadrature = vec2(cos(angle), sin(angle));";
fragment_shader +=
is_svideo ?
"return vec2(colour.r, dot(quadrature, colour.gb));" :
"return mix(colour.r, dot(quadrature, colour.gb), compositeAmplitude);";
break;
}
fragment_shader += "}";
}
fragment_shader +=
"void main(void) {"
"vec3 fragColour3;";
switch(modals_.display_type) {
case DisplayType::RGB:
fragment_shader += "fragColour3 = textureLod(textureName, textureCoordinate, 0).rgb;";
break;
case DisplayType::SVideo:
fragment_shader +=
// Sample four times over, at proper angle offsets.
"vec2 samples[4] = vec2[4]("
"svideo_sample(textureCoordinates[0], angles[0]),"
"svideo_sample(textureCoordinates[1], angles[1]),"
"svideo_sample(textureCoordinates[2], angles[2]),"
"svideo_sample(textureCoordinates[3], angles[3])"
");"
"vec4 chrominances = vec4("
"samples[0].y,"
"samples[1].y,"
"samples[2].y,"
"samples[3].y"
");"
// Split and average chrominance.
"vec2 channels = vec2("
"dot(cos(angles), chrominances),"
"dot(sin(angles), chrominances)"
") * vec2(0.25);"
// Apply a colour space conversion to get RGB.
"fragColour3 = lumaChromaToRGB * vec3(samples[1].x, channels);";
break;
case DisplayType::CompositeColour:
fragment_shader +=
"vec4 angles[2] = vec4[2]("
"vec4(compositeAngle) + compositeAngleOffsets[0],"
"vec4(compositeAngle) + compositeAngleOffsets[1]"
");"
// Sample four times over, at proper angle offsets. // Sample four times over, at proper angle offsets.
"vec4 samples[2] = vec4[2](vec4(" "vec4 samples = vec4("
"composite_sample(textureCoordinates[0], angles[0].x)," "composite_sample(textureCoordinates[0], angles.x),"
"composite_sample(textureCoordinates[1], angles[0].y)," "composite_sample(textureCoordinates[1], angles.y),"
"composite_sample(textureCoordinates[2], angles[0].z)," "composite_sample(textureCoordinates[2], angles.z),"
"composite_sample(textureCoordinates[3], angles[0].w)" "composite_sample(textureCoordinates[3], angles.w)"
"), vec4(" ");"
"composite_sample(textureCoordinates[4], angles[1].x),"
"composite_sample(textureCoordinates[5], angles[1].y),"
"composite_sample(textureCoordinates[6], angles[1].z),"
"0.0"
"));"
// Compute a luminance for use if there's no colour information, now, before
// modifying samples.
"float mono_luminance = dot(vec3(samples[0].zw, samples[1].x), vec3(0.15, 0.7, 0.15));"
// Take the average to calculate luminance, then subtract that from all four samples to // Take the average to calculate luminance, then subtract that from all four samples to
// give chrominance. // give chrominance.
"float luminances[4] = float[4](" "float luminance = dot(samples, vec4(0.25));"
"dot(samples[0], vec4(0.25))," "float chrominance = (dot(samples.yz, vec2(0.5)) - luminance) * oneOverCompositeAmplitude;"
"dot(vec4(samples[0].yzw, samples[1].x), vec4(0.25)),"
"dot(vec4(samples[0].zw, samples[1].xy), vec4(0.25)),"
"dot(vec4(samples[0].w, samples[1].xyz), vec4(0.25))"
");"
// Split and average chrominance. // Pack that all up and send it on its way.
"vec4 chrominances = vec4(" "fragColour = vec4(luminance, vec2(cos(compositeAngle), sin(compositeAngle)) * chrominance, 1.0);";
"samples[0].y - luminances[0]," };
"samples[0].z - luminances[1],"
"samples[0].w - luminances[2],"
"samples[1].x - luminances[3]"
");"
"vec4 chrominance_angles = vec4(angles[0].yzw, angles[1].x);"
"vec2 channels = vec2("
"dot(cos(chrominance_angles), chrominances),"
"dot(sin(chrominance_angles), chrominances)"
") * vec2(0.125 * oneOverCompositeAmplitude);"
// Apply a colour space conversion to get RGB.
"fragColour3 = mix("
"lumaChromaToRGB * vec3(luminances[2] / (1.0 - compositeAmplitude), channels),"
"vec3(mono_luminance),"
"step(oneOverCompositeAmplitude, 0.01)"
");";
break;
case DisplayType::CompositeMonochrome:
fragment_shader += "fragColour3 = vec3(composite_sample(textureCoordinate, compositeAngle));";
break;
}
// Apply a brightness adjustment if requested.
if(fabs(modals_.brightness - 1.0f) > 0.05f) {
fragment_shader += "fragColour3 = fragColour3 * " + std::to_string(modals_.brightness) + ";";
}
// Apply a gamma correction if required.
if(fabs(output_gamma_ - modals_.intended_gamma) > 0.05f) {
const float gamma_ratio = output_gamma_ / modals_.intended_gamma;
fragment_shader += "fragColour3 = pow(fragColour3, vec3(" + std::to_string(gamma_ratio) + "));";
}
fragment_shader += fragment_shader +=
"fragColour = vec4(fragColour3, 0.64);" "fragColour = fragColour*0.5 + vec4(0.5);"
"}"; "}";
const auto shader = new Shader( return std::unique_ptr<Shader>(new Shader(
vertex_shader, vertex_shader,
fragment_shader, fragment_shader,
{ bindings(ShaderType::QAMSeparation)
"startPoint", ));
"endPoint",
"startClock",
"endClock",
"lineY",
"lineCompositeAmplitude",
"startCompositeAngle",
"endCompositeAngle"
}
);
// If this isn't an RGB or composite colour shader, set the proper colour space.
if(modals_.display_type != DisplayType::RGB) {
const float clocks_per_angle = float(modals_.cycles_per_line) * float(modals_.colour_cycle_denominator) / float(modals_.colour_cycle_numerator);
GLfloat texture_offsets[7];
GLfloat angles[8];
for(int c = 0; c < 7; ++c) {
GLfloat angle = (GLfloat(c) - 3.5f) / 4.0f;
texture_offsets[c] = angle * clocks_per_angle;
angles[c] = GLfloat(angle * 2.0f * M_PI);
}
shader->set_uniform("textureCoordinateOffsets", 1, 7, texture_offsets);
shader->set_uniform("compositeAngleOffsets", 4, 2, angles);
switch(modals_.composite_colour_space) {
case ColourSpace::YIQ: {
const GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f};
const GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f};
shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yiqToRGB);
shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYIQ);
} break;
case ColourSpace::YUV: {
const GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f};
const GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f};
shader->set_uniform_matrix("lumaChromaToRGB", 3, false, yuvToRGB);
shader->set_uniform_matrix("rgbToLumaChroma", 3, false, rgbToYUV);
} break;
}
}
return std::unique_ptr<Shader>(shader);
} }