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:
commit
c72e360012
@ -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 */,
|
||||
);
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
@ -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 */
|
432
Outputs/CRT/Internals/Shaders/IntermediateShader.cpp
Normal file
432
Outputs/CRT/Internals/Shaders/IntermediateShader.cpp
Normal 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);
|
||||
}
|
99
Outputs/CRT/Internals/Shaders/IntermediateShader.hpp
Normal file
99
Outputs/CRT/Internals/Shaders/IntermediateShader.hpp
Normal 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 */
|
126
Outputs/CRT/Internals/Shaders/OutputShader.cpp
Normal file
126
Outputs/CRT/Internals/Shaders/OutputShader.cpp
Normal 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);
|
||||
}
|
66
Outputs/CRT/Internals/Shaders/OutputShader.hpp
Normal file
66
Outputs/CRT/Internals/Shaders/OutputShader.hpp
Normal 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 */
|
@ -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)
|
@ -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.
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user