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

Merge pull request #77 from TomHarte/LineBuffer

Makes input and output OpenGL data submission atomic, while cleaning code generally
This commit is contained in:
Thomas Harte 2016-11-21 12:24:43 +08:00 committed by GitHub
commit 4ec042fad1
18 changed files with 1083 additions and 783 deletions

View File

@ -485,6 +485,7 @@ void Machine::synchronise()
{
update_display();
update_audio();
_speaker->flush();
}
void Machine::configure_as_target(const StaticAnalyser::Target &target)

View File

@ -47,6 +47,8 @@
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; };
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8261D2C2470003C5BF8 /* C1540.cpp */; };
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; };
4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */; };
4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */; };
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; };
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
@ -352,7 +354,7 @@
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 */; };
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */; };
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
@ -486,6 +488,10 @@
4B4DC8271D2C2470003C5BF8 /* C1540.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = C1540.hpp; sourceTree = "<group>"; };
4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerialBus.cpp; sourceTree = "<group>"; };
4B4DC82A1D2C27A4003C5BF8 /* SerialBus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialBus.hpp; sourceTree = "<group>"; };
4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ArrayBuilder.cpp; sourceTree = "<group>"; };
4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ArrayBuilder.hpp; sourceTree = "<group>"; };
4B5073081DDFCFDF00C48FBD /* ArrayBuilderTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayBuilderTests.h; sourceTree = "<group>"; };
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArrayBuilderTests.mm; sourceTree = "<group>"; };
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
@ -828,8 +834,8 @@
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>"; };
4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; 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>"; };
4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureBuilder.cpp; sourceTree = "<group>"; };
4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureBuilder.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>"; };
4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = "<group>"; };
@ -1630,6 +1636,8 @@
4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = {
isa = PBXGroup;
children = (
4B5073081DDFCFDF00C48FBD /* ArrayBuilderTests.h */,
4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
@ -1687,16 +1695,18 @@
4BBF99071C8FBA6F0075DAFB /* Internals */ = {
isa = PBXGroup;
children = (
4BC3B74C1CD194CC00F86E85 /* Shaders */,
4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */,
4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */,
4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */,
4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */,
4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */,
4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */,
4B5073061DDD3B9400C48FBD /* ArrayBuilder.hpp */,
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */,
4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */,
4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */,
4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */,
4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */,
4BBF99091C8FBA6F0075DAFB /* TextureBuilder.hpp */,
4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */,
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */,
4BC3B74C1CD194CC00F86E85 /* Shaders */,
);
path = Internals;
sourceTree = "<group>";
@ -1704,12 +1714,12 @@
4BC3B74C1CD194CC00F86E85 /* Shaders */ = {
isa = PBXGroup;
children = (
4BC3B74D1CD194CC00F86E85 /* Shader.cpp */,
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */,
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */,
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */,
4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */,
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */,
4BC3B74D1CD194CC00F86E85 /* Shader.cpp */,
4BBB14301CD2CECE00BDB55C /* IntermediateShader.hpp */,
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */,
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */,
);
path = Shaders;
sourceTree = "<group>";
@ -2320,7 +2330,7 @@
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */,
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */,
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */,
@ -2353,6 +2363,7 @@
4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */,
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
4B4C83701D4F623200CD541F /* D64.cpp in Sources */,
4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */,
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */,
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */,
4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */,
@ -2377,6 +2388,7 @@
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */,
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */,
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */,
4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */,
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */,
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */,
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,

View File

@ -0,0 +1,13 @@
//
// ArrayBuilderTests.h
// Clock Signal
//
// Created by Thomas Harte on 19/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
@interface ArrayBuilderTests : XCTestCase
@end

View File

@ -0,0 +1,128 @@
//
// ArrayBuilderTests.m
// Clock Signal
//
// Created by Thomas Harte on 19/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "ArrayBuilderTests.h"
#include "ArrayBuilder.hpp"
static NSData *inputData, *outputData;
static void setData(bool is_input, uint8_t *data, size_t size)
{
NSData *dataObject = [NSData dataWithBytes:data length:size];
if(is_input) inputData = dataObject; else outputData = dataObject;
}
@implementation ArrayBuilderTests
+ (void)setUp
{
inputData = nil;
outputData = nil;
}
- (void)assertMonotonicForInputSize:(size_t)inputSize outputSize:(size_t)outputSize
{
XCTAssert(inputData != nil, @"Should have received some input data");
XCTAssert(outputData != nil, @"Should have received some output data");
XCTAssert(inputData.length == inputSize, @"Input data should be %lu bytes long, was %lu", inputSize, (unsigned long)inputData.length);
XCTAssert(outputData.length == outputSize, @"Output data should be %lu bytes long, was %lu", outputSize, (unsigned long)outputData.length);
uint8_t *input = (uint8_t *)inputData.bytes;
uint8_t *output = (uint8_t *)outputData.bytes;
for(int c = 0; c < inputSize; c++) XCTAssert(input[c] == c, @"Input item %d should be %d, was %d", c, c, input[c]);
for(int c = 0; c < outputSize; c++) XCTAssert(output[c] == c + 0x80, @"Output item %d should be %d, was %d", c, c+0x80, output[c]);
}
- (void)testSingleWriteSingleFlush
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
uint8_t *input = arrayBuilder.get_input_storage(5);
uint8_t *output = arrayBuilder.get_output_storage(3);
for(int c = 0; c < 5; c++) input[c] = c;
for(int c = 0; c < 3; c++) output[c] = c + 0x80;
arrayBuilder.flush();
arrayBuilder.submit();
[self assertMonotonicForInputSize:5 outputSize:3];
}
- (void)testDoubleWriteSingleFlush
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
uint8_t *input;
uint8_t *output;
input = arrayBuilder.get_input_storage(2);
output = arrayBuilder.get_output_storage(2);
for(int c = 0; c < 2; c++) input[c] = c;
for(int c = 0; c < 2; c++) output[c] = c + 0x80;
input = arrayBuilder.get_input_storage(2);
output = arrayBuilder.get_output_storage(2);
for(int c = 0; c < 2; c++) input[c] = c+2;
for(int c = 0; c < 2; c++) output[c] = c+2 + 0x80;
arrayBuilder.flush();
arrayBuilder.submit();
[self assertMonotonicForInputSize:4 outputSize:4];
}
- (void)testSubmitWithoutFlush
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
arrayBuilder.get_input_storage(5);
arrayBuilder.get_input_storage(8);
arrayBuilder.get_output_storage(6);
arrayBuilder.get_input_storage(12);
arrayBuilder.get_output_storage(3);
arrayBuilder.submit();
XCTAssert(inputData.length == 0, @"No input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
XCTAssert(outputData.length == 0, @"No output data should have been received; %lu bytes were received", (unsigned long)outputData.length);
arrayBuilder.flush();
arrayBuilder.submit();
XCTAssert(inputData.length == 25, @"All input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
XCTAssert(outputData.length == 9, @"All output data should have been received; %lu bytes were received", (unsigned long)outputData.length);
}
- (void)testSubmitContinuity
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
arrayBuilder.get_input_storage(5);
arrayBuilder.get_output_storage(5);
arrayBuilder.flush();
uint8_t *input = arrayBuilder.get_input_storage(5);
uint8_t *output = arrayBuilder.get_output_storage(5);
arrayBuilder.submit();
for(int c = 0; c < 5; c++) input[c] = c;
for(int c = 0; c < 5; c++) output[c] = c + 0x80;
arrayBuilder.flush();
arrayBuilder.submit();
[self assertMonotonicForInputSize:5 outputSize:5];
}
@end

View File

@ -16,7 +16,7 @@ using namespace Outputs::CRT;
void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator)
{
_openGL_output_builder->set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
const unsigned int syncCapacityLineChargeThreshold = 2;
const unsigned int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234
@ -28,24 +28,21 @@ 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 = IntermediateBufferWidth / cycles_per_line;
// store fundamental display configuration properties
_height_of_display = height_of_display;
_cycles_per_line = cycles_per_line * _time_multiplier;
time_multiplier_ = IntermediateBufferWidth / cycles_per_line;
unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
// generate timing values implied by the given arbuments
_sync_capacitor_charge_threshold = (int)(syncCapacityLineChargeThreshold * _cycles_per_line);
sync_capacitor_charge_threshold_ = (int)(syncCapacityLineChargeThreshold * multiplied_cycles_per_line);
// create the two flywheels
_horizontal_flywheel.reset(new Flywheel(_cycles_per_line, (millisecondsHorizontalRetraceTime * _cycles_per_line) >> 6, _cycles_per_line >> 6));
_vertical_flywheel.reset(new Flywheel(_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * _cycles_per_line, (_cycles_per_line * height_of_display) >> 3));
horizontal_flywheel_.reset(new Flywheel(multiplied_cycles_per_line, (millisecondsHorizontalRetraceTime * multiplied_cycles_per_line) >> 6, multiplied_cycles_per_line >> 6));
vertical_flywheel_.reset(new Flywheel(multiplied_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * multiplied_cycles_per_line, (multiplied_cycles_per_line * height_of_display) >> 3));
// figure out the divisor necessary to get the horizontal flywheel into a 16-bit range
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));
unsigned int real_clock_scan_period = (multiplied_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, _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, multiplied_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)
@ -62,24 +59,25 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display
}
}
CRT::CRT(unsigned int common_output_divisor) :
_sync_capacitor_charge_level(0),
_is_receiving_sync(false),
_sync_period(0),
_common_output_divisor(common_output_divisor),
_is_writing_composite_run(false),
_delegate(nullptr),
_frames_since_last_delegate_call(0) {}
CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
sync_capacitor_charge_level_(0),
is_receiving_sync_(false),
sync_period_(0),
common_output_divisor_(common_output_divisor),
is_writing_composite_run_(false),
delegate_(nullptr),
frames_since_last_delegate_call_(0),
openGL_output_builder_(buffer_depth) {}
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int buffer_depth) : CRT(common_output_divisor)
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int buffer_depth) :
CRT(common_output_divisor, buffer_depth)
{
_openGL_output_builder.reset(new OpenGLOutputBuilder(buffer_depth));
set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator);
}
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) : CRT(common_output_divisor)
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) :
CRT(common_output_divisor, buffer_depth)
{
_openGL_output_builder.reset(new OpenGLOutputBuilder(buffer_depth));
set_new_display_type(cycles_per_line, displayType);
}
@ -87,12 +85,12 @@ CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, Displ
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
{
return _vertical_flywheel->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced);
return vertical_flywheel_->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced);
}
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
{
return _horizontal_flywheel->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced);
return horizontal_flywheel_->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced);
}
#define output_x1() (*(uint16_t *)&next_run[OutputVertexOffsetOfHorizontal + 0])
@ -112,7 +110,7 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested,
void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type, uint16_t tex_x, uint16_t tex_y)
{
number_of_cycles *= _time_multiplier;
number_of_cycles *= time_multiplier_;
bool is_output_run = ((type == Scan::Type::Level) || (type == Scan::Type::Data));
@ -129,22 +127,23 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
hsync_requested = false;
vsync_requested = false;
bool is_output_segment = ((is_output_run && next_run_length) && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace());
bool is_output_segment = ((is_output_run && next_run_length) && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace());
uint8_t *next_run = nullptr;
if(is_output_segment && !_openGL_output_builder->composite_output_buffer_is_full())
if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full())
{
next_run = _openGL_output_builder->get_next_source_run();
next_run = openGL_output_builder_.array_builder.get_input_storage(SourceVertexSize);
}
if(next_run)
{
source_input_position_x1() = tex_x;
source_input_position_y() = tex_y;
source_output_position_x1() = (uint16_t)_horizontal_flywheel->get_current_output_position();
source_output_position_y() = _openGL_output_builder->get_composite_output_y();
source_phase() = _colour_burst_phase;
source_amplitude() = _colour_burst_amplitude;
source_phase_time() = (uint8_t)_colour_burst_time; // assumption: burst was within the first 1/16 of the line
source_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position();
// Don't write output_y now, write it later; we won't necessarily know what it is outside of the locked region
// source_output_position_y() = openGL_output_builder_->get_composite_output_y();
source_phase() = colour_burst_phase_;
source_amplitude() = colour_burst_amplitude_;
source_phase_time() = (uint8_t)colour_burst_time_; // assumption: burst was within the first 1/16 of the line
}
// decrement the number of cycles left to run for and increment the
@ -153,23 +152,21 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
// either charge or deplete the vertical retrace capacitor (making sure it stops at 0)
if(vsync_charging)
_sync_capacitor_charge_level += next_run_length;
sync_capacitor_charge_level_ += next_run_length;
else
_sync_capacitor_charge_level = std::max(_sync_capacitor_charge_level - (int)next_run_length, 0);
sync_capacitor_charge_level_ = std::max(sync_capacitor_charge_level_ - (int)next_run_length, 0);
// react to the incoming event...
_horizontal_flywheel->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None);
_vertical_flywheel->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None);
horizontal_flywheel_->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None);
vertical_flywheel_->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None);
if(next_run)
{
// 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(type == Scan::Type::Data && source_divider) tex_x += next_run_length / (time_multiplier_ * source_divider);
source_input_position_x2() = tex_x;
source_output_position_x2() = (uint16_t)_horizontal_flywheel->get_current_output_position();
_openGL_output_builder->complete_source_run();
source_output_position_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
}
// if this is horizontal retrace then advance the output line counter and bookend an output run
@ -177,53 +174,65 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
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());
(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)
{
if(
_openGL_output_builder->composite_output_run_has_room_for_vertex() &&
!_openGL_output_builder->composite_output_buffer_is_full())
!openGL_output_builder_.array_builder.is_full() &&
!openGL_output_builder_.composite_output_buffer_is_full())
{
if(!_is_writing_composite_run)
if(!is_writing_composite_run_)
{
_output_run.x1 = (uint16_t)_horizontal_flywheel->get_current_output_position();
_output_run.y = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider);
_output_run.tex_y = _openGL_output_builder->get_composite_output_y();
output_run_.x1 = (uint16_t)horizontal_flywheel_->get_current_output_position();
output_run_.y = (uint16_t)(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_);
}
else
{
uint8_t *next_run = _openGL_output_builder->get_next_output_run();
output_x1() = _output_run.x1;
output_position_y() = _output_run.y;
output_tex_y() = _output_run.tex_y;
output_x2() = (uint16_t)_horizontal_flywheel->get_current_output_position();
_openGL_output_builder->complete_output_run();
openGL_output_builder_.lock_output();
// Get and write all those previously unwritten output ys
uint16_t output_y = openGL_output_builder_.get_composite_output_y();
size_t size;
uint8_t *buffered_lines = openGL_output_builder_.array_builder.get_unflushed_input(size);
for(size_t position = 0; position < size; position += SourceVertexSize)
{
(*(uint16_t *)&buffered_lines[position + SourceVertexOffsetOfOutputStart + 2]) = output_y;
}
// Construct the output run
uint8_t *next_run = openGL_output_builder_.array_builder.get_output_storage(OutputVertexSize);
if(next_run)
{
output_x1() = output_run_.x1;
output_position_y() = output_run_.y;
output_tex_y() = output_y;
output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
}
openGL_output_builder_.array_builder.flush();
openGL_output_builder_.unlock_output();
}
_is_writing_composite_run ^= true;
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();
openGL_output_builder_.increment_composite_output_y();
}
// if this is vertical retrace then adcance a field
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace)
{
if(_delegate)
if(delegate_)
{
_frames_since_last_delegate_call++;
if(_frames_since_last_delegate_call == 20)
frames_since_last_delegate_call_++;
if(frames_since_last_delegate_call_ == 20)
{
// Yuck: to deal with the permitted ability of the delegate to make CRT changes that require the lock to be
// asserted during the delegate call, temporarily release the lock. TODO: find a less blunt instrument.
_openGL_output_builder->unlock_output();
_delegate->crt_did_end_batch_of_frames(this, _frames_since_last_delegate_call, _vertical_flywheel->get_and_reset_number_of_surprises());
_openGL_output_builder->lock_output();
_frames_since_last_delegate_call = 0;
delegate_->crt_did_end_batch_of_frames(this, frames_since_last_delegate_call_, vertical_flywheel_->get_and_reset_number_of_surprises());
frames_since_last_delegate_call_ = 0;
}
}
}
@ -250,31 +259,31 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
void CRT::output_scan(const Scan *const scan)
{
const bool this_is_sync = (scan->type == Scan::Type::Sync);
const bool is_trailing_edge = (_is_receiving_sync && !this_is_sync);
const bool is_leading_edge = (!_is_receiving_sync && this_is_sync);
_is_receiving_sync = this_is_sync;
const bool is_trailing_edge = (is_receiving_sync_ && !this_is_sync);
const bool is_leading_edge = (!is_receiving_sync_ && this_is_sync);
is_receiving_sync_ = this_is_sync;
// This introduces a blackout period close to the expected vertical sync point in which horizontal syncs are not
// recognised, effectively causing the horizontal flywheel to freewheel during that period. This attempts to seek
// the problem that vertical sync otherwise often starts halfway through a scanline, which confuses the horizontal
// flywheel. I'm currently unclear whether this is an accurate solution to this problem.
const bool hsync_requested = is_leading_edge && !_vertical_flywheel->is_near_expected_sync();
const bool vsync_requested = is_trailing_edge && (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold);
const bool hsync_requested = is_leading_edge && !vertical_flywheel_->is_near_expected_sync();
const bool vsync_requested = is_trailing_edge && (sync_capacitor_charge_level_ >= sync_capacitor_charge_threshold_);
// simplified colour burst logic: if it's within the back porch we'll take it
if(scan->type == Scan::Type::ColourBurst)
{
if(_horizontal_flywheel->get_current_time() < (_horizontal_flywheel->get_standard_period() * 12) >> 6)
if(horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6)
{
_colour_burst_time = (uint16_t)_horizontal_flywheel->get_current_time();
_colour_burst_phase = scan->phase;
_colour_burst_amplitude = scan->amplitude;
colour_burst_time_ = (uint16_t)horizontal_flywheel_->get_current_time();
colour_burst_phase_ = scan->phase;
colour_burst_amplitude_ = scan->amplitude;
}
}
// TODO: inspect raw data for potential colour burst if required
_sync_period = _is_receiving_sync ? (_sync_period + scan->number_of_cycles) : 0;
sync_period_ = is_receiving_sync_ ? (sync_period_ + scan->number_of_cycles) : 0;
advance_cycles(scan->number_of_cycles, scan->source_divider, hsync_requested, vsync_requested, this_is_sync, scan->type, scan->tex_x, scan->tex_y);
}
@ -283,36 +292,31 @@ void CRT::output_scan(const Scan *const scan)
*/
void CRT::output_sync(unsigned int number_of_cycles)
{
_openGL_output_builder->lock_output();
Scan scan{
.type = Scan::Type::Sync,
.number_of_cycles = number_of_cycles
};
output_scan(&scan);
_openGL_output_builder->unlock_output();
}
void CRT::output_blank(unsigned int number_of_cycles)
{
_openGL_output_builder->lock_output();
Scan scan {
.type = Scan::Type::Blank,
.number_of_cycles = number_of_cycles
};
output_scan(&scan);
_openGL_output_builder->unlock_output();
}
void CRT::output_level(unsigned int number_of_cycles)
{
_openGL_output_builder->lock_output();
if(!_openGL_output_builder->input_buffer_is_full())
if(!openGL_output_builder_.array_builder.is_full())
{
Scan scan {
.type = Scan::Type::Level,
.number_of_cycles = number_of_cycles,
.tex_x = _openGL_output_builder->get_last_write_x_posititon(),
.tex_y = _openGL_output_builder->get_last_write_y_posititon()
.tex_x = openGL_output_builder_.texture_builder.get_last_write_x_position(),
.tex_y = openGL_output_builder_.texture_builder.get_last_write_y_position()
};
output_scan(&scan);
}
@ -324,12 +328,10 @@ void CRT::output_level(unsigned int number_of_cycles)
};
output_scan(&scan);
}
_openGL_output_builder->unlock_output();
}
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude)
{
_openGL_output_builder->lock_output();
Scan scan {
.type = Scan::Type::ColourBurst,
.number_of_cycles = number_of_cycles,
@ -337,20 +339,18 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint
.amplitude = amplitude
};
output_scan(&scan);
_openGL_output_builder->unlock_output();
}
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider)
{
_openGL_output_builder->lock_output();
if(!_openGL_output_builder->input_buffer_is_full())
if(!openGL_output_builder_.array_builder.is_full())
{
_openGL_output_builder->reduce_previous_allocation_to(number_of_cycles / source_divider);
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_cycles / source_divider);
Scan scan {
.type = Scan::Type::Data,
.number_of_cycles = number_of_cycles,
.tex_x = _openGL_output_builder->get_last_write_x_posititon(),
.tex_y = _openGL_output_builder->get_last_write_y_posititon(),
.tex_x = openGL_output_builder_.texture_builder.get_last_write_x_position(),
.tex_y = openGL_output_builder_.texture_builder.get_last_write_y_position(),
.source_divider = source_divider
};
output_scan(&scan);
@ -363,18 +363,19 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider
};
output_scan(&scan);
}
_openGL_output_builder->unlock_output();
}
Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio)
{
first_cycle_after_sync *= _time_multiplier;
number_of_cycles *= _time_multiplier;
number_of_lines++;
first_cycle_after_sync *= time_multiplier_;
number_of_cycles *= time_multiplier_;
first_line_after_sync -= 2;
number_of_lines += 4;
// determine prima facie x extent
unsigned int horizontal_period = _horizontal_flywheel->get_standard_period();
unsigned int horizontal_scan_period = _horizontal_flywheel->get_scan_period();
unsigned int horizontal_period = horizontal_flywheel_->get_standard_period();
unsigned int horizontal_scan_period = horizontal_flywheel_->get_scan_period();
unsigned int horizontal_retrace_period = horizontal_period - horizontal_scan_period;
// make sure that the requested range is visible
@ -385,8 +386,8 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_
float width = (float)number_of_cycles / (float)horizontal_scan_period;
// determine prima facie y extent
unsigned int vertical_period = _vertical_flywheel->get_standard_period();
unsigned int vertical_scan_period = _vertical_flywheel->get_scan_period();
unsigned int vertical_period = vertical_flywheel_->get_standard_period();
unsigned int vertical_scan_period = vertical_flywheel_->get_scan_period();
unsigned int vertical_retrace_period = vertical_period - vertical_scan_period;
// make sure that the requested range is visible

View File

@ -14,6 +14,8 @@
#include "CRTTypes.hpp"
#include "Internals/Flywheel.hpp"
#include "Internals/CRTOpenGL.hpp"
#include "Internals/ArrayBuilder.hpp"
#include "Internals/TextureBuilder.hpp"
namespace Outputs {
namespace CRT {
@ -27,26 +29,22 @@ class Delegate {
class CRT {
private:
CRT(unsigned int common_output_divisor);
CRT(unsigned int common_output_divisor, unsigned int buffer_depth);
// the incoming clock lengths will be multiplied by something to give at least 1000
// sample points per line
unsigned int _time_multiplier;
const unsigned int _common_output_divisor;
// fundamental creator-specified properties
unsigned int _cycles_per_line;
unsigned int _height_of_display;
unsigned int time_multiplier_;
const unsigned int common_output_divisor_;
// the two flywheels regulating scanning
std::unique_ptr<Flywheel> _horizontal_flywheel, _vertical_flywheel;
uint16_t _vertical_flywheel_output_divider;
std::unique_ptr<Flywheel> horizontal_flywheel_, vertical_flywheel_;
uint16_t vertical_flywheel_output_divider_;
// elements of sync separation
bool _is_receiving_sync; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync)
int _sync_capacitor_charge_level; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
int _sync_capacitor_charge_threshold; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
unsigned int _sync_period;
bool is_receiving_sync_; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync)
int sync_capacitor_charge_level_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
int sync_capacitor_charge_threshold_; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
unsigned int sync_period_;
// each call to output_* generates a scan. A two-slot queue for scans allows edge extensions.
struct Scan {
@ -66,9 +64,9 @@ class CRT {
};
void output_scan(const Scan *scan);
uint8_t _colour_burst_phase, _colour_burst_amplitude;
uint16_t _colour_burst_time;
bool _is_writing_composite_run;
uint8_t colour_burst_phase_, colour_burst_amplitude_;
uint16_t colour_burst_time_;
bool is_writing_composite_run_;
// the outer entry point for dispatching output_sync, output_blank, output_level and output_data
void advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type, uint16_t tex_x, uint16_t tex_y);
@ -78,17 +76,17 @@ class CRT {
Flywheel::SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced);
Flywheel::SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced);
// OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here.
std::unique_ptr<OpenGLOutputBuilder> _openGL_output_builder;
// OpenGL state
OpenGLOutputBuilder openGL_output_builder_;
// temporary storage used during the construction of output runs
struct {
uint16_t x1, y, tex_y;
} _output_run;
uint16_t x1, y;
} output_run_;
// The delegate
Delegate *_delegate;
unsigned int _frames_since_last_delegate_call;
Delegate *delegate_;
unsigned int frames_since_last_delegate_call_;
public:
/*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency.
@ -194,7 +192,7 @@ class CRT {
*/
inline uint8_t *allocate_write_area(size_t required_length)
{
return _openGL_output_builder->allocate_write_area(required_length);
return openGL_output_builder_.texture_builder.allocate_write_area(required_length);
}
/*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state.
@ -202,7 +200,7 @@ class CRT {
*/
inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
{
_openGL_output_builder->draw_frame(output_width, output_height, only_if_dirty);
openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty);
}
/*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than
@ -214,7 +212,7 @@ class CRT {
*/
inline void set_openGL_context_will_change(bool should_delete_resources)
{
_openGL_output_builder->set_openGL_context_will_change(should_delete_resources);
openGL_output_builder_.set_openGL_context_will_change(should_delete_resources);
}
/*! Sets a function that will map from whatever data the machine provided to a composite signal.
@ -226,7 +224,7 @@ class CRT {
*/
inline void set_composite_sampling_function(const char *shader)
{
_openGL_output_builder->set_composite_sampling_function(shader);
openGL_output_builder_.set_composite_sampling_function(shader);
}
/*! Sets a function that will map from whatever data the machine provided to an RGB signal.
@ -244,24 +242,24 @@ class CRT {
*/
inline void set_rgb_sampling_function(const char *shader)
{
_openGL_output_builder->set_rgb_sampling_function(shader);
openGL_output_builder_.set_rgb_sampling_function(shader);
}
inline void set_output_device(OutputDevice output_device)
{
_openGL_output_builder->set_output_device(output_device);
openGL_output_builder_.set_output_device(output_device);
}
inline void set_visible_area(Rect visible_area)
{
_openGL_output_builder->set_visible_area(visible_area);
openGL_output_builder_.set_visible_area(visible_area);
}
Rect get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio);
inline void set_delegate(Delegate *delegate)
{
_delegate = delegate;
delegate_ = delegate;
}
};

View File

@ -0,0 +1,187 @@
//
// ArrayBuilder.cpp
// Clock Signal
//
// Created by Thomas Harte on 17/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "ArrayBuilder.hpp"
using namespace Outputs::CRT;
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size) :
output_(output_size, nullptr),
input_(input_size, nullptr)
{}
ArrayBuilder::ArrayBuilder(size_t input_size, size_t output_size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
output_(output_size, submission_function),
input_(input_size, submission_function)
{}
bool ArrayBuilder::is_full()
{
bool was_full;
buffer_mutex_.lock();
was_full = is_full_;
buffer_mutex_.unlock();
return was_full;
}
uint8_t *ArrayBuilder::get_input_storage(size_t size)
{
return get_storage(size, input_);
}
uint8_t *ArrayBuilder::get_unflushed_input(size_t &size)
{
return input_.get_unflushed(size);
}
uint8_t *ArrayBuilder::get_output_storage(size_t size)
{
return get_storage(size, output_);
}
uint8_t *ArrayBuilder::get_unflushed_output(size_t &size)
{
return output_.get_unflushed(size);
}
void ArrayBuilder::flush()
{
buffer_mutex_.lock();
if(!is_full_)
{
input_.flush();
output_.flush();
}
buffer_mutex_.unlock();
}
void ArrayBuilder::bind_input()
{
input_.bind();
}
void ArrayBuilder::bind_output()
{
output_.bind();
}
ArrayBuilder::Submission ArrayBuilder::submit()
{
ArrayBuilder::Submission submission;
buffer_mutex_.lock();
submission.input_size = input_.submit(true);
submission.output_size = output_.submit(false);
if(is_full_)
{
is_full_ = false;
input_.reset();
output_.reset();
}
buffer_mutex_.unlock();
return submission;
}
ArrayBuilder::Buffer::Buffer(size_t size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function) :
allocated_data(0), flushed_data(0), submitted_data(0), is_full(false), submission_function_(submission_function)
{
if(!submission_function_)
{
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)size, NULL, GL_STREAM_DRAW);
}
data.resize(size);
}
ArrayBuilder::Buffer::~Buffer()
{
if(!submission_function_)
glDeleteBuffers(1, &buffer);
}
uint8_t *ArrayBuilder::get_storage(size_t size, Buffer &buffer)
{
buffer_mutex_.lock();
uint8_t *pointer = buffer.get_storage(size);
if(!pointer) is_full_ = true;
buffer_mutex_.unlock();
return pointer;
}
uint8_t *ArrayBuilder::Buffer::get_storage(size_t size)
{
if(is_full || allocated_data + size > data.size())
{
is_full = true;
return nullptr;
}
uint8_t *pointer = &data[allocated_data];
allocated_data += size;
return pointer;
}
uint8_t *ArrayBuilder::Buffer::get_unflushed(size_t &size)
{
if(is_full)
{
return nullptr;
}
size = allocated_data - flushed_data;
return &data[flushed_data];
}
void ArrayBuilder::Buffer::flush()
{
if(submitted_data)
{
memcpy(data.data(), &data[submitted_data], allocated_data - submitted_data);
allocated_data -= submitted_data;
flushed_data -= submitted_data;
submitted_data = 0;
}
flushed_data = allocated_data;
if(was_reset)
{
allocated_data = 0;
flushed_data = 0;
submitted_data = 0;
was_reset = false;
}
}
size_t ArrayBuilder::Buffer::submit(bool is_input)
{
size_t length = flushed_data;
if(submission_function_)
submission_function_(is_input, data.data(), length);
else
{
glBindBuffer(GL_ARRAY_BUFFER, buffer);
uint8_t *destination = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
memcpy(destination, data.data(), length);
glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)length);
glUnmapBuffer(GL_ARRAY_BUFFER);
}
submitted_data = flushed_data;
return length;
}
void ArrayBuilder::Buffer::bind()
{
glBindBuffer(GL_ARRAY_BUFFER, buffer);
}
void ArrayBuilder::Buffer::reset()
{
was_reset = true;
is_full = false;
}

View File

@ -0,0 +1,111 @@
//
// ArrayBuilder.hpp
// Clock Signal
//
// Created by Thomas Harte on 17/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef ArrayBuilder_hpp
#define ArrayBuilder_hpp
#include <vector>
#include <mutex>
#include <memory>
#include "OpenGL.hpp"
namespace Outputs {
namespace CRT {
/*!
Owns two array buffers, an 'input' and an 'output' and vends pointers to allow an owner to write provisional data into those
plus a flush function to lock provisional data into place. Also supplies a submit method to transfer all currently locked
data to the GPU and bind_input/output methods to bind the internal buffers.
It is safe for one thread to communicate via the get_*_storage and flush inputs asynchronously from another that is making
use of the bind and submit outputs.
*/
class ArrayBuilder {
public:
/// Creates an instance of ArrayBuilder with @c output_size bytes of storage for the output buffer and
/// @c input_size bytes of storage for the input buffer.
ArrayBuilder(size_t input_size, size_t output_size);
/// Creates an instance of ArrayBuilder with @c output_size bytes of storage for the output buffer and
/// @c input_size bytes of storage for the input buffer that, rather than using OpenGL, will submit data
/// to the @c submission_function. [Teleological: this is provided as a testing hook.]
ArrayBuilder(size_t input_size, size_t output_size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function);
/// Attempts to add @c size bytes to the input set.
/// @returns a pointer to the allocated area if allocation was possible; @c nullptr otherwise.
uint8_t *get_input_storage(size_t size);
/// Gets the size of and a pointer to all data so far added to the input set but not yet flushed.
/// @returns a pointer from which it is safe to access @c size elements, which contains all regions returned via
/// @c get_input_storage in FIFO order.
uint8_t *get_unflushed_input(size_t &size);
/// Attempts to add @c size bytes to the output set.
/// @returns a pointer to the allocated area if allocation was possible; @c nullptr otherwise.
uint8_t *get_output_storage(size_t size);
/// Gets the size of and a pointer to all data so far added to the output set but not yet flushed.
/// @returns a pointer from which it is safe to access @c size elements, which contains all regions returned via
/// @c get_input_storage in FIFO order.
uint8_t *get_unflushed_output(size_t &size);
/// @returns @c true if either of the input or output storage areas is currently exhausted; @c false otherwise.
bool is_full();
/// If neither input nor output was exhausted since the last flush, atomically commits both input and output
/// up to the currently allocated size for use upon the next @c submit. Otherwise acts as a no-op.
void flush();
/// Binds the input array to GL_ARRAY_BUFFER.
void bind_input();
/// Binds the output array to GL_ARRAY_BUFFER.
void bind_output();
struct Submission {
size_t input_size, output_size;
};
/// Submits all flushed input and output data to the corresponding arrays.
/// @returns A @c Submission record, indicating how much data of each type was submitted.
Submission submit();
private:
class Buffer {
public:
Buffer(size_t size, std::function<void(bool is_input, uint8_t *, size_t)> submission_function);
~Buffer();
uint8_t *get_storage(size_t size);
uint8_t *get_unflushed(size_t &size);
void flush();
size_t submit(bool is_input);
void bind();
void reset();
private:
bool is_full, was_reset;
GLuint buffer;
std::function<void(bool is_input, uint8_t *, size_t)> submission_function_;
std::vector<uint8_t> data;
size_t allocated_data;
size_t flushed_data;
size_t submitted_data;
} output_, input_;
uint8_t *get_storage(size_t size, Buffer &buffer);
std::mutex buffer_mutex_;
bool is_full_;
};
}
}
#endif /* ArrayBuilder_hpp */

View File

@ -37,7 +37,7 @@ const GLsizei InputBufferBuilderHeight = 512;
// This is the size of the intermediate buffers used during composite to RGB conversion
const GLsizei IntermediateBufferWidth = 2048;
const GLsizei IntermediateBufferHeight = 1024;
const GLsizei IntermediateBufferHeight = 512;
// Some internal buffer sizes
const GLsizeiptr OutputVertexBufferDataSize = OutputVertexSize * IntermediateBufferHeight; // i.e. the maximum number of scans of output that can be created between draws

View File

@ -1,110 +0,0 @@
//
// CRTInputBufferBuilder.cpp
// Clock Signal
//
// Created by Thomas Harte on 08/03/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "CRTInputBufferBuilder.hpp"
#include "CRTOpenGL.hpp"
#include <string.h>
using namespace Outputs::CRT;
CRTInputBufferBuilder::CRTInputBufferBuilder(size_t bytes_per_pixel) :
_bytes_per_pixel(bytes_per_pixel),
_next_write_x_position(0),
_next_write_y_position(0),
_image(new uint8_t[bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight])
{}
void CRTInputBufferBuilder::allocate_write_area(size_t required_length)
{
if(_next_write_y_position != InputBufferBuilderHeight)
{
_last_allocation_amount = required_length;
if(_next_write_x_position + required_length + 2 > InputBufferBuilderWidth)
{
_next_write_x_position = 0;
_next_write_y_position++;
if(_next_write_y_position == InputBufferBuilderHeight)
return;
}
_write_x_position = _next_write_x_position + 1;
_write_y_position = _next_write_y_position;
_write_target_pointer = (_write_y_position * InputBufferBuilderWidth) + _write_x_position;
_next_write_x_position += required_length + 2;
}
}
bool CRTInputBufferBuilder::is_full()
{
return (_next_write_y_position == InputBufferBuilderHeight);
}
void CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length)
{
if(_next_write_y_position == InputBufferBuilderHeight) return;
uint8_t *const image_pointer = _image.get();
// correct if the writing cursor was reset while a client was writing
if(_next_write_x_position == 0 && _next_write_y_position == 0)
{
memmove(&image_pointer[_bytes_per_pixel], &image_pointer[_write_target_pointer * _bytes_per_pixel], actual_length * _bytes_per_pixel);
_write_target_pointer = 1;
_last_allocation_amount = actual_length;
_next_write_x_position = (uint16_t)(actual_length + 2);
_write_x_position = 1;
_write_y_position = 0;
}
// book end the allocation with duplicates of the first and last pixel, to protect
// against rounding errors when this run is drawn
memcpy( &image_pointer[(_write_target_pointer - 1) * _bytes_per_pixel],
&image_pointer[_write_target_pointer * _bytes_per_pixel],
_bytes_per_pixel);
memcpy( &image_pointer[(_write_target_pointer + actual_length) * _bytes_per_pixel],
&image_pointer[(_write_target_pointer + actual_length - 1) * _bytes_per_pixel],
_bytes_per_pixel);
// return any allocated length that wasn't actually used to the available pool
_next_write_x_position -= (_last_allocation_amount - actual_length);
}
uint8_t *CRTInputBufferBuilder::get_image_pointer()
{
return _image.get();
}
uint16_t CRTInputBufferBuilder::get_and_finalise_current_line()
{
uint16_t result = _write_y_position + (_next_write_x_position ? 1 : 0);
_next_write_x_position = _next_write_y_position = 0;
return result;
}
uint8_t *CRTInputBufferBuilder::get_write_target()
{
return (_next_write_y_position == InputBufferBuilderHeight) ? nullptr : &_image.get()[_write_target_pointer * _bytes_per_pixel];
}
uint16_t CRTInputBufferBuilder::get_last_write_x_position()
{
return _write_x_position;
}
uint16_t CRTInputBufferBuilder::get_last_write_y_position()
{
return _write_y_position;
}
size_t CRTInputBufferBuilder::get_bytes_per_pixel()
{
return _bytes_per_pixel;
}

View File

@ -1,62 +0,0 @@
//
// CRTInputBufferBuilder.hpp
// Clock Signal
//
// Created by Thomas Harte on 08/03/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef CRTInputBufferBuilder_hpp
#define CRTInputBufferBuilder_hpp
#include <stdint.h>
#include <stdarg.h>
#include <stddef.h>
#include "CRTConstants.hpp"
#include "OpenGL.hpp"
#include <memory>
namespace Outputs {
namespace CRT {
struct CRTInputBufferBuilder {
CRTInputBufferBuilder(size_t bytes_per_pixel);
void allocate_write_area(size_t required_length);
void reduce_previous_allocation_to(size_t actual_length);
uint16_t get_and_finalise_current_line();
uint8_t *get_image_pointer();
uint8_t *get_write_target();
uint16_t get_last_write_x_position();
uint16_t get_last_write_y_position();
size_t get_bytes_per_pixel();
bool is_full();
private:
// where pixel data will be put to the next time a write is requested
uint16_t _next_write_x_position, _next_write_y_position;
// the most recent position returned for pixel data writing
uint16_t _write_x_position, _write_y_position;
// details of the most recent allocation
size_t _write_target_pointer;
size_t _last_allocation_amount;
// the buffer size
size_t _bytes_per_pixel;
// the buffer
std::unique_ptr<uint8_t> _image;
};
}
}
#endif /* CRTInputBufferBuilder_hpp */

View File

@ -13,88 +13,6 @@
#include "../../../SignalProcessing/FIRFilter.hpp"
#include "Shaders/OutputShader.hpp"
static const GLint internalFormatForDepth(size_t depth)
{
switch(depth)
{
default: return GL_FALSE;
case 1: return GL_R8UI;
case 2: return GL_RG8UI;
case 3: return GL_RGB8UI;
case 4: return GL_RGBA8UI;
}
}
static const GLenum formatForDepth(size_t depth)
{
switch(depth)
{
default: return GL_FALSE;
case 1: return GL_RED_INTEGER;
case 2: return GL_RG_INTEGER;
case 3: return GL_RGB_INTEGER;
case 4: return GL_RGBA_INTEGER;
}
}
struct Range {
GLsizei location, length;
};
static int getCircularRanges(GLsizei *start_pointer, GLsizei *end_pointer, GLsizei buffer_length, GLsizei granularity, GLsizei offset, Range *ranges)
{
GLsizei start = *start_pointer;
GLsizei end = *end_pointer;
*end_pointer %= buffer_length;
*start_pointer = *end_pointer;
start += offset;
end += offset;
start -= start%granularity;
end -= end%granularity;
GLsizei length = end - start;
if(!length) return 0;
if(length >= buffer_length)
{
ranges[0].location = 0;
ranges[0].length = buffer_length;
return 1;
}
else
{
ranges[0].location = start % buffer_length;
if(ranges[0].location + length <= buffer_length)
{
ranges[0].length = length;
return 1;
}
else
{
ranges[0].length = buffer_length - ranges[0].location;
ranges[1].location = 0;
ranges[1].length = length - ranges[0].length;
return 2;
}
}
}
static GLsizei submitArrayData(GLuint buffer, uint8_t *source, GLsizei *length_pointer)
{
GLsizei length = *length_pointer;
glBindBuffer(GL_ARRAY_BUFFER, buffer);
uint8_t *data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
memcpy(data, source, (size_t)length);
glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, length);
glUnmapBuffer(GL_ARRAY_BUFFER);
*length_pointer = 0;
return length;
}
using namespace Outputs::CRT;
namespace {
@ -106,78 +24,46 @@ namespace {
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE5;
}
OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) :
_output_mutex(new std::mutex),
_draw_mutex(new std::mutex),
_visible_area(Rect(0, 0, 1, 1)),
_composite_src_output_y(0),
_cleared_composite_output_y(0),
_composite_shader(nullptr),
_rgb_shader(nullptr),
_output_buffer_data(new uint8_t[OutputVertexBufferDataSize]),
_source_buffer_data(new uint8_t[SourceVertexBufferDataSize]),
_output_buffer_data_pointer(0),
_source_buffer_data_pointer(0),
_last_output_width(0),
_last_output_height(0),
_fence(nullptr)
OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
visible_area_(Rect(0, 0, 1, 1)),
composite_src_output_y_(0),
composite_shader_(nullptr),
rgb_shader_(nullptr),
last_output_width_(0),
last_output_height_(0),
fence_(nullptr),
texture_builder(bytes_per_pixel, source_data_texture_unit),
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize),
composite_texture_(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit),
separated_texture_(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit),
filtered_y_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit),
filtered_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit)
{
_buffer_builder.reset(new CRTInputBufferBuilder(buffer_depth));
glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR);
glBlendColor(0.6f, 0.6f, 0.6f, 1.0f);
// Create intermediate textures and bind to slots 0, 1 and 2
compositeTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit));
separatedTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit));
filteredYTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit));
filteredTexture.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit));
// create the surce texture
glGenTextures(1, &textureName);
glActiveTexture(source_data_texture_unit);
glBindTexture(GL_TEXTURE_2D, textureName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
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, internalFormatForDepth(_buffer_builder->get_bytes_per_pixel()), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(_buffer_builder->get_bytes_per_pixel()), GL_UNSIGNED_BYTE, nullptr);
// create the output vertex array
glGenVertexArrays(1, &output_vertex_array);
// create a buffer for output vertex attributes
glGenBuffers(1, &output_array_buffer);
glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer);
glBufferData(GL_ARRAY_BUFFER, OutputVertexBufferDataSize, NULL, GL_STREAM_DRAW);
glGenVertexArrays(1, &output_vertex_array_);
// create the source vertex array
glGenVertexArrays(1, &source_vertex_array);
// create a buffer for source vertex attributes
glGenBuffers(1, &source_array_buffer);
glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer);
glBufferData(GL_ARRAY_BUFFER, SourceVertexBufferDataSize, NULL, GL_STREAM_DRAW);
glGenVertexArrays(1, &source_vertex_array_);
}
OpenGLOutputBuilder::~OpenGLOutputBuilder()
{
glDeleteTextures(1, &textureName);
glDeleteBuffers(1, &output_array_buffer);
glDeleteBuffers(1, &source_array_buffer);
glDeleteVertexArrays(1, &output_vertex_array);
glDeleteVertexArrays(1, &output_vertex_array_);
free(_composite_shader);
free(_rgb_shader);
free(composite_shader_);
free(rgb_shader_);
}
void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
{
// lock down any other draw_frames
_draw_mutex->lock();
draw_mutex_.lock();
// establish essentials
if(!output_shader_program)
if(!output_shader_program_)
{
prepare_composite_input_shaders();
prepare_rgb_input_shaders();
@ -190,67 +76,51 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
set_colour_space_uniforms();
}
if(_fence != nullptr)
if(fence_ != nullptr)
{
// if the GPU is still busy, don't wait; we'll catch it next time
if(glClientWaitSync(_fence, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED)
if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, only_if_dirty ? 0 : GL_TIMEOUT_IGNORED) == GL_TIMEOUT_EXPIRED)
{
_draw_mutex->unlock();
draw_mutex_.unlock();
return;
}
glDeleteSync(_fence);
glDeleteSync(fence_);
}
// make sure there's a target to draw to
if(!framebuffer || framebuffer->get_height() != output_height || framebuffer->get_width() != output_width)
if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width)
{
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit));
if(framebuffer)
if(framebuffer_)
{
new_framebuffer->bind_framebuffer();
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(pixel_accumulation_texture_unit);
framebuffer->bind_texture();
framebuffer->draw((float)output_width / (float)output_height);
framebuffer_->bind_texture();
framebuffer_->draw((float)output_width / (float)output_height);
new_framebuffer->bind_texture();
}
framebuffer = std::move(new_framebuffer);
framebuffer_ = std::move(new_framebuffer);
}
// lock out the machine emulation until data is copied
_output_mutex->lock();
output_mutex_.lock();
// release the mapping, giving up on trying to draw if data has been lost
GLsizei submitted_output_data = submitArrayData(output_array_buffer, _output_buffer_data.get(), &_output_buffer_data_pointer);
ArrayBuilder::Submission array_submission = array_builder.submit();
// bind and flush the source array buffer
GLsizei submitted_source_data = submitArrayData(source_array_buffer, _source_buffer_data.get(), &_source_buffer_data_pointer);
// upload new source pixels, if any
glActiveTexture(source_data_texture_unit);
texture_builder.submit();
// determine how many lines are newly reclaimed; they'll need to be cleared
Range clearing_zones[2];
// the clearing zones for the composite output Y are calculated with a fixed offset of '1' which has the effect of clearing
// one ahead of the expected drawing area this frame; that's because the current _composite_src_output_y may or may not have been
// written to during the last update, so we want it to have been cleared during the last update.
int number_of_clearing_zones = getCircularRanges(&_cleared_composite_output_y, &_composite_src_output_y, IntermediateBufferHeight, 1, 1, clearing_zones);
uint16_t completed_texture_y = _buffer_builder->get_and_finalise_current_line();
// upload new source pixels
if(completed_texture_y)
{
glActiveTexture(source_data_texture_unit);
glTexSubImage2D( GL_TEXTURE_2D, 0,
0, 0,
InputBufferBuilderWidth, completed_texture_y,
formatForDepth(_buffer_builder->get_bytes_per_pixel()), GL_UNSIGNED_BYTE,
_buffer_builder->get_image_pointer());
}
// buffer usage restart from 0 for the next time around
composite_src_output_y_ = 0;
// data having been grabbed, allow the machine to continue
_output_mutex->unlock();
output_mutex_.unlock();
struct RenderStage {
OpenGL::TextureTarget *const target;
@ -258,196 +128,184 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
float clear_colour[3];
};
// for composite video, go through four steps to get to something that can be painted to the output
RenderStage composite_render_stages[] =
{
{compositeTexture.get(), composite_input_shader_program.get(), {0.0, 0.0, 0.0}},
{separatedTexture.get(), composite_separation_filter_program.get(), {0.0, 0.5, 0.5}},
{filteredYTexture.get(), composite_y_filter_shader_program.get(), {0.0, 0.5, 0.5}},
{filteredTexture.get(), composite_chrominance_filter_shader_program.get(), {0.0, 0.0, 0.0}},
{&composite_texture_, composite_input_shader_program_.get(), {0.0, 0.0, 0.0}},
{&separated_texture_, composite_separation_filter_program_.get(), {0.0, 0.5, 0.5}},
{&filtered_y_texture_, composite_y_filter_shader_program_.get(), {0.0, 0.5, 0.5}},
{&filtered_texture_, composite_chrominance_filter_shader_program_.get(), {0.0, 0.0, 0.0}},
{nullptr}
};
// for RGB video, there's only two steps
RenderStage rgb_render_stages[] =
{
{compositeTexture.get(), rgb_input_shader_program.get(), {0.0, 0.0, 0.0}},
{filteredTexture.get(), rgb_filter_shader_program.get(), {0.0, 0.0, 0.0}},
{&composite_texture_, rgb_input_shader_program_.get(), {0.0, 0.0, 0.0}},
{&filtered_texture_, rgb_filter_shader_program_.get(), {0.0, 0.0, 0.0}},
{nullptr}
};
RenderStage *active_pipeline = (_output_device == Television || !rgb_input_shader_program) ? composite_render_stages : rgb_render_stages;
RenderStage *active_pipeline = (output_device_ == Television || !rgb_input_shader_program_) ? composite_render_stages : rgb_render_stages;
// for television, update intermediate buffers and then draw; for a monitor, just draw
if(submitted_source_data)
if(array_submission.input_size || array_submission.output_size)
{
// all drawing will be from the source vertex array and without blending
glBindVertexArray(source_vertex_array);
glBindVertexArray(source_vertex_array_);
glDisable(GL_BLEND);
while(active_pipeline->target)
{
// switch to the initial texture
active_pipeline->target->bind_framebuffer();
// switch to the framebuffer and shader associated with this stage
active_pipeline->shader->bind();
active_pipeline->target->bind_framebuffer();
// clear as desired
if(number_of_clearing_zones)
// if this is the final stage before painting to the CRT, clear the framebuffer before drawing in order to blank out
// those portions for which no input was provided
if(!active_pipeline[1].target)
{
glEnable(GL_SCISSOR_TEST);
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0);
for(int c = 0; c < number_of_clearing_zones; c++)
{
glScissor(0, clearing_zones[c].location, IntermediateBufferWidth, clearing_zones[c].length);
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
// draw as desired
glDrawArraysInstanced(GL_LINES, 0, 2, submitted_source_data / SourceVertexSize);
// draw
glDrawArraysInstanced(GL_LINES, 0, 2, (GLsizei)array_submission.input_size / SourceVertexSize);
active_pipeline++;
}
}
// transfer to framebuffer
framebuffer->bind_framebuffer();
// prepare to transfer to framebuffer
framebuffer_->bind_framebuffer();
if(submitted_output_data)
{
// draw from the output array buffer, with blending
glBindVertexArray(output_vertex_array_);
glEnable(GL_BLEND);
// Ensure we're back on the output framebuffer, drawing from the output array buffer
glBindVertexArray(output_vertex_array);
// update uniforms (implicitly binding the shader)
if(_last_output_width != output_width || _last_output_height != output_height)
// update uniforms, then bind the target
if(last_output_width_ != output_width || last_output_height_ != output_height)
{
output_shader_program->set_output_size(output_width, output_height, _visible_area);
_last_output_width = output_width;
_last_output_height = output_height;
output_shader_program_->set_output_size(output_width, output_height, visible_area_);
last_output_width_ = output_width;
last_output_height_ = output_height;
}
output_shader_program->bind();
output_shader_program_->bind();
// draw
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, submitted_output_data / OutputVertexSize);
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize);
}
// copy framebuffer to the intended place
glDisable(GL_BLEND);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(pixel_accumulation_texture_unit);
framebuffer->bind_texture();
framebuffer->draw((float)output_width / (float)output_height);
// glViewport(0, 0, (GLsizei)output_width / 4, (GLsizei)output_height / 4);
// compositeTexture->bind_texture();
// compositeTexture->draw((float)output_width / (float)output_height);
framebuffer_->bind_texture();
framebuffer_->draw((float)output_width / (float)output_height);
_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
_draw_mutex->unlock();
fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
draw_mutex_.unlock();
}
void OpenGLOutputBuilder::reset_all_OpenGL_state()
{
composite_input_shader_program = nullptr;
composite_separation_filter_program = nullptr;
composite_y_filter_shader_program = nullptr;
composite_chrominance_filter_shader_program = nullptr;
rgb_input_shader_program = nullptr;
rgb_filter_shader_program = nullptr;
output_shader_program = nullptr;
framebuffer = nullptr;
_last_output_width = _last_output_height = 0;
composite_input_shader_program_ = nullptr;
composite_separation_filter_program_ = nullptr;
composite_y_filter_shader_program_ = nullptr;
composite_chrominance_filter_shader_program_ = nullptr;
rgb_input_shader_program_ = nullptr;
rgb_filter_shader_program_ = nullptr;
output_shader_program_ = nullptr;
framebuffer_ = nullptr;
last_output_width_ = last_output_height_ = 0;
}
void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources)
{
_output_mutex->lock();
output_mutex_.lock();
reset_all_OpenGL_state();
_output_mutex->unlock();
output_mutex_.unlock();
}
void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader)
{
_output_mutex->lock();
_composite_shader = strdup(shader);
output_mutex_.lock();
composite_shader_ = strdup(shader);
reset_all_OpenGL_state();
_output_mutex->unlock();
output_mutex_.unlock();
}
void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader)
{
_output_mutex->lock();
_rgb_shader = strdup(shader);
output_mutex_.lock();
rgb_shader_ = strdup(shader);
reset_all_OpenGL_state();
_output_mutex->unlock();
output_mutex_.unlock();
}
#pragma mark - Program compilation
void OpenGLOutputBuilder::prepare_composite_input_shaders()
{
composite_input_shader_program = OpenGL::IntermediateShader::make_source_conversion_shader(_composite_shader, _rgb_shader);
composite_input_shader_program->set_source_texture_unit(source_data_texture_unit);
composite_input_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_input_shader_program_ = OpenGL::IntermediateShader::make_source_conversion_shader(composite_shader_, rgb_shader_);
composite_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_separation_filter_program = OpenGL::IntermediateShader::make_chroma_luma_separation_shader();
composite_separation_filter_program->set_source_texture_unit(composite_texture_unit);
composite_separation_filter_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_separation_filter_program_ = OpenGL::IntermediateShader::make_chroma_luma_separation_shader();
composite_separation_filter_program_->set_source_texture_unit(composite_texture_unit);
composite_separation_filter_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_y_filter_shader_program = OpenGL::IntermediateShader::make_luma_filter_shader();
composite_y_filter_shader_program->set_source_texture_unit(separated_texture_unit);
composite_y_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_y_filter_shader_program_ = OpenGL::IntermediateShader::make_luma_filter_shader();
composite_y_filter_shader_program_->set_source_texture_unit(separated_texture_unit);
composite_y_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_chrominance_filter_shader_program = OpenGL::IntermediateShader::make_chroma_filter_shader();
composite_chrominance_filter_shader_program->set_source_texture_unit(filtered_y_texture_unit);
composite_chrominance_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_chrominance_filter_shader_program_ = OpenGL::IntermediateShader::make_chroma_filter_shader();
composite_chrominance_filter_shader_program_->set_source_texture_unit(filtered_y_texture_unit);
composite_chrominance_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
}
void OpenGLOutputBuilder::prepare_rgb_input_shaders()
{
if(_rgb_shader)
if(rgb_shader_)
{
rgb_input_shader_program = OpenGL::IntermediateShader::make_rgb_source_shader(_rgb_shader);
rgb_input_shader_program->set_source_texture_unit(source_data_texture_unit);
rgb_input_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
rgb_input_shader_program_ = OpenGL::IntermediateShader::make_rgb_source_shader(rgb_shader_);
rgb_input_shader_program_->set_source_texture_unit(source_data_texture_unit);
rgb_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
rgb_filter_shader_program = OpenGL::IntermediateShader::make_rgb_filter_shader();
rgb_filter_shader_program->set_source_texture_unit(composite_texture_unit);
rgb_filter_shader_program->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
rgb_filter_shader_program_ = OpenGL::IntermediateShader::make_rgb_filter_shader();
rgb_filter_shader_program_->set_source_texture_unit(composite_texture_unit);
rgb_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
}
}
void OpenGLOutputBuilder::prepare_source_vertex_array()
{
if(composite_input_shader_program)
if(composite_input_shader_program_)
{
glBindVertexArray(source_vertex_array);
glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer);
glBindVertexArray(source_vertex_array_);
array_builder.bind_input();
composite_input_shader_program->enable_vertex_attribute_with_pointer("inputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfInputStart, 1);
composite_input_shader_program->enable_vertex_attribute_with_pointer("outputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfOutputStart, 1);
composite_input_shader_program->enable_vertex_attribute_with_pointer("ends", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfEnds, 1);
composite_input_shader_program->enable_vertex_attribute_with_pointer("phaseTimeAndAmplitude", 3, GL_UNSIGNED_BYTE, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfPhaseTimeAndAmplitude, 1);
composite_input_shader_program_->enable_vertex_attribute_with_pointer("inputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfInputStart, 1);
composite_input_shader_program_->enable_vertex_attribute_with_pointer("outputStart", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfOutputStart, 1);
composite_input_shader_program_->enable_vertex_attribute_with_pointer("ends", 2, GL_UNSIGNED_SHORT, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfEnds, 1);
composite_input_shader_program_->enable_vertex_attribute_with_pointer("phaseTimeAndAmplitude", 3, GL_UNSIGNED_BYTE, GL_FALSE, SourceVertexSize, (void *)SourceVertexOffsetOfPhaseTimeAndAmplitude, 1);
}
}
void OpenGLOutputBuilder::prepare_output_shader()
{
output_shader_program = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false);
output_shader_program->set_source_texture_unit(filtered_texture_unit);
output_shader_program_ = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false);
output_shader_program_->set_source_texture_unit(filtered_texture_unit);
}
void OpenGLOutputBuilder::prepare_output_vertex_array()
{
if(output_shader_program)
if(output_shader_program_)
{
glBindVertexArray(output_vertex_array);
glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer);
output_shader_program->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1);
output_shader_program->enable_vertex_attribute_with_pointer("vertical", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfVertical, 1);
glBindVertexArray(output_vertex_array_);
array_builder.bind_output();
output_shader_program_->enable_vertex_attribute_with_pointer("horizontal", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfHorizontal, 1);
output_shader_program_->enable_vertex_attribute_with_pointer("vertical", 2, GL_UNSIGNED_SHORT, GL_FALSE, OutputVertexSize, (void *)OutputVertexOffsetOfVertical, 1);
}
}
@ -455,27 +313,27 @@ void OpenGLOutputBuilder::prepare_output_vertex_array()
void OpenGLOutputBuilder::set_output_device(OutputDevice output_device)
{
if(_output_device != output_device)
if(output_device_ != output_device)
{
_output_device = output_device;
_composite_src_output_y = 0;
_last_output_width = 0;
_last_output_height = 0;
output_device_ = output_device;
composite_src_output_y_ = 0;
last_output_width_ = 0;
last_output_height_ = 0;
}
}
void OpenGLOutputBuilder::set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider)
{
_output_mutex->lock();
_input_frequency = input_frequency;
_cycles_per_line = cycles_per_line;
_height_of_display = height_of_display;
_horizontal_scan_period = horizontal_scan_period;
_vertical_scan_period = vertical_scan_period;
_vertical_period_divider = vertical_period_divider;
output_mutex_.lock();
input_frequency_ = input_frequency;
cycles_per_line_ = cycles_per_line;
height_of_display_ = height_of_display;
horizontal_scan_period_ = horizontal_scan_period;
vertical_scan_period_ = vertical_scan_period;
vertical_period_divider_ = vertical_period_divider;
set_timing_uniforms();
_output_mutex->unlock();
output_mutex_.unlock();
}
#pragma mark - Internal Configuration
@ -490,7 +348,7 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
GLfloat *fromRGB, *toRGB;
switch(_colour_space)
switch(colour_space_)
{
case ColourSpace::YIQ:
fromRGB = rgbToYIQ;
@ -503,31 +361,31 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
break;
}
if(composite_input_shader_program) composite_input_shader_program->set_colour_conversion_matrices(fromRGB, toRGB);
if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_colour_conversion_matrices(fromRGB, toRGB);
if(composite_input_shader_program_) composite_input_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
}
void OpenGLOutputBuilder::set_timing_uniforms()
{
OpenGL::IntermediateShader *intermediate_shaders[] = {
composite_input_shader_program.get(),
composite_separation_filter_program.get(),
composite_y_filter_shader_program.get(),
composite_chrominance_filter_shader_program.get()
composite_input_shader_program_.get(),
composite_separation_filter_program_.get(),
composite_y_filter_shader_program_.get(),
composite_chrominance_filter_shader_program_.get()
};
bool extends = false;
float phaseCyclesPerTick = (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line);
float phaseCyclesPerTick = (float)colour_cycle_numerator_ / (float)(colour_cycle_denominator_ * cycles_per_line_);
for(int c = 0; c < 3; c++)
{
if(intermediate_shaders[c]) intermediate_shaders[c]->set_phase_cycles_per_sample(phaseCyclesPerTick, extends);
extends = true;
}
if(output_shader_program) output_shader_program->set_timing(_height_of_display, _cycles_per_line, _horizontal_scan_period, _vertical_scan_period, _vertical_period_divider);
if(output_shader_program_) output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_);
float colour_subcarrier_frequency = (float)_colour_cycle_numerator / (float)_colour_cycle_denominator;
if(composite_separation_filter_program) composite_separation_filter_program->set_separation_frequency(_cycles_per_line, colour_subcarrier_frequency);
if(composite_y_filter_shader_program) composite_y_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.66f);
if(composite_chrominance_filter_shader_program) composite_chrominance_filter_shader_program->set_filter_coefficients(_cycles_per_line, colour_subcarrier_frequency * 0.5f);
if(rgb_filter_shader_program) rgb_filter_shader_program->set_filter_coefficients(_cycles_per_line, (float)_input_frequency * 0.5f);
float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_;
if(composite_separation_filter_program_) composite_separation_filter_program_->set_separation_frequency(cycles_per_line_, colour_subcarrier_frequency);
if(composite_y_filter_shader_program_) composite_y_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.66f);
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.5f);
if(rgb_filter_shader_program_) rgb_filter_shader_program_->set_filter_coefficients(cycles_per_line_, (float)input_frequency_ * 0.5f);
}

View File

@ -14,12 +14,15 @@
#include "OpenGL.hpp"
#include "TextureTarget.hpp"
#include "Shader.hpp"
#include "CRTInputBufferBuilder.hpp"
#include "ArrayBuilder.hpp"
#include "TextureBuilder.hpp"
#include "Shaders/OutputShader.hpp"
#include "Shaders/IntermediateShader.hpp"
#include <mutex>
#include <vector>
namespace Outputs {
namespace CRT {
@ -27,25 +30,25 @@ namespace CRT {
class OpenGLOutputBuilder {
private:
// colour information
ColourSpace _colour_space;
unsigned int _colour_cycle_numerator;
unsigned int _colour_cycle_denominator;
OutputDevice _output_device;
ColourSpace colour_space_;
unsigned int colour_cycle_numerator_;
unsigned int colour_cycle_denominator_;
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;
unsigned int _vertical_scan_period;
unsigned int _vertical_period_divider;
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_;
// The user-supplied visible area
Rect _visible_area;
Rect visible_area_;
// Other things the caller may have provided.
char *_composite_shader;
char *_rgb_shader;
char *composite_shader_;
char *rgb_shader_;
// Methods used by the OpenGL code
void prepare_output_shader();
@ -56,32 +59,27 @@ class OpenGLOutputBuilder {
void prepare_source_vertex_array();
// the run and input data buffers
std::unique_ptr<CRTInputBufferBuilder> _buffer_builder;
std::unique_ptr<std::mutex> _output_mutex;
std::unique_ptr<std::mutex> _draw_mutex;
std::mutex output_mutex_;
std::mutex draw_mutex_;
// transient buffers indicating composite data not yet decoded
GLsizei _composite_src_output_y, _cleared_composite_output_y;
GLsizei composite_src_output_y_;
std::unique_ptr<OpenGL::OutputShader> output_shader_program;
std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program, composite_separation_filter_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program;
std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program, rgb_filter_shader_program;
std::unique_ptr<OpenGL::OutputShader> output_shader_program_;
std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_, composite_separation_filter_program_, composite_y_filter_shader_program_, composite_chrominance_filter_shader_program_;
std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program_, rgb_filter_shader_program_;
std::unique_ptr<OpenGL::TextureTarget> compositeTexture; // receives raw composite levels
std::unique_ptr<OpenGL::TextureTarget> separatedTexture; // receives unfiltered Y in the R channel plus unfiltered but demodulated chrominance in G and B
std::unique_ptr<OpenGL::TextureTarget> filteredYTexture; // receives filtered Y in the R channel plus unfiltered chrominance in G and B
std::unique_ptr<OpenGL::TextureTarget> filteredTexture; // receives filtered YIQ or YUV
OpenGL::TextureTarget composite_texture_; // receives raw composite levels
OpenGL::TextureTarget separated_texture_; // receives unfiltered Y in the R channel plus unfiltered but demodulated chrominance in G and B
OpenGL::TextureTarget filtered_y_texture_; // receives filtered Y in the R channel plus unfiltered chrominance in G and B
OpenGL::TextureTarget filtered_texture_; // receives filtered YIQ or YUV
std::unique_ptr<OpenGL::TextureTarget> framebuffer; // the current pixel output
std::unique_ptr<OpenGL::TextureTarget> framebuffer_; // the current pixel output
GLuint output_array_buffer, output_vertex_array;
GLuint source_array_buffer, source_vertex_array;
GLuint output_vertex_array_;
GLuint source_vertex_array_;
unsigned int _last_output_width, _last_output_height;
GLuint textureName, shadowMaskTextureName;
GLuint defaultFramebuffer;
unsigned int last_output_width_, last_output_height_;
void set_timing_uniforms();
void set_colour_space_uniforms();
@ -89,107 +87,59 @@ class OpenGLOutputBuilder {
void establish_OpenGL_state();
void reset_all_OpenGL_state();
GLsync fence_;
public:
OpenGLOutputBuilder(unsigned int buffer_depth);
TextureBuilder texture_builder;
ArrayBuilder array_builder;
OpenGLOutputBuilder(size_t bytes_per_pixel);
~OpenGLOutputBuilder();
inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator)
{
_output_mutex->lock();
_colour_space = colour_space;
_colour_cycle_numerator = colour_cycle_numerator;
_colour_cycle_denominator = colour_cycle_denominator;
output_mutex_.lock();
colour_space_ = colour_space;
colour_cycle_numerator_ = colour_cycle_numerator;
colour_cycle_denominator_ = colour_cycle_denominator;
set_colour_space_uniforms();
_output_mutex->unlock();
output_mutex_.unlock();
}
inline void set_visible_area(Rect visible_area)
{
_visible_area = visible_area;
}
inline uint8_t *get_next_source_run()
{
if(_source_buffer_data_pointer == SourceVertexBufferDataSize) return nullptr;
return &_source_buffer_data.get()[_source_buffer_data_pointer];
}
inline void complete_source_run()
{
_source_buffer_data_pointer += SourceVertexSize;
}
inline bool composite_output_run_has_room_for_vertex()
{
return _output_buffer_data_pointer < OutputVertexBufferDataSize;
}
inline uint8_t *get_next_output_run()
{
if(_output_buffer_data_pointer == OutputVertexBufferDataSize) return nullptr;
return &_output_buffer_data.get()[_output_buffer_data_pointer];
}
inline void complete_output_run()
{
_output_buffer_data_pointer += OutputVertexSize;
visible_area_ = visible_area;
}
inline void lock_output()
{
_output_mutex->lock();
output_mutex_.lock();
}
inline void unlock_output()
{
_output_mutex->unlock();
output_mutex_.unlock();
}
inline OutputDevice get_output_device()
{
return _output_device;
return output_device_;
}
inline uint16_t get_composite_output_y()
{
return _composite_src_output_y % IntermediateBufferHeight;
return (uint16_t)composite_src_output_y_;
}
inline bool composite_output_buffer_is_full()
{
return _composite_src_output_y == _cleared_composite_output_y + IntermediateBufferHeight;
return composite_src_output_y_ == IntermediateBufferHeight;
}
inline void increment_composite_output_y()
{
if(!composite_output_buffer_is_full())
_composite_src_output_y++;
}
inline uint8_t *allocate_write_area(size_t required_length)
{
_buffer_builder->allocate_write_area(required_length);
return _buffer_builder->get_write_target();
}
inline void reduce_previous_allocation_to(size_t actual_length)
{
_buffer_builder->reduce_previous_allocation_to(actual_length);
}
inline bool input_buffer_is_full()
{
return _buffer_builder->is_full();
}
inline uint16_t get_last_write_x_posititon()
{
return _buffer_builder->get_last_write_x_position();
}
inline uint16_t get_last_write_y_posititon()
{
return _buffer_builder->get_last_write_y_position();
composite_src_output_y_++;
}
void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty);
@ -198,14 +148,6 @@ class OpenGLOutputBuilder {
void set_rgb_sampling_function(const char *shader);
void set_output_device(OutputDevice output_device);
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);
std::unique_ptr<uint8_t> _source_buffer_data;
GLsizei _source_buffer_data_pointer;
std::unique_ptr<uint8_t> _output_buffer_data;
GLsizei _output_buffer_data_pointer;
GLsync _fence;
};
}

View File

@ -31,13 +31,13 @@ struct Flywheel
@param sync_error_window The permitted deviation of sync timings from the norm.
*/
Flywheel(unsigned int standard_period, unsigned int retrace_time, unsigned int sync_error_window) :
_standard_period(standard_period),
_retrace_time(retrace_time),
_sync_error_window(sync_error_window),
_counter(0),
_expected_next_sync(standard_period),
_counter_before_retrace(standard_period - retrace_time),
_number_of_surprises(0) {}
standard_period_(standard_period),
retrace_time_(retrace_time),
sync_error_window_(sync_error_window),
counter_(0),
expected_next_sync_(standard_period),
counter_before_retrace_(standard_period - retrace_time),
number_of_surprises_(0) {}
enum SyncEvent {
/// Indicates that no synchronisation events will occur in the queried window.
@ -66,22 +66,22 @@ struct Flywheel
// do we recognise this hsync, thereby adjusting future time expectations?
if(sync_is_requested)
{
if(_counter < _sync_error_window || _counter > _expected_next_sync - _sync_error_window)
if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_)
{
unsigned int time_now = (_counter < _sync_error_window) ? _expected_next_sync + _counter : _counter;
_expected_next_sync = (3*_expected_next_sync + time_now) >> 2;
unsigned int time_now = (counter_ < sync_error_window_) ? expected_next_sync_ + counter_ : counter_;
expected_next_sync_ = (3*expected_next_sync_ + time_now) >> 2;
}
else
{
_number_of_surprises++;
number_of_surprises_++;
if(_counter < _retrace_time + (_expected_next_sync >> 1))
if(counter_ < retrace_time_ + (expected_next_sync_ >> 1))
{
_expected_next_sync = (3*_expected_next_sync + _standard_period + _sync_error_window) >> 2;
expected_next_sync_ = (3*expected_next_sync_ + standard_period_ + sync_error_window_) >> 2;
}
else
{
_expected_next_sync = (3*_expected_next_sync + _standard_period - _sync_error_window) >> 2;
expected_next_sync_ = (3*expected_next_sync_ + standard_period_ - sync_error_window_) >> 2;
}
}
}
@ -90,16 +90,16 @@ struct Flywheel
unsigned int proposed_sync_time = cycles_to_run_for;
// will we end an ongoing retrace?
if(_counter < _retrace_time && _counter + proposed_sync_time >= _retrace_time)
if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_)
{
proposed_sync_time = _retrace_time - _counter;
proposed_sync_time = retrace_time_ - counter_;
proposed_event = SyncEvent::EndRetrace;
}
// will we start a retrace?
if(_counter + proposed_sync_time >= _expected_next_sync)
if(counter_ + proposed_sync_time >= expected_next_sync_)
{
proposed_sync_time = _expected_next_sync - _counter;
proposed_sync_time = expected_next_sync_ - counter_;
proposed_event = SyncEvent::StartRetrace;
}
@ -117,14 +117,14 @@ struct Flywheel
*/
inline void apply_event(unsigned int cycles_advanced, SyncEvent event)
{
_counter += cycles_advanced;
counter_ += cycles_advanced;
switch(event)
{
default: return;
case StartRetrace:
_counter_before_retrace = _counter - _retrace_time;
_counter = 0;
counter_before_retrace_ = counter_ - retrace_time_;
counter_ = 0;
return;
}
}
@ -137,14 +137,14 @@ struct Flywheel
*/
inline unsigned int get_current_output_position()
{
if(_counter < _retrace_time)
if(counter_ < retrace_time_)
{
unsigned int retrace_distance = (_counter * _standard_period) / _retrace_time;
if(retrace_distance > _counter_before_retrace) return 0;
return _counter_before_retrace - retrace_distance;
unsigned int retrace_distance = (counter_ * standard_period_) / retrace_time_;
if(retrace_distance > counter_before_retrace_) return 0;
return counter_before_retrace_ - retrace_distance;
}
return _counter - _retrace_time;
return counter_ - retrace_time_;
}
/*!
@ -152,7 +152,7 @@ struct Flywheel
*/
inline unsigned int get_current_time()
{
return _counter;
return counter_;
}
/*!
@ -160,7 +160,7 @@ struct Flywheel
*/
inline bool is_in_retrace()
{
return _counter < _retrace_time;
return counter_ < retrace_time_;
}
/*!
@ -168,7 +168,7 @@ struct Flywheel
*/
inline unsigned int get_scan_period()
{
return _standard_period - _retrace_time;
return standard_period_ - retrace_time_;
}
/*!
@ -176,7 +176,7 @@ struct Flywheel
*/
inline unsigned int get_standard_period()
{
return _standard_period;
return standard_period_;
}
/*!
@ -185,8 +185,8 @@ struct Flywheel
*/
inline unsigned int get_and_reset_number_of_surprises()
{
unsigned int result = _number_of_surprises;
_number_of_surprises = 0;
unsigned int result = number_of_surprises_;
number_of_surprises_ = 0;
return result;
}
@ -195,19 +195,19 @@ struct Flywheel
*/
inline bool is_near_expected_sync()
{
return abs((int)_counter - (int)_expected_next_sync) < (int)_standard_period / 50;
return abs((int)counter_ - (int)expected_next_sync_) < (int)standard_period_ / 50;
}
private:
unsigned int _standard_period; // the normal length of time between syncs
const unsigned int _retrace_time; // a constant indicating the amount of time it takes to perform a retrace
const unsigned int _sync_error_window; // a constant indicating the window either side of the next expected sync in which we'll accept other syncs
unsigned int standard_period_; // the normal length of time between syncs
const unsigned int retrace_time_; // a constant indicating the amount of time it takes to perform a retrace
const unsigned int sync_error_window_; // a constant indicating the window either side of the next expected sync in which we'll accept other syncs
unsigned int _counter; // time since the _start_ of the last sync
unsigned int _counter_before_retrace; // the value of _counter immediately before retrace began
unsigned int _expected_next_sync; // our current expection of when the next sync will be encountered (which implies velocity)
unsigned int counter_; // time since the _start_ of the last sync
unsigned int counter_before_retrace_; // the value of _counter immediately before retrace began
unsigned int expected_next_sync_; // our current expection of when the next sync will be encountered (which implies velocity)
unsigned int _number_of_surprises; // a count of the surprising syncs
unsigned int number_of_surprises_; // a count of the surprising syncs
/*
Implementation notes:

View File

@ -46,34 +46,34 @@ GLuint Shader::compile_shader(const char *source, GLenum type)
Shader::Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings)
{
_shader_program = glCreateProgram();
shader_program_ = glCreateProgram();
GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER);
GLuint fragment = compile_shader(fragment_shader, GL_FRAGMENT_SHADER);
glAttachShader(_shader_program, vertex);
glAttachShader(_shader_program, fragment);
glAttachShader(shader_program_, vertex);
glAttachShader(shader_program_, fragment);
if(attribute_bindings)
{
while(attribute_bindings->name)
{
glBindAttribLocation(_shader_program, attribute_bindings->index, attribute_bindings->name);
glBindAttribLocation(shader_program_, attribute_bindings->index, attribute_bindings->name);
attribute_bindings++;
}
}
glLinkProgram(_shader_program);
glLinkProgram(shader_program_);
#if defined(DEBUG)
GLint didLink = 0;
glGetProgramiv(_shader_program, GL_LINK_STATUS, &didLink);
glGetProgramiv(shader_program_, GL_LINK_STATUS, &didLink);
if(didLink == GL_FALSE)
{
GLint logLength;
glGetProgramiv(_shader_program, GL_INFO_LOG_LENGTH, &logLength);
glGetProgramiv(shader_program_, GL_INFO_LOG_LENGTH, &logLength);
if(logLength > 0) {
GLchar *log = (GLchar *)malloc((size_t)logLength);
glGetProgramInfoLog(_shader_program, logLength, &logLength, log);
glGetProgramInfoLog(shader_program_, logLength, &logLength, log);
printf("Link log:\n%s\n", log);
free(log);
}
@ -85,14 +85,14 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att
Shader::~Shader()
{
if(bound_shader == this) Shader::unbind();
glDeleteProgram(_shader_program);
glDeleteProgram(shader_program_);
}
void Shader::bind()
{
if(bound_shader != this)
{
glUseProgram(_shader_program);
glUseProgram(shader_program_);
bound_shader = this;
}
flush_functions();
@ -106,12 +106,12 @@ void Shader::unbind()
GLint Shader::get_attrib_location(const GLchar *name)
{
return glGetAttribLocation(_shader_program, name);
return glGetAttribLocation(shader_program_, name);
}
GLint Shader::get_uniform_location(const GLchar *name)
{
return glGetUniformLocation(_shader_program, name);
return glGetUniformLocation(shader_program_, name);
}
void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor)
@ -123,7 +123,7 @@ void Shader::enable_vertex_attribute_with_pointer(const char *name, GLint size,
}
// The various set_uniforms...
#define location() glGetUniformLocation(_shader_program, name.c_str())
#define location() glGetUniformLocation(shader_program_, name.c_str())
void Shader::set_uniform(const std::string &name, GLint value)
{
enqueue_function([name, value, this] {
@ -291,18 +291,18 @@ void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei cou
void Shader::enqueue_function(std::function<void(void)> function)
{
_function_mutex.lock();
_enqueued_functions.push_back(function);
_function_mutex.unlock();
function_mutex_.lock();
enqueued_functions_.push_back(function);
function_mutex_.unlock();
}
void Shader::flush_functions()
{
_function_mutex.lock();
for(std::function<void(void)> function : _enqueued_functions)
function_mutex_.lock();
for(std::function<void(void)> function : enqueued_functions_)
{
function();
}
_enqueued_functions.clear();
_function_mutex.unlock();
enqueued_functions_.clear();
function_mutex_.unlock();
}

View File

@ -12,7 +12,7 @@
#include "OpenGL.hpp"
#include <string>
#include <functional>
#include <list>
#include <vector>
#include <mutex>
namespace OpenGL {
@ -31,8 +31,8 @@ public:
};
struct AttributeBinding {
const GLchar *name;
GLuint index;
const GLchar *const name;
const GLuint index;
};
/*!
@ -107,11 +107,11 @@ public:
private:
GLuint compile_shader(const char *source, GLenum type);
GLuint _shader_program;
GLuint shader_program_;
void flush_functions();
std::list<std::function<void(void)>> _enqueued_functions;
std::mutex _function_mutex;
std::vector<std::function<void(void)>> enqueued_functions_;
std::mutex function_mutex_;
protected:
void enqueue_function(std::function<void(void)> function);

View File

@ -0,0 +1,143 @@
//
// TextureBuilder.cpp
// Clock Signal
//
// Created by Thomas Harte on 08/03/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "TextureBuilder.hpp"
#include "CRTOpenGL.hpp"
#include "OpenGL.hpp"
#include <string.h>
using namespace Outputs::CRT;
static const GLint internalFormatForDepth(size_t depth)
{
switch(depth)
{
default: return GL_FALSE;
case 1: return GL_R8UI;
case 2: return GL_RG8UI;
case 3: return GL_RGB8UI;
case 4: return GL_RGBA8UI;
}
}
static const GLenum formatForDepth(size_t depth)
{
switch(depth)
{
default: return GL_FALSE;
case 1: return GL_RED_INTEGER;
case 2: return GL_RG_INTEGER;
case 3: return GL_RGB_INTEGER;
case 4: return GL_RGBA_INTEGER;
}
}
TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) :
bytes_per_pixel_(bytes_per_pixel),
next_write_x_position_(0),
next_write_y_position_(0)
{
image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight);
glGenTextures(1, &texture_name_);
glActiveTexture(texture_unit);
glBindTexture(GL_TEXTURE_2D, texture_name_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
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, internalFormatForDepth(bytes_per_pixel), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(bytes_per_pixel), GL_UNSIGNED_BYTE, nullptr);
}
TextureBuilder::~TextureBuilder()
{
glDeleteTextures(1, &texture_name_);
}
uint8_t *TextureBuilder::allocate_write_area(size_t required_length)
{
if(next_write_y_position_ != InputBufferBuilderHeight)
{
last_allocation_amount_ = required_length;
if(next_write_x_position_ + required_length + 2 > InputBufferBuilderWidth)
{
next_write_x_position_ = 0;
next_write_y_position_++;
if(next_write_y_position_ == InputBufferBuilderHeight)
return nullptr;
}
write_x_position_ = next_write_x_position_ + 1;
write_y_position_ = next_write_y_position_;
write_target_pointer_ = (write_y_position_ * InputBufferBuilderWidth) + write_x_position_;
next_write_x_position_ += required_length + 2;
}
else return nullptr;
return &image_[write_target_pointer_ * bytes_per_pixel_];
}
bool TextureBuilder::is_full()
{
return (next_write_y_position_ == InputBufferBuilderHeight);
}
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length)
{
if(next_write_y_position_ == InputBufferBuilderHeight) return;
uint8_t *const image_pointer = image_.data();
// correct if the writing cursor was reset while a client was writing
if(next_write_x_position_ == 0 && next_write_y_position_ == 0)
{
memmove(&image_pointer[bytes_per_pixel_], &image_pointer[write_target_pointer_ * bytes_per_pixel_], actual_length * bytes_per_pixel_);
write_target_pointer_ = 1;
last_allocation_amount_ = actual_length;
next_write_x_position_ = (uint16_t)(actual_length + 2);
write_x_position_ = 1;
write_y_position_ = 0;
}
// book end the allocation with duplicates of the first and last pixel, to protect
// against rounding errors when this run is drawn
memcpy( &image_pointer[(write_target_pointer_ - 1) * bytes_per_pixel_],
&image_pointer[write_target_pointer_ * bytes_per_pixel_],
bytes_per_pixel_);
memcpy( &image_pointer[(write_target_pointer_ + actual_length) * bytes_per_pixel_],
&image_pointer[(write_target_pointer_ + actual_length - 1) * bytes_per_pixel_],
bytes_per_pixel_);
// return any allocated length that wasn't actually used to the available pool
next_write_x_position_ -= (last_allocation_amount_ - actual_length);
}
uint16_t TextureBuilder::get_last_write_x_position()
{
return write_x_position_;
}
uint16_t TextureBuilder::get_last_write_y_position()
{
return write_y_position_;
}
void TextureBuilder::submit()
{
uint16_t height = write_y_position_ + (next_write_x_position_ ? 1 : 0);
next_write_x_position_ = next_write_y_position_ = 0;
glTexSubImage2D( GL_TEXTURE_2D, 0,
0, 0,
InputBufferBuilderWidth, height,
formatForDepth(bytes_per_pixel_), GL_UNSIGNED_BYTE,
image_.data());
}

View File

@ -0,0 +1,78 @@
//
// TextureBuilder.hpp
// Clock Signal
//
// Created by Thomas Harte on 08/03/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Outputs_CRT_Internals_TextureBuilder_hpp
#define Outputs_CRT_Internals_TextureBuilder_hpp
#include <cstdint>
#include <memory>
#include <vector>
#include "OpenGL.hpp"
#include "CRTConstants.hpp"
namespace Outputs {
namespace CRT {
/*!
Owns an OpenGL texture resource and provides mechanisms to fill it from bottom left to top right
with runs of data, ensuring each run is neighboured immediately to the left and right by copies of its
first and last pixels.
*/
class TextureBuilder {
public:
/// Constructs an instance of InputTextureBuilder that contains a texture of colour depth @c bytes_per_pixel;
/// this creates a new texture and binds it to the current active texture unit.
TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit);
virtual ~TextureBuilder();
/// Finds the first available space of at least @c required_length pixels in size. Calls must be paired off
/// with calls to @c reduce_previous_allocation_to.
/// @returns a pointer to the allocated space if any was available; @c nullptr otherwise.
uint8_t *allocate_write_area(size_t required_length);
/// Announces that the owner is finished with the region created by the most recent @c allocate_write_area
/// and indicates that its actual final size was @c actual_length.
void reduce_previous_allocation_to(size_t actual_length);
/// @returns the start column for the most recent allocated write area.
uint16_t get_last_write_x_position();
/// @returns the row of the most recent allocated write area.
uint16_t get_last_write_y_position();
/// @returns @c true if all future calls to @c allocate_write_area will fail on account of the input texture
/// being full; @c false if calls may succeed.
bool is_full();
/// Updates the currently-bound texture with all new data provided since the last @c submit.
void submit();
private:
// where pixel data will be put to the next time a write is requested
uint16_t next_write_x_position_, next_write_y_position_;
// the most recent position returned for pixel data writing
uint16_t write_x_position_, write_y_position_;
// details of the most recent allocation
size_t write_target_pointer_;
size_t last_allocation_amount_;
// the buffer size
size_t bytes_per_pixel_;
// the buffer
std::vector<uint8_t> image_;
GLuint texture_name_;
};
}
}
#endif /* Outputs_CRT_Internals_TextureBuilder_hpp */