1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-28 22:30:30 +00:00

Merge pull request #2 from TomHarte/PixelAccumulation

Switches to in-framebuffer pixel accumulation
This commit is contained in:
Thomas Harte 2016-05-02 21:18:38 -04:00
commit c72e360012
16 changed files with 1197 additions and 906 deletions

View File

@ -303,11 +303,12 @@
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */; };
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; };
4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; };
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; };
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */; };
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
4BBF99161C8FBA6F0075DAFB /* CRTRunBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */; };
4BBF99171C8FBA6F0075DAFB /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */; };
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
4BCB70B41C947DDC005B1712 /* plus1.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BCB70B31C947DDC005B1712 /* plus1.rom */; };
@ -656,19 +657,21 @@
4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clock_SignalUITests.swift; sourceTree = "<group>"; };
4BB73EC31B587A5100552FC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "Clock Signal.entitlements"; sourceTree = "<group>"; };
4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; };
4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; };
4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTInputBufferBuilder.cpp; sourceTree = "<group>"; };
4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTInputBufferBuilder.hpp; sourceTree = "<group>"; };
4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = "<group>"; };
4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTOpenGL.hpp; sourceTree = "<group>"; };
4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTRunBuilder.cpp; sourceTree = "<group>"; };
4BBF990D1C8FBA6F0075DAFB /* CRTRunBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTRunBuilder.hpp; sourceTree = "<group>"; };
4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = "<group>"; };
4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; };
4BBF99101C8FBA6F0075DAFB /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = "<group>"; };
4BBF99111C8FBA6F0075DAFB /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = "<group>"; };
4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = "<group>"; };
4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; };
4BC3B74D1CD194CC00F86E85 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = "<group>"; };
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = "<group>"; };
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
@ -1206,16 +1209,13 @@
4BBF99071C8FBA6F0075DAFB /* Internals */ = {
isa = PBXGroup;
children = (
4BC3B74C1CD194CC00F86E85 /* Shaders */,
4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */,
4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */,
4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */,
4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */,
4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */,
4BBF990D1C8FBA6F0075DAFB /* CRTRunBuilder.hpp */,
4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */,
4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */,
4BBF99101C8FBA6F0075DAFB /* Shader.cpp */,
4BBF99111C8FBA6F0075DAFB /* Shader.hpp */,
4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */,
4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */,
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */,
@ -1223,6 +1223,19 @@
path = Internals;
sourceTree = "<group>";
};
4BC3B74C1CD194CC00F86E85 /* Shaders */ = {
isa = PBXGroup;
children = (
4BC3B74D1CD194CC00F86E85 /* Shader.cpp */,
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */,
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */,
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */,
4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */,
4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */,
);
path = Shaders;
sourceTree = "<group>";
};
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
isa = PBXGroup;
children = (
@ -1652,10 +1665,11 @@
4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */,
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */,
4BBF99161C8FBA6F0075DAFB /* CRTRunBuilder.cpp in Sources */,
4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */,
4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */,
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */,
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */,
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
@ -1664,10 +1678,10 @@
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
4BBF99171C8FBA6F0075DAFB /* Shader.cpp in Sources */,
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */,
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */,
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */,
);

View File

@ -66,12 +66,15 @@
}
- (void)clearAllKeys {
@synchronized(self) {
_electron.clear_all_keys();
}
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
@synchronized(self) {
_electron.clear_all_keys();
}
// });
}
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
@synchronized(self) {
switch(key)
{
@ -149,13 +152,16 @@
break;
}
}
// });
}
- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack {
@synchronized(self) {
_useFastLoadingHack = useFastLoadingHack;
_electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false);
}
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
@synchronized(self) {
_useFastLoadingHack = useFastLoadingHack;
_electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false);
}
// });
}
- (void)setUseTelevisionOutput:(BOOL)useTelevisionOutput {

View File

@ -10,6 +10,7 @@
#include "CRTOpenGL.hpp"
#include <stdarg.h>
#include <math.h>
#include <algorithm>
using namespace Outputs::CRT;
@ -27,7 +28,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
// a TV picture tube or camera tube to the starting point of a line or field. It is about 7 µs
// for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV."
_time_multiplier = (2000 + cycles_per_line - 1) / cycles_per_line;
_time_multiplier = IntermediateBufferWidth / cycles_per_line;
// store fundamental display configuration properties
_height_of_display = height_of_display;
@ -44,7 +45,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
unsigned int real_clock_scan_period = (_cycles_per_line * height_of_display) / (_time_multiplier * _common_output_divisor);
_vertical_flywheel_output_divider = (uint16_t)(ceilf(real_clock_scan_period / 65536.0f) * (_time_multiplier * _common_output_divisor));
_openGL_output_builder->set_timing(_cycles_per_line, _height_of_display, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period(), _vertical_flywheel_output_divider);
_openGL_output_builder->set_timing(cycles_per_line, _cycles_per_line, _height_of_display, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period(), _vertical_flywheel_output_divider);
}
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType)
@ -99,8 +100,6 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested,
#define output_tex_x(v) (*(uint16_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTexCoord + 0])
#define output_tex_y(v) (*(uint16_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTexCoord + 2])
#define output_lateral(v) next_run[OutputVertexSize*v + OutputVertexOffsetOfLateral]
#define output_frame_id(v) next_run[OutputVertexSize*v + OutputVertexOffsetOfFrameID]
#define output_timestamp(v) (*(uint32_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTimestamp])
#define source_input_position_x(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfInputPosition + 0])
#define source_input_position_y(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfInputPosition + 2])
@ -134,7 +133,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
uint8_t *next_run = nullptr;
if(is_output_segment)
{
next_run = (_openGL_output_builder->get_output_device() == Monitor) ? _openGL_output_builder->get_next_output_run() : _openGL_output_builder->get_next_source_run();
next_run = _openGL_output_builder->get_next_source_run();
}
// Vertex output is arranged for triangle strips, as:
@ -144,38 +143,20 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
// [0/1] 3
if(next_run)
{
if(_openGL_output_builder->get_output_device() == Monitor)
{
// set the type, initial raster position and type of this run
output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position();
output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider);
output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _openGL_output_builder->get_current_field_time();
output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = tex_x;
// these things are constants across the line so just throw them out now
output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = output_tex_y(3) = output_tex_y(4) = output_tex_y(5) = tex_y;
output_lateral(0) = output_lateral(1) = output_lateral(3) = 0;
output_lateral(2) = output_lateral(4) = output_lateral(5) = 1;
output_frame_id(0) = output_frame_id(1) = output_frame_id(2) = output_frame_id(3) = output_frame_id(4) = output_frame_id(5) = (uint8_t)_openGL_output_builder->get_current_field();
}
else
{
source_input_position_x(0) = tex_x;
source_input_position_y(0) = source_input_position_y(1) = tex_y;
source_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position();
source_output_position_y(0) = source_output_position_y(1) = _openGL_output_builder->get_composite_output_y();
source_phase(0) = source_phase(1) = _colour_burst_phase;
source_amplitude(0) = source_amplitude(1) = _colour_burst_amplitude;
source_phase_time(0) = source_phase_time(1) = _colour_burst_time;
source_offset(0) = 0;
source_offset(1) = 255;
}
source_input_position_x(0) = tex_x;
source_input_position_y(0) = source_input_position_y(1) = tex_y;
source_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position();
source_output_position_y(0) = source_output_position_y(1) = _openGL_output_builder->get_composite_output_y();
source_phase(0) = source_phase(1) = _colour_burst_phase;
source_amplitude(0) = source_amplitude(1) = _colour_burst_amplitude;
source_phase_time(0) = source_phase_time(1) = _colour_burst_time;
source_offset(0) = 0;
source_offset(1) = 255;
}
// decrement the number of cycles left to run for and increment the
// horizontal counter appropriately
number_of_cycles -= next_run_length;
_openGL_output_builder->add_to_field_time(next_run_length);
// either charge or deplete the vertical retrace capacitor (making sure it stops at 0)
if(vsync_charging)
@ -192,57 +173,39 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
// if this is a data run then advance the buffer pointer
if(type == Scan::Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider);
if(_openGL_output_builder->get_output_device() == Monitor)
{
// store the final raster position
output_position_x(3) = output_position_x(4) = output_position_x(5) = (uint16_t)_horizontal_flywheel->get_current_output_position();
output_position_y(3) = output_position_y(4) = output_position_y(5) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider);
output_timestamp(3) = output_timestamp(4) = output_timestamp(5) = _openGL_output_builder->get_current_field_time();
output_tex_x(3) = output_tex_x(4) = output_tex_x(5) = tex_x;
source_input_position_x(1) = tex_x;
source_output_position_x(1) = (uint16_t)_horizontal_flywheel->get_current_output_position();
_openGL_output_builder->complete_output_run(6);
}
else
{
source_input_position_x(1) = tex_x;
source_output_position_x(1) = (uint16_t)_horizontal_flywheel->get_current_output_position();
_openGL_output_builder->complete_source_run();
}
_openGL_output_builder->complete_source_run();
}
// if this is horizontal retrace then advance the output line counter and bookend an output run
if(_openGL_output_builder->get_output_device() == Television)
Flywheel::SyncEvent honoured_event = Flywheel::SyncEvent::None;
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event != Flywheel::SyncEvent::None) honoured_event = next_vertical_sync_event;
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) honoured_event = next_horizontal_sync_event;
bool needs_endpoint =
(honoured_event == Flywheel::SyncEvent::StartRetrace && _is_writing_composite_run) ||
(honoured_event == Flywheel::SyncEvent::EndRetrace && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace());
if(needs_endpoint)
{
Flywheel::SyncEvent honoured_event = Flywheel::SyncEvent::None;
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event != Flywheel::SyncEvent::None) honoured_event = next_vertical_sync_event;
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) honoured_event = next_horizontal_sync_event;
bool needs_endpoint =
(honoured_event == Flywheel::SyncEvent::StartRetrace && _is_writing_composite_run) ||
(honoured_event == Flywheel::SyncEvent::EndRetrace && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace());
uint8_t *next_run = _openGL_output_builder->get_next_output_run();
if(needs_endpoint)
{
uint8_t *next_run = _openGL_output_builder->get_next_output_run();
output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position();
output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider);
output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position();
output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = _openGL_output_builder->get_composite_output_y();
output_lateral(0) = 0;
output_lateral(1) = _is_writing_composite_run ? 1 : 0;
output_lateral(2) = 1;
output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position();
output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider);
output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _openGL_output_builder->get_current_field_time();
output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position();
output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = _openGL_output_builder->get_composite_output_y();
output_lateral(0) = 0;
output_lateral(1) = _is_writing_composite_run ? 1 : 0;
output_lateral(2) = 1;
output_frame_id(0) = output_frame_id(1) = output_frame_id(2) = (uint8_t)_openGL_output_builder->get_current_field();
_openGL_output_builder->complete_output_run(3);
_is_writing_composite_run ^= true;
}
_openGL_output_builder->complete_output_run(3);
_is_writing_composite_run ^= true;
}
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace)
{
_openGL_output_builder->increment_composite_output_y();
}
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace)
{
_openGL_output_builder->increment_composite_output_y();
}
// if this is vertical retrace then adcance a field
@ -257,8 +220,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
_frames_since_last_delegate_call = 0;
}
}
_openGL_output_builder->increment_field();
}
}
}
@ -268,7 +229,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
#undef output_tex_x
#undef output_tex_y
#undef output_lateral
#undef output_timestamp
#undef input_input_position_x
#undef input_input_position_y

View File

@ -19,11 +19,9 @@ namespace CRT {
// or is one of the intermediate buffers that we've used to convert from composite towards RGB.
const GLsizei OutputVertexOffsetOfPosition = 0;
const GLsizei OutputVertexOffsetOfTexCoord = 4;
const GLsizei OutputVertexOffsetOfTimestamp = 8;
const GLsizei OutputVertexOffsetOfLateral = 12;
const GLsizei OutputVertexOffsetOfFrameID = 13;
const GLsizei OutputVertexOffsetOfLateral = 8;
const GLsizei OutputVertexSize = 16;
const GLsizei OutputVertexSize = 12;
// Input vertices, used only in composite mode, map from the input buffer to temporary buffer locations; such
// remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour
@ -43,16 +41,9 @@ const GLsizei IntermediateBufferWidth = 2048;
const GLsizei IntermediateBufferHeight = 2048;
// Some internal buffer sizes
const GLsizeiptr OutputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize
const GLsizeiptr OutputVertexBufferDataSize = 89856; // a multiple of 6 * OutputVertexSize
const GLsizeiptr SourceVertexBufferDataSize = 87360; // a multiple of 2 * SourceVertexSize
// Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track
// run age; that therefore creates a discrete number of fields that are stored. This number should be the
// number of historic fields that are required fully to complete a frame. It should be at least two and not
// more than four.
const int NumberOfFields = 4;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,9 @@
#include "TextureTarget.hpp"
#include "Shader.hpp"
#include "CRTInputBufferBuilder.hpp"
#include "CRTRunBuilder.hpp"
#include "Shaders/OutputShader.hpp"
#include "Shaders/IntermediateShader.hpp"
#include <mutex>
@ -31,6 +33,7 @@ class OpenGLOutputBuilder {
OutputDevice _output_device;
// timing information to allow reasoning about input information
unsigned int _input_frequency;
unsigned int _cycles_per_line;
unsigned int _height_of_display;
unsigned int _horizontal_scan_period;
@ -45,48 +48,28 @@ class OpenGLOutputBuilder {
char *_rgb_shader;
// Methods used by the OpenGL code
void prepare_rgb_output_shader();
void prepare_composite_output_shader();
std::unique_ptr<OpenGL::Shader> prepare_output_shader(char *vertex_shader, char *fragment_shader, GLint source_texture_unit);
void prepare_composite_input_shader();
std::unique_ptr<OpenGL::Shader> prepare_intermediate_shader(const char *input_position, const char *header, char *fragment_shader, GLenum texture_unit, bool extends);
void prepare_output_shader();
void prepare_rgb_input_shaders();
void prepare_composite_input_shaders();
void prepare_output_vertex_array();
void prepare_source_vertex_array();
void push_size_uniforms(unsigned int output_width, unsigned int output_height);
// the run and input data buffers
std::unique_ptr<CRTInputBufferBuilder> _buffer_builder;
CRTRunBuilder **_run_builders;
int _run_write_pointer;
std::shared_ptr<std::mutex> _output_mutex;
// transient buffers indicating composite data not yet decoded
uint16_t _composite_src_output_y, _cleared_composite_output_y;
char *get_output_vertex_shader(const char *header);
char *get_rgb_output_vertex_shader();
char *get_composite_output_vertex_shader();
char *get_output_fragment_shader(const char *sampling_function, const char *header, const char *fragColour_function);
char *get_rgb_output_fragment_shader();
char *get_composite_output_fragment_shader();
char *get_input_vertex_shader(const char *input_position, const char *header);
char *get_input_fragment_shader();
char *get_y_filter_fragment_shader();
char *get_chrominance_filter_fragment_shader();
std::unique_ptr<OpenGL::Shader> rgb_shader_program;
std::unique_ptr<OpenGL::Shader> composite_input_shader_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program, composite_output_shader_program;
std::unique_ptr<OpenGL::OutputShader> output_shader_program;
std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program;
std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program, rgb_filter_shader_program;
GLuint output_array_buffer, output_vertex_array;
GLuint source_array_buffer, source_vertex_array;
GLint windowSizeUniform, timestampBaseUniform;
GLint boundsOriginUniform, boundsSizeUniform;
unsigned int _last_output_width, _last_output_height;
GLuint textureName, shadowMaskTextureName;
@ -96,7 +79,9 @@ class OpenGLOutputBuilder {
std::unique_ptr<OpenGL::TextureTarget> filteredYTexture; // receives filtered Y in the R channel plus unfiltered I/U and Q/V in G and B
std::unique_ptr<OpenGL::TextureTarget> filteredTexture; // receives filtered YIQ or YUV
void perform_output_stage(unsigned int output_width, unsigned int output_height, OpenGL::Shader *const shader);
std::unique_ptr<OpenGL::TextureTarget> framebuffer; // the current pixel output
void perform_output_stage(unsigned int output_width, unsigned int output_height, OpenGL::OutputShader *const shader);
void set_timing_uniforms();
void set_colour_space_uniforms();
@ -132,13 +117,12 @@ class OpenGLOutputBuilder {
inline uint8_t *get_next_output_run()
{
_output_mutex->lock();
return &_output_buffer_data[_output_buffer_data_pointer];
return &_output_buffer_data[_output_buffer_data_pointer % OutputVertexBufferDataSize];
}
inline void complete_output_run(GLsizei vertices_written)
{
_run_builders[_run_write_pointer]->amount_of_data += (size_t)(vertices_written * OutputVertexSize);
_output_buffer_data_pointer = (_output_buffer_data_pointer + vertices_written * OutputVertexSize) % OutputVertexBufferDataSize;
_output_buffer_data_pointer += vertices_written * OutputVertexSize;
_output_mutex->unlock();
}
@ -147,16 +131,6 @@ class OpenGLOutputBuilder {
return _output_device;
}
inline uint32_t get_current_field_time()
{
return _run_builders[_run_write_pointer]->duration;
}
inline void add_to_field_time(uint32_t amount)
{
_run_builders[_run_write_pointer]->duration += amount;
}
inline uint16_t get_composite_output_y()
{
return _composite_src_output_y % IntermediateBufferHeight;
@ -167,20 +141,6 @@ class OpenGLOutputBuilder {
_composite_src_output_y++;
}
inline void increment_field()
{
_output_mutex->lock();
_run_write_pointer = (_run_write_pointer + 1)%NumberOfFields;
_run_builders[_run_write_pointer]->start = (size_t)_output_buffer_data_pointer;
_run_builders[_run_write_pointer]->reset();
_output_mutex->unlock();
}
inline int get_current_field()
{
return _run_write_pointer;
}
inline uint8_t *allocate_write_area(size_t required_length)
{
_output_mutex->lock();
@ -210,19 +170,20 @@ class OpenGLOutputBuilder {
void set_composite_sampling_function(const char *shader);
void set_rgb_sampling_function(const char *shader);
void set_output_device(OutputDevice output_device);
void set_timing(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);
void 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);
uint8_t *_input_texture_data;
GLuint _input_texture_array;
GLsync _input_texture_sync;
GLsizeiptr _input_texture_array_size;
uint8_t *_output_buffer_data;
GLsizei _output_buffer_data_pointer;
uint8_t *_source_buffer_data;
GLsizei _source_buffer_data_pointer;
GLsizei _drawn_source_buffer_data_pointer;
uint8_t *_output_buffer_data;
GLsizei _output_buffer_data_pointer;
GLsizei _drawn_output_buffer_data_pointer;
};
}

View File

@ -1,12 +0,0 @@
//
// CRTFrameBuilder.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/02/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "CRT.hpp"
#include "CRTOpenGL.hpp"
using namespace Outputs::CRT;

View File

@ -1,41 +0,0 @@
//
// CRTRunBuilder.h
// Clock Signal
//
// Created by Thomas Harte on 08/03/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef CRTRunBuilder_h
#define CRTRunBuilder_h
#import <vector>
namespace Outputs {
namespace CRT {
struct CRTRunBuilder {
CRTRunBuilder() : start(0) { reset(); }
// Resets the run builder.
inline void reset()
{
duration = 0;
amount_of_uploaded_data = 0;
amount_of_data = 0;
}
// Container for total length in cycles of all contained runs.
uint32_t duration;
size_t start;
// Storage for the length of run data uploaded so far; reset to zero by reset but otherwise
// entrusted to the CRT to update.
size_t amount_of_uploaded_data;
size_t amount_of_data;
};
}
}
#endif /* CRTRunBuilder_h */

View File

@ -0,0 +1,432 @@
//
// IntermediateShader.cpp
// Clock Signal
//
// Created by Thomas Harte on 28/04/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "IntermediateShader.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "../../../../SignalProcessing/FIRFilter.hpp"
using namespace OpenGL;
namespace {
const OpenGL::Shader::AttributeBinding bindings[] =
{
{"inputPosition", 0},
{"outputPosition", 1},
{"phaseAmplitudeAndOffset", 2},
{"phaseTime", 3},
{nullptr}
};
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition)
{
const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D";
const char *input_variable = input_is_inputPosition ? "inputPosition" : "outputPosition";
char *vertex_shader;
asprintf(&vertex_shader,
"#version 150\n"
"in vec2 inputPosition;"
"in vec2 outputPosition;"
"in vec3 phaseAmplitudeAndOffset;"
"in float phaseTime;"
"uniform float phaseCyclesPerTick;"
"uniform ivec2 outputTextureSize;"
"uniform float extension;"
"uniform %s texID;"
"uniform float offsets[5];"
"out vec2 phaseAndAmplitudeVarying;"
"out vec2 inputPositionsVarying[11];"
"out vec2 iInputPositionVarying;"
"out vec2 delayLinePositionVarying;"
"void main(void)"
"{"
"vec2 extensionVector = vec2(extension, 0.0) * 2.0 * (phaseAmplitudeAndOffset.z - 0.5);"
"vec2 extendedInputPosition = %s + extensionVector;"
"vec2 extendedOutputPosition = outputPosition + extensionVector;"
"vec2 textureSize = vec2(textureSize(texID, 0));"
"iInputPositionVarying = extendedInputPosition;"
"vec2 mappedInputPosition = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;"
"inputPositionsVarying[0] = mappedInputPosition - (vec2(offsets[0], 0.0) / textureSize);"
"inputPositionsVarying[1] = mappedInputPosition - (vec2(offsets[1], 0.0) / textureSize);"
"inputPositionsVarying[2] = mappedInputPosition - (vec2(offsets[2], 0.0) / textureSize);"
"inputPositionsVarying[3] = mappedInputPosition - (vec2(offsets[3], 0.0) / textureSize);"
"inputPositionsVarying[4] = mappedInputPosition - (vec2(offsets[4], 0.0) / textureSize);"
"inputPositionsVarying[5] = mappedInputPosition;"
"inputPositionsVarying[6] = mappedInputPosition + (vec2(offsets[4], 0.0) / textureSize);"
"inputPositionsVarying[7] = mappedInputPosition + (vec2(offsets[3], 0.0) / textureSize);"
"inputPositionsVarying[8] = mappedInputPosition + (vec2(offsets[2], 0.0) / textureSize);"
"inputPositionsVarying[9] = mappedInputPosition + (vec2(offsets[1], 0.0) / textureSize);"
"inputPositionsVarying[10] = mappedInputPosition + (vec2(offsets[0], 0.0) / textureSize);"
"delayLinePositionVarying = mappedInputPosition - vec2(0.0, 1.0);"
"phaseAndAmplitudeVarying.x = (phaseCyclesPerTick * (extendedOutputPosition.x - phaseTime) + phaseAmplitudeAndOffset.x) * 2.0 * 3.141592654;"
"phaseAndAmplitudeVarying.y = 0.33;"
"vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;"
"gl_Position = vec4(eyePosition, 0.0, 1.0);"
"}", sampler_type, input_variable);
std::unique_ptr<IntermediateShader> shader = std::unique_ptr<IntermediateShader>(new IntermediateShader(vertex_shader, fragment_shader, bindings));
free(vertex_shader);
shader->texIDUniform = shader->get_uniform_location("texID");
shader->outputTextureSizeUniform = shader->get_uniform_location("outputTextureSize");
shader->phaseCyclesPerTickUniform = shader->get_uniform_location("phaseCyclesPerTick");
shader->extensionUniform = shader->get_uniform_location("extension");
shader->weightsUniform = shader->get_uniform_location("weights");
shader->rgbToLumaChromaUniform = shader->get_uniform_location("rgbToLumaChroma");
shader->lumaChromaToRGBUniform = shader->get_uniform_location("lumaChromaToRGB");
shader->offsetsUniform = shader->get_uniform_location("offsets");
return shader;
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_source_conversion_shader(const char *composite_shader, const char *rgb_shader)
{
char *composite_sample = (char *)composite_shader;
if(!composite_sample)
{
asprintf(&composite_sample,
"%s\n"
"uniform mat3 rgbToLumaChroma;"
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));"
"vec3 lumaChromaColour = rgbToLumaChroma * rgbColour;"
"vec2 quadrature = vec2(cos(phase), -sin(phase)) * amplitude;"
"return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));"
"}",
rgb_shader);
}
char *fragment_shader;
asprintf(&fragment_shader,
"#version 150\n"
"in vec2 inputPositionsVarying[11];"
"in vec2 iInputPositionVarying;"
"in vec2 phaseAndAmplitudeVarying;"
"out vec4 fragColour;"
"uniform usampler2D texID;"
"\n%s\n"
"void main(void)"
"{"
"fragColour = vec4(composite_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x, phaseAndAmplitudeVarying.y));"
"}"
, composite_sample);
if(!composite_shader) free(composite_sample);
std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true);
free(fragment_shader);
return shader;
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_source_shader(const char *rgb_shader)
{
char *fragment_shader;
asprintf(&fragment_shader,
"#version 150\n"
"in vec2 inputPositionsVarying[11];"
"in vec2 iInputPositionVarying;"
"in vec2 phaseAndAmplitudeVarying;"
"out vec3 fragColour;"
"uniform usampler2D texID;"
"\n%s\n"
"void main(void)"
"{"
"fragColour = rgb_sample(texID, inputPositionsVarying[5], iInputPositionVarying);"
"}"
, rgb_shader);
std::unique_ptr<IntermediateShader> shader = make_shader(fragment_shader, true, true);
free(fragment_shader);
return shader;
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separation_shader()
{
return make_shader(
"#version 150\n"
"in vec2 phaseAndAmplitudeVarying;"
"in vec2 inputPositionsVarying[11];"
"uniform vec4 weights[3];"
"out vec3 fragColour;"
"uniform sampler2D texID;"
"void main(void)"
"{"
"vec4 samples[3] = vec4[]("
"vec4("
"texture(texID, inputPositionsVarying[0]).r,"
"texture(texID, inputPositionsVarying[1]).r,"
"texture(texID, inputPositionsVarying[2]).r,"
"texture(texID, inputPositionsVarying[3]).r"
"),"
"vec4("
"texture(texID, inputPositionsVarying[4]).r,"
"texture(texID, inputPositionsVarying[5]).r,"
"texture(texID, inputPositionsVarying[6]).r,"
"texture(texID, inputPositionsVarying[7]).r"
"),"
"vec4("
"texture(texID, inputPositionsVarying[8]).r,"
"texture(texID, inputPositionsVarying[9]).r,"
"texture(texID, inputPositionsVarying[10]).r,"
"0.0"
")"
");"
"float luminance = "
"dot(vec3("
"dot(samples[0], weights[0]),"
"dot(samples[1], weights[1]),"
"dot(samples[2], weights[2])"
"), vec3(1.0)) / (1.0 - phaseAndAmplitudeVarying.y);"
"float chrominance = 0.5 * (samples[1].y - luminance) / phaseAndAmplitudeVarying.y;"
"vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x));"
"fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));"
"}",false, false);
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shader()
{
return make_shader(
"#version 150\n"
"in vec2 inputPositionsVarying[11];"
"uniform vec4 weights[3];"
"out vec3 fragColour;"
"uniform sampler2D texID;"
"uniform mat3 lumaChromaToRGB;"
"void main(void)"
"{"
"vec3 centreSample = texture(texID, inputPositionsVarying[5]).rgb;"
"vec2 samples[] = vec2[]("
"texture(texID, inputPositionsVarying[0]).gb - vec2(0.5),"
"texture(texID, inputPositionsVarying[1]).gb - vec2(0.5),"
"texture(texID, inputPositionsVarying[2]).gb - vec2(0.5),"
"texture(texID, inputPositionsVarying[3]).gb - vec2(0.5),"
"texture(texID, inputPositionsVarying[4]).gb - vec2(0.5),"
"centreSample.gb - vec2(0.5),"
"texture(texID, inputPositionsVarying[6]).gb - vec2(0.5),"
"texture(texID, inputPositionsVarying[7]).gb - vec2(0.5),"
"texture(texID, inputPositionsVarying[8]).gb - vec2(0.5),"
"texture(texID, inputPositionsVarying[9]).gb - vec2(0.5),"
"texture(texID, inputPositionsVarying[10]).gb - vec2(0.5)"
");"
"vec4 channel1[] = vec4[]("
"vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r),"
"vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r),"
"vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)"
");"
"vec4 channel2[] = vec4[]("
"vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g),"
"vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g),"
"vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)"
");"
"vec3 lumaChromaColour = vec3(centreSample.r,"
"dot(vec3("
"dot(channel1[0], weights[0]),"
"dot(channel1[1], weights[1]),"
"dot(channel1[2], weights[2])"
"), vec3(1.0)) + 0.5,"
"dot(vec3("
"dot(channel2[0], weights[0]),"
"dot(channel2[1], weights[1]),"
"dot(channel2[2], weights[2])"
"), vec3(1.0)) + 0.5"
");"
"vec3 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);"
"fragColour = lumaChromaToRGB * lumaChromaColourInRange;"
"}", false, false);
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader()
{
return make_shader(
"#version 150\n"
"in vec2 inputPositionsVarying[11];"
"uniform vec4 weights[3];"
"out vec3 fragColour;"
"uniform sampler2D texID;"
"void main(void)"
"{"
"vec3 samples[] = vec3[]("
"texture(texID, inputPositionsVarying[0]).rgb,"
"texture(texID, inputPositionsVarying[1]).rgb,"
"texture(texID, inputPositionsVarying[2]).rgb,"
"texture(texID, inputPositionsVarying[3]).rgb,"
"texture(texID, inputPositionsVarying[4]).rgb,"
"texture(texID, inputPositionsVarying[5]).rgb,"
"texture(texID, inputPositionsVarying[6]).rgb,"
"texture(texID, inputPositionsVarying[7]).rgb,"
"texture(texID, inputPositionsVarying[8]).rgb,"
"texture(texID, inputPositionsVarying[9]).rgb,"
"texture(texID, inputPositionsVarying[10]).rgb"
");"
"vec4 channel1[] = vec4[]("
"vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r),"
"vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r),"
"vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)"
");"
"vec4 channel2[] = vec4[]("
"vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g),"
"vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g),"
"vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)"
");"
"vec4 channel3[] = vec4[]("
"vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b),"
"vec4(samples[4].b, samples[5].b, samples[6].b, samples[7].b),"
"vec4(samples[8].b, samples[9].b, samples[10].b, 0.0)"
");"
"fragColour = vec3("
"dot(vec3("
"dot(channel1[0], weights[0]),"
"dot(channel1[1], weights[1]),"
"dot(channel1[2], weights[2])"
"), vec3(1.0)),"
"dot(vec3("
"dot(channel2[0], weights[0]),"
"dot(channel2[1], weights[1]),"
"dot(channel2[2], weights[2])"
"), vec3(1.0)),"
"dot(vec3("
"dot(channel3[0], weights[0]),"
"dot(channel3[1], weights[1]),"
"dot(channel3[2], weights[2])"
"), vec3(1.0))"
");"
"}", false, false);
}
void IntermediateShader::set_output_size(unsigned int output_width, unsigned int output_height)
{
bind();
glUniform2i(outputTextureSizeUniform, (GLint)output_width, (GLint)output_height);
}
void IntermediateShader::set_source_texture_unit(GLenum unit)
{
bind();
glUniform1i(texIDUniform, (GLint)(unit - GL_TEXTURE0));
}
void IntermediateShader::set_filter_coefficients(float sampling_rate, float cutoff_frequency)
{
bind();
// The process below: the source texture will have bilinear filtering enabled; so by
// sampling at non-integral offsets from the centre the shader can get a weighted sum
// of two source pixels, then scale that once, to do two taps per sample. However
// that works only if the two coefficients being joined have the same sign. So the
// number of usable taps is between 11 and 21 depending on the values that come out.
// Perform a linear search for the highest number of taps we can use with 11 samples.
float weights[12];
float offsets[5];
unsigned int taps = 21;
while(1)
{
float coefficients[21];
SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation);
luminance_filter.get_coefficients(coefficients);
int sample = 0;
int c = 0;
memset(weights, 0, sizeof(float)*12);
memset(offsets, 0, sizeof(float)*5);
int halfSize = (taps >> 1);
while(c < halfSize && sample < 5)
{
offsets[sample] = (float)(halfSize - c);
if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1))
{
weights[sample] = coefficients[c] + coefficients[c+1];
offsets[sample] -= (coefficients[c+1] / weights[sample]);
c += 2;
}
else
{
weights[sample] = coefficients[c];
c++;
}
sample ++;
}
if(c == halfSize) // i.e. we finished combining inputs before we ran out of space
{
weights[sample] = coefficients[c];
for(int c = 0; c < sample; c++)
{
weights[sample+c+1] = weights[sample-c-1];
}
break;
}
taps -= 2;
}
glUniform4fv(weightsUniform, 3, weights);
glUniform1fv(offsetsUniform, 5, offsets);
}
void IntermediateShader::set_separation_frequency(float sampling_rate, float colour_burst_frequency)
{
// TODO: apply separately-formed filters for luminance and chrominance
set_filter_coefficients(sampling_rate, colour_burst_frequency - 50.0f);
}
void IntermediateShader::set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle)
{
bind();
glUniform1f(phaseCyclesPerTickUniform, phase_cycles_per_sample);
glUniform1f(extensionUniform, extend_runs_to_full_cycle ? ceilf(1.0f / phase_cycles_per_sample) : 0.0f);
}
void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB)
{
bind();
glUniformMatrix3fv(lumaChromaToRGBUniform, 1, GL_FALSE, toRGB);
glUniformMatrix3fv(rgbToLumaChromaUniform, 1, GL_FALSE, fromRGB);
}

View File

@ -0,0 +1,99 @@
//
// IntermediateShader.hpp
// Clock Signal
//
// Created by Thomas Harte on 28/04/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef IntermediateShader_hpp
#define IntermediateShader_hpp
#include <stdio.h>
#include "Shader.hpp"
#include <memory>
namespace OpenGL {
class IntermediateShader: public Shader {
public:
using Shader::Shader;
/*!
Constructs and returns an intermediate shader that will take runs from the inputPositions,
converting them to single-channel composite values using @c composite_shader if supplied
or @c rgb_shader and a reference composite conversion if @c composite_shader is @c nullptr.
*/
static std::unique_ptr<IntermediateShader> make_source_conversion_shader(const char *composite_shader, const char *rgb_shader);
/*!
Constructs and returns an intermediate shader that will take runs from the inputPositions,
converting them to RGB values using @c rgb_shader.
*/
static std::unique_ptr<IntermediateShader> make_rgb_source_shader(const char *rgb_shader);
/*!
Constructs and returns an intermediate shader that will read composite samples from the R channel,
filter then to obtain luminance, stored to R, and to separate out unfiltered chrominance, store to G and B.
*/
static std::unique_ptr<IntermediateShader> make_chroma_luma_separation_shader();
/*!
Constructs and returns an intermediate shader that will pass R through unchanged while filtering G and B.
*/
static std::unique_ptr<IntermediateShader> make_chroma_filter_shader();
/*!
Constructs and returns an intermediate shader that will filter R, G and B.
*/
static std::unique_ptr<IntermediateShader> make_rgb_filter_shader();
/*!
Binds this shader and configures it for output to an area of `output_width` and `output_height` pixels.
*/
void set_output_size(unsigned int output_width, unsigned int output_height);
/*!
Binds this shader and sets the texture unit (as an enum, e.g. `GL_TEXTURE0`) to sample as source data.
*/
void set_source_texture_unit(GLenum unit);
/*!
Binds this shader and sets filtering coefficients for a lowpass filter based on the cutoff.
*/
void set_filter_coefficients(float sampling_rate, float cutoff_frequency);
/*!
Binds this shader and configures filtering to separate luminance and chrominance based on a colour
subcarrier of the given frequency.
*/
void set_separation_frequency(float sampling_rate, float colour_burst_frequency);
/*!
Binds this shader and sets the number of colour phase cycles per sample, indicating whether output
geometry should be extended so that a complete colour cycle is included at both the beginning and end.
*/
void set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle);
/*!
Binds this shader and sets the matrices that convert between RGB and chrominance/luminance.
*/
void set_colour_conversion_matrices(float *fromRGB, float *toRGB);
private:
static std::unique_ptr<IntermediateShader> make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition);
GLint texIDUniform;
GLint outputTextureSizeUniform;
GLint weightsUniform;
GLint phaseCyclesPerTickUniform;
GLint extensionUniform;
GLint rgbToLumaChromaUniform;
GLint lumaChromaToRGBUniform;
GLint offsetsUniform;
};
}
#endif /* IntermediateShader_hpp */

View File

@ -0,0 +1,126 @@
//
// OutputShader.cpp
// Clock Signal
//
// Created by Thomas Harte on 27/04/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "OutputShader.hpp"
#include <stdlib.h>
#include <math.h>
using namespace OpenGL;
namespace {
const OpenGL::Shader::AttributeBinding bindings[] =
{
{"position", 0},
{"srcCoordinates", 1},
{"lateral", 2},
{nullptr}
};
}
std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler)
{
const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D";
char *vertex_shader;
asprintf(&vertex_shader,
"#version 150\n"
"in vec2 position;"
"in vec2 srcCoordinates;"
"in float lateral;"
"uniform vec2 boundsOrigin;"
"uniform vec2 boundsSize;"
"uniform vec2 positionConversion;"
"uniform vec2 scanNormal;"
"uniform %s texID;"
"out float lateralVarying;"
"out vec2 srcCoordinatesVarying;"
"out vec2 iSrcCoordinatesVarying;"
"void main(void)"
"{"
"lateralVarying = lateral - 0.5;"
"ivec2 textureSize = textureSize(texID, 0);"
"iSrcCoordinatesVarying = srcCoordinates;"
"srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);"
"vec2 floatingPosition = (position / positionConversion) + lateral * scanNormal;"
"vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;"
"gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);"
"}", sampler_type);
char *fragment_shader;
asprintf(&fragment_shader,
"#version 150\n"
"in float lateralVarying;"
"in vec2 srcCoordinatesVarying;"
"in vec2 iSrcCoordinatesVarying;"
"out vec4 fragColour;"
"uniform %s texID;"
"\n%s\n"
"void main(void)"
"{"
"fragColour = vec4(%s, 0.5*cos(lateralVarying));"
"}",
sampler_type, fragment_methods, colour_expression);
std::unique_ptr<OutputShader> result = std::unique_ptr<OutputShader>(new OutputShader(vertex_shader, fragment_shader, bindings));
free(vertex_shader);
free(fragment_shader);
result->boundsSizeUniform = result->get_uniform_location("boundsSize");
result->boundsOriginUniform = result->get_uniform_location("boundsOrigin");
result->texIDUniform = result->get_uniform_location("texID");
result->scanNormalUniform = result->get_uniform_location("scanNormal");
result->positionConversionUniform = result->get_uniform_location("positionConversion");
return result;
}
void OutputShader::set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area)
{
bind();
GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f);
GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * visible_area.size.width;
visible_area.origin.x -= bonusWidth * 0.5f * visible_area.size.width;
visible_area.size.width *= outputAspectRatioMultiplier;
glUniform2f(boundsOriginUniform, (GLfloat)visible_area.origin.x, (GLfloat)visible_area.origin.y);
glUniform2f(boundsSizeUniform, (GLfloat)visible_area.size.width, (GLfloat)visible_area.size.height);
}
void OutputShader::set_source_texture_unit(GLenum unit)
{
bind();
glUniform1i(texIDUniform, (GLint)(unit - GL_TEXTURE0));
}
void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider)
{
bind();
float scan_angle = atan2f(1.0f / (float)height_of_display, 1.0f);
float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)};
float multiplier = (float)cycles_per_line / ((float)height_of_display * (float)horizontal_scan_period);
scan_normal[0] *= multiplier;
scan_normal[1] *= multiplier;
glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]);
glUniform2f(positionConversionUniform, horizontal_scan_period, vertical_scan_period / (unsigned int)vertical_period_divider);
}

View File

@ -0,0 +1,66 @@
//
// OutputShader.hpp
// Clock Signal
//
// Created by Thomas Harte on 27/04/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef OutputShader_hpp
#define OutputShader_hpp
#include "Shader.hpp"
#include "../../CRTTypes.hpp"
#include <memory>
namespace OpenGL {
class OutputShader: public Shader {
public:
/*!
Constructs and returns an instance of OutputShader. OutputShaders are intended to read source data
from a texture and draw a single raster scan containing that data as output.
Does not catch any of the exceptions potentially thrown by `Shader::Shader`.
All instances of OutputShader are guaranteed to use the same attribute locations for their inputs.
@param fragment_methods A block of code that will appear within the global area of the fragment shader.
@param colour_expression An expression that should evaluate to a `vec3` indicating the colour at the current location. The
decision should be a function of the uniform `texID`, which will be either a `usampler2D` or a `sampler2D` as per the
`use_usampler` parameter, and the inputs `srcCoordinatesVarying` which is a location within the texture from which to
take the source value, and `iSrcCoordinatesVarying` which is a value proportional to `srcCoordinatesVarying` but scaled
so that one unit equals one source sample.
@param use_usampler Dictates the type of the `texID` uniform; will be a `usampler2D` if this parameter is `true`, a
`sampler2D` otherwise.
@returns an instance of OutputShader.
*/
static std::unique_ptr<OutputShader> make_shader(const char *fragment_methods, const char *colour_expression, bool use_usampler);
using Shader::Shader;
/*!
Binds this shader and configures it for output to an area of `output_width` and `output_height` pixels, ensuring
the largest possible drawing size that allows everything within `visible_area` to be visible.
*/
void set_output_size(unsigned int output_width, unsigned int output_height, Outputs::CRT::Rect visible_area);
/*!
Binds this shader and sets the texture unit (as an enum, e.g. `GL_TEXTURE0`) to sample as source data.
*/
void set_source_texture_unit(GLenum unit);
/*!
Binds this shader and configures its understanding of how to map from the source vertex stream to screen coordinates.
*/
void set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider);
private:
GLint boundsOriginUniform, boundsSizeUniform, texIDUniform, scanNormalUniform, positionConversionUniform;
};
}
#endif /* OutputShader_hpp */

View File

@ -13,6 +13,10 @@
using namespace OpenGL;
namespace {
Shader *bound_shader = nullptr;
}
GLuint Shader::compile_shader(const char *source, GLenum type)
{
GLuint shader = glCreateShader(type);
@ -80,12 +84,23 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att
Shader::~Shader()
{
if(bound_shader == this) Shader::unbind();
glDeleteProgram(_shader_program);
}
void Shader::bind()
{
glUseProgram(_shader_program);
if(bound_shader != this)
{
glUseProgram(_shader_program);
bound_shader = this;
}
}
void Shader::unbind()
{
bound_shader = nullptr;
glUseProgram(0);
}
GLint Shader::get_attrib_location(const GLchar *name)

View File

@ -41,10 +41,17 @@ public:
~Shader();
/*!
Performs an @c glUseProgram to make this the active shader.
Performs an @c glUseProgram to make this the active shader unless:
(i) it was the previous shader bound; and
(ii) no calls have been received to unbind in the interim.
*/
void bind();
/*!
Unbinds the current instance of Shader, if one is bound.
*/
static void unbind();
/*!
Performs a @c glGetAttribLocation call.
@param name The name of the attribute to locate.

View File

@ -7,19 +7,31 @@
//
#include "TextureTarget.hpp"
#include <math.h>
using namespace OpenGL;
TextureTarget::TextureTarget(GLsizei width, GLsizei height) : _width(width), _height(height)
TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit) :
_width(width),
_height(height),
_pixel_shader(nullptr),
_drawing_vertex_array(0),
_drawing_array_buffer(0),
_set_aspect_ratio(0.0f),
_texture_unit(texture_unit)
{
glGenFramebuffers(1, &_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
_expanded_width = 1 << (GLsizei)ceil(log2(width));
_expanded_height = 1 << (GLsizei)ceil(log2(height));
glGenTextures(1, &_texture);
glActiveTexture(texture_unit);
glBindTexture(GL_TEXTURE_2D, _texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)_expanded_width, (GLsizei)_expanded_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
@ -31,6 +43,8 @@ TextureTarget::~TextureTarget()
{
glDeleteFramebuffers(1, &_framebuffer);
glDeleteTextures(1, &_texture);
if(_drawing_vertex_array) glDeleteVertexArrays(1, &_drawing_vertex_array);
if(_drawing_array_buffer) glDeleteBuffers(1, &_drawing_array_buffer);
}
void TextureTarget::bind_framebuffer()
@ -43,3 +57,88 @@ void TextureTarget::bind_texture()
{
glBindTexture(GL_TEXTURE_2D, _texture);
}
void TextureTarget::draw(float aspect_ratio)
{
if(!_pixel_shader)
{
const char *vertex_shader =
"#version 150\n"
"in vec2 texCoord;"
"in vec2 position;"
"out vec2 texCoordVarying;"
"void main(void)"
"{"
"texCoordVarying = texCoord;"
"gl_Position = vec4(position, 0.0, 1.0);"
"}";
const char *fragment_shader =
"#version 150\n"
"in vec2 texCoordVarying;"
"uniform sampler2D texID;"
"out vec4 fragColour;"
"void main(void)"
"{"
"fragColour = texture(texID, texCoordVarying);"
"}";
_pixel_shader = std::unique_ptr<Shader>(new Shader(vertex_shader, fragment_shader, nullptr));
_pixel_shader->bind();
glGenVertexArrays(1, &_drawing_vertex_array);
glGenBuffers(1, &_drawing_array_buffer);
glBindVertexArray(_drawing_vertex_array);
glBindBuffer(GL_ARRAY_BUFFER, _drawing_array_buffer);
GLint positionAttribute = _pixel_shader->get_attrib_location("position");
GLint texCoordAttribute = _pixel_shader->get_attrib_location("texCoord");
glEnableVertexAttribArray((GLuint)positionAttribute);
glEnableVertexAttribArray((GLuint)texCoordAttribute);
const GLsizei vertexStride = 4 * sizeof(GLfloat);
glVertexAttribPointer((GLuint)positionAttribute, 2, GL_FLOAT, GL_FALSE, vertexStride, (void *)0);
glVertexAttribPointer((GLuint)texCoordAttribute, 2, GL_FLOAT, GL_FALSE, vertexStride, (void *)(2 * sizeof(GLfloat)));
GLint texIDUniform = _pixel_shader->get_uniform_location("texID");
glUniform1i(texIDUniform, (GLint)(_texture_unit - GL_TEXTURE0));
}
if(_set_aspect_ratio != aspect_ratio)
{
_set_aspect_ratio = aspect_ratio;
float buffer[4*4];
// establish texture coordinates
buffer[2] = 0.0f;
buffer[3] = 0.0f;
buffer[6] = 0.0f;
buffer[7] = (float)_height / (float)_expanded_height;
buffer[10] = (float)_width / (float)_expanded_width;
buffer[11] = 0;
buffer[14] = buffer[10];
buffer[15] = buffer[7];
// determine positions; rule is to keep the same height and centre
float internal_aspect_ratio = (float)_width / (float)_height;
float aspect_ratio_ratio = internal_aspect_ratio / aspect_ratio;
buffer[0] = -aspect_ratio_ratio; buffer[1] = -1.0f;
buffer[4] = -aspect_ratio_ratio; buffer[5] = 1.0f;
buffer[8] = aspect_ratio_ratio; buffer[9] = -1.0f;
buffer[12] = aspect_ratio_ratio; buffer[13] = 1.0f;
// upload buffer
glBindBuffer(GL_ARRAY_BUFFER, _drawing_array_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(buffer), buffer, GL_STATIC_DRAW);
}
_pixel_shader->bind();
glBindVertexArray(_drawing_vertex_array);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

View File

@ -10,6 +10,8 @@
#define TextureTarget_hpp
#include "OpenGL.hpp"
#include "Shaders/Shader.hpp"
#include <memory>
namespace OpenGL {
@ -22,12 +24,13 @@ class TextureTarget {
/*!
Creates a new texture target.
Throws ErrorFramebufferIncomplete if creation fails.
Throws ErrorFramebufferIncomplete if creation fails. Leaves both the generated texture and framebuffer bound.
@param width The width of target to create.
@param height The height of target to create.
@param texture_unit A texture unit on which to bind the texture.
*/
TextureTarget(GLsizei width, GLsizei height);
TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit);
~TextureTarget();
/*!
@ -40,6 +43,27 @@ class TextureTarget {
*/
void bind_texture();
/*!
@returns the width of the texture target.
*/
GLsizei get_width()
{
return _width;
}
/*!
@returns the height of the texture target.
*/
GLsizei get_height()
{
return _height;
}
/*!
*/
void draw(float aspect_ratio);
enum {
ErrorFramebufferIncomplete
};
@ -47,6 +71,12 @@ class TextureTarget {
private:
GLuint _framebuffer, _texture;
GLsizei _width, _height;
GLsizei _expanded_width, _expanded_height;
GLenum _texture_unit;
std::unique_ptr<Shader> _pixel_shader;
GLuint _drawing_vertex_array, _drawing_array_buffer;
float _set_aspect_ratio;
};
}