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:
commit
4ec042fad1
@ -485,6 +485,7 @@ void Machine::synchronise()
|
||||
{
|
||||
update_display();
|
||||
update_audio();
|
||||
_speaker->flush();
|
||||
}
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
|
@ -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 */,
|
||||
|
13
OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.h
Normal file
13
OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.h
Normal 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
|
128
OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm
Normal file
128
OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm
Normal 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
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
187
Outputs/CRT/Internals/ArrayBuilder.cpp
Normal file
187
Outputs/CRT/Internals/ArrayBuilder.cpp
Normal 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;
|
||||
}
|
111
Outputs/CRT/Internals/ArrayBuilder.hpp
Normal file
111
Outputs/CRT/Internals/ArrayBuilder.hpp
Normal 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 */
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
@ -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 */
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
143
Outputs/CRT/Internals/TextureBuilder.cpp
Normal file
143
Outputs/CRT/Internals/TextureBuilder.cpp
Normal 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());
|
||||
}
|
78
Outputs/CRT/Internals/TextureBuilder.hpp
Normal file
78
Outputs/CRT/Internals/TextureBuilder.hpp
Normal 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 */
|
Loading…
Reference in New Issue
Block a user