From aa0714fe27335410131047b3ce4eaf661da581e1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 3 Jan 2016 20:41:43 -0500 Subject: [PATCH 001/307] Started sketching out an Acorn Electron emulation, as it's a platform I'm familiar with and will force me to figure out PAL decoding. Factored out NTSC-specific parts of the display decoding logic and hence added RGB output mode. --- .../Clock Signal.xcodeproj/project.pbxproj | 16 ++ OSBindings/Mac/Clock Signal/CSAtari2600.mm | 2 +- .../Mac/Clock Signal/CSCathodeRayView.h | 7 +- .../Mac/Clock Signal/CSCathodeRayView.m | 205 +++++++++++------- .../Mac/Clock Signal/ElectronDocument.swift | 46 ++++ OSBindings/Mac/Clock Signal/Info.plist | 14 ++ 6 files changed, 211 insertions(+), 79 deletions(-) create mode 100644 OSBindings/Mac/Clock Signal/ElectronDocument.swift diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 90690fa4f..915068d5c 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; }; 4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; }; 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; }; + 4B2E2D921C399B9900138695 /* ElectronDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D911C399B9900138695 /* ElectronDocument.swift */; }; + 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; 4B366DFC1B5C165A0026627B /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B366DFA1B5C165A0026627B /* CRT.cpp */; }; 4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; @@ -324,6 +326,8 @@ 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = ""; }; 4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = ""; }; 4B2632551B631A510082A461 /* CRTFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CRTFrame.h; path = ../../Outputs/CRTFrame.h; sourceTree = ""; }; + 4B2E2D911C399B9900138695 /* ElectronDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronDocument.swift; sourceTree = ""; }; + 4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = ""; }; 4B366DFA1B5C165A0026627B /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRT.cpp; path = ../../Outputs/CRT.cpp; sourceTree = ""; }; 4B366DFB1B5C165A0026627B /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRT.hpp; path = ../../Outputs/CRT.hpp; sourceTree = ""; }; 4B6D7F921B58822000787C9A /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; @@ -970,6 +974,7 @@ 4BB73EA01B587A5100552FC2 /* Clock Signal */ = { isa = PBXGroup; children = ( + 4B2E2D931C399D1200138695 /* ElectronDocument.xib */, 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, 4B14144A1B5883E500E04248 /* CSAtari2600.h */, 4B14144B1B5883E500E04248 /* CSAtari2600.mm */, @@ -982,6 +987,7 @@ 4BB73EA81B587A5100552FC2 /* Assets.xcassets */, 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */, 4BB73EAD1B587A5100552FC2 /* Info.plist */, + 4B2E2D911C399B9900138695 /* ElectronDocument.swift */, ); path = "Clock Signal"; sourceTree = ""; @@ -1140,6 +1146,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */, 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, 4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */, 4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, @@ -1438,6 +1445,7 @@ 4B366DFC1B5C165A0026627B /* CRT.cpp in Sources */, 4BB73EA41B587A5100552FC2 /* Atari2600Document.swift in Sources */, 4B14144E1B5883E500E04248 /* CSAtari2600.mm in Sources */, + 4B2E2D921C399B9900138695 /* ElectronDocument.swift in Sources */, 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */, 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */, 4B14144F1B5883E500E04248 /* CSCathodeRayView.m in Sources */, @@ -1482,6 +1490,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 4B2E2D931C399D1200138695 /* ElectronDocument.xib */ = { + isa = PBXVariantGroup; + children = ( + 4B2E2D941C399D1200138695 /* Base */, + ); + name = ElectronDocument.xib; + sourceTree = ""; + }; 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */ = { isa = PBXVariantGroup; children = ( diff --git a/OSBindings/Mac/Clock Signal/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/CSAtari2600.mm index a2b9a52f5..07b3ac646 100644 --- a/OSBindings/Mac/Clock Signal/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/CSAtari2600.mm @@ -95,7 +95,7 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { - (void)setView:(CSCathodeRayView *)view { _view = view; - _view.signalDecoder = [NSString stringWithUTF8String:_atari2600.get_signal_decoder()]; + [_view setSignalDecoder:[NSString stringWithUTF8String:_atari2600.get_signal_decoder()] type:CSCathodeRayViewSignalTypeNTSC]; } - (instancetype)init { diff --git a/OSBindings/Mac/Clock Signal/CSCathodeRayView.h b/OSBindings/Mac/Clock Signal/CSCathodeRayView.h index ae2b8a80a..d07416c8e 100644 --- a/OSBindings/Mac/Clock Signal/CSCathodeRayView.h +++ b/OSBindings/Mac/Clock Signal/CSCathodeRayView.h @@ -22,6 +22,11 @@ - (void)flagsChanged:(nonnull NSEvent *)newModifiers; @end +typedef NS_ENUM(NSInteger, CSCathodeRayViewSignalType) { + CSCathodeRayViewSignalTypeNTSC, + CSCathodeRayViewSignalTypeRGB +}; + @interface CSCathodeRayView : NSOpenGLView @property (nonatomic, weak) id delegate; @@ -30,6 +35,6 @@ - (void)invalidate; - (BOOL)pushFrame:(nonnull CRTFrame *)crtFrame; -- (void)setSignalDecoder:(nonnull NSString *)signalDecoder; +- (void)setSignalDecoder:(nonnull NSString *)decoder type:(CSCathodeRayViewSignalType)type; @end diff --git a/OSBindings/Mac/Clock Signal/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/CSCathodeRayView.m index d9e8557f1..d9d4870f1 100644 --- a/OSBindings/Mac/Clock Signal/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/CSCathodeRayView.m @@ -33,6 +33,7 @@ CRTFrame *_crtFrame; NSString *_signalDecoder; + CSCathodeRayViewSignalType _signalType; int32_t _signalDecoderGeneration; int32_t _compiledSignalDecoderGeneration; } @@ -201,80 +202,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt #pragma mark - Frame output -// the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the -// top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates -// from integral to floating point. -static NSString *const vertexShader = - @"#version 150\n" - "\n" - "in vec2 position;\n" - "in vec2 srcCoordinates;\n" - "in float lateral;\n" - "\n" - "out vec2 srcCoordinatesVarying[4];\n" - "out float lateralVarying;\n" - "out float phase;\n" - "out vec2 shadowMaskCoordinates;\n" - "\n" - "uniform vec2 textureSize;\n" - "\n" - "const float shadowMaskMultiple = 300;\n" - "\n" - "void main (void)\n" - "{\n" - "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" - "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n" - "\n" - "lateralVarying = lateral + 1.0707963267949;\n" - "phase = srcCoordinates.x * 6.283185308;\n" - "\n" - "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);\n" - "\n" - "gl_Position = vec4(position.x * 2.0 - 1.0, 1.0 - position.y * 2.0 + position.x / 131.0, 0.0, 1.0);\n" - "}\n"; - -// TODO: this should be factored out and be per project - -static NSString *const fragmentShader = - @"#version 150\n" - "\n" - "in vec2 srcCoordinatesVarying[4];\n" - "in float lateralVarying;\n" - "in float phase;\n" - "in vec2 shadowMaskCoordinates;\n" - "out vec4 fragColour;\n" - "\n" - "uniform sampler2D texID;\n" - "uniform sampler2D shadowMaskTexID;\n" - "uniform float alpha;\n" - "\n" - "%@" - "\n" - "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" - "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n" - "\n" - "void main(void)\n" - "{\n" - "vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" - "vec4 samples = vec4(" - " sample(srcCoordinatesVarying[0], angles.x)," - " sample(srcCoordinatesVarying[1], angles.y)," - " sample(srcCoordinatesVarying[2], angles.z)," - " sample(srcCoordinatesVarying[3], angles.w)" - ");\n" - "\n" - "float y = dot(vec4(0.25), samples);\n" - "samples -= vec4(y);\n" - "\n" - "float i = dot(cos(angles), samples);\n" - "float q = dot(sin(angles), samples);\n" - "\n" - "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n" - "}\n"; - #if defined(DEBUG) - (void)logErrorForObject:(GLuint)object { @@ -302,12 +229,130 @@ static NSString *const fragmentShader = return shader; } -- (void)setSignalDecoder:(nonnull NSString *)signalDecoder +- (void)setSignalDecoder:(nonnull NSString *)signalDecoder type:(CSCathodeRayViewSignalType)type { _signalDecoder = [signalDecoder copy]; OSAtomicIncrement32(&_signalDecoderGeneration); } +- (nonnull NSString *)vertexShaderForType:(CSCathodeRayViewSignalType)type +{ + // the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the + // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates + // from integral to floating point; there's also some setup for NTSC, PAL or whatever. + + NSString *const ntscVertexShaderGlobals = + @"out vec2 srcCoordinatesVarying[4];\n" + "out float phase;\n"; + + NSString *const ntscVertexShaderBody = + @"phase = srcCoordinates.x * 6.283185308;\n" + "\n" + "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" + "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; + + NSString *const rgbVertexShaderGlobals = + @"out vec2 srcCoordinatesVarying;\n"; + + NSString *const rgbVertexShaderBody = + @"srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n"; + + NSString *const vertexShader = + @"#version 150\n" + "\n" + "in vec2 position;\n" + "in vec2 srcCoordinates;\n" + "in float lateral;\n" + "\n" + "out float lateralVarying;\n" + "out vec2 shadowMaskCoordinates;\n" + "\n" + "uniform vec2 textureSize;\n" + "\n" + "const float shadowMaskMultiple = 600;\n" + "\n" + "%@\n" + "void main (void)\n" + "{\n" + "lateralVarying = lateral + 1.0707963267949;\n" + "\n" + "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);\n" + "\n" + "%@\n" + "\n" + "gl_Position = vec4(position.x * 2.0 - 1.0, 1.0 - position.y * 2.0 + position.x / 131.0, 0.0, 1.0);\n" + "}\n"; + + switch(_signalType) + { + case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:vertexShader, ntscVertexShaderGlobals, ntscVertexShaderBody]; + case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:vertexShader, rgbVertexShaderGlobals, rgbVertexShaderBody]; + } +} + +- (nonnull NSString *)fragmentShaderForType:(CSCathodeRayViewSignalType)type +{ + + NSString *const fragmentShader = + @"#version 150\n" + "\n" + "in float lateralVarying;\n" + "in vec2 shadowMaskCoordinates;\n" + "out vec4 fragColour;\n" + "\n" + "uniform sampler2D texID;\n" + "uniform sampler2D shadowMaskTexID;\n" + "uniform float alpha;\n" + "\n" + "%@\n" + "\n" + "void main(void)\n" + "{\n" + "%@\n" + "}\n"; + + NSString *const ntscFragmentShaderGlobals = + @"in vec2 srcCoordinatesVarying[4];\n" + "in float phase;\n" + "\n" + "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" + "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n" + "\n" + "\%@\n"; + + NSString *const ntscFragmentShaderBody = + @"vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" + "vec4 samples = vec4(" + " sample(srcCoordinatesVarying[0], angles.x)," + " sample(srcCoordinatesVarying[1], angles.y)," + " sample(srcCoordinatesVarying[2], angles.z)," + " sample(srcCoordinatesVarying[3], angles.w)" + ");\n" + "\n" + "float y = dot(vec4(0.25), samples);\n" + "samples -= vec4(y);\n" + "\n" + "float i = dot(cos(angles), samples);\n" + "float q = dot(sin(angles), samples);\n" + "\n" + "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; + + NSString *const rgbFragmentShaderGlobals = + @""; + + NSString *const rgbFragmentShaderBody = + @"fragColour = texture(srcCoordinatesVarying, srcCoordinatesVarying);//sin(lateralVarying));\n"; + + switch(_signalType) + { + case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:fragmentShader, ntscFragmentShaderGlobals, ntscFragmentShaderBody]; + case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:fragmentShader, rgbFragmentShaderGlobals, rgbFragmentShaderBody]; + } +} + - (void)prepareShader { if(_shaderProgram) @@ -320,9 +365,15 @@ static NSString *const fragmentShader = if(!_signalDecoder) return; + NSString *const vertexShader = [self vertexShaderForType:_signalType]; + NSString *const fragmentShader = [self fragmentShaderForType:_signalType]; + _shaderProgram = glCreateProgram(); _vertexShader = [self compileShader:[vertexShader UTF8String] type:GL_VERTEX_SHADER]; - _fragmentShader = [self compileShader:[[NSString stringWithFormat:fragmentShader, _signalDecoder] UTF8String] type:GL_FRAGMENT_SHADER]; + _fragmentShader = [self compileShader:_signalDecoder ? + [[NSString stringWithFormat:fragmentShader, _signalDecoder] UTF8String] : + [fragmentShader UTF8String] + type:GL_FRAGMENT_SHADER]; glAttachShader(_shaderProgram, _vertexShader); glAttachShader(_shaderProgram, _fragmentShader); @@ -379,7 +430,7 @@ static NSString *const fragmentShader = [self.openGLContext makeCurrentContext]; CGLLockContext([[self openGLContext] CGLContextObj]); - while(!_shaderProgram || (_signalDecoderGeneration != _compiledSignalDecoderGeneration)) { + while((!_shaderProgram || (_signalDecoderGeneration != _compiledSignalDecoderGeneration)) && _signalDecoder) { _compiledSignalDecoderGeneration = _signalDecoderGeneration; [self prepareShader]; } diff --git a/OSBindings/Mac/Clock Signal/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/ElectronDocument.swift new file mode 100644 index 000000000..0fbd05563 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/ElectronDocument.swift @@ -0,0 +1,46 @@ +// +// ElectronDocument.swift +// Clock Signal +// +// Created by Thomas Harte on 03/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +import Foundation + +class ElectronDocument: NSDocument, CSCathodeRayViewResponderDelegate, CSCathodeRayViewDelegate { + + @IBOutlet weak var openGLView: CSCathodeRayView! + override func windowControllerDidLoadNib(aController: NSWindowController) { + super.windowControllerDidLoadNib(aController) + + openGLView.delegate = self + openGLView.responderDelegate = self +// atari2600!.view = openGLView! + + // bind the content aspect ratio to remain 4:3 from now on + aController.window!.contentAspectRatio = NSSize(width: 4.0, height: 3.0) + } + + override var windowNibName: String? { + return "ElectronDocument" + } + + override func readFromData(data: NSData, ofType typeName: String) throws { + } + + override func close() { + super.close() + openGLView.invalidate() + } + + // MARK: CSOpenGLViewDelegate + func openGLView(view: CSCathodeRayView, didUpdateToTime time: CVTimeStamp) { + } + + // MARK: CSOpenGLViewResponderDelegate + func keyDown(event: NSEvent) {} + func keyUp(event: NSEvent) {} + func flagsChanged(newModifiers: NSEvent) {} + +} diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index bf2f637cb..65aac910a 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -27,6 +27,20 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).Atari2600Document + + CFBundleTypeExtensions + + uef + + CFBundleTypeName + Electron/BBC Tape Image + CFBundleTypeRole + Editor + LSTypeIsPackage + 0 + NSDocumentClass + $(PRODUCT_MODULE_NAME).ElectronDocument + CFBundleExecutable $(EXECUTABLE_NAME) From 22fa02454614a97ee0ad35ebc2c4ff6ae37ec6b1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 3 Jan 2016 20:46:39 -0500 Subject: [PATCH 002/307] Added document XIB. --- .../Base.lproj/ElectronDocument.xib | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib new file mode 100644 index 000000000..a98c860c9 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ab45c1d530649e89a9b1c4709617f1c4c24367c0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Jan 2016 23:12:47 -0500 Subject: [PATCH 003/307] Started working on a shell for the Electron emulation, including factoring out the common CRT delegate -> Objective-C bridging, serial dispatch queue and frameskipping logic from the Atari 2600 shell. --- Machines/{ => Atari2600}/Atari2600.cpp | 0 Machines/{ => Atari2600}/Atari2600.hpp | 4 +- Machines/{ => Atari2600}/Atari2600Inputs.h | 0 Machines/Electron/Electron.cpp | 24 ++++ Machines/Electron/Electron.hpp | 32 +++++ .../Clock Signal.xcodeproj/project.pbxproj | 66 +++++++--- .../Mac/Clock Signal/Atari2600Document.swift | 18 +-- OSBindings/Mac/Clock Signal/CSAtari2600.mm | 114 ------------------ .../Mac/Clock Signal/ElectronDocument.swift | 5 + .../Clock Signal/{ => Wrappers}/CSAtari2600.h | 7 +- .../Mac/Clock Signal/Wrappers/CSAtari2600.mm | 77 ++++++++++++ .../Wrappers/CSMachine+Subclassing.h | 20 +++ .../Mac/Clock Signal/Wrappers/CSMachine.h | 18 +++ .../Mac/Clock Signal/Wrappers/CSMachine.mm | 63 ++++++++++ 14 files changed, 304 insertions(+), 144 deletions(-) rename Machines/{ => Atari2600}/Atari2600.cpp (100%) rename Machines/{ => Atari2600}/Atari2600.hpp (96%) rename Machines/{ => Atari2600}/Atari2600Inputs.h (100%) create mode 100644 Machines/Electron/Electron.cpp create mode 100644 Machines/Electron/Electron.hpp delete mode 100644 OSBindings/Mac/Clock Signal/CSAtari2600.mm rename OSBindings/Mac/Clock Signal/{ => Wrappers}/CSAtari2600.h (75%) create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm diff --git a/Machines/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp similarity index 100% rename from Machines/Atari2600.cpp rename to Machines/Atari2600/Atari2600.cpp diff --git a/Machines/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp similarity index 96% rename from Machines/Atari2600.hpp rename to Machines/Atari2600/Atari2600.hpp index 0e53f801b..7b65b9db2 100644 --- a/Machines/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -9,8 +9,8 @@ #ifndef Atari2600_cpp #define Atari2600_cpp -#include "../Processors/6502/CPU6502.hpp" -#include "../Outputs/CRT.hpp" +#include "../../Processors/6502/CPU6502.hpp" +#include "../../Outputs/CRT.hpp" #include #include "Atari2600Inputs.h" diff --git a/Machines/Atari2600Inputs.h b/Machines/Atari2600/Atari2600Inputs.h similarity index 100% rename from Machines/Atari2600Inputs.h rename to Machines/Atari2600/Atari2600Inputs.h diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp new file mode 100644 index 000000000..808bff52b --- /dev/null +++ b/Machines/Electron/Electron.cpp @@ -0,0 +1,24 @@ +// +// Electron.cpp +// Clock Signal +// +// Created by Thomas Harte on 03/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Electron.hpp" + +using namespace Electron; + +Machine::Machine() +{ +} + +Machine::~Machine() +{ +} + +unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) +{ + return 1; +} diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp new file mode 100644 index 000000000..23f5930c7 --- /dev/null +++ b/Machines/Electron/Electron.hpp @@ -0,0 +1,32 @@ +// +// Electron.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Electron_hpp +#define Electron_hpp + +#include "../../Processors/6502/CPU6502.hpp" +#include "../../Outputs/CRT.hpp" +#include +#include "Atari2600Inputs.h" + +namespace Electron { + +class Machine: public CPU6502::Processor { + + public: + + Machine(); + ~Machine(); + + unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); + +}; + +} + +#endif /* Electron_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 915068d5c..eb1964c7d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -7,9 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 4B14144E1B5883E500E04248 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14144B1B5883E500E04248 /* CSAtari2600.mm */; }; 4B14144F1B5883E500E04248 /* CSCathodeRayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B14144D1B5883E500E04248 /* CSCathodeRayView.m */; }; - 4B1414511B5885DF00E04248 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6D7F921B58822000787C9A /* Atari2600.cpp */; }; 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; }; @@ -17,7 +15,11 @@ 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; }; 4B2E2D921C399B9900138695 /* ElectronDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D911C399B9900138695 /* ElectronDocument.swift */; }; 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; + 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; + 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; 4B366DFC1B5C165A0026627B /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B366DFA1B5C165A0026627B /* CRT.cpp */; }; + 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE4A1C3B3B0C0093A61B /* CSAtari2600.mm */; }; + 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */; }; 4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; }; @@ -314,8 +316,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 4B14144A1B5883E500E04248 /* CSAtari2600.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAtari2600.h; sourceTree = ""; }; - 4B14144B1B5883E500E04248 /* CSAtari2600.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAtari2600.mm; sourceTree = ""; }; 4B14144C1B5883E500E04248 /* CSCathodeRayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSCathodeRayView.h; sourceTree = ""; }; 4B14144D1B5883E500E04248 /* CSCathodeRayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSCathodeRayView.m; sourceTree = ""; }; 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = ""; }; @@ -328,12 +328,19 @@ 4B2632551B631A510082A461 /* CRTFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CRTFrame.h; path = ../../Outputs/CRTFrame.h; sourceTree = ""; }; 4B2E2D911C399B9900138695 /* ElectronDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronDocument.swift; sourceTree = ""; }; 4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = ""; }; + 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; + 4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = ""; }; + 4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = ""; }; + 4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = ""; }; + 4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = ""; }; 4B366DFA1B5C165A0026627B /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRT.cpp; path = ../../Outputs/CRT.cpp; sourceTree = ""; }; 4B366DFB1B5C165A0026627B /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRT.hpp; path = ../../Outputs/CRT.hpp; sourceTree = ""; }; - 4B6D7F921B58822000787C9A /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; - 4B6D7F931B58822000787C9A /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = ""; }; + 4B55CE491C3B3B0C0093A61B /* CSAtari2600.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAtari2600.h; sourceTree = ""; }; + 4B55CE4A1C3B3B0C0093A61B /* CSAtari2600.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAtari2600.mm; sourceTree = ""; }; + 4B55CE4C1C3B3BDA0093A61B /* CSMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSMachine.h; sourceTree = ""; }; + 4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSMachine.mm; sourceTree = ""; }; + 4B55CE4F1C3B78A80093A61B /* CSMachine+Subclassing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Subclassing.h"; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = ""; }; - 4BA3921C1B8402B3007FBF0E /* Atari2600Inputs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = ""; }; 4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = ""; }; 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = 6502_functional_test.bin; sourceTree = ""; }; 4BB297E11B587D8300A49093 /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = AllSuiteA.bin; sourceTree = ""; }; @@ -666,6 +673,25 @@ name = "Test Binaries"; sourceTree = ""; }; + 4B2E2D961C3A06EC00138695 /* Atari2600 */ = { + isa = PBXGroup; + children = ( + 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */, + 4B2E2D981C3A06EC00138695 /* Atari2600.hpp */, + 4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */, + ); + path = Atari2600; + sourceTree = ""; + }; + 4B2E2D9E1C3A070900138695 /* Electron */ = { + isa = PBXGroup; + children = ( + 4B2E2D9B1C3A070400138695 /* Electron.cpp */, + 4B2E2D9C1C3A070400138695 /* Electron.hpp */, + ); + name = Electron; + sourceTree = ""; + }; 4B366DFD1B5C165F0026627B /* Outputs */ = { isa = PBXGroup; children = ( @@ -676,6 +702,18 @@ name = Outputs; sourceTree = ""; }; + 4B55CE481C3B3B0C0093A61B /* Wrappers */ = { + isa = PBXGroup; + children = ( + 4B55CE491C3B3B0C0093A61B /* CSAtari2600.h */, + 4B55CE4A1C3B3B0C0093A61B /* CSAtari2600.mm */, + 4B55CE4C1C3B3BDA0093A61B /* CSMachine.h */, + 4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */, + 4B55CE4F1C3B78A80093A61B /* CSMachine+Subclassing.h */, + ); + path = Wrappers; + sourceTree = ""; + }; 4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = { isa = PBXGroup; children = ( @@ -974,10 +1012,9 @@ 4BB73EA01B587A5100552FC2 /* Clock Signal */ = { isa = PBXGroup; children = ( + 4B55CE481C3B3B0C0093A61B /* Wrappers */, 4B2E2D931C399D1200138695 /* ElectronDocument.xib */, 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, - 4B14144A1B5883E500E04248 /* CSAtari2600.h */, - 4B14144B1B5883E500E04248 /* CSAtari2600.mm */, 4B14144C1B5883E500E04248 /* CSCathodeRayView.h */, 4B14144D1B5883E500E04248 /* CSCathodeRayView.m */, 4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */, @@ -1020,9 +1057,8 @@ 4BB73EDC1B587CA500552FC2 /* Machines */ = { isa = PBXGroup; children = ( - 4B6D7F921B58822000787C9A /* Atari2600.cpp */, - 4B6D7F931B58822000787C9A /* Atari2600.hpp */, - 4BA3921C1B8402B3007FBF0E /* Atari2600Inputs.h */, + 4B2E2D961C3A06EC00138695 /* Atari2600 */, + 4B2E2D9E1C3A070900138695 /* Electron */, ); name = Machines; path = ../../Machines; @@ -1441,10 +1477,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4B1414511B5885DF00E04248 /* Atari2600.cpp in Sources */, + 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, + 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */, + 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4B366DFC1B5C165A0026627B /* CRT.cpp in Sources */, + 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, 4BB73EA41B587A5100552FC2 /* Atari2600Document.swift in Sources */, - 4B14144E1B5883E500E04248 /* CSAtari2600.mm in Sources */, 4B2E2D921C399B9900138695 /* ElectronDocument.swift in Sources */, 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */, 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Atari2600Document.swift index f3edcd55d..ffa7ce5d6 100644 --- a/OSBindings/Mac/Clock Signal/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Atari2600Document.swift @@ -16,10 +16,10 @@ class Atari2600Document: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewR openGLView.delegate = self openGLView.responderDelegate = self - atari2600!.view = openGLView! + atari2600.view = openGLView // bind the content aspect ratio to remain 4:3 from now on - aController.window!.contentAspectRatio = NSSize(width: 4.0, height: 3.0) + aController.window?.contentAspectRatio = NSSize(width: 4.0, height: 3.0) } override class func autosavesInPlace() -> Bool { @@ -32,7 +32,7 @@ class Atari2600Document: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewR return "Atari2600Document" } - private var atari2600: CSAtari2600? = nil + private var atari2600: CSAtari2600! = nil override func dataOfType(typeName: String) throws -> NSData { // Insert code here to write your document to data of the specified type. If outError != nil, ensure that you create and set an appropriate error when returning nil. // You can also choose to override fileWrapperOfType:error:, writeToURL:ofType:error:, or writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead. @@ -41,7 +41,7 @@ class Atari2600Document: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewR override func readFromData(data: NSData, ofType typeName: String) throws { atari2600 = CSAtari2600() - atari2600!.setROM(data) + atari2600.setROM(data) } override func close() { @@ -66,7 +66,7 @@ class Atari2600Document: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewR let cycleCount = cycleCountLow + cycleCountHigh if let lastCycleCount = lastCycleCount { let elapsedTime = cycleCount - lastCycleCount - atari2600!.runForNumberOfCycles(Int32(elapsedTime)) + atari2600.runForNumberOfCycles(Int32(elapsedTime)) } lastCycleCount = cycleCount } @@ -86,21 +86,21 @@ class Atari2600Document: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewR func keyDown(event: NSEvent) { if let input = inputForKey(event) { - atari2600!.setState(true, forDigitalInput: input) + atari2600.setState(true, forDigitalInput: input) } if event.keyCode == 36 { - atari2600!.setResetLineEnabled(true) + atari2600.setResetLineEnabled(true) } } func keyUp(event: NSEvent) { if let input = inputForKey(event) { - atari2600!.setState(false, forDigitalInput: input) + atari2600.setState(false, forDigitalInput: input) } if event.keyCode == 36 { - atari2600!.setResetLineEnabled(false) + atari2600.setResetLineEnabled(false) } } diff --git a/OSBindings/Mac/Clock Signal/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/CSAtari2600.mm deleted file mode 100644 index 07b3ac646..000000000 --- a/OSBindings/Mac/Clock Signal/CSAtari2600.mm +++ /dev/null @@ -1,114 +0,0 @@ -// -// Atari2600.m -// CLK -// -// Created by Thomas Harte on 14/07/2015. -// Copyright © 2015 Thomas Harte. All rights reserved. -// - -#import "CSAtari2600.h" -#import "Atari2600.hpp" - -@interface CSAtari2600 (Callbacks) -- (void)crtDidEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; -@end - -struct Atari2600CRTDelegate: public Outputs::CRT::CRTDelegate { - __weak CSAtari2600 *atari; - void crt_did_end_frame(Outputs::CRT *crt, CRTFrame *frame, bool did_detect_vsync) { [atari crtDidEndFrame:frame didDetectVSync:did_detect_vsync]; } -}; - -typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { - CSAtari2600RunningStateRunning, - CSAtari2600RunningStateStopped -}; - -@implementation CSAtari2600 { - Atari2600::Machine _atari2600; - Atari2600CRTDelegate _crtDelegate; - - dispatch_queue_t _serialDispatchQueue; - - int _frameCount; - int _hitCount; - BOOL _didDecideRegion; - - NSConditionLock *_runningLock; -} - -- (void)crtDidEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync { - - if(!_didDecideRegion) - { - _frameCount++; - _hitCount += didDetectVSync ? 1 : 0; - - if(_frameCount > 30) - { - if(_hitCount < _frameCount >> 1) - { - _atari2600.switch_region(); - _didDecideRegion = YES; - } - - if(_hitCount > (_frameCount * 7) >> 3) - { - _didDecideRegion = YES; - } - } - } - - BOOL hasReturn = [self.view pushFrame:frame]; - - if(hasReturn) - _atari2600.get_crt()->return_frame(); -} - -- (void)runForNumberOfCycles:(int)cycles { - if([_runningLock tryLockWhenCondition:CSAtari2600RunningStateStopped]) { - [_runningLock unlockWithCondition:CSAtari2600RunningStateRunning]; - dispatch_async(_serialDispatchQueue, ^{ - [_runningLock lockWhenCondition:CSAtari2600RunningStateRunning]; - _atari2600.run_for_cycles(cycles); - [_runningLock unlockWithCondition:CSAtari2600RunningStateStopped]; - }); - } -} - -- (void)setROM:(NSData *)rom { - dispatch_async(_serialDispatchQueue, ^{ - _atari2600.set_rom(rom.length, (const uint8_t *)rom.bytes); - }); -} - -- (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput { - dispatch_async(_serialDispatchQueue, ^{ - _atari2600.set_digital_input(digitalInput, state ? true : false); - }); -} - -- (void)setResetLineEnabled:(BOOL)enabled { - dispatch_async(_serialDispatchQueue, ^{ - _atari2600.set_reset_line(enabled ? true : false); - }); -} - -- (void)setView:(CSCathodeRayView *)view { - _view = view; - [_view setSignalDecoder:[NSString stringWithUTF8String:_atari2600.get_signal_decoder()] type:CSCathodeRayViewSignalTypeNTSC]; -} - -- (instancetype)init { - self = [super init]; - - if (self) { - _crtDelegate.atari = self; - _atari2600.get_crt()->set_delegate(&_crtDelegate); - _serialDispatchQueue = dispatch_queue_create("Atari 2600 queue", DISPATCH_QUEUE_SERIAL); - _runningLock = [[NSConditionLock alloc] initWithCondition:CSAtari2600RunningStateStopped]; - } - - return self; -} - -@end diff --git a/OSBindings/Mac/Clock Signal/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/ElectronDocument.swift index 0fbd05563..8d3f8ae66 100644 --- a/OSBindings/Mac/Clock Signal/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/ElectronDocument.swift @@ -10,6 +10,10 @@ import Foundation class ElectronDocument: NSDocument, CSCathodeRayViewResponderDelegate, CSCathodeRayViewDelegate { + override init() { + super.init() + } + @IBOutlet weak var openGLView: CSCathodeRayView! override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) @@ -27,6 +31,7 @@ class ElectronDocument: NSDocument, CSCathodeRayViewResponderDelegate, CSCathode } override func readFromData(data: NSData, ofType typeName: String) throws { + print("H") } override func close() { diff --git a/OSBindings/Mac/Clock Signal/CSAtari2600.h b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h similarity index 75% rename from OSBindings/Mac/Clock Signal/CSAtari2600.h rename to OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h index ffa4191c9..38dc12c78 100644 --- a/OSBindings/Mac/Clock Signal/CSAtari2600.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h @@ -9,14 +9,11 @@ #import #import "CSCathodeRayView.h" #include "Atari2600Inputs.h" +#include "CSMachine.h" -@interface CSAtari2600 : NSObject +@interface CSAtari2600 : CSMachine -@property (nonatomic, weak) CSCathodeRayView *view; - -- (void)runForNumberOfCycles:(int)cycles; - (void)setROM:(nonnull NSData *)rom; - - (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput; - (void)setResetLineEnabled:(BOOL)enabled; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm new file mode 100644 index 000000000..d7006d766 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -0,0 +1,77 @@ +// +// Atari2600.m +// CLK +// +// Created by Thomas Harte on 14/07/2015. +// Copyright © 2015 Thomas Harte. All rights reserved. +// + +#import "CSAtari2600.h" + +#import "Atari2600.hpp" +#import "CSMachine+Subclassing.h" + +@implementation CSAtari2600 { + Atari2600::Machine _atari2600; + + int _frameCount; + int _hitCount; + BOOL _didDecideRegion; +} + +- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync { + if(!_didDecideRegion) + { + _frameCount++; + _hitCount += didDetectVSync ? 1 : 0; + + if(_frameCount > 30) + { + if(_hitCount < _frameCount >> 1) + { + _atari2600.switch_region(); + _didDecideRegion = YES; + } + + if(_hitCount > (_frameCount * 7) >> 3) + { + _didDecideRegion = YES; + } + } + } + + [super crt:crt didEndFrame:frame didDetectVSync:didDetectVSync]; +} + +- (void)doRunForNumberOfCycles:(int)numberOfCycles { + _atari2600.run_for_cycles(numberOfCycles); +} + +- (void)setROM:(NSData *)rom { + [self perform:^{ + _atari2600.set_rom(rom.length, (const uint8_t *)rom.bytes); + }]; +} + +- (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput { + [self perform:^{ + _atari2600.set_digital_input(digitalInput, state ? true : false); + }]; +} + +- (void)setResetLineEnabled:(BOOL)enabled { + [self perform:^{ + _atari2600.set_reset_line(enabled ? true : false); + }]; +} + +- (void)setView:(CSCathodeRayView *)view { + [super setView:view]; + [view setSignalDecoder:[NSString stringWithUTF8String:_atari2600.get_signal_decoder()] type:CSCathodeRayViewSignalTypeNTSC]; +} + +- (void)setCRTDelegate:(Outputs::CRT::CRTDelegate *)delegate{ + _atari2600.get_crt()->set_delegate(delegate); +} + +@end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h new file mode 100644 index 000000000..1aa88b268 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -0,0 +1,20 @@ +// +// CSMachine+Subclassing.h +// Clock Signal +// +// Created by Thomas Harte on 04/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "CSMachine.h" +#include "../../../../Outputs/CRT.hpp" + +@interface CSMachine (Subclassing) + +- (void)setCRTDelegate:(Outputs::CRT::CRTDelegate *)delegate; +- (void)doRunForNumberOfCycles:(int)numberOfCycles; +- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; + +- (void)perform:(dispatch_block_t)action; + +@end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h new file mode 100644 index 000000000..dbce321a3 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -0,0 +1,18 @@ +// +// CSMachine.h +// Clock Signal +// +// Created by Thomas Harte on 04/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import +#import "CSCathodeRayView.h" + +@interface CSMachine : NSObject + +- (void)runForNumberOfCycles:(int)numberOfCycles; + +@property (nonatomic, weak) CSCathodeRayView *view; + +@end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm new file mode 100644 index 000000000..fa5b80452 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -0,0 +1,63 @@ +// +// CSMachine.m +// Clock Signal +// +// Created by Thomas Harte on 04/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "CSMachine.h" +#import "CSMachine+Subclassing.h" + +struct CRTDelegate: public Outputs::CRT::CRTDelegate { + __weak CSMachine *machine; + void crt_did_end_frame(Outputs::CRT *crt, CRTFrame *frame, bool did_detect_vsync) { + [machine crt:crt didEndFrame:frame didDetectVSync:did_detect_vsync]; + } +}; + +typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { + CSMachineRunningStateRunning, + CSMachineRunningStateStopped +}; + +@implementation CSMachine { + CRTDelegate _crtDelegate; + + dispatch_queue_t _serialDispatchQueue; + NSConditionLock *_runningLock; +} + +- (void)perform:(dispatch_block_t)action { + dispatch_async(_serialDispatchQueue, action); +} + +- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync { + if([self.view pushFrame:frame]) crt->return_frame(); +} + +- (void)runForNumberOfCycles:(int)cycles { + if([_runningLock tryLockWhenCondition:CSMachineRunningStateStopped]) { + [_runningLock unlockWithCondition:CSMachineRunningStateRunning]; + dispatch_async(_serialDispatchQueue, ^{ + [_runningLock lockWhenCondition:CSMachineRunningStateRunning]; + [self doRunForNumberOfCycles:cycles]; + [_runningLock unlockWithCondition:CSMachineRunningStateStopped]; + }); + } +} + +- (instancetype)init { + self = [super init]; + + if (self) { + _crtDelegate.machine = self; + [self setCRTDelegate:&_crtDelegate]; + _serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL); + _runningLock = [[NSConditionLock alloc] initWithCondition:CSMachineRunningStateStopped]; + } + + return self; +} + +@end From 43ab8bbad54999856487295116f23bafbfa60745 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Jan 2016 23:16:37 -0500 Subject: [PATCH 004/307] Okay, so then here's a first shot at the Objective-C -> C++ bridge for the Electron. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 +++++ .../Mac/Clock Signal/Wrappers/CSAtari2600.h | 4 +--- .../Mac/Clock Signal/Wrappers/CSElectron.h | 13 +++++++++++ .../Mac/Clock Signal/Wrappers/CSElectron.mm | 22 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index eb1964c7d..5f8dcda32 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 4B366DFC1B5C165A0026627B /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B366DFA1B5C165A0026627B /* CRT.cpp */; }; 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE4A1C3B3B0C0093A61B /* CSAtari2600.mm */; }; 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */; }; + 4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */; }; 4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; }; @@ -340,6 +341,8 @@ 4B55CE4C1C3B3BDA0093A61B /* CSMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSMachine.h; sourceTree = ""; }; 4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSMachine.mm; sourceTree = ""; }; 4B55CE4F1C3B78A80093A61B /* CSMachine+Subclassing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Subclassing.h"; sourceTree = ""; }; + 4B55CE521C3B7ABF0093A61B /* CSElectron.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSElectron.h; sourceTree = ""; }; + 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = ""; }; 4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = ""; }; 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = 6502_functional_test.bin; sourceTree = ""; }; @@ -710,6 +713,8 @@ 4B55CE4C1C3B3BDA0093A61B /* CSMachine.h */, 4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */, 4B55CE4F1C3B78A80093A61B /* CSMachine+Subclassing.h */, + 4B55CE521C3B7ABF0093A61B /* CSElectron.h */, + 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */, ); path = Wrappers; sourceTree = ""; @@ -1477,6 +1482,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */, 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h index 38dc12c78..05a0b70c3 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h @@ -6,10 +6,8 @@ // Copyright © 2015 Thomas Harte. All rights reserved. // -#import -#import "CSCathodeRayView.h" -#include "Atari2600Inputs.h" #include "CSMachine.h" +#include "Atari2600Inputs.h" @interface CSAtari2600 : CSMachine diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h new file mode 100644 index 000000000..d95b9dd32 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -0,0 +1,13 @@ +// +// CSElectron.h +// Clock Signal +// +// Created by Thomas Harte on 04/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "CSMachine.h" + +@interface CSElectron : CSMachine + +@end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm new file mode 100644 index 000000000..9bd8f32fc --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -0,0 +1,22 @@ +// +// CSElectron.m +// Clock Signal +// +// Created by Thomas Harte on 04/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "CSElectron.h" + +#import "Electron.hpp" +#import "CSMachine+Subclassing.h" + +@implementation CSElectron { + Electron::Machine _electron; +} + +- (void)doRunForNumberOfCycles:(int)numberOfCycles { + _electron.run_for_cycles(numberOfCycles); +} + +@end From 616dc0b57c5213d0f88de3d9d3f5cf8dc461cdc0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Jan 2016 23:40:43 -0500 Subject: [PATCH 005/307] Okay, so this is the absolute bare minimum about of refactoring and wiring necessary to get the [unimplemented] Electron machine pumping. --- .../Clock Signal.xcodeproj/project.pbxproj | 64 +++++++++++++------ .../ClockSignal-Bridging-Header.h | 2 + .../{ => Documents}/Atari2600Document.swift | 50 +++++---------- .../Documents/ElectronDocument.swift | 40 ++++++++++++ .../Documents/MachineDocument.swift | 52 +++++++++++++++ .../Mac/Clock Signal/ElectronDocument.swift | 51 --------------- .../{ => Views}/CSCathodeRayView.h | 0 .../{ => Views}/CSCathodeRayView.m | 0 .../Mac/Clock Signal/Wrappers/CSMachine.mm | 3 + 9 files changed, 157 insertions(+), 105 deletions(-) rename OSBindings/Mac/Clock Signal/{ => Documents}/Atari2600Document.swift (61%) create mode 100644 OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift create mode 100644 OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift delete mode 100644 OSBindings/Mac/Clock Signal/ElectronDocument.swift rename OSBindings/Mac/Clock Signal/{ => Views}/CSCathodeRayView.h (100%) rename OSBindings/Mac/Clock Signal/{ => Views}/CSCathodeRayView.m (100%) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 5f8dcda32..f84d757ea 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -7,13 +7,11 @@ objects = { /* Begin PBXBuildFile section */ - 4B14144F1B5883E500E04248 /* CSCathodeRayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B14144D1B5883E500E04248 /* CSCathodeRayView.m */; }; 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; }; 4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; }; 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; }; - 4B2E2D921C399B9900138695 /* ElectronDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D911C399B9900138695 /* ElectronDocument.swift */; }; 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; @@ -21,6 +19,10 @@ 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE4A1C3B3B0C0093A61B /* CSAtari2600.mm */; }; 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */; }; 4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */; }; + 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE561C3B7D360093A61B /* Atari2600Document.swift */; }; + 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE571C3B7D360093A61B /* ElectronDocument.swift */; }; + 4B55CE5D1C3B7D6F0093A61B /* CSCathodeRayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSCathodeRayView.m */; }; + 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; 4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; }; @@ -291,7 +293,6 @@ 4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; }; 4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; }; 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; }; - 4BB73EA41B587A5100552FC2 /* Atari2600Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA31B587A5100552FC2 /* Atari2600Document.swift */; }; 4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */; }; 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA81B587A5100552FC2 /* Assets.xcassets */; }; 4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */; }; @@ -317,8 +318,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 4B14144C1B5883E500E04248 /* CSCathodeRayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSCathodeRayView.h; sourceTree = ""; }; - 4B14144D1B5883E500E04248 /* CSCathodeRayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSCathodeRayView.m; sourceTree = ""; }; 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = ""; }; 4B1414571B58879D00E04248 /* CPU6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPU6502.cpp; sourceTree = ""; }; 4B1414581B58879D00E04248 /* CPU6502.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502.hpp; sourceTree = ""; }; @@ -327,7 +326,6 @@ 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = ""; }; 4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = ""; }; 4B2632551B631A510082A461 /* CRTFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CRTFrame.h; path = ../../Outputs/CRTFrame.h; sourceTree = ""; }; - 4B2E2D911C399B9900138695 /* ElectronDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronDocument.swift; sourceTree = ""; }; 4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = ""; }; 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; 4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = ""; }; @@ -343,6 +341,11 @@ 4B55CE4F1C3B78A80093A61B /* CSMachine+Subclassing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Subclassing.h"; sourceTree = ""; }; 4B55CE521C3B7ABF0093A61B /* CSElectron.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSElectron.h; sourceTree = ""; }; 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = ""; }; + 4B55CE561C3B7D360093A61B /* Atari2600Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atari2600Document.swift; sourceTree = ""; }; + 4B55CE571C3B7D360093A61B /* ElectronDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronDocument.swift; sourceTree = ""; }; + 4B55CE5B1C3B7D6F0093A61B /* CSCathodeRayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSCathodeRayView.h; sourceTree = ""; }; + 4B55CE5C1C3B7D6F0093A61B /* CSCathodeRayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSCathodeRayView.m; sourceTree = ""; }; + 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = ""; }; 4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = ""; }; 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = 6502_functional_test.bin; sourceTree = ""; }; @@ -616,7 +619,6 @@ 4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = ""; }; 4BB73E9E1B587A5100552FC2 /* Clock Signal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Clock Signal.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 4BB73EA31B587A5100552FC2 /* Atari2600Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atari2600Document.swift; sourceTree = ""; }; 4BB73EA61B587A5100552FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Atari2600Document.xib; sourceTree = ""; }; 4BB73EA81B587A5100552FC2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4BB73EAB1B587A5100552FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -719,6 +721,27 @@ path = Wrappers; sourceTree = ""; }; + 4B55CE551C3B7D360093A61B /* Documents */ = { + isa = PBXGroup; + children = ( + 4B55CE561C3B7D360093A61B /* Atari2600Document.swift */, + 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */, + 4B55CE571C3B7D360093A61B /* ElectronDocument.swift */, + 4B2E2D931C399D1200138695 /* ElectronDocument.xib */, + 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */, + ); + path = Documents; + sourceTree = ""; + }; + 4B55CE5A1C3B7D6F0093A61B /* Views */ = { + isa = PBXGroup; + children = ( + 4B55CE5B1C3B7D6F0093A61B /* CSCathodeRayView.h */, + 4B55CE5C1C3B7D6F0093A61B /* CSCathodeRayView.m */, + ); + path = Views; + sourceTree = ""; + }; 4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = { isa = PBXGroup; children = ( @@ -1017,19 +1040,15 @@ 4BB73EA01B587A5100552FC2 /* Clock Signal */ = { isa = PBXGroup; children = ( - 4B55CE481C3B3B0C0093A61B /* Wrappers */, - 4B2E2D931C399D1200138695 /* ElectronDocument.xib */, - 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, - 4B14144C1B5883E500E04248 /* CSCathodeRayView.h */, - 4B14144D1B5883E500E04248 /* CSCathodeRayView.m */, 4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */, - 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */, - 4BB73EA31B587A5100552FC2 /* Atari2600Document.swift */, - 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */, - 4BB73EA81B587A5100552FC2 /* Assets.xcassets */, - 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */, + 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, 4BB73EAD1B587A5100552FC2 /* Info.plist */, - 4B2E2D911C399B9900138695 /* ElectronDocument.swift */, + 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */, + 4BB73EA81B587A5100552FC2 /* Assets.xcassets */, + 4B55CE551C3B7D360093A61B /* Documents */, + 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */, + 4B55CE5A1C3B7D6F0093A61B /* Views */, + 4B55CE481C3B3B0C0093A61B /* Wrappers */, ); path = "Clock Signal"; sourceTree = ""; @@ -1483,16 +1502,17 @@ buildActionMask = 2147483647; files = ( 4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */, + 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, + 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, + 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, + 4B55CE5D1C3B7D6F0093A61B /* CSCathodeRayView.m in Sources */, 4B366DFC1B5C165A0026627B /* CRT.cpp in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, - 4BB73EA41B587A5100552FC2 /* Atari2600Document.swift in Sources */, - 4B2E2D921C399B9900138695 /* ElectronDocument.swift in Sources */, 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */, 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */, - 4B14144F1B5883E500E04248 /* CSCathodeRayView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1540,6 +1560,7 @@ 4B2E2D941C399D1200138695 /* Base */, ); name = ElectronDocument.xib; + path = ..; sourceTree = ""; }; 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */ = { @@ -1548,6 +1569,7 @@ 4BB73EA61B587A5100552FC2 /* Base */, ); name = Atari2600Document.xib; + path = ..; sourceTree = ""; }; 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */ = { diff --git a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h index 096105656..d9434a171 100644 --- a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h +++ b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h @@ -2,5 +2,7 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // +#import "CSMachine.h" #import "CSAtari2600.h" +#import "CSElectron.h" #import "CSCathodeRayView.h" diff --git a/OSBindings/Mac/Clock Signal/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift similarity index 61% rename from OSBindings/Mac/Clock Signal/Atari2600Document.swift rename to OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index ffa7ce5d6..bf58f176f 100644 --- a/OSBindings/Mac/Clock Signal/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -8,18 +8,17 @@ import Cocoa -class Atari2600Document: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewResponderDelegate { +class Atari2600Document: MachineDocument { + + // MARK: NSDocument overrides + override init() { + super.init() + self.intendedCyclesPerSecond = 1194720 + } - @IBOutlet weak var openGLView: CSCathodeRayView! override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) - - openGLView.delegate = self - openGLView.responderDelegate = self atari2600.view = openGLView - - // bind the content aspect ratio to remain 4:3 from now on - aController.window?.contentAspectRatio = NSSize(width: 4.0, height: 3.0) } override class func autosavesInPlace() -> Bool { @@ -49,31 +48,15 @@ class Atari2600Document: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewR openGLView.invalidate() } - // MARK: CSOpenGLViewDelegate + // MARK: MachineDocument overrides - private var lastCycleCount: Int64? - func openGLView(view: CSCathodeRayView, didUpdateToTime time: CVTimeStamp) { - - // TODO: treat time as a delta from old time, work out how many cycles that is plus error - - // this slightly elaborate dance is to avoid overflow - let intendedCyclesPerSecond: Int64 = 1194720 - let videoTimeScale64 = Int64(time.videoTimeScale) - - let cycleCountLow = ((time.videoTime % videoTimeScale64) * intendedCyclesPerSecond) / videoTimeScale64 - let cycleCountHigh = (time.videoTime / videoTimeScale64) * intendedCyclesPerSecond - - let cycleCount = cycleCountLow + cycleCountHigh - if let lastCycleCount = lastCycleCount { - let elapsedTime = cycleCount - lastCycleCount - atari2600.runForNumberOfCycles(Int32(elapsedTime)) - } - lastCycleCount = cycleCount + override func runForNumberOfCycles(numberOfCycles: Int32) { + atari2600.runForNumberOfCycles(numberOfCycles) } // MARK: CSOpenGLViewResponderDelegate - func inputForKey(event: NSEvent) -> Atari2600DigitalInput? { + private func inputForKey(event: NSEvent) -> Atari2600DigitalInput? { switch event.keyCode { case 123: return Atari2600DigitalInputJoy1Left case 126: return Atari2600DigitalInputJoy1Up @@ -84,7 +67,9 @@ class Atari2600Document: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewR } } - func keyDown(event: NSEvent) { + override func keyDown(event: NSEvent) { + super.keyDown(event) + if let input = inputForKey(event) { atari2600.setState(true, forDigitalInput: input) } @@ -94,7 +79,9 @@ class Atari2600Document: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewR } } - func keyUp(event: NSEvent) { + override func keyUp(event: NSEvent) { + super.keyUp(event) + if let input = inputForKey(event) { atari2600.setState(false, forDigitalInput: input) } @@ -103,7 +90,4 @@ class Atari2600Document: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewR atari2600.setResetLineEnabled(false) } } - - func flagsChanged(newModifiers: NSEvent) { - } } \ No newline at end of file diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift new file mode 100644 index 000000000..026e0bb1c --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -0,0 +1,40 @@ +// +// ElectronDocument.swift +// Clock Signal +// +// Created by Thomas Harte on 03/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +import Foundation + +class ElectronDocument: MachineDocument { + + private var electron = CSElectron() + override init() { + super.init() + self.intendedCyclesPerSecond = 2000000 + } + +// override func windowControllerDidLoadNib(aController: NSWindowController) { +// super.windowControllerDidLoadNib(aController) +// } + + override var windowNibName: String? { + return "ElectronDocument" + } + + override func readFromData(data: NSData, ofType typeName: String) throws { + } + + // MARK: CSOpenGLViewDelegate + override func runForNumberOfCycles(numberOfCycles: Int32) { + electron.runForNumberOfCycles(numberOfCycles) + } + + // MARK: CSOpenGLViewResponderDelegate +// func keyDown(event: NSEvent) {} +// func keyUp(event: NSEvent) {} +// func flagsChanged(newModifiers: NSEvent) {} + +} diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift new file mode 100644 index 000000000..54bfd891f --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -0,0 +1,52 @@ +// +// MachineDocument.swift +// Clock Signal +// +// Created by Thomas Harte on 04/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +import Cocoa + +class MachineDocument: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewResponderDelegate { + + @IBOutlet weak var openGLView: CSCathodeRayView! { + didSet { + openGLView.delegate = self + openGLView.responderDelegate = self + } + } + + override func windowControllerDidLoadNib(aController: NSWindowController) { + super.windowControllerDidLoadNib(aController) + + // bind the content aspect ratio to remain 4:3 from now on + aController.window?.contentAspectRatio = NSSize(width: 4.0, height: 3.0) + } + + var intendedCyclesPerSecond: Int64 = 0 + private var lastCycleCount: Int64? + final func openGLView(view: CSCathodeRayView, didUpdateToTime time: CVTimeStamp) { + // TODO: treat time as a delta from old time, work out how many cycles that is plus error + + // this slightly elaborate dance is to avoid overflow + let videoTimeScale64 = Int64(time.videoTimeScale) + + let cycleCountLow = ((time.videoTime % videoTimeScale64) * intendedCyclesPerSecond) / videoTimeScale64 + let cycleCountHigh = (time.videoTime / videoTimeScale64) * intendedCyclesPerSecond + + let cycleCount = cycleCountLow + cycleCountHigh + if let lastCycleCount = lastCycleCount { + let elapsedTime = cycleCount - lastCycleCount + runForNumberOfCycles(Int32(elapsedTime)) + } + lastCycleCount = cycleCount + } + + func runForNumberOfCycles(numberOfCycles: Int32) {} + + // MARK: CSOpenGLViewResponderDelegate + func keyDown(event: NSEvent) {} + func keyUp(event: NSEvent) {} + func flagsChanged(newModifiers: NSEvent) {} +} diff --git a/OSBindings/Mac/Clock Signal/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/ElectronDocument.swift deleted file mode 100644 index 8d3f8ae66..000000000 --- a/OSBindings/Mac/Clock Signal/ElectronDocument.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// ElectronDocument.swift -// Clock Signal -// -// Created by Thomas Harte on 03/01/2016. -// Copyright © 2016 Thomas Harte. All rights reserved. -// - -import Foundation - -class ElectronDocument: NSDocument, CSCathodeRayViewResponderDelegate, CSCathodeRayViewDelegate { - - override init() { - super.init() - } - - @IBOutlet weak var openGLView: CSCathodeRayView! - override func windowControllerDidLoadNib(aController: NSWindowController) { - super.windowControllerDidLoadNib(aController) - - openGLView.delegate = self - openGLView.responderDelegate = self -// atari2600!.view = openGLView! - - // bind the content aspect ratio to remain 4:3 from now on - aController.window!.contentAspectRatio = NSSize(width: 4.0, height: 3.0) - } - - override var windowNibName: String? { - return "ElectronDocument" - } - - override func readFromData(data: NSData, ofType typeName: String) throws { - print("H") - } - - override func close() { - super.close() - openGLView.invalidate() - } - - // MARK: CSOpenGLViewDelegate - func openGLView(view: CSCathodeRayView, didUpdateToTime time: CVTimeStamp) { - } - - // MARK: CSOpenGLViewResponderDelegate - func keyDown(event: NSEvent) {} - func keyUp(event: NSEvent) {} - func flagsChanged(newModifiers: NSEvent) {} - -} diff --git a/OSBindings/Mac/Clock Signal/CSCathodeRayView.h b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h similarity index 100% rename from OSBindings/Mac/Clock Signal/CSCathodeRayView.h rename to OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h diff --git a/OSBindings/Mac/Clock Signal/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m similarity index 100% rename from OSBindings/Mac/Clock Signal/CSCathodeRayView.m rename to OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index fa5b80452..696c78438 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -60,4 +60,7 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { return self; } +- (void)setCRTDelegate:(Outputs::CRT::CRTDelegate *)delegate {} +- (void)doRunForNumberOfCycles:(int)numberOfCycles {} + @end From c69b3256bad8258cfb60fa560ad0220a0652407b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Jan 2016 23:44:36 -0500 Subject: [PATCH 006/307] Slightly simplified Swift usage. --- OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index bf58f176f..835597711 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -31,7 +31,7 @@ class Atari2600Document: MachineDocument { return "Atari2600Document" } - private var atari2600: CSAtari2600! = nil + private var atari2600 = CSAtari2600() override func dataOfType(typeName: String) throws -> NSData { // Insert code here to write your document to data of the specified type. If outError != nil, ensure that you create and set an appropriate error when returning nil. // You can also choose to override fileWrapperOfType:error:, writeToURL:ofType:error:, or writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead. @@ -39,7 +39,6 @@ class Atari2600Document: MachineDocument { } override func readFromData(data: NSData, ofType typeName: String) throws { - atari2600 = CSAtari2600() atari2600.setROM(data) } From bfd9957c81a7e6e0e4133c221d0527c47296ce85 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 6 Jan 2016 21:09:49 -0500 Subject: [PATCH 007/307] =?UTF-8?q?You=20now=20get=20an=20Electron=20only?= =?UTF-8?q?=20if=20you=20ask=20for=20a=20new=20file.=20That'll=20do=20for?= =?UTF-8?q?=20now=20while=20it's=20the=20only=20thing=20that=20one=20might?= =?UTF-8?q?=20want=20to=20start=20without=20supplying=20a=20file.=20The=20?= =?UTF-8?q?6502=20now=20starts=20from=20a=20defined=20point=20=E2=80=94=20?= =?UTF-8?q?being=20reset.=20The=20Electron=20is=20starting=20to=20grow=20t?= =?UTF-8?q?he=20absolute=20most=20simple=20buds=20of=20its=20memory=20map.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Machines/Electron/Electron.cpp | 38 +++++++++++++++++++ Machines/Electron/Electron.hpp | 9 +++++ OSBindings/Mac/Clock Signal/AppDelegate.swift | 7 ++-- Processors/6502/CPU6502.hpp | 5 +++ 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 808bff52b..6d8a0fd2c 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -8,10 +8,13 @@ #include "Electron.hpp" +#include + using namespace Electron; Machine::Machine() { + setup6502(); } Machine::~Machine() @@ -20,5 +23,40 @@ Machine::~Machine() unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { + if(address < 32768) + { + if(isReadOperation(operation)) + { + *value = ram[address]; + } + else + { + ram[address] = *value; + } + } + else + { + if(address > 49152) + { + if(isReadOperation(operation)) *value = os[address - 49152]; + } + else + { + if(isReadOperation(operation)) *value = basic[address - 32768]; + } + } + return 1; } + +void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) +{ + uint8_t *target = nullptr; + switch(slot) + { + case ROMTypeBASIC: target = basic; break; + case ROMTypeOS: target = os; break; + } + + memcpy(target, data, std::min((size_t)16384, length)); +} diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 23f5930c7..82ab352e3 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -16,6 +16,11 @@ namespace Electron { +enum ROMSlot: int { + ROMTypeBASIC = 12, + ROMTypeOS = 16, +}; + class Machine: public CPU6502::Processor { public: @@ -25,6 +30,10 @@ class Machine: public CPU6502::Processor { unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); + void set_rom(ROMSlot slot, size_t length, const uint8_t *data); + + private: + uint8_t os[16384], basic[16384], ram[32768]; }; } diff --git a/OSBindings/Mac/Clock Signal/AppDelegate.swift b/OSBindings/Mac/Clock Signal/AppDelegate.swift index 2abf97a3a..2286db90e 100644 --- a/OSBindings/Mac/Clock Signal/AppDelegate.swift +++ b/OSBindings/Mac/Clock Signal/AppDelegate.swift @@ -11,8 +11,6 @@ import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - - func applicationDidFinishLaunching(aNotification: NSNotification) { // Insert code here to initialize your application } @@ -21,6 +19,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { // Insert code here to tear down your application } - + // decline to open a new file unless the user explicitly requests it + func applicationShouldOpenUntitledFile(sender: NSApplication) -> Bool { + return false + } } diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 3c420e0a0..2411bc30b 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -946,6 +946,11 @@ template class Processor { _overflowFlag &= Flag::Overflow; _s = 0; _nextBusOperation = BusOperation::None; + + // TODO: is this accurate? It feels more likely that a CPU would need to wait + // on an explicit reset command, since the relative startup times of different + // components from power on would be a bit unpredictable. + schedule_program(get_reset_program()); } void return_from_subroutine() From 0db8938d27915cd556db943aee119ed763413389 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 6 Jan 2016 23:14:36 -0500 Subject: [PATCH 008/307] Added the option for the CSCathodeRayView to show only a subsection of the full scan area. Zoomed in a little on the 2600. Put in enough piping to give the Electron sight of its ROMs at least. --- Machines/Electron/Electron.cpp | 2 + .../Clock Signal.xcodeproj/project.pbxproj | 24 +++++++++ .../Documents/Atari2600Document.swift | 1 + .../Documents/ElectronDocument.swift | 7 +++ .../Mac/Clock Signal/Views/CSCathodeRayView.h | 4 ++ .../Mac/Clock Signal/Views/CSCathodeRayView.m | 49 +++++++++++++++++-- .../Mac/Clock Signal/Wrappers/CSElectron.h | 3 ++ .../Mac/Clock Signal/Wrappers/CSElectron.mm | 8 +++ 8 files changed, 93 insertions(+), 5 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 6d8a0fd2c..7967c06a6 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -23,6 +23,8 @@ Machine::~Machine() unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { + printf("%04x\n", address); + if(address < 32768) { if(isReadOperation(operation)) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f84d757ea..a6e376595 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -298,6 +298,8 @@ 4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */; }; 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; }; 4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; + 4BE5F85E1C3E1C2500C43F01 /* basic.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85C1C3E1C2500C43F01 /* basic.rom */; }; + 4BE5F85F1C3E1C2500C43F01 /* os.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85D1C3E1C2500C43F01 /* os.rom */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -630,6 +632,8 @@ 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clock_SignalUITests.swift; sourceTree = ""; }; 4BB73EC31B587A5100552FC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "Clock Signal.entitlements"; sourceTree = ""; }; + 4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = ""; }; + 4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1040,6 +1044,7 @@ 4BB73EA01B587A5100552FC2 /* Clock Signal */ = { isa = PBXGroup; children = ( + 4BE5F85A1C3E1C2500C43F01 /* Resources */, 4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */, 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */, 4BB73EAD1B587A5100552FC2 /* Info.plist */, @@ -1097,6 +1102,23 @@ path = ../../Processors; sourceTree = ""; }; + 4BE5F85A1C3E1C2500C43F01 /* Resources */ = { + isa = PBXGroup; + children = ( + 4BE5F85B1C3E1C2500C43F01 /* Electron */, + ); + path = Resources; + sourceTree = ""; + }; + 4BE5F85B1C3E1C2500C43F01 /* Electron */ = { + isa = PBXGroup; + children = ( + 4BE5F85C1C3E1C2500C43F01 /* basic.rom */, + 4BE5F85D1C3E1C2500C43F01 /* os.rom */, + ); + path = Electron; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1208,8 +1230,10 @@ files = ( 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */, 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, + 4BE5F85F1C3E1C2500C43F01 /* os.rom in Resources */, 4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */, 4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, + 4BE5F85E1C3E1C2500C43F01 /* basic.rom in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index 835597711..ba88e0797 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -19,6 +19,7 @@ class Atari2600Document: MachineDocument { override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) atari2600.view = openGLView + openGLView.frameBounds = CGRectMake(0.1, 0.1, 0.8, 0.8) } override class func autosavesInPlace() -> Bool { diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 026e0bb1c..0756920e9 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -14,6 +14,13 @@ class ElectronDocument: MachineDocument { override init() { super.init() self.intendedCyclesPerSecond = 2000000 + + if let osPath = NSBundle.mainBundle().pathForResource("os", ofType: "rom") { + electron.setOSROM(NSData(contentsOfFile: osPath)!) + } + if let basicPath = NSBundle.mainBundle().pathForResource("basic", ofType: "rom") { + electron.setBASICROM(NSData(contentsOfFile: basicPath)!) + } } // override func windowControllerDidLoadNib(aController: NSWindowController) { diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h index d07416c8e..5eff05a12 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h @@ -37,4 +37,8 @@ typedef NS_ENUM(NSInteger, CSCathodeRayViewSignalType) { - (BOOL)pushFrame:(nonnull CRTFrame *)crtFrame; - (void)setSignalDecoder:(nonnull NSString *)decoder type:(CSCathodeRayViewSignalType)type; +// these are relative to a [0, 1] range in both width and height; +// default is .origin = (0, 0), .size = (1, 1) +@property (nonatomic, assign) CGRect frameBounds; + @end diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index d9d4870f1..bbbaa3628 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -25,6 +25,7 @@ GLint _lateralAttribute; GLint _textureSizeUniform, _windowSizeUniform; + GLint _boundsOriginUniform, _boundsSizeUniform; GLint _alphaUniform; GLuint _textureName, _shadowMaskTextureName; @@ -117,6 +118,12 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt glDeleteProgram(_shaderProgram); } +- (NSPoint)backingViewSize +{ + NSPoint backingSize = {.x = self.bounds.size.width, .y = self.bounds.size.height}; + return [self convertPointToBacking:backingSize]; +} + - (void)reshape { [super reshape]; @@ -124,15 +131,35 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt [self.openGLContext makeCurrentContext]; CGLLockContext([[self openGLContext] CGLContextObj]); - NSPoint backingSize = {.x = self.bounds.size.width, .y = self.bounds.size.height}; - NSPoint viewSize = [self convertPointToBacking:backingSize]; + NSPoint viewSize = [self backingViewSize]; glViewport(0, 0, (GLsizei)viewSize.x, (GLsizei)viewSize.y); - glUniform2f(_windowSizeUniform, (GLfloat)viewSize.x, (GLfloat)viewSize.y); + [self pushSizeUniforms]; CGLUnlockContext([[self openGLContext] CGLContextObj]); } +- (void)setFrameBounds:(CGRect)frameBounds +{ + _frameBounds = frameBounds; + + [self.openGLContext makeCurrentContext]; + CGLLockContext([[self openGLContext] CGLContextObj]); + + [self pushSizeUniforms]; + + CGLUnlockContext([[self openGLContext] CGLContextObj]); +} + +- (void)pushSizeUniforms +{ + NSPoint viewSize = [self backingViewSize]; + glUniform2f(_windowSizeUniform, (GLfloat)viewSize.x, (GLfloat)viewSize.y); + + glUniform2f(_boundsOriginUniform, (GLfloat)_frameBounds.origin.x, (GLfloat)_frameBounds.origin.y); + glUniform2f(_boundsSizeUniform, (GLfloat)_frameBounds.size.width, (GLfloat)_frameBounds.size.height); +} + - (void)awakeFromNib { NSOpenGLPixelFormatAttribute attributes[] = @@ -159,6 +186,9 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt self.pixelFormat = pixelFormat; self.openGLContext = context; self.wantsBestResolutionOpenGLSurface = YES; + + // establish default instance variable values + self.frameBounds = CGRectMake(0.0, 0.0, 1.0, 1.0); } - (GLint)formatForDepth:(unsigned int)depth @@ -267,6 +297,9 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "in vec2 srcCoordinates;\n" "in float lateral;\n" "\n" + "uniform vec2 boundsOrigin;\n" + "uniform vec2 boundsSize;\n" + "\n" "out float lateralVarying;\n" "out vec2 shadowMaskCoordinates;\n" "\n" @@ -283,9 +316,12 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "\n" "%@\n" "\n" - "gl_Position = vec4(position.x * 2.0 - 1.0, 1.0 - position.y * 2.0 + position.x / 131.0, 0.0, 1.0);\n" + "vec2 mappedPosition = (position - boundsOrigin) / boundsSize;" + "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);\n" "}\n"; +// + mappedPosition.x / 131.0 + switch(_signalType) { case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:vertexShader, ntscVertexShaderGlobals, ntscVertexShaderBody]; @@ -295,7 +331,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (nonnull NSString *)fragmentShaderForType:(CSCathodeRayViewSignalType)type { - NSString *const fragmentShader = @"#version 150\n" "\n" @@ -396,10 +431,14 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt _alphaUniform = glGetUniformLocation(_shaderProgram, "alpha"); _textureSizeUniform = glGetUniformLocation(_shaderProgram, "textureSize"); _windowSizeUniform = glGetUniformLocation(_shaderProgram, "windowSize"); + _boundsSizeUniform = glGetUniformLocation(_shaderProgram, "boundsSize"); + _boundsOriginUniform = glGetUniformLocation(_shaderProgram, "boundsOrigin"); GLint texIDUniform = glGetUniformLocation(_shaderProgram, "texID"); GLint shadowMaskTexIDUniform = glGetUniformLocation(_shaderProgram, "shadowMaskTexID"); + [self pushSizeUniforms]; + glUniform1i(texIDUniform, 0); glUniform1i(shadowMaskTexIDUniform, 1); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index d95b9dd32..1826efd24 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -10,4 +10,7 @@ @interface CSElectron : CSMachine +- (void)setOSROM:(nonnull NSData *)rom; +- (void)setBASICROM:(nonnull NSData *)rom; + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 9bd8f32fc..459e5eb00 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -19,4 +19,12 @@ _electron.run_for_cycles(numberOfCycles); } +- (void)setOSROM:(nonnull NSData *)rom { + _electron.set_rom(Electron::ROMTypeOS, rom.length, (const uint8_t *)rom.bytes); +} + +- (void)setBASICROM:(nonnull NSData *)rom { + _electron.set_rom(Electron::ROMTypeBASIC, rom.length, (const uint8_t *)rom.bytes); +} + @end From 8c1bfa5a0589503d23e63058735a0735bccaee62 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 7 Jan 2016 20:36:27 -0500 Subject: [PATCH 009/307] This is the bare minimum to prove that the ROM is trying properly to boot. --- Machines/Electron/Electron.cpp | 40 +++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 7967c06a6..1d6a16378 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -23,8 +23,6 @@ Machine::~Machine() unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { - printf("%04x\n", address); - if(address < 32768) { if(isReadOperation(operation)) @@ -40,7 +38,43 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { if(address > 49152) { - if(isReadOperation(operation)) *value = os[address - 49152]; + if((address & 0xff00) == 0xfe00) + { + printf("%c: %02x: ", isReadOperation(operation) ? 'r' : 'w', *value); + + switch(address&0xf) + { + case 0x0: + printf("Interrupt status or control\n"); + break; + case 0x1: + break; + case 0x2: + case 0x3: + printf("Screen start address\n"); + break; + case 0x4: + printf("Cassette\n"); + break; + case 0x5: + printf("Interrupt clear and paging\n"); + break; + case 0x6: + printf("Counter\n"); + break; + case 0x7: + printf("Misc. control\n"); + break; + default: + printf("Palette\n"); + break; + } + } + else + { + if(isReadOperation(operation)) + *value = os[address - 49152]; + } } else { From 47a7654c00e29c50b6f934574719afc23832351a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 7 Jan 2016 21:01:13 -0500 Subject: [PATCH 010/307] Added just enough that this is probably a successful boot. I guess I'm going to need to get invested in graphics next? Hmmm. --- Machines/Electron/Electron.cpp | 47 +++++++++++++++++-- Machines/Electron/Electron.hpp | 16 +++++-- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 4 +- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 1d6a16378..2a2cafb60 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -15,6 +15,7 @@ using namespace Electron; Machine::Machine() { setup6502(); + _interruptStatus = 0x02; } Machine::~Machine() @@ -45,19 +46,40 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin switch(address&0xf) { case 0x0: + if(isReadOperation(operation)) + { + *value = _interruptStatus; + _interruptStatus &= ~0x02; + } + else + { + _interruptControl = *value; + } printf("Interrupt status or control\n"); break; case 0x1: break; case 0x2: + _screenStartAddress = (_screenStartAddress & 0xff00) | ((*value) & 0xe0); + printf("Screen start address low, now %04x\n", _screenStartAddress); + break; case 0x3: - printf("Screen start address\n"); + _screenStartAddress = (_screenStartAddress & 0x00ff) | (uint16_t)(((*value) & 0x3f) << 8); + printf("Screen start address high, now %04x\n", _screenStartAddress); break; case 0x4: printf("Cassette\n"); break; case 0x5: - printf("Interrupt clear and paging\n"); + if(!isReadOperation(operation)) + { + uint8_t nextROM = (*value)&0xf; + if((_activeRom&0x12) != 0x8 || nextROM >= 8) + { + _activeRom = (Electron::ROMSlot)nextROM; + } + printf("Interrupt clear and paging\n"); + } break; case 0x6: printf("Counter\n"); @@ -78,7 +100,21 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { - if(isReadOperation(operation)) *value = basic[address - 32768]; + if(isReadOperation(operation)) + { + switch(_activeRom) + { + case ROMSlotBASIC: + case ROMSlotBASIC+1: + *value = basic[address - 32768]; + break; + case ROMSlotKeyboard: + case ROMSlotKeyboard+1: + *value = 0; + break; + default: break; + } + } } } @@ -90,8 +126,9 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) uint8_t *target = nullptr; switch(slot) { - case ROMTypeBASIC: target = basic; break; - case ROMTypeOS: target = os; break; + case ROMSlotBASIC: target = basic; break; + case ROMSlotOS: target = os; break; + default: return; } memcpy(target, data, std::min((size_t)16384, length)); diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 82ab352e3..9477e381f 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -16,9 +16,16 @@ namespace Electron { -enum ROMSlot: int { - ROMTypeBASIC = 12, - ROMTypeOS = 16, +enum ROMSlot: uint8_t { + ROMSlot0 = 0, ROMSlot1, ROMSlot2, ROMSlot3, + ROMSlot4, ROMSlot5, ROMSlot6, ROMSlot7, + + ROMSlotKeyboard = 8, ROMSlot9, + ROMSlotBASIC = 10, ROMSlot11, + + ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15, + + ROMSlotOS }; class Machine: public CPU6502::Processor { @@ -34,6 +41,9 @@ class Machine: public CPU6502::Processor { private: uint8_t os[16384], basic[16384], ram[32768]; + uint8_t _interruptStatus, _interruptControl; + uint16_t _screenStartAddress; + ROMSlot _activeRom; }; } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 459e5eb00..6f5fbdecf 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -20,11 +20,11 @@ } - (void)setOSROM:(nonnull NSData *)rom { - _electron.set_rom(Electron::ROMTypeOS, rom.length, (const uint8_t *)rom.bytes); + _electron.set_rom(Electron::ROMSlotOS, rom.length, (const uint8_t *)rom.bytes); } - (void)setBASICROM:(nonnull NSData *)rom { - _electron.set_rom(Electron::ROMTypeBASIC, rom.length, (const uint8_t *)rom.bytes); + _electron.set_rom(Electron::ROMSlotBASIC, rom.length, (const uint8_t *)rom.bytes); } @end From 716bb3281b94c0a2987571ae5d345097afe72a26 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 7 Jan 2016 22:26:49 -0500 Subject: [PATCH 011/307] This at least now connects up a CRT, though it never talks to it. --- Machines/Electron/Electron.cpp | 17 ++++++++++------- Machines/Electron/Electron.hpp | 13 +++++++++---- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 4 ++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 2a2cafb60..9a628bf26 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -14,8 +14,9 @@ using namespace Electron; Machine::Machine() { - setup6502(); + _crt = new Outputs::CRT(128, 312, 1, 1); _interruptStatus = 0x02; + setup6502(); } Machine::~Machine() @@ -28,12 +29,14 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { if(isReadOperation(operation)) { - *value = ram[address]; + *value = _ram[address]; } else { - ram[address] = *value; + _ram[address] = *value; } + + // TODO: RAM timing } else { @@ -95,7 +98,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin else { if(isReadOperation(operation)) - *value = os[address - 49152]; + *value = _os[address - 49152]; } } else @@ -106,7 +109,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { case ROMSlotBASIC: case ROMSlotBASIC+1: - *value = basic[address - 32768]; + *value = _basic[address - 32768]; break; case ROMSlotKeyboard: case ROMSlotKeyboard+1: @@ -126,8 +129,8 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) uint8_t *target = nullptr; switch(slot) { - case ROMSlotBASIC: target = basic; break; - case ROMSlotOS: target = os; break; + case ROMSlotBASIC: target = _basic; break; + case ROMSlotOS: target = _os; break; default: return; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 9477e381f..60459f343 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -17,13 +17,14 @@ namespace Electron { enum ROMSlot: uint8_t { - ROMSlot0 = 0, ROMSlot1, ROMSlot2, ROMSlot3, - ROMSlot4, ROMSlot5, ROMSlot6, ROMSlot7, + ROMSlot0 = 0, + ROMSlot1, ROMSlot2, ROMSlot3, + ROMSlot4, ROMSlot5, ROMSlot6, ROMSlot7, ROMSlotKeyboard = 8, ROMSlot9, ROMSlotBASIC = 10, ROMSlot11, - ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15, + ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15, ROMSlotOS }; @@ -39,11 +40,15 @@ class Machine: public CPU6502::Processor { void set_rom(ROMSlot slot, size_t length, const uint8_t *data); + Outputs::CRT *get_crt() { return _crt; } + private: - uint8_t os[16384], basic[16384], ram[32768]; + uint8_t _os[16384], _basic[16384], _ram[32768]; uint8_t _interruptStatus, _interruptControl; uint16_t _screenStartAddress; ROMSlot _activeRom; + + Outputs::CRT *_crt; }; } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 6f5fbdecf..5f72d1145 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -27,4 +27,8 @@ _electron.set_rom(Electron::ROMSlotBASIC, rom.length, (const uint8_t *)rom.bytes); } +- (void)setCRTDelegate:(Outputs::CRT::CRTDelegate *)delegate{ + _electron.get_crt()->set_delegate(delegate); +} + @end From 7341f5c341e39e71efbe6958460d90d1641dd939 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Jan 2016 20:26:40 -0500 Subject: [PATCH 012/307] This is intended to be enough to start producing something of an output. But something's obviously still broken. --- Machines/Electron/Electron.cpp | 132 ++++++++++++++++++++++++++++++--- Machines/Electron/Electron.hpp | 16 +++- Processors/6502/CPU6502.hpp | 10 +++ 3 files changed, 145 insertions(+), 13 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 9a628bf26..bd043717d 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -12,9 +12,17 @@ using namespace Electron; -Machine::Machine() +static const int cycles_per_line = 128; +static const int cycles_per_frame = 312*cycles_per_line; +static const int crt_cycles_multiplier = 8; +static const int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; + +Machine::Machine() : + _interruptControl(0), + _frameCycles(0), + _outputPosition(0) { - _crt = new Outputs::CRT(128, 312, 1, 1); + _crt = new Outputs::CRT(crt_cycles_per_line, 312, 1, 1); _interruptStatus = 0x02; setup6502(); } @@ -25,6 +33,8 @@ Machine::~Machine() unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { + unsigned int cycles = 1; + if(address < 32768) { if(isReadOperation(operation)) @@ -34,9 +44,15 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin else { _ram[address] = *value; + +// TODO: range check on address; a lot of the time the machine will be running code outside of +// the screen area, meaning that no update is required. +// if (address + update_display(); } - // TODO: RAM timing + // TODO: RAM timing for Modes 0–3 + cycles += (_frameCycles&1)^1; } else { @@ -44,7 +60,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { if((address & 0xff00) == 0xfe00) { - printf("%c: %02x: ", isReadOperation(operation) ? 'r' : 'w', *value); +// printf("%c: %02x: ", isReadOperation(operation) ? 'r' : 'w', *value); switch(address&0xf) { @@ -57,18 +73,16 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin else { _interruptControl = *value; + evaluate_interrupts(); } - printf("Interrupt status or control\n"); break; case 0x1: break; case 0x2: - _screenStartAddress = (_screenStartAddress & 0xff00) | ((*value) & 0xe0); - printf("Screen start address low, now %04x\n", _screenStartAddress); + _startScreenAddress = (_startScreenAddress & 0xff00) | ((*value) & 0xe0); break; case 0x3: - _screenStartAddress = (_screenStartAddress & 0x00ff) | (uint16_t)(((*value) & 0x3f) << 8); - printf("Screen start address high, now %04x\n", _screenStartAddress); + _startScreenAddress = (_startScreenAddress & 0x00ff) | (uint16_t)(((*value) & 0x3f) << 8); break; case 0x4: printf("Cassette\n"); @@ -81,7 +95,13 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { _activeRom = (Electron::ROMSlot)nextROM; } - printf("Interrupt clear and paging\n"); + + if( (*value)&0x10 ) _interruptStatus &= ~InterruptDisplayEnd; + if( (*value)&0x20 ) _interruptStatus &= InterruptRealTimeClock; + if( (*value)&0x40 ) _interruptStatus &= InterruptHighToneDetect; + evaluate_interrupts(); + + // TODO: NMI (?) } break; case 0x6: @@ -91,7 +111,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin printf("Misc. control\n"); break; default: - printf("Palette\n"); + update_display(); +// printf("Palette\n"); break; } } @@ -121,7 +142,16 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } } - return 1; + _frameCycles += cycles; + if(_frameCycles == cycles_per_frame) + { + update_display(); + _frameCycles = 0; + } + if(_frameCycles == 128*128) signal_interrupt(InterruptRealTimeClock); + if(_frameCycles == 284*128) signal_interrupt(InterruptDisplayEnd); + + return cycles; } void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) @@ -136,3 +166,81 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) memcpy(target, data, std::min((size_t)16384, length)); } + +inline void Machine::signal_interrupt(Electron::Interrupt interrupt) +{ + _interruptStatus |= (interrupt << 2); + evaluate_interrupts(); +} + +inline void Machine::evaluate_interrupts() +{ + if(_interruptStatus & _interruptControl) + { + _interruptStatus |= 1; + } + set_irq_line(_interruptStatus & 1); +} + +inline void Machine::update_display() +{ + const int end_of_hsync = 3 * cycles_per_line; + + if(_frameCycles >= end_of_hsync) + { + // assert sync for the first three lines of the display + if(_outputPosition < end_of_hsync) + { + _crt->output_sync(end_of_hsync * crt_cycles_multiplier); + _outputPosition = end_of_hsync; + } + + while(_outputPosition < _frameCycles) + { + const int current_line = _outputPosition >> 7; + const int line_position = _outputPosition & 127; + + // all lines then start with 9 cycles of sync + if(!line_position) + { + _crt->output_sync(9 * crt_cycles_multiplier); + _outputPosition += 9; + } + else + { + // on lines prior to 28 or after or equal to 284, or on a line that is equal to 8 or 9 modulo 10 in a line-spaced mode, + // the line is then definitely blank. + if(current_line < 28 || current_line >= 284) + { + if(line_position == 9) + { + _crt->output_blank(119 * crt_cycles_multiplier); + _outputPosition += 119; + } + } + else + { + // there are then 15 cycles of blank, 80 cycles of pixels, and 24 further cycles of blank + if(line_position == 9) + { + _crt->output_blank(15 * crt_cycles_multiplier); + _outputPosition += 15; + _crt->output_data(80 * crt_cycles_multiplier); + } + + if(line_position >= 24 && line_position < 104) + { + // TODO: actually output some pixels, why not? + _outputPosition++; + } + + if(line_position == 104) + { + _crt->output_blank(24 * crt_cycles_multiplier); + _outputPosition += 24; + } + } + } + } + } +} diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 60459f343..68d3eae93 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -29,6 +29,14 @@ enum ROMSlot: uint8_t { ROMSlotOS }; +enum Interrupt: uint8_t { + InterruptRealTimeClock = 0x01, + InterruptDisplayEnd = 0x02, + InterruptTransmitDataEmpty = 0x04, + InterruptReceiveDataFull = 0x08, + InterruptHighToneDetect = 0x10 +}; + class Machine: public CPU6502::Processor { public: @@ -45,10 +53,16 @@ class Machine: public CPU6502::Processor { private: uint8_t _os[16384], _basic[16384], _ram[32768]; uint8_t _interruptStatus, _interruptControl; - uint16_t _screenStartAddress; ROMSlot _activeRom; Outputs::CRT *_crt; + + int _frameCycles, _outputPosition; + uint16_t _startScreenAddress, _currentScreenAddress; + + inline void update_display(); + inline void signal_interrupt(Interrupt interrupt); + inline void evaluate_interrupts(); }; } diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 2411bc30b..f0f41d876 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -981,6 +981,16 @@ template class Processor { _reset_line_is_enabled = active; } + void set_irq_line(bool active) + { + // TODO + } + + void set_nmi_line(bool active) + { + // TODO + } + bool is_jammed() { return _is_jammed; From a900bfed65e96c1edc74a708c698a984bd435770 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Jan 2016 20:34:22 -0500 Subject: [PATCH 013/307] Fixed to ensure that frame rendering wraps around, and to properly connect the Electron to its view. Now I need a working pixel shader. --- Machines/Electron/Electron.cpp | 4 ++-- .../Mac/Clock Signal/Documents/ElectronDocument.swift | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index bd043717d..c436187c6 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -195,7 +195,7 @@ inline void Machine::update_display() _outputPosition = end_of_hsync; } - while(_outputPosition < _frameCycles) + while(_outputPosition >= end_of_hsync && _outputPosition < _frameCycles) { const int current_line = _outputPosition >> 7; const int line_position = _outputPosition & 127; @@ -215,7 +215,7 @@ inline void Machine::update_display() if(line_position == 9) { _crt->output_blank(119 * crt_cycles_multiplier); - _outputPosition += 119; + _outputPosition = (_outputPosition + 119) % cycles_per_frame;; } } else diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 0756920e9..86f06db98 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -23,9 +23,11 @@ class ElectronDocument: MachineDocument { } } -// override func windowControllerDidLoadNib(aController: NSWindowController) { -// super.windowControllerDidLoadNib(aController) -// } + override func windowControllerDidLoadNib(aController: NSWindowController) { + super.windowControllerDidLoadNib(aController) + electron.view = openGLView +// openGLView.frameBounds = CGRectMake(0.1, 0.1, 0.8, 0.8) + } override var windowNibName: String? { return "ElectronDocument" From b4f31edea3059bca3b660518b0f97f3382cba3f0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Jan 2016 21:32:56 -0500 Subject: [PATCH 014/307] Made an attempt to get RGB output mode up and running, and showing at least a box where pixels would be. --- Machines/Electron/Electron.cpp | 9 +++++++++ Machines/Electron/Electron.hpp | 1 + OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m | 8 ++++---- OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm | 5 +++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c436187c6..828700589 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -244,3 +244,12 @@ inline void Machine::update_display() } } } + +const char *Machine::get_signal_decoder() +{ + return + "vec4 sample(vec2 coordinate)\n" + "{\n" + "return vec4(1.0, 1.0, 0.0, 1.0);\n" + "}"; +} diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 68d3eae93..6c683b181 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -49,6 +49,7 @@ class Machine: public CPU6502::Processor { void set_rom(ROMSlot slot, size_t length, const uint8_t *data); Outputs::CRT *get_crt() { return _crt; } + const char *get_signal_decoder(); private: uint8_t _os[16384], _basic[16384], _ram[32768]; diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index bbbaa3628..582f1d008 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -261,6 +261,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)setSignalDecoder:(nonnull NSString *)signalDecoder type:(CSCathodeRayViewSignalType)type { + _signalType = type; _signalDecoder = [signalDecoder copy]; OSAtomicIncrement32(&_signalDecoderGeneration); } @@ -343,6 +344,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "uniform float alpha;\n" "\n" "%@\n" + "%%@\n" "\n" "void main(void)\n" "{\n" @@ -354,9 +356,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "in float phase;\n" "\n" "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" - "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n" - "\n" - "\%@\n"; + "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; NSString *const ntscFragmentShaderBody = @"vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" @@ -379,7 +379,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt @""; NSString *const rgbFragmentShaderBody = - @"fragColour = texture(srcCoordinatesVarying, srcCoordinatesVarying);//sin(lateralVarying));\n"; + @"fragColour = texture(shadowMaskTexID, shadowMaskCoordinates) * sample(srcCoordinatesVarying);//sin(lateralVarying));\n"; switch(_signalType) { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 5f72d1145..c800f98ab 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -31,4 +31,9 @@ _electron.get_crt()->set_delegate(delegate); } +- (void)setView:(CSCathodeRayView *)view { + [super setView:view]; + [view setSignalDecoder:[NSString stringWithUTF8String:_electron.get_signal_decoder()] type:CSCathodeRayViewSignalTypeRGB]; +} + @end From 09df218c01bf2f28eb878fbe5d492735237d0911 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Jan 2016 21:48:53 -0500 Subject: [PATCH 015/307] Some output! Showing that three lines of solid sync isn't smart. But here it is. --- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 582f1d008..f659b2424 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -153,11 +153,17 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)pushSizeUniforms { - NSPoint viewSize = [self backingViewSize]; - glUniform2f(_windowSizeUniform, (GLfloat)viewSize.x, (GLfloat)viewSize.y); + if(_shaderProgram) + { + if(_windowSizeUniform >= 0) + { + NSPoint viewSize = [self backingViewSize]; + glUniform2f(_windowSizeUniform, (GLfloat)viewSize.x, (GLfloat)viewSize.y); + } - glUniform2f(_boundsOriginUniform, (GLfloat)_frameBounds.origin.x, (GLfloat)_frameBounds.origin.y); - glUniform2f(_boundsSizeUniform, (GLfloat)_frameBounds.size.width, (GLfloat)_frameBounds.size.height); + if(_boundsOriginUniform >= 0) glUniform2f(_boundsOriginUniform, (GLfloat)_frameBounds.origin.x, (GLfloat)_frameBounds.origin.y); + if(_boundsSizeUniform >= 0) glUniform2f(_boundsSizeUniform, (GLfloat)_frameBounds.size.width, (GLfloat)_frameBounds.size.height); + } } - (void)awakeFromNib @@ -235,13 +241,18 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt #if defined(DEBUG) - (void)logErrorForObject:(GLuint)object { - GLint logLength; - glGetShaderiv(object, GL_INFO_LOG_LENGTH, &logLength); - if (logLength > 0) { - GLchar *log = (GLchar *)malloc((size_t)logLength); - glGetShaderInfoLog(object, logLength, &logLength, log); - NSLog(@"Compile log:\n%s", log); - free(log); + GLint isCompiled = 0; + glGetShaderiv(object, GL_COMPILE_STATUS, &isCompiled); + if(isCompiled == GL_FALSE) + { + GLint logLength; + glGetShaderiv(object, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc((size_t)logLength); + glGetShaderInfoLog(object, logLength, &logLength, log); + NSLog(@"Compile log:\n%s", log); + free(log); + } } } #endif @@ -289,7 +300,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt @"out vec2 srcCoordinatesVarying;\n"; NSString *const rgbVertexShaderBody = - @"srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n"; + @"srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n"; NSString *const vertexShader = @"#version 150\n" @@ -376,7 +387,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; NSString *const rgbFragmentShaderGlobals = - @""; + @"in vec2 srcCoordinatesVarying;\n"; NSString *const rgbFragmentShaderBody = @"fragColour = texture(shadowMaskTexID, shadowMaskCoordinates) * sample(srcCoordinatesVarying);//sin(lateralVarying));\n"; @@ -415,7 +426,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt glLinkProgram(_shaderProgram); #ifdef DEBUG - [self logErrorForObject:_shaderProgram]; +// [self logErrorForObject:_shaderProgram]; #endif glGenVertexArrays(1, &_vertexArray); @@ -478,7 +489,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt if (_crtFrame) { - glUniform2f(_textureSizeUniform, _crtFrame->size.width, _crtFrame->size.height); + if(_textureSizeUniform >= 0) glUniform2f(_textureSizeUniform, _crtFrame->size.width, _crtFrame->size.height); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)(_crtFrame->number_of_runs*6)); } From 3d6f20b7b9315f9dd7c67a88986cd053c0a97a92 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Jan 2016 21:53:33 -0500 Subject: [PATCH 016/307] Output, at last! Though sync is clearly way off. --- Machines/Electron/Electron.cpp | 9 +++++++-- OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 828700589..fab8dca3e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -188,10 +188,15 @@ inline void Machine::update_display() if(_frameCycles >= end_of_hsync) { - // assert sync for the first three lines of the display + // assert sync for the first three lines of the display, with a break at the end for horizontal alignment if(_outputPosition < end_of_hsync) { - _crt->output_sync(end_of_hsync * crt_cycles_multiplier); + for(int c = 0; c < 3; c++) + { + _crt->output_sync(9 * crt_cycles_multiplier); + _crt->output_blank(9 * crt_cycles_multiplier); + _crt->output_sync(110 * crt_cycles_multiplier); + } _outputPosition = end_of_hsync; } diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index f659b2424..59cad6145 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -387,10 +387,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; NSString *const rgbFragmentShaderGlobals = - @"in vec2 srcCoordinatesVarying;\n"; + @"in vec2 srcCoordinatesVarying;\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * NSString *const rgbFragmentShaderBody = - @"fragColour = texture(shadowMaskTexID, shadowMaskCoordinates) * sample(srcCoordinatesVarying);//sin(lateralVarying));\n"; + @"fragColour = sample(srcCoordinatesVarying);//sin(lateralVarying));\n"; switch(_signalType) { From 07a041d78854291b736d6976e3153adfd84a39aa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Jan 2016 21:54:31 -0500 Subject: [PATCH 017/307] Fixed vertical retrace sync. --- Machines/Electron/Electron.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index fab8dca3e..0c8b0d1c8 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -193,9 +193,8 @@ inline void Machine::update_display() { for(int c = 0; c < 3; c++) { - _crt->output_sync(9 * crt_cycles_multiplier); + _crt->output_sync(119 * crt_cycles_multiplier); _crt->output_blank(9 * crt_cycles_multiplier); - _crt->output_sync(110 * crt_cycles_multiplier); } _outputPosition = end_of_hsync; } From 037602765a5435a6499b0847d16cb173e20c034a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Jan 2016 22:19:00 -0500 Subject: [PATCH 018/307] This now correctly (I think) decodes information posted to the CRT. But doesn't yet post it correctly. I'm very close now, I hope. --- Machines/Electron/Electron.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 0c8b0d1c8..bb078c324 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -230,6 +230,12 @@ inline void Machine::update_display() _crt->output_blank(15 * crt_cycles_multiplier); _outputPosition += 15; _crt->output_data(80 * crt_cycles_multiplier); + + uint8_t *output = (uint8_t *)_crt->get_write_target_for_buffer(0); + for(int c = 0; c < 80 * crt_cycles_multiplier; c++) + { + output[c] = (uint8_t)(c&7); + } } if(line_position >= 24 && line_position < 104) @@ -254,6 +260,7 @@ const char *Machine::get_signal_decoder() return "vec4 sample(vec2 coordinate)\n" "{\n" - "return vec4(1.0, 1.0, 0.0, 1.0);\n" + "float texValue = texture(texID, srcCoordinatesVarying).r;" // step(mod(texValue, 4.0), 2.0) + "return vec4( step(mod(texValue, 8.0/256.0), 4.0/256.0), step(mod(texValue, 4.0/256.0), 2.0/256.0), step(mod(texValue, 2.0/256.0), 1.0/256.0), 1.0);\n" "}"; } From 1308332a71bd14db515473f0cd92092bc97fd946 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Jan 2016 22:39:46 -0500 Subject: [PATCH 019/307] Hard-coded to 40 columns of black and white, here's some text, at least. --- Machines/Electron/Electron.cpp | 38 +++++++++++++++++++++++++++------- Machines/Electron/Electron.hpp | 4 +++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index bb078c324..3292cb8c7 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -229,23 +229,47 @@ inline void Machine::update_display() { _crt->output_blank(15 * crt_cycles_multiplier); _outputPosition += 15; - _crt->output_data(80 * crt_cycles_multiplier); - uint8_t *output = (uint8_t *)_crt->get_write_target_for_buffer(0); - for(int c = 0; c < 80 * crt_cycles_multiplier; c++) - { - output[c] = (uint8_t)(c&7); - } + _crt->allocate_write_area(80 * crt_cycles_multiplier); + _currentLine = (uint8_t *)_crt->get_write_target_for_buffer(0); + + if(current_line == 28) + _startLineAddress = _startScreenAddress; + _currentScreenAddress = _startLineAddress; } if(line_position >= 24 && line_position < 104) { - // TODO: actually output some pixels, why not? + if(_currentLine) + { + if(!(line_position&1)) + { + uint8_t pixels = _ram[_currentScreenAddress]; + _currentScreenAddress += 8; + int output_ptr = (line_position - 24) << 3; + + for(int c = 0; c < 16; c+=2) + { + _currentLine[output_ptr + c] = (pixels&0x80) ? 0 : 7; + _currentLine[output_ptr + c + 1] = (pixels&0x80) ? 0 : 7; + pixels <<= 1; + } + } + } _outputPosition++; } if(line_position == 104) { + if(!((current_line - 27)&7)) + { + _startLineAddress += 40*8 - 7; + } + else + _startLineAddress++; + + _currentLine = nullptr; + _crt->output_data(80 * crt_cycles_multiplier); _crt->output_blank(24 * crt_cycles_multiplier); _outputPosition += 24; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 6c683b181..30b08d8ce 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -54,12 +54,14 @@ class Machine: public CPU6502::Processor { private: uint8_t _os[16384], _basic[16384], _ram[32768]; uint8_t _interruptStatus, _interruptControl; + uint8_t palette[16]; ROMSlot _activeRom; Outputs::CRT *_crt; int _frameCycles, _outputPosition; - uint16_t _startScreenAddress, _currentScreenAddress; + uint16_t _startScreenAddress, _startLineAddress, _currentScreenAddress; + uint8_t *_currentLine; inline void update_display(); inline void signal_interrupt(Interrupt interrupt); From e07981c147b71ddb34ee25fbd236e56a9a7b69a9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 9 Jan 2016 22:52:08 -0500 Subject: [PATCH 020/307] Fixed screen start address. --- Machines/Electron/Electron.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 3292cb8c7..32d45f8db 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -79,10 +79,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x1: break; case 0x2: - _startScreenAddress = (_startScreenAddress & 0xff00) | ((*value) & 0xe0); + _startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); break; case 0x3: - _startScreenAddress = (_startScreenAddress & 0x00ff) | (uint16_t)(((*value) & 0x3f) << 8); + _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); break; case 0x4: printf("Cassette\n"); @@ -250,8 +250,7 @@ inline void Machine::update_display() for(int c = 0; c < 16; c+=2) { - _currentLine[output_ptr + c] = (pixels&0x80) ? 0 : 7; - _currentLine[output_ptr + c + 1] = (pixels&0x80) ? 0 : 7; + _currentLine[output_ptr + c] = _currentLine[output_ptr + c + 1] = (pixels&0x80) ? 0 : 7; pixels <<= 1; } } From d95414b2eb4bac8ac66067cefd0918bb4d6a8aa5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jan 2016 12:30:24 -0500 Subject: [PATCH 021/307] I'm not really sure what's going wrong with paging yet but this fixes the 0xc000 byte error. --- Machines/Electron/Electron.cpp | 48 +++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 32d45f8db..3d3b2eacf 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -35,7 +35,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { unsigned int cycles = 1; - if(address < 32768) + if(address < 0x8000) { if(isReadOperation(operation)) { @@ -56,7 +56,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { - if(address > 49152) + if(address >= 0xc000) { if((address & 0xff00) == 0xfe00) { @@ -90,18 +90,34 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x5: if(!isReadOperation(operation)) { - uint8_t nextROM = (*value)&0xf; - if((_activeRom&0x12) != 0x8 || nextROM >= 8) + const uint8_t interruptDisable = (*value)&0xf0; + if( interruptDisable ) { - _activeRom = (Electron::ROMSlot)nextROM; + if( interruptDisable&0x10 ) _interruptStatus &= ~InterruptDisplayEnd; + if( interruptDisable&0x20 ) _interruptStatus &= ~InterruptRealTimeClock; + if( interruptDisable&0x40 ) _interruptStatus &= ~InterruptHighToneDetect; + evaluate_interrupts(); + // TODO: NMI (?) } +// else + { + uint8_t nextROM = (*value)&0xf; - if( (*value)&0x10 ) _interruptStatus &= ~InterruptDisplayEnd; - if( (*value)&0x20 ) _interruptStatus &= InterruptRealTimeClock; - if( (*value)&0x40 ) _interruptStatus &= InterruptHighToneDetect; - evaluate_interrupts(); - - // TODO: NMI (?) +// if(nextROM&0x08) +// { +// _activeRom = (Electron::ROMSlot)(nextROM&0x0e); +// printf("%d -> Paged %d\n", nextROM, _activeRom); +// } + if((_activeRom&12) != 8 || nextROM&8) + { + _activeRom = (Electron::ROMSlot)nextROM; + } +// else +// { +// printf("Ignored!"); +// } +// printf("%d -> Paged %d\n", nextROM, _activeRom); + } } break; case 0x6: @@ -119,7 +135,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin else { if(isReadOperation(operation)) - *value = _os[address - 49152]; + *value = _os[address & 16383]; } } else @@ -130,13 +146,15 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { case ROMSlotBASIC: case ROMSlotBASIC+1: - *value = _basic[address - 32768]; + *value = _basic[address & 16383]; break; case ROMSlotKeyboard: case ROMSlotKeyboard+1: - *value = 0; + *value = 0xf0; + break; + default: + *value = 0xff; break; - default: break; } } } From cc5ba8243e7f1a4ea678e229ac527e6883d7ba92 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jan 2016 17:17:39 -0500 Subject: [PATCH 022/307] Fixed: turned out the power-on bit was being cleared. --- Machines/Electron/Electron.cpp | 4 ++-- Machines/Electron/Electron.hpp | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 3d3b2eacf..29c932e4d 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -108,7 +108,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // _activeRom = (Electron::ROMSlot)(nextROM&0x0e); // printf("%d -> Paged %d\n", nextROM, _activeRom); // } - if((_activeRom&12) != 8 || nextROM&8) + if(((_activeRom&12) != 8) || (nextROM&8)) { _activeRom = (Electron::ROMSlot)nextROM; } @@ -187,7 +187,7 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) inline void Machine::signal_interrupt(Electron::Interrupt interrupt) { - _interruptStatus |= (interrupt << 2); + _interruptStatus |= interrupt; evaluate_interrupts(); } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 30b08d8ce..83eaf070c 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -30,11 +30,11 @@ enum ROMSlot: uint8_t { }; enum Interrupt: uint8_t { - InterruptRealTimeClock = 0x01, - InterruptDisplayEnd = 0x02, - InterruptTransmitDataEmpty = 0x04, - InterruptReceiveDataFull = 0x08, - InterruptHighToneDetect = 0x10 + InterruptRealTimeClock = 0x04, + InterruptDisplayEnd = 0x08, + InterruptTransmitDataEmpty = 0x10, + InterruptReceiveDataFull = 0x20, + InterruptHighToneDetect = 0x40 }; class Machine: public CPU6502::Processor { From ccf20299a3a3faea19fe48afcf6bbbd853534322 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jan 2016 19:06:46 -0500 Subject: [PATCH 023/307] Made an attempt at getting some interrupts all up inside this thing. --- Machines/Electron/Electron.cpp | 17 +++++++++++++++-- Machines/Electron/Electron.hpp | 4 ++-- Processors/6502/CPU6502.hpp | 33 +++++++++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 29c932e4d..deb49ca7e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -160,6 +160,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } } +// if(operation == CPU6502::BusOperation::ReadOpcode) +// { +// printf("%04x: %02x (%d)\n", address, *value, _frameCycles); +// } + _frameCycles += cycles; if(_frameCycles == cycles_per_frame) { @@ -197,6 +202,10 @@ inline void Machine::evaluate_interrupts() { _interruptStatus |= 1; } + else + { + _interruptStatus &= ~1; + } set_irq_line(_interruptStatus & 1); } @@ -232,7 +241,10 @@ inline void Machine::update_display() { // on lines prior to 28 or after or equal to 284, or on a line that is equal to 8 or 9 modulo 10 in a line-spaced mode, // the line is then definitely blank. - if(current_line < 28 || current_line >= 284) + if( + (current_line < 28 || current_line >= 284) +// || (((current_line - 28)%10) > 7) + ) { if(line_position == 9) { @@ -278,7 +290,8 @@ inline void Machine::update_display() if(line_position == 104) { - if(!((current_line - 27)&7)) + if(!((current_line - 27)%8)) +// if(!((current_line - 27)&7)) { _startLineAddress += 40*8 - 7; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 83eaf070c..52acfaadc 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -30,8 +30,8 @@ enum ROMSlot: uint8_t { }; enum Interrupt: uint8_t { - InterruptRealTimeClock = 0x04, - InterruptDisplayEnd = 0x08, + InterruptDisplayEnd = 0x04, + InterruptRealTimeClock = 0x08, InterruptTransmitDataEmpty = 0x10, InterruptReceiveDataFull = 0x20, InterruptHighToneDetect = 0x40 diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index f0f41d876..c2e0cdda8 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -58,7 +58,8 @@ template class Processor { enum MicroOp { CycleFetchOperation, CycleFetchOperand, OperationDecodeOperation, CycleIncPCPushPCH, CyclePushPCH, CyclePushPCL, CyclePushA, CyclePushOperand, - CycleSetIReadBRKLow, CycleReadBRKHigh, CycleReadFromS, CycleReadFromPC, + CycleSetIReadBRKLow, CycleReadBRKHigh, + CycleReadFromS, CycleReadFromPC, CyclePullOperand, CyclePullPCL, CyclePullPCH, CyclePullA, CycleReadAndIncrementPC, CycleIncrementPCAndReadStack, CycleIncrementPCReadPCHLoadPCL, CycleReadPCHLoadPCL, CycleReadAddressHLoadAddressL, CycleReadPCLFromAddress, CycleReadPCHFromAddress, CycleLoadAddressAbsolute, @@ -84,6 +85,7 @@ template class Processor { OperationTAY, OperationTAX, OperationTSX, OperationARR, OperationSBX, OperationLXA, OperationANE, OperationANC, OperationLAS, CycleAddSignedOperandToPC, OperationSetFlagsFromOperand, OperationSetOperandFromFlagsWithBRKSet, + OperationSetOperandFromFlags, OperationSetFlagsFromA, CycleReadRSTLow, CycleReadRSTHigh, CycleScheduleJam }; @@ -378,6 +380,8 @@ template class Processor { bool _ready_line_is_enabled; bool _reset_line_is_enabled; + bool _irq_line_is_enabled, _irq_line_history[2]; + bool _nmi_line_is_enabled; bool _ready_is_active; public: @@ -406,6 +410,19 @@ template class Processor { return reset; } + const MicroOp *get_irq_program() { + static const MicroOp reset[] = { + CyclePushPCH, + CyclePushPCL, + OperationSetOperandFromFlags, + CyclePushOperand, + CycleSetIReadBRKLow, + CycleReadBRKHigh, + OperationMoveToNextProgram + }; + return reset; + } + void run_for_cycles(int number_of_cycles) { static const MicroOp doBranch[] = { @@ -428,7 +445,12 @@ template class Processor { if(_reset_line_is_enabled)\ schedule_program(get_reset_program());\ else\ - schedule_program(fetch_decode_execute);\ + {\ + if(_irq_line_history[1] && !_interruptFlag)\ + schedule_program(get_irq_program());\ + else\ + schedule_program(fetch_decode_execute);\ + }\ op;\ } @@ -444,6 +466,8 @@ template class Processor { while (!_ready_is_active && _cycles_left_to_run > 0) { if (_nextBusOperation != BusOperation::None) { + _irq_line_history[0] = _irq_line_history[1]; + _irq_line_history[1] = _irq_line_is_enabled; _cycles_left_to_run -= static_cast(this)->perform_bus_operation(_nextBusOperation, _busAddress, _busValue); _nextBusOperation = BusOperation::None; } @@ -520,6 +544,7 @@ template class Processor { case CyclePullOperand: _s++; read_mem(_operand, _s | 0x100); break; case OperationSetFlagsFromOperand: set_flags(_operand); break; case OperationSetOperandFromFlagsWithBRKSet: _operand = get_flags() | Flag::Break; break; + case OperationSetOperandFromFlags: _operand = get_flags(); break; case OperationSetFlagsFromA: _zeroResult = _negativeResult = _a; break; case CycleIncrementPCAndReadStack: _pc.full++; throwaway_read(_s | 0x100); break; @@ -983,12 +1008,12 @@ template class Processor { void set_irq_line(bool active) { - // TODO + _irq_line_is_enabled = active; } void set_nmi_line(bool active) { - // TODO + _nmi_line_is_enabled = active; } bool is_jammed() From 93f7df04a0bd1fed738b42c3b2f51d3224043cbb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jan 2016 22:55:56 -0500 Subject: [PATCH 024/307] Ensured correctly timed sampling of the interrupt line. --- Processors/6502/CPU6502.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index c2e0cdda8..b263063c3 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -380,7 +380,7 @@ template class Processor { bool _ready_line_is_enabled; bool _reset_line_is_enabled; - bool _irq_line_is_enabled, _irq_line_history[2]; + bool _irq_line_is_enabled, _irq_request_history[2]; bool _nmi_line_is_enabled; bool _ready_is_active; @@ -446,7 +446,7 @@ template class Processor { schedule_program(get_reset_program());\ else\ {\ - if(_irq_line_history[1] && !_interruptFlag)\ + if(_irq_request_history[0])\ schedule_program(get_irq_program());\ else\ schedule_program(fetch_decode_execute);\ @@ -466,8 +466,8 @@ template class Processor { while (!_ready_is_active && _cycles_left_to_run > 0) { if (_nextBusOperation != BusOperation::None) { - _irq_line_history[0] = _irq_line_history[1]; - _irq_line_history[1] = _irq_line_is_enabled; + _irq_request_history[0] = _irq_request_history[1]; + _irq_request_history[1] = _irq_line_is_enabled && !_interruptFlag; _cycles_left_to_run -= static_cast(this)->perform_bus_operation(_nextBusOperation, _busAddress, _busValue); _nextBusOperation = BusOperation::None; } From ce916ebd6a4a0b6b9dc2adbd9a093b2de1f6ff9d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jan 2016 23:32:57 -0500 Subject: [PATCH 025/307] Fixed runaway frame generator. --- Machines/Electron/Electron.cpp | 8 ++++---- OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index deb49ca7e..58d0aaa7b 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -170,6 +170,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { update_display(); _frameCycles = 0; + _outputPosition = 0; } if(_frameCycles == 128*128) signal_interrupt(InterruptRealTimeClock); if(_frameCycles == 284*128) signal_interrupt(InterruptDisplayEnd); @@ -249,7 +250,7 @@ inline void Machine::update_display() if(line_position == 9) { _crt->output_blank(119 * crt_cycles_multiplier); - _outputPosition = (_outputPosition + 119) % cycles_per_frame;; + _outputPosition += 119; } } else @@ -290,8 +291,7 @@ inline void Machine::update_display() if(line_position == 104) { - if(!((current_line - 27)%8)) -// if(!((current_line - 27)&7)) + if(!((current_line - 27)&7)) { _startLineAddress += 40*8 - 7; } @@ -314,7 +314,7 @@ const char *Machine::get_signal_decoder() return "vec4 sample(vec2 coordinate)\n" "{\n" - "float texValue = texture(texID, srcCoordinatesVarying).r;" // step(mod(texValue, 4.0), 2.0) + "float texValue = texture(texID, srcCoordinatesVarying).r;\n" "return vec4( step(mod(texValue, 8.0/256.0), 4.0/256.0), step(mod(texValue, 4.0/256.0), 2.0/256.0), step(mod(texValue, 2.0/256.0), 1.0/256.0), 1.0);\n" "}"; } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index 696c78438..cb5a5f3f8 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -26,6 +26,9 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { dispatch_queue_t _serialDispatchQueue; NSConditionLock *_runningLock; + + int _frameCount; + NSTimeInterval _firstFrame; } - (void)perform:(dispatch_block_t)action { @@ -34,6 +37,17 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { - (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync { if([self.view pushFrame:frame]) crt->return_frame(); + + if(!_frameCount) _firstFrame = [NSDate timeIntervalSinceReferenceDate]; + _frameCount++; + +// NSLog(@"!-!"); + + if(!(_frameCount%50)) + { + NSTimeInterval timeSinceFirstFrame = [NSDate timeIntervalSinceReferenceDate] - _firstFrame; + NSLog(@"%d in %0.2f: %0.2f", _frameCount, timeSinceFirstFrame, (double)_frameCount / timeSinceFirstFrame); + } } - (void)runForNumberOfCycles:(int)cycles { From e93dbdb4636e2adf88d1e16eca689c510c284345 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Jan 2016 19:48:31 -0500 Subject: [PATCH 026/307] Implemented keyboard input. --- Machines/Electron/Electron.cpp | 20 +++ Machines/Electron/Electron.hpp | 76 ++++++++- .../Clock Signal.xcodeproj/project.pbxproj | 2 + .../Documents/ElectronDocument.swift | 16 +- .../Mac/Clock Signal/Wrappers/CSElectron.h | 3 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 74 +++++++++ .../Mac/Clock Signal/Wrappers/CSMachine.mm | 14 -- .../Mac/Clock Signal/Wrappers/KeyCodes.h | 146 ++++++++++++++++++ 8 files changed, 333 insertions(+), 18 deletions(-) create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/KeyCodes.h diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 58d0aaa7b..4c2fb0b14 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -22,6 +22,7 @@ Machine::Machine() : _frameCycles(0), _outputPosition(0) { + memset(_keyStates, 0, sizeof(_keyStates)); _crt = new Outputs::CRT(crt_cycles_per_line, 312, 1, 1); _interruptStatus = 0x02; setup6502(); @@ -151,6 +152,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case ROMSlotKeyboard: case ROMSlotKeyboard+1: *value = 0xf0; + for(int address_line = 0; address_line < 14; address_line++) + { + if(!(address&(1 << address_line))) *value |= _keyStates[address_line]; + } break; default: *value = 0xff; @@ -318,3 +323,18 @@ const char *Machine::get_signal_decoder() "return vec4( step(mod(texValue, 8.0/256.0), 4.0/256.0), step(mod(texValue, 4.0/256.0), 2.0/256.0), step(mod(texValue, 2.0/256.0), 1.0/256.0), 1.0);\n" "}"; } + +void Machine::set_key_state(Key key, bool isPressed) +{ + if(key == KeyBreak) + { + set_reset_line(isPressed); + } + else + { + if(isPressed) + _keyStates[key >> 4] |= key&0xf; + else + _keyStates[key >> 4] &= ~(key&0xf); + } +} diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 52acfaadc..ac9b64b65 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -37,6 +37,78 @@ enum Interrupt: uint8_t { InterruptHighToneDetect = 0x40 }; +enum Key: uint16_t { + KeySpace = 0x0000 | 0x08, + KeyCopy = 0x0000 | 0x02, + KeyRight = 0x0000 | 0x01, + + KeyDelete = 0x0010 | 0x08, + KeyReturn = 0x0010 | 0x04, + KeyDown = 0x0010 | 0x02, + KeyLeft = 0x0010 | 0x01, + + KeyColon = 0x0020 | 0x04, + KeyUp = 0x0020 | 0x02, + KeyMinus = 0x0020 | 0x01, + + KeySlash = 0x0030 | 0x08, + KeySemiColon = 0x0030 | 0x04, + KeyP = 0x0030 | 0x02, + Key0 = 0x0030 | 0x01, + + KeyFullStop = 0x0040 | 0x08, + KeyL = 0x0040 | 0x04, + KeyO = 0x0040 | 0x02, + Key9 = 0x0040 | 0x01, + + KeyComma = 0x0050 | 0x08, + KeyK = 0x0050 | 0x04, + KeyI = 0x0050 | 0x02, + Key8 = 0x0050 | 0x01, + + KeyM = 0x0060 | 0x08, + KeyJ = 0x0060 | 0x04, + KeyU = 0x0060 | 0x02, + Key7 = 0x0060 | 0x01, + + KeyN = 0x0070 | 0x08, + KeyH = 0x0070 | 0x04, + KeyY = 0x0070 | 0x02, + Key6 = 0x0070 | 0x01, + + KeyB = 0x0080 | 0x08, + KeyG = 0x0080 | 0x04, + KeyT = 0x0080 | 0x02, + Key5 = 0x0080 | 0x01, + + KeyV = 0x0090 | 0x08, + KeyF = 0x0090 | 0x04, + KeyR = 0x0090 | 0x02, + Key4 = 0x0090 | 0x01, + + KeyC = 0x00a0 | 0x08, + KeyD = 0x00a0 | 0x04, + KeyE = 0x00a0 | 0x02, + Key3 = 0x00a0 | 0x01, + + KeyX = 0x00b0 | 0x08, + KeyS = 0x00b0 | 0x04, + KeyW = 0x00b0 | 0x02, + Key2 = 0x00b0 | 0x01, + + KeyZ = 0x00c0 | 0x08, + KeyA = 0x00c0 | 0x04, + KeyQ = 0x00c0 | 0x02, + Key1 = 0x00c0 | 0x01, + + KeyShift = 0x00d0 | 0x08, + KeyControl = 0x00d0 | 0x04, + KeyFunc = 0x00d0 | 0x02, + KeyEscape = 0x00d0 | 0x01, + + KeyBreak = 0xffff +}; + class Machine: public CPU6502::Processor { public: @@ -47,6 +119,7 @@ class Machine: public CPU6502::Processor { unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); void set_rom(ROMSlot slot, size_t length, const uint8_t *data); + void set_key_state(Key key, bool isPressed); Outputs::CRT *get_crt() { return _crt; } const char *get_signal_decoder(); @@ -54,7 +127,8 @@ class Machine: public CPU6502::Processor { private: uint8_t _os[16384], _basic[16384], _ram[32768]; uint8_t _interruptStatus, _interruptControl; - uint8_t palette[16]; + uint8_t _palette[16]; + uint8_t _keyStates[14]; ROMSlot _activeRom; Outputs::CRT *_crt; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a6e376595..9f1210530 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -349,6 +349,7 @@ 4B55CE5C1C3B7D6F0093A61B /* CSCathodeRayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSCathodeRayView.m; sourceTree = ""; }; 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = ""; }; + 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = ""; }; 4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = ""; }; 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = 6502_functional_test.bin; sourceTree = ""; }; 4BB297E11B587D8300A49093 /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = AllSuiteA.bin; sourceTree = ""; }; @@ -721,6 +722,7 @@ 4B55CE4F1C3B78A80093A61B /* CSMachine+Subclassing.h */, 4B55CE521C3B7ABF0093A61B /* CSElectron.h */, 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */, + 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */, ); path = Wrappers; sourceTree = ""; diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 86f06db98..f5a890a60 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -42,8 +42,18 @@ class ElectronDocument: MachineDocument { } // MARK: CSOpenGLViewResponderDelegate -// func keyDown(event: NSEvent) {} -// func keyUp(event: NSEvent) {} -// func flagsChanged(newModifiers: NSEvent) {} + override func keyDown(event: NSEvent) { + electron.setKey(event.keyCode, isPressed: true) + } + + override func keyUp(event: NSEvent) { + electron.setKey(event.keyCode, isPressed: false) + } + + override func flagsChanged(newModifiers: NSEvent) { + electron.setKey(kVK_Shift, isPressed: newModifiers.modifierFlags.contains(.ShiftKeyMask)) + electron.setKey(kVK_Control, isPressed: newModifiers.modifierFlags.contains(.ControlKeyMask)) + electron.setKey(kVK_Command, isPressed: newModifiers.modifierFlags.contains(.CommandKeyMask)) + } } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index 1826efd24..b8af2bec4 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -7,10 +7,13 @@ // #include "CSMachine.h" +#import "KeyCodes.h" @interface CSElectron : CSMachine - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; +- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index c800f98ab..5df5b09a2 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -36,4 +36,78 @@ [view setSignalDecoder:[NSString stringWithUTF8String:_electron.get_signal_decoder()] type:CSCathodeRayViewSignalTypeRGB]; } +- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { + switch(key) + { + case kVK_ANSI_0: _electron.set_key_state(Electron::Key::Key0, isPressed); break; + case kVK_ANSI_1: _electron.set_key_state(Electron::Key::Key1, isPressed); break; + case kVK_ANSI_2: _electron.set_key_state(Electron::Key::Key2, isPressed); break; + case kVK_ANSI_3: _electron.set_key_state(Electron::Key::Key3, isPressed); break; + case kVK_ANSI_4: _electron.set_key_state(Electron::Key::Key4, isPressed); break; + case kVK_ANSI_5: _electron.set_key_state(Electron::Key::Key5, isPressed); break; + case kVK_ANSI_6: _electron.set_key_state(Electron::Key::Key6, isPressed); break; + case kVK_ANSI_7: _electron.set_key_state(Electron::Key::Key7, isPressed); break; + case kVK_ANSI_8: _electron.set_key_state(Electron::Key::Key8, isPressed); break; + case kVK_ANSI_9: _electron.set_key_state(Electron::Key::Key9, isPressed); break; + + case kVK_ANSI_Q: _electron.set_key_state(Electron::Key::KeyQ, isPressed); break; + case kVK_ANSI_W: _electron.set_key_state(Electron::Key::KeyW, isPressed); break; + case kVK_ANSI_E: _electron.set_key_state(Electron::Key::KeyE, isPressed); break; + case kVK_ANSI_R: _electron.set_key_state(Electron::Key::KeyR, isPressed); break; + case kVK_ANSI_T: _electron.set_key_state(Electron::Key::KeyT, isPressed); break; + case kVK_ANSI_Y: _electron.set_key_state(Electron::Key::KeyY, isPressed); break; + case kVK_ANSI_U: _electron.set_key_state(Electron::Key::KeyU, isPressed); break; + case kVK_ANSI_I: _electron.set_key_state(Electron::Key::KeyI, isPressed); break; + case kVK_ANSI_O: _electron.set_key_state(Electron::Key::KeyO, isPressed); break; + case kVK_ANSI_P: _electron.set_key_state(Electron::Key::KeyP, isPressed); break; + case kVK_ANSI_A: _electron.set_key_state(Electron::Key::KeyA, isPressed); break; + case kVK_ANSI_S: _electron.set_key_state(Electron::Key::KeyS, isPressed); break; + case kVK_ANSI_D: _electron.set_key_state(Electron::Key::KeyD, isPressed); break; + case kVK_ANSI_F: _electron.set_key_state(Electron::Key::KeyF, isPressed); break; + case kVK_ANSI_G: _electron.set_key_state(Electron::Key::KeyG, isPressed); break; + case kVK_ANSI_H: _electron.set_key_state(Electron::Key::KeyH, isPressed); break; + case kVK_ANSI_J: _electron.set_key_state(Electron::Key::KeyJ, isPressed); break; + case kVK_ANSI_K: _electron.set_key_state(Electron::Key::KeyK, isPressed); break; + case kVK_ANSI_L: _electron.set_key_state(Electron::Key::KeyL, isPressed); break; + case kVK_ANSI_Z: _electron.set_key_state(Electron::Key::KeyZ, isPressed); break; + case kVK_ANSI_X: _electron.set_key_state(Electron::Key::KeyX, isPressed); break; + case kVK_ANSI_C: _electron.set_key_state(Electron::Key::KeyC, isPressed); break; + case kVK_ANSI_V: _electron.set_key_state(Electron::Key::KeyV, isPressed); break; + case kVK_ANSI_B: _electron.set_key_state(Electron::Key::KeyB, isPressed); break; + case kVK_ANSI_N: _electron.set_key_state(Electron::Key::KeyN, isPressed); break; + case kVK_ANSI_M: _electron.set_key_state(Electron::Key::KeyM, isPressed); break; + + case kVK_Space: _electron.set_key_state(Electron::Key::KeySpace, isPressed); break; + case kVK_ANSI_Grave: + case kVK_ANSI_Backslash: + _electron.set_key_state(Electron::Key::KeyCopy, isPressed); break; + case kVK_Return: _electron.set_key_state(Electron::Key::KeyReturn, isPressed); break; + case kVK_ANSI_Minus: _electron.set_key_state(Electron::Key::KeyMinus, isPressed); break; + + case kVK_RightArrow: _electron.set_key_state(Electron::Key::KeyRight, isPressed); break; + case kVK_LeftArrow: _electron.set_key_state(Electron::Key::KeyLeft, isPressed); break; + case kVK_DownArrow: _electron.set_key_state(Electron::Key::KeyDown, isPressed); break; + case kVK_UpArrow: _electron.set_key_state(Electron::Key::KeyUp, isPressed); break; + + case kVK_Delete: _electron.set_key_state(Electron::Key::KeyDelete, isPressed); break; + case kVK_Escape: _electron.set_key_state(Electron::Key::KeyEscape, isPressed); break; + + case kVK_ANSI_Comma: _electron.set_key_state(Electron::Key::KeyComma, isPressed); break; + case kVK_ANSI_Period: _electron.set_key_state(Electron::Key::KeyFullStop, isPressed); break; + + case kVK_ANSI_Semicolon: + _electron.set_key_state(Electron::Key::KeySemiColon, isPressed); break; + case kVK_ANSI_Quote: _electron.set_key_state(Electron::Key::KeyColon, isPressed); break; + + case kVK_ANSI_Slash: _electron.set_key_state(Electron::Key::KeySlash, isPressed); break; + + case kVK_Shift: _electron.set_key_state(Electron::Key::KeyShift, isPressed); break; + case kVK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break; + case kVK_Command: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break; + + default: + break; + } +} + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index cb5a5f3f8..696c78438 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -26,9 +26,6 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { dispatch_queue_t _serialDispatchQueue; NSConditionLock *_runningLock; - - int _frameCount; - NSTimeInterval _firstFrame; } - (void)perform:(dispatch_block_t)action { @@ -37,17 +34,6 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { - (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync { if([self.view pushFrame:frame]) crt->return_frame(); - - if(!_frameCount) _firstFrame = [NSDate timeIntervalSinceReferenceDate]; - _frameCount++; - -// NSLog(@"!-!"); - - if(!(_frameCount%50)) - { - NSTimeInterval timeSinceFirstFrame = [NSDate timeIntervalSinceReferenceDate] - _firstFrame; - NSLog(@"%d in %0.2f: %0.2f", _frameCount, timeSinceFirstFrame, (double)_frameCount / timeSinceFirstFrame); - } } - (void)runForNumberOfCycles:(int)cycles { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/KeyCodes.h b/OSBindings/Mac/Clock Signal/Wrappers/KeyCodes.h new file mode 100644 index 000000000..bc30eaaf2 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/KeyCodes.h @@ -0,0 +1,146 @@ +// +// KeyCodes.h +// Clock Signal +// +// Emancipated from Carbon's HIToolbox by Thomas Harte on 11/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef KeyCodes_h +#define KeyCodes_h + +/* + * Summary: + * Virtual keycodes + * + * Discussion: + * These constants are the virtual keycodes defined originally in + * Inside Mac Volume V, pg. V-191. They identify physical keys on a + * keyboard. Those constants with "ANSI" in the name are labeled + * according to the key position on an ANSI-standard US keyboard. + * For example, kVK_ANSI_A indicates the virtual keycode for the key + * with the letter 'A' in the US keyboard layout. Other keyboard + * layouts may have the 'A' key label on a different physical key; + * in this case, pressing 'A' will generate a different virtual + * keycode. + */ +enum: uint16_t { + kVK_ANSI_A = 0x00, + kVK_ANSI_S = 0x01, + kVK_ANSI_D = 0x02, + kVK_ANSI_F = 0x03, + kVK_ANSI_H = 0x04, + kVK_ANSI_G = 0x05, + kVK_ANSI_Z = 0x06, + kVK_ANSI_X = 0x07, + kVK_ANSI_C = 0x08, + kVK_ANSI_V = 0x09, + kVK_ANSI_B = 0x0B, + kVK_ANSI_Q = 0x0C, + kVK_ANSI_W = 0x0D, + kVK_ANSI_E = 0x0E, + kVK_ANSI_R = 0x0F, + kVK_ANSI_Y = 0x10, + kVK_ANSI_T = 0x11, + kVK_ANSI_1 = 0x12, + kVK_ANSI_2 = 0x13, + kVK_ANSI_3 = 0x14, + kVK_ANSI_4 = 0x15, + kVK_ANSI_6 = 0x16, + kVK_ANSI_5 = 0x17, + kVK_ANSI_Equal = 0x18, + kVK_ANSI_9 = 0x19, + kVK_ANSI_7 = 0x1A, + kVK_ANSI_Minus = 0x1B, + kVK_ANSI_8 = 0x1C, + kVK_ANSI_0 = 0x1D, + kVK_ANSI_RightBracket = 0x1E, + kVK_ANSI_O = 0x1F, + kVK_ANSI_U = 0x20, + kVK_ANSI_LeftBracket = 0x21, + kVK_ANSI_I = 0x22, + kVK_ANSI_P = 0x23, + kVK_ANSI_L = 0x25, + kVK_ANSI_J = 0x26, + kVK_ANSI_Quote = 0x27, + kVK_ANSI_K = 0x28, + kVK_ANSI_Semicolon = 0x29, + kVK_ANSI_Backslash = 0x2A, + kVK_ANSI_Comma = 0x2B, + kVK_ANSI_Slash = 0x2C, + kVK_ANSI_N = 0x2D, + kVK_ANSI_M = 0x2E, + kVK_ANSI_Period = 0x2F, + kVK_ANSI_Grave = 0x32, + kVK_ANSI_KeypadDecimal = 0x41, + kVK_ANSI_KeypadMultiply = 0x43, + kVK_ANSI_KeypadPlus = 0x45, + kVK_ANSI_KeypadClear = 0x47, + kVK_ANSI_KeypadDivide = 0x4B, + kVK_ANSI_KeypadEnter = 0x4C, + kVK_ANSI_KeypadMinus = 0x4E, + kVK_ANSI_KeypadEquals = 0x51, + kVK_ANSI_Keypad0 = 0x52, + kVK_ANSI_Keypad1 = 0x53, + kVK_ANSI_Keypad2 = 0x54, + kVK_ANSI_Keypad3 = 0x55, + kVK_ANSI_Keypad4 = 0x56, + kVK_ANSI_Keypad5 = 0x57, + kVK_ANSI_Keypad6 = 0x58, + kVK_ANSI_Keypad7 = 0x59, + kVK_ANSI_Keypad8 = 0x5B, + kVK_ANSI_Keypad9 = 0x5C +}; + +/* keycodes for keys that are independent of keyboard layout*/ +enum: uint16_t { + kVK_Return = 0x24, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Control = 0x3B, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_F20 = 0x5A, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_F11 = 0x67, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E +}; + +#endif /* KeyCodes_h */ From 72019d0ea3e8b702437927f1e4b68590164478fe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Jan 2016 22:08:30 -0500 Subject: [PATCH 027/307] Made an attempt to get video output correct. --- Machines/Electron/Electron.cpp | 141 ++++++++++++++++++++++++++++----- Machines/Electron/Electron.hpp | 3 + 2 files changed, 123 insertions(+), 21 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 4c2fb0b14..0caccc2a7 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -20,9 +20,11 @@ static const int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; Machine::Machine() : _interruptControl(0), _frameCycles(0), - _outputPosition(0) + _outputPosition(0), + _currentOutputLine(0) { memset(_keyStates, 0, sizeof(_keyStates)); + memset(_palette, 0xf, sizeof(_palette)); _crt = new Outputs::CRT(crt_cycles_per_line, 312, 1, 1); _interruptStatus = 0x02; setup6502(); @@ -125,11 +127,56 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin printf("Counter\n"); break; case 0x7: - printf("Misc. control\n"); + if(!isReadOperation(operation)) + { + _screenMode = ((*value) >> 3)&7; + if(_screenMode == 7) _screenMode = 4; + switch(_screenMode) + { + case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; + case 3: _screenModeBaseAddress = 0x4000; break; + case 4: case 5: _screenModeBaseAddress = 0x5800; break; + case 6: _screenModeBaseAddress = 0x6000; break; + } + printf("Misc. control\n"); + } break; default: - update_display(); -// printf("Palette\n"); + { + if(!isReadOperation(operation)) + { + update_display(); + + static const int registers[4][4] = { + {10, 8, 2, 0}, + {14, 12, 6, 4}, + {15, 13, 7, 5}, + {11, 9, 3, 1}, + }; + const int index = (address >> 1)&3; + const uint8_t colour = ~(*value); + if(address&1) + { + _palette[registers[index][0]] = (_palette[registers[index][0]]&3) | ((colour >> 1)&4); + _palette[registers[index][1]] = (_palette[registers[index][1]]&3) | ((colour >> 0)&4); + _palette[registers[index][2]] = (_palette[registers[index][2]]&3) | ((colour << 1)&4); + _palette[registers[index][3]] = (_palette[registers[index][3]]&3) | ((colour << 2)&4); + + _palette[registers[index][2]] = (_palette[registers[index][2]]&5) | ((colour >> 4)&2); + _palette[registers[index][3]] = (_palette[registers[index][3]]&5) | ((colour >> 3)&2); + } + else + { + _palette[registers[index][0]] = (_palette[registers[index][0]]&6) | ((colour >> 7)&1); + _palette[registers[index][1]] = (_palette[registers[index][1]]&6) | ((colour >> 6)&1); + _palette[registers[index][2]] = (_palette[registers[index][2]]&6) | ((colour >> 5)&1); + _palette[registers[index][3]] = (_palette[registers[index][3]]&6) | ((colour >> 4)&1); + + _palette[registers[index][0]] = (_palette[registers[index][0]]&5) | ((colour >> 2)&2); + _palette[registers[index][1]] = (_palette[registers[index][1]]&5) | ((colour >> 1)&2); + } + } + } break; } } @@ -176,6 +223,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin update_display(); _frameCycles = 0; _outputPosition = 0; + _currentOutputLine = 0; } if(_frameCycles == 128*128) signal_interrupt(InterruptRealTimeClock); if(_frameCycles == 284*128) signal_interrupt(InterruptDisplayEnd); @@ -247,10 +295,12 @@ inline void Machine::update_display() { // on lines prior to 28 or after or equal to 284, or on a line that is equal to 8 or 9 modulo 10 in a line-spaced mode, // the line is then definitely blank. - if( - (current_line < 28 || current_line >= 284) -// || (((current_line - 28)%10) > 7) - ) + bool isBlankLine = + ((_screenMode == 3) || (_screenMode == 6)) ? + ((current_line < 28 || current_line >= 277) || (((current_line - 28)%10) > 7)) : + ((current_line < 28 || current_line >= 284)); + + if(isBlankLine) { if(line_position == 9) { @@ -276,19 +326,67 @@ inline void Machine::update_display() if(line_position >= 24 && line_position < 104) { - if(_currentLine) + if(_currentLine && ((_screenMode < 4) || !(line_position&1))) { - if(!(line_position&1)) + if(_currentScreenAddress&32768) { - uint8_t pixels = _ram[_currentScreenAddress]; - _currentScreenAddress += 8; - int output_ptr = (line_position - 24) << 3; + _currentScreenAddress = _screenModeBaseAddress + (_currentScreenAddress&32767); + } + uint8_t pixels = _ram[_currentScreenAddress]; + _currentScreenAddress = _currentScreenAddress+8; + int output_ptr = (line_position - 24) << 3; - for(int c = 0; c < 16; c+=2) - { - _currentLine[output_ptr + c] = _currentLine[output_ptr + c + 1] = (pixels&0x80) ? 0 : 7; - pixels <<= 1; - } + switch(_screenMode) + { + case 0: + case 3: + for(int c = 0; c < 8; c++) + { + uint8_t colour = (pixels&0x80) >> 4; + _currentLine[output_ptr + c] = _palette[colour]; + pixels <<= 1; + } + break; + + case 1: + for(int c = 0; c < 8; c += 2) + { + uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x08) >> 2); + _currentLine[output_ptr + c + 0] = _currentLine[output_ptr + c + 1] = _palette[colour]; + pixels <<= 1; + } + break; + + case 2: + for(int c = 0; c < 8; c += 4) + { + uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1); + _currentLine[output_ptr + c + 0] = _currentLine[output_ptr + c + 1] = + _currentLine[output_ptr + c + 2] = _currentLine[output_ptr + c + 3] = _palette[colour]; + pixels <<= 1; + } + break; + + case 5: + for(int c = 0; c < 16; c += 4) + { + uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x08) >> 2); + _currentLine[output_ptr + c + 0] = _currentLine[output_ptr + c + 1] = + _currentLine[output_ptr + c + 2] = _currentLine[output_ptr + c + 3] = _palette[colour]; + pixels <<= 1; + } + break; + + default: + case 4: + case 6: + for(int c = 0; c < 16; c += 2) + { + uint8_t colour = (pixels&0x80) >> 4; + _currentLine[output_ptr + c] = _currentLine[output_ptr + c + 1] = _palette[colour]; + pixels <<= 1; + } + break; } } _outputPosition++; @@ -296,9 +394,10 @@ inline void Machine::update_display() if(line_position == 104) { - if(!((current_line - 27)&7)) + _currentOutputLine++; + if(!(_currentOutputLine&7)) { - _startLineAddress += 40*8 - 7; + _startLineAddress += ((_screenMode < 4) ? 80 : 40)*8 - 7; } else _startLineAddress++; @@ -320,7 +419,7 @@ const char *Machine::get_signal_decoder() "vec4 sample(vec2 coordinate)\n" "{\n" "float texValue = texture(texID, srcCoordinatesVarying).r;\n" - "return vec4( step(mod(texValue, 8.0/256.0), 4.0/256.0), step(mod(texValue, 4.0/256.0), 2.0/256.0), step(mod(texValue, 2.0/256.0), 1.0/256.0), 1.0);\n" + "return vec4( step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)), 1.0);\n" "}"; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index ac9b64b65..a172618ca 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -130,11 +130,14 @@ class Machine: public CPU6502::Processor { uint8_t _palette[16]; uint8_t _keyStates[14]; ROMSlot _activeRom; + uint8_t _screenMode; + uint16_t _screenModeBaseAddress; Outputs::CRT *_crt; int _frameCycles, _outputPosition; uint16_t _startScreenAddress, _startLineAddress, _currentScreenAddress; + int _currentOutputLine; uint8_t *_currentLine; inline void update_display(); From 650077feace2f6ae0b3ea8bfd05804def62c6adb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Jan 2016 22:18:34 -0500 Subject: [PATCH 028/307] Pulled a few things out as constants, zoomed in a little. Still in a 4:3 window though. --- Machines/Electron/Electron.cpp | 14 +++++++------- .../Clock Signal/Documents/ElectronDocument.swift | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 0caccc2a7..cb670e0a4 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -265,14 +265,16 @@ inline void Machine::evaluate_interrupts() inline void Machine::update_display() { - const int end_of_hsync = 3 * cycles_per_line; + const int lines_of_hsync = 3; + const int end_of_hsync = lines_of_hsync * cycles_per_line; + const int first_graphics_line = 28; if(_frameCycles >= end_of_hsync) { // assert sync for the first three lines of the display, with a break at the end for horizontal alignment if(_outputPosition < end_of_hsync) { - for(int c = 0; c < 3; c++) + for(int c = 0; c < lines_of_hsync; c++) { _crt->output_sync(119 * crt_cycles_multiplier); _crt->output_blank(9 * crt_cycles_multiplier); @@ -293,12 +295,10 @@ inline void Machine::update_display() } else { - // on lines prior to 28 or after or equal to 284, or on a line that is equal to 8 or 9 modulo 10 in a line-spaced mode, - // the line is then definitely blank. bool isBlankLine = ((_screenMode == 3) || (_screenMode == 6)) ? - ((current_line < 28 || current_line >= 277) || (((current_line - 28)%10) > 7)) : - ((current_line < 28 || current_line >= 284)); + ((current_line < first_graphics_line || current_line >= first_graphics_line+248) || (((current_line - first_graphics_line)%10) > 7)) : + ((current_line < first_graphics_line || current_line >= first_graphics_line+256)); if(isBlankLine) { @@ -319,7 +319,7 @@ inline void Machine::update_display() _crt->allocate_write_area(80 * crt_cycles_multiplier); _currentLine = (uint8_t *)_crt->get_write_target_for_buffer(0); - if(current_line == 28) + if(current_line == first_graphics_line) _startLineAddress = _startScreenAddress; _currentScreenAddress = _startLineAddress; } diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index f5a890a60..92dbaf0b3 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -26,7 +26,7 @@ class ElectronDocument: MachineDocument { override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) electron.view = openGLView -// openGLView.frameBounds = CGRectMake(0.1, 0.1, 0.8, 0.8) + openGLView.frameBounds = CGRectMake(0.0225, 0.0625, 0.75, 0.75) } override var windowNibName: String? { From cba09b5490795dd0d83d82f735519a3416fd85af Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Jan 2016 22:29:16 -0500 Subject: [PATCH 029/307] Switched display update and RAM write order. --- Machines/Electron/Electron.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index cb670e0a4..4d6dee662 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -46,12 +46,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { - _ram[address] = *value; - -// TODO: range check on address; a lot of the time the machine will be running code outside of -// the screen area, meaning that no update is required. -// if (address + // TODO: range check on address; a lot of the time the machine will be running code outside of + // the screen area, meaning that no update is required. update_display(); + + _ram[address] = *value; } // TODO: RAM timing for Modes 0–3 From 49a36ec9aca44528304dd7316afd9cd9a239bfc4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Jan 2016 22:35:52 -0500 Subject: [PATCH 030/307] Added F12 as break. --- OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 5df5b09a2..a0110d815 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -105,7 +105,10 @@ case kVK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break; case kVK_Command: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break; + case kVK_F12: _electron.set_key_state(Electron::Key::KeyBreak, isPressed); break; + default: +// printf("%02x\n", key); break; } } From 75d6ec354bc7ff49cd818c07c7c74a7fd1bd89c3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Jan 2016 16:27:09 -0500 Subject: [PATCH 031/307] Added some very basic filtering in RGB mode. --- Machines/Electron/Electron.cpp | 2 +- OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 4d6dee662..8d43ec14b 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -417,7 +417,7 @@ const char *Machine::get_signal_decoder() return "vec4 sample(vec2 coordinate)\n" "{\n" - "float texValue = texture(texID, srcCoordinatesVarying).r;\n" + "float texValue = texture(texID, coordinate).r;\n" "return vec4( step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)), 1.0);\n" "}"; } diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 59cad6145..731f7989d 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -297,10 +297,12 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; NSString *const rgbVertexShaderGlobals = - @"out vec2 srcCoordinatesVarying;\n"; + @"out vec2 srcCoordinatesVarying[3];\n"; NSString *const rgbVertexShaderBody = - @"srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n"; + @"srcCoordinatesVarying[1] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" + "srcCoordinatesVarying[0] = srcCoordinatesVarying[1] - vec2(0.5 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[2] = srcCoordinatesVarying[1] + vec2(0.5 / textureSize.x, 0.0);\n"; NSString *const vertexShader = @"#version 150\n" @@ -387,10 +389,12 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; NSString *const rgbFragmentShaderGlobals = - @"in vec2 srcCoordinatesVarying;\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * + @"in vec2 srcCoordinatesVarying[3];\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * NSString *const rgbFragmentShaderBody = - @"fragColour = sample(srcCoordinatesVarying);//sin(lateralVarying));\n"; + @"fragColour = (sample(srcCoordinatesVarying[0]) + (sample(srcCoordinatesVarying[1]) * 2.0) + sample(srcCoordinatesVarying[2])) / 4.0;"; + +// dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; switch(_signalType) { From 6112f4ef6bdb26a7cdf613136c6820b195001550 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Jan 2016 16:42:16 -0500 Subject: [PATCH 032/307] Withdrew soft filtering for now; until the sampling frequency is passed on it's not sufficiently rigorous. --- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 731f7989d..3fea768ab 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -297,12 +297,14 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; NSString *const rgbVertexShaderGlobals = - @"out vec2 srcCoordinatesVarying[3];\n"; + @"out vec2 srcCoordinatesVarying[5];\n"; NSString *const rgbVertexShaderBody = - @"srcCoordinatesVarying[1] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" - "srcCoordinatesVarying[0] = srcCoordinatesVarying[1] - vec2(0.5 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[2] = srcCoordinatesVarying[1] + vec2(0.5 / textureSize.x, 0.0);\n"; + @"srcCoordinatesVarying[2] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" + "srcCoordinatesVarying[0] = srcCoordinatesVarying[1] - vec2(1.0 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[1] = srcCoordinatesVarying[1] - vec2(0.5 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[3] = srcCoordinatesVarying[1] + vec2(0.5 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[4] = srcCoordinatesVarying[1] + vec2(1.0 / textureSize.x, 0.0);\n"; NSString *const vertexShader = @"#version 150\n" @@ -389,10 +391,15 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; NSString *const rgbFragmentShaderGlobals = - @"in vec2 srcCoordinatesVarying[3];\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * + @"in vec2 srcCoordinatesVarying[5];\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * NSString *const rgbFragmentShaderBody = - @"fragColour = (sample(srcCoordinatesVarying[0]) + (sample(srcCoordinatesVarying[1]) * 2.0) + sample(srcCoordinatesVarying[2])) / 4.0;"; + @"fragColour = sample(srcCoordinatesVarying[2]);"; +// @"fragColour = (sample(srcCoordinatesVarying[0]) * -0.1) + \ +// (sample(srcCoordinatesVarying[1]) * 0.3) + \ +// (sample(srcCoordinatesVarying[2]) * 0.6) + \ +// (sample(srcCoordinatesVarying[3]) * 0.3) + \ +// (sample(srcCoordinatesVarying[4]) * -0.1);"; // dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; From 3437781abdc91b5758842f619806df717da4980c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Jan 2016 16:54:09 -0500 Subject: [PATCH 033/307] Started sketching out an interface for sound generation. Which made me realise that the CRT in CRTDelegate was redundant, since C++ has namespaces. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 ++++ .../Mac/Clock Signal/Wrappers/CSAtari2600.mm | 2 +- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 2 +- .../Wrappers/CSMachine+Subclassing.h | 2 +- .../Mac/Clock Signal/Wrappers/CSMachine.mm | 4 +-- Outputs/CRT.cpp | 2 +- Outputs/CRT.hpp | 6 ++-- Outputs/Speaker.cpp | 9 ++++++ Outputs/Speaker.hpp | 32 +++++++++++++++++++ 9 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 Outputs/Speaker.cpp create mode 100644 Outputs/Speaker.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 9f1210530..af606077c 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; }; 4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; }; 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; }; + 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; }; 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; @@ -327,6 +328,8 @@ 4B14145A1B58879D00E04248 /* CPU6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502AllRAM.hpp; sourceTree = ""; }; 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = ""; }; 4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = ""; }; + 4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = ""; }; + 4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = ""; }; 4B2632551B631A510082A461 /* CRTFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CRTFrame.h; path = ../../Outputs/CRTFrame.h; sourceTree = ""; }; 4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = ""; }; 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; @@ -708,6 +711,8 @@ 4B366DFA1B5C165A0026627B /* CRT.cpp */, 4B366DFB1B5C165A0026627B /* CRT.hpp */, 4B2632551B631A510082A461 /* CRTFrame.h */, + 4B2409531C45AB05004DA684 /* Speaker.cpp */, + 4B2409541C45AB05004DA684 /* Speaker.hpp */, ); name = Outputs; sourceTree = ""; @@ -1532,6 +1537,7 @@ 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, + 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSCathodeRayView.m in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm index d7006d766..49c47e6c3 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -70,7 +70,7 @@ [view setSignalDecoder:[NSString stringWithUTF8String:_atari2600.get_signal_decoder()] type:CSCathodeRayViewSignalTypeNTSC]; } -- (void)setCRTDelegate:(Outputs::CRT::CRTDelegate *)delegate{ +- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate{ _atari2600.get_crt()->set_delegate(delegate); } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index a0110d815..630666989 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -27,7 +27,7 @@ _electron.set_rom(Electron::ROMSlotBASIC, rom.length, (const uint8_t *)rom.bytes); } -- (void)setCRTDelegate:(Outputs::CRT::CRTDelegate *)delegate{ +- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate{ _electron.get_crt()->set_delegate(delegate); } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 1aa88b268..6ca467be8 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -11,7 +11,7 @@ @interface CSMachine (Subclassing) -- (void)setCRTDelegate:(Outputs::CRT::CRTDelegate *)delegate; +- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate; - (void)doRunForNumberOfCycles:(int)numberOfCycles; - (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index 696c78438..e5b98a87d 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -9,7 +9,7 @@ #import "CSMachine.h" #import "CSMachine+Subclassing.h" -struct CRTDelegate: public Outputs::CRT::CRTDelegate { +struct CRTDelegate: public Outputs::CRT::Delegate { __weak CSMachine *machine; void crt_did_end_frame(Outputs::CRT *crt, CRTFrame *frame, bool did_detect_vsync) { [machine crt:crt didEndFrame:frame didDetectVSync:did_detect_vsync]; @@ -60,7 +60,7 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { return self; } -- (void)setCRTDelegate:(Outputs::CRT::CRTDelegate *)delegate {} +- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate {} - (void)doRunForNumberOfCycles:(int)numberOfCycles {} @end diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index 6d5ad9f0a..e36e01bec 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -335,7 +335,7 @@ void CRT::return_frame() #pragma mark - delegate -void CRT::set_delegate(CRTDelegate *delegate) +void CRT::set_delegate(Delegate *delegate) { _delegate = delegate; } diff --git a/Outputs/CRT.hpp b/Outputs/CRT.hpp index 50dd0948c..89e981da1 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT.hpp @@ -58,11 +58,11 @@ class CRT { void output_level(unsigned int number_of_cycles); void output_data(unsigned int number_of_cycles); - class CRTDelegate { + class Delegate { public: virtual void crt_did_end_frame(CRT *crt, CRTFrame *frame, bool did_detect_vsync) = 0; }; - void set_delegate(CRTDelegate *delegate); + void set_delegate(Delegate *delegate); void return_frame(); void allocate_write_area(int required_length); @@ -90,7 +90,7 @@ class CRT { CRTFrameBuilder *_current_frame_builder; int _frames_with_delegate; int _frame_read_pointer; - CRTDelegate *_delegate; + Delegate *_delegate; // outer 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) diff --git a/Outputs/Speaker.cpp b/Outputs/Speaker.cpp new file mode 100644 index 000000000..fa8c60291 --- /dev/null +++ b/Outputs/Speaker.cpp @@ -0,0 +1,9 @@ +// +// Speaker.cpp +// Clock Signal +// +// Created by Thomas Harte on 12/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Speaker.hpp" diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp new file mode 100644 index 000000000..21312d5b2 --- /dev/null +++ b/Outputs/Speaker.hpp @@ -0,0 +1,32 @@ +// +// Speaker.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Speaker_hpp +#define Speaker_hpp + +#include + +namespace Speaker { + +class Delegate { + public: + virtual void speaker_did_complete_samples(uint8_t *buffer); +}; + +template class Filter { + public: + void set_output_rate(int cycles_per_second); + void set_delegate(Delegate *delegate); + + void set_input_rate(int cycles_per_second); + void run_for_cycles(int input_cycles); +}; + +} + +#endif /* Speaker_hpp */ From 84ba4e2900d08516b6b3ddbad1225ca5380c75d7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Jan 2016 22:19:56 -0500 Subject: [PATCH 034/307] Tidied a little, started working towards supporting speaker output. --- Machines/Electron/Electron.hpp | 85 ++++--------------- .../Clock Signal.xcodeproj/project.pbxproj | 17 +++- Outputs/Speaker.cpp | 3 + Outputs/Speaker.hpp | 68 ++++++++++++++- SignalProcessing/Stepper.hpp | 46 ++++++++++ 5 files changed, 145 insertions(+), 74 deletions(-) create mode 100644 SignalProcessing/Stepper.hpp diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index a172618ca..5c8269186 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -11,6 +11,7 @@ #include "../../Processors/6502/CPU6502.hpp" #include "../../Outputs/CRT.hpp" +#include "../../Outputs/Speaker.hpp" #include #include "Atari2600Inputs.h" @@ -38,73 +39,20 @@ enum Interrupt: uint8_t { }; enum Key: uint16_t { - KeySpace = 0x0000 | 0x08, - KeyCopy = 0x0000 | 0x02, - KeyRight = 0x0000 | 0x01, - - KeyDelete = 0x0010 | 0x08, - KeyReturn = 0x0010 | 0x04, - KeyDown = 0x0010 | 0x02, - KeyLeft = 0x0010 | 0x01, - - KeyColon = 0x0020 | 0x04, - KeyUp = 0x0020 | 0x02, - KeyMinus = 0x0020 | 0x01, - - KeySlash = 0x0030 | 0x08, - KeySemiColon = 0x0030 | 0x04, - KeyP = 0x0030 | 0x02, - Key0 = 0x0030 | 0x01, - - KeyFullStop = 0x0040 | 0x08, - KeyL = 0x0040 | 0x04, - KeyO = 0x0040 | 0x02, - Key9 = 0x0040 | 0x01, - - KeyComma = 0x0050 | 0x08, - KeyK = 0x0050 | 0x04, - KeyI = 0x0050 | 0x02, - Key8 = 0x0050 | 0x01, - - KeyM = 0x0060 | 0x08, - KeyJ = 0x0060 | 0x04, - KeyU = 0x0060 | 0x02, - Key7 = 0x0060 | 0x01, - - KeyN = 0x0070 | 0x08, - KeyH = 0x0070 | 0x04, - KeyY = 0x0070 | 0x02, - Key6 = 0x0070 | 0x01, - - KeyB = 0x0080 | 0x08, - KeyG = 0x0080 | 0x04, - KeyT = 0x0080 | 0x02, - Key5 = 0x0080 | 0x01, - - KeyV = 0x0090 | 0x08, - KeyF = 0x0090 | 0x04, - KeyR = 0x0090 | 0x02, - Key4 = 0x0090 | 0x01, - - KeyC = 0x00a0 | 0x08, - KeyD = 0x00a0 | 0x04, - KeyE = 0x00a0 | 0x02, - Key3 = 0x00a0 | 0x01, - - KeyX = 0x00b0 | 0x08, - KeyS = 0x00b0 | 0x04, - KeyW = 0x00b0 | 0x02, - Key2 = 0x00b0 | 0x01, - - KeyZ = 0x00c0 | 0x08, - KeyA = 0x00c0 | 0x04, - KeyQ = 0x00c0 | 0x02, - Key1 = 0x00c0 | 0x01, - - KeyShift = 0x00d0 | 0x08, - KeyControl = 0x00d0 | 0x04, - KeyFunc = 0x00d0 | 0x02, - KeyEscape = 0x00d0 | 0x01, + KeySpace = 0x0000 | 0x08, KeyCopy = 0x0000 | 0x02, KeyRight = 0x0000 | 0x01, + KeyDelete = 0x0010 | 0x08, KeyReturn = 0x0010 | 0x04, KeyDown = 0x0010 | 0x02, KeyLeft = 0x0010 | 0x01, + KeyColon = 0x0020 | 0x04, KeyUp = 0x0020 | 0x02, KeyMinus = 0x0020 | 0x01, + KeySlash = 0x0030 | 0x08, KeySemiColon = 0x0030 | 0x04, KeyP = 0x0030 | 0x02, Key0 = 0x0030 | 0x01, + KeyFullStop = 0x0040 | 0x08, KeyL = 0x0040 | 0x04, KeyO = 0x0040 | 0x02, Key9 = 0x0040 | 0x01, + KeyComma = 0x0050 | 0x08, KeyK = 0x0050 | 0x04, KeyI = 0x0050 | 0x02, Key8 = 0x0050 | 0x01, + KeyM = 0x0060 | 0x08, KeyJ = 0x0060 | 0x04, KeyU = 0x0060 | 0x02, Key7 = 0x0060 | 0x01, + KeyN = 0x0070 | 0x08, KeyH = 0x0070 | 0x04, KeyY = 0x0070 | 0x02, Key6 = 0x0070 | 0x01, + KeyB = 0x0080 | 0x08, KeyG = 0x0080 | 0x04, KeyT = 0x0080 | 0x02, Key5 = 0x0080 | 0x01, + KeyV = 0x0090 | 0x08, KeyF = 0x0090 | 0x04, KeyR = 0x0090 | 0x02, Key4 = 0x0090 | 0x01, + KeyC = 0x00a0 | 0x08, KeyD = 0x00a0 | 0x04, KeyE = 0x00a0 | 0x02, Key3 = 0x00a0 | 0x01, + KeyX = 0x00b0 | 0x08, KeyS = 0x00b0 | 0x04, KeyW = 0x00b0 | 0x02, Key2 = 0x00b0 | 0x01, + KeyZ = 0x00c0 | 0x08, KeyA = 0x00c0 | 0x04, KeyQ = 0x00c0 | 0x02, Key1 = 0x00c0 | 0x01, + KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01, KeyBreak = 0xffff }; @@ -143,6 +91,9 @@ class Machine: public CPU6502::Processor { inline void update_display(); inline void signal_interrupt(Interrupt interrupt); inline void evaluate_interrupts(); + + class Speaker: public ::Speaker::Filter { + }; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index af606077c..cb526d640 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -330,6 +330,7 @@ 4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = ""; }; 4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = ""; }; 4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = ""; }; + 4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = ""; }; 4B2632551B631A510082A461 /* CRTFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CRTFrame.h; path = ../../Outputs/CRTFrame.h; sourceTree = ""; }; 4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = ""; }; 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; @@ -686,6 +687,15 @@ name = "Test Binaries"; sourceTree = ""; }; + 4B2409591C45DF85004DA684 /* SignalProcessing */ = { + isa = PBXGroup; + children = ( + 4B24095A1C45DF85004DA684 /* Stepper.hpp */, + ); + name = SignalProcessing; + path = ../../SignalProcessing; + sourceTree = ""; + }; 4B2E2D961C3A06EC00138695 /* Atari2600 */ = { isa = PBXGroup; children = ( @@ -1028,12 +1038,13 @@ 4BB73E951B587A5100552FC2 = { isa = PBXGroup; children = ( - 4B366DFD1B5C165F0026627B /* Outputs */, - 4BB73EDC1B587CA500552FC2 /* Machines */, - 4BB73EDD1B587CA500552FC2 /* Processors */, 4BB73EA01B587A5100552FC2 /* Clock Signal */, 4BB73EB51B587A5100552FC2 /* Clock SignalTests */, 4BB73EC01B587A5100552FC2 /* Clock SignalUITests */, + 4BB73EDC1B587CA500552FC2 /* Machines */, + 4B366DFD1B5C165F0026627B /* Outputs */, + 4BB73EDD1B587CA500552FC2 /* Processors */, + 4B2409591C45DF85004DA684 /* SignalProcessing */, 4BB73E9F1B587A5100552FC2 /* Products */, ); sourceTree = ""; diff --git a/Outputs/Speaker.cpp b/Outputs/Speaker.cpp index fa8c60291..e393dbc76 100644 --- a/Outputs/Speaker.cpp +++ b/Outputs/Speaker.cpp @@ -7,3 +7,6 @@ // #include "Speaker.hpp" + +using namespace Speaker; + diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 21312d5b2..59eec9862 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -10,6 +10,7 @@ #define Speaker_hpp #include +#include "../SignalProcessing/Stepper.hpp" namespace Speaker { @@ -20,11 +21,70 @@ class Delegate { template class Filter { public: - void set_output_rate(int cycles_per_second); - void set_delegate(Delegate *delegate); + void set_output_rate(int cycles_per_second, int buffer_size) + { + _output_cycles_per_second = cycles_per_second; + if(_buffer_size != buffer_size) + { + delete[] _buffer_in_progress; + _buffer_in_progress = new uint16_t[buffer_size]; + _buffer_size = buffer_size; + } + set_needs_updated_filter_coefficients(); + } - void set_input_rate(int cycles_per_second); - void run_for_cycles(int input_cycles); + void set_output_quality(int number_of_taps) + { + _number_of_taps = number_of_taps; + set_needs_updated_filter_coefficients(); + } + + void set_delegate(Delegate *delegate) + { + _delegate = delegate; + } + + void set_input_rate(int cycles_per_second) + { + _input_cycles_per_second = cycles_per_second; + set_needs_updated_filter_coefficients(); + } + + void run_for_cycles(int input_cycles) + { + if(_coefficients_are_dirty) update_filter_coefficients(); + + // point sample for now, as a temporary measure + while(input_cycles--) + { + static_cast(this)->perform_bus_operation(); + } + } + + private: + uint16_t *_buffer_in_progress; + int _buffer_size; + int _buffer_in_progress_pointer; + int _number_of_taps; + bool _coefficients_are_dirty; + Delegate *_delegate; + SignalProcessing::Stepper *_stepper; + + int _input_cycles_per_second, _output_cycles_per_second; + + void set_needs_updated_filter_coefficients() + { + _coefficients_are_dirty = true; + } + + void update_filter_coefficients() + { + _coefficients_are_dirty = false; + _buffer_in_progress_pointer = 0; + + delete[] _stepper; + _stepper = Stepper(_input_cycles_per_second, _output_cycles_per_second); + } }; } diff --git a/SignalProcessing/Stepper.hpp b/SignalProcessing/Stepper.hpp new file mode 100644 index 000000000..ad61480c4 --- /dev/null +++ b/SignalProcessing/Stepper.hpp @@ -0,0 +1,46 @@ +// +// Stepper.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Stepper_hpp +#define Stepper_hpp + +#include + +namespace SignalProcessing { + +class Stepper +{ + public: + Stepper(uint64_t input_rate, uint64_t output_rate) + { + whole_step_ = output_rate / input_rate; + adjustment_up_ = (int64_t)(output_rate % input_rate) << 1; + adjustment_down_ = (int64_t)input_rate << 1; + } + + inline uint64_t update() + { + uint64_t update = whole_step_; + accumulated_error_ += adjustment_up_; + if(accumulated_error_ > 0) + { + update++; + accumulated_error_ -= adjustment_down_; + } + return update; + } + + private: + uint64_t whole_step_; + int64_t adjustment_up_, adjustment_down_; + int64_t accumulated_error_; +}; + +} + +#endif /* Stepper_hpp */ From d28abdc037154f529e68784464902d7d4ee3d818 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Jan 2016 22:34:26 -0500 Subject: [PATCH 035/307] Made an attempt at correct timing, adding support for additional paged ROMs, added file association for .rom. --- Machines/Electron/Electron.cpp | 20 +++++++++++-------- Machines/Electron/Electron.hpp | 3 ++- .../Documents/ElectronDocument.swift | 1 + OSBindings/Mac/Clock Signal/Info.plist | 14 +++++++++++++ .../Mac/Clock Signal/Wrappers/CSElectron.h | 1 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 6 +++++- 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 8d43ec14b..0113939d2 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -27,6 +27,8 @@ Machine::Machine() : memset(_palette, 0xf, sizeof(_palette)); _crt = new Outputs::CRT(crt_cycles_per_line, 312, 1, 1); _interruptStatus = 0x02; + for(int c = 0; c < 16; c++) + memset(_roms[c], 0xff, 16384); setup6502(); } @@ -55,6 +57,13 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // TODO: RAM timing for Modes 0–3 cycles += (_frameCycles&1)^1; + if(_screenMode < 4) + { + const int current_line = _frameCycles >> 7; + const int line_position = _frameCycles & 127; + if(current_line >= 28 && current_line < 28+256 && line_position >= 24 && line_position < 104) + cycles = (unsigned int)(104 - line_position); + } } else { @@ -191,10 +200,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { switch(_activeRom) { - case ROMSlotBASIC: - case ROMSlotBASIC+1: - *value = _basic[address & 16383]; - break; case ROMSlotKeyboard: case ROMSlotKeyboard+1: *value = 0xf0; @@ -204,7 +209,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } break; default: - *value = 0xff; + *value = _roms[_activeRom][address & 16383]; break; } } @@ -235,9 +240,8 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) uint8_t *target = nullptr; switch(slot) { - case ROMSlotBASIC: target = _basic; break; - case ROMSlotOS: target = _os; break; - default: return; + case ROMSlotOS: target = _os; break; + default: target = _roms[slot]; break; } memcpy(target, data, std::min((size_t)16384, length)); diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 5c8269186..7c3db47ce 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -73,7 +73,8 @@ class Machine: public CPU6502::Processor { const char *get_signal_decoder(); private: - uint8_t _os[16384], _basic[16384], _ram[32768]; + uint8_t _roms[16][16384]; + uint8_t _os[16384], _ram[32768]; uint8_t _interruptStatus, _interruptControl; uint8_t _palette[16]; uint8_t _keyStates[14]; diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 92dbaf0b3..06e494000 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -34,6 +34,7 @@ class ElectronDocument: MachineDocument { } override func readFromData(data: NSData, ofType typeName: String) throws { + electron.setROM(data, slot: 15) } // MARK: CSOpenGLViewDelegate diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 65aac910a..af40ec174 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -41,6 +41,20 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).ElectronDocument + + CFBundleTypeExtensions + + rom + + CFBundleTypeName + Electron/BBC ROM Image + CFBundleTypeRole + Viewer + LSTypeIsPackage + 0 + NSDocumentClass + $(PRODUCT_MODULE_NAME).ElectronDocument + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index b8af2bec4..aefd4d4e0 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -13,6 +13,7 @@ - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; +- (void)setROM:(nonnull NSData *)rom slot:(int)slot; - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 630666989..f8b260442 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -27,7 +27,11 @@ _electron.set_rom(Electron::ROMSlotBASIC, rom.length, (const uint8_t *)rom.bytes); } -- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate{ +- (void)setROM:(nonnull NSData *)rom slot:(int)slot { + _electron.set_rom((Electron::ROMSlot)slot, rom.length, (const uint8_t *)rom.bytes); +} + +- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate { _electron.get_crt()->set_delegate(delegate); } From d9a7ef9e46850ac34ab0c0af559b2381150dc9df Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 13 Jan 2016 21:03:43 -0500 Subject: [PATCH 036/307] Edging towards audio output; the speaker is given appropriate input and output rates, and then updated with current divider and enabled/disabled status. --- Machines/Electron/Electron.cpp | 107 +++++++++++++----- Machines/Electron/Electron.hpp | 12 +- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 5 + .../Wrappers/CSMachine+Subclassing.h | 9 +- .../Mac/Clock Signal/Wrappers/CSMachine.mm | 2 + Outputs/Speaker.cpp | 2 +- Outputs/Speaker.hpp | 47 ++++---- 7 files changed, 130 insertions(+), 54 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 0113939d2..d3d0530d3 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -20,7 +20,9 @@ static const int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; Machine::Machine() : _interruptControl(0), _frameCycles(0), - _outputPosition(0), + _displayOutputPosition(0), + _audioOutputPosition(0), + _audioOutputPositionError(0), _currentOutputLine(0) { memset(_keyStates, 0, sizeof(_keyStates)); @@ -29,6 +31,8 @@ Machine::Machine() : _interruptStatus = 0x02; for(int c = 0; c < 16; c++) memset(_roms[c], 0xff, 16384); + + _speaker.set_input_rate(62500); setup6502(); } @@ -132,21 +136,41 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } break; case 0x6: - printf("Counter\n"); + if(!isReadOperation(operation)) + { + if(_speaker.is_enabled) + update_audio(); + _speaker.divider = *value; + } break; case 0x7: if(!isReadOperation(operation)) { - _screenMode = ((*value) >> 3)&7; - if(_screenMode == 7) _screenMode = 4; - switch(_screenMode) + // update screen mode + uint8_t new_screen_mode = ((*value) >> 3)&7; + if(new_screen_mode == 7) new_screen_mode = 4; + if(new_screen_mode != _screenMode) { - case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; - case 3: _screenModeBaseAddress = 0x4000; break; - case 4: case 5: _screenModeBaseAddress = 0x5800; break; - case 6: _screenModeBaseAddress = 0x6000; break; + update_display(); + _screenMode = new_screen_mode; + switch(_screenMode) + { + case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; + case 3: _screenModeBaseAddress = 0x4000; break; + case 4: case 5: _screenModeBaseAddress = 0x5800; break; + case 6: _screenModeBaseAddress = 0x6000; break; + } } - printf("Misc. control\n"); + + // update speaker mode + bool new_speaker_is_enabled = (*value & 6) == 2; + if(new_speaker_is_enabled != _speaker.is_enabled) + { + update_audio(); + _speaker.is_enabled = new_speaker_is_enabled; + } + + // TODO: tape mode, tape motor, caps lock LED } break; default: @@ -222,15 +246,36 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // } _frameCycles += cycles; - if(_frameCycles == cycles_per_frame) + switch(_frameCycles) { - update_display(); - _frameCycles = 0; - _outputPosition = 0; - _currentOutputLine = 0; + case 64*128: + update_audio(); + break; + + case 128*128: + update_audio(); + signal_interrupt(InterruptRealTimeClock); + break; + + case 196*128: + update_audio(); + break; + + case 284*128: + update_audio(); + signal_interrupt(InterruptDisplayEnd); + break; + + case cycles_per_frame: + update_display(); + update_audio(); + _frameCycles = 0; + _displayOutputPosition = 0; + _audioOutputPosition = 0; + _currentOutputLine = 0; + break; + } - if(_frameCycles == 128*128) signal_interrupt(InterruptRealTimeClock); - if(_frameCycles == 284*128) signal_interrupt(InterruptDisplayEnd); return cycles; } @@ -266,6 +311,14 @@ inline void Machine::evaluate_interrupts() set_irq_line(_interruptStatus & 1); } +inline void Machine::update_audio() +{ + int difference = _frameCycles - _audioOutputPosition; + _audioOutputPosition = _frameCycles; + _speaker.run_for_cycles((_audioOutputPositionError + difference) >> 5); + _audioOutputPositionError = (_audioOutputPositionError + difference)&31; +} + inline void Machine::update_display() { const int lines_of_hsync = 3; @@ -275,26 +328,26 @@ inline void Machine::update_display() if(_frameCycles >= end_of_hsync) { // assert sync for the first three lines of the display, with a break at the end for horizontal alignment - if(_outputPosition < end_of_hsync) + if(_displayOutputPosition < end_of_hsync) { for(int c = 0; c < lines_of_hsync; c++) { _crt->output_sync(119 * crt_cycles_multiplier); _crt->output_blank(9 * crt_cycles_multiplier); } - _outputPosition = end_of_hsync; + _displayOutputPosition = end_of_hsync; } - while(_outputPosition >= end_of_hsync && _outputPosition < _frameCycles) + while(_displayOutputPosition >= end_of_hsync && _displayOutputPosition < _frameCycles) { - const int current_line = _outputPosition >> 7; - const int line_position = _outputPosition & 127; + const int current_line = _displayOutputPosition >> 7; + const int line_position = _displayOutputPosition & 127; // all lines then start with 9 cycles of sync if(!line_position) { _crt->output_sync(9 * crt_cycles_multiplier); - _outputPosition += 9; + _displayOutputPosition += 9; } else { @@ -308,7 +361,7 @@ inline void Machine::update_display() if(line_position == 9) { _crt->output_blank(119 * crt_cycles_multiplier); - _outputPosition += 119; + _displayOutputPosition += 119; } } else @@ -317,7 +370,7 @@ inline void Machine::update_display() if(line_position == 9) { _crt->output_blank(15 * crt_cycles_multiplier); - _outputPosition += 15; + _displayOutputPosition += 15; _crt->allocate_write_area(80 * crt_cycles_multiplier); _currentLine = (uint8_t *)_crt->get_write_target_for_buffer(0); @@ -392,7 +445,7 @@ inline void Machine::update_display() break; } } - _outputPosition++; + _displayOutputPosition++; } if(line_position == 104) @@ -408,7 +461,7 @@ inline void Machine::update_display() _currentLine = nullptr; _crt->output_data(80 * crt_cycles_multiplier); _crt->output_blank(24 * crt_cycles_multiplier); - _outputPosition += 24; + _displayOutputPosition += 24; } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 7c3db47ce..cbbd83c3e 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -70,6 +70,7 @@ class Machine: public CPU6502::Processor { void set_key_state(Key key, bool isPressed); Outputs::CRT *get_crt() { return _crt; } + Outputs::Speaker *get_speaker() { return &_speaker; } const char *get_signal_decoder(); private: @@ -84,17 +85,22 @@ class Machine: public CPU6502::Processor { Outputs::CRT *_crt; - int _frameCycles, _outputPosition; + int _frameCycles, _displayOutputPosition, _audioOutputPosition, _audioOutputPositionError; + uint16_t _startScreenAddress, _startLineAddress, _currentScreenAddress; int _currentOutputLine; uint8_t *_currentLine; inline void update_display(); + inline void update_audio(); inline void signal_interrupt(Interrupt interrupt); inline void evaluate_interrupts(); - class Speaker: public ::Speaker::Filter { - }; + class Speaker: public ::Outputs::Filter { + public: + uint8_t divider; + bool is_enabled; + } _speaker; }; } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index f8b260442..3f0efb63c 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -35,6 +35,11 @@ _electron.get_crt()->set_delegate(delegate); } +- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate { + _electron.get_speaker()->set_output_rate(44100, 512); + _electron.get_speaker()->set_output_quality(15); +} + - (void)setView:(CSCathodeRayView *)view { [super setView:view]; [view setSignalDecoder:[NSString stringWithUTF8String:_electron.get_signal_decoder()] type:CSCathodeRayViewSignalTypeRGB]; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 6ca467be8..61c3460f6 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -7,14 +7,17 @@ // #import "CSMachine.h" -#include "../../../../Outputs/CRT.hpp" +#include "CRT.hpp" +#include "Speaker.hpp" @interface CSMachine (Subclassing) +- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate; - (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate; -- (void)doRunForNumberOfCycles:(int)numberOfCycles; -- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; +- (void)doRunForNumberOfCycles:(int)numberOfCycles; - (void)perform:(dispatch_block_t)action; +- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index e5b98a87d..e3d656f9c 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -53,6 +53,7 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { if (self) { _crtDelegate.machine = self; [self setCRTDelegate:&_crtDelegate]; + [self setSpeakerDelegate:nil]; _serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL); _runningLock = [[NSConditionLock alloc] initWithCondition:CSMachineRunningStateStopped]; } @@ -61,6 +62,7 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { } - (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate {} +- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate {} - (void)doRunForNumberOfCycles:(int)numberOfCycles {} @end diff --git a/Outputs/Speaker.cpp b/Outputs/Speaker.cpp index e393dbc76..bba4e6396 100644 --- a/Outputs/Speaker.cpp +++ b/Outputs/Speaker.cpp @@ -8,5 +8,5 @@ #include "Speaker.hpp" -using namespace Speaker; +using namespace Outputs; diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 59eec9862..6a1b24928 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -10,17 +10,18 @@ #define Speaker_hpp #include +#include #include "../SignalProcessing/Stepper.hpp" -namespace Speaker { +namespace Outputs { -class Delegate { +class Speaker { public: - virtual void speaker_did_complete_samples(uint8_t *buffer); -}; + class Delegate { + public: + virtual void speaker_did_complete_samples(uint8_t *buffer); + }; -template class Filter { - public: void set_output_rate(int cycles_per_second, int buffer_size) { _output_cycles_per_second = cycles_per_second; @@ -50,18 +51,7 @@ template class Filter { set_needs_updated_filter_coefficients(); } - void run_for_cycles(int input_cycles) - { - if(_coefficients_are_dirty) update_filter_coefficients(); - - // point sample for now, as a temporary measure - while(input_cycles--) - { - static_cast(this)->perform_bus_operation(); - } - } - - private: + protected: uint16_t *_buffer_in_progress; int _buffer_size; int _buffer_in_progress_pointer; @@ -76,14 +66,31 @@ template class Filter { { _coefficients_are_dirty = true; } +}; + +template class Filter: public Speaker { + public: + void run_for_cycles(int input_cycles) + { + if(_coefficients_are_dirty) update_filter_coefficients(); + + // point sample for now, as a temporary measure + while(input_cycles--) + { +// static_cast(this)->perform_bus_operation(); + } + } + + private: + SignalProcessing::Stepper *_stepper; void update_filter_coefficients() { _coefficients_are_dirty = false; _buffer_in_progress_pointer = 0; - delete[] _stepper; - _stepper = Stepper(_input_cycles_per_second, _output_cycles_per_second); + delete _stepper; + _stepper = new SignalProcessing::Stepper((uint64_t)_input_cycles_per_second, (uint64_t)_output_cycles_per_second); } }; From 49e89a4bcb3f04d9a3bf9fdce35125c5d41f19ba Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 13 Jan 2016 22:02:39 -0500 Subject: [PATCH 037/307] I'm not yet completely convinced by my approach to time basing, but it'll probably do? If so then this more or less gets me ready for a point-sampled filtering. --- Machines/Electron/Electron.cpp | 11 ++++++++--- Machines/Electron/Electron.hpp | 2 ++ Outputs/Speaker.hpp | 22 ++++++++++++++++++++-- SignalProcessing/Stepper.hpp | 8 ++++---- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index d3d0530d3..403a592da 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -32,7 +32,7 @@ Machine::Machine() : for(int c = 0; c < 16; c++) memset(_roms[c], 0xff, 16384); - _speaker.set_input_rate(62500); + _speaker.set_input_rate(125000); setup6502(); } @@ -315,8 +315,8 @@ inline void Machine::update_audio() { int difference = _frameCycles - _audioOutputPosition; _audioOutputPosition = _frameCycles; - _speaker.run_for_cycles((_audioOutputPositionError + difference) >> 5); - _audioOutputPositionError = (_audioOutputPositionError + difference)&31; + _speaker.run_for_cycles((_audioOutputPositionError + difference) >> 4); + _audioOutputPositionError = (_audioOutputPositionError + difference)&15; } inline void Machine::update_display() @@ -493,3 +493,8 @@ void Machine::set_key_state(Key key, bool isPressed) _keyStates[key >> 4] &= ~(key&0xf); } } + +void Machine::Speaker::get_sample_range(uint64_t start_time, int number_of_samples, uint16_t *target) +{ + *target = 0; +} diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index cbbd83c3e..d2fa989bf 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -100,6 +100,8 @@ class Machine: public CPU6502::Processor { public: uint8_t divider; bool is_enabled; + + void get_sample_range(uint64_t start_time, int number_of_samples, uint16_t *target); } _speaker; }; diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 6a1b24928..09eb974d6 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -19,7 +19,7 @@ class Speaker { public: class Delegate { public: - virtual void speaker_did_complete_samples(uint8_t *buffer); + virtual void speaker_did_complete_samples(Speaker *speaker, const uint16_t *buffer, int buffer_size); }; void set_output_rate(int cycles_per_second, int buffer_size) @@ -77,10 +77,28 @@ template class Filter: public Speaker { // point sample for now, as a temporary measure while(input_cycles--) { -// static_cast(this)->perform_bus_operation(); + // get a sample for the current location + static_cast(this)->get_sample_range(time_base, 1, &_buffer_in_progress[_buffer_in_progress_pointer]); + _buffer_in_progress_pointer++; + + // announce to delegate if full + if(_buffer_in_progress_pointer == _buffer_size) + { + _buffer_in_progress_pointer = 0; + if(_delegate) + { + _delegate->speaker_did_complete_samples(this, _buffer_in_progress, _buffer_size); + } + } + + // determine how many source samples to step + time_base += _stepper->update(); } } + protected: + uint64_t time_base; + private: SignalProcessing::Stepper *_stepper; diff --git a/SignalProcessing/Stepper.hpp b/SignalProcessing/Stepper.hpp index ad61480c4..bb4ae754d 100644 --- a/SignalProcessing/Stepper.hpp +++ b/SignalProcessing/Stepper.hpp @@ -16,11 +16,11 @@ namespace SignalProcessing { class Stepper { public: - Stepper(uint64_t input_rate, uint64_t output_rate) + Stepper(uint64_t output_rate, uint64_t update_rate) { - whole_step_ = output_rate / input_rate; - adjustment_up_ = (int64_t)(output_rate % input_rate) << 1; - adjustment_down_ = (int64_t)input_rate << 1; + whole_step_ = output_rate / update_rate; + adjustment_up_ = (int64_t)(output_rate % update_rate) << 1; + adjustment_down_ = (int64_t)update_rate << 1; } inline uint64_t update() From 439d452e2368d5271cdc95156d483848033c0d62 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 13 Jan 2016 22:11:33 -0500 Subject: [PATCH 038/307] Resolved some errors. --- Machines/Electron/Electron.cpp | 29 +++++++++++++++++++++++------ Machines/Electron/Electron.hpp | 11 +++++++++-- Outputs/Speaker.hpp | 6 +++--- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 403a592da..0d45a9e3c 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -138,9 +138,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x6: if(!isReadOperation(operation)) { - if(_speaker.is_enabled) - update_audio(); - _speaker.divider = *value; + update_audio(); + _speaker.set_divider(*value); } break; case 0x7: @@ -164,10 +163,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // update speaker mode bool new_speaker_is_enabled = (*value & 6) == 2; - if(new_speaker_is_enabled != _speaker.is_enabled) + if(new_speaker_is_enabled != _speaker.get_is_enabled()) { update_audio(); - _speaker.is_enabled = new_speaker_is_enabled; + _speaker.set_is_enabled(new_speaker_is_enabled); } // TODO: tape mode, tape motor, caps lock LED @@ -496,5 +495,23 @@ void Machine::set_key_state(Key key, bool isPressed) void Machine::Speaker::get_sample_range(uint64_t start_time, int number_of_samples, uint16_t *target) { - *target = 0; + if(!_is_enabled) + { + *target = 0; + } + else + { + *target = ((start_time / _divider)&1) ? 255 : 0; + } +} + +void Machine::Speaker::set_divider(uint8_t divider) +{ + _divider = divider; + _time_base = 0; +} + +void Machine::Speaker::set_is_enabled(bool is_enabled) +{ + _is_enabled = false; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index d2fa989bf..fb2a944a5 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -98,10 +98,17 @@ class Machine: public CPU6502::Processor { class Speaker: public ::Outputs::Filter { public: - uint8_t divider; - bool is_enabled; + void set_divider(uint8_t divider); + + void set_is_enabled(bool is_enabled); + inline bool get_is_enabled() { return _is_enabled; } void get_sample_range(uint64_t start_time, int number_of_samples, uint16_t *target); + + private: + uint8_t _divider; + bool _is_enabled; + } _speaker; }; diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 09eb974d6..8dfcd6dc1 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -78,7 +78,7 @@ template class Filter: public Speaker { while(input_cycles--) { // get a sample for the current location - static_cast(this)->get_sample_range(time_base, 1, &_buffer_in_progress[_buffer_in_progress_pointer]); + static_cast(this)->get_sample_range(_time_base, 1, &_buffer_in_progress[_buffer_in_progress_pointer]); _buffer_in_progress_pointer++; // announce to delegate if full @@ -92,12 +92,12 @@ template class Filter: public Speaker { } // determine how many source samples to step - time_base += _stepper->update(); + _time_base += _stepper->update(); } } protected: - uint64_t time_base; + uint64_t _time_base; private: SignalProcessing::Stepper *_stepper; From afde8dac497619902267a110584e8491f4cb59b5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 13 Jan 2016 22:38:59 -0500 Subject: [PATCH 039/307] Closed the loop such that audio manages to bubble up into Objective-C. --- .../Documents/ElectronDocument.swift | 1 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 6 +++-- .../Wrappers/CSMachine+Subclassing.h | 3 ++- .../Mac/Clock Signal/Wrappers/CSMachine.mm | 23 +++++++++++++++---- Outputs/Speaker.hpp | 2 +- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 06e494000..cd1adc6cd 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -7,6 +7,7 @@ // import Foundation +import AudioToolbox class ElectronDocument: MachineDocument { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 3f0efb63c..90477f081 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -35,9 +35,11 @@ _electron.get_crt()->set_delegate(delegate); } -- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate { - _electron.get_speaker()->set_output_rate(44100, 512); +- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { + _electron.get_speaker()->set_output_rate(sampleRate, 512); _electron.get_speaker()->set_output_quality(15); + _electron.get_speaker()->set_delegate(delegate); + return YES; } - (void)setView:(CSCathodeRayView *)view { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 61c3460f6..64c00b6fa 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -12,12 +12,13 @@ @interface CSMachine (Subclassing) -- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate; +- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate; - (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate; - (void)doRunForNumberOfCycles:(int)numberOfCycles; - (void)perform:(dispatch_block_t)action; - (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; +- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const uint16_t *)samples length:(int)length; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index e3d656f9c..f6ce615dc 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -16,6 +16,13 @@ struct CRTDelegate: public Outputs::CRT::Delegate { } }; +struct SpeakerDelegate: public Outputs::Speaker::Delegate { + __weak CSMachine *machine; + void speaker_did_complete_samples(Outputs::Speaker *speaker, const uint16_t *buffer, int buffer_size) { + [machine speaker:speaker didCompleteSamples:buffer length:buffer_size]; + } +}; + typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { CSMachineRunningStateRunning, CSMachineRunningStateStopped @@ -23,6 +30,7 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { @implementation CSMachine { CRTDelegate _crtDelegate; + SpeakerDelegate _speakerDelegate; dispatch_queue_t _serialDispatchQueue; NSConditionLock *_runningLock; @@ -36,6 +44,9 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { if([self.view pushFrame:frame]) crt->return_frame(); } +- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const uint16_t *)samples length:(int)length { +} + - (void)runForNumberOfCycles:(int)cycles { if([_runningLock tryLockWhenCondition:CSMachineRunningStateStopped]) { [_runningLock unlockWithCondition:CSMachineRunningStateRunning]; @@ -51,18 +62,22 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { self = [super init]; if (self) { - _crtDelegate.machine = self; - [self setCRTDelegate:&_crtDelegate]; - [self setSpeakerDelegate:nil]; _serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL); _runningLock = [[NSConditionLock alloc] initWithCondition:CSMachineRunningStateStopped]; + + _crtDelegate.machine = self; + _speakerDelegate.machine = self; + [self setCRTDelegate:&_crtDelegate]; + [self setSpeakerDelegate:&_speakerDelegate sampleRate:44100]; } return self; } - (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate {} -- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate {} +- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { + return NO; +} - (void)doRunForNumberOfCycles:(int)numberOfCycles {} @end diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 8dfcd6dc1..a0085c755 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -19,7 +19,7 @@ class Speaker { public: class Delegate { public: - virtual void speaker_did_complete_samples(Speaker *speaker, const uint16_t *buffer, int buffer_size); + virtual void speaker_did_complete_samples(Speaker *speaker, const uint16_t *buffer, int buffer_size) = 0; }; void set_output_rate(int cycles_per_second, int buffer_size) From 38ffcaa262d131c412f2077eecf1fd804bbec064 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Jan 2016 20:33:22 -0500 Subject: [PATCH 040/307] Here, at last, is _some_ audio output, at least. --- Machines/Electron/Electron.cpp | 6 +- Machines/Electron/Electron.hpp | 2 +- .../Clock Signal.xcodeproj/project.pbxproj | 6 + .../ClockSignal-Bridging-Header.h | 1 + .../Documents/ElectronDocument.swift | 1 + .../Documents/MachineDocument.swift | 3 + .../Mac/Clock Signal/Wrappers/AudioQueue.h | 15 ++ .../Mac/Clock Signal/Wrappers/AudioQueue.m | 131 ++++++++++++++++++ .../Wrappers/CSMachine+Subclassing.h | 2 +- .../Mac/Clock Signal/Wrappers/CSMachine.h | 2 + .../Mac/Clock Signal/Wrappers/CSMachine.mm | 5 +- Outputs/Speaker.hpp | 6 +- 12 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h create mode 100644 OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 0d45a9e3c..fc4dddc4a 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -493,7 +493,7 @@ void Machine::set_key_state(Key key, bool isPressed) } } -void Machine::Speaker::get_sample_range(uint64_t start_time, int number_of_samples, uint16_t *target) +void Machine::Speaker::get_sample_range(uint64_t start_time, int number_of_samples, int16_t *target) { if(!_is_enabled) { @@ -501,7 +501,7 @@ void Machine::Speaker::get_sample_range(uint64_t start_time, int number_of_sampl } else { - *target = ((start_time / _divider)&1) ? 255 : 0; + *target = ((start_time / (_divider+1))&1) ? 255 : 0; } } @@ -513,5 +513,5 @@ void Machine::Speaker::set_divider(uint8_t divider) void Machine::Speaker::set_is_enabled(bool is_enabled) { - _is_enabled = false; + _is_enabled = is_enabled; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index fb2a944a5..4efc1fa04 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -103,7 +103,7 @@ class Machine: public CPU6502::Processor { void set_is_enabled(bool is_enabled); inline bool get_is_enabled() { return _is_enabled; } - void get_sample_range(uint64_t start_time, int number_of_samples, uint16_t *target); + void get_sample_range(uint64_t start_time, int number_of_samples, int16_t *target); private: uint8_t _divider; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index cb526d640..dfb6aa1f9 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */; }; 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; }; @@ -321,6 +322,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioQueue.h; sourceTree = ""; }; + 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioQueue.m; sourceTree = ""; }; 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = ""; }; 4B1414571B58879D00E04248 /* CPU6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPU6502.cpp; sourceTree = ""; }; 4B1414581B58879D00E04248 /* CPU6502.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502.hpp; sourceTree = ""; }; @@ -738,6 +741,8 @@ 4B55CE521C3B7ABF0093A61B /* CSElectron.h */, 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */, 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */, + 4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */, + 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */, ); path = Wrappers; sourceTree = ""; @@ -1547,6 +1552,7 @@ 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, + 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */, diff --git a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h index d9434a171..1ad92dd77 100644 --- a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h +++ b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h @@ -6,3 +6,4 @@ #import "CSAtari2600.h" #import "CSElectron.h" #import "CSCathodeRayView.h" +#import "AudioQueue.h" diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index cd1adc6cd..9c54ab8b1 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -27,6 +27,7 @@ class ElectronDocument: MachineDocument { override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) electron.view = openGLView + electron.audioQueue = self.audioQueue openGLView.frameBounds = CGRectMake(0.0225, 0.0625, 0.75, 0.75) } diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 54bfd891f..428cc05a0 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -7,6 +7,7 @@ // import Cocoa +import AudioToolbox class MachineDocument: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewResponderDelegate { @@ -17,6 +18,8 @@ class MachineDocument: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewRes } } + lazy var audioQueue = AudioQueue() + override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h new file mode 100644 index 000000000..d72eed389 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h @@ -0,0 +1,15 @@ +// +// AudioQueue.h +// Clock Signal +// +// Created by Thomas Harte on 14/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import + +@interface AudioQueue : NSObject + +- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(unsigned int)lengthInSamples; + +@end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m new file mode 100644 index 000000000..764e4fecf --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -0,0 +1,131 @@ +// +// AudioQueue.m +// Clock Signal +// +// Created by Thomas Harte on 14/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "AudioQueue.h" +@import AudioToolbox; + +#define AudioQueueNumAudioBuffers 3 +#define AudioQueueStreamLength 2048 +#define AudioQueueBufferLength 512 + +@implementation AudioQueue +{ + AudioQueueRef _audioQueue; + AudioQueueBufferRef _audioBuffers[AudioQueueNumAudioBuffers]; + unsigned int _audioStreamReadPosition, _audioStreamWritePosition, _queuedAudioStreamSegments; + int16_t _audioStream[AudioQueueStreamLength]; + BOOL _isOutputtingAudio; +} + + +#pragma mark - +#pragma mark AudioQueue callbacks and setup; for pushing audio out + +- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer +{ + @synchronized(self) + { + if(_queuedAudioStreamSegments > AudioQueueNumAudioBuffers-1) _isOutputtingAudio = YES; + + if(_isOutputtingAudio && _queuedAudioStreamSegments) + { + _queuedAudioStreamSegments--; + memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition], buffer->mAudioDataByteSize); + _audioStreamReadPosition = (_audioStreamReadPosition + AudioQueueBufferLength)%AudioQueueStreamLength; + } + else + { + memset(buffer->mAudioData, 0, buffer->mAudioDataByteSize); + _isOutputtingAudio = NO; + } + AudioQueueEnqueueBuffer(theAudioQueue, buffer, 0, NULL); + } +} + +static void audioOutputCallback( + void *inUserData, + AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer) +{ + [(__bridge AudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer]; +} + +- (instancetype)init +{ + self = [super init]; + + if(self) + { + /* + + Describe a mono, 16bit, 44.1Khz audio format + + */ + AudioStreamBasicDescription outputDescription; + + outputDescription.mSampleRate = 44100; + + outputDescription.mFormatID = kAudioFormatLinearPCM; + outputDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + + outputDescription.mBytesPerPacket = 2; + outputDescription.mFramesPerPacket = 1; + outputDescription.mBytesPerFrame = 2; + outputDescription.mChannelsPerFrame = 1; + outputDescription.mBitsPerChannel = 16; + + outputDescription.mReserved = 0; + + // create an audio output queue along those lines + if(!AudioQueueNewOutput( + &outputDescription, + audioOutputCallback, + (__bridge void *)(self), + NULL, + kCFRunLoopCommonModes, + 0, + &_audioQueue)) + { + _audioStreamWritePosition = AudioQueueBufferLength; + UInt32 bufferBytes = AudioQueueBufferLength * sizeof(int16_t); + + int c = AudioQueueNumAudioBuffers; + while(c--) + { + AudioQueueAllocateBuffer(_audioQueue, bufferBytes, &_audioBuffers[c]); + memset(_audioBuffers[c]->mAudioData, 0, bufferBytes); + _audioBuffers[c]->mAudioDataByteSize = bufferBytes; + AudioQueueEnqueueBuffer(_audioQueue, _audioBuffers[c], 0, NULL); + } + + AudioQueueStart(_audioQueue, NULL); + } + } + + return self; +} + +- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(unsigned int)lengthInSamples +{ + @synchronized(self) + { + memcpy(&_audioStream[_audioStreamWritePosition], buffer, lengthInSamples * sizeof(int16_t)); + _audioStreamWritePosition = (_audioStreamWritePosition + lengthInSamples)%AudioQueueStreamLength; + + if(_queuedAudioStreamSegments == (AudioQueueStreamLength/AudioQueueBufferLength)) + { + _audioStreamReadPosition = (_audioStreamReadPosition + lengthInSamples)%AudioQueueStreamLength; + } + else + { + _queuedAudioStreamSegments++; + } + } +} + +@end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 64c00b6fa..255af2ae1 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -19,6 +19,6 @@ - (void)perform:(dispatch_block_t)action; - (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; -- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const uint16_t *)samples length:(int)length; +- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h index dbce321a3..9ce936fe2 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -8,11 +8,13 @@ #import #import "CSCathodeRayView.h" +#import "AudioQueue.h" @interface CSMachine : NSObject - (void)runForNumberOfCycles:(int)numberOfCycles; @property (nonatomic, weak) CSCathodeRayView *view; +@property (nonatomic, weak) AudioQueue *audioQueue; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index f6ce615dc..0711abeec 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -18,7 +18,7 @@ struct CRTDelegate: public Outputs::CRT::Delegate { struct SpeakerDelegate: public Outputs::Speaker::Delegate { __weak CSMachine *machine; - void speaker_did_complete_samples(Outputs::Speaker *speaker, const uint16_t *buffer, int buffer_size) { + void speaker_did_complete_samples(Outputs::Speaker *speaker, const int16_t *buffer, int buffer_size) { [machine speaker:speaker didCompleteSamples:buffer length:buffer_size]; } }; @@ -44,7 +44,8 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { if([self.view pushFrame:frame]) crt->return_frame(); } -- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const uint16_t *)samples length:(int)length { +- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length { + [self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; } - (void)runForNumberOfCycles:(int)cycles { diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index a0085c755..4e22a8c1e 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -19,7 +19,7 @@ class Speaker { public: class Delegate { public: - virtual void speaker_did_complete_samples(Speaker *speaker, const uint16_t *buffer, int buffer_size) = 0; + virtual void speaker_did_complete_samples(Speaker *speaker, const int16_t *buffer, int buffer_size) = 0; }; void set_output_rate(int cycles_per_second, int buffer_size) @@ -28,7 +28,7 @@ class Speaker { if(_buffer_size != buffer_size) { delete[] _buffer_in_progress; - _buffer_in_progress = new uint16_t[buffer_size]; + _buffer_in_progress = new int16_t[buffer_size]; _buffer_size = buffer_size; } set_needs_updated_filter_coefficients(); @@ -52,7 +52,7 @@ class Speaker { } protected: - uint16_t *_buffer_in_progress; + int16_t *_buffer_in_progress; int _buffer_size; int _buffer_in_progress_pointer; int _number_of_taps; From 781249acf757e85bb2cd16270c9d44f51324cc51 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Jan 2016 20:35:36 -0500 Subject: [PATCH 041/307] Fixed crash. --- OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m index 764e4fecf..e22ba865f 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -110,6 +110,11 @@ static void audioOutputCallback( return self; } +- (void)dealloc +{ + if(_audioQueue) AudioQueueDispose(_audioQueue, NO); +} + - (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(unsigned int)lengthInSamples { @synchronized(self) From 8bd04a6be4c1ed9131fab6bb12760b05ebc4bae2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Jan 2016 21:27:02 -0500 Subject: [PATCH 042/307] Switched model for filter subclasses. Implemented test square[-ish] wave to check for obvious stream errors. None is clear. --- Machines/Electron/Electron.cpp | 35 ++++++++++++++++++++++++++-------- Machines/Electron/Electron.hpp | 7 ++++++- Outputs/Speaker.hpp | 10 +++++----- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index fc4dddc4a..c9510be1b 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -493,25 +493,44 @@ void Machine::set_key_state(Key key, bool isPressed) } } -void Machine::Speaker::get_sample_range(uint64_t start_time, int number_of_samples, int16_t *target) +void Machine::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { - if(!_is_enabled) +// if(!_is_enabled) +// { +// *target = 0; +// } +// else { - *target = 0; + *target = _output_level; + _output_level++; + if(_output_level&64) + { + _output_level ^= (8192 | 64); + } } - else + skip_samples(number_of_samples); +} + +void Machine::Speaker::skip_samples(unsigned int number_of_samples) +{ + while(number_of_samples--) { - *target = ((start_time / (_divider+1))&1) ? 255 : 0; + _counter ++; + if(_counter > _divider) + { + _counter = 0; +// _output_level ^= 8192; + } } } void Machine::Speaker::set_divider(uint8_t divider) { - _divider = divider; - _time_base = 0; +// _divider = divider; +// _time_base = 0; } void Machine::Speaker::set_is_enabled(bool is_enabled) { - _is_enabled = is_enabled; +// _is_enabled = is_enabled; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 4efc1fa04..8547f7ed2 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -103,11 +103,16 @@ class Machine: public CPU6502::Processor { void set_is_enabled(bool is_enabled); inline bool get_is_enabled() { return _is_enabled; } - void get_sample_range(uint64_t start_time, int number_of_samples, int16_t *target); + void get_samples(unsigned int number_of_samples, int16_t *target); + void skip_samples(unsigned int number_of_samples); + + Speaker() : _counter(0), _divider(0x32), _is_enabled(false), _output_level(0) {} private: + unsigned int _counter; uint8_t _divider; bool _is_enabled; + int16_t _output_level; } _speaker; }; diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 4e22a8c1e..c7114ca46 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -51,6 +51,8 @@ class Speaker { set_needs_updated_filter_coefficients(); } + Speaker() : _buffer_in_progress_pointer(0) {} + protected: int16_t *_buffer_in_progress; int _buffer_size; @@ -78,7 +80,8 @@ template class Filter: public Speaker { while(input_cycles--) { // get a sample for the current location - static_cast(this)->get_sample_range(_time_base, 1, &_buffer_in_progress[_buffer_in_progress_pointer]); + static_cast(this)->get_samples(1, &_buffer_in_progress[_buffer_in_progress_pointer]); +// _buffer_in_progress[_buffer_in_progress_pointer] = (_buffer_in_progress_pointer&64) ? 8192 : 0; _buffer_in_progress_pointer++; // announce to delegate if full @@ -92,13 +95,10 @@ template class Filter: public Speaker { } // determine how many source samples to step - _time_base += _stepper->update(); + static_cast(this)->skip_samples((unsigned int)_stepper->update()); } } - protected: - uint64_t _time_base; - private: SignalProcessing::Stepper *_stepper; From 383b2be4c689dbe7d7bc1746409d392583d2cabd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Jan 2016 21:33:27 -0500 Subject: [PATCH 043/307] Fixed one off-by-one error. --- Machines/Electron/Electron.cpp | 23 +++++++++-------------- Outputs/Speaker.hpp | 5 +++-- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c9510be1b..1f4176edc 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -495,18 +495,13 @@ void Machine::set_key_state(Key key, bool isPressed) void Machine::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { -// if(!_is_enabled) -// { -// *target = 0; -// } -// else + if(!_is_enabled) + { + *target = 0; + } + else { *target = _output_level; - _output_level++; - if(_output_level&64) - { - _output_level ^= (8192 | 64); - } } skip_samples(number_of_samples); } @@ -519,18 +514,18 @@ void Machine::Speaker::skip_samples(unsigned int number_of_samples) if(_counter > _divider) { _counter = 0; -// _output_level ^= 8192; + _output_level ^= 8192; } } } void Machine::Speaker::set_divider(uint8_t divider) { -// _divider = divider; -// _time_base = 0; + _divider = divider; } void Machine::Speaker::set_is_enabled(bool is_enabled) { -// _is_enabled = is_enabled; + _is_enabled = is_enabled; + _counter = 0; } diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index c7114ca46..74f29975c 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -81,7 +81,6 @@ template class Filter: public Speaker { { // get a sample for the current location static_cast(this)->get_samples(1, &_buffer_in_progress[_buffer_in_progress_pointer]); -// _buffer_in_progress[_buffer_in_progress_pointer] = (_buffer_in_progress_pointer&64) ? 8192 : 0; _buffer_in_progress_pointer++; // announce to delegate if full @@ -95,7 +94,9 @@ template class Filter: public Speaker { } // determine how many source samples to step - static_cast(this)->skip_samples((unsigned int)_stepper->update()); + uint64_t steps = _stepper->update(); + if(steps > 1) + static_cast(this)->skip_samples((unsigned int)(steps-1)); } } From e63b6d22ae8c1e895c77ce8eb4c4866ebb050ae4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Jan 2016 21:59:37 -0500 Subject: [PATCH 044/307] As shown by the commented-out code, the aliasing-adaptation seems to be working. So something is wrong somewhere else. --- Machines/Electron/Electron.cpp | 11 +++++++++++ Machines/Electron/Electron.hpp | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 1f4176edc..cd48d9f57 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -502,6 +502,7 @@ void Machine::Speaker::get_samples(unsigned int number_of_samples, int16_t *targ else { *target = _output_level; +// fwrite(target, sizeof(int16_t), 1, rawStream); } skip_samples(number_of_samples); } @@ -529,3 +530,13 @@ void Machine::Speaker::set_is_enabled(bool is_enabled) _is_enabled = is_enabled; _counter = 0; } + +Machine::Speaker::Speaker() : _counter(0), _divider(0x32), _is_enabled(false), _output_level(0) +{ +// rawStream = fopen("/Users/thomasharte/Desktop/sound.rom", "wb"); +} + +Machine::Speaker::~Speaker() +{ +// fclose(rawStream); +} diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 8547f7ed2..24041cee2 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -106,7 +106,8 @@ class Machine: public CPU6502::Processor { void get_samples(unsigned int number_of_samples, int16_t *target); void skip_samples(unsigned int number_of_samples); - Speaker() : _counter(0), _divider(0x32), _is_enabled(false), _output_level(0) {} + Speaker(); + ~Speaker(); private: unsigned int _counter; @@ -114,6 +115,8 @@ class Machine: public CPU6502::Processor { bool _is_enabled; int16_t _output_level; +// FILE *rawStream; + } _speaker; }; From b58e59e2bbef31074bf54ad9c10453f0847c9ee0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Jan 2016 23:12:40 -0500 Subject: [PATCH 045/307] Fixed: now outputting the requested output rate, rather than massively overproducing at the input rate. --- Outputs/Speaker.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 74f29975c..077b5014b 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -77,7 +77,8 @@ template class Filter: public Speaker { if(_coefficients_are_dirty) update_filter_coefficients(); // point sample for now, as a temporary measure - while(input_cycles--) + input_cycles += _input_cycles_carry; + while(input_cycles > 0) { // get a sample for the current location static_cast(this)->get_samples(1, &_buffer_in_progress[_buffer_in_progress_pointer]); @@ -97,11 +98,14 @@ template class Filter: public Speaker { uint64_t steps = _stepper->update(); if(steps > 1) static_cast(this)->skip_samples((unsigned int)(steps-1)); + input_cycles -= steps; } + _input_cycles_carry = input_cycles; } private: SignalProcessing::Stepper *_stepper; + int _input_cycles_carry; void update_filter_coefficients() { From ddcc34740b290f3aa99371f3fde3d98883497844 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jan 2016 13:50:19 -0600 Subject: [PATCH 046/307] Added lots of debugging output while I attempt to locate the source of audio scratchiness, also simplified manner of dealing with wraparound within the AudioQueue wrapper. --- .../Mac/Clock Signal/Wrappers/AudioQueue.h | 2 +- .../Mac/Clock Signal/Wrappers/AudioQueue.m | 40 +++++++++++-------- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 12 ++++++ Outputs/Speaker.hpp | 18 +++++++++ 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h index d72eed389..cf96e1166 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h @@ -10,6 +10,6 @@ @interface AudioQueue : NSObject -- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(unsigned int)lengthInSamples; +- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m index e22ba865f..b0b72bb59 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -17,9 +17,8 @@ { AudioQueueRef _audioQueue; AudioQueueBufferRef _audioBuffers[AudioQueueNumAudioBuffers]; - unsigned int _audioStreamReadPosition, _audioStreamWritePosition, _queuedAudioStreamSegments; + unsigned int _audioStreamReadPosition, _audioStreamWritePosition; int16_t _audioStream[AudioQueueStreamLength]; - BOOL _isOutputtingAudio; } @@ -30,18 +29,26 @@ { @synchronized(self) { - if(_queuedAudioStreamSegments > AudioQueueNumAudioBuffers-1) _isOutputtingAudio = YES; - - if(_isOutputtingAudio && _queuedAudioStreamSegments) + const unsigned int writeLead = _audioStreamWritePosition - _audioStreamReadPosition; + const size_t audioDataSampleSize = buffer->mAudioDataByteSize / sizeof(int16_t); + if(writeLead >= audioDataSampleSize) { - _queuedAudioStreamSegments--; - memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition], buffer->mAudioDataByteSize); - _audioStreamReadPosition = (_audioStreamReadPosition + AudioQueueBufferLength)%AudioQueueStreamLength; + size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamReadPosition % AudioQueueStreamLength); + if(audioDataSampleSize <= samplesBeforeOverflow) + { + memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % AudioQueueStreamLength], buffer->mAudioDataByteSize); + } + else + { + const size_t bytesRemaining = samplesBeforeOverflow * sizeof(int16_t); + memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % AudioQueueStreamLength], bytesRemaining); + memcpy(buffer->mAudioData, &_audioStream[0], buffer->mAudioDataByteSize - bytesRemaining); + } + _audioStreamReadPosition += audioDataSampleSize; } else { memset(buffer->mAudioData, 0, buffer->mAudioDataByteSize); - _isOutputtingAudio = NO; } AudioQueueEnqueueBuffer(theAudioQueue, buffer, 0, NULL); } @@ -91,7 +98,6 @@ static void audioOutputCallback( 0, &_audioQueue)) { - _audioStreamWritePosition = AudioQueueBufferLength; UInt32 bufferBytes = AudioQueueBufferLength * sizeof(int16_t); int c = AudioQueueNumAudioBuffers; @@ -115,21 +121,23 @@ static void audioOutputCallback( if(_audioQueue) AudioQueueDispose(_audioQueue, NO); } -- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(unsigned int)lengthInSamples +- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples { @synchronized(self) { - memcpy(&_audioStream[_audioStreamWritePosition], buffer, lengthInSamples * sizeof(int16_t)); - _audioStreamWritePosition = (_audioStreamWritePosition + lengthInSamples)%AudioQueueStreamLength; + size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamWritePosition % AudioQueueStreamLength); - if(_queuedAudioStreamSegments == (AudioQueueStreamLength/AudioQueueBufferLength)) + if(samplesBeforeOverflow < lengthInSamples) { - _audioStreamReadPosition = (_audioStreamReadPosition + lengthInSamples)%AudioQueueStreamLength; + memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, samplesBeforeOverflow * sizeof(int16_t)); + memcpy(&_audioStream[0], &buffer[samplesBeforeOverflow], (lengthInSamples - samplesBeforeOverflow) * sizeof(int16_t)); } else { - _queuedAudioStreamSegments++; + memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, lengthInSamples * sizeof(int16_t)); } + + _audioStreamWritePosition += lengthInSamples; } } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 90477f081..446209f94 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -13,9 +13,21 @@ @implementation CSElectron { Electron::Machine _electron; + + NSTimeInterval _periodicStart; + int _numberOfCycles; } - (void)doRunForNumberOfCycles:(int)numberOfCycles { + _numberOfCycles += numberOfCycles; + NSTimeInterval timeNow = [NSDate timeIntervalSinceReferenceDate]; + NSTimeInterval difference = timeNow - _periodicStart; + if(difference > 1.0) + { + NSLog(@"cycles: %0.0f", (double)_numberOfCycles / difference); + _periodicStart = timeNow; + _numberOfCycles = 0; + } _electron.run_for_cycles(numberOfCycles); } diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 077b5014b..5b42e651a 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -11,6 +11,7 @@ #include #include +#include #include "../SignalProcessing/Stepper.hpp" namespace Outputs { @@ -76,6 +77,17 @@ template class Filter: public Speaker { { if(_coefficients_are_dirty) update_filter_coefficients(); + _periodic_cycles += input_cycles; + time_t time_now = time(nullptr); + if(time_now > _periodic_start) + { + printf("input audio samples: %d\n", _periodic_cycles); + printf("output audio samples: %d\n", _periodic_output); + _periodic_cycles = 0; + _periodic_output = 0; + _periodic_start = time_now; + } + // point sample for now, as a temporary measure input_cycles += _input_cycles_carry; while(input_cycles > 0) @@ -99,11 +111,17 @@ template class Filter: public Speaker { if(steps > 1) static_cast(this)->skip_samples((unsigned int)(steps-1)); input_cycles -= steps; + _periodic_output ++; } _input_cycles_carry = input_cycles; } + Filter() : _periodic_cycles(0), _periodic_start(0) {} + private: + time_t _periodic_start; + int _periodic_cycles; + int _periodic_output; SignalProcessing::Stepper *_stepper; int _input_cycles_carry; From b70fdc2de84387f9fb076ce9154a908cc355f9f1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jan 2016 13:56:20 -0600 Subject: [PATCH 047/307] Switched to being explicitly careful about not wrapping around and stomping on data not yet output. --- .../Mac/Clock Signal/Wrappers/AudioQueue.m | 79 +++++++++++-------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m index b0b72bb59..c762dfc38 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -13,12 +13,18 @@ #define AudioQueueStreamLength 2048 #define AudioQueueBufferLength 512 +enum { + AudioQueueCanWrite, + AudioQueueWait +}; + @implementation AudioQueue { AudioQueueRef _audioQueue; AudioQueueBufferRef _audioBuffers[AudioQueueNumAudioBuffers]; unsigned int _audioStreamReadPosition, _audioStreamWritePosition; int16_t _audioStream[AudioQueueStreamLength]; + NSConditionLock *_writeLock; } @@ -27,31 +33,32 @@ - (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer { - @synchronized(self) + [_writeLock lock]; + + const unsigned int writeLead = _audioStreamWritePosition - _audioStreamReadPosition; + const size_t audioDataSampleSize = buffer->mAudioDataByteSize / sizeof(int16_t); + if(writeLead >= audioDataSampleSize*2) { - const unsigned int writeLead = _audioStreamWritePosition - _audioStreamReadPosition; - const size_t audioDataSampleSize = buffer->mAudioDataByteSize / sizeof(int16_t); - if(writeLead >= audioDataSampleSize) + size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamReadPosition % AudioQueueStreamLength); + if(audioDataSampleSize <= samplesBeforeOverflow) { - size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamReadPosition % AudioQueueStreamLength); - if(audioDataSampleSize <= samplesBeforeOverflow) - { - memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % AudioQueueStreamLength], buffer->mAudioDataByteSize); - } - else - { - const size_t bytesRemaining = samplesBeforeOverflow * sizeof(int16_t); - memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % AudioQueueStreamLength], bytesRemaining); - memcpy(buffer->mAudioData, &_audioStream[0], buffer->mAudioDataByteSize - bytesRemaining); - } - _audioStreamReadPosition += audioDataSampleSize; + memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % AudioQueueStreamLength], buffer->mAudioDataByteSize); } else { - memset(buffer->mAudioData, 0, buffer->mAudioDataByteSize); + const size_t bytesRemaining = samplesBeforeOverflow * sizeof(int16_t); + memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % AudioQueueStreamLength], bytesRemaining); + memcpy(buffer->mAudioData, &_audioStream[0], buffer->mAudioDataByteSize - bytesRemaining); } - AudioQueueEnqueueBuffer(theAudioQueue, buffer, 0, NULL); + _audioStreamReadPosition += audioDataSampleSize; } + else + { + memset(buffer->mAudioData, 0, buffer->mAudioDataByteSize); + } + AudioQueueEnqueueBuffer(theAudioQueue, buffer, 0, NULL); + + [_writeLock unlockWithCondition:AudioQueueCanWrite]; } static void audioOutputCallback( @@ -68,10 +75,10 @@ static void audioOutputCallback( if(self) { + _writeLock = [[NSConditionLock alloc] initWithCondition:AudioQueueCanWrite]; + /* - Describe a mono, 16bit, 44.1Khz audio format - */ AudioStreamBasicDescription outputDescription; @@ -123,22 +130,26 @@ static void audioOutputCallback( - (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples { - @synchronized(self) + [_writeLock lockWhenCondition:AudioQueueCanWrite]; + size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamWritePosition % AudioQueueStreamLength); + + if(samplesBeforeOverflow < lengthInSamples) { - size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamWritePosition % AudioQueueStreamLength); - - if(samplesBeforeOverflow < lengthInSamples) - { - memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, samplesBeforeOverflow * sizeof(int16_t)); - memcpy(&_audioStream[0], &buffer[samplesBeforeOverflow], (lengthInSamples - samplesBeforeOverflow) * sizeof(int16_t)); - } - else - { - memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, lengthInSamples * sizeof(int16_t)); - } - - _audioStreamWritePosition += lengthInSamples; + memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, samplesBeforeOverflow * sizeof(int16_t)); + memcpy(&_audioStream[0], &buffer[samplesBeforeOverflow], (lengthInSamples - samplesBeforeOverflow) * sizeof(int16_t)); } + else + { + memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, lengthInSamples * sizeof(int16_t)); + } + + _audioStreamWritePosition += lengthInSamples; + [_writeLock unlockWithCondition:[self writeLockCondition]]; +} + +- (NSInteger)writeLockCondition +{ + return ((_audioStreamWritePosition - _audioStreamReadPosition) < AudioQueueStreamLength) ? AudioQueueCanWrite : AudioQueueWait; } @end From 2779f0e5690067dfc379ffee47fa9316cc12609c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jan 2016 15:46:41 -0600 Subject: [PATCH 048/307] Statred working on support for at least the most fundamental file format. --- .../Clock Signal.xcodeproj/project.pbxproj | 39 ++++++++++++++++++- .../Documents/ElectronDocument.swift | 11 ++++++ OSBindings/Mac/Clock Signal/Info.plist | 21 +++++++++- .../Mac/Clock Signal/Wrappers/AudioQueue.m | 16 ++++++-- Storage/Tape/Formats/TapeUEF.cpp | 9 +++++ Storage/Tape/Formats/TapeUEF.hpp | 22 +++++++++++ Storage/Tape/Tape.cpp | 16 ++++++++ Storage/Tape/Tape.hpp | 39 +++++++++++++++++++ 8 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 Storage/Tape/Formats/TapeUEF.cpp create mode 100644 Storage/Tape/Formats/TapeUEF.hpp create mode 100644 Storage/Tape/Tape.cpp create mode 100644 Storage/Tape/Tape.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index dfb6aa1f9..5b7f842c8 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE571C3B7D360093A61B /* ElectronDocument.swift */; }; 4B55CE5D1C3B7D6F0093A61B /* CSCathodeRayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSCathodeRayView.m */; }; 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; + 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; + 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; }; @@ -355,6 +357,10 @@ 4B55CE5B1C3B7D6F0093A61B /* CSCathodeRayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSCathodeRayView.h; sourceTree = ""; }; 4B55CE5C1C3B7D6F0093A61B /* CSCathodeRayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSCathodeRayView.m; sourceTree = ""; }; 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = ""; }; + 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = ""; }; + 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = ""; }; + 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = ""; }; + 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = ""; }; 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = ""; }; 4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = ""; }; @@ -768,6 +774,34 @@ path = Views; sourceTree = ""; }; + 4B69FB391C4D908A00B5F0AA /* Storage */ = { + isa = PBXGroup; + children = ( + 4B69FB3A1C4D908A00B5F0AA /* Tape */, + ); + name = Storage; + path = ../../Storage; + sourceTree = ""; + }; + 4B69FB3A1C4D908A00B5F0AA /* Tape */ = { + isa = PBXGroup; + children = ( + 4B69FB411C4D941400B5F0AA /* Formats */, + 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */, + 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */, + ); + path = Tape; + sourceTree = ""; + }; + 4B69FB411C4D941400B5F0AA /* Formats */ = { + isa = PBXGroup; + children = ( + 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */, + 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */, + ); + path = Formats; + sourceTree = ""; + }; 4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = { isa = PBXGroup; children = ( @@ -1049,8 +1083,9 @@ 4BB73EDC1B587CA500552FC2 /* Machines */, 4B366DFD1B5C165F0026627B /* Outputs */, 4BB73EDD1B587CA500552FC2 /* Processors */, - 4B2409591C45DF85004DA684 /* SignalProcessing */, 4BB73E9F1B587A5100552FC2 /* Products */, + 4B2409591C45DF85004DA684 /* SignalProcessing */, + 4B69FB391C4D908A00B5F0AA /* Storage */, ); sourceTree = ""; }; @@ -1554,9 +1589,11 @@ 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, + 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, + 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSCathodeRayView.m in Sources */, 4B366DFC1B5C165A0026627B /* CRT.cpp in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 9c54ab8b1..75e42624a 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -35,6 +35,17 @@ class ElectronDocument: MachineDocument { return "ElectronDocument" } + override func readFromURL(url: NSURL, ofType typeName: String) throws { + print(url) + print(typeName) + switch typeName { + default: + let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0)) + try self.readFromFileWrapper(fileWrapper, ofType: typeName) + break; + } + } + override func readFromData(data: NSData, ofType typeName: String) throws { electron.setROM(data, slot: 15) } diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index af40ec174..d761ab5d5 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -35,7 +35,9 @@ CFBundleTypeName Electron/BBC Tape Image CFBundleTypeRole - Editor + Viewer + LSItemContentTypes + LSTypeIsPackage 0 NSDocumentClass @@ -49,6 +51,23 @@ CFBundleTypeName Electron/BBC ROM Image CFBundleTypeRole + Editor + LSItemContentTypes + + LSTypeIsPackage + 0 + NSDocumentClass + $(PRODUCT_MODULE_NAME).ElectronDocument + + + CFBundleTypeExtensions + + uef + uef.gz + + CFBundleTypeName + Electron/BBC UEF Image + CFBundleTypeRole Viewer LSTypeIsPackage 0 diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m index c762dfc38..ccbcf7712 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -10,7 +10,7 @@ @import AudioToolbox; #define AudioQueueNumAudioBuffers 3 -#define AudioQueueStreamLength 2048 +#define AudioQueueStreamLength 32768 #define AudioQueueBufferLength 512 enum { @@ -25,6 +25,10 @@ enum { unsigned int _audioStreamReadPosition, _audioStreamWritePosition; int16_t _audioStream[AudioQueueStreamLength]; NSConditionLock *_writeLock; + +#ifdef DEBUG_INPUT + NSFileHandle * +#endif } @@ -37,7 +41,9 @@ enum { const unsigned int writeLead = _audioStreamWritePosition - _audioStreamReadPosition; const size_t audioDataSampleSize = buffer->mAudioDataByteSize / sizeof(int16_t); - if(writeLead >= audioDataSampleSize*2) + + // TODO: if write lead is too great, skip some audio + if(writeLead >= audioDataSampleSize) { size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamReadPosition % AudioQueueStreamLength); if(audioDataSampleSize <= samplesBeforeOverflow) @@ -125,6 +131,10 @@ static void audioOutputCallback( - (void)dealloc { + int c = AudioQueueNumAudioBuffers; + while(c--) + AudioQueueFreeBuffer(_audioQueue, _audioBuffers[c]); + if(_audioQueue) AudioQueueDispose(_audioQueue, NO); } @@ -149,7 +159,7 @@ static void audioOutputCallback( - (NSInteger)writeLockCondition { - return ((_audioStreamWritePosition - _audioStreamReadPosition) < AudioQueueStreamLength) ? AudioQueueCanWrite : AudioQueueWait; + return ((_audioStreamWritePosition - _audioStreamReadPosition) < (AudioQueueStreamLength - AudioQueueBufferLength)) ? AudioQueueCanWrite : AudioQueueWait; } @end diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp new file mode 100644 index 000000000..c724a8c15 --- /dev/null +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -0,0 +1,9 @@ +// +// TapeUEF.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "TapeUEF.hpp" diff --git a/Storage/Tape/Formats/TapeUEF.hpp b/Storage/Tape/Formats/TapeUEF.hpp new file mode 100644 index 000000000..5cdf7869e --- /dev/null +++ b/Storage/Tape/Formats/TapeUEF.hpp @@ -0,0 +1,22 @@ +// +// TapeUEF.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef TapeUEF_hpp +#define TapeUEF_hpp + +#include "../Tape.hpp" + +class UEF : public Storage::Tape { + public: + UEF(const char *file_name); + Cycle get_next_cycle(); + + private: +}; + +#endif /* TapeUEF_hpp */ diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp new file mode 100644 index 000000000..e90165245 --- /dev/null +++ b/Storage/Tape/Tape.cpp @@ -0,0 +1,16 @@ +// +// Tape.cpp +// Clock Signal +// +// Created by Thomas Harte on 18/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Tape.hpp" + +using namespace Storage; + +void Tape::seek(Tape::Time seek_time) +{ + // TODO: as best we can +} diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp new file mode 100644 index 000000000..022bec740 --- /dev/null +++ b/Storage/Tape/Tape.hpp @@ -0,0 +1,39 @@ +// +// Tape.hpp +// Clock Signal +// +// Created by Thomas Harte on 18/01/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Tape_hpp +#define Tape_hpp + +#include + +namespace Storage { + +class Tape { + public: + + struct Time { + unsigned int length, clock_rate; + }; + + struct Cycle { + enum { + High, Low, Zero + } type; + Time length; + }; + + virtual Cycle get_next_cycle() = 0; + virtual void reset() = 0; + + virtual void seek(Time seek_time); +}; + +} + + +#endif /* Tape_hpp */ From 5a39e4241340e0ea0e760534c049568078c91182 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jan 2016 17:06:09 -0600 Subject: [PATCH 049/307] Wired up enough such that some basic attempt at parsing a UEF occurs.` --- .../Clock Signal.xcodeproj/project.pbxproj | 4 + .../Documents/ElectronDocument.swift | 3 +- .../Mac/Clock Signal/Wrappers/CSElectron.h | 1 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 6 ++ Storage/Tape/Formats/TapeUEF.cpp | 94 +++++++++++++++++++ Storage/Tape/Formats/TapeUEF.hpp | 21 ++++- Storage/Tape/Tape.hpp | 4 +- 7 files changed, 128 insertions(+), 5 deletions(-) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 5b7f842c8..24c2b772f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; + 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; 4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; }; @@ -361,6 +362,7 @@ 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = ""; }; 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = ""; }; 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = ""; }; + 4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = ""; }; 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = ""; }; 4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = ""; }; @@ -655,6 +657,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -796,6 +799,7 @@ 4B69FB411C4D941400B5F0AA /* Formats */ = { isa = PBXGroup; children = ( + 4B69FB451C4D950F00B5F0AA /* libz.tbd */, 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */, 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */, ); diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 75e42624a..e735c1a26 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -39,10 +39,11 @@ class ElectronDocument: MachineDocument { print(url) print(typeName) switch typeName { + case "Electron/BBC Tape Image": // this somewhat implies I've misunderstood the info.plist, doesn't it? + electron.openUEFAtURL(url) default: let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0)) try self.readFromFileWrapper(fileWrapper, ofType: typeName) - break; } } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index aefd4d4e0..306e91ef7 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -14,6 +14,7 @@ - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; - (void)setROM:(nonnull NSData *)rom slot:(int)slot; +- (void)openUEFAtURL:(NSURL *)URL; - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 446209f94..a7b47dbb0 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -10,6 +10,7 @@ #import "Electron.hpp" #import "CSMachine+Subclassing.h" +#import "TapeUEF.hpp" @implementation CSElectron { Electron::Machine _electron; @@ -47,6 +48,11 @@ _electron.get_crt()->set_delegate(delegate); } +- (void)openUEFAtURL:(NSURL *)URL { + Storage::UEF tape([URL fileSystemRepresentation]); +// _electron. +} + - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { _electron.get_speaker()->set_output_rate(sampleRate, 512); _electron.get_speaker()->set_output_quality(15); diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index c724a8c15..6fd56d3ab 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -7,3 +7,97 @@ // #include "TapeUEF.hpp" +#include + +Storage::UEF::UEF(const char *file_name) : + _chunk_id(0), _chunk_length(0), _chunk_position(0), + _time_base(1200) +{ + _file = gzopen(file_name, "rb"); + + char identifier[10]; + int bytes_read = gzread(_file, identifier, 10); + if(bytes_read < 10 || strcmp(identifier, "UEF File!")) + { + // exception? + } + + int minor, major; + minor = gzgetc(_file); + major = gzgetc(_file); + + if(major > 0 || minor > 10 || major < 0 || minor < 0) + { + // exception? + } + + find_next_tape_chunk(); +} + +Storage::UEF::~UEF() +{ + gzclose(_file); +} + +void Storage::UEF::reset() +{ + gzseek(_file, 12, SEEK_SET); +} + +Storage::Tape::Pulse Storage::UEF::get_next_pulse() +{ + Pulse next_pulse; + + return next_pulse; +} + +void Storage::UEF::find_next_tape_chunk() +{ + int reset_count = 0; + + while(1) + { + // read chunk ID + _chunk_id = (uint16_t)gzgetc(_file); + _chunk_id |= (uint16_t)(gzgetc(_file) << 8); + + _chunk_length = (uint32_t)(gzgetc(_file) << 0); + _chunk_length |= (uint32_t)(gzgetc(_file) << 8); + _chunk_length |= (uint32_t)(gzgetc(_file) << 16); + _chunk_length |= (uint32_t)(gzgetc(_file) << 24); + + printf("%04x: %d\n", _chunk_id, _chunk_length); + + if (gzeof(_file)) + { + reset_count++; + if(reset_count == 2) break; + reset(); + continue; + } + + switch(_chunk_id) + { + case 0x0100: case 0x0102: // implicit and explicit bit patterns + case 0x0112: case 0x0116: // gaps + return; + + case 0x0110: // carrier tone + // TODO: read length + return; + case 0x0111: // carrier tone with dummy byte + // TODO: read length + return; + case 0x0114: // security cycles + // TODO: read number, Ps and Ws + break; + + case 0x113: // change of base rate + break; + + default: + gzseek(_file, _chunk_length, SEEK_CUR); + break; + } + } +} diff --git a/Storage/Tape/Formats/TapeUEF.hpp b/Storage/Tape/Formats/TapeUEF.hpp index 5cdf7869e..bf13f0ad5 100644 --- a/Storage/Tape/Formats/TapeUEF.hpp +++ b/Storage/Tape/Formats/TapeUEF.hpp @@ -10,13 +10,30 @@ #define TapeUEF_hpp #include "../Tape.hpp" +#include +#include -class UEF : public Storage::Tape { +namespace Storage { + +class UEF : public Tape { public: UEF(const char *file_name); - Cycle get_next_cycle(); + ~UEF(); + + Pulse get_next_pulse(); + void reset(); private: + gzFile _file; + unsigned int _time_base; + + uint16_t _chunk_id; + uint32_t _chunk_length; + uint32_t _chunk_position; + + void find_next_tape_chunk(); }; +} + #endif /* TapeUEF_hpp */ diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index 022bec740..9ee6c1fd0 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -20,14 +20,14 @@ class Tape { unsigned int length, clock_rate; }; - struct Cycle { + struct Pulse { enum { High, Low, Zero } type; Time length; }; - virtual Cycle get_next_cycle() = 0; + virtual Pulse get_next_pulse() = 0; virtual void reset() = 0; virtual void seek(Time seek_time); From 7d6214e078aa2b99e4f5fc0adf745b9922a41423 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jan 2016 19:37:36 -0600 Subject: [PATCH 050/307] This likely gets chunks 0100 and 0102 correct. --- Storage/Tape/Formats/TapeUEF.cpp | 81 ++++++++++++++++++++++++++++++-- Storage/Tape/Formats/TapeUEF.hpp | 7 +++ 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index 6fd56d3ab..1f7cf6126 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -48,12 +48,40 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() { Pulse next_pulse; + if(!_bit_position && chunk_is_finished()) + { + find_next_tape_chunk(); + } + + next_pulse.length.clock_rate = _time_base; + + switch(_chunk_id) + { + case 0x0100: case 0x0102: case 0x0110: case 0x0111: case 0x0114: + { + // In the ordinary ("1200 baud") data encoding format, + // a zero bit is encoded as one complete cycle at the base frequency. + // A one bit is two complete cycles at twice the base frequency. + + if(!_bit_position) + { + _current_bit = get_next_bit(); + } + + next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low; + next_pulse.length.length = _current_bit ? 1 : 2; + _bit_position = (_bit_position+1)&(_current_bit ? 3 : 1); + } break; + } + return next_pulse; } void Storage::UEF::find_next_tape_chunk() { int reset_count = 0; + _chunk_position = 0; + _bit_position = 0; while(1) { @@ -66,9 +94,7 @@ void Storage::UEF::find_next_tape_chunk() _chunk_length |= (uint32_t)(gzgetc(_file) << 16); _chunk_length |= (uint32_t)(gzgetc(_file) << 24); - printf("%04x: %d\n", _chunk_id, _chunk_length); - - if (gzeof(_file)) + if(gzeof(_file)) { reset_count++; if(reset_count == 2) break; @@ -101,3 +127,52 @@ void Storage::UEF::find_next_tape_chunk() } } } + +bool Storage::UEF::chunk_is_finished() +{ + switch(_chunk_id) + { + case 0x0100: return (_chunk_position / 10) == _chunk_length; + case 0x0102: return (_chunk_position / 8) == _chunk_length; + + default: return true; + } +} + +bool Storage::UEF::get_next_bit() +{ + switch(_chunk_id) + { + case 0x0100: + { + uint32_t bit_position = _chunk_position%10; + _chunk_position++; + if(!bit_position) + { + _current_byte = (uint8_t)gzgetc(_file); + } + if(bit_position == 0) return false; + if(bit_position == 9) return true; + bool result = (_current_byte&1) ? true : false; + _current_byte >>= 1; + return result; + } + break; + + case 0x0102: + { + uint32_t bit_position = _chunk_position%8; + _chunk_position++; + if(!bit_position) + { + _current_byte = (uint8_t)gzgetc(_file); + } + bool result = (_current_byte&1) ? true : false; + _current_byte >>= 1; + return result; + } + break; + + default: return true; + } +} diff --git a/Storage/Tape/Formats/TapeUEF.hpp b/Storage/Tape/Formats/TapeUEF.hpp index bf13f0ad5..3841e66d7 100644 --- a/Storage/Tape/Formats/TapeUEF.hpp +++ b/Storage/Tape/Formats/TapeUEF.hpp @@ -29,9 +29,16 @@ class UEF : public Tape { uint16_t _chunk_id; uint32_t _chunk_length; + + uint8_t _current_byte; uint32_t _chunk_position; + bool _current_bit; + uint32_t _bit_position; + void find_next_tape_chunk(); + bool get_next_bit(); + bool chunk_is_finished(); }; } From 127684c5908a473ecd62063f49a56753834e821f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jan 2016 19:45:14 -0600 Subject: [PATCH 051/307] With silence and high tone implemented, this may well hit a large proportion of existing files. --- Storage/Tape/Formats/TapeUEF.cpp | 31 ++++++++++++++++++++++++++++--- Storage/Tape/Formats/TapeUEF.hpp | 2 ++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index 1f7cf6126..3893dba2a 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -53,11 +53,11 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() find_next_tape_chunk(); } - next_pulse.length.clock_rate = _time_base; + next_pulse.length.clock_rate = _time_base * 2; switch(_chunk_id) { - case 0x0100: case 0x0102: case 0x0110: case 0x0111: case 0x0114: + case 0x0100: case 0x0102: { // In the ordinary ("1200 baud") data encoding format, // a zero bit is encoded as one complete cycle at the base frequency. @@ -72,6 +72,21 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() next_pulse.length.length = _current_bit ? 1 : 2; _bit_position = (_bit_position+1)&(_current_bit ? 3 : 1); } break; + + case 0x0110: + next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low; + next_pulse.length.length = 2; + _bit_position ^= 1; + + if(!_bit_position) _chunk_position++; + break; + + case 0x0112: + case 0x0116: + next_pulse.type = Pulse::Zero; + next_pulse.length.length = _tone_length; + _chunk_position++; + break; } return next_pulse; @@ -109,7 +124,9 @@ void Storage::UEF::find_next_tape_chunk() return; case 0x0110: // carrier tone - // TODO: read length + _tone_length = (uint16_t)gzgetc(_file); + _tone_length |= (uint16_t)(gzgetc(_file) << 8); + gzseek(_file, _chunk_length - 2, SEEK_CUR); return; case 0x0111: // carrier tone with dummy byte // TODO: read length @@ -134,6 +151,10 @@ bool Storage::UEF::chunk_is_finished() { case 0x0100: return (_chunk_position / 10) == _chunk_length; case 0x0102: return (_chunk_position / 8) == _chunk_length; + case 0x0111: return _chunk_position == _tone_length; + + case 0x0112: + case 0x0116: return _chunk_position ? true : false; default: return true; } @@ -173,6 +194,10 @@ bool Storage::UEF::get_next_bit() } break; + case 0x0110: + _chunk_position++; + return true; + default: return true; } } diff --git a/Storage/Tape/Formats/TapeUEF.hpp b/Storage/Tape/Formats/TapeUEF.hpp index 3841e66d7..586647d22 100644 --- a/Storage/Tape/Formats/TapeUEF.hpp +++ b/Storage/Tape/Formats/TapeUEF.hpp @@ -36,6 +36,8 @@ class UEF : public Tape { bool _current_bit; uint32_t _bit_position; + uint16_t _tone_length; + void find_next_tape_chunk(); bool get_next_bit(); bool chunk_is_finished(); From 03f683d588c65a15da03b1b131ecb6c1a77f315b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jan 2016 19:57:18 -0600 Subject: [PATCH 052/307] Gave the audio queue something a lot like a spin lock on waiting to pipe out audio. --- .../Mac/Clock Signal/Wrappers/AudioQueue.m | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m index ccbcf7712..fa5031ed6 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -140,21 +140,32 @@ static void audioOutputCallback( - (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples { - [_writeLock lockWhenCondition:AudioQueueCanWrite]; - size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamWritePosition % AudioQueueStreamLength); - - if(samplesBeforeOverflow < lengthInSamples) + while(1) { - memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, samplesBeforeOverflow * sizeof(int16_t)); - memcpy(&_audioStream[0], &buffer[samplesBeforeOverflow], (lengthInSamples - samplesBeforeOverflow) * sizeof(int16_t)); - } - else - { - memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, lengthInSamples * sizeof(int16_t)); - } + [_writeLock lockWhenCondition:AudioQueueCanWrite]; + if((_audioStreamReadPosition + AudioQueueStreamLength) - _audioStreamWritePosition >= lengthInSamples) + { + size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamWritePosition % AudioQueueStreamLength); - _audioStreamWritePosition += lengthInSamples; - [_writeLock unlockWithCondition:[self writeLockCondition]]; + if(samplesBeforeOverflow < lengthInSamples) + { + memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, samplesBeforeOverflow * sizeof(int16_t)); + memcpy(&_audioStream[0], &buffer[samplesBeforeOverflow], (lengthInSamples - samplesBeforeOverflow) * sizeof(int16_t)); + } + else + { + memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, lengthInSamples * sizeof(int16_t)); + } + + _audioStreamWritePosition += lengthInSamples; + [_writeLock unlockWithCondition:[self writeLockCondition]]; + break; + } + else + { + [_writeLock unlockWithCondition:AudioQueueWait]; + } + } } - (NSInteger)writeLockCondition From 90eef1df748ba67e064ae5ceda79b3e92940ad0f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jan 2016 20:01:06 -0600 Subject: [PATCH 053/307] Tightened to provide lower latency audio, hopefully. --- OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m index fa5031ed6..f2e0235a8 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -9,9 +9,9 @@ #import "AudioQueue.h" @import AudioToolbox; -#define AudioQueueNumAudioBuffers 3 -#define AudioQueueStreamLength 32768 -#define AudioQueueBufferLength 512 +#define AudioQueueNumAudioBuffers 4 +#define AudioQueueStreamLength 1024 +#define AudioQueueBufferLength 256 enum { AudioQueueCanWrite, From 956b90e20310f1c17169ae58f91cb951c21b3137 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Jan 2016 23:55:52 -0500 Subject: [PATCH 054/307] Attempted further to improve latency; added view adjustment for non-4:3 output aspect ratios. The 4:3 is currently hardcoded, so further work will be required. --- Machines/Electron/Electron.cpp | 5 +---- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 17 ++++++++++++++--- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index cd48d9f57..ee5d4bf44 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -248,6 +248,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin switch(_frameCycles) { case 64*128: + case 196*128: update_audio(); break; @@ -256,10 +257,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin signal_interrupt(InterruptRealTimeClock); break; - case 196*128: - update_audio(); - break; - case 284*128: update_audio(); signal_interrupt(InterruptDisplayEnd); diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 3fea768ab..f8ef0992c 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -37,6 +37,8 @@ CSCathodeRayViewSignalType _signalType; int32_t _signalDecoderGeneration; int32_t _compiledSignalDecoderGeneration; + + CGRect _aspectRatioCorrectedBounds; } - (GLuint)textureForImageNamed:(NSString *)name @@ -155,14 +157,23 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt { if(_shaderProgram) { + NSPoint viewSize = [self backingViewSize]; if(_windowSizeUniform >= 0) { - NSPoint viewSize = [self backingViewSize]; glUniform2f(_windowSizeUniform, (GLfloat)viewSize.x, (GLfloat)viewSize.y); } - if(_boundsOriginUniform >= 0) glUniform2f(_boundsOriginUniform, (GLfloat)_frameBounds.origin.x, (GLfloat)_frameBounds.origin.y); - if(_boundsSizeUniform >= 0) glUniform2f(_boundsSizeUniform, (GLfloat)_frameBounds.size.width, (GLfloat)_frameBounds.size.height); + CGFloat outputAspectRatioMultiplier = (viewSize.x / viewSize.y) / (4.0 / 3.0); + +// NSLog(@"%0.2f v %0.2f", outputAspectRatio, desiredOutputAspectRatio); + _aspectRatioCorrectedBounds = _frameBounds; + + CGFloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * _frameBounds.size.width; + _aspectRatioCorrectedBounds.origin.x -= bonusWidth * 0.5f * _aspectRatioCorrectedBounds.size.width; + _aspectRatioCorrectedBounds.size.width *= outputAspectRatioMultiplier; + + if(_boundsOriginUniform >= 0) glUniform2f(_boundsOriginUniform, (GLfloat)_aspectRatioCorrectedBounds.origin.x, (GLfloat)_aspectRatioCorrectedBounds.origin.y); + if(_boundsSizeUniform >= 0) glUniform2f(_boundsSizeUniform, (GLfloat)_aspectRatioCorrectedBounds.size.width, (GLfloat)_aspectRatioCorrectedBounds.size.height); } } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index a7b47dbb0..eaf1c4cac 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -54,7 +54,7 @@ } - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { - _electron.get_speaker()->set_output_rate(sampleRate, 512); + _electron.get_speaker()->set_output_rate(sampleRate, 256); _electron.get_speaker()->set_output_quality(15); _electron.get_speaker()->set_delegate(delegate); return YES; From 832797182fe7150e2f32af0f8901fff3cd6e07e2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Jan 2016 18:59:10 -0500 Subject: [PATCH 055/307] Update TapeUEF.cpp Corrected chunk for which _tone_length is used, definition of a carrier tone chunk. --- Storage/Tape/Formats/TapeUEF.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index 3893dba2a..4df4dffc3 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -75,7 +75,7 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() case 0x0110: next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low; - next_pulse.length.length = 2; + next_pulse.length.length = 1; _bit_position ^= 1; if(!_bit_position) _chunk_position++; @@ -151,7 +151,7 @@ bool Storage::UEF::chunk_is_finished() { case 0x0100: return (_chunk_position / 10) == _chunk_length; case 0x0102: return (_chunk_position / 8) == _chunk_length; - case 0x0111: return _chunk_position == _tone_length; + case 0x0110: return _chunk_position == _tone_length; case 0x0112: case 0x0116: return _chunk_position ? true : false; From e65cd4cf0623383e3118a01b7d46674a9988f9e7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Jan 2016 22:05:34 -0500 Subject: [PATCH 056/307] Some data is marginally reaching the CPU from the tape. --- Machines/Electron/Electron.cpp | 82 ++++++++++++++++++- Machines/Electron/Electron.hpp | 45 ++++++++-- .../Documents/ElectronDocument.swift | 17 ++-- .../Mac/Clock Signal/Wrappers/CSElectron.h | 2 +- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 11 ++- Outputs/Speaker.hpp | 2 +- SignalProcessing/Stepper.hpp | 28 +++++-- Storage/Tape/Formats/TapeUEF.cpp | 28 ++++--- Storage/Tape/Formats/TapeUEF.hpp | 6 +- 9 files changed, 184 insertions(+), 37 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ee5d4bf44..290bee98e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -23,7 +23,8 @@ Machine::Machine() : _displayOutputPosition(0), _audioOutputPosition(0), _audioOutputPositionError(0), - _currentOutputLine(0) + _currentOutputLine(0), + _tape({.is_running = false, .dataRegister = 0}) { memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); @@ -100,6 +101,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); break; case 0x4: + if(isReadOperation(operation)) + { + *value = (uint8_t)_tape.dataRegister; + } printf("Cassette\n"); break; case 0x5: @@ -170,6 +175,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } // TODO: tape mode, tape motor, caps lock LED + _tape.is_running = ((*value)&0x40) ? true : false; } break; default: @@ -270,12 +276,86 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _audioOutputPosition = 0; _currentOutputLine = 0; break; + } + if(_tape.is_running && _tape.media != nullptr) + { + _tape.time_into_pulse += (unsigned int)_tape.pulseStepper->step(); + if(_tape.time_into_pulse == _tape.currentPulse.length.length) + { + get_next_tape_pulse(); + + _tape.crossings[0] = _tape.crossings[1]; + _tape.crossings[1] = _tape.crossings[2]; + _tape.crossings[2] = _tape.crossings[3]; + + _tape.crossings[3] = Tape::Unrecognised; + if(_tape.currentPulse.type != Storage::Tape::Pulse::Zero) + { + float pulse_length = (float)_tape.currentPulse.length.length / (float)_tape.currentPulse.length.clock_rate; + if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _tape.crossings[3] = Tape::Short; + if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _tape.crossings[3] = Tape::Long; + } + + if(_tape.crossings[0] == Tape::Long && _tape.crossings[1] == Tape::Long) + { + push_tape_bit(0); + _tape.crossings[1] = Tape::Unrecognised; + } + else + { + if(_tape.crossings[0] == Tape::Short && _tape.crossings[1] == Tape::Short && _tape.crossings[2] == Tape::Short && _tape.crossings[3] == Tape::Short) + { + push_tape_bit(1); + _tape.crossings[3] = Tape::Unrecognised; + } + } + } } return cycles; } +inline void Machine::get_next_tape_pulse() +{ + _tape.time_into_pulse = 0; + _tape.currentPulse = _tape.media->get_next_pulse(); + if(_tape.pulseStepper == nullptr || _tape.currentPulse.length.clock_rate != _tape.pulseStepper->get_output_rate()) + { + _tape.pulseStepper = std::shared_ptr(new SignalProcessing::Stepper(_tape.currentPulse.length.clock_rate, 2000000)); + } +} + +inline void Machine::push_tape_bit(uint16_t bit) +{ + _tape.dataRegister = (uint16_t)((_tape.dataRegister >> 1) | (bit << 9)); + + if(_tape.dataRegister == 0x3ff) + _interruptStatus |= InterruptHighToneDetect; + else + _interruptStatus &= !InterruptHighToneDetect; + + if(_tape.bits_since_start > 0) + { + _tape.bits_since_start--; + + if(_tape.bits_since_start == 0) + { + printf("%02x [%c]\n", _tape.dataRegister&0xff, _tape.dataRegister&0x7f); + _interruptStatus |= InterruptTransmitDataEmpty; + } + } + + if(!bit && !_tape.bits_since_start) + { + _tape.bits_since_start = 10; + } + + printf("."); + + evaluate_interrupts(); +} + void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) { uint8_t *target = nullptr; diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 24041cee2..26d943619 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -12,6 +12,7 @@ #include "../../Processors/6502/CPU6502.hpp" #include "../../Outputs/CRT.hpp" #include "../../Outputs/Speaker.hpp" +#include "../../Storage/Tape/Tape.hpp" #include #include "Atari2600Inputs.h" @@ -67,6 +68,8 @@ class Machine: public CPU6502::Processor { unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); void set_rom(ROMSlot slot, size_t length, const uint8_t *data); + void set_tape(std::shared_ptr tape) { _tape.media = tape; get_next_tape_pulse(); } + void set_key_state(Key key, bool isPressed); Outputs::CRT *get_crt() { return _crt; } @@ -74,27 +77,53 @@ class Machine: public CPU6502::Processor { const char *get_signal_decoder(); private: + + inline void update_display(); + inline void update_audio(); + inline void signal_interrupt(Interrupt interrupt); + inline void evaluate_interrupts(); + + inline void get_next_tape_pulse(); + inline void push_tape_bit(uint16_t bit); + + // Things that directly constitute the memory map. uint8_t _roms[16][16384]; uint8_t _os[16384], _ram[32768]; + + // Things affected by registers, explicitly or otherwise. uint8_t _interruptStatus, _interruptControl; uint8_t _palette[16]; uint8_t _keyStates[14]; ROMSlot _activeRom; uint8_t _screenMode; uint16_t _screenModeBaseAddress; + uint16_t _startScreenAddress; - Outputs::CRT *_crt; - + // Counters related to simultaneous subsystems; int _frameCycles, _displayOutputPosition, _audioOutputPosition, _audioOutputPositionError; - uint16_t _startScreenAddress, _startLineAddress, _currentScreenAddress; + // Display generation. + uint16_t _startLineAddress, _currentScreenAddress; int _currentOutputLine; uint8_t *_currentLine; - inline void update_display(); - inline void update_audio(); - inline void signal_interrupt(Interrupt interrupt); - inline void evaluate_interrupts(); + // Tape. + struct Tape { + std::shared_ptr media; + Storage::Tape::Pulse currentPulse; + std::shared_ptr pulseStepper; + uint32_t time_into_pulse; + bool is_running; + uint16_t dataRegister; + int bits_since_start; + + enum { + Long, Short, Unrecognised + } crossings[4]; + } _tape; + + // Outputs. + Outputs::CRT *_crt; class Speaker: public ::Outputs::Filter { public: @@ -115,8 +144,6 @@ class Machine: public CPU6502::Processor { bool _is_enabled; int16_t _output_level; -// FILE *rawStream; - } _speaker; }; diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index e735c1a26..2e2f31dc4 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -38,13 +38,18 @@ class ElectronDocument: MachineDocument { override func readFromURL(url: NSURL, ofType typeName: String) throws { print(url) print(typeName) - switch typeName { - case "Electron/BBC Tape Image": // this somewhat implies I've misunderstood the info.plist, doesn't it? - electron.openUEFAtURL(url) - default: - let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0)) - try self.readFromFileWrapper(fileWrapper, ofType: typeName) + + if let pathExtension = url.pathExtension { + switch pathExtension.lowercaseString { + case "uef": + electron.openUEFAtURL(url) + return + default: break; + } } + + let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0)) + try self.readFromFileWrapper(fileWrapper, ofType: typeName) } override func readFromData(data: NSData, ofType typeName: String) throws { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index 306e91ef7..474c6f726 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -14,7 +14,7 @@ - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; - (void)setROM:(nonnull NSData *)rom slot:(int)slot; -- (void)openUEFAtURL:(NSURL *)URL; +- (BOOL)openUEFAtURL:(NSURL *)URL; - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index eaf1c4cac..e04ce5154 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -48,9 +48,14 @@ _electron.get_crt()->set_delegate(delegate); } -- (void)openUEFAtURL:(NSURL *)URL { - Storage::UEF tape([URL fileSystemRepresentation]); -// _electron. +- (BOOL)openUEFAtURL:(NSURL *)URL { + try { + std::shared_ptr tape(new Storage::UEF([URL fileSystemRepresentation])); + _electron.set_tape(tape); + return YES; + } catch(int exception) { + return NO; + } } - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 5b42e651a..00faf36b3 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -107,7 +107,7 @@ template class Filter: public Speaker { } // determine how many source samples to step - uint64_t steps = _stepper->update(); + uint64_t steps = _stepper->step(); if(steps > 1) static_cast(this)->skip_samples((unsigned int)(steps-1)); input_cycles -= steps; diff --git a/SignalProcessing/Stepper.hpp b/SignalProcessing/Stepper.hpp index bb4ae754d..53e046354 100644 --- a/SignalProcessing/Stepper.hpp +++ b/SignalProcessing/Stepper.hpp @@ -16,14 +16,21 @@ namespace SignalProcessing { class Stepper { public: - Stepper(uint64_t output_rate, uint64_t update_rate) + Stepper() { - whole_step_ = output_rate / update_rate; - adjustment_up_ = (int64_t)(output_rate % update_rate) << 1; - adjustment_down_ = (int64_t)update_rate << 1; + Stepper(1, 1); } - inline uint64_t update() + Stepper(uint64_t output_rate, uint64_t input_rate) + { + input_rate_ = input_rate; + output_rate_ = output_rate; + whole_step_ = output_rate / input_rate; + adjustment_up_ = (int64_t)(output_rate % input_rate) << 1; + adjustment_down_ = (int64_t)input_rate << 1; + } + + inline uint64_t step() { uint64_t update = whole_step_; accumulated_error_ += adjustment_up_; @@ -35,10 +42,21 @@ class Stepper return update; } + inline uint64_t get_output_rate() + { + return output_rate_; + } + + inline uint64_t get_input_rate() + { + return input_rate_; + } + private: uint64_t whole_step_; int64_t adjustment_up_, adjustment_down_; int64_t accumulated_error_; + uint64_t input_rate_, output_rate_; }; } diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index 4df4dffc3..ba6d4b6de 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -19,7 +19,7 @@ Storage::UEF::UEF(const char *file_name) : int bytes_read = gzread(_file, identifier, 10); if(bytes_read < 10 || strcmp(identifier, "UEF File!")) { - // exception? + throw ErrorNotUEF; } int minor, major; @@ -28,7 +28,7 @@ Storage::UEF::UEF(const char *file_name) : if(major > 0 || minor > 10 || major < 0 || minor < 0) { - // exception? + throw ErrorNotUEF; } find_next_tape_chunk(); @@ -53,8 +53,6 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() find_next_tape_chunk(); } - next_pulse.length.clock_rate = _time_base * 2; - switch(_chunk_id) { case 0x0100: case 0x0102: @@ -70,12 +68,14 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low; next_pulse.length.length = _current_bit ? 1 : 2; + next_pulse.length.clock_rate = _time_base * 4; _bit_position = (_bit_position+1)&(_current_bit ? 3 : 1); } break; case 0x0110: next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low; next_pulse.length.length = 1; + next_pulse.length.clock_rate = _time_base * 4; _bit_position ^= 1; if(!_bit_position) _chunk_position++; @@ -84,7 +84,7 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() case 0x0112: case 0x0116: next_pulse.type = Pulse::Zero; - next_pulse.length.length = _tone_length; + next_pulse.length = _chunk_duration; _chunk_position++; break; } @@ -120,16 +120,24 @@ void Storage::UEF::find_next_tape_chunk() switch(_chunk_id) { case 0x0100: case 0x0102: // implicit and explicit bit patterns - case 0x0112: case 0x0116: // gaps + return; + + case 0x0112: + _chunk_duration.length = (uint16_t)gzgetc(_file); + _chunk_duration.length |= (uint16_t)(gzgetc(_file) << 8); + _chunk_duration.clock_rate = _time_base; + return; + + case 0x0116: // gaps return; case 0x0110: // carrier tone - _tone_length = (uint16_t)gzgetc(_file); - _tone_length |= (uint16_t)(gzgetc(_file) << 8); + _chunk_duration.length = (uint16_t)gzgetc(_file); + _chunk_duration.length |= (uint16_t)(gzgetc(_file) << 8); gzseek(_file, _chunk_length - 2, SEEK_CUR); return; case 0x0111: // carrier tone with dummy byte - // TODO: read length + // TODO: read lengths return; case 0x0114: // security cycles // TODO: read number, Ps and Ws @@ -151,7 +159,7 @@ bool Storage::UEF::chunk_is_finished() { case 0x0100: return (_chunk_position / 10) == _chunk_length; case 0x0102: return (_chunk_position / 8) == _chunk_length; - case 0x0110: return _chunk_position == _tone_length; + case 0x0110: return _chunk_position == _chunk_duration.length; case 0x0112: case 0x0116: return _chunk_position ? true : false; diff --git a/Storage/Tape/Formats/TapeUEF.hpp b/Storage/Tape/Formats/TapeUEF.hpp index 586647d22..e04ee7092 100644 --- a/Storage/Tape/Formats/TapeUEF.hpp +++ b/Storage/Tape/Formats/TapeUEF.hpp @@ -23,6 +23,10 @@ class UEF : public Tape { Pulse get_next_pulse(); void reset(); + enum { + ErrorNotUEF + }; + private: gzFile _file; unsigned int _time_base; @@ -36,7 +40,7 @@ class UEF : public Tape { bool _current_bit; uint32_t _bit_position; - uint16_t _tone_length; + Time _chunk_duration; void find_next_tape_chunk(); bool get_next_bit(); From 9036cf3b7cf8d0467772dee53db06d6313309428 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Jan 2016 23:03:27 -0500 Subject: [PATCH 057/307] Thus did the first UEF load. --- Machines/Electron/Electron.cpp | 33 ++++++++++++++++++--------------- Machines/Electron/Electron.hpp | 3 ++- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 290bee98e..c74f5953f 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -103,7 +103,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x4: if(isReadOperation(operation)) { - *value = (uint8_t)_tape.dataRegister; + *value = (uint8_t)(_tape.dataRegister >> 2); + _interruptStatus &= ~InterruptTransmitDataEmpty; + evaluate_interrupts(); } printf("Cassette\n"); break; @@ -328,29 +330,30 @@ inline void Machine::get_next_tape_pulse() inline void Machine::push_tape_bit(uint16_t bit) { - _tape.dataRegister = (uint16_t)((_tape.dataRegister >> 1) | (bit << 9)); + _tape.dataRegister = (uint16_t)((_tape.dataRegister >> 1) | (bit << 10)); - if(_tape.dataRegister == 0x3ff) - _interruptStatus |= InterruptHighToneDetect; - else - _interruptStatus &= !InterruptHighToneDetect; - - if(_tape.bits_since_start > 0) + if(_tape.bits_since_start) { _tape.bits_since_start--; - if(_tape.bits_since_start == 0) + if(_tape.bits_since_start == 7) { - printf("%02x [%c]\n", _tape.dataRegister&0xff, _tape.dataRegister&0x7f); - _interruptStatus |= InterruptTransmitDataEmpty; + _interruptStatus &= ~InterruptTransmitDataEmpty; } } - - if(!bit && !_tape.bits_since_start) + else { - _tape.bits_since_start = 10; - } + if((_tape.dataRegister&0x3) == 0x1) + { + _interruptStatus |= InterruptTransmitDataEmpty; + _tape.bits_since_start = 9; + } + if(_tape.dataRegister == 0x3ff) + _interruptStatus |= InterruptHighToneDetect; + else + _interruptStatus &= ~InterruptHighToneDetect; + } printf("."); evaluate_interrupts(); diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 26d943619..89b616348 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -114,9 +114,10 @@ class Machine: public CPU6502::Processor { std::shared_ptr pulseStepper; uint32_t time_into_pulse; bool is_running; - uint16_t dataRegister; int bits_since_start; + uint16_t dataRegister; + enum { Long, Short, Unrecognised } crossings[4]; From 280a2292ff75915a70ccab289215d786c20f938a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Jan 2016 23:33:15 -0500 Subject: [PATCH 058/307] Shushed. --- Machines/Electron/Electron.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c74f5953f..6aafa81d8 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -107,7 +107,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _interruptStatus &= ~InterruptTransmitDataEmpty; evaluate_interrupts(); } - printf("Cassette\n"); + else + { + } +// printf("Cassette\n"); break; case 0x5: if(!isReadOperation(operation)) @@ -354,7 +357,7 @@ inline void Machine::push_tape_bit(uint16_t bit) else _interruptStatus &= ~InterruptHighToneDetect; } - printf("."); +// printf("."); evaluate_interrupts(); } From f727582911b905a30a794b401cd566dcd8755a15 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 20 Jan 2016 08:21:33 -0500 Subject: [PATCH 059/307] Started making first moves towards stripping CSCathodeRayView of its responsibilities. If that can be merely an OpenGL view that presents things, things will be a lot more portable. --- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 4 +-- Outputs/CRT.cpp | 27 +++++++++++---- Outputs/CRTFrame.h | 34 ++++++++++++++----- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index f8ef0992c..3ba042ca0 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -228,7 +228,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt BOOL hadFrame = _crtFrame ? YES : NO; _crtFrame = crtFrame; - glBufferData(GL_ARRAY_BUFFER, _crtFrame->number_of_runs * kCRTSizeOfVertex * 6, _crtFrame->runs, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_crtFrame->number_of_vertices * _crtFrame->size_per_vertex), _crtFrame->vertices, GL_DYNAMIC_DRAW); glBindTexture(GL_TEXTURE_2D, _textureName); if(_textureSize.width != _crtFrame->size.width || _textureSize.height != _crtFrame->size.height) @@ -512,7 +512,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt if (_crtFrame) { if(_textureSizeUniform >= 0) glUniform2f(_textureSizeUniform, _crtFrame->size.width, _crtFrame->size.height); - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)(_crtFrame->number_of_runs*6)); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_crtFrame->number_of_vertices); } CGLFlushDrawable([[self openGLContext] CGLContextObj]); diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index e36e01bec..a581b3df9 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -15,6 +15,18 @@ using namespace Outputs; static const uint32_t kCRTFixedPointRange = 0xf7ffffff; static const uint32_t kCRTFixedPointOffset = 0x04000000; +typedef uint16_t kCRTPositionType; +typedef uint16_t kCRTTexCoordType; +typedef uint8_t kCRTLateralType; +typedef uint8_t kCRTPhaseType; + +//static const size_t kCRTVertexOffsetOfPosition = 0; +//static const size_t kCRTVertexOffsetOfTexCoord = 4; +//static const size_t kCRTVertexOffsetOfLateral = 8; +//static const size_t kCRTVertexOffsetOfPhase = 9; +// +//static const int kCRTSizeOfVertex = 10; + #define kRetraceXMask 0x01 #define kRetraceYMask 0x02 @@ -394,6 +406,8 @@ CRTFrameBuilder::CRTFrameBuilder(uint16_t width, uint16_t height, unsigned int n frame.size.height = height; frame.number_of_buffers = number_of_buffers; frame.buffers = new CRTBuffer[number_of_buffers]; + frame.size_per_vertex = kCRTSizeOfVertex; + frame.geometry_mode = CRTGeometryModeTriangles; for(int buffer = 0; buffer < number_of_buffers; buffer++) { @@ -413,7 +427,7 @@ CRTFrameBuilder::~CRTFrameBuilder() void CRTFrameBuilder::reset() { - frame.number_of_runs = 0; + frame.number_of_vertices = 0; _next_write_x_position = _next_write_y_position = 0; frame.dirty_size.width = 0; frame.dirty_size.height = 1; @@ -421,22 +435,21 @@ void CRTFrameBuilder::reset() void CRTFrameBuilder::complete() { - frame.runs = &_all_runs[0]; + frame.vertices = &_all_runs[0]; } uint8_t *CRTFrameBuilder::get_next_run() { const size_t vertices_per_run = 6; - const size_t size_of_run = kCRTSizeOfVertex * vertices_per_run; // get a run from the allocated list, allocating more if we're about to overrun - if(frame.number_of_runs * size_of_run >= _all_runs.size()) + if((frame.number_of_vertices + vertices_per_run) * frame.size_per_vertex >= _all_runs.size()) { - _all_runs.resize(_all_runs.size() + size_of_run * 200); + _all_runs.resize(_all_runs.size() + frame.size_per_vertex * vertices_per_run * 100); } - uint8_t *next_run = &_all_runs[frame.number_of_runs * size_of_run]; - frame.number_of_runs++; + uint8_t *next_run = &_all_runs[frame.number_of_vertices * frame.size_per_vertex]; + frame.number_of_vertices += vertices_per_run; return next_run; } diff --git a/Outputs/CRTFrame.h b/Outputs/CRTFrame.h index d63799a37..14ebf3ea2 100644 --- a/Outputs/CRTFrame.h +++ b/Outputs/CRTFrame.h @@ -22,21 +22,37 @@ typedef struct { uint16_t width, height; } CRTSize; -typedef struct { - CRTSize size, dirty_size; +typedef enum { + CRTGeometryModeTriangles +} CRTGeometryMode; +typedef struct { + /** The total size, in pixels, of the pixel buffer storage. Guaranteed to be a power of two. */ + CRTSize size; + + /** The portion of the pixel buffer that has been changed since the last time this set of buffers was provided. */ + CRTSize dirty_size; + + /** The number of individual buffers that adds up to the complete pixel buffer. */ unsigned int number_of_buffers; + + /** A C array of those buffers. */ CRTBuffer *buffers; - unsigned int number_of_runs; - uint8_t *runs; + /** The number of vertices that constitute the output. */ + unsigned int number_of_vertices; + + /** The type of output. */ + CRTGeometryMode geometry_mode; + + /** The size of each vertex in bytes. */ + size_t size_per_vertex; + + /** The vertex data. */ + uint8_t *vertices; } CRTFrame; -typedef uint16_t kCRTPositionType; -typedef uint16_t kCRTTexCoordType; -typedef uint8_t kCRTLateralType; -typedef uint8_t kCRTPhaseType; - +// TODO: these should be private to whomever builds the shaders static const size_t kCRTVertexOffsetOfPosition = 0; static const size_t kCRTVertexOffsetOfTexCoord = 4; static const size_t kCRTVertexOffsetOfLateral = 8; From 4c16d340637211709cef90869b4f45b0affd1398 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 20 Jan 2016 08:27:25 -0500 Subject: [PATCH 060/307] Moved set_tape so that the inline definition of get_next_tape_pulse is visible to it. Also eliminated some dead typedefs. --- Machines/Electron/Electron.cpp | 6 ++++++ Machines/Electron/Electron.hpp | 2 +- Outputs/CRT.cpp | 5 ----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 6aafa81d8..1e8c60b33 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -321,6 +321,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin return cycles; } +void Machine::set_tape(std::shared_ptr tape) +{ + _tape.media = tape; + get_next_tape_pulse(); +} + inline void Machine::get_next_tape_pulse() { _tape.time_into_pulse = 0; diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 89b616348..7913dca92 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -68,7 +68,7 @@ class Machine: public CPU6502::Processor { unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); void set_rom(ROMSlot slot, size_t length, const uint8_t *data); - void set_tape(std::shared_ptr tape) { _tape.media = tape; get_next_tape_pulse(); } + void set_tape(std::shared_ptr tape); void set_key_state(Key key, bool isPressed); diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index a581b3df9..ffef2e553 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -15,11 +15,6 @@ using namespace Outputs; static const uint32_t kCRTFixedPointRange = 0xf7ffffff; static const uint32_t kCRTFixedPointOffset = 0x04000000; -typedef uint16_t kCRTPositionType; -typedef uint16_t kCRTTexCoordType; -typedef uint8_t kCRTLateralType; -typedef uint8_t kCRTPhaseType; - //static const size_t kCRTVertexOffsetOfPosition = 0; //static const size_t kCRTVertexOffsetOfTexCoord = 4; //static const size_t kCRTVertexOffsetOfLateral = 8; From de7218cdf09ebcef1c93fdbd0c2aee51d8618233 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 20 Jan 2016 21:55:38 -0500 Subject: [PATCH 061/307] Added a lot of commenting to the CPU6502 definition, simplifying its construction. Added missing nullability modifier to CSElectron. Fixed bad-habit Objective-C style naming on the Electron's Interrupt enum. --- Machines/Atari2600/Atari2600.cpp | 1 - Machines/Electron/Electron.cpp | 21 ++- Machines/Electron/Electron.hpp | 17 ++- .../Mac/Clock Signal/Wrappers/CSElectron.h | 2 +- Processors/6502/CPU6502.hpp | 132 ++++++++++++++---- 5 files changed, 124 insertions(+), 49 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 5d9a5784b..543e7a406 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -26,7 +26,6 @@ Machine::Machine() : { _crt = new Outputs::CRT(228, 262, 1, 2); memset(_collisions, 0xff, sizeof(_collisions)); - setup6502(); set_reset_line(true); } diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 1e8c60b33..c26609cfa 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -34,7 +34,6 @@ Machine::Machine() : memset(_roms[c], 0xff, 16384); _speaker.set_input_rate(125000); - setup6502(); } Machine::~Machine() @@ -104,7 +103,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if(isReadOperation(operation)) { *value = (uint8_t)(_tape.dataRegister >> 2); - _interruptStatus &= ~InterruptTransmitDataEmpty; + _interruptStatus &= ~Interrupt::TransmitDataEmpty; evaluate_interrupts(); } else @@ -118,9 +117,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin const uint8_t interruptDisable = (*value)&0xf0; if( interruptDisable ) { - if( interruptDisable&0x10 ) _interruptStatus &= ~InterruptDisplayEnd; - if( interruptDisable&0x20 ) _interruptStatus &= ~InterruptRealTimeClock; - if( interruptDisable&0x40 ) _interruptStatus &= ~InterruptHighToneDetect; + if( interruptDisable&0x10 ) _interruptStatus &= ~Interrupt::DisplayEnd; + if( interruptDisable&0x20 ) _interruptStatus &= ~Interrupt::RealTimeClock; + if( interruptDisable&0x40 ) _interruptStatus &= ~Interrupt::HighToneDetect; evaluate_interrupts(); // TODO: NMI (?) } @@ -265,12 +264,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 128*128: update_audio(); - signal_interrupt(InterruptRealTimeClock); + signal_interrupt(Interrupt::RealTimeClock); break; case 284*128: update_audio(); - signal_interrupt(InterruptDisplayEnd); + signal_interrupt(Interrupt::DisplayEnd); break; case cycles_per_frame: @@ -347,21 +346,21 @@ inline void Machine::push_tape_bit(uint16_t bit) if(_tape.bits_since_start == 7) { - _interruptStatus &= ~InterruptTransmitDataEmpty; + _interruptStatus &= ~Interrupt::TransmitDataEmpty; } } else { if((_tape.dataRegister&0x3) == 0x1) { - _interruptStatus |= InterruptTransmitDataEmpty; + _interruptStatus |= Interrupt::TransmitDataEmpty; _tape.bits_since_start = 9; } if(_tape.dataRegister == 0x3ff) - _interruptStatus |= InterruptHighToneDetect; + _interruptStatus |= Interrupt::HighToneDetect; else - _interruptStatus &= ~InterruptHighToneDetect; + _interruptStatus &= ~Interrupt::HighToneDetect; } // printf("."); diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 7913dca92..55b9cc375 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -14,7 +14,6 @@ #include "../../Outputs/Speaker.hpp" #include "../../Storage/Tape/Tape.hpp" #include -#include "Atari2600Inputs.h" namespace Electron { @@ -32,11 +31,11 @@ enum ROMSlot: uint8_t { }; enum Interrupt: uint8_t { - InterruptDisplayEnd = 0x04, - InterruptRealTimeClock = 0x08, - InterruptTransmitDataEmpty = 0x10, - InterruptReceiveDataFull = 0x20, - InterruptHighToneDetect = 0x40 + DisplayEnd = 0x04, + RealTimeClock = 0x08, + TransmitDataEmpty = 0x10, + ReceiveDataFull = 0x20, + HighToneDetect = 0x40 }; enum Key: uint16_t { @@ -58,6 +57,12 @@ enum Key: uint16_t { KeyBreak = 0xffff }; +/*! + @abstract Represents an Acorn Electron. + + @discussion An instance of Electron::Machine represents the current state of an + Acorn Electron. +*/ class Machine: public CPU6502::Processor { public: diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index 474c6f726..ca21d949d 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -14,7 +14,7 @@ - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; - (void)setROM:(nonnull NSData *)rom slot:(int)slot; -- (BOOL)openUEFAtURL:(NSURL *)URL; +- (BOOL)openUEFAtURL:(nonnull NSURL *)URL; - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index b263063c3..8bce31e26 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -25,7 +25,6 @@ enum Register { S }; - enum Flag { Sign = 0x80, Overflow = 0x40, @@ -41,10 +40,24 @@ enum BusOperation { Read, ReadOpcode, Write, Ready, None }; +/*! + Evaluates to `true` if the operation is a read; `false` if it is a write. +*/ #define isReadOperation(v) (v == CPU6502::BusOperation::Read || v == CPU6502::BusOperation::ReadOpcode) +/*! + An opcode that is guaranteed to cause the CPU to jam. +*/ extern const uint8_t JamOpcode; +/*! + @abstact An abstract base class for emulation of a 6502 processor. + + @discussion Subclasses should implement @c perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) in + order to provde the bus on which the 6502 operates. Additional functionality can be provided by the host machine by providing + a jam handler and inserting jam opcodes where appropriate; that will cause call outs when the program counter reaches those + addresses. @c return_from_subroutine can be used to exit from a jammed state. +*/ template class Processor { public: @@ -384,18 +397,6 @@ template class Processor { bool _nmi_line_is_enabled; bool _ready_is_active; - public: - Processor() : - _scheduleProgramsReadPointer(0), - _scheduleProgramsWritePointer(0), - _is_jammed(false), - _jam_handler(nullptr), - _cycles_left_to_run(0), - _ready_line_is_enabled(false), - _ready_is_active(false), - _scheduledPrograms{nullptr, nullptr, nullptr, nullptr} - {} - const MicroOp *get_reset_program() { static const MicroOp reset[] = { CycleFetchOperand, @@ -423,6 +424,41 @@ template class Processor { return reset; } + public: + Processor() : + _scheduleProgramsReadPointer(0), + _scheduleProgramsWritePointer(0), + _is_jammed(false), + _jam_handler(nullptr), + _cycles_left_to_run(0), + _ready_line_is_enabled(false), + _ready_is_active(false), + _scheduledPrograms{nullptr, nullptr, nullptr, nullptr}, + _interruptFlag(Flag::Interrupt), + _s(0), + _nextBusOperation(BusOperation::None) + + { + // only the interrupt flag is defined upon reset but get_flags isn't going to + // mask the other flags so we need to do that, at least + _carryFlag &= Flag::Carry; + _decimalFlag &= Flag::Decimal; + _overflowFlag &= Flag::Overflow; + + // TODO: is this accurate? It feels more likely that a CPU would need to wait + // on an explicit reset command, since the relative startup times of different + // components from power on would be a bit unpredictable. + schedule_program(get_reset_program()); + } + + /*! + Runs the 6502 for a supplied number of cycles. + + @discussion Subclasses must implement @c perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) . + The 6502 will call that method for all bus accesses. The 6502 is guaranteed to perform one bus operation call per cycle. + + @param number_of_cycles The number of cycles to run the 6502 for. + */ void run_for_cycles(int number_of_cycles) { static const MicroOp doBranch[] = { @@ -932,6 +968,14 @@ template class Processor { } } + /*! + Gets the value of a register. + + @see set_value_of_register + + @param r The register to set. + @returns The value of the register. 8-bit registers will be returned as unsigned. + */ uint16_t get_value_of_register(Register r) { switch (r) { @@ -947,6 +991,14 @@ template class Processor { } } + /*! + Sets the value of a register. + + @see get_value_of_register + + @param r The register to set. + @param value The value to set. If the register is only 8 bit, the value will be truncated. + */ void set_value_of_register(Register r, uint16_t value) { switch (r) { @@ -961,23 +1013,10 @@ template class Processor { } } - void setup6502() - { - // only the interrupt flag is defined upon reset but get_flags isn't going to - // mask the other flags so we need to do that, at least - _interruptFlag = Flag::Interrupt; - _carryFlag &= Flag::Carry; - _decimalFlag &= Flag::Decimal; - _overflowFlag &= Flag::Overflow; - _s = 0; - _nextBusOperation = BusOperation::None; - - // TODO: is this accurate? It feels more likely that a CPU would need to wait - // on an explicit reset command, since the relative startup times of different - // components from power on would be a bit unpredictable. - schedule_program(get_reset_program()); - } - + /*! + Interrupts current execution flow to perform an RTS and, if the 6502 is currently jammed, + to unjam it. + */ void return_from_subroutine() { _s++; @@ -990,6 +1029,11 @@ template class Processor { } } + /*! + Sets the current level of the RDY line. + + @param active @c true if the line is logically active; @c false otherwise. + */ void set_ready_line(bool active) { if(active) @@ -1001,26 +1045,54 @@ template class Processor { } } + /*! + Sets the current level of the RST line. + + @param active @c true if the line is logically active; @c false otherwise. + */ void set_reset_line(bool active) { _reset_line_is_enabled = active; } + /*! + Sets the current level of the IRQ line. + + @param active @c true if the line is logically active; @c false otherwise. + */ void set_irq_line(bool active) { _irq_line_is_enabled = active; } + /*! + Sets the current level of the NMI line. + + @param active `true` if the line is logically active; `false` otherwise. + */ void set_nmi_line(bool active) { + // TODO: NMI is edge triggered, not level, and in any case _nmi_line_is_enabled + // is not honoured elsewhere. So NMI is yet to be implemented. _nmi_line_is_enabled = active; } + /*! + Queries whether the 6502 is now 'jammed'; i.e. has entered an invalid state + such that it will not of itself perform any more meaningful processing. + + @returns @c true if the 6502 is jammed; @c false otherwise. + */ bool is_jammed() { return _is_jammed; } + /*! + Installs a jam handler. Jam handlers are notified if a running 6502 jams. + + @param handler The class instance that will be this 6502's jam handler from now on. + */ void set_jam_handler(JamHandler *handler) { _jam_handler = handler; From 9fa35dd559a8605849030d5460a92c9ef4797492 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 20 Jan 2016 21:58:34 -0500 Subject: [PATCH 062/307] Killed some whitespace lines. --- Processors/6502/CPU6502.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 8bce31e26..1541a9317 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -453,10 +453,10 @@ template class Processor { /*! Runs the 6502 for a supplied number of cycles. - + @discussion Subclasses must implement @c perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) . The 6502 will call that method for all bus accesses. The 6502 is guaranteed to perform one bus operation call per cycle. - + @param number_of_cycles The number of cycles to run the 6502 for. */ void run_for_cycles(int number_of_cycles) @@ -970,9 +970,9 @@ template class Processor { /*! Gets the value of a register. - + @see set_value_of_register - + @param r The register to set. @returns The value of the register. 8-bit registers will be returned as unsigned. */ @@ -993,9 +993,9 @@ template class Processor { /*! Sets the value of a register. - + @see get_value_of_register - + @param r The register to set. @param value The value to set. If the register is only 8 bit, the value will be truncated. */ @@ -1057,7 +1057,7 @@ template class Processor { /*! Sets the current level of the IRQ line. - + @param active @c true if the line is logically active; @c false otherwise. */ void set_irq_line(bool active) From baef1ccd578f0c83f015939c51b65cece497e642 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Jan 2016 08:39:34 -0500 Subject: [PATCH 063/307] Made the constructor protected, to emphasise that this class isn't for instantiation. Also added extra comments aplenty. --- Processors/6502/CPU6502.hpp | 78 +++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 1541a9317..698ba47fc 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -14,6 +14,9 @@ namespace CPU6502 { +/* + The list of registers that can be accessed via @c set_value_of_register and @c set_value_of_register. +*/ enum Register { LastOperationAddress, ProgramCounter, @@ -25,6 +28,9 @@ enum Register { S }; +/* + Flags as defined on the 6502; can be used to decode the result of @c get_flags or to form a value for @c set_flags. +*/ enum Flag { Sign = 0x80, Overflow = 0x40, @@ -36,6 +42,13 @@ enum Flag { Carry = 0x01 }; +/*! + Subclasses will be given the task of performing bus operations, allowing them to provide whatever interface they like + between a 6502 and the rest of the system. @c BusOperation lists the types of bus operation that may be requested. + + @c None is reserved for internal use. It will never be requested from a subclass. It is safe always to use the + isReadOperation macro to make a binary choice between reading and writing. +*/ enum BusOperation { Read, ReadOpcode, Write, Ready, None }; @@ -51,8 +64,8 @@ enum BusOperation { extern const uint8_t JamOpcode; /*! - @abstact An abstract base class for emulation of a 6502 processor. - + @abstact An abstract base class for emulation of a 6502 processor via the curiously recurring template pattern/f-bounded polymorphism. + @discussion Subclasses should implement @c perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) in order to provde the bus on which the 6502 operates. Additional functionality can be provided by the host machine by providing a jam handler and inserting jam opcodes where appropriate; that will cause call outs when the program counter reaches those @@ -68,6 +81,11 @@ template class Processor { private: + /* + This emulation funcitons by decomposing instructions into micro programs, consisting of the micro operations + as per the enum below. Each micro op takes at most one cycle. By convention, those called CycleX take a cycle + to perform whereas those called OperationX occur for free (so, in effect, their cost is loaded onto the next cycle). + */ enum MicroOp { CycleFetchOperation, CycleFetchOperand, OperationDecodeOperation, CycleIncPCPushPCH, CyclePushPCH, CyclePushPCL, CyclePushA, CyclePushOperand, @@ -111,33 +129,69 @@ template class Processor { } bytes; }; + /* + Storage for the 6502 registers; F is stored as individual flags. + */ RegisterPair _pc, _lastOperationPC; uint8_t _a, _x, _y, _s; uint8_t _carryFlag, _negativeResult, _zeroResult, _decimalFlag, _overflowFlag, _interruptFlag; + /* + Temporary state for the micro programs. + */ uint8_t _operation, _operand; RegisterPair _address, _nextAddress; + /* + Up to four programs can be scheduled; each will be carried out in turn. This + storage maintains pointers to the scheduled list of programs. + + Programs should be terminated by an OperationMoveToNextProgram, causing this + queue to take that step. + */ const MicroOp *_scheduledPrograms[4]; unsigned int _scheduleProgramsWritePointer, _scheduleProgramsReadPointer, _scheduleProgramProgramCounter; + /* + Temporary storage allowing a common dispatch point for calling perform_bus_operation; + possibly deferring is no longer of value. + */ BusOperation _nextBusOperation; uint16_t _busAddress; uint8_t *_busValue; - uint64_t _externalBus; + /*! + Schedules a new program, adding it to the end of the queue. Programs should be + terminated with a OperationMoveToNextProgram. No attempt to copy the program + is made; a non-owning reference is kept. + @param program The program to schedule. + */ void schedule_program(const MicroOp *program) { _scheduledPrograms[_scheduleProgramsWritePointer] = program; _scheduleProgramsWritePointer = (_scheduleProgramsWritePointer+1)&3; } + /*! + Gets the flags register. + + @see set_flags + + @returns The current value of the flags register. + */ uint8_t get_flags() { return _carryFlag | _overflowFlag | _interruptFlag | (_negativeResult & 0x80) | (_zeroResult ? 0 : Flag::Zero) | Flag::Always | _decimalFlag; } + /*! + Sets the flags register. + + @see set_flags + + @param flags The new value of the flags register. + */ void set_flags(uint8_t flags) { _carryFlag = flags & Flag::Carry; @@ -148,6 +202,11 @@ template class Processor { _decimalFlag = flags & Flag::Decimal; } + /*! + Schedules the program corresponding to the specified operation. + + @param operation The operation code for which to schedule a program. + */ void decode_operation(uint8_t operation) { #define Program(...) {__VA_ARGS__, OperationMoveToNextProgram} @@ -397,6 +456,11 @@ template class Processor { bool _nmi_line_is_enabled; bool _ready_is_active; + /*! + Gets the program representing an RST response. + + @returns The program representing an RST response. + */ const MicroOp *get_reset_program() { static const MicroOp reset[] = { CycleFetchOperand, @@ -411,6 +475,11 @@ template class Processor { return reset; } + /*! + Gets the program representing an IRQ response. + + @returns The program representing an IRQ response. + */ const MicroOp *get_irq_program() { static const MicroOp reset[] = { CyclePushPCH, @@ -424,7 +493,7 @@ template class Processor { return reset; } - public: + protected: Processor() : _scheduleProgramsReadPointer(0), _scheduleProgramsWritePointer(0), @@ -451,6 +520,7 @@ template class Processor { schedule_program(get_reset_program()); } + public: /*! Runs the 6502 for a supplied number of cycles. From 48ddd3c497f1019b6e4aa0fc9aa938850c50ae22 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Jan 2016 21:17:47 -0500 Subject: [PATCH 064/307] Set about documenting the CRT; while doing so decided to add an optional clock divider for input; having done so decided to try to exploit it with the Electron. --- Machines/Atari2600/Atari2600.cpp | 2 +- Machines/Electron/Electron.cpp | 75 +++++++++++++++++++++++--------- Machines/Electron/Electron.hpp | 3 +- Outputs/CRT.cpp | 14 +++--- Outputs/CRT.hpp | 40 ++++++++++++++++- 5 files changed, 103 insertions(+), 31 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 543e7a406..2450d9889 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -223,7 +223,7 @@ void Machine::output_pixels(unsigned int count) { case OutputState::Blank: _crt->output_blank(_lastOutputStateDuration); break; case OutputState::Sync: _crt->output_sync(_lastOutputStateDuration); break; - case OutputState::Pixel: _crt->output_data(_lastOutputStateDuration); break; + case OutputState::Pixel: _crt->output_data(_lastOutputStateDuration, 1); break; } _lastOutputStateDuration = 0; _lastOutputState = state; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c26609cfa..37480da1b 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -12,10 +12,10 @@ using namespace Electron; -static const int cycles_per_line = 128; -static const int cycles_per_frame = 312*cycles_per_line; -static const int crt_cycles_multiplier = 8; -static const int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; +static const unsigned int cycles_per_line = 128; +static const unsigned int cycles_per_frame = 312*cycles_per_line; +static const unsigned int crt_cycles_multiplier = 8; +static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; Machine::Machine() : _interruptControl(0), @@ -459,8 +459,15 @@ inline void Machine::update_display() _crt->output_blank(15 * crt_cycles_multiplier); _displayOutputPosition += 15; - _crt->allocate_write_area(80 * crt_cycles_multiplier); - _currentLine = (uint8_t *)_crt->get_write_target_for_buffer(0); + switch(_screenMode) + { + case 0: case 3: _currentOutputDivider = 1; break; + case 1: case 4: case 6: _currentOutputDivider = 2; break; + case 2: case 5: _currentOutputDivider = 4; break; + } + + _crt->allocate_write_area(80 * crt_cycles_multiplier / _currentOutputDivider); + _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); if(current_line == first_graphics_line) _startLineAddress = _startScreenAddress; @@ -469,6 +476,32 @@ inline void Machine::update_display() if(line_position >= 24 && line_position < 104) { + unsigned int newDivider = 0; + switch(_screenMode) + { + case 0: case 3: newDivider = 1; break; + case 1: case 4: case 6: newDivider = 2; break; + case 2: case 5: newDivider = 4; break; + } + if(newDivider != _currentOutputDivider) + { + _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); + _currentOutputDivider = newDivider; + _crt->allocate_write_area((int)((104 - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); + _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); + } + + // TODO: determine whether we need to change divider +// int pixels_to_output = std::max(_frameCycles - _displayOutputPosition, 104 - line_position); +// if(_screenMode >= 4) +// { +// // just shifting wouldn't be enough if both +// if(_displayOutputPosition&1) pixels_to_output++; +// pixels_to_output >>= 1; +// } +// +// swi + if(_currentLine && ((_screenMode < 4) || !(line_position&1))) { if(_currentScreenAddress&32768) @@ -477,7 +510,6 @@ inline void Machine::update_display() } uint8_t pixels = _ram[_currentScreenAddress]; _currentScreenAddress = _currentScreenAddress+8; - int output_ptr = (line_position - 24) << 3; switch(_screenMode) { @@ -486,49 +518,52 @@ inline void Machine::update_display() for(int c = 0; c < 8; c++) { uint8_t colour = (pixels&0x80) >> 4; - _currentLine[output_ptr + c] = _palette[colour]; + _writePointer[c] = _palette[colour]; pixels <<= 1; } + _writePointer += 8; break; case 1: - for(int c = 0; c < 8; c += 2) + for(int c = 0; c < 4; c ++) { uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x08) >> 2); - _currentLine[output_ptr + c + 0] = _currentLine[output_ptr + c + 1] = _palette[colour]; + _writePointer[c] = _palette[colour]; pixels <<= 1; } + _writePointer += 4; break; case 2: - for(int c = 0; c < 8; c += 4) + for(int c = 0; c < 2; c ++) { uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1); - _currentLine[output_ptr + c + 0] = _currentLine[output_ptr + c + 1] = - _currentLine[output_ptr + c + 2] = _currentLine[output_ptr + c + 3] = _palette[colour]; + _writePointer[c] = _palette[colour]; pixels <<= 1; } + _writePointer += 2; break; case 5: - for(int c = 0; c < 16; c += 4) + for(int c = 0; c < 4; c ++) { uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x08) >> 2); - _currentLine[output_ptr + c + 0] = _currentLine[output_ptr + c + 1] = - _currentLine[output_ptr + c + 2] = _currentLine[output_ptr + c + 3] = _palette[colour]; + _writePointer[c] = _palette[colour]; pixels <<= 1; } + _writePointer += 4; break; default: case 4: case 6: - for(int c = 0; c < 16; c += 2) + for(int c = 0; c < 8; c ++) { uint8_t colour = (pixels&0x80) >> 4; - _currentLine[output_ptr + c] = _currentLine[output_ptr + c + 1] = _palette[colour]; + _writePointer[c] = _palette[colour]; pixels <<= 1; } + _writePointer += 8; break; } } @@ -545,10 +580,10 @@ inline void Machine::update_display() else _startLineAddress++; - _currentLine = nullptr; - _crt->output_data(80 * crt_cycles_multiplier); + _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); _crt->output_blank(24 * crt_cycles_multiplier); _displayOutputPosition += 24; + _currentLine = nullptr; } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 55b9cc375..ae711ed52 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -110,7 +110,8 @@ class Machine: public CPU6502::Processor { // Display generation. uint16_t _startLineAddress, _currentScreenAddress; int _currentOutputLine; - uint8_t *_currentLine; + unsigned int _currentOutputDivider; + uint8_t *_currentLine, *_writePointer; // Tape. struct Tape { diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index ffef2e553..307be6caa 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -175,7 +175,7 @@ CRT::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsi return proposedEvent; } -void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type) +void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type) { number_of_cycles *= _time_multiplier; @@ -252,7 +252,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo position_y(5) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.y + _beamWidth[lengthMask].y); // if this is a data run then advance the buffer pointer - if(type == Type::Data) tex_x += next_run_length / _time_multiplier; + if(type == Type::Data) tex_x += next_run_length / (_time_multiplier * source_divider); // if this is a data or level run then store the end point tex_x(2) = tex_x(3) = tex_x(5) = tex_x; @@ -356,28 +356,28 @@ void CRT::output_sync(unsigned int number_of_cycles) { bool _hsync_requested = !_is_receiving_sync; // ensure this really is edge triggered; someone calling output_sync twice in succession shouldn't trigger it twice _is_receiving_sync = true; - advance_cycles(number_of_cycles, _hsync_requested, false, true, Type::Sync); + advance_cycles(number_of_cycles, 1, _hsync_requested, false, true, Type::Sync); } void CRT::output_blank(unsigned int number_of_cycles) { bool _vsync_requested = _is_receiving_sync; _is_receiving_sync = false; - advance_cycles(number_of_cycles, false, _vsync_requested, false, Type::Blank); + advance_cycles(number_of_cycles, 1, false, _vsync_requested, false, Type::Blank); } void CRT::output_level(unsigned int number_of_cycles) { bool _vsync_requested = _is_receiving_sync; _is_receiving_sync = false; - advance_cycles(number_of_cycles, false, _vsync_requested, false, Type::Level); + advance_cycles(number_of_cycles, 1, false, _vsync_requested, false, Type::Level); } -void CRT::output_data(unsigned int number_of_cycles) +void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { bool _vsync_requested = _is_receiving_sync; _is_receiving_sync = false; - advance_cycles(number_of_cycles, false, _vsync_requested, false, Type::Data); + advance_cycles(number_of_cycles, source_divider, false, _vsync_requested, false, Type::Data); } #pragma mark - Buffer supply diff --git a/Outputs/CRT.hpp b/Outputs/CRT.hpp index 89e981da1..af87c4f29 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT.hpp @@ -53,10 +53,46 @@ class CRT { void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display); + /*! Output at the sync level. + + @param number_of_cycles The amount of time to putput sync for. + */ void output_sync(unsigned int number_of_cycles); + + /*! Output at the blanking level. + + @param number_of_cycles The amount of time to putput the blanking level for. + */ void output_blank(unsigned int number_of_cycles); + + /*! Outputs the first written to the most-recently created run of data repeatedly for a prolonged period. + + @param number_of_cycles The number of cycles to repeat the output for. + */ void output_level(unsigned int number_of_cycles); - void output_data(unsigned int number_of_cycles); + + /*! Declares that the caller has created a run of data via @c allocate_write_area and @c get_write_target_for_buffer + that is at least @c number_of_cycles long, and that the first @c number_of_cycles/source_divider should be spread + over that amount of time. + + @param number_of_cycles The amount of data to output. + + @param source_divider A divider for source data; if the divider is 1 then one source pixel is output every cycle, + if it is 2 then one source pixel covers two cycles; if it is n then one source pixel covers n cycles. + */ + void output_data(unsigned int number_of_cycles, unsigned int source_divider); + + /*! Outputs a colour burst. + + @param number_of_cycles The length of the colour burst. + + @param phase The initial phase of the colour burst in a measuring system with 256 units + per circle, e.g. 0 = 0 degrees, 128 = 180 degrees, 256 = 360 degree. + + @param magnitude The magnitude of the colour burst in 1/256ths of the magnitude of the + positive portion of the wave. + */ + void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t magnitude); class Delegate { public: @@ -110,7 +146,7 @@ class CRT { enum Type { Sync, Level, Data, Blank } type; - void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, bool vsync_charging, Type type); + void advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, bool vsync_charging, Type type); // the inner entry point that determines whether and when the next sync event will occur within // the current output window From 34640cec935e7b2bdd360e90a9710f32dfcb337a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Jan 2016 21:28:09 -0500 Subject: [PATCH 065/307] Started trying to move some logic out of the inner loop. --- Machines/Electron/Electron.cpp | 106 +++++++++++++++------------------ 1 file changed, 49 insertions(+), 57 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 37480da1b..68fb251eb 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -476,6 +476,8 @@ inline void Machine::update_display() if(line_position >= 24 && line_position < 104) { + // determine whether the pixel clock divider has changed; if so write out the old + // data and start a new run unsigned int newDivider = 0; switch(_screenMode) { @@ -491,83 +493,73 @@ inline void Machine::update_display() _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); } - // TODO: determine whether we need to change divider -// int pixels_to_output = std::max(_frameCycles - _displayOutputPosition, 104 - line_position); -// if(_screenMode >= 4) -// { -// // just shifting wouldn't be enough if both -// if(_displayOutputPosition&1) pixels_to_output++; -// pixels_to_output >>= 1; -// } -// -// swi - if(_currentLine && ((_screenMode < 4) || !(line_position&1))) + int pixels_to_output = std::min(_frameCycles - _displayOutputPosition, 104 - line_position); + _displayOutputPosition += pixels_to_output; + if(_screenMode >= 4) { - if(_currentScreenAddress&32768) - { - _currentScreenAddress = _screenModeBaseAddress + (_currentScreenAddress&32767); - } - uint8_t pixels = _ram[_currentScreenAddress]; - _currentScreenAddress = _currentScreenAddress+8; + // just shifting wouldn't be enough if both + if(_displayOutputPosition&1) pixels_to_output++; + pixels_to_output >>= 1; + } +#define GetNextPixels() \ + if(_currentScreenAddress&32768)\ + {\ + _currentScreenAddress = _screenModeBaseAddress + (_currentScreenAddress&32767);\ + }\ + uint8_t pixels = _ram[_currentScreenAddress];\ + _currentScreenAddress = _currentScreenAddress+8 + + if(pixels_to_output) + { switch(_screenMode) { - case 0: - case 3: - for(int c = 0; c < 8; c++) + default: + case 0: case 3: case 4: case 6: + while(pixels_to_output--) { - uint8_t colour = (pixels&0x80) >> 4; - _writePointer[c] = _palette[colour]; - pixels <<= 1; + GetNextPixels(); + for(int c = 0; c < 8; c++) + { + uint8_t colour = (pixels&0x80) >> 4; + _writePointer[c] = _palette[colour]; + pixels <<= 1; + } + _writePointer += 8; } - _writePointer += 8; break; case 1: - for(int c = 0; c < 4; c ++) + case 5: + while(pixels_to_output--) { - uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x08) >> 2); - _writePointer[c] = _palette[colour]; - pixels <<= 1; + GetNextPixels(); + for(int c = 0; c < 4; c ++) + { + uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x08) >> 2); + _writePointer[c] = _palette[colour]; + pixels <<= 1; + } + _writePointer += 4; } - _writePointer += 4; break; case 2: - for(int c = 0; c < 2; c ++) + while(pixels_to_output--) { - uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1); - _writePointer[c] = _palette[colour]; - pixels <<= 1; + GetNextPixels(); + for(int c = 0; c < 2; c ++) + { + uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1); + _writePointer[c] = _palette[colour]; + pixels <<= 1; + } + _writePointer += 2; } - _writePointer += 2; - break; - - case 5: - for(int c = 0; c < 4; c ++) - { - uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x08) >> 2); - _writePointer[c] = _palette[colour]; - pixels <<= 1; - } - _writePointer += 4; - break; - - default: - case 4: - case 6: - for(int c = 0; c < 8; c ++) - { - uint8_t colour = (pixels&0x80) >> 4; - _writePointer[c] = _palette[colour]; - pixels <<= 1; - } - _writePointer += 8; break; } } - _displayOutputPosition++; } if(line_position == 104) From 82cb1c365c625137fb2de1e63f4ce6bf38f30225 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Jan 2016 21:35:04 -0500 Subject: [PATCH 066/307] Further simplified inner loops. --- Machines/Electron/Electron.cpp | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 68fb251eb..5bf777fa8 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -520,12 +520,16 @@ inline void Machine::update_display() while(pixels_to_output--) { GetNextPixels(); - for(int c = 0; c < 8; c++) - { - uint8_t colour = (pixels&0x80) >> 4; - _writePointer[c] = _palette[colour]; - pixels <<= 1; - } + + _writePointer[0] = _palette[(pixels&0x80) >> 4]; + _writePointer[1] = _palette[(pixels&0x40) >> 3]; + _writePointer[2] = _palette[(pixels&0x20) >> 2]; + _writePointer[3] = _palette[(pixels&0x10) >> 1]; + _writePointer[4] = _palette[(pixels&0x08) >> 0]; + _writePointer[5] = _palette[(pixels&0x04) << 1]; + _writePointer[6] = _palette[(pixels&0x02) << 2]; + _writePointer[7] = _palette[(pixels&0x01) << 3]; + _writePointer += 8; } break; @@ -535,12 +539,12 @@ inline void Machine::update_display() while(pixels_to_output--) { GetNextPixels(); - for(int c = 0; c < 4; c ++) - { - uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x08) >> 2); - _writePointer[c] = _palette[colour]; - pixels <<= 1; - } + + _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x08) >> 2)]; + _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x04) >> 1)]; + _writePointer[2] = _palette[((pixels&0x20) >> 2) | ((pixels&0x02) >> 0)]; + _writePointer[3] = _palette[((pixels&0x10) >> 1) | ((pixels&0x01) << 1)]; + _writePointer += 4; } break; @@ -549,12 +553,8 @@ inline void Machine::update_display() while(pixels_to_output--) { GetNextPixels(); - for(int c = 0; c < 2; c ++) - { - uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1); - _writePointer[c] = _palette[colour]; - pixels <<= 1; - } + _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1)]; + _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x10) >> 2) | ((pixels&0x04) >> 1) | ((pixels&0x01) >> 0)]; _writePointer += 2; } break; From 0efe4b312cc5465dc1b7f657e14a0352f0c1bc88 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Jan 2016 22:16:52 -0500 Subject: [PATCH 067/307] Disabled my various bits of rate interchange debugging; improved test for when to call update_display due to a RAM write. --- Machines/Electron/Electron.cpp | 21 +++++++++---- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 8 ++--- Outputs/Speaker.hpp | 30 +++++++++---------- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 5bf777fa8..2048133a1 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -17,6 +17,8 @@ static const unsigned int cycles_per_frame = 312*cycles_per_line; static const unsigned int crt_cycles_multiplier = 8; static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; +const int first_graphics_line = 28; + Machine::Machine() : _interruptControl(0), _frameCycles(0), @@ -52,20 +54,28 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { - // TODO: range check on address; a lot of the time the machine will be running code outside of - // the screen area, meaning that no update is required. - update_display(); + // If we're still before the display will start to be painted, or the address is + // less than both the current line address and 0x3000, (the minimum screen mode + // base address) then there's no way this write can affect the current frame. Sp + // no need to flush the display. Otherwise, output up until now so that any + // write doesn't have retroactive effect on the video output. + if(!( + (_frameCycles < first_graphics_line * cycles_per_line) || + (address < _startLineAddress && address < 0x3000) + )) + update_display(); _ram[address] = *value; } - // TODO: RAM timing for Modes 0–3 + // for the entire frame, RAM is accessible only on odd cycles; in modes below 4 + // it's also accessible only outside of the pixel regions cycles += (_frameCycles&1)^1; if(_screenMode < 4) { const int current_line = _frameCycles >> 7; const int line_position = _frameCycles & 127; - if(current_line >= 28 && current_line < 28+256 && line_position >= 24 && line_position < 104) + if(current_line >= first_graphics_line && current_line < first_graphics_line+256 && line_position >= 24 && line_position < 104) cycles = (unsigned int)(104 - line_position); } } @@ -410,7 +420,6 @@ inline void Machine::update_display() { const int lines_of_hsync = 3; const int end_of_hsync = lines_of_hsync * cycles_per_line; - const int first_graphics_line = 28; if(_frameCycles >= end_of_hsync) { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index e04ce5154..54ce70b87 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -15,12 +15,12 @@ @implementation CSElectron { Electron::Machine _electron; - NSTimeInterval _periodicStart; - int _numberOfCycles; +// NSTimeInterval _periodicStart; +// int _numberOfCycles; } - (void)doRunForNumberOfCycles:(int)numberOfCycles { - _numberOfCycles += numberOfCycles; +/* _numberOfCycles += numberOfCycles; NSTimeInterval timeNow = [NSDate timeIntervalSinceReferenceDate]; NSTimeInterval difference = timeNow - _periodicStart; if(difference > 1.0) @@ -28,7 +28,7 @@ NSLog(@"cycles: %0.0f", (double)_numberOfCycles / difference); _periodicStart = timeNow; _numberOfCycles = 0; - } + }*/ _electron.run_for_cycles(numberOfCycles); } diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 00faf36b3..7a9d11b8e 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -77,16 +77,16 @@ template class Filter: public Speaker { { if(_coefficients_are_dirty) update_filter_coefficients(); - _periodic_cycles += input_cycles; - time_t time_now = time(nullptr); - if(time_now > _periodic_start) - { - printf("input audio samples: %d\n", _periodic_cycles); - printf("output audio samples: %d\n", _periodic_output); - _periodic_cycles = 0; - _periodic_output = 0; - _periodic_start = time_now; - } +// _periodic_cycles += input_cycles; +// time_t time_now = time(nullptr); +// if(time_now > _periodic_start) +// { +// printf("input audio samples: %d\n", _periodic_cycles); +// printf("output audio samples: %d\n", _periodic_output); +// _periodic_cycles = 0; +// _periodic_output = 0; +// _periodic_start = time_now; +// } // point sample for now, as a temporary measure input_cycles += _input_cycles_carry; @@ -111,17 +111,17 @@ template class Filter: public Speaker { if(steps > 1) static_cast(this)->skip_samples((unsigned int)(steps-1)); input_cycles -= steps; - _periodic_output ++; +// _periodic_output ++; } _input_cycles_carry = input_cycles; } - Filter() : _periodic_cycles(0), _periodic_start(0) {} + Filter() {} // _periodic_cycles(0), _periodic_start(0) private: - time_t _periodic_start; - int _periodic_cycles; - int _periodic_output; +// time_t _periodic_start; +// int _periodic_cycles; +// int _periodic_output; SignalProcessing::Stepper *_stepper; int _input_cycles_carry; From 43a7d1b7ae7c7c49900c6294db84138204894994 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 23 Jan 2016 00:14:44 -0500 Subject: [PATCH 068/307] It'll be helpful to be able to isolate the tape interface when I'm faking a fast load. So factored out. --- Machines/Electron/Electron.cpp | 198 +++++++++++++++++++-------------- Machines/Electron/Electron.hpp | 68 +++++++---- 2 files changed, 163 insertions(+), 103 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 2048133a1..dd1cb49b5 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -25,8 +25,7 @@ Machine::Machine() : _displayOutputPosition(0), _audioOutputPosition(0), _audioOutputPositionError(0), - _currentOutputLine(0), - _tape({.is_running = false, .dataRegister = 0}) + _currentOutputLine(0) { memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); @@ -112,9 +111,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x4: if(isReadOperation(operation)) { - *value = (uint8_t)(_tape.dataRegister >> 2); - _interruptStatus &= ~Interrupt::TransmitDataEmpty; - evaluate_interrupts(); + *value = _tape.get_data_register(); + _tape.clear_interrupts(Interrupt::TransmitDataEmpty); } else { @@ -188,8 +186,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _speaker.set_is_enabled(new_speaker_is_enabled); } - // TODO: tape mode, tape motor, caps lock LED - _tape.is_running = ((*value)&0x40) ? true : false; + _tape.set_is_running(((*value)&0x40) ? true : false); + // TODO: tape mode, caps lock LED } break; default: @@ -292,89 +290,14 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin break; } - if(_tape.is_running && _tape.media != nullptr) - { - _tape.time_into_pulse += (unsigned int)_tape.pulseStepper->step(); - if(_tape.time_into_pulse == _tape.currentPulse.length.length) - { - get_next_tape_pulse(); - - _tape.crossings[0] = _tape.crossings[1]; - _tape.crossings[1] = _tape.crossings[2]; - _tape.crossings[2] = _tape.crossings[3]; - - _tape.crossings[3] = Tape::Unrecognised; - if(_tape.currentPulse.type != Storage::Tape::Pulse::Zero) - { - float pulse_length = (float)_tape.currentPulse.length.length / (float)_tape.currentPulse.length.clock_rate; - if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _tape.crossings[3] = Tape::Short; - if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _tape.crossings[3] = Tape::Long; - } - - if(_tape.crossings[0] == Tape::Long && _tape.crossings[1] == Tape::Long) - { - push_tape_bit(0); - _tape.crossings[1] = Tape::Unrecognised; - } - else - { - if(_tape.crossings[0] == Tape::Short && _tape.crossings[1] == Tape::Short && _tape.crossings[2] == Tape::Short && _tape.crossings[3] == Tape::Short) - { - push_tape_bit(1); - _tape.crossings[3] = Tape::Unrecognised; - } - } - } - } + _tape.run_for_cycle(); return cycles; } void Machine::set_tape(std::shared_ptr tape) { - _tape.media = tape; - get_next_tape_pulse(); -} - -inline void Machine::get_next_tape_pulse() -{ - _tape.time_into_pulse = 0; - _tape.currentPulse = _tape.media->get_next_pulse(); - if(_tape.pulseStepper == nullptr || _tape.currentPulse.length.clock_rate != _tape.pulseStepper->get_output_rate()) - { - _tape.pulseStepper = std::shared_ptr(new SignalProcessing::Stepper(_tape.currentPulse.length.clock_rate, 2000000)); - } -} - -inline void Machine::push_tape_bit(uint16_t bit) -{ - _tape.dataRegister = (uint16_t)((_tape.dataRegister >> 1) | (bit << 10)); - - if(_tape.bits_since_start) - { - _tape.bits_since_start--; - - if(_tape.bits_since_start == 7) - { - _interruptStatus &= ~Interrupt::TransmitDataEmpty; - } - } - else - { - if((_tape.dataRegister&0x3) == 0x1) - { - _interruptStatus |= Interrupt::TransmitDataEmpty; - _tape.bits_since_start = 9; - } - - if(_tape.dataRegister == 0x3ff) - _interruptStatus |= Interrupt::HighToneDetect; - else - _interruptStatus &= ~Interrupt::HighToneDetect; - } -// printf("."); - - evaluate_interrupts(); + _tape.set_tape(tape); } void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) @@ -395,6 +318,12 @@ inline void Machine::signal_interrupt(Electron::Interrupt interrupt) evaluate_interrupts(); } +void Machine::tape_did_change_interrupt_status(Tape *tape) +{ + _interruptStatus = (_interruptStatus & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | _tape.get_interrupt_status(); + evaluate_interrupts(); +} + inline void Machine::evaluate_interrupts() { if(_interruptStatus & _interruptControl) @@ -664,3 +593,104 @@ Machine::Speaker::~Speaker() { // fclose(rawStream); } + +/* + Tape +*/ + +Tape::Tape() : _is_running(false), _data_register(0), _delegate(nullptr) {} + +void Tape::set_tape(std::shared_ptr tape) +{ + _tape = tape; + get_next_tape_pulse(); +} + +inline void Tape::get_next_tape_pulse() +{ + _time_into_pulse = 0; + _current_pulse = _tape->get_next_pulse(); + if(_pulse_stepper == nullptr || _current_pulse.length.clock_rate != _pulse_stepper->get_output_rate()) + { + _pulse_stepper = std::shared_ptr(new SignalProcessing::Stepper(_current_pulse.length.clock_rate, 2000000)); + } +} + +inline void Tape::push_tape_bit(uint16_t bit) +{ + _data_register = (uint16_t)((_data_register >> 1) | (bit << 10)); + + uint8_t old_interrupt_status = _interrupt_status; + + if(_bits_since_start) + { + _bits_since_start--; + + if(_bits_since_start == 7) + { + _interrupt_status &= ~Interrupt::TransmitDataEmpty; + } + } + else + { + if((_data_register&0x3) == 0x1) + { + _interrupt_status |= Interrupt::TransmitDataEmpty; + _bits_since_start = 9; + } + + if(_data_register == 0x3ff) + _interrupt_status |= Interrupt::HighToneDetect; + else + _interrupt_status &= ~Interrupt::HighToneDetect; + } + + if(old_interrupt_status != _interrupt_status && _delegate) _delegate->tape_did_change_interrupt_status(this); +} + +inline void Tape::clear_interrupts(uint8_t interrupts) +{ + if(_interrupt_status & interrupts) + { + _interrupt_status &= ~interrupts; + if(_delegate) _delegate->tape_did_change_interrupt_status(this); + } +} + +inline void Tape::run_for_cycle() +{ + if(_is_running && _tape != nullptr) + { + _time_into_pulse += (unsigned int)_pulse_stepper->step(); + if(_time_into_pulse == _current_pulse.length.length) + { + get_next_tape_pulse(); + + _crossings[0] = _crossings[1]; + _crossings[1] = _crossings[2]; + _crossings[2] = _crossings[3]; + + _crossings[3] = Tape::Unrecognised; + if(_current_pulse.type != Storage::Tape::Pulse::Zero) + { + float pulse_length = (float)_current_pulse.length.length / (float)_current_pulse.length.clock_rate; + if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _crossings[3] = Tape::Short; + if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _crossings[3] = Tape::Long; + } + + if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long) + { + push_tape_bit(0); + _crossings[1] = Tape::Unrecognised; + } + else + { + if(_crossings[0] == Tape::Short && _crossings[1] == Tape::Short && _crossings[2] == Tape::Short && _crossings[3] == Tape::Short) + { + push_tape_bit(1); + _crossings[3] = Tape::Unrecognised; + } + } + } + } +} diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index ae711ed52..e83f5b84a 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -57,13 +57,57 @@ enum Key: uint16_t { KeyBreak = 0xffff }; -/*! +class Tape { + public: + Tape(); + + void set_tape(std::shared_ptr tape); + + inline uint8_t get_data_register() { return (uint8_t)(_data_register >> 2); } + + inline uint8_t get_interrupt_status() { return _interrupt_status; } + inline void clear_interrupts(uint8_t interrupts); + + class Delegate { + public: + virtual void tape_did_change_interrupt_status(Tape *tape) = 0; + }; + inline void set_delegate(Delegate *delegate) { _delegate = delegate; } + + inline void run_for_cycle(); + + void set_is_running(bool is_running) { _is_running = is_running; } + + private: + inline void push_tape_bit(uint16_t bit); + inline void get_next_tape_pulse(); + + std::shared_ptr _tape; + + Storage::Tape::Pulse _current_pulse; + std::shared_ptr _pulse_stepper; + uint32_t _time_into_pulse; + + bool _is_running; + + int _bits_since_start; + uint16_t _data_register; + + uint8_t _interrupt_status; + Delegate *_delegate; + + enum { + Long, Short, Unrecognised + } _crossings[4]; +}; + +/*! @abstract Represents an Acorn Electron. @discussion An instance of Electron::Machine represents the current state of an Acorn Electron. */ -class Machine: public CPU6502::Processor { +class Machine: public CPU6502::Processor, Tape::Delegate { public: @@ -81,6 +125,8 @@ class Machine: public CPU6502::Processor { Outputs::Speaker *get_speaker() { return &_speaker; } const char *get_signal_decoder(); + virtual void tape_did_change_interrupt_status(Tape *tape); + private: inline void update_display(); @@ -88,9 +134,6 @@ class Machine: public CPU6502::Processor { inline void signal_interrupt(Interrupt interrupt); inline void evaluate_interrupts(); - inline void get_next_tape_pulse(); - inline void push_tape_bit(uint16_t bit); - // Things that directly constitute the memory map. uint8_t _roms[16][16384]; uint8_t _os[16384], _ram[32768]; @@ -114,20 +157,7 @@ class Machine: public CPU6502::Processor { uint8_t *_currentLine, *_writePointer; // Tape. - struct Tape { - std::shared_ptr media; - Storage::Tape::Pulse currentPulse; - std::shared_ptr pulseStepper; - uint32_t time_into_pulse; - bool is_running; - int bits_since_start; - - uint16_t dataRegister; - - enum { - Long, Short, Unrecognised - } crossings[4]; - } _tape; + Tape _tape; // Outputs. Outputs::CRT *_crt; From 949c33774a25fa5f1018e9abae2604ec5b25fecb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 23 Jan 2016 11:42:14 -0500 Subject: [PATCH 069/307] Fixed failure to set a delegate, slowed tape speed if operating in RAM. --- Machines/Electron/Electron.cpp | 56 ++++++++++++++++++---------------- Machines/Electron/Electron.hpp | 2 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index dd1cb49b5..ebb56eaf9 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -35,6 +35,7 @@ Machine::Machine() : memset(_roms[c], 0xff, 16384); _speaker.set_input_rate(125000); + _tape.set_delegate(this); } Machine::~Machine() @@ -290,7 +291,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin break; } - _tape.run_for_cycle(); + _tape.run_for_cycles(cycles); return cycles; } @@ -657,38 +658,41 @@ inline void Tape::clear_interrupts(uint8_t interrupts) } } -inline void Tape::run_for_cycle() +inline void Tape::run_for_cycles(unsigned int number_of_cycles) { if(_is_running && _tape != nullptr) { - _time_into_pulse += (unsigned int)_pulse_stepper->step(); - if(_time_into_pulse == _current_pulse.length.length) + while(number_of_cycles--) { - get_next_tape_pulse(); - - _crossings[0] = _crossings[1]; - _crossings[1] = _crossings[2]; - _crossings[2] = _crossings[3]; - - _crossings[3] = Tape::Unrecognised; - if(_current_pulse.type != Storage::Tape::Pulse::Zero) + _time_into_pulse += (unsigned int)_pulse_stepper->step(); + if(_time_into_pulse == _current_pulse.length.length) { - float pulse_length = (float)_current_pulse.length.length / (float)_current_pulse.length.clock_rate; - if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _crossings[3] = Tape::Short; - if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _crossings[3] = Tape::Long; - } + get_next_tape_pulse(); - if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long) - { - push_tape_bit(0); - _crossings[1] = Tape::Unrecognised; - } - else - { - if(_crossings[0] == Tape::Short && _crossings[1] == Tape::Short && _crossings[2] == Tape::Short && _crossings[3] == Tape::Short) + _crossings[0] = _crossings[1]; + _crossings[1] = _crossings[2]; + _crossings[2] = _crossings[3]; + + _crossings[3] = Tape::Unrecognised; + if(_current_pulse.type != Storage::Tape::Pulse::Zero) { - push_tape_bit(1); - _crossings[3] = Tape::Unrecognised; + float pulse_length = (float)_current_pulse.length.length / (float)_current_pulse.length.clock_rate; + if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _crossings[3] = Tape::Short; + if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _crossings[3] = Tape::Long; + } + + if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long) + { + push_tape_bit(0); + _crossings[1] = Tape::Unrecognised; + } + else + { + if(_crossings[0] == Tape::Short && _crossings[1] == Tape::Short && _crossings[2] == Tape::Short && _crossings[3] == Tape::Short) + { + push_tape_bit(1); + _crossings[3] = Tape::Unrecognised; + } } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index e83f5b84a..100a6476a 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -74,7 +74,7 @@ class Tape { }; inline void set_delegate(Delegate *delegate) { _delegate = delegate; } - inline void run_for_cycle(); + inline void run_for_cycles(unsigned int number_of_cycles); void set_is_running(bool is_running) { _is_running = is_running; } From f4cd0aa38ef49b5a0825cac60d6da2aa8d0c6c2f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 23 Jan 2016 13:09:56 -0500 Subject: [PATCH 070/307] Ensured that all relevant information is given to the tape class. Made an attempt not to write to a screen output buffer if we don't have one. Added some debug printing. --- Machines/Electron/Electron.cpp | 22 +++++++++++++++++----- Machines/Electron/Electron.hpp | 7 ++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ebb56eaf9..cdde445c2 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -104,10 +104,14 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x1: break; case 0x2: + printf("%02x to [2] mutates %04x ", *value, _startScreenAddress); _startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); + printf("into %04x\n", _startScreenAddress); break; case 0x3: + printf("%02x to [3] mutates %04x ", *value, _startScreenAddress); _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); + printf("into %04x\n", _startScreenAddress); break; case 0x4: if(isReadOperation(operation)) @@ -117,8 +121,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { + _tape.set_data_register(*value); + _tape.clear_interrupts(Interrupt::ReceiveDataFull); } -// printf("Cassette\n"); break; case 0x5: if(!isReadOperation(operation)) @@ -158,6 +163,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { update_audio(); _speaker.set_divider(*value); + _tape.set_counter(*value); } break; case 0x7: @@ -185,10 +191,13 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { update_audio(); _speaker.set_is_enabled(new_speaker_is_enabled); + _tape.set_is_enabled(!new_speaker_is_enabled); } _tape.set_is_running(((*value)&0x40) ? true : false); - // TODO: tape mode, caps lock LED + _tape.set_is_in_input_mode(((*value)&0x04) ? false : true); + + // TODO: caps lock LED } break; default: @@ -450,7 +459,7 @@ inline void Machine::update_display() uint8_t pixels = _ram[_currentScreenAddress];\ _currentScreenAddress = _currentScreenAddress+8 - if(pixels_to_output) + if(pixels_to_output && _writePointer) { switch(_screenMode) { @@ -511,7 +520,10 @@ inline void Machine::update_display() else _startLineAddress++; - _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); + if(_writePointer) + _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); + else + _crt->output_data(80 * crt_cycles_multiplier, _currentOutputDivider); _crt->output_blank(24 * crt_cycles_multiplier); _displayOutputPosition += 24; _currentLine = nullptr; @@ -660,7 +672,7 @@ inline void Tape::clear_interrupts(uint8_t interrupts) inline void Tape::run_for_cycles(unsigned int number_of_cycles) { - if(_is_running && _tape != nullptr) + if(_is_running && _is_enabled && _tape != nullptr) { while(number_of_cycles--) { diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 100a6476a..0921aca6b 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -64,6 +64,8 @@ class Tape { void set_tape(std::shared_ptr tape); inline uint8_t get_data_register() { return (uint8_t)(_data_register >> 2); } + inline void set_data_register(uint8_t value) {} + inline void set_counter(uint8_t value) {} inline uint8_t get_interrupt_status() { return _interrupt_status; } inline void clear_interrupts(uint8_t interrupts); @@ -76,7 +78,9 @@ class Tape { inline void run_for_cycles(unsigned int number_of_cycles); - void set_is_running(bool is_running) { _is_running = is_running; } + inline void set_is_running(bool is_running) { _is_running = is_running; } + inline void set_is_enabled(bool is_enabled) { _is_enabled = is_enabled; } + inline void set_is_in_input_mode(bool is_in_input_mode) {} private: inline void push_tape_bit(uint16_t bit); @@ -89,6 +93,7 @@ class Tape { uint32_t _time_into_pulse; bool _is_running; + bool _is_enabled; int _bits_since_start; uint16_t _data_register; From 20cab08f8f480deea45af737b4d4a942b00b123a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 23 Jan 2016 17:44:34 -0500 Subject: [PATCH 071/307] Added a two-slot buffer of scans and a comon dispatch mechanism. --- Outputs/CRT.cpp | 75 +++++++++++++++++++++++++++++++------------------ Outputs/CRT.hpp | 19 ++++++++++++- 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index 307be6caa..9e5b73204 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -64,7 +64,16 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di } } -CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int number_of_buffers, ...) +CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int number_of_buffers, ...) : + _next_scan(0), + _frames_with_delegate(0), + _frame_read_pointer(0), + _horizontal_counter(0), + _sync_capacitor_charge_level(0), + _is_receiving_sync(false), + _is_in_hsync(false), + _is_in_vsync(false), + _rasterPosition({.x = 0, .y = 0}) { set_new_timing(cycles_per_line, height_of_display); @@ -79,24 +88,10 @@ CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned _frame_builders[frame] = new CRTFrameBuilder(bufferWidth, bufferHeight, number_of_buffers, va); va_end(va); } - _frames_with_delegate = 0; - _frame_read_pointer = 0; _current_frame_builder = _frame_builders[0]; - // reset raster position - _rasterPosition.x = _rasterPosition.y = 0; - // reset flywheel sync _expected_next_hsync = _cycles_per_line; - _horizontal_counter = 0; - - // reset the vertical charge capacitor - _sync_capacitor_charge_level = 0; - - // start off not in horizontal sync, not receiving a sync signal - _is_receiving_sync = false; - _is_in_hsync = false; - _is_in_vsync = false; } CRT::~CRT() @@ -349,35 +344,61 @@ void CRT::set_delegate(Delegate *delegate) #pragma mark - stream feeding methods +void CRT::output_scan(Scan *scan) +{ + bool this_is_sync = (scan->type == Type::Sync); + bool hsync_requested = !_is_receiving_sync && this_is_sync; + bool vsync_requested = _is_receiving_sync; + _is_receiving_sync = this_is_sync; + + advance_cycles(scan->number_of_cycles, scan->source_divider, hsync_requested, vsync_requested, this_is_sync, scan->type); + + _next_scan ^= 1; +} + /* These all merely channel into advance_cycles, supplying appropriate arguments */ void CRT::output_sync(unsigned int number_of_cycles) { - bool _hsync_requested = !_is_receiving_sync; // ensure this really is edge triggered; someone calling output_sync twice in succession shouldn't trigger it twice - _is_receiving_sync = true; - advance_cycles(number_of_cycles, 1, _hsync_requested, false, true, Type::Sync); + _scans[_next_scan].type = Type::Sync; + _scans[_next_scan].number_of_cycles = number_of_cycles; + output_scan(&_scans[_next_scan]); } void CRT::output_blank(unsigned int number_of_cycles) { - bool _vsync_requested = _is_receiving_sync; - _is_receiving_sync = false; - advance_cycles(number_of_cycles, 1, false, _vsync_requested, false, Type::Blank); + _scans[_next_scan].type = Type::Blank; + _scans[_next_scan].number_of_cycles = number_of_cycles; + output_scan(&_scans[_next_scan]); } void CRT::output_level(unsigned int number_of_cycles) { - bool _vsync_requested = _is_receiving_sync; - _is_receiving_sync = false; - advance_cycles(number_of_cycles, 1, false, _vsync_requested, false, Type::Level); + _scans[_next_scan].type = Type::Level; + _scans[_next_scan].number_of_cycles = number_of_cycles; + _scans[_next_scan].tex_x = _current_frame_builder ? _current_frame_builder->_write_x_position : 0; + _scans[_next_scan].tex_y = _current_frame_builder ? _current_frame_builder->_write_y_position : 0; + output_scan(&_scans[_next_scan]); +} + +void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t magnitude) +{ + _scans[_next_scan].type = Type::ColourBurst; + _scans[_next_scan].number_of_cycles = number_of_cycles; + _scans[_next_scan].phase = phase; + _scans[_next_scan].magnitude = magnitude; + output_scan(&_scans[_next_scan]); } void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { - bool _vsync_requested = _is_receiving_sync; - _is_receiving_sync = false; - advance_cycles(number_of_cycles, source_divider, false, _vsync_requested, false, Type::Data); + _scans[_next_scan].type = Type::Data; + _scans[_next_scan].number_of_cycles = number_of_cycles; + _scans[_next_scan].tex_x = _current_frame_builder ? _current_frame_builder->_write_x_position : 0; + _scans[_next_scan].tex_y = _current_frame_builder ? _current_frame_builder->_write_y_position : 0; + _scans[_next_scan].source_divider = source_divider; + output_scan(&_scans[_next_scan]); } #pragma mark - Buffer supply diff --git a/Outputs/CRT.hpp b/Outputs/CRT.hpp index af87c4f29..865e9dda7 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT.hpp @@ -144,7 +144,7 @@ class CRT { // the outer entry point for dispatching output_sync, output_blank, output_level and output_data enum Type { - Sync, Level, Data, Blank + Sync, Level, Data, Blank, ColourBurst } type; void advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, bool vsync_charging, Type type); @@ -157,6 +157,23 @@ class CRT { }; SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced); SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced); + + // each call to output_* generates a scan. A two-slot queue for scans allows edge extensions. + struct Scan { + Type type; + unsigned int number_of_cycles; + union { + struct { + unsigned int source_divider; + uint16_t tex_x, tex_y; + }; + struct { + uint8_t phase, magnitude; + }; + }; + } _scans[2]; + int _next_scan; + void output_scan(Scan *scan); }; } From cd97617a8a206016a1e3a819c03c21065cf66276 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 23 Jan 2016 17:45:06 -0500 Subject: [PATCH 072/307] Bumped the speaker up to the full namespace, since Tape lives there to avoid delegation complexity. Turned the CRT into something the Electron statically owns, fixing a memory leak by simplifying. --- Machines/Electron/Electron.cpp | 55 ++++++++++++++++------------------ Machines/Electron/Electron.hpp | 43 +++++++++++++------------- 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index cdde445c2..41089bcac 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -25,11 +25,11 @@ Machine::Machine() : _displayOutputPosition(0), _audioOutputPosition(0), _audioOutputPositionError(0), - _currentOutputLine(0) + _currentOutputLine(0), + _crt(Outputs::CRT(crt_cycles_per_line, 312, 1, 1)) { memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); - _crt = new Outputs::CRT(crt_cycles_per_line, 312, 1, 1); _interruptStatus = 0x02; for(int c = 0; c < 16; c++) memset(_roms[c], 0xff, 16384); @@ -367,8 +367,8 @@ inline void Machine::update_display() { for(int c = 0; c < lines_of_hsync; c++) { - _crt->output_sync(119 * crt_cycles_multiplier); - _crt->output_blank(9 * crt_cycles_multiplier); + _crt.output_sync(119 * crt_cycles_multiplier); + _crt.output_blank(9 * crt_cycles_multiplier); } _displayOutputPosition = end_of_hsync; } @@ -381,7 +381,7 @@ inline void Machine::update_display() // all lines then start with 9 cycles of sync if(!line_position) { - _crt->output_sync(9 * crt_cycles_multiplier); + _crt.output_sync(9 * crt_cycles_multiplier); _displayOutputPosition += 9; } else @@ -395,7 +395,7 @@ inline void Machine::update_display() { if(line_position == 9) { - _crt->output_blank(119 * crt_cycles_multiplier); + _crt.output_blank(119 * crt_cycles_multiplier); _displayOutputPosition += 119; } } @@ -404,7 +404,7 @@ inline void Machine::update_display() // there are then 15 cycles of blank, 80 cycles of pixels, and 24 further cycles of blank if(line_position == 9) { - _crt->output_blank(15 * crt_cycles_multiplier); + _crt.output_blank(15 * crt_cycles_multiplier); _displayOutputPosition += 15; switch(_screenMode) @@ -414,8 +414,8 @@ inline void Machine::update_display() case 2: case 5: _currentOutputDivider = 4; break; } - _crt->allocate_write_area(80 * crt_cycles_multiplier / _currentOutputDivider); - _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); + _crt.allocate_write_area(80 * crt_cycles_multiplier / _currentOutputDivider); + _currentLine = _writePointer = (uint8_t *)_crt.get_write_target_for_buffer(0); if(current_line == first_graphics_line) _startLineAddress = _startScreenAddress; @@ -435,10 +435,10 @@ inline void Machine::update_display() } if(newDivider != _currentOutputDivider) { - _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); + _crt.output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); _currentOutputDivider = newDivider; - _crt->allocate_write_area((int)((104 - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); - _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); + _crt.allocate_write_area((int)((104 - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); + _currentLine = _writePointer = (uint8_t *)_crt.get_write_target_for_buffer(0); } @@ -510,6 +510,8 @@ inline void Machine::update_display() } } +#undef GetNextPixels + if(line_position == 104) { _currentOutputLine++; @@ -521,10 +523,10 @@ inline void Machine::update_display() _startLineAddress++; if(_writePointer) - _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); + _crt.output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); else - _crt->output_data(80 * crt_cycles_multiplier, _currentOutputDivider); - _crt->output_blank(24 * crt_cycles_multiplier); + _crt.output_data(80 * crt_cycles_multiplier, _currentOutputDivider); + _crt.output_blank(24 * crt_cycles_multiplier); _displayOutputPosition += 24; _currentLine = nullptr; } @@ -559,7 +561,11 @@ void Machine::set_key_state(Key key, bool isPressed) } } -void Machine::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) +/* + Speaker +*/ + +void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { if(!_is_enabled) { @@ -568,12 +574,11 @@ void Machine::Speaker::get_samples(unsigned int number_of_samples, int16_t *targ else { *target = _output_level; -// fwrite(target, sizeof(int16_t), 1, rawStream); } skip_samples(number_of_samples); } -void Machine::Speaker::skip_samples(unsigned int number_of_samples) +void Speaker::skip_samples(unsigned int number_of_samples) { while(number_of_samples--) { @@ -586,27 +591,17 @@ void Machine::Speaker::skip_samples(unsigned int number_of_samples) } } -void Machine::Speaker::set_divider(uint8_t divider) +void Speaker::set_divider(uint8_t divider) { _divider = divider; } -void Machine::Speaker::set_is_enabled(bool is_enabled) +void Speaker::set_is_enabled(bool is_enabled) { _is_enabled = is_enabled; _counter = 0; } -Machine::Speaker::Speaker() : _counter(0), _divider(0x32), _is_enabled(false), _output_level(0) -{ -// rawStream = fopen("/Users/thomasharte/Desktop/sound.rom", "wb"); -} - -Machine::Speaker::~Speaker() -{ -// fclose(rawStream); -} - /* Tape */ diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 0921aca6b..f1f2f337f 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -106,6 +106,23 @@ class Tape { } _crossings[4]; }; +class Speaker: public ::Outputs::Filter { + public: + void set_divider(uint8_t divider); + + void set_is_enabled(bool is_enabled); + inline bool get_is_enabled() { return _is_enabled; } + + void get_samples(unsigned int number_of_samples, int16_t *target); + void skip_samples(unsigned int number_of_samples); + + private: + unsigned int _counter; + uint8_t _divider; + bool _is_enabled; + int16_t _output_level; +}; + /*! @abstract Represents an Acorn Electron. @@ -126,7 +143,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { void set_key_state(Key key, bool isPressed); - Outputs::CRT *get_crt() { return _crt; } + Outputs::CRT *get_crt() { return &_crt; } Outputs::Speaker *get_speaker() { return &_speaker; } const char *get_signal_decoder(); @@ -165,28 +182,8 @@ class Machine: public CPU6502::Processor, Tape::Delegate { Tape _tape; // Outputs. - Outputs::CRT *_crt; - - class Speaker: public ::Outputs::Filter { - public: - void set_divider(uint8_t divider); - - void set_is_enabled(bool is_enabled); - inline bool get_is_enabled() { return _is_enabled; } - - void get_samples(unsigned int number_of_samples, int16_t *target); - void skip_samples(unsigned int number_of_samples); - - Speaker(); - ~Speaker(); - - private: - unsigned int _counter; - uint8_t _divider; - bool _is_enabled; - int16_t _output_level; - - } _speaker; + Outputs::CRT _crt; + Speaker _speaker; }; } From a9e26d7808cfe5bdea1fcce26ba66720dcbe7e95 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 23 Jan 2016 17:49:25 -0500 Subject: [PATCH 073/307] Introduced a scan's delay, as intended. --- Outputs/CRT.cpp | 28 +++++++++++----------------- Outputs/CRT.hpp | 4 ++-- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index 9e5b73204..80558c48b 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -170,18 +170,11 @@ CRT::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsi return proposedEvent; } -void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type) +void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type, uint16_t tex_x, uint16_t tex_y) { number_of_cycles *= _time_multiplier; bool is_output_run = ((type == Type::Level) || (type == Type::Data)); - uint16_t tex_x = 0; - uint16_t tex_y = 0; - - if(is_output_run && _current_frame_builder) { - tex_x = _current_frame_builder->_write_x_position; - tex_y = _current_frame_builder->_write_y_position; - } while(number_of_cycles) { @@ -344,16 +337,17 @@ void CRT::set_delegate(Delegate *delegate) #pragma mark - stream feeding methods -void CRT::output_scan(Scan *scan) +void CRT::output_scan() { + _next_scan ^= 1; + Scan *scan = &_scans[_next_scan]; + bool this_is_sync = (scan->type == Type::Sync); bool hsync_requested = !_is_receiving_sync && this_is_sync; bool vsync_requested = _is_receiving_sync; _is_receiving_sync = this_is_sync; - advance_cycles(scan->number_of_cycles, scan->source_divider, hsync_requested, vsync_requested, this_is_sync, scan->type); - - _next_scan ^= 1; + advance_cycles(scan->number_of_cycles, scan->source_divider, hsync_requested, vsync_requested, this_is_sync, scan->type, scan->tex_x, scan->tex_y); } /* @@ -363,14 +357,14 @@ void CRT::output_sync(unsigned int number_of_cycles) { _scans[_next_scan].type = Type::Sync; _scans[_next_scan].number_of_cycles = number_of_cycles; - output_scan(&_scans[_next_scan]); + output_scan(); } void CRT::output_blank(unsigned int number_of_cycles) { _scans[_next_scan].type = Type::Blank; _scans[_next_scan].number_of_cycles = number_of_cycles; - output_scan(&_scans[_next_scan]); + output_scan(); } void CRT::output_level(unsigned int number_of_cycles) @@ -379,7 +373,7 @@ void CRT::output_level(unsigned int number_of_cycles) _scans[_next_scan].number_of_cycles = number_of_cycles; _scans[_next_scan].tex_x = _current_frame_builder ? _current_frame_builder->_write_x_position : 0; _scans[_next_scan].tex_y = _current_frame_builder ? _current_frame_builder->_write_y_position : 0; - output_scan(&_scans[_next_scan]); + output_scan(); } void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t magnitude) @@ -388,7 +382,7 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint _scans[_next_scan].number_of_cycles = number_of_cycles; _scans[_next_scan].phase = phase; _scans[_next_scan].magnitude = magnitude; - output_scan(&_scans[_next_scan]); + output_scan(); } void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) @@ -398,7 +392,7 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider _scans[_next_scan].tex_x = _current_frame_builder ? _current_frame_builder->_write_x_position : 0; _scans[_next_scan].tex_y = _current_frame_builder ? _current_frame_builder->_write_y_position : 0; _scans[_next_scan].source_divider = source_divider; - output_scan(&_scans[_next_scan]); + output_scan(); } #pragma mark - Buffer supply diff --git a/Outputs/CRT.hpp b/Outputs/CRT.hpp index 865e9dda7..dd17d6b28 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT.hpp @@ -146,7 +146,7 @@ class CRT { enum Type { Sync, Level, Data, Blank, ColourBurst } type; - void advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, bool vsync_charging, Type type); + void advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type, uint16_t tex_x, uint16_t tex_y); // the inner entry point that determines whether and when the next sync event will occur within // the current output window @@ -173,7 +173,7 @@ class CRT { }; } _scans[2]; int _next_scan; - void output_scan(Scan *scan); + void output_scan(); }; } From ace331d4b451851ec5a3de8f62293c4ad1defeea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 23 Jan 2016 18:53:20 -0500 Subject: [PATCH 074/307] Got a bit higher level in CRT timing specification. Which internally allows it now to infer the colour clock frequency. Which will be helpful momentarily. --- Machines/Atari2600/Atari2600.cpp | 4 +- Machines/Electron/Electron.cpp | 2 +- Outputs/CRT.cpp | 69 ++++++++++++++++++++++++-------- Outputs/CRT.hpp | 14 ++++++- 4 files changed, 67 insertions(+), 22 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 2450d9889..3cd0d00be 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -24,7 +24,7 @@ Machine::Machine() : _piaDataValue{0xff, 0xff}, _tiaInputValue{0xff, 0xff} { - _crt = new Outputs::CRT(228, 262, 1, 2); + _crt = new Outputs::CRT(228, Outputs::CRT::DisplayType::NTSC60, 1, 2); memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); } @@ -37,7 +37,7 @@ Machine::~Machine() void Machine::switch_region() { - _crt->set_new_timing(228, 312); + _crt->set_new_display_type(228, Outputs::CRT::DisplayType::PAL50); } void Machine::get_output_pixel(uint8_t *pixel, int offset) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 41089bcac..59bffe30d 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -26,7 +26,7 @@ Machine::Machine() : _audioOutputPosition(0), _audioOutputPositionError(0), _currentOutputLine(0), - _crt(Outputs::CRT(crt_cycles_per_line, 312, 1, 1)) + _crt(Outputs::CRT(crt_cycles_per_line, Outputs::CRT::DisplayType::PAL50, 1, 1)) { memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index 80558c48b..7a4bd9dc5 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -25,7 +25,7 @@ static const uint32_t kCRTFixedPointOffset = 0x04000000; #define kRetraceXMask 0x01 #define kRetraceYMask 0x02 -void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display) +void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) { const unsigned int syncCapacityLineChargeThreshold = 3; const unsigned int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234 @@ -62,9 +62,42 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di _beamWidth[c].x = (uint32_t)((sinf(angle) / halfLineWidth) * kCRTFixedPointRange); _beamWidth[c].y = (uint32_t)((cosf(angle) / halfLineWidth) * kCRTFixedPointRange); } + + // reset flywheel sync + _expected_next_hsync = _cycles_per_line; } -CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int number_of_buffers, ...) : +void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) +{ + switch(displayType) + { + case DisplayType::PAL50: + set_new_timing(cycles_per_line, 312, 1135, 4); + break; + + case DisplayType::NTSC60: + set_new_timing(cycles_per_line, 262, 545, 2); + break; + } +} + +void CRT::allocate_buffers(unsigned int number, va_list sizes) +{ + // generate buffers for signal storage as requested — format is + // number of buffers, size of buffer 1, size of buffer 2... + const uint16_t bufferWidth = 2048; + const uint16_t bufferHeight = 2048; + for(int frame = 0; frame < sizeof(_frame_builders) / sizeof(*_frame_builders); frame++) + { + va_list va; + va_copy(va, sizes); + _frame_builders[frame] = new CRTFrameBuilder(bufferWidth, bufferHeight, number, va); + va_end(va); + } + _current_frame_builder = _frame_builders[0]; +} + +CRT::CRT() : _next_scan(0), _frames_with_delegate(0), _frame_read_pointer(0), @@ -74,24 +107,26 @@ CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned _is_in_hsync(false), _is_in_vsync(false), _rasterPosition({.x = 0, .y = 0}) +{} + +CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...) : CRT() { - set_new_timing(cycles_per_line, height_of_display); + set_new_timing(cycles_per_line, height_of_display, colour_cycle_numerator, colour_cycle_denominator); - // generate buffers for signal storage as requested — format is - // number of buffers, size of buffer 1, size of buffer 2... - const uint16_t bufferWidth = 2048; - const uint16_t bufferHeight = 2048; - for(int frame = 0; frame < sizeof(_frame_builders) / sizeof(*_frame_builders); frame++) - { - va_list va; - va_start(va, number_of_buffers); - _frame_builders[frame] = new CRTFrameBuilder(bufferWidth, bufferHeight, number_of_buffers, va); - va_end(va); - } - _current_frame_builder = _frame_builders[0]; + va_list buffer_sizes; + va_start(buffer_sizes, number_of_buffers); + allocate_buffers(number_of_buffers, buffer_sizes); + va_end(buffer_sizes); +} - // reset flywheel sync - _expected_next_hsync = _cycles_per_line; +CRT::CRT(unsigned int cycles_per_line, DisplayType displayType, unsigned int number_of_buffers, ...) : CRT() +{ + set_new_display_type(cycles_per_line, displayType); + + va_list buffer_sizes; + va_start(buffer_sizes, number_of_buffers); + allocate_buffers(number_of_buffers, buffer_sizes); + va_end(buffer_sizes); } CRT::~CRT() diff --git a/Outputs/CRT.hpp b/Outputs/CRT.hpp index dd17d6b28..7b39c68fc 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT.hpp @@ -48,10 +48,17 @@ static const int kCRTNumberOfFrames = 4; class CRT { public: - CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int number_of_buffers, ...); + enum DisplayType { + PAL50, + NTSC60 + }; + + CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...); + CRT(unsigned int cycles_per_line, DisplayType displayType, unsigned int number_of_buffers, ...); ~CRT(); - void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display); + void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator); + void set_new_display_type(unsigned int cycles_per_line, DisplayType displayType); /*! Output at the sync level. @@ -105,6 +112,9 @@ class CRT { uint8_t *get_write_target_for_buffer(int buffer); private: + CRT(); + void allocate_buffers(unsigned int number, va_list sizes); + // the incoming clock lengths will be multiplied by something to give at least 1000 // sample points per line unsigned int _time_multiplier; From 1819e7b9cc017d783091808d52573b1c421680bd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 23 Jan 2016 19:06:32 -0500 Subject: [PATCH 075/307] Started sketching out an appropriate interface to transfer responsibility for shaders to the CRT. --- Outputs/CRT.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ Outputs/CRT.hpp | 28 ++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index 7a4bd9dc5..8664e11c6 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -519,3 +519,72 @@ uint8_t *CRTFrameBuilder::get_write_target_for_buffer(int buffer) { return &frame.buffers[buffer].data[_write_target_pointer * frame.buffers[buffer].depth]; } + +char *CRT::get_vertex_shader() +{ + // the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the + // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates + // from integral to floating point; there's also some setup for NTSC, PAL or whatever. + + const char *const ntscVertexShaderGlobals = + "out vec2 srcCoordinatesVarying[4];\n" + "out float phase;\n"; + + const char *const ntscVertexShaderBody = + "phase = srcCoordinates.x * 6.283185308;\n" + "\n" + "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" + "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; + + const char *const rgbVertexShaderGlobals = + "out vec2 srcCoordinatesVarying[5];\n"; + + const char *const rgbVertexShaderBody = + "srcCoordinatesVarying[2] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" + "srcCoordinatesVarying[0] = srcCoordinatesVarying[1] - vec2(1.0 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[1] = srcCoordinatesVarying[1] - vec2(0.5 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[3] = srcCoordinatesVarying[1] + vec2(0.5 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[4] = srcCoordinatesVarying[1] + vec2(1.0 / textureSize.x, 0.0);\n"; + + const char *const vertexShader = + "#version 150\n" + "\n" + "in vec2 position;\n" + "in vec2 srcCoordinates;\n" + "in float lateral;\n" + "\n" + "uniform vec2 boundsOrigin;\n" + "uniform vec2 boundsSize;\n" + "\n" + "out float lateralVarying;\n" + "out vec2 shadowMaskCoordinates;\n" + "\n" + "uniform vec2 textureSize;\n" + "\n" + "const float shadowMaskMultiple = 600;\n" + "\n" + "%@\n" + "void main (void)\n" + "{\n" + "lateralVarying = lateral + 1.0707963267949;\n" + "\n" + "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);\n" + "\n" + "%@\n" + "\n" + "vec2 mappedPosition = (position - boundsOrigin) / boundsSize;" + "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);\n" + "}\n"; + + return nullptr; +// + mappedPosition.x / 131.0 + +// switch(_signalType) +// { +// case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:vertexShader, ntscVertexShaderGlobals, ntscVertexShaderBody]; +// case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:vertexShader, rgbVertexShaderGlobals, rgbVertexShaderBody]; +// } +} diff --git a/Outputs/CRT.hpp b/Outputs/CRT.hpp index 7b39c68fc..2eb7df443 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT.hpp @@ -111,6 +111,34 @@ class CRT { void allocate_write_area(int required_length); uint8_t *get_write_target_for_buffer(int buffer); + /*! Gets the vertex shader for display of vended CRTFrames. + + @returns A vertex shader, allocated using a C function. The caller then owns the memory + and is responsible for free'ing it. + */ + char *get_vertex_shader(); + + /*! Gets a fragment shader for display of vended CRTFrames based on the supplied sampling function. + + @param sample_function A GLSL fragment including a function with the signature + `float sample(vec2 coordinate, float phase)` that evaluates to the composite signal level + as a function of a source buffer sampling location and the current colour carrier phase. + + @returns A complete fragment shader. + */ + char *get_fragment_shader(const char *sample_function); + + /*! Gets a fragment shader for composite display of vended CRTFrames based on a default encoding + of the supplied sampling function. + + @param sample_function A GLSL fragent including a function with the signature + `vec3 sample(vec2 coordinate)` that evaluates to an RGB colour as a function of + the source buffer sampling location. + + @returns A complete fragment shader. + */ + char *get_rgb_encoding_fragment_shader(const char *sample_function); + private: CRT(); void allocate_buffers(unsigned int number, va_list sizes); From 96c946b8affc0eead7fabee8abfe31f4f2e419b0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Jan 2016 18:20:55 -0500 Subject: [PATCH 076/307] Made a very basic attempt to emulate tape output. --- Machines/Electron/Electron.cpp | 134 ++++++++++++++++++++++----------- Machines/Electron/Electron.hpp | 16 ++-- 2 files changed, 98 insertions(+), 52 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 59bffe30d..062d6741e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -117,12 +117,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if(isReadOperation(operation)) { *value = _tape.get_data_register(); - _tape.clear_interrupts(Interrupt::TransmitDataEmpty); + _tape.clear_interrupts(Interrupt::ReceiveDataFull); } else { _tape.set_data_register(*value); - _tape.clear_interrupts(Interrupt::ReceiveDataFull); + _tape.clear_interrupts(Interrupt::TransmitDataEmpty); } break; case 0x5: @@ -606,7 +606,7 @@ void Speaker::set_is_enabled(bool is_enabled) Tape */ -Tape::Tape() : _is_running(false), _data_register(0), _delegate(nullptr) {} +Tape::Tape() : _is_running(false), _data_register(0), _delegate(nullptr), _output_bits_remaining(0), _last_posted_interrupt_status(0), _interrupt_status(0) {} void Tape::set_tape(std::shared_ptr tape) { @@ -627,33 +627,36 @@ inline void Tape::get_next_tape_pulse() inline void Tape::push_tape_bit(uint16_t bit) { _data_register = (uint16_t)((_data_register >> 1) | (bit << 10)); - - uint8_t old_interrupt_status = _interrupt_status; - if(_bits_since_start) { _bits_since_start--; if(_bits_since_start == 7) { - _interrupt_status &= ~Interrupt::TransmitDataEmpty; + _interrupt_status &= ~Interrupt::ReceiveDataFull; } } - else + evaluate_interrupts(); +} + +inline void Tape::evaluate_interrupts() +{ + if((_data_register&0x3) == 0x1) { - if((_data_register&0x3) == 0x1) - { - _interrupt_status |= Interrupt::TransmitDataEmpty; - _bits_since_start = 9; - } - - if(_data_register == 0x3ff) - _interrupt_status |= Interrupt::HighToneDetect; - else - _interrupt_status &= ~Interrupt::HighToneDetect; + _interrupt_status |= Interrupt::ReceiveDataFull; + _bits_since_start = 9; } - if(old_interrupt_status != _interrupt_status && _delegate) _delegate->tape_did_change_interrupt_status(this); + if(_data_register == 0x3ff) + _interrupt_status |= Interrupt::HighToneDetect; + else + _interrupt_status &= ~Interrupt::HighToneDetect; + + if(_last_posted_interrupt_status != _interrupt_status) + { + _last_posted_interrupt_status = _interrupt_status; + if(_delegate) _delegate->tape_did_change_interrupt_status(this); + } } inline void Tape::clear_interrupts(uint8_t interrupts) @@ -665,43 +668,84 @@ inline void Tape::clear_interrupts(uint8_t interrupts) } } +inline void Tape::set_is_in_input_mode(bool is_in_input_mode) +{ + _is_in_input_mode = is_in_input_mode; +} + +inline void Tape::set_counter(uint8_t value) +{ + _pulse_stepper = std::shared_ptr(new SignalProcessing::Stepper(1200, 2000000)); +} + +inline void Tape::set_data_register(uint8_t value) +{ + _data_register = (uint16_t)((value << 2) | 1); + _output_bits_remaining = 9; +} + inline void Tape::run_for_cycles(unsigned int number_of_cycles) { - if(_is_running && _is_enabled && _tape != nullptr) + if(_is_enabled) { - while(number_of_cycles--) + if(_is_in_input_mode) { - _time_into_pulse += (unsigned int)_pulse_stepper->step(); - if(_time_into_pulse == _current_pulse.length.length) + if(_is_running && _tape != nullptr) { - get_next_tape_pulse(); - - _crossings[0] = _crossings[1]; - _crossings[1] = _crossings[2]; - _crossings[2] = _crossings[3]; - - _crossings[3] = Tape::Unrecognised; - if(_current_pulse.type != Storage::Tape::Pulse::Zero) + while(number_of_cycles--) { - float pulse_length = (float)_current_pulse.length.length / (float)_current_pulse.length.clock_rate; - if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _crossings[3] = Tape::Short; - if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _crossings[3] = Tape::Long; - } - - if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long) - { - push_tape_bit(0); - _crossings[1] = Tape::Unrecognised; - } - else - { - if(_crossings[0] == Tape::Short && _crossings[1] == Tape::Short && _crossings[2] == Tape::Short && _crossings[3] == Tape::Short) + _time_into_pulse += (unsigned int)_pulse_stepper->step(); + if(_time_into_pulse == _current_pulse.length.length) { - push_tape_bit(1); + get_next_tape_pulse(); + + _crossings[0] = _crossings[1]; + _crossings[1] = _crossings[2]; + _crossings[2] = _crossings[3]; + _crossings[3] = Tape::Unrecognised; + if(_current_pulse.type != Storage::Tape::Pulse::Zero) + { + float pulse_length = (float)_current_pulse.length.length / (float)_current_pulse.length.clock_rate; + if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _crossings[3] = Tape::Short; + if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _crossings[3] = Tape::Long; + } + + if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long) + { + push_tape_bit(0); + _crossings[1] = Tape::Unrecognised; + } + else + { + if(_crossings[0] == Tape::Short && _crossings[1] == Tape::Short && _crossings[2] == Tape::Short && _crossings[3] == Tape::Short) + { + push_tape_bit(1); + _crossings[3] = Tape::Unrecognised; + } + } } } } } + else + { + while(number_of_cycles--) + { + if(_pulse_stepper->step()) + { + _output_bits_remaining--; + if(!_output_bits_remaining) + { + _output_bits_remaining = 9; + _interrupt_status |= Interrupt::TransmitDataEmpty; + } + + evaluate_interrupts(); + + _data_register = (_data_register >> 1) | 0x200; + } + } + } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index f1f2f337f..31aed8644 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -33,8 +33,8 @@ enum ROMSlot: uint8_t { enum Interrupt: uint8_t { DisplayEnd = 0x04, RealTimeClock = 0x08, - TransmitDataEmpty = 0x10, - ReceiveDataFull = 0x20, + ReceiveDataFull = 0x10, + TransmitDataEmpty = 0x20, HighToneDetect = 0x40 }; @@ -64,8 +64,8 @@ class Tape { void set_tape(std::shared_ptr tape); inline uint8_t get_data_register() { return (uint8_t)(_data_register >> 2); } - inline void set_data_register(uint8_t value) {} - inline void set_counter(uint8_t value) {} + inline void set_data_register(uint8_t value); + inline void set_counter(uint8_t value); inline uint8_t get_interrupt_status() { return _interrupt_status; } inline void clear_interrupts(uint8_t interrupts); @@ -80,7 +80,7 @@ class Tape { inline void set_is_running(bool is_running) { _is_running = is_running; } inline void set_is_enabled(bool is_enabled) { _is_enabled = is_enabled; } - inline void set_is_in_input_mode(bool is_in_input_mode) {} + inline void set_is_in_input_mode(bool is_in_input_mode); private: inline void push_tape_bit(uint16_t bit); @@ -94,11 +94,13 @@ class Tape { bool _is_running; bool _is_enabled; + bool _is_in_input_mode; - int _bits_since_start; + inline void evaluate_interrupts(); + int _bits_since_start, _output_bits_remaining; uint16_t _data_register; - uint8_t _interrupt_status; + uint8_t _interrupt_status, _last_posted_interrupt_status; Delegate *_delegate; enum { From adc6838ba063897a2ddeed815a1598ea2c8ace25 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 27 Jan 2016 21:35:57 -0500 Subject: [PATCH 077/307] Continued slow migration of shader ownership to CRT. Attempted to start debugging the tape interface. --- Machines/Electron/Electron.cpp | 50 +++++++++++++++--- Machines/Electron/Electron.hpp | 3 +- Outputs/CRT.cpp | 91 +++++++++++++++++++++++++++++--- Outputs/CRT.hpp | 2 +- Storage/Tape/Formats/TapeUEF.cpp | 4 +- 5 files changed, 132 insertions(+), 18 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 062d6741e..c6fe3f06f 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -7,6 +7,7 @@ // #include "Electron.hpp" +#include "TapeUEF.hpp" #include @@ -85,6 +86,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { if((address & 0xff00) == 0xfe00) { + cycles += (_frameCycles&1)^1; // printf("%c: %02x: ", isReadOperation(operation) ? 'r' : 'w', *value); switch(address&0xf) @@ -639,12 +641,34 @@ inline void Tape::push_tape_bit(uint16_t bit) evaluate_interrupts(); } +inline void Tape::reset_tape_input() +{ + _bits_since_start = 0; +// _interrupt_status &= ~(Interrupt::ReceiveDataFull | Interrupt::TransmitDataEmpty | Interrupt::HighToneDetect); +// +// if(_last_posted_interrupt_status != _interrupt_status) +// { +// _last_posted_interrupt_status = _interrupt_status; +// if(_delegate) _delegate->tape_did_change_interrupt_status(this); +// } +} + +extern uint8_t dr; + inline void Tape::evaluate_interrupts() { - if((_data_register&0x3) == 0x1) + if(!_bits_since_start) { - _interrupt_status |= Interrupt::ReceiveDataFull; - _bits_since_start = 9; + if((_data_register&0x3) == 0x1) + { + if(dr != ((_data_register >> 2)&0xff)) + { + printf("Mismatch\n"); + } + + _interrupt_status |= Interrupt::ReceiveDataFull; + if(_is_in_input_mode) _bits_since_start = 9; + } } if(_data_register == 0x3ff) @@ -699,6 +723,11 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) { get_next_tape_pulse(); +// if(_crossings[0] != Tape::Recognised) +// { +// reset_tape_input(); +// } + _crossings[0] = _crossings[1]; _crossings[1] = _crossings[2]; _crossings[2] = _crossings[3]; @@ -707,23 +736,30 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) if(_current_pulse.type != Storage::Tape::Pulse::Zero) { float pulse_length = (float)_current_pulse.length.length / (float)_current_pulse.length.clock_rate; - if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _crossings[3] = Tape::Short; - if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _crossings[3] = Tape::Long; + if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) _crossings[3] = Tape::Short; + if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) _crossings[3] = Tape::Long; + + if(_crossings[3] == Tape::Unrecognised) + { + printf("Wah wah?\n"); + } } if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long) { push_tape_bit(0); - _crossings[1] = Tape::Unrecognised; + _crossings[0] = _crossings[1] = Tape::Recognised; } else { if(_crossings[0] == Tape::Short && _crossings[1] == Tape::Short && _crossings[2] == Tape::Short && _crossings[3] == Tape::Short) { push_tape_bit(1); - _crossings[3] = Tape::Unrecognised; + _crossings[0] = _crossings[1] = + _crossings[2] = _crossings[3] = Tape::Recognised; } } + } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 31aed8644..7e2db617d 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -84,6 +84,7 @@ class Tape { private: inline void push_tape_bit(uint16_t bit); + inline void reset_tape_input(void); inline void get_next_tape_pulse(); std::shared_ptr _tape; @@ -104,7 +105,7 @@ class Tape { Delegate *_delegate; enum { - Long, Short, Unrecognised + Long, Short, Unrecognised, Recognised } _crossings[4]; }; diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index 8664e11c6..efd4451ea 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -539,15 +539,15 @@ char *CRT::get_vertex_shader() "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; - const char *const rgbVertexShaderGlobals = - "out vec2 srcCoordinatesVarying[5];\n"; +// const char *const rgbVertexShaderGlobals = +// "out vec2 srcCoordinatesVarying[5];\n"; - const char *const rgbVertexShaderBody = - "srcCoordinatesVarying[2] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" - "srcCoordinatesVarying[0] = srcCoordinatesVarying[1] - vec2(1.0 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[1] = srcCoordinatesVarying[1] - vec2(0.5 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[3] = srcCoordinatesVarying[1] + vec2(0.5 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[4] = srcCoordinatesVarying[1] + vec2(1.0 / textureSize.x, 0.0);\n"; +// const char *const rgbVertexShaderBody = +// "srcCoordinatesVarying[2] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" +// "srcCoordinatesVarying[0] = srcCoordinatesVarying[1] - vec2(1.0 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[1] = srcCoordinatesVarying[1] - vec2(0.5 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[3] = srcCoordinatesVarying[1] + vec2(0.5 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[4] = srcCoordinatesVarying[1] + vec2(1.0 / textureSize.x, 0.0);\n"; const char *const vertexShader = "#version 150\n" @@ -588,3 +588,78 @@ char *CRT::get_vertex_shader() // case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:vertexShader, rgbVertexShaderGlobals, rgbVertexShaderBody]; // } } + +char *CRT::get_fragment_shader(const char *sample_function) +{ + // assumes y = [0, 1], i and q = [-0.5, 0.5]; therefore i components are multiplied by 1.1914 versus standard matrices, q by 1.0452 + const char *const yiqToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);"; + + // assumes y = [0,1], u and v = [-0.5, 0.5]; therefore u components are multiplied by 1.14678899082569, v by 0.8130081300813 + const char *const yuvToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.75213899082569, 2.33040137614679, 0.92669105691057, -0.4720325203252, 0.0);"; + + const char *const fragmentShader = + "#version 150\n" + "\n" + "in float lateralVarying;\n" + "in vec2 shadowMaskCoordinates;\n" + "out vec4 fragColour;\n" + "\n" + "uniform sampler2D texID;\n" + "uniform sampler2D shadowMaskTexID;\n" + "uniform float alpha;\n" + "\n" + "in vec2 srcCoordinatesVarying[4];\n" + "in float phase;\n" + "%@\n" + "%@\n" + "\n" + "void main(void)\n" + "{\n" + "%@\n" + "}\n"; + + const char *const ntscFragmentShaderGlobals = + "in vec2 srcCoordinatesVarying[4];\n" + "in float phase;\n" + "\n" + "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" + "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; + + const char *const ntscFragmentShaderBody = + "vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" + "vec4 samples = vec4(" + " sample(srcCoordinatesVarying[0], angles.x)," + " sample(srcCoordinatesVarying[1], angles.y)," + " sample(srcCoordinatesVarying[2], angles.z)," + " sample(srcCoordinatesVarying[3], angles.w)" + ");\n" + "\n" + "float y = dot(vec4(0.25), samples);\n" + "samples -= vec4(y);\n" + "\n" + "float i = dot(cos(angles), samples);\n" + "float q = dot(sin(angles), samples);\n" + "\n" + "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; + +// const char *const rgbFragmentShaderGlobals = +// "in vec2 srcCoordinatesVarying[5];\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * + +// const char *const rgbFragmentShaderBody = +// "fragColour = sample(srcCoordinatesVarying[2]);"; +// @"fragColour = (sample(srcCoordinatesVarying[0]) * -0.1) + \ +// (sample(srcCoordinatesVarying[1]) * 0.3) + \ +// (sample(srcCoordinatesVarying[2]) * 0.6) + \ +// (sample(srcCoordinatesVarying[3]) * 0.3) + \ +// (sample(srcCoordinatesVarying[4]) * -0.1);"; + +// dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; + + return nullptr; + +// switch(_signalType) +// { +// case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:fragmentShader, ntscFragmentShaderGlobals, ntscFragmentShaderBody]; +// case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:fragmentShader, rgbFragmentShaderGlobals, rgbFragmentShaderBody]; +// } +} diff --git a/Outputs/CRT.hpp b/Outputs/CRT.hpp index 2eb7df443..037d45c56 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT.hpp @@ -132,7 +132,7 @@ class CRT { of the supplied sampling function. @param sample_function A GLSL fragent including a function with the signature - `vec3 sample(vec2 coordinate)` that evaluates to an RGB colour as a function of + `vec3 rgb_sample(vec2 coordinate)` that evaluates to an RGB colour as a function of the source buffer sampling location. @returns A complete fragment shader. diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index ba6d4b6de..b9345f8f2 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -9,6 +9,8 @@ #include "TapeUEF.hpp" #include +uint8_t dr; + Storage::UEF::UEF(const char *file_name) : _chunk_id(0), _chunk_length(0), _chunk_position(0), _time_base(1200) @@ -178,7 +180,7 @@ bool Storage::UEF::get_next_bit() _chunk_position++; if(!bit_position) { - _current_byte = (uint8_t)gzgetc(_file); + dr = _current_byte = (uint8_t)gzgetc(_file); } if(bit_position == 0) return false; if(bit_position == 9) return true; From 4554abb755be97adbc248c8592b82ef67bcca955 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 29 Jan 2016 21:14:13 -0500 Subject: [PATCH 078/307] Made an attempt to be more rigorous in display generation on the Electron, to make sure I deal with mid-line changes to/from blank line mode. Even if it turns out that they generate pixels. --- Machines/Electron/Electron.cpp | 98 ++++++++++++------- Machines/Electron/Electron.hpp | 2 +- .../Documents/ElectronDocument.swift | 2 +- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 25 +++-- Outputs/CRT.cpp | 2 +- 5 files changed, 76 insertions(+), 53 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c6fe3f06f..44bacb00d 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -379,12 +379,19 @@ inline void Machine::update_display() { const int current_line = _displayOutputPosition >> 7; const int line_position = _displayOutputPosition & 127; + const int cycles_left = _frameCycles - _displayOutputPosition; // all lines then start with 9 cycles of sync - if(!line_position) + if(line_position < 9) { - _crt.output_sync(9 * crt_cycles_multiplier); - _displayOutputPosition += 9; + int remaining_period = std::min(9 - line_position, cycles_left); + _displayOutputPosition += remaining_period; + + if(line_position + remaining_period == 9) + { +// printf("!%d!", 9); + _crt.output_sync(9 * crt_cycles_multiplier); + } } else { @@ -395,33 +402,37 @@ inline void Machine::update_display() if(isBlankLine) { - if(line_position == 9) - { - _crt.output_blank(119 * crt_cycles_multiplier); - _displayOutputPosition += 119; - } + int remaining_period = std::min(128 - line_position, cycles_left); + _crt.output_blank((unsigned int)remaining_period * crt_cycles_multiplier); +// printf(".[%d]", remaining_period); + _displayOutputPosition += remaining_period; } else { // there are then 15 cycles of blank, 80 cycles of pixels, and 24 further cycles of blank - if(line_position == 9) + if(line_position < 24) { - _crt.output_blank(15 * crt_cycles_multiplier); - _displayOutputPosition += 15; + int remaining_period = std::min(24 - line_position, cycles_left); + _crt.output_blank((unsigned int)remaining_period * crt_cycles_multiplier); +// printf("/(%d)(%d)[%d]", 24 - line_position, cycles_left, remaining_period); + _displayOutputPosition += remaining_period; - switch(_screenMode) + if(line_position + remaining_period == 24) { - case 0: case 3: _currentOutputDivider = 1; break; - case 1: case 4: case 6: _currentOutputDivider = 2; break; - case 2: case 5: _currentOutputDivider = 4; break; + switch(_screenMode) + { + case 0: case 3: _currentOutputDivider = 1; break; + case 1: case 4: case 6: _currentOutputDivider = 2; break; + case 2: case 5: _currentOutputDivider = 4; break; + } + + _crt.allocate_write_area(80 * crt_cycles_multiplier / _currentOutputDivider); + _currentLine = _writePointer = (uint8_t *)_crt.get_write_target_for_buffer(0); + + if(current_line == first_graphics_line) + _startLineAddress = _startScreenAddress; + _currentScreenAddress = _startLineAddress; } - - _crt.allocate_write_area(80 * crt_cycles_multiplier / _currentOutputDivider); - _currentLine = _writePointer = (uint8_t *)_crt.get_write_target_for_buffer(0); - - if(current_line == first_graphics_line) - _startLineAddress = _startScreenAddress; - _currentScreenAddress = _startLineAddress; } if(line_position >= 24 && line_position < 104) @@ -446,6 +457,7 @@ inline void Machine::update_display() int pixels_to_output = std::min(_frameCycles - _displayOutputPosition, 104 - line_position); _displayOutputPosition += pixels_to_output; +// printf("<- %d ->", pixels_to_output); if(_screenMode >= 4) { // just shifting wouldn't be enough if both @@ -514,23 +526,29 @@ inline void Machine::update_display() #undef GetNextPixels - if(line_position == 104) + if(line_position >= 104) { - _currentOutputLine++; - if(!(_currentOutputLine&7)) - { - _startLineAddress += ((_screenMode < 4) ? 80 : 40)*8 - 7; - } - else - _startLineAddress++; + int pixels_to_output = std::min(_frameCycles - _displayOutputPosition, 128 - line_position); + _crt.output_blank((unsigned int)pixels_to_output * crt_cycles_multiplier); + _displayOutputPosition += pixels_to_output; - if(_writePointer) - _crt.output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); - else - _crt.output_data(80 * crt_cycles_multiplier, _currentOutputDivider); - _crt.output_blank(24 * crt_cycles_multiplier); - _displayOutputPosition += 24; - _currentLine = nullptr; + if(line_position + pixels_to_output == 128) + { + _currentOutputLine++; +// printf("\n%d: ", _currentOutputLine); + if(!(_currentOutputLine&7)) + { + _startLineAddress += ((_screenMode < 4) ? 80 : 40)*8 - 7; + } + else + _startLineAddress++; + + if(_writePointer) + _crt.output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); + else + _crt.output_data(80 * crt_cycles_multiplier, _currentOutputDivider); + _currentLine = nullptr; + } } } } @@ -708,6 +726,12 @@ inline void Tape::set_data_register(uint8_t value) _output_bits_remaining = 9; } +inline uint8_t Tape::get_data_register() +{ + int shift = std::max(_bits_since_start - 7, 0); + return (uint8_t)(_data_register >> shift); +} + inline void Tape::run_for_cycles(unsigned int number_of_cycles) { if(_is_enabled) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 7e2db617d..d730be65a 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -63,7 +63,7 @@ class Tape { void set_tape(std::shared_ptr tape); - inline uint8_t get_data_register() { return (uint8_t)(_data_register >> 2); } + inline uint8_t get_data_register(); inline void set_data_register(uint8_t value); inline void set_counter(uint8_t value); diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 2e2f31dc4..ba3243a39 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -28,7 +28,7 @@ class ElectronDocument: MachineDocument { super.windowControllerDidLoadNib(aController) electron.view = openGLView electron.audioQueue = self.audioQueue - openGLView.frameBounds = CGRectMake(0.0225, 0.0625, 0.75, 0.75) +// openGLView.frameBounds = CGRectMake(0.0225, 0.0625, 0.75, 0.75) } override var windowNibName: String? { diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 3ba042ca0..7f08a7107 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -302,10 +302,8 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt @"phase = srcCoordinates.x * 6.283185308;\n" "\n" "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" - "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; + "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.5 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] - vec2(0.25 / textureSize.x, 0.0);\n"; NSString *const rgbVertexShaderGlobals = @"out vec2 srcCoordinatesVarying[5];\n"; @@ -385,21 +383,22 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; NSString *const ntscFragmentShaderBody = - @"vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" - "vec4 samples = vec4(" + @"vec3 angles = vec3(phase) + vec3(0.0, -3.141592654, -1.570796327);\n" + "vec3 samples = vec3(" " sample(srcCoordinatesVarying[0], angles.x)," " sample(srcCoordinatesVarying[1], angles.y)," - " sample(srcCoordinatesVarying[2], angles.z)," - " sample(srcCoordinatesVarying[3], angles.w)" + " sample(srcCoordinatesVarying[2], angles.z)" ");\n" "\n" - "float y = dot(vec4(0.25), samples);\n" - "samples -= vec4(y);\n" + "float y = dot(vec2(0.5), samples.xy);\n" + "samples -= vec3(y);\n" "\n" - "float i = dot(cos(angles), samples);\n" - "float q = dot(sin(angles), samples);\n" + "float i = dot(vec3(0.75), cos(angles) * samples);\n" + "float q = dot(vec3(0.75), sin(angles) * samples);\n" "\n" - "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; + "fragColour = vec4(yiqToRGB * vec3(y, i, q), 1.0);\n"; //sin(lateralVarying)); + // 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * +// "float y2 = dot(vec2(0.5), samples.zw);\n" NSString *const rgbFragmentShaderGlobals = @"in vec2 srcCoordinatesVarying[5];\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index efd4451ea..e87d8b8c2 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -275,7 +275,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi position_y(5) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.y + _beamWidth[lengthMask].y); // if this is a data run then advance the buffer pointer - if(type == Type::Data) tex_x += next_run_length / (_time_multiplier * source_divider); + if(type == Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider); // if this is a data or level run then store the end point tex_x(2) = tex_x(3) = tex_x(5) = tex_x; From 19393390ac442f6ac9c66c8aa3168050017dcd33 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 2 Feb 2016 22:41:33 -0500 Subject: [PATCH 079/307] It's broken now, but this is what I now intend the full public interface of the CRT to be. --- Outputs/CRT.hpp | 261 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 195 insertions(+), 66 deletions(-) diff --git a/Outputs/CRT.hpp b/Outputs/CRT.hpp index 037d45c56..b473844a2 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT.hpp @@ -18,67 +18,80 @@ namespace Outputs { -class CRT; -struct CRTFrameBuilder { - CRTFrame frame; - - CRTFrameBuilder(uint16_t width, uint16_t height, unsigned int number_of_buffers, va_list buffer_sizes); - ~CRTFrameBuilder(); - - private: - std::vector _all_runs; - - void reset(); - void complete(); - - uint8_t *get_next_run(); - friend CRT; - - void allocate_write_area(int required_length); - uint8_t *get_write_target_for_buffer(int buffer); - - // a pointer to the section of content buffer currently being - // returned and to where the next section will begin - uint16_t _next_write_x_position, _next_write_y_position; - uint16_t _write_x_position, _write_y_position; - size_t _write_target_pointer; -}; - -static const int kCRTNumberOfFrames = 4; - class CRT { public: + ~CRT(); + enum DisplayType { PAL50, NTSC60 }; - CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...); - CRT(unsigned int cycles_per_line, DisplayType displayType, unsigned int number_of_buffers, ...); - ~CRT(); + /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. + The requested number of buffers, each with the requested number of bytes per pixel, + is created for the machine to write raw pixel data to. + @param cycles_per_line The clock rate at which this CRT will be driven, specified as the number + of cycles expected to take up one whole scanline of the display. + + @param height_of_dispaly The number of lines that nominally form one field of the display, rounded + up to the next whole integer. + + @param colour_cycle_numerator Specifies the numerator for the per-line frequency of the colour subcarrier. + + @param colour_cycle_denominator Specifies the denominator for the per-line frequency of the colour subcarrier. + The colour subcarrier is taken to have colour_cycle_numerator/colour_cycle_denominator cycles per line. + + @param number_of_buffers The number of source data buffers to create for this machine. Machines + may provide per-clock-cycle data in any form that they consider convenient, supplying a sampling + function to convert between their data format and either a composite or RGB signal, allowing that + work to be offloaded onto the GPU and allowing the output signal to be sampled at a rate appropriate + to the display size. + + @param ... A list of sizes for source data buffers, provided as the number of bytes per sample. + For compatibility with OpenGL ES, samples should be 1–4 bytes in size. If a machine requires more + than 4 bytes/sample then it should use multiple buffers. + + @see @c set_rgb_sampling_function , @c set_composite_sampling_function + */ + CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...); + + /*! Constructs the CRT with the specified clock rate, with the display height and colour + subcarrier frequency dictated by a standard display type and with the requested number of + buffers, each with the requested number of bytes per pixel. + + Exactly identical to calling the designated constructor with colour subcarrier information + looked up by display type. + */ + CRT(unsigned int cycles_per_line, DisplayType displayType, unsigned int number_of_buffers, ...); + + /*! Resets the CRT with new timing information. The CRT then continues as though the new timing had + been provided at construction. */ void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator); + + /*! Resets the CRT with new timing information derived from a new display type. The CRT then continues + as though the new timing had been provided at construction. */ void set_new_display_type(unsigned int cycles_per_line, DisplayType displayType); - /*! Output at the sync level. + /*! Output at the sync level. @param number_of_cycles The amount of time to putput sync for. */ void output_sync(unsigned int number_of_cycles); - /*! Output at the blanking level. + /*! Output at the blanking level. @param number_of_cycles The amount of time to putput the blanking level for. */ void output_blank(unsigned int number_of_cycles); - /*! Outputs the first written to the most-recently created run of data repeatedly for a prolonged period. + /*! Outputs the first written to the most-recently created run of data repeatedly for a prolonged period. @param number_of_cycles The number of cycles to repeat the output for. */ void output_level(unsigned int number_of_cycles); - /*! Declares that the caller has created a run of data via @c allocate_write_area and @c get_write_target_for_buffer + /*! Declares that the caller has created a run of data via @c allocate_write_area and @c get_write_target_for_buffer that is at least @c number_of_cycles long, and that the first @c number_of_cycles/source_divider should be spread over that amount of time. @@ -86,10 +99,12 @@ class CRT { @param source_divider A divider for source data; if the divider is 1 then one source pixel is output every cycle, if it is 2 then one source pixel covers two cycles; if it is n then one source pixel covers n cycles. + + @see @c allocate_write_area , @c get_write_target_for_buffer */ void output_data(unsigned int number_of_cycles, unsigned int source_divider); - /*! Outputs a colour burst. + /*! Outputs a colour burst. @param number_of_cycles The length of the colour burst. @@ -101,43 +116,101 @@ class CRT { */ void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t magnitude); - class Delegate { - public: - virtual void crt_did_end_frame(CRT *crt, CRTFrame *frame, bool did_detect_vsync) = 0; - }; - void set_delegate(Delegate *delegate); - void return_frame(); + /*! Ensures that the given number of output samples are allocated for writing. + Following this call, the caller should call @c get_write_target_for_buffer for each + buffer they requested to get the location of the allocated memory. + + The beginning of the most recently allocated area is used as the start + of data written by a call to @c output_data; it is acceptable to write and to + output less data than the amount requested but that may be less efficient. + + @param required_length The number of samples to allocate. + */ void allocate_write_area(int required_length); + + /*! Gets a pointer for writing to the area created by the most recent call to @c allocate_write_area + for the nominated buffer. + + @param buffer The buffer to get a write target for. + */ uint8_t *get_write_target_for_buffer(int buffer); - /*! Gets the vertex shader for display of vended CRTFrames. + // MARK: Binding + class Delegate { + public: + /*! Notifies the delegate that a new frame is complete, providing an ID for it. - @returns A vertex shader, allocated using a C function. The caller then owns the memory - and is responsible for free'ing it. + The delegate then owns that frame. It can draw it by issuing an @c draw_frame + call to the CRT. It should call @c return_frame to return the oldest frame it + is currently holding. + + @param crt The CRT that produced the new frame. + @param frame_id An identifier for the finished frame, used for calls to @c draw_frame. + @param did_detect_vsync @c true if this frame ended due to a vsync signal detected in + the incoming signal; @c false otherwise. + */ + virtual void crt_did_end_frame(CRT *crt, int frame_id, bool did_detect_vsync) = 0; + }; + + /*! Sets the CRT frame delegate. The delegate will be notified as frames are completed; it is + responsible for requesting that they be drawn and returning them when they are no longer needed. + + @param delegate The delegate. */ - char *get_vertex_shader(); + void set_delegate(std::weak_ptr delegate); - /*! Gets a fragment shader for display of vended CRTFrames based on the supplied sampling function. + /*! Causes appropriate OpenGL or OpenGL ES calls to be made in order to draw a frame. The caller + is responsible for ensuring that a valid OpenGL context exists for the duration of this call. - @param sample_function A GLSL fragment including a function with the signature - `float sample(vec2 coordinate, float phase)` that evaluates to the composite signal level - as a function of a source buffer sampling location and the current colour carrier phase. - - @returns A complete fragment shader. + @param frame_id The frame to draw. */ - char *get_fragment_shader(const char *sample_function); + void draw_frame(int frame_id); - /*! Gets a fragment shader for composite display of vended CRTFrames based on a default encoding - of the supplied sampling function. + /*! Indicates that the delegate has no further interest in the oldest frame posted to it. */ + void return_frame(); - @param sample_function A GLSL fragent including a function with the signature + /*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than + the previous. + + @param should_delete_resources If @c true then all resources — textures, vertex arrays, etc — + currently held by the CRT will be deleted now via calls to glDeleteTexture and equivalent. If + @c false then the references are simply marked as invalid. + */ + void set_openGL_context_will_change(bool should_delete_resources); + + /*! Sets a function that will map from whatever data the machine provided to a composite signal. + + @param shader A GLSL fragment including a function with the signature + `float composite_sample(vec2 coordinate, float phase)` that evaluates to the composite signal + level as a function of a source buffer sampling location and the provided colour carrier phase. + The shader may assume a uniform array of sampler2Ds named `buffers` provides access to all input data. + */ + void set_composite_sampling_function(const char *shader); + + /*! Sets a function that will map from whatever data the machine provided to an RGB signal. + + If the output mode is composite then a default mapping from RGB to the display's composite + format will be applied. + + @param shader A GLSL fragent including a function with the signature `vec3 rgb_sample(vec2 coordinate)` that evaluates to an RGB colour as a function of the source buffer sampling location. - - @returns A complete fragment shader. + The shader may assume a uniform array of sampler2Ds named `buffers` provides access to all input data. */ - char *get_rgb_encoding_fragment_shader(const char *sample_function); + void set_rgb_sampling_function(const char *shader); + + /*! Optionally sets a function that will map from an input cycle count to a colour carrier phase. + + If this function is not supplied then the colour phase is determined from + the input clock rate and the the colour cycle clock rate. Machines whose per-line clock rate + is not intended exactly to match the normal line time may prefer to supply a custom function. + + @param A GLSL fragent including a function with the signature + `float phase_for_clock_cycle(int cycle)` that returns the colour phase at the beginning of + the supplied cycle. + */ + void set_phase_function(const char *shader); private: CRT(); @@ -159,13 +232,6 @@ class CRT { uint32_t x, y; } _rasterPosition, _scanSpeed[4], _beamWidth[4]; - // the run delegate and the triple buffer - CRTFrameBuilder *_frame_builders[kCRTNumberOfFrames]; - CRTFrameBuilder *_current_frame_builder; - int _frames_with_delegate; - int _frame_read_pointer; - Delegate *_delegate; - // outer 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) bool _did_detect_hsync; // true if horizontal sync was detected during this scanline (so, this affects flywheel adjustments) @@ -212,6 +278,69 @@ class CRT { } _scans[2]; int _next_scan; void output_scan(); + + // MARK: shader storage and information. + /*! Gets the vertex shader for display of vended CRTFrames. + + @returns A vertex shader, allocated using a C function. The caller then owns the memory + and is responsible for free'ing it. + */ + char *get_vertex_shader(); + + /*! Gets a fragment shader for display of vended CRTFrames based on the supplied sampling function. + + @param sample_function A GLSL fragment including a function with the signature + `float sample(vec2 coordinate, float phase)` that evaluates to the composite signal level + as a function of a source buffer sampling location and the current colour carrier phase. + + @returns A complete fragment shader. + */ + char *get_fragment_shader(const char *sample_function); + + /*! Gets a fragment shader for composite display of vended CRTFrames based on a default encoding + of the supplied sampling function. + + @param sample_function A GLSL fragent including a function with the signature + `vec3 rgb_sample(vec2 coordinate)` that evaluates to an RGB colour as a function of + the source buffer sampling location. + + @returns A complete fragment shader. + */ + char *get_rgb_encoding_fragment_shader(const char *sample_function); + + struct CRTFrameBuilder { + CRTFrame frame; + + CRTFrameBuilder(uint16_t width, uint16_t height, unsigned int number_of_buffers, va_list buffer_sizes); + ~CRTFrameBuilder(); + + private: + std::vector _all_runs; + + void reset(); + void complete(); + + uint8_t *get_next_run(); + friend CRT; + + void allocate_write_area(int required_length); + uint8_t *get_write_target_for_buffer(int buffer); + + // a pointer to the section of content buffer currently being + // returned and to where the next section will begin + uint16_t _next_write_x_position, _next_write_y_position; + uint16_t _write_x_position, _write_y_position; + size_t _write_target_pointer; + }; + + static const int kCRTNumberOfFrames = 4; + + // the run delegate and the triple buffer + CRTFrameBuilder *_frame_builders[kCRTNumberOfFrames]; + CRTFrameBuilder *_current_frame_builder; + int _frames_with_delegate; + int _frame_read_pointer; + Delegate *_delegate; }; } From 09e11469c33260c75259776fb69ce007f8ebe776 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 3 Feb 2016 19:11:18 -0500 Subject: [PATCH 080/307] Simplified further: eliminated delegate. Which means I can go frame-free internally to the CRT at some point, if I want. --- Outputs/CRT.hpp | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/Outputs/CRT.hpp b/Outputs/CRT.hpp index b473844a2..8a811a888 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT.hpp @@ -137,38 +137,11 @@ class CRT { uint8_t *get_write_target_for_buffer(int buffer); // MARK: Binding - class Delegate { - public: - /*! Notifies the delegate that a new frame is complete, providing an ID for it. - The delegate then owns that frame. It can draw it by issuing an @c draw_frame - call to the CRT. It should call @c return_frame to return the oldest frame it - is currently holding. - - @param crt The CRT that produced the new frame. - @param frame_id An identifier for the finished frame, used for calls to @c draw_frame. - @param did_detect_vsync @c true if this frame ended due to a vsync signal detected in - the incoming signal; @c false otherwise. - */ - virtual void crt_did_end_frame(CRT *crt, int frame_id, bool did_detect_vsync) = 0; - }; - - /*! Sets the CRT frame delegate. The delegate will be notified as frames are completed; it is - responsible for requesting that they be drawn and returning them when they are no longer needed. - - @param delegate The delegate. + /*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state. + The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call. */ - void set_delegate(std::weak_ptr delegate); - - /*! Causes appropriate OpenGL or OpenGL ES calls to be made in order to draw a frame. The caller - is responsible for ensuring that a valid OpenGL context exists for the duration of this call. - - @param frame_id The frame to draw. - */ - void draw_frame(int frame_id); - - /*! Indicates that the delegate has no further interest in the oldest frame posted to it. */ - void return_frame(); + void draw_frame(); /*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than the previous. From e0d51408e479c954c082c2c6fbb6806cf1801e49 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 4 Feb 2016 22:28:50 -0500 Subject: [PATCH 081/307] Onward with carrying this through to the bitter end, this at least results in all the appropriate knowledge and call-ins occuring at the CRT. --- Machines/Atari2600/Atari2600.cpp | 21 +-- Machines/Atari2600/Atari2600.hpp | 5 +- Machines/Electron/Electron.cpp | 21 +-- Machines/Electron/Electron.hpp | 3 +- .../Clock Signal.xcodeproj/project.pbxproj | 33 +++- .../Documents/Atari2600Document.swift | 2 +- .../Documents/ElectronDocument.swift | 4 + .../Documents/MachineDocument.swift | 1 + .../Mac/Clock Signal/Views/CSCathodeRayView.h | 13 +- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 171 ++++++++---------- .../Mac/Clock Signal/Wrappers/CSAtari2600.mm | 13 +- .../Mac/Clock Signal/Wrappers/CSElectron.h | 2 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 17 +- .../Wrappers/CSMachine+Subclassing.h | 2 - .../Mac/Clock Signal/Wrappers/CSMachine.mm | 15 -- Outputs/{ => CRT}/CRT.cpp | 105 ++--------- Outputs/{ => CRT}/CRT.hpp | 12 +- Outputs/{ => CRT}/CRTFrame.h | 0 Outputs/CRT/CRTFrameBuilder.cpp | 86 +++++++++ Outputs/CRT/CRTOpenGL.cpp | 50 +++++ 20 files changed, 286 insertions(+), 290 deletions(-) rename Outputs/{ => CRT}/CRT.cpp (88%) rename Outputs/{ => CRT}/CRT.hpp (98%) rename Outputs/{ => CRT}/CRTFrame.h (100%) create mode 100644 Outputs/CRT/CRTFrameBuilder.cpp create mode 100644 Outputs/CRT/CRTOpenGL.cpp diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 3cd0d00be..13d370462 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -25,6 +25,14 @@ Machine::Machine() : _tiaInputValue{0xff, 0xff} { _crt = new Outputs::CRT(228, Outputs::CRT::DisplayType::NTSC60, 1, 2); + _crt->set_composite_sampling_function( + "float sample(vec2 coordinate, float phase)\n" + "{\n" + "vec2 c = texture(texID, coordinate).rg;" + "float y = 0.1 + c.x * 0.91071428571429;\n" + "float aOffset = 6.283185308 * (c.y - 3.0 / 16.0) * 1.14285714285714;\n" + "return y + step(0.03125, c.y) * 0.1 * cos(phase - aOffset);\n" + "}"); memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); } @@ -147,19 +155,6 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset) pixel[1] = outputColour&0xf0; } -const char *Machine::get_signal_decoder() -{ - return - "float sample(vec2 coordinate, float phase)\n" - "{\n" - "vec2 c = texture(texID, coordinate).rg;" - "float y = 0.1 + c.x * 0.91071428571429;\n" - "float aOffset = 6.283185308 * (c.y - 3.0 / 16.0) * 1.14285714285714;\n" - "return y + step(0.03125, c.y) * 0.1 * cos(phase - aOffset);\n" - "}"; -} - - // in imputing the knowledge that all we're dealing with is the rollover from 159 to 0, // this is faster than the straightforward +1)%160 per profiling #define increment_object_counter(c) _objectCounter[c] = (_objectCounter[c]+1)&~((158-_objectCounter[c]) >> 8) diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 7b65b9db2..8c77f8605 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -10,7 +10,7 @@ #define Atari2600_cpp #include "../../Processors/6502/CPU6502.hpp" -#include "../../Outputs/CRT.hpp" +#include "../../Outputs/CRT/CRT.hpp" #include #include "Atari2600Inputs.h" @@ -19,7 +19,6 @@ namespace Atari2600 { class Machine: public CPU6502::Processor { public: - Machine(); ~Machine(); @@ -32,8 +31,6 @@ class Machine: public CPU6502::Processor { Outputs::CRT *get_crt() { return _crt; } - const char *get_signal_decoder(); - private: uint8_t *_rom, *_romPages[4], _ram[128]; size_t _rom_size; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 44bacb00d..825fe437f 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -29,6 +29,13 @@ Machine::Machine() : _currentOutputLine(0), _crt(Outputs::CRT(crt_cycles_per_line, Outputs::CRT::DisplayType::PAL50, 1, 1)) { + _crt.set_rgb_sampling_function( + "vec4 sample(vec2 coordinate)\n" + "{\n" + "float texValue = texture(texID, coordinate).r;\n" + "return vec4( step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)), 1.0);\n" + "}"); + memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); _interruptStatus = 0x02; @@ -455,7 +462,7 @@ inline void Machine::update_display() } - int pixels_to_output = std::min(_frameCycles - _displayOutputPosition, 104 - line_position); + int pixels_to_output = std::min(104 - line_position, cycles_left); _displayOutputPosition += pixels_to_output; // printf("<- %d ->", pixels_to_output); if(_screenMode >= 4) @@ -528,7 +535,7 @@ inline void Machine::update_display() if(line_position >= 104) { - int pixels_to_output = std::min(_frameCycles - _displayOutputPosition, 128 - line_position); + int pixels_to_output = std::min(128 - line_position, cycles_left); _crt.output_blank((unsigned int)pixels_to_output * crt_cycles_multiplier); _displayOutputPosition += pixels_to_output; @@ -556,16 +563,6 @@ inline void Machine::update_display() } } -const char *Machine::get_signal_decoder() -{ - return - "vec4 sample(vec2 coordinate)\n" - "{\n" - "float texValue = texture(texID, coordinate).r;\n" - "return vec4( step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)), 1.0);\n" - "}"; -} - void Machine::set_key_state(Key key, bool isPressed) { if(key == KeyBreak) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index d730be65a..af80ed2de 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -10,7 +10,7 @@ #define Electron_hpp #include "../../Processors/6502/CPU6502.hpp" -#include "../../Outputs/CRT.hpp" +#include "../../Outputs/CRT/CRT.hpp" #include "../../Outputs/Speaker.hpp" #include "../../Storage/Tape/Tape.hpp" #include @@ -148,7 +148,6 @@ class Machine: public CPU6502::Processor, Tape::Delegate { Outputs::CRT *get_crt() { return &_crt; } Outputs::Speaker *get_speaker() { return &_speaker; } - const char *get_signal_decoder(); virtual void tape_did_change_interrupt_status(Tape *tape); diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 24c2b772f..c60adc189 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; }; + 4B0CCC471C62D1A8001CAC5F /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */; }; 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */; }; 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; @@ -17,7 +19,6 @@ 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; - 4B366DFC1B5C165A0026627B /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B366DFA1B5C165A0026627B /* CRT.cpp */; }; 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE4A1C3B3B0C0093A61B /* CSAtari2600.mm */; }; 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */; }; 4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */; }; @@ -28,6 +29,7 @@ 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; + 4B7BFEFE1C6446EF00089C1C /* CRTFrameBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */; }; 4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; }; @@ -325,6 +327,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = ""; }; + 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = ""; }; + 4B0CCC441C62D0B3001CAC5F /* CRTFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CRTFrame.h; sourceTree = ""; }; + 4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = ""; }; 4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioQueue.h; sourceTree = ""; }; 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioQueue.m; sourceTree = ""; }; 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = ""; }; @@ -337,15 +343,12 @@ 4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = ""; }; 4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = ""; }; 4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = ""; }; - 4B2632551B631A510082A461 /* CRTFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CRTFrame.h; path = ../../Outputs/CRTFrame.h; sourceTree = ""; }; 4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = ""; }; 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; 4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = ""; }; 4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = ""; }; 4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = ""; }; 4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = ""; }; - 4B366DFA1B5C165A0026627B /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRT.cpp; path = ../../Outputs/CRT.cpp; sourceTree = ""; }; - 4B366DFB1B5C165A0026627B /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRT.hpp; path = ../../Outputs/CRT.hpp; sourceTree = ""; }; 4B55CE491C3B3B0C0093A61B /* CSAtari2600.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAtari2600.h; sourceTree = ""; }; 4B55CE4A1C3B3B0C0093A61B /* CSAtari2600.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAtari2600.mm; sourceTree = ""; }; 4B55CE4C1C3B3BDA0093A61B /* CSMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSMachine.h; sourceTree = ""; }; @@ -363,6 +366,7 @@ 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = ""; }; 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = ""; }; 4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTFrameBuilder.cpp; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = ""; }; 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = ""; }; 4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = ""; }; @@ -678,6 +682,19 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 4B0CCC411C62D0B3001CAC5F /* CRT */ = { + isa = PBXGroup; + children = ( + 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */, + 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */, + 4B0CCC441C62D0B3001CAC5F /* CRTFrame.h */, + 4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */, + 4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */, + ); + name = CRT; + path = ../../Outputs/CRT; + sourceTree = ""; + }; 4B1414561B58879D00E04248 /* 6502 */ = { isa = PBXGroup; children = ( @@ -730,9 +747,7 @@ 4B366DFD1B5C165F0026627B /* Outputs */ = { isa = PBXGroup; children = ( - 4B366DFA1B5C165A0026627B /* CRT.cpp */, - 4B366DFB1B5C165A0026627B /* CRT.hpp */, - 4B2632551B631A510082A461 /* CRTFrame.h */, + 4B0CCC411C62D0B3001CAC5F /* CRT */, 4B2409531C45AB05004DA684 /* Speaker.cpp */, 4B2409541C45AB05004DA684 /* Speaker.hpp */, ); @@ -1588,10 +1603,13 @@ buildActionMask = 2147483647; files = ( 4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */, + 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, + 4B0CCC471C62D1A8001CAC5F /* CRTOpenGL.cpp in Sources */, 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, + 4B7BFEFE1C6446EF00089C1C /* CRTFrameBuilder.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, @@ -1599,7 +1617,6 @@ 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSCathodeRayView.m in Sources */, - 4B366DFC1B5C165A0026627B /* CRT.cpp in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */, 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index ba88e0797..ec6b1e3ce 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -19,7 +19,7 @@ class Atari2600Document: MachineDocument { override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) atari2600.view = openGLView - openGLView.frameBounds = CGRectMake(0.1, 0.1, 0.8, 0.8) +// openGLView.frameBounds = CGRectMake(0.1, 0.1, 0.8, 0.8) } override class func autosavesInPlace() -> Bool { diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index ba3243a39..43cb38475 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -61,6 +61,10 @@ class ElectronDocument: MachineDocument { electron.runForNumberOfCycles(numberOfCycles) } + override func openGLViewDrawView(view: CSCathodeRayView) { + electron.drawViewForPixelSize(view.backingSize) + } + // MARK: CSOpenGLViewResponderDelegate override func keyDown(event: NSEvent) { electron.setKey(event.keyCode, isPressed: true) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 428cc05a0..126eb2a9d 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -46,6 +46,7 @@ class MachineDocument: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewRes lastCycleCount = cycleCount } + func openGLViewDrawView(view: CSCathodeRayView) {} func runForNumberOfCycles(numberOfCycles: Int32) {} // MARK: CSOpenGLViewResponderDelegate diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h index 5eff05a12..abecc655b 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h @@ -7,13 +7,13 @@ // #import -#import "CRTFrame.h" #import @class CSCathodeRayView; @protocol CSCathodeRayViewDelegate - (void)openGLView:(nonnull CSCathodeRayView *)view didUpdateToTime:(CVTimeStamp)time; +- (void)openGLViewDrawView:(nonnull CSCathodeRayView *)view; @end @protocol CSCathodeRayViewResponderDelegate @@ -22,10 +22,6 @@ - (void)flagsChanged:(nonnull NSEvent *)newModifiers; @end -typedef NS_ENUM(NSInteger, CSCathodeRayViewSignalType) { - CSCathodeRayViewSignalTypeNTSC, - CSCathodeRayViewSignalTypeRGB -}; @interface CSCathodeRayView : NSOpenGLView @@ -34,11 +30,6 @@ typedef NS_ENUM(NSInteger, CSCathodeRayViewSignalType) { - (void)invalidate; -- (BOOL)pushFrame:(nonnull CRTFrame *)crtFrame; -- (void)setSignalDecoder:(nonnull NSString *)decoder type:(CSCathodeRayViewSignalType)type; - -// these are relative to a [0, 1] range in both width and height; -// default is .origin = (0, 0), .size = (1, 1) -@property (nonatomic, assign) CGRect frameBounds; +@property (nonatomic, readonly) CGSize backingSize; @end diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 7f08a7107..45a9a3f64 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -10,38 +10,16 @@ @import CoreVideo; @import GLKit; #import -#import -#import +//#import +//#import @implementation CSCathodeRayView { - CVDisplayLinkRef displayLink; - - GLuint _vertexShader, _fragmentShader; - GLuint _shaderProgram; - GLuint _arrayBuffer, _vertexArray; - GLint _positionAttribute; - GLint _textureCoordinatesAttribute; - GLint _lateralAttribute; - - GLint _textureSizeUniform, _windowSizeUniform; - GLint _boundsOriginUniform, _boundsSizeUniform; - GLint _alphaUniform; - - GLuint _textureName, _shadowMaskTextureName; - CRTSize _textureSize; - - CRTFrame *_crtFrame; - - NSString *_signalDecoder; - CSCathodeRayViewSignalType _signalType; - int32_t _signalDecoderGeneration; - int32_t _compiledSignalDecoderGeneration; - + CVDisplayLinkRef _displayLink; CGRect _aspectRatioCorrectedBounds; } -- (GLuint)textureForImageNamed:(NSString *)name +/*- (GLuint)textureForImageNamed:(NSString *)name { NSImage *const image = [NSImage imageNamed:name]; NSBitmapImageRep *bitmapRepresentation = [[NSBitmapImageRep alloc] initWithData: [image TIFFRepresentation]]; @@ -57,7 +35,7 @@ glGenerateMipmap(GL_TEXTURE_2D); return textureName; -} +}*/ - (void)prepareOpenGL { @@ -66,29 +44,29 @@ [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; // Create a display link capable of being used with all active displays - CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); + CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); // Set the renderer output callback function - CVDisplayLinkSetOutputCallback(displayLink, DisplayLinkCallback, (__bridge void * __nullable)(self)); + CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void * __nullable)(self)); // Set the display link for the current renderer CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; - CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat); + CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat); // install the shadow mask texture as the second texture - glActiveTexture(GL_TEXTURE1); +/* glActiveTexture(GL_TEXTURE1); _shadowMaskTextureName = [self textureForImageNamed:@"ShadowMask"]; // otherwise, we'll be working on the first texture - glActiveTexture(GL_TEXTURE0); + glActiveTexture(GL_TEXTURE0);*/ // get the shader ready, set the clear colour [self.openGLContext makeCurrentContext]; glClearColor(0.0, 0.0, 0.0, 1.0); // Activate the display link - CVDisplayLinkStart(displayLink); + CVDisplayLinkStart(_displayLink); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); @@ -103,27 +81,26 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)invalidate { - CVDisplayLinkStop(displayLink); + CVDisplayLinkStop(_displayLink); } - (void)dealloc { // Release the display link - CVDisplayLinkRelease(displayLink); + CVDisplayLinkRelease(_displayLink); // Release OpenGL buffers - [self.openGLContext makeCurrentContext]; - glDeleteBuffers(1, &_arrayBuffer); - glDeleteVertexArrays(1, &_vertexArray); - glDeleteTextures(1, &_textureName); - glDeleteTextures(1, &_shadowMaskTextureName); - glDeleteProgram(_shaderProgram); +// [self.openGLContext makeCurrentContext]; +// glDeleteBuffers(1, &_arrayBuffer); +// glDeleteVertexArrays(1, &_vertexArray); +// glDeleteTextures(1, &_textureName); +// glDeleteTextures(1, &_shadowMaskTextureName); +// glDeleteProgram(_shaderProgram); } -- (NSPoint)backingViewSize +- (CGSize)backingSize { - NSPoint backingSize = {.x = self.bounds.size.width, .y = self.bounds.size.height}; - return [self convertPointToBacking:backingSize]; + return [self convertSizeToBacking:self.bounds.size]; } - (void)reshape @@ -133,49 +110,49 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt [self.openGLContext makeCurrentContext]; CGLLockContext([[self openGLContext] CGLContextObj]); - NSPoint viewSize = [self backingViewSize]; - glViewport(0, 0, (GLsizei)viewSize.x, (GLsizei)viewSize.y); + CGSize viewSize = [self backingSize]; + glViewport(0, 0, (GLsizei)viewSize.width, (GLsizei)viewSize.height); - [self pushSizeUniforms]; +// [self pushSizeUniforms]; CGLUnlockContext([[self openGLContext] CGLContextObj]); } -- (void)setFrameBounds:(CGRect)frameBounds -{ - _frameBounds = frameBounds; - - [self.openGLContext makeCurrentContext]; - CGLLockContext([[self openGLContext] CGLContextObj]); - - [self pushSizeUniforms]; - - CGLUnlockContext([[self openGLContext] CGLContextObj]); -} - -- (void)pushSizeUniforms -{ - if(_shaderProgram) - { - NSPoint viewSize = [self backingViewSize]; - if(_windowSizeUniform >= 0) - { - glUniform2f(_windowSizeUniform, (GLfloat)viewSize.x, (GLfloat)viewSize.y); - } - - CGFloat outputAspectRatioMultiplier = (viewSize.x / viewSize.y) / (4.0 / 3.0); - -// NSLog(@"%0.2f v %0.2f", outputAspectRatio, desiredOutputAspectRatio); - _aspectRatioCorrectedBounds = _frameBounds; - - CGFloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * _frameBounds.size.width; - _aspectRatioCorrectedBounds.origin.x -= bonusWidth * 0.5f * _aspectRatioCorrectedBounds.size.width; - _aspectRatioCorrectedBounds.size.width *= outputAspectRatioMultiplier; - - if(_boundsOriginUniform >= 0) glUniform2f(_boundsOriginUniform, (GLfloat)_aspectRatioCorrectedBounds.origin.x, (GLfloat)_aspectRatioCorrectedBounds.origin.y); - if(_boundsSizeUniform >= 0) glUniform2f(_boundsSizeUniform, (GLfloat)_aspectRatioCorrectedBounds.size.width, (GLfloat)_aspectRatioCorrectedBounds.size.height); - } -} +//- (void)setFrameBounds:(CGRect)frameBounds +//{ +// _frameBounds = frameBounds; +// +// [self.openGLContext makeCurrentContext]; +// CGLLockContext([[self openGLContext] CGLContextObj]); +// +// [self pushSizeUniforms]; +// +// CGLUnlockContext([[self openGLContext] CGLContextObj]); +//} +// +//- (void)pushSizeUniforms +//{ +// if(_shaderProgram) +// { +// NSPoint viewSize = [self backingViewSize]; +// if(_windowSizeUniform >= 0) +// { +// glUniform2f(_windowSizeUniform, (GLfloat)viewSize.x, (GLfloat)viewSize.y); +// } +// +// CGFloat outputAspectRatioMultiplier = (viewSize.x / viewSize.y) / (4.0 / 3.0); +// +//// NSLog(@"%0.2f v %0.2f", outputAspectRatio, desiredOutputAspectRatio); +// _aspectRatioCorrectedBounds = _frameBounds; +// +// CGFloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * _frameBounds.size.width; +// _aspectRatioCorrectedBounds.origin.x -= bonusWidth * 0.5f * _aspectRatioCorrectedBounds.size.width; +// _aspectRatioCorrectedBounds.size.width *= outputAspectRatioMultiplier; +// +// if(_boundsOriginUniform >= 0) glUniform2f(_boundsOriginUniform, (GLfloat)_aspectRatioCorrectedBounds.origin.x, (GLfloat)_aspectRatioCorrectedBounds.origin.y); +// if(_boundsSizeUniform >= 0) glUniform2f(_boundsSizeUniform, (GLfloat)_aspectRatioCorrectedBounds.size.width, (GLfloat)_aspectRatioCorrectedBounds.size.height); +// } +//} - (void)awakeFromNib { @@ -205,10 +182,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt self.wantsBestResolutionOpenGLSurface = YES; // establish default instance variable values - self.frameBounds = CGRectMake(0.0, 0.0, 1.0, 1.0); +// self.frameBounds = CGRectMake(0.0, 0.0, 1.0, 1.0); } -- (GLint)formatForDepth:(unsigned int)depth +/*- (GLint)formatForDepth:(unsigned int)depth { switch(depth) { @@ -489,7 +466,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt 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); -} +}*/ - (void)drawRect:(NSRect)dirtyRect { @@ -501,18 +478,20 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt [self.openGLContext makeCurrentContext]; CGLLockContext([[self openGLContext] CGLContextObj]); - while((!_shaderProgram || (_signalDecoderGeneration != _compiledSignalDecoderGeneration)) && _signalDecoder) { - _compiledSignalDecoderGeneration = _signalDecoderGeneration; - [self prepareShader]; - } - glClear(GL_COLOR_BUFFER_BIT); - - if (_crtFrame) - { - if(_textureSizeUniform >= 0) glUniform2f(_textureSizeUniform, _crtFrame->size.width, _crtFrame->size.height); - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_crtFrame->number_of_vertices); - } + [self.delegate openGLViewDrawView:self]; +// while((!_shaderProgram || (_signalDecoderGeneration != _compiledSignalDecoderGeneration)) && _signalDecoder) { +// _compiledSignalDecoderGeneration = _signalDecoderGeneration; +// [self prepareShader]; +// } +// +// glClear(GL_COLOR_BUFFER_BIT); +// +// if (_crtFrame) +// { +// if(_textureSizeUniform >= 0) glUniform2f(_textureSizeUniform, _crtFrame->size.width, _crtFrame->size.height); +// glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_crtFrame->number_of_vertices); +// } CGLFlushDrawable([[self openGLContext] CGLContextObj]); CGLUnlockContext([[self openGLContext] CGLContextObj]); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm index 49c47e6c3..80e9d564f 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -19,7 +19,7 @@ BOOL _didDecideRegion; } -- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync { +/*- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync { if(!_didDecideRegion) { _frameCount++; @@ -41,7 +41,7 @@ } [super crt:crt didEndFrame:frame didDetectVSync:didDetectVSync]; -} +}*/ - (void)doRunForNumberOfCycles:(int)numberOfCycles { _atari2600.run_for_cycles(numberOfCycles); @@ -65,13 +65,4 @@ }]; } -- (void)setView:(CSCathodeRayView *)view { - [super setView:view]; - [view setSignalDecoder:[NSString stringWithUTF8String:_atari2600.get_signal_decoder()] type:CSCathodeRayViewSignalTypeNTSC]; -} - -- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate{ - _atari2600.get_crt()->set_delegate(delegate); -} - @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index ca21d949d..a58cf49cc 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -18,4 +18,6 @@ - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; +- (void)drawViewForPixelSize:(CGSize)pixelSize; + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 54ce70b87..09a50c8f4 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -14,21 +14,9 @@ @implementation CSElectron { Electron::Machine _electron; - -// NSTimeInterval _periodicStart; -// int _numberOfCycles; } - (void)doRunForNumberOfCycles:(int)numberOfCycles { -/* _numberOfCycles += numberOfCycles; - NSTimeInterval timeNow = [NSDate timeIntervalSinceReferenceDate]; - NSTimeInterval difference = timeNow - _periodicStart; - if(difference > 1.0) - { - NSLog(@"cycles: %0.0f", (double)_numberOfCycles / difference); - _periodicStart = timeNow; - _numberOfCycles = 0; - }*/ _electron.run_for_cycles(numberOfCycles); } @@ -44,8 +32,8 @@ _electron.set_rom((Electron::ROMSlot)slot, rom.length, (const uint8_t *)rom.bytes); } -- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate { - _electron.get_crt()->set_delegate(delegate); +- (void)drawViewForPixelSize:(CGSize)pixelSize { + _electron.get_crt()->draw_frame((int)pixelSize.width, (int)pixelSize.height); } - (BOOL)openUEFAtURL:(NSURL *)URL { @@ -67,7 +55,6 @@ - (void)setView:(CSCathodeRayView *)view { [super setView:view]; - [view setSignalDecoder:[NSString stringWithUTF8String:_electron.get_signal_decoder()] type:CSCathodeRayViewSignalTypeRGB]; } - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 255af2ae1..0f991da4b 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -13,12 +13,10 @@ @interface CSMachine (Subclassing) - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate; -- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate; - (void)doRunForNumberOfCycles:(int)numberOfCycles; - (void)perform:(dispatch_block_t)action; -- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync; - (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index 0711abeec..a237f1eb7 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -9,13 +9,6 @@ #import "CSMachine.h" #import "CSMachine+Subclassing.h" -struct CRTDelegate: public Outputs::CRT::Delegate { - __weak CSMachine *machine; - void crt_did_end_frame(Outputs::CRT *crt, CRTFrame *frame, bool did_detect_vsync) { - [machine crt:crt didEndFrame:frame didDetectVSync:did_detect_vsync]; - } -}; - struct SpeakerDelegate: public Outputs::Speaker::Delegate { __weak CSMachine *machine; void speaker_did_complete_samples(Outputs::Speaker *speaker, const int16_t *buffer, int buffer_size) { @@ -29,7 +22,6 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { }; @implementation CSMachine { - CRTDelegate _crtDelegate; SpeakerDelegate _speakerDelegate; dispatch_queue_t _serialDispatchQueue; @@ -40,10 +32,6 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { dispatch_async(_serialDispatchQueue, action); } -- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync { - if([self.view pushFrame:frame]) crt->return_frame(); -} - - (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length { [self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; } @@ -66,16 +54,13 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { _serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL); _runningLock = [[NSConditionLock alloc] initWithCondition:CSMachineRunningStateStopped]; - _crtDelegate.machine = self; _speakerDelegate.machine = self; - [self setCRTDelegate:&_crtDelegate]; [self setSpeakerDelegate:&_speakerDelegate sampleRate:44100]; } return self; } -- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate {} - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { return NO; } diff --git a/Outputs/CRT.cpp b/Outputs/CRT/CRT.cpp similarity index 88% rename from Outputs/CRT.cpp rename to Outputs/CRT/CRT.cpp index e87d8b8c2..153059d4f 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -99,13 +99,14 @@ void CRT::allocate_buffers(unsigned int number, va_list sizes) CRT::CRT() : _next_scan(0), - _frames_with_delegate(0), _frame_read_pointer(0), _horizontal_counter(0), _sync_capacitor_charge_level(0), _is_receiving_sync(false), _is_in_hsync(false), _is_in_vsync(false), + _current_frame(nullptr), + _current_frame_mutex(new std::mutex), _rasterPosition({.x = 0, .y = 0}) {} @@ -332,21 +333,24 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // end of vertical sync: tell the delegate that we finished vertical sync, // releasing all runs back into the common pool case SyncEvent::EndVSync: - if(_delegate && _current_frame_builder) + if(_current_frame_builder) { _current_frame_builder->complete(); - _frames_with_delegate++; - _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); + _current_frame_mutex->lock(); + _current_frame = &_current_frame_builder->frame; + _current_frame_mutex->unlock(); + // TODO: how to communicate did_detect_vsync? Bring the delegate back? +// _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); } - if(_frames_with_delegate < kCRTNumberOfFrames) +// if(_frames_with_delegate < kCRTNumberOfFrames) { _frame_read_pointer = (_frame_read_pointer + 1)%kCRTNumberOfFrames; _current_frame_builder = _frame_builders[_frame_read_pointer]; _current_frame_builder->reset(); } - else - _current_frame_builder = nullptr; +// else +// _current_frame_builder = nullptr; _is_in_vsync = false; _did_detect_vsync = false; @@ -358,18 +362,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi } } -void CRT::return_frame() -{ - _frames_with_delegate--; -} - -#pragma mark - delegate - -void CRT::set_delegate(Delegate *delegate) -{ - _delegate = delegate; -} - #pragma mark - stream feeding methods void CRT::output_scan() @@ -443,82 +435,7 @@ uint8_t *CRT::get_write_target_for_buffer(int buffer) return _current_frame_builder->get_write_target_for_buffer(buffer); } -#pragma mark - CRTFrame -CRTFrameBuilder::CRTFrameBuilder(uint16_t width, uint16_t height, unsigned int number_of_buffers, va_list buffer_sizes) -{ - frame.size.width = width; - frame.size.height = height; - frame.number_of_buffers = number_of_buffers; - frame.buffers = new CRTBuffer[number_of_buffers]; - frame.size_per_vertex = kCRTSizeOfVertex; - frame.geometry_mode = CRTGeometryModeTriangles; - - for(int buffer = 0; buffer < number_of_buffers; buffer++) - { - frame.buffers[buffer].depth = va_arg(buffer_sizes, unsigned int); - frame.buffers[buffer].data = new uint8_t[width * height * frame.buffers[buffer].depth]; - } - - reset(); -} - -CRTFrameBuilder::~CRTFrameBuilder() -{ - for(int buffer = 0; buffer < frame.number_of_buffers; buffer++) - delete[] frame.buffers[buffer].data; - delete frame.buffers; -} - -void CRTFrameBuilder::reset() -{ - frame.number_of_vertices = 0; - _next_write_x_position = _next_write_y_position = 0; - frame.dirty_size.width = 0; - frame.dirty_size.height = 1; -} - -void CRTFrameBuilder::complete() -{ - frame.vertices = &_all_runs[0]; -} - -uint8_t *CRTFrameBuilder::get_next_run() -{ - const size_t vertices_per_run = 6; - - // get a run from the allocated list, allocating more if we're about to overrun - if((frame.number_of_vertices + vertices_per_run) * frame.size_per_vertex >= _all_runs.size()) - { - _all_runs.resize(_all_runs.size() + frame.size_per_vertex * vertices_per_run * 100); - } - - uint8_t *next_run = &_all_runs[frame.number_of_vertices * frame.size_per_vertex]; - frame.number_of_vertices += vertices_per_run; - - return next_run; -} - -void CRTFrameBuilder::allocate_write_area(int required_length) -{ - if (_next_write_x_position + required_length > frame.size.width) - { - _next_write_x_position = 0; - _next_write_y_position = (_next_write_y_position+1)&(frame.size.height-1); - frame.dirty_size.height++; - } - - _write_x_position = _next_write_x_position; - _write_y_position = _next_write_y_position; - _write_target_pointer = (_write_y_position * frame.size.width) + _write_x_position; - _next_write_x_position += required_length; - frame.dirty_size.width = std::max(frame.dirty_size.width, _next_write_x_position); -} - -uint8_t *CRTFrameBuilder::get_write_target_for_buffer(int buffer) -{ - return &frame.buffers[buffer].data[_write_target_pointer * frame.buffers[buffer].depth]; -} char *CRT::get_vertex_shader() { diff --git a/Outputs/CRT.hpp b/Outputs/CRT/CRT.hpp similarity index 98% rename from Outputs/CRT.hpp rename to Outputs/CRT/CRT.hpp index 8a811a888..be40816f9 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "CRTFrame.h" @@ -136,12 +137,10 @@ class CRT { */ uint8_t *get_write_target_for_buffer(int buffer); - // MARK: Binding - /*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state. The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call. */ - void draw_frame(); + void draw_frame(int output_width, int output_height); /*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than the previous. @@ -308,12 +307,13 @@ class CRT { static const int kCRTNumberOfFrames = 4; - // the run delegate and the triple buffer + // the triple buffer and OpenGL state CRTFrameBuilder *_frame_builders[kCRTNumberOfFrames]; CRTFrameBuilder *_current_frame_builder; - int _frames_with_delegate; + CRTFrame *_current_frame; + std::shared_ptr _current_frame_mutex; int _frame_read_pointer; - Delegate *_delegate; + void *openGLState; }; } diff --git a/Outputs/CRTFrame.h b/Outputs/CRT/CRTFrame.h similarity index 100% rename from Outputs/CRTFrame.h rename to Outputs/CRT/CRTFrame.h diff --git a/Outputs/CRT/CRTFrameBuilder.cpp b/Outputs/CRT/CRTFrameBuilder.cpp new file mode 100644 index 000000000..5a7752077 --- /dev/null +++ b/Outputs/CRT/CRTFrameBuilder.cpp @@ -0,0 +1,86 @@ +// +// CRTFrameBuilder.cpp +// Clock Signal +// +// Created by Thomas Harte on 04/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "CRT.hpp" + +using namespace Outputs; + +CRT::CRTFrameBuilder::CRTFrameBuilder(uint16_t width, uint16_t height, unsigned int number_of_buffers, va_list buffer_sizes) +{ + frame.size.width = width; + frame.size.height = height; + frame.number_of_buffers = number_of_buffers; + frame.buffers = new CRTBuffer[number_of_buffers]; + frame.size_per_vertex = kCRTSizeOfVertex; + frame.geometry_mode = CRTGeometryModeTriangles; + + for(int buffer = 0; buffer < number_of_buffers; buffer++) + { + frame.buffers[buffer].depth = va_arg(buffer_sizes, unsigned int); + frame.buffers[buffer].data = new uint8_t[width * height * frame.buffers[buffer].depth]; + } + + reset(); +} + +CRT::CRTFrameBuilder::~CRTFrameBuilder() +{ + for(int buffer = 0; buffer < frame.number_of_buffers; buffer++) + delete[] frame.buffers[buffer].data; + delete frame.buffers; +} + +void CRT::CRTFrameBuilder::reset() +{ + frame.number_of_vertices = 0; + _next_write_x_position = _next_write_y_position = 0; + frame.dirty_size.width = 0; + frame.dirty_size.height = 1; +} + +void CRT::CRTFrameBuilder::complete() +{ + frame.vertices = &_all_runs[0]; +} + +uint8_t *CRT::CRTFrameBuilder::get_next_run() +{ + const size_t vertices_per_run = 6; + + // get a run from the allocated list, allocating more if we're about to overrun + if((frame.number_of_vertices + vertices_per_run) * frame.size_per_vertex >= _all_runs.size()) + { + _all_runs.resize(_all_runs.size() + frame.size_per_vertex * vertices_per_run * 100); + } + + uint8_t *next_run = &_all_runs[frame.number_of_vertices * frame.size_per_vertex]; + frame.number_of_vertices += vertices_per_run; + + return next_run; +} + +void CRT::CRTFrameBuilder::allocate_write_area(int required_length) +{ + if (_next_write_x_position + required_length > frame.size.width) + { + _next_write_x_position = 0; + _next_write_y_position = (_next_write_y_position+1)&(frame.size.height-1); + frame.dirty_size.height++; + } + + _write_x_position = _next_write_x_position; + _write_y_position = _next_write_y_position; + _write_target_pointer = (_write_y_position * frame.size.width) + _write_x_position; + _next_write_x_position += required_length; + frame.dirty_size.width = std::max(frame.dirty_size.width, _next_write_x_position); +} + +uint8_t *CRT::CRTFrameBuilder::get_write_target_for_buffer(int buffer) +{ + return &frame.buffers[buffer].data[_write_target_pointer * frame.buffers[buffer].depth]; +} diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp new file mode 100644 index 000000000..12174fd22 --- /dev/null +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -0,0 +1,50 @@ +// +// CRTOpenGL.cpp +// Clock Signal +// +// Created by Thomas Harte on 03/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "CRT.hpp" + +// TODO: figure out correct include paths for other platforms. +#include + +using namespace Outputs; + +namespace { + struct OpenGLState { + GLuint _vertexShader, _fragmentShader; + GLuint _shaderProgram; + GLuint _arrayBuffer, _vertexArray; + + GLint _positionAttribute; + GLint _textureCoordinatesAttribute; + GLint _lateralAttribute; + + GLint _textureSizeUniform, _windowSizeUniform; + GLint _boundsOriginUniform, _boundsSizeUniform; + GLint _alphaUniform; + + GLuint _textureName, _shadowMaskTextureName; + }; +} + +void CRT::draw_frame(int output_width, int output_height) +{ + printf("%d %d\n", output_width, output_height); +} + +void CRT::set_openGL_context_will_change(bool should_delete_resources) +{ +} + +void CRT::set_composite_sampling_function(const char *shader) +{ +} + +void CRT::set_rgb_sampling_function(const char *shader) +{ + printf("%s\n", shader); +} From 8bc3f8046d93ab684e4baf04c63f597b57021096 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 4 Feb 2016 22:57:46 -0500 Subject: [PATCH 082/307] This endeavours to move everything into the CRT class except the final version of shader building. --- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 194 +------------ .../Mac/Clock Signal/Wrappers/CSElectron.mm | 2 +- Outputs/CRT/CRT.cpp | 152 +---------- Outputs/CRT/CRT.hpp | 45 +-- Outputs/CRT/CRTOpenGL.cpp | 257 ++++++++++++++++-- 5 files changed, 260 insertions(+), 390 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 45a9a3f64..069f10dd0 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -185,44 +185,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt // self.frameBounds = CGRectMake(0.0, 0.0, 1.0, 1.0); } -/*- (GLint)formatForDepth:(unsigned int)depth -{ - switch(depth) - { - default: return -1; - case 1: return GL_RED; - case 2: return GL_RG; - case 3: return GL_RGB; - case 4: return GL_RGBA; - } -} - -- (BOOL)pushFrame:(nonnull CRTFrame *)crtFrame -{ - [[self openGLContext] makeCurrentContext]; - CGLLockContext([[self openGLContext] CGLContextObj]); - - BOOL hadFrame = _crtFrame ? YES : NO; - _crtFrame = crtFrame; - - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_crtFrame->number_of_vertices * _crtFrame->size_per_vertex), _crtFrame->vertices, GL_DYNAMIC_DRAW); - - glBindTexture(GL_TEXTURE_2D, _textureName); - if(_textureSize.width != _crtFrame->size.width || _textureSize.height != _crtFrame->size.height) - { - GLint format = [self formatForDepth:_crtFrame->buffers[0].depth]; - glTexImage2D(GL_TEXTURE_2D, 0, format, _crtFrame->size.width, _crtFrame->size.height, 0, (GLenum)format, GL_UNSIGNED_BYTE, _crtFrame->buffers[0].data); - _textureSize = _crtFrame->size; - } - else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _crtFrame->size.width, _crtFrame->dirty_size.height, (GLenum)[self formatForDepth:_crtFrame->buffers[0].depth], GL_UNSIGNED_BYTE, _crtFrame->buffers[0].data); - - [self drawView]; - - CGLUnlockContext([[self openGLContext] CGLContextObj]); - - return hadFrame; -} +/* #pragma mark - Frame output @@ -265,138 +228,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt OSAtomicIncrement32(&_signalDecoderGeneration); } -- (nonnull NSString *)vertexShaderForType:(CSCathodeRayViewSignalType)type -{ - // the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the - // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates - // from integral to floating point; there's also some setup for NTSC, PAL or whatever. - - NSString *const ntscVertexShaderGlobals = - @"out vec2 srcCoordinatesVarying[4];\n" - "out float phase;\n"; - - NSString *const ntscVertexShaderBody = - @"phase = srcCoordinates.x * 6.283185308;\n" - "\n" - "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" - "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.5 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] - vec2(0.25 / textureSize.x, 0.0);\n"; - - NSString *const rgbVertexShaderGlobals = - @"out vec2 srcCoordinatesVarying[5];\n"; - - NSString *const rgbVertexShaderBody = - @"srcCoordinatesVarying[2] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" - "srcCoordinatesVarying[0] = srcCoordinatesVarying[1] - vec2(1.0 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[1] = srcCoordinatesVarying[1] - vec2(0.5 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[3] = srcCoordinatesVarying[1] + vec2(0.5 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[4] = srcCoordinatesVarying[1] + vec2(1.0 / textureSize.x, 0.0);\n"; - - NSString *const vertexShader = - @"#version 150\n" - "\n" - "in vec2 position;\n" - "in vec2 srcCoordinates;\n" - "in float lateral;\n" - "\n" - "uniform vec2 boundsOrigin;\n" - "uniform vec2 boundsSize;\n" - "\n" - "out float lateralVarying;\n" - "out vec2 shadowMaskCoordinates;\n" - "\n" - "uniform vec2 textureSize;\n" - "\n" - "const float shadowMaskMultiple = 600;\n" - "\n" - "%@\n" - "void main (void)\n" - "{\n" - "lateralVarying = lateral + 1.0707963267949;\n" - "\n" - "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);\n" - "\n" - "%@\n" - "\n" - "vec2 mappedPosition = (position - boundsOrigin) / boundsSize;" - "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);\n" - "}\n"; - -// + mappedPosition.x / 131.0 - - switch(_signalType) - { - case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:vertexShader, ntscVertexShaderGlobals, ntscVertexShaderBody]; - case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:vertexShader, rgbVertexShaderGlobals, rgbVertexShaderBody]; - } -} - -- (nonnull NSString *)fragmentShaderForType:(CSCathodeRayViewSignalType)type -{ - NSString *const fragmentShader = - @"#version 150\n" - "\n" - "in float lateralVarying;\n" - "in vec2 shadowMaskCoordinates;\n" - "out vec4 fragColour;\n" - "\n" - "uniform sampler2D texID;\n" - "uniform sampler2D shadowMaskTexID;\n" - "uniform float alpha;\n" - "\n" - "%@\n" - "%%@\n" - "\n" - "void main(void)\n" - "{\n" - "%@\n" - "}\n"; - - NSString *const ntscFragmentShaderGlobals = - @"in vec2 srcCoordinatesVarying[4];\n" - "in float phase;\n" - "\n" - "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" - "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; - - NSString *const ntscFragmentShaderBody = - @"vec3 angles = vec3(phase) + vec3(0.0, -3.141592654, -1.570796327);\n" - "vec3 samples = vec3(" - " sample(srcCoordinatesVarying[0], angles.x)," - " sample(srcCoordinatesVarying[1], angles.y)," - " sample(srcCoordinatesVarying[2], angles.z)" - ");\n" - "\n" - "float y = dot(vec2(0.5), samples.xy);\n" - "samples -= vec3(y);\n" - "\n" - "float i = dot(vec3(0.75), cos(angles) * samples);\n" - "float q = dot(vec3(0.75), sin(angles) * samples);\n" - "\n" - "fragColour = vec4(yiqToRGB * vec3(y, i, q), 1.0);\n"; //sin(lateralVarying)); - // 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * -// "float y2 = dot(vec2(0.5), samples.zw);\n" - - NSString *const rgbFragmentShaderGlobals = - @"in vec2 srcCoordinatesVarying[5];\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * - - NSString *const rgbFragmentShaderBody = - @"fragColour = sample(srcCoordinatesVarying[2]);"; -// @"fragColour = (sample(srcCoordinatesVarying[0]) * -0.1) + \ -// (sample(srcCoordinatesVarying[1]) * 0.3) + \ -// (sample(srcCoordinatesVarying[2]) * 0.6) + \ -// (sample(srcCoordinatesVarying[3]) * 0.3) + \ -// (sample(srcCoordinatesVarying[4]) * -0.1);"; - -// dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; - - switch(_signalType) - { - case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:fragmentShader, ntscFragmentShaderGlobals, ntscFragmentShaderBody]; - case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:fragmentShader, rgbFragmentShaderGlobals, rgbFragmentShaderBody]; - } -} - - (void)prepareShader { if(_shaderProgram) @@ -427,10 +258,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt // [self logErrorForObject:_shaderProgram]; #endif - glGenVertexArrays(1, &_vertexArray); - glBindVertexArray(_vertexArray); - glGenBuffers(1, &_arrayBuffer); - glBindBuffer(GL_ARRAY_BUFFER, _arrayBuffer); glUseProgram(_shaderProgram); @@ -460,12 +287,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt glVertexAttribPointer((GLuint)_textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTexCoord); glVertexAttribPointer((GLuint)_lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfLateral); - glGenTextures(1, &_textureName); - 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); }*/ - (void)drawRect:(NSRect)dirtyRect @@ -478,20 +299,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt [self.openGLContext makeCurrentContext]; CGLLockContext([[self openGLContext] CGLContextObj]); - glClear(GL_COLOR_BUFFER_BIT); [self.delegate openGLViewDrawView:self]; -// while((!_shaderProgram || (_signalDecoderGeneration != _compiledSignalDecoderGeneration)) && _signalDecoder) { -// _compiledSignalDecoderGeneration = _signalDecoderGeneration; -// [self prepareShader]; -// } -// -// glClear(GL_COLOR_BUFFER_BIT); -// -// if (_crtFrame) -// { -// if(_textureSizeUniform >= 0) glUniform2f(_textureSizeUniform, _crtFrame->size.width, _crtFrame->size.height); -// glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_crtFrame->number_of_vertices); -// } CGLFlushDrawable([[self openGLContext] CGLContextObj]); CGLUnlockContext([[self openGLContext] CGLContextObj]); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 09a50c8f4..11957973b 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -33,7 +33,7 @@ } - (void)drawViewForPixelSize:(CGSize)pixelSize { - _electron.get_crt()->draw_frame((int)pixelSize.width, (int)pixelSize.height); + _electron.get_crt()->draw_frame((int)pixelSize.width, (int)pixelSize.height, false); } - (BOOL)openUEFAtURL:(NSURL *)URL { diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 153059d4f..e3a7dcfb2 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -105,10 +105,11 @@ CRT::CRT() : _is_receiving_sync(false), _is_in_hsync(false), _is_in_vsync(false), - _current_frame(nullptr), _current_frame_mutex(new std::mutex), _rasterPosition({.x = 0, .y = 0}) -{} +{ + construct_openGL(); +} CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...) : CRT() { @@ -136,6 +137,7 @@ CRT::~CRT() { delete _frame_builders[frame]; } + destruct_openGL(); } #pragma mark - Sync loop @@ -434,149 +436,3 @@ uint8_t *CRT::get_write_target_for_buffer(int buffer) if (!_current_frame_builder) return nullptr; return _current_frame_builder->get_write_target_for_buffer(buffer); } - - - -char *CRT::get_vertex_shader() -{ - // the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the - // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates - // from integral to floating point; there's also some setup for NTSC, PAL or whatever. - - const char *const ntscVertexShaderGlobals = - "out vec2 srcCoordinatesVarying[4];\n" - "out float phase;\n"; - - const char *const ntscVertexShaderBody = - "phase = srcCoordinates.x * 6.283185308;\n" - "\n" - "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" - "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; - -// const char *const rgbVertexShaderGlobals = -// "out vec2 srcCoordinatesVarying[5];\n"; - -// const char *const rgbVertexShaderBody = -// "srcCoordinatesVarying[2] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" -// "srcCoordinatesVarying[0] = srcCoordinatesVarying[1] - vec2(1.0 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[1] = srcCoordinatesVarying[1] - vec2(0.5 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[3] = srcCoordinatesVarying[1] + vec2(0.5 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[4] = srcCoordinatesVarying[1] + vec2(1.0 / textureSize.x, 0.0);\n"; - - const char *const vertexShader = - "#version 150\n" - "\n" - "in vec2 position;\n" - "in vec2 srcCoordinates;\n" - "in float lateral;\n" - "\n" - "uniform vec2 boundsOrigin;\n" - "uniform vec2 boundsSize;\n" - "\n" - "out float lateralVarying;\n" - "out vec2 shadowMaskCoordinates;\n" - "\n" - "uniform vec2 textureSize;\n" - "\n" - "const float shadowMaskMultiple = 600;\n" - "\n" - "%@\n" - "void main (void)\n" - "{\n" - "lateralVarying = lateral + 1.0707963267949;\n" - "\n" - "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);\n" - "\n" - "%@\n" - "\n" - "vec2 mappedPosition = (position - boundsOrigin) / boundsSize;" - "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);\n" - "}\n"; - - return nullptr; -// + mappedPosition.x / 131.0 - -// switch(_signalType) -// { -// case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:vertexShader, ntscVertexShaderGlobals, ntscVertexShaderBody]; -// case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:vertexShader, rgbVertexShaderGlobals, rgbVertexShaderBody]; -// } -} - -char *CRT::get_fragment_shader(const char *sample_function) -{ - // assumes y = [0, 1], i and q = [-0.5, 0.5]; therefore i components are multiplied by 1.1914 versus standard matrices, q by 1.0452 - const char *const yiqToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);"; - - // assumes y = [0,1], u and v = [-0.5, 0.5]; therefore u components are multiplied by 1.14678899082569, v by 0.8130081300813 - const char *const yuvToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.75213899082569, 2.33040137614679, 0.92669105691057, -0.4720325203252, 0.0);"; - - const char *const fragmentShader = - "#version 150\n" - "\n" - "in float lateralVarying;\n" - "in vec2 shadowMaskCoordinates;\n" - "out vec4 fragColour;\n" - "\n" - "uniform sampler2D texID;\n" - "uniform sampler2D shadowMaskTexID;\n" - "uniform float alpha;\n" - "\n" - "in vec2 srcCoordinatesVarying[4];\n" - "in float phase;\n" - "%@\n" - "%@\n" - "\n" - "void main(void)\n" - "{\n" - "%@\n" - "}\n"; - - const char *const ntscFragmentShaderGlobals = - "in vec2 srcCoordinatesVarying[4];\n" - "in float phase;\n" - "\n" - "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" - "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; - - const char *const ntscFragmentShaderBody = - "vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" - "vec4 samples = vec4(" - " sample(srcCoordinatesVarying[0], angles.x)," - " sample(srcCoordinatesVarying[1], angles.y)," - " sample(srcCoordinatesVarying[2], angles.z)," - " sample(srcCoordinatesVarying[3], angles.w)" - ");\n" - "\n" - "float y = dot(vec4(0.25), samples);\n" - "samples -= vec4(y);\n" - "\n" - "float i = dot(cos(angles), samples);\n" - "float q = dot(sin(angles), samples);\n" - "\n" - "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; - -// const char *const rgbFragmentShaderGlobals = -// "in vec2 srcCoordinatesVarying[5];\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * - -// const char *const rgbFragmentShaderBody = -// "fragColour = sample(srcCoordinatesVarying[2]);"; -// @"fragColour = (sample(srcCoordinatesVarying[0]) * -0.1) + \ -// (sample(srcCoordinatesVarying[1]) * 0.3) + \ -// (sample(srcCoordinatesVarying[2]) * 0.6) + \ -// (sample(srcCoordinatesVarying[3]) * 0.3) + \ -// (sample(srcCoordinatesVarying[4]) * -0.1);"; - -// dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; - - return nullptr; - -// switch(_signalType) -// { -// case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:fragmentShader, ntscFragmentShaderGlobals, ntscFragmentShaderBody]; -// case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:fragmentShader, rgbFragmentShaderGlobals, rgbFragmentShaderBody]; -// } -} diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index be40816f9..3d21807d4 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -140,7 +140,7 @@ class CRT { /*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state. The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call. */ - void draw_frame(int output_width, int output_height); + void draw_frame(int output_width, int output_height, bool only_if_dirty); /*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than the previous. @@ -251,34 +251,6 @@ class CRT { int _next_scan; void output_scan(); - // MARK: shader storage and information. - /*! Gets the vertex shader for display of vended CRTFrames. - - @returns A vertex shader, allocated using a C function. The caller then owns the memory - and is responsible for free'ing it. - */ - char *get_vertex_shader(); - - /*! Gets a fragment shader for display of vended CRTFrames based on the supplied sampling function. - - @param sample_function A GLSL fragment including a function with the signature - `float sample(vec2 coordinate, float phase)` that evaluates to the composite signal level - as a function of a source buffer sampling location and the current colour carrier phase. - - @returns A complete fragment shader. - */ - char *get_fragment_shader(const char *sample_function); - - /*! Gets a fragment shader for composite display of vended CRTFrames based on a default encoding - of the supplied sampling function. - - @param sample_function A GLSL fragent including a function with the signature - `vec3 rgb_sample(vec2 coordinate)` that evaluates to an RGB colour as a function of - the source buffer sampling location. - - @returns A complete fragment shader. - */ - char *get_rgb_encoding_fragment_shader(const char *sample_function); struct CRTFrameBuilder { CRTFrame frame; @@ -310,10 +282,21 @@ class CRT { // the triple buffer and OpenGL state CRTFrameBuilder *_frame_builders[kCRTNumberOfFrames]; CRTFrameBuilder *_current_frame_builder; - CRTFrame *_current_frame; + CRTFrame *_current_frame, *_last_drawn_frame; std::shared_ptr _current_frame_mutex; int _frame_read_pointer; - void *openGLState; + + struct OpenGLState; + OpenGLState *_openGL_state; + + char *_composite_shader; + char *_rgb_shader; + + void construct_openGL(); + void destruct_openGL(); + + char *get_vertex_shader(); + char *get_fragment_shader(); }; } diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 12174fd22..5c815a904 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -1,4 +1,4 @@ -// + // CRTOpenGL.cpp // Clock Signal // @@ -7,33 +7,111 @@ // #include "CRT.hpp" +#include // TODO: figure out correct include paths for other platforms. #include +#include using namespace Outputs; -namespace { - struct OpenGLState { - GLuint _vertexShader, _fragmentShader; - GLuint _shaderProgram; - GLuint _arrayBuffer, _vertexArray; +struct CRT::OpenGLState { + GLuint vertexShader, fragmentShader; + GLuint shaderProgram; + GLuint arrayBuffer, vertexArray; - GLint _positionAttribute; - GLint _textureCoordinatesAttribute; - GLint _lateralAttribute; + GLint positionAttribute; + GLint textureCoordinatesAttribute; + GLint lateralAttribute; - GLint _textureSizeUniform, _windowSizeUniform; - GLint _boundsOriginUniform, _boundsSizeUniform; - GLint _alphaUniform; + GLint textureSizeUniform, windowSizeUniform; + GLint boundsOriginUniform, boundsSizeUniform; + GLint alphaUniform; - GLuint _textureName, _shadowMaskTextureName; - }; + GLuint textureName, shadowMaskTextureName; + + CRTSize textureSize; +}; + +static GLenum formatForDepth(unsigned int depth) +{ + switch(depth) + { + default: return -1; + case 1: return GL_RED; + case 2: return GL_RG; + case 3: return GL_RGB; + case 4: return GL_RGBA; + } } -void CRT::draw_frame(int output_width, int output_height) + +void CRT::construct_openGL() { - printf("%d %d\n", output_width, output_height); + _openGL_state = nullptr; + _current_frame = _last_drawn_frame = nullptr; + _composite_shader = _rgb_shader = nullptr; +} + +void CRT::destruct_openGL() +{ + delete (OpenGLState *)_openGL_state; + if(_composite_shader) free(_composite_shader); + if(_rgb_shader) free(_rgb_shader); +} + +void CRT::draw_frame(int output_width, int output_height, bool only_if_dirty) +{ + _current_frame_mutex->lock(); + + if(!_current_frame) + { + glClear(GL_COLOR_BUFFER_BIT); + } + else + { + if(_current_frame != _last_drawn_frame) + { + if(!_openGL_state) + { + _openGL_state = new OpenGLState; + + glGenTextures(1, &_openGL_state->textureName); + glBindTexture(GL_TEXTURE_2D, _openGL_state->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); + + glGenVertexArrays(1, &_openGL_state->vertexArray); + glBindVertexArray(_openGL_state->vertexArray); + glGenBuffers(1, &_openGL_state->arrayBuffer); + glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); + } + + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW); + + glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); + if(_openGL_state->textureSize.width != _current_frame->size.width || _openGL_state->textureSize.height != _current_frame->size.height) + { + GLenum format = formatForDepth(_current_frame->buffers[0].depth); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); + _openGL_state->textureSize = _current_frame->size; + + if(_openGL_state->textureSizeUniform >= 0) glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height); + } + else + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->dirty_size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); + } + } + + if(_current_frame != _last_drawn_frame || only_if_dirty) + { + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_current_frame->number_of_vertices); + } + + _current_frame_mutex->unlock(); } void CRT::set_openGL_context_will_change(bool should_delete_resources) @@ -42,9 +120,154 @@ void CRT::set_openGL_context_will_change(bool should_delete_resources) void CRT::set_composite_sampling_function(const char *shader) { + _composite_shader = strdup(shader); } void CRT::set_rgb_sampling_function(const char *shader) { - printf("%s\n", shader); + _rgb_shader = strdup(shader); +} + +char *CRT::get_vertex_shader() +{ + // the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the + // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates + // from integral to floating point; there's also some setup for NTSC, PAL or whatever. + + const char *const ntscVertexShaderGlobals = + "out vec2 srcCoordinatesVarying[4];\n" + "out float phase;\n"; + + const char *const ntscVertexShaderBody = + "phase = srcCoordinates.x * 6.283185308;\n" + "\n" + "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" + "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" + "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; + +// const char *const rgbVertexShaderGlobals = +// "out vec2 srcCoordinatesVarying[5];\n"; + +// const char *const rgbVertexShaderBody = +// "srcCoordinatesVarying[2] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" +// "srcCoordinatesVarying[0] = srcCoordinatesVarying[1] - vec2(1.0 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[1] = srcCoordinatesVarying[1] - vec2(0.5 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[3] = srcCoordinatesVarying[1] + vec2(0.5 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[4] = srcCoordinatesVarying[1] + vec2(1.0 / textureSize.x, 0.0);\n"; + + const char *const vertexShader = + "#version 150\n" + "\n" + "in vec2 position;\n" + "in vec2 srcCoordinates;\n" + "in float lateral;\n" + "\n" + "uniform vec2 boundsOrigin;\n" + "uniform vec2 boundsSize;\n" + "\n" + "out float lateralVarying;\n" + "out vec2 shadowMaskCoordinates;\n" + "\n" + "uniform vec2 textureSize;\n" + "\n" + "const float shadowMaskMultiple = 600;\n" + "\n" + "%@\n" + "void main (void)\n" + "{\n" + "lateralVarying = lateral + 1.0707963267949;\n" + "\n" + "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);\n" + "\n" + "%@\n" + "\n" + "vec2 mappedPosition = (position - boundsOrigin) / boundsSize;" + "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);\n" + "}\n"; + + return nullptr; +// + mappedPosition.x / 131.0 + +// switch(_signalType) +// { +// case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:vertexShader, ntscVertexShaderGlobals, ntscVertexShaderBody]; +// case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:vertexShader, rgbVertexShaderGlobals, rgbVertexShaderBody]; +// } +} + +char *CRT::get_fragment_shader() +{ + // assumes y = [0, 1], i and q = [-0.5, 0.5]; therefore i components are multiplied by 1.1914 versus standard matrices, q by 1.0452 + const char *const yiqToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);"; + + // assumes y = [0,1], u and v = [-0.5, 0.5]; therefore u components are multiplied by 1.14678899082569, v by 0.8130081300813 + const char *const yuvToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.75213899082569, 2.33040137614679, 0.92669105691057, -0.4720325203252, 0.0);"; + + const char *const fragmentShader = + "#version 150\n" + "\n" + "in float lateralVarying;\n" + "in vec2 shadowMaskCoordinates;\n" + "out vec4 fragColour;\n" + "\n" + "uniform sampler2D texID;\n" + "uniform sampler2D shadowMaskTexID;\n" + "uniform float alpha;\n" + "\n" + "in vec2 srcCoordinatesVarying[4];\n" + "in float phase;\n" + "%@\n" + "%@\n" + "\n" + "void main(void)\n" + "{\n" + "%@\n" + "}\n"; + + const char *const ntscFragmentShaderGlobals = + "in vec2 srcCoordinatesVarying[4];\n" + "in float phase;\n" + "\n" + "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" + "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; + + const char *const ntscFragmentShaderBody = + "vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" + "vec4 samples = vec4(" + " sample(srcCoordinatesVarying[0], angles.x)," + " sample(srcCoordinatesVarying[1], angles.y)," + " sample(srcCoordinatesVarying[2], angles.z)," + " sample(srcCoordinatesVarying[3], angles.w)" + ");\n" + "\n" + "float y = dot(vec4(0.25), samples);\n" + "samples -= vec4(y);\n" + "\n" + "float i = dot(cos(angles), samples);\n" + "float q = dot(sin(angles), samples);\n" + "\n" + "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; + +// const char *const rgbFragmentShaderGlobals = +// "in vec2 srcCoordinatesVarying[5];\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * + +// const char *const rgbFragmentShaderBody = +// "fragColour = sample(srcCoordinatesVarying[2]);"; +// @"fragColour = (sample(srcCoordinatesVarying[0]) * -0.1) + \ +// (sample(srcCoordinatesVarying[1]) * 0.3) + \ +// (sample(srcCoordinatesVarying[2]) * 0.6) + \ +// (sample(srcCoordinatesVarying[3]) * 0.3) + \ +// (sample(srcCoordinatesVarying[4]) * -0.1);"; + +// dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; + + return nullptr; + +// switch(_signalType) +// { +// case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:fragmentShader, ntscFragmentShaderGlobals, ntscFragmentShaderBody]; +// case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:fragmentShader, rgbFragmentShaderGlobals, rgbFragmentShaderBody]; +// } } From 80e7e5e6022ce586295a0e58d64411e7aa015e42 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 4 Feb 2016 23:05:47 -0500 Subject: [PATCH 083/307] Updated to return old behaviour of drawing only upon changes. --- .../Documents/ElectronDocument.swift | 4 +- .../Documents/MachineDocument.swift | 2 +- .../Mac/Clock Signal/Views/CSCathodeRayView.h | 2 +- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 7 +- .../Mac/Clock Signal/Wrappers/CSElectron.h | 2 +- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 4 +- Outputs/CRT/CRTOpenGL.cpp | 64 +++++++++---------- 7 files changed, 40 insertions(+), 45 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 43cb38475..4568d6982 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -61,8 +61,8 @@ class ElectronDocument: MachineDocument { electron.runForNumberOfCycles(numberOfCycles) } - override func openGLViewDrawView(view: CSCathodeRayView) { - electron.drawViewForPixelSize(view.backingSize) + override func openGLView(view: CSCathodeRayView, drawViewOnlyIfDirty onlyIfDirty: Bool) { + electron.drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty) } // MARK: CSOpenGLViewResponderDelegate diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 126eb2a9d..e1f14f833 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -46,7 +46,7 @@ class MachineDocument: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewRes lastCycleCount = cycleCount } - func openGLViewDrawView(view: CSCathodeRayView) {} + func openGLView(view: CSCathodeRayView, drawViewOnlyIfDirty onlyIfDirty: Bool) {} func runForNumberOfCycles(numberOfCycles: Int32) {} // MARK: CSOpenGLViewResponderDelegate diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h index abecc655b..a463953d5 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h @@ -13,7 +13,7 @@ @protocol CSCathodeRayViewDelegate - (void)openGLView:(nonnull CSCathodeRayView *)view didUpdateToTime:(CVTimeStamp)time; -- (void)openGLViewDrawView:(nonnull CSCathodeRayView *)view; +- (void)openGLView:(nonnull CSCathodeRayView *)view drawViewOnlyIfDirty:(BOOL)onlyIfDirty; @end @protocol CSCathodeRayViewResponderDelegate diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 069f10dd0..1dc801978 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -76,6 +76,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt { CSCathodeRayView *view = (__bridge CSCathodeRayView *)displayLinkContext; [view.delegate openGLView:view didUpdateToTime:*now]; + [view drawViewOnlyIfDirty:YES]; return kCVReturnSuccess; } @@ -291,15 +292,15 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawRect:(NSRect)dirtyRect { - [self drawView]; + [self drawViewOnlyIfDirty:NO]; } -- (void)drawView +- (void)drawViewOnlyIfDirty:(BOOL)onlyIfDirty { [self.openGLContext makeCurrentContext]; CGLLockContext([[self openGLContext] CGLContextObj]); - [self.delegate openGLViewDrawView:self]; + [self.delegate openGLView:self drawViewOnlyIfDirty:onlyIfDirty]; CGLFlushDrawable([[self openGLContext] CGLContextObj]); CGLUnlockContext([[self openGLContext] CGLContextObj]); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index a58cf49cc..7902fee52 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -18,6 +18,6 @@ - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; -- (void)drawViewForPixelSize:(CGSize)pixelSize; +- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 11957973b..a48cde887 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -32,8 +32,8 @@ _electron.set_rom((Electron::ROMSlot)slot, rom.length, (const uint8_t *)rom.bytes); } -- (void)drawViewForPixelSize:(CGSize)pixelSize { - _electron.get_crt()->draw_frame((int)pixelSize.width, (int)pixelSize.height, false); +- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { + _electron.get_crt()->draw_frame((int)pixelSize.width, (int)pixelSize.height, onlyIfDirty ? true : false); } - (BOOL)openUEFAtURL:(NSURL *)URL { diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 5c815a904..83ed3a0b9 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -37,7 +37,7 @@ static GLenum formatForDepth(unsigned int depth) { switch(depth) { - default: return -1; + default: return GL_FALSE; case 1: return GL_RED; case 2: return GL_RG; case 3: return GL_RGB; @@ -64,50 +64,44 @@ void CRT::draw_frame(int output_width, int output_height, bool only_if_dirty) { _current_frame_mutex->lock(); - if(!_current_frame) + if(!_current_frame || !only_if_dirty) { glClear(GL_COLOR_BUFFER_BIT); } - else + + if(_current_frame && _current_frame != _last_drawn_frame) { - if(_current_frame != _last_drawn_frame) + if(!_openGL_state) { - if(!_openGL_state) - { - _openGL_state = new OpenGLState; - - glGenTextures(1, &_openGL_state->textureName); - glBindTexture(GL_TEXTURE_2D, _openGL_state->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); - - glGenVertexArrays(1, &_openGL_state->vertexArray); - glBindVertexArray(_openGL_state->vertexArray); - glGenBuffers(1, &_openGL_state->arrayBuffer); - glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); - } - - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW); + _openGL_state = new OpenGLState; + glGenTextures(1, &_openGL_state->textureName); glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); - if(_openGL_state->textureSize.width != _current_frame->size.width || _openGL_state->textureSize.height != _current_frame->size.height) - { - GLenum format = formatForDepth(_current_frame->buffers[0].depth); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); - _openGL_state->textureSize = _current_frame->size; + 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); - if(_openGL_state->textureSizeUniform >= 0) glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height); - } - else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->dirty_size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); + glGenVertexArrays(1, &_openGL_state->vertexArray); + glBindVertexArray(_openGL_state->vertexArray); + glGenBuffers(1, &_openGL_state->arrayBuffer); + glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); } - } - if(_current_frame != _last_drawn_frame || only_if_dirty) - { - glClear(GL_COLOR_BUFFER_BIT); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW); + + glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); + if(_openGL_state->textureSize.width != _current_frame->size.width || _openGL_state->textureSize.height != _current_frame->size.height) + { + GLenum format = formatForDepth(_current_frame->buffers[0].depth); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); + _openGL_state->textureSize = _current_frame->size; + + if(_openGL_state->textureSizeUniform >= 0) glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height); + } + else + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->dirty_size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_current_frame->number_of_vertices); } From 6ab425deda7d3fa67697f578fa57c317c14de405 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 5 Feb 2016 21:35:39 -0500 Subject: [PATCH 084/307] There is now a semi-reasonable amount of screen output again. --- Machines/Electron/Electron.cpp | 8 +- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 104 ------ Outputs/CRT/CRT.hpp | 3 + Outputs/CRT/CRTOpenGL.cpp | 308 +++++++++++------- 4 files changed, 195 insertions(+), 228 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 825fe437f..ba8a62f35 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -30,10 +30,10 @@ Machine::Machine() : _crt(Outputs::CRT(crt_cycles_per_line, Outputs::CRT::DisplayType::PAL50, 1, 1)) { _crt.set_rgb_sampling_function( - "vec4 sample(vec2 coordinate)\n" - "{\n" - "float texValue = texture(texID, coordinate).r;\n" - "return vec4( step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)), 1.0);\n" + "vec3 rgb_sample(vec2 coordinate)" + "{" + "float texValue = texture(texID, coordinate).r;" + "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); memset(_keyStates, 0, sizeof(_keyStates)); diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 1dc801978..c0b721534 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -186,110 +186,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt // self.frameBounds = CGRectMake(0.0, 0.0, 1.0, 1.0); } -/* - -#pragma mark - Frame output - -#if defined(DEBUG) -- (void)logErrorForObject:(GLuint)object -{ - GLint isCompiled = 0; - glGetShaderiv(object, GL_COMPILE_STATUS, &isCompiled); - if(isCompiled == GL_FALSE) - { - GLint logLength; - glGetShaderiv(object, GL_INFO_LOG_LENGTH, &logLength); - if (logLength > 0) { - GLchar *log = (GLchar *)malloc((size_t)logLength); - glGetShaderInfoLog(object, logLength, &logLength, log); - NSLog(@"Compile log:\n%s", log); - free(log); - } - } -} -#endif - -- (GLuint)compileShader:(const char *)source type:(GLenum)type -{ - GLuint shader = glCreateShader(type); - glShaderSource(shader, 1, &source, NULL); - glCompileShader(shader); - -#ifdef DEBUG - [self logErrorForObject:shader]; -#endif - - return shader; -} - -- (void)setSignalDecoder:(nonnull NSString *)signalDecoder type:(CSCathodeRayViewSignalType)type -{ - _signalType = type; - _signalDecoder = [signalDecoder copy]; - OSAtomicIncrement32(&_signalDecoderGeneration); -} - -- (void)prepareShader -{ - if(_shaderProgram) - { - glDeleteProgram(_shaderProgram); - glDeleteShader(_vertexShader); - glDeleteShader(_fragmentShader); - } - - if(!_signalDecoder) - return; - - NSString *const vertexShader = [self vertexShaderForType:_signalType]; - NSString *const fragmentShader = [self fragmentShaderForType:_signalType]; - - _shaderProgram = glCreateProgram(); - _vertexShader = [self compileShader:[vertexShader UTF8String] type:GL_VERTEX_SHADER]; - _fragmentShader = [self compileShader:_signalDecoder ? - [[NSString stringWithFormat:fragmentShader, _signalDecoder] UTF8String] : - [fragmentShader UTF8String] - type:GL_FRAGMENT_SHADER]; - - glAttachShader(_shaderProgram, _vertexShader); - glAttachShader(_shaderProgram, _fragmentShader); - glLinkProgram(_shaderProgram); - -#ifdef DEBUG -// [self logErrorForObject:_shaderProgram]; -#endif - - - glUseProgram(_shaderProgram); - - _positionAttribute = glGetAttribLocation(_shaderProgram, "position"); - _textureCoordinatesAttribute = glGetAttribLocation(_shaderProgram, "srcCoordinates"); - _lateralAttribute = glGetAttribLocation(_shaderProgram, "lateral"); - _alphaUniform = glGetUniformLocation(_shaderProgram, "alpha"); - _textureSizeUniform = glGetUniformLocation(_shaderProgram, "textureSize"); - _windowSizeUniform = glGetUniformLocation(_shaderProgram, "windowSize"); - _boundsSizeUniform = glGetUniformLocation(_shaderProgram, "boundsSize"); - _boundsOriginUniform = glGetUniformLocation(_shaderProgram, "boundsOrigin"); - - GLint texIDUniform = glGetUniformLocation(_shaderProgram, "texID"); - GLint shadowMaskTexIDUniform = glGetUniformLocation(_shaderProgram, "shadowMaskTexID"); - - [self pushSizeUniforms]; - - glUniform1i(texIDUniform, 0); - glUniform1i(shadowMaskTexIDUniform, 1); - - glEnableVertexAttribArray((GLuint)_positionAttribute); - glEnableVertexAttribArray((GLuint)_textureCoordinatesAttribute); - glEnableVertexAttribArray((GLuint)_lateralAttribute); - - const GLsizei vertexStride = kCRTSizeOfVertex; - glVertexAttribPointer((GLuint)_positionAttribute, 2, GL_UNSIGNED_SHORT, GL_TRUE, vertexStride, (void *)kCRTVertexOffsetOfPosition); - glVertexAttribPointer((GLuint)_textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTexCoord); - glVertexAttribPointer((GLuint)_lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfLateral); - -}*/ - - (void)drawRect:(NSRect)dirtyRect { [self drawViewOnlyIfDirty:NO]; diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 3d21807d4..c5233aee6 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -294,9 +294,12 @@ class CRT { void construct_openGL(); void destruct_openGL(); + void prepare_shader(); + void push_size_uniforms(unsigned int output_width, unsigned int output_height); char *get_vertex_shader(); char *get_fragment_shader(); + char *get_compound_shader(const char *base, const char *insert); }; } diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 83ed3a0b9..cdfb0e8f6 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -31,6 +31,32 @@ struct CRT::OpenGLState { GLuint textureName, shadowMaskTextureName; CRTSize textureSize; + + GLuint compile_shader(const char *source, GLenum type) + { + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + +#if defined(DEBUG) + GLint isCompiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled); + if(isCompiled == GL_FALSE) + { + GLint logLength; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc((size_t)logLength); + glGetShaderInfoLog(shader, logLength, &logLength, log); + printf("Compile log:\n%s\n", log); + free(log); + } + } +#endif + + return shader; + } + }; static GLenum formatForDepth(unsigned int depth) @@ -86,8 +112,12 @@ void CRT::draw_frame(int output_width, int output_height, bool only_if_dirty) glBindVertexArray(_openGL_state->vertexArray); glGenBuffers(1, &_openGL_state->arrayBuffer); glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); + + prepare_shader(); } + push_size_uniforms(output_width, output_height); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW); glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); @@ -112,6 +142,28 @@ void CRT::set_openGL_context_will_change(bool should_delete_resources) { } +void CRT::push_size_uniforms(unsigned int output_width, unsigned int output_height) +{ + if(_openGL_state->windowSizeUniform >= 0) + { + glUniform2f(_openGL_state->windowSizeUniform, output_width, output_height); + } + +// GLfloat outputAspectRatioMultiplier = 1.0;//(viewSize.x / viewSize.y) / (4.0 / 3.0); + +// _aspectRatioCorrectedBounds = _frameBounds; + +// CGFloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * _frameBounds.size.width; +// _aspectRatioCorrectedBounds.origin.x -= bonusWidth * 0.5f * _aspectRatioCorrectedBounds.size.width; +// _aspectRatioCorrectedBounds.size.width *= outputAspectRatioMultiplier; + + if(_openGL_state->boundsOriginUniform >= 0) + glUniform2f(_openGL_state->boundsOriginUniform, 0.0, 0.0); //(GLfloat)_aspectRatioCorrectedBounds.origin.x, (GLfloat)_aspectRatioCorrectedBounds.origin.y); + + if(_openGL_state->boundsSizeUniform >= 0) + glUniform2f(_openGL_state->boundsSizeUniform, 1.0, 1.0);//(GLfloat)_aspectRatioCorrectedBounds.size.width, (GLfloat)_aspectRatioCorrectedBounds.size.height); +} + void CRT::set_composite_sampling_function(const char *shader) { _composite_shader = strdup(shader); @@ -128,140 +180,156 @@ char *CRT::get_vertex_shader() // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates // from integral to floating point; there's also some setup for NTSC, PAL or whatever. - const char *const ntscVertexShaderGlobals = - "out vec2 srcCoordinatesVarying[4];\n" - "out float phase;\n"; +// const char *const ntscVertexShaderGlobals = +// "out vec2 srcCoordinatesVarying[4];\n" +// "out float phase;\n"; +// +// const char *const ntscVertexShaderBody = +// "phase = srcCoordinates.x * 6.283185308;\n" +// "\n" +// "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" +// "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; - const char *const ntscVertexShaderBody = - "phase = srcCoordinates.x * 6.283185308;\n" - "\n" - "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" - "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" - "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; - -// const char *const rgbVertexShaderGlobals = -// "out vec2 srcCoordinatesVarying[5];\n"; - -// const char *const rgbVertexShaderBody = -// "srcCoordinatesVarying[2] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" -// "srcCoordinatesVarying[0] = srcCoordinatesVarying[1] - vec2(1.0 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[1] = srcCoordinatesVarying[1] - vec2(0.5 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[3] = srcCoordinatesVarying[1] + vec2(0.5 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[4] = srcCoordinatesVarying[1] + vec2(1.0 / textureSize.x, 0.0);\n"; - - const char *const vertexShader = + return strdup( "#version 150\n" - "\n" - "in vec2 position;\n" - "in vec2 srcCoordinates;\n" - "in float lateral;\n" - "\n" - "uniform vec2 boundsOrigin;\n" - "uniform vec2 boundsSize;\n" - "\n" - "out float lateralVarying;\n" - "out vec2 shadowMaskCoordinates;\n" - "\n" - "uniform vec2 textureSize;\n" - "\n" - "const float shadowMaskMultiple = 600;\n" - "\n" - "%@\n" - "void main (void)\n" - "{\n" - "lateralVarying = lateral + 1.0707963267949;\n" - "\n" - "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);\n" - "\n" - "%@\n" - "\n" + + "in vec2 position;" + "in vec2 srcCoordinates;" + "in float lateral;" + + "uniform vec2 boundsOrigin;" + "uniform vec2 boundsSize;" + + "out float lateralVarying;" + "out vec2 shadowMaskCoordinates;" + + "uniform vec2 textureSize;" + + "const float shadowMaskMultiple = 600;" + + "out vec2 srcCoordinatesVarying;" + + "void main(void)" + "{" + "lateralVarying = lateral + 1.0707963267949;" + + "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);" + + "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" + "vec2 mappedPosition = (position - boundsOrigin) / boundsSize;" - "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);\n" - "}\n"; - - return nullptr; -// + mappedPosition.x / 131.0 - -// switch(_signalType) -// { -// case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:vertexShader, ntscVertexShaderGlobals, ntscVertexShaderBody]; -// case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:vertexShader, rgbVertexShaderGlobals, rgbVertexShaderBody]; -// } + "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);" + "}"); } char *CRT::get_fragment_shader() { // assumes y = [0, 1], i and q = [-0.5, 0.5]; therefore i components are multiplied by 1.1914 versus standard matrices, q by 1.0452 - const char *const yiqToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);"; +// const char *const yiqToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);"; // assumes y = [0,1], u and v = [-0.5, 0.5]; therefore u components are multiplied by 1.14678899082569, v by 0.8130081300813 - const char *const yuvToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.75213899082569, 2.33040137614679, 0.92669105691057, -0.4720325203252, 0.0);"; +// const char *const yuvToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.75213899082569, 2.33040137614679, 0.92669105691057, -0.4720325203252, 0.0);"; - const char *const fragmentShader = - "#version 150\n" - "\n" - "in float lateralVarying;\n" - "in vec2 shadowMaskCoordinates;\n" - "out vec4 fragColour;\n" - "\n" - "uniform sampler2D texID;\n" - "uniform sampler2D shadowMaskTexID;\n" - "uniform float alpha;\n" - "\n" - "in vec2 srcCoordinatesVarying[4];\n" - "in float phase;\n" - "%@\n" - "%@\n" - "\n" - "void main(void)\n" - "{\n" - "%@\n" - "}\n"; +// const char *const ntscFragmentShaderGlobals = +// "in vec2 srcCoordinatesVarying[4];\n" +// "in float phase;\n" +// "\n" +// "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" +// "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; - const char *const ntscFragmentShaderGlobals = - "in vec2 srcCoordinatesVarying[4];\n" - "in float phase;\n" - "\n" - "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" - "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; - - const char *const ntscFragmentShaderBody = - "vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" - "vec4 samples = vec4(" - " sample(srcCoordinatesVarying[0], angles.x)," - " sample(srcCoordinatesVarying[1], angles.y)," - " sample(srcCoordinatesVarying[2], angles.z)," - " sample(srcCoordinatesVarying[3], angles.w)" - ");\n" - "\n" - "float y = dot(vec4(0.25), samples);\n" - "samples -= vec4(y);\n" - "\n" - "float i = dot(cos(angles), samples);\n" - "float q = dot(sin(angles), samples);\n" - "\n" - "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; - -// const char *const rgbFragmentShaderGlobals = -// "in vec2 srcCoordinatesVarying[5];\n"; // texture(shadowMaskTexID, shadowMaskCoordinates) * - -// const char *const rgbFragmentShaderBody = -// "fragColour = sample(srcCoordinatesVarying[2]);"; -// @"fragColour = (sample(srcCoordinatesVarying[0]) * -0.1) + \ -// (sample(srcCoordinatesVarying[1]) * 0.3) + \ -// (sample(srcCoordinatesVarying[2]) * 0.6) + \ -// (sample(srcCoordinatesVarying[3]) * 0.3) + \ -// (sample(srcCoordinatesVarying[4]) * -0.1);"; +// const char *const ntscFragmentShaderBody = +// "vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" +// "vec4 samples = vec4(" +// " sample(srcCoordinatesVarying[0], angles.x)," +// " sample(srcCoordinatesVarying[1], angles.y)," +// " sample(srcCoordinatesVarying[2], angles.z)," +// " sample(srcCoordinatesVarying[3], angles.w)" +// ");\n" +// "\n" +// "float y = dot(vec4(0.25), samples);\n" +// "samples -= vec4(y);\n" +// "\n" +// "float i = dot(cos(angles), samples);\n" +// "float q = dot(sin(angles), samples);\n" +// "\n" +// "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; // dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; - return nullptr; + return get_compound_shader( + "#version 150\n" -// switch(_signalType) -// { -// case CSCathodeRayViewSignalTypeNTSC: return [NSString stringWithFormat:fragmentShader, ntscFragmentShaderGlobals, ntscFragmentShaderBody]; -// case CSCathodeRayViewSignalTypeRGB: return [NSString stringWithFormat:fragmentShader, rgbFragmentShaderGlobals, rgbFragmentShaderBody]; -// } + "in float lateralVarying;" + "in vec2 shadowMaskCoordinates;" + "out vec4 fragColour;" + + "uniform sampler2D texID;" + "uniform sampler2D shadowMaskTexID;" + "uniform float alpha;" + + "in vec2 srcCoordinatesVarying;" + "in float phase;\n" + "%s\n" + + "void main(void)" + "{" + "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, 1.0);" + "}" + , _rgb_shader); +} + +char *CRT::get_compound_shader(const char *base, const char *insert) +{ + size_t totalLength = strlen(base) + strlen(insert) + 1; + char *text = new char[totalLength]; + snprintf(text, totalLength, base, insert); + return text; +} + +void CRT::prepare_shader() +{ + char *vertex_shader = get_vertex_shader(); + char *fragment_shader = get_fragment_shader(); + + _openGL_state->shaderProgram = glCreateProgram(); + _openGL_state->vertexShader = _openGL_state->compile_shader(vertex_shader, GL_VERTEX_SHADER); + _openGL_state->fragmentShader = _openGL_state->compile_shader(fragment_shader, GL_FRAGMENT_SHADER); + + delete vertex_shader; + delete fragment_shader; + + glAttachShader(_openGL_state->shaderProgram, _openGL_state->vertexShader); + glAttachShader(_openGL_state->shaderProgram, _openGL_state->fragmentShader); + glLinkProgram(_openGL_state->shaderProgram); + + glUseProgram(_openGL_state->shaderProgram); + + _openGL_state->positionAttribute = glGetAttribLocation(_openGL_state->shaderProgram, "position"); + _openGL_state->textureCoordinatesAttribute = glGetAttribLocation(_openGL_state->shaderProgram, "srcCoordinates"); + _openGL_state->lateralAttribute = glGetAttribLocation(_openGL_state->shaderProgram, "lateral"); + _openGL_state->alphaUniform = glGetUniformLocation(_openGL_state->shaderProgram, "alpha"); + _openGL_state->textureSizeUniform = glGetUniformLocation(_openGL_state->shaderProgram, "textureSize"); + _openGL_state->windowSizeUniform = glGetUniformLocation(_openGL_state->shaderProgram, "windowSize"); + _openGL_state->boundsSizeUniform = glGetUniformLocation(_openGL_state->shaderProgram, "boundsSize"); + _openGL_state->boundsOriginUniform = glGetUniformLocation(_openGL_state->shaderProgram, "boundsOrigin"); + + GLint texIDUniform = glGetUniformLocation(_openGL_state->shaderProgram, "texID"); + GLint shadowMaskTexIDUniform = glGetUniformLocation(_openGL_state->shaderProgram, "shadowMaskTexID"); + +// [self pushSizeUniforms]; + + glUniform1i(texIDUniform, 0); + glUniform1i(shadowMaskTexIDUniform, 1); + + glEnableVertexAttribArray((GLuint)_openGL_state->positionAttribute); + glEnableVertexAttribArray((GLuint)_openGL_state->textureCoordinatesAttribute); + glEnableVertexAttribArray((GLuint)_openGL_state->lateralAttribute); + + const GLsizei vertexStride = kCRTSizeOfVertex; + glVertexAttribPointer((GLuint)_openGL_state->positionAttribute, 2, GL_UNSIGNED_SHORT, GL_TRUE, vertexStride, (void *)kCRTVertexOffsetOfPosition); + glVertexAttribPointer((GLuint)_openGL_state->textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTexCoord); + glVertexAttribPointer((GLuint)_openGL_state->lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfLateral); } From bf348ccb96e526ad98fbafbcf7948c1a755b6833 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 5 Feb 2016 22:47:12 -0500 Subject: [PATCH 085/307] Fixed clearing logic. --- Outputs/CRT/CRTOpenGL.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index cdfb0e8f6..6fbabba30 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -90,13 +90,15 @@ void CRT::draw_frame(int output_width, int output_height, bool only_if_dirty) { _current_frame_mutex->lock(); - if(!_current_frame || !only_if_dirty) + if(!_current_frame && !only_if_dirty) { glClear(GL_COLOR_BUFFER_BIT); } - if(_current_frame && _current_frame != _last_drawn_frame) + if(_current_frame && (_current_frame != _last_drawn_frame || !only_if_dirty)) { + glClear(GL_COLOR_BUFFER_BIT); + if(!_openGL_state) { _openGL_state = new OpenGLState; @@ -127,10 +129,11 @@ void CRT::draw_frame(int output_width, int output_height, bool only_if_dirty) glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); _openGL_state->textureSize = _current_frame->size; - if(_openGL_state->textureSizeUniform >= 0) glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height); + if(_openGL_state->textureSizeUniform >= 0) + glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height); } else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->dirty_size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_current_frame->number_of_vertices); } From d3d505b7bc82d95682ec2ec3d7cf7bd421da6596 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 5 Feb 2016 23:05:58 -0500 Subject: [PATCH 086/307] That's one header file less. --- OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index c0b721534..7c223f4de 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -9,7 +9,7 @@ #import "CSCathodeRayView.h" @import CoreVideo; @import GLKit; -#import +//#import //#import //#import From 2cb12a75139484342b0166e16c6c31ffe0b2ac40 Mon Sep 17 00:00:00 2001 From: thomasharte Date: Sat, 6 Feb 2016 16:07:23 -0500 Subject: [PATCH 087/307] Allowed overly aggressive allocations to be cleared up after the fact. --- Outputs/CRT/CRT.cpp | 2 ++ Outputs/CRT/CRT.hpp | 26 +++++++++++++------------- Outputs/CRT/CRTFrameBuilder.cpp | 8 ++++++++ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index e3a7dcfb2..dde71349e 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -416,6 +416,8 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { + if(_current_frame_builder) _current_frame_builder->reduce_previous_allocation_to(number_of_cycles / source_divider); + _scans[_next_scan].type = Type::Data; _scans[_next_scan].number_of_cycles = number_of_cycles; _scans[_next_scan].tex_x = _current_frame_builder ? _current_frame_builder->_write_x_position : 0; diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index c5233aee6..58d818b9e 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -258,23 +258,23 @@ class CRT { CRTFrameBuilder(uint16_t width, uint16_t height, unsigned int number_of_buffers, va_list buffer_sizes); ~CRTFrameBuilder(); - private: - std::vector _all_runs; + std::vector _all_runs; - void reset(); - void complete(); + void reset(); + void complete(); - uint8_t *get_next_run(); - friend CRT; + uint8_t *get_next_run(); - void allocate_write_area(int required_length); - uint8_t *get_write_target_for_buffer(int buffer); + void allocate_write_area(int required_length); + void reduce_previous_allocation_to(int actual_length); + uint8_t *get_write_target_for_buffer(int buffer); - // a pointer to the section of content buffer currently being - // returned and to where the next section will begin - uint16_t _next_write_x_position, _next_write_y_position; - uint16_t _write_x_position, _write_y_position; - size_t _write_target_pointer; + // a pointer to the section of content buffer currently being + // returned and to where the next section will begin + uint16_t _next_write_x_position, _next_write_y_position; + uint16_t _write_x_position, _write_y_position; + size_t _write_target_pointer; + int _last_allocation_amount; }; static const int kCRTNumberOfFrames = 4; diff --git a/Outputs/CRT/CRTFrameBuilder.cpp b/Outputs/CRT/CRTFrameBuilder.cpp index 5a7752077..a229500ae 100644 --- a/Outputs/CRT/CRTFrameBuilder.cpp +++ b/Outputs/CRT/CRTFrameBuilder.cpp @@ -66,6 +66,8 @@ uint8_t *CRT::CRTFrameBuilder::get_next_run() void CRT::CRTFrameBuilder::allocate_write_area(int required_length) { + _last_allocation_amount = required_length; + if (_next_write_x_position + required_length > frame.size.width) { _next_write_x_position = 0; @@ -80,6 +82,12 @@ void CRT::CRTFrameBuilder::allocate_write_area(int required_length) frame.dirty_size.width = std::max(frame.dirty_size.width, _next_write_x_position); } +void CRT::CRTFrameBuilder::reduce_previous_allocation_to(int actual_length) +{ + _next_write_x_position -= (_last_allocation_amount - actual_length); +} + + uint8_t *CRT::CRTFrameBuilder::get_write_target_for_buffer(int buffer) { return &frame.buffers[buffer].data[_write_target_pointer * frame.buffers[buffer].depth]; From 2e3ba6bbb2026e6155f7b788850865baa7a81f8a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Feb 2016 14:42:40 -0500 Subject: [PATCH 088/307] Removed some logging. --- Machines/Electron/Electron.cpp | 16 ++-------------- Storage/Tape/Formats/TapeUEF.cpp | 4 +--- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ba8a62f35..af47f4d06 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -144,6 +144,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if( interruptDisable&0x20 ) _interruptStatus &= ~Interrupt::RealTimeClock; if( interruptDisable&0x40 ) _interruptStatus &= ~Interrupt::HighToneDetect; evaluate_interrupts(); + // TODO: NMI (?) } // else @@ -668,19 +669,12 @@ inline void Tape::reset_tape_input() // } } -extern uint8_t dr; - inline void Tape::evaluate_interrupts() { if(!_bits_since_start) { if((_data_register&0x3) == 0x1) { - if(dr != ((_data_register >> 2)&0xff)) - { - printf("Mismatch\n"); - } - _interrupt_status |= Interrupt::ReceiveDataFull; if(_is_in_input_mode) _bits_since_start = 9; } @@ -725,8 +719,7 @@ inline void Tape::set_data_register(uint8_t value) inline uint8_t Tape::get_data_register() { - int shift = std::max(_bits_since_start - 7, 0); - return (uint8_t)(_data_register >> shift); + return (uint8_t)(_data_register >> 2); } inline void Tape::run_for_cycles(unsigned int number_of_cycles) @@ -759,11 +752,6 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) float pulse_length = (float)_current_pulse.length.length / (float)_current_pulse.length.clock_rate; if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) _crossings[3] = Tape::Short; if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) _crossings[3] = Tape::Long; - - if(_crossings[3] == Tape::Unrecognised) - { - printf("Wah wah?\n"); - } } if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long) diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index b9345f8f2..ba6d4b6de 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -9,8 +9,6 @@ #include "TapeUEF.hpp" #include -uint8_t dr; - Storage::UEF::UEF(const char *file_name) : _chunk_id(0), _chunk_length(0), _chunk_position(0), _time_base(1200) @@ -180,7 +178,7 @@ bool Storage::UEF::get_next_bit() _chunk_position++; if(!bit_position) { - dr = _current_byte = (uint8_t)gzgetc(_file); + _current_byte = (uint8_t)gzgetc(_file); } if(bit_position == 0) return false; if(bit_position == 9) return true; From 602327cd9db4392d6f0beb1f4f9ae1d0db4164d6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Feb 2016 15:42:02 -0500 Subject: [PATCH 089/307] Sketched out a quick class for rendering to texture. --- .../Clock Signal.xcodeproj/project.pbxproj | 8 ++++ Outputs/CRT/CRTOpenGL.cpp | 7 +-- Outputs/CRT/OpenGL.h | 16 +++++++ Outputs/CRT/TextureTarget.cpp | 46 +++++++++++++++++++ Outputs/CRT/TextureTarget.hpp | 30 ++++++++++++ 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 Outputs/CRT/OpenGL.h create mode 100644 Outputs/CRT/TextureTarget.cpp create mode 100644 Outputs/CRT/TextureTarget.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index c60adc189..fd262e6e2 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; }; 4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; }; 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; }; + 4B2039941C67E20B001375C3 /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2039921C67E20B001375C3 /* TextureTarget.cpp */; }; 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; }; 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; @@ -340,6 +341,9 @@ 4B14145A1B58879D00E04248 /* CPU6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502AllRAM.hpp; sourceTree = ""; }; 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = ""; }; 4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = ""; }; + 4B2039921C67E20B001375C3 /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = ""; }; + 4B2039931C67E20B001375C3 /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = ""; }; + 4B2039951C67E2A3001375C3 /* OpenGL.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OpenGL.h; sourceTree = ""; }; 4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = ""; }; 4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = ""; }; 4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = ""; }; @@ -690,6 +694,9 @@ 4B0CCC441C62D0B3001CAC5F /* CRTFrame.h */, 4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */, 4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */, + 4B2039921C67E20B001375C3 /* TextureTarget.cpp */, + 4B2039931C67E20B001375C3 /* TextureTarget.hpp */, + 4B2039951C67E2A3001375C3 /* OpenGL.h */, ); name = CRT; path = ../../Outputs/CRT; @@ -1608,6 +1615,7 @@ 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, 4B0CCC471C62D1A8001CAC5F /* CRTOpenGL.cpp in Sources */, + 4B2039941C67E20B001375C3 /* TextureTarget.cpp in Sources */, 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, 4B7BFEFE1C6446EF00089C1C /* CRTFrameBuilder.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 6fbabba30..e20fb1f18 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -9,9 +9,7 @@ #include "CRT.hpp" #include -// TODO: figure out correct include paths for other platforms. -#include -#include +#include "OpenGL.h" using namespace Outputs; @@ -90,6 +88,9 @@ void CRT::draw_frame(int output_width, int output_height, bool only_if_dirty) { _current_frame_mutex->lock(); + GLint defaultFramebuffer; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFramebuffer); + if(!_current_frame && !only_if_dirty) { glClear(GL_COLOR_BUFFER_BIT); diff --git a/Outputs/CRT/OpenGL.h b/Outputs/CRT/OpenGL.h new file mode 100644 index 000000000..ed9899546 --- /dev/null +++ b/Outputs/CRT/OpenGL.h @@ -0,0 +1,16 @@ +// +// OpenGL.h +// Clock Signal +// +// Created by Thomas Harte on 07/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef OpenGL_h +#define OpenGL_h + +// TODO: figure out correct include paths for other platforms. +#include +#include + +#endif /* OpenGL_h */ diff --git a/Outputs/CRT/TextureTarget.cpp b/Outputs/CRT/TextureTarget.cpp new file mode 100644 index 000000000..8b61dcf7a --- /dev/null +++ b/Outputs/CRT/TextureTarget.cpp @@ -0,0 +1,46 @@ +// +// TextureTarget.cpp +// Clock Signal +// +// Created by Thomas Harte on 07/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "TextureTarget.hpp" + +using namespace OpenGL; + +TextureTarget::TextureTarget(unsigned int width, unsigned int height) +{ + glGenFramebuffers(1, &_framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); + + glGenTextures(1, &_texture); + glBindTexture(GL_TEXTURE_2D, _texture); + + uint8_t *emptySpace = new uint8_t[width*height*4]; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, emptySpace); + delete[] emptySpace; + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + + // TODO: raise an exception if check framebuffer status fails. +// if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) +// return nil; +} + +TextureTarget::~TextureTarget() +{ + glDeleteFramebuffers(1, &_framebuffer); + glDeleteTextures(1, &_texture); +} + +void TextureTarget::bind_framebuffer() +{ + glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); +} + +void TextureTarget::bind_texture() +{ + glBindTexture(GL_TEXTURE_2D, _texture); +} diff --git a/Outputs/CRT/TextureTarget.hpp b/Outputs/CRT/TextureTarget.hpp new file mode 100644 index 000000000..f98ad8305 --- /dev/null +++ b/Outputs/CRT/TextureTarget.hpp @@ -0,0 +1,30 @@ +// +// TextureTarget.hpp +// Clock Signal +// +// Created by Thomas Harte on 07/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef TextureTarget_hpp +#define TextureTarget_hpp + +#include "OpenGL.h" + +namespace OpenGL { + +class TextureTarget { + public: + TextureTarget(unsigned int width, unsigned int height); + ~TextureTarget(); + + void bind_framebuffer(); + void bind_texture(); + + private: + GLuint _framebuffer, _texture; +}; + +} + +#endif /* TextureTarget_hpp */ From 4d0a218a576fe0677c41cdd52e25c9a39a5c4842 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Feb 2016 17:32:38 -0500 Subject: [PATCH 090/307] Certainly at least seriously considering a separate holder for the "compile a shader" logic. Otherwise taking steps back towards PAL/NTSC decoding. --- .../Clock Signal.xcodeproj/project.pbxproj | 10 ++++++-- Outputs/CRT/CRT.cpp | 14 +++++++---- Outputs/CRT/CRT.hpp | 23 ++++++++++++++++--- Outputs/CRT/CRTOpenGL.cpp | 7 +++++- Outputs/CRT/{OpenGL.h => OpenGL.hpp} | 0 Outputs/CRT/Shader.cpp | 9 ++++++++ Outputs/CRT/Shader.hpp | 22 ++++++++++++++++++ Outputs/CRT/TextureTarget.hpp | 2 +- 8 files changed, 75 insertions(+), 12 deletions(-) rename Outputs/CRT/{OpenGL.h => OpenGL.hpp} (100%) create mode 100644 Outputs/CRT/Shader.cpp create mode 100644 Outputs/CRT/Shader.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index fd262e6e2..b521ea229 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; }; 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; }; 4B2039941C67E20B001375C3 /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2039921C67E20B001375C3 /* TextureTarget.cpp */; }; + 4B2039991C67FA92001375C3 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2039971C67FA92001375C3 /* Shader.cpp */; }; 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; }; 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; @@ -343,7 +344,9 @@ 4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = ""; }; 4B2039921C67E20B001375C3 /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = ""; }; 4B2039931C67E20B001375C3 /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = ""; }; - 4B2039951C67E2A3001375C3 /* OpenGL.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OpenGL.h; sourceTree = ""; }; + 4B2039951C67E2A3001375C3 /* OpenGL.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = ""; }; + 4B2039971C67FA92001375C3 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = ""; }; + 4B2039981C67FA92001375C3 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = ""; }; 4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = ""; }; 4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = ""; }; 4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = ""; }; @@ -696,7 +699,9 @@ 4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */, 4B2039921C67E20B001375C3 /* TextureTarget.cpp */, 4B2039931C67E20B001375C3 /* TextureTarget.hpp */, - 4B2039951C67E2A3001375C3 /* OpenGL.h */, + 4B2039951C67E2A3001375C3 /* OpenGL.hpp */, + 4B2039971C67FA92001375C3 /* Shader.cpp */, + 4B2039981C67FA92001375C3 /* Shader.hpp */, ); name = CRT; path = ../../Outputs/CRT; @@ -1623,6 +1628,7 @@ 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, + 4B2039991C67FA92001375C3 /* Shader.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSCathodeRayView.m in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index dde71349e..b80bbc6dc 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -25,8 +25,12 @@ static const uint32_t kCRTFixedPointOffset = 0x04000000; #define kRetraceXMask 0x01 #define kRetraceYMask 0x02 -void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) +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) { + _colour_space = colour_space; + _colour_cycle_numerator = colour_cycle_numerator; + _colour_cycle_denominator = colour_cycle_denominator; + const unsigned int syncCapacityLineChargeThreshold = 3; const unsigned int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234 const unsigned int scanlinesVerticalRetraceTime = 10; // source: ibid @@ -72,11 +76,11 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display switch(displayType) { case DisplayType::PAL50: - set_new_timing(cycles_per_line, 312, 1135, 4); + set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 1135, 4); break; case DisplayType::NTSC60: - set_new_timing(cycles_per_line, 262, 545, 2); + set_new_timing(cycles_per_line, 262, ColourSpace::YIQ, 545, 2); break; } } @@ -111,9 +115,9 @@ CRT::CRT() : construct_openGL(); } -CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...) : CRT() +CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...) : CRT() { - set_new_timing(cycles_per_line, height_of_display, colour_cycle_numerator, colour_cycle_denominator); + set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator); va_list buffer_sizes; va_start(buffer_sizes, number_of_buffers); diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 58d818b9e..ce8d6a3f2 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -28,6 +28,16 @@ class CRT { NTSC60 }; + enum ColourSpace { + YIQ, + YUV + }; + + enum OutputDevice { + Monitor, + Television + }; + /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. The requested number of buffers, each with the requested number of bytes per pixel, is created for the machine to write raw pixel data to. @@ -55,7 +65,7 @@ class CRT { @see @c set_rgb_sampling_function , @c set_composite_sampling_function */ - CRT(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...); + CRT(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...); /*! Constructs the CRT with the specified clock rate, with the display height and colour subcarrier frequency dictated by a standard display type and with the requested number of @@ -68,7 +78,7 @@ class CRT { /*! Resets the CRT with new timing information. The CRT then continues as though the new timing had been provided at construction. */ - void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator); + void 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); /*! Resets the CRT with new timing information derived from a new display type. The CRT then continues as though the new timing had been provided at construction. */ @@ -182,7 +192,9 @@ class CRT { `float phase_for_clock_cycle(int cycle)` that returns the colour phase at the beginning of the supplied cycle. */ - void set_phase_function(const char *shader); +// void set_phase_function(const char *shader); + + void set_output_device(OutputDevice output_device); private: CRT(); @@ -196,6 +208,11 @@ class CRT { unsigned int _cycles_per_line; unsigned int _height_of_display; + // colour invormation + ColourSpace _colour_space; + unsigned int _colour_cycle_numerator; + unsigned int _colour_cycle_denominator; + // properties directly derived from there unsigned int _hsync_error_window; // the permitted window around the expected sync position in which a sync pulse will be recognised; calculated once at init diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index e20fb1f18..2a7a37266 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -9,7 +9,8 @@ #include "CRT.hpp" #include -#include "OpenGL.h" +#include "OpenGL.hpp" +#include "TextureTarget.hpp" using namespace Outputs; @@ -30,6 +31,10 @@ struct CRT::OpenGLState { CRTSize textureSize; + OpenGL::TextureTarget *compositeTexture; + OpenGL::TextureTarget *colourTexture; + OpenGL::TextureTarget *filteredTexture; + GLuint compile_shader(const char *source, GLenum type) { GLuint shader = glCreateShader(type); diff --git a/Outputs/CRT/OpenGL.h b/Outputs/CRT/OpenGL.hpp similarity index 100% rename from Outputs/CRT/OpenGL.h rename to Outputs/CRT/OpenGL.hpp diff --git a/Outputs/CRT/Shader.cpp b/Outputs/CRT/Shader.cpp new file mode 100644 index 000000000..aae0b835a --- /dev/null +++ b/Outputs/CRT/Shader.cpp @@ -0,0 +1,9 @@ +// +// Shader.cpp +// Clock Signal +// +// Created by Thomas Harte on 07/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Shader.hpp" diff --git a/Outputs/CRT/Shader.hpp b/Outputs/CRT/Shader.hpp new file mode 100644 index 000000000..1d9046035 --- /dev/null +++ b/Outputs/CRT/Shader.hpp @@ -0,0 +1,22 @@ +// +// Shader.hpp +// Clock Signal +// +// Created by Thomas Harte on 07/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Shader_hpp +#define Shader_hpp + +namespace OpenGL { + class Shader { + public: + Shader(const char *vertex_shader, const char *fragment_shader); + ~Shader(); + + void bind(); + }; +} + +#endif /* Shader_hpp */ diff --git a/Outputs/CRT/TextureTarget.hpp b/Outputs/CRT/TextureTarget.hpp index f98ad8305..b4b77f42d 100644 --- a/Outputs/CRT/TextureTarget.hpp +++ b/Outputs/CRT/TextureTarget.hpp @@ -9,7 +9,7 @@ #ifndef TextureTarget_hpp #define TextureTarget_hpp -#include "OpenGL.h" +#include "OpenGL.hpp" namespace OpenGL { From 3a9ea66f8b66b6e88cdce109343990d5701abb26 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Feb 2016 19:21:22 -0500 Subject: [PATCH 091/307] Filled in shader class. --- Outputs/CRT/CRT.hpp | 1 + Outputs/CRT/CRTOpenGL.cpp | 71 ++++++++++++--------------------------- Outputs/CRT/Shader.cpp | 61 +++++++++++++++++++++++++++++++++ Outputs/CRT/Shader.hpp | 8 +++++ 4 files changed, 91 insertions(+), 50 deletions(-) diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index ce8d6a3f2..bb4c4ddb0 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -212,6 +212,7 @@ class CRT { ColourSpace _colour_space; unsigned int _colour_cycle_numerator; unsigned int _colour_cycle_denominator; + OutputDevice _output_device; // properties directly derived from there unsigned int _hsync_error_window; // the permitted window around the expected sync position in which a sync pulse will be recognised; calculated once at init diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 2a7a37266..e1522e9bc 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -11,12 +11,12 @@ #include "OpenGL.hpp" #include "TextureTarget.hpp" +#include "Shader.hpp" using namespace Outputs; struct CRT::OpenGLState { - GLuint vertexShader, fragmentShader; - GLuint shaderProgram; + OpenGL::Shader *shaderProgram; GLuint arrayBuffer, vertexArray; GLint positionAttribute; @@ -35,31 +35,11 @@ struct CRT::OpenGLState { OpenGL::TextureTarget *colourTexture; OpenGL::TextureTarget *filteredTexture; - GLuint compile_shader(const char *source, GLenum type) + OpenGLState() : shaderProgram(nullptr) {} + ~OpenGLState() { - GLuint shader = glCreateShader(type); - glShaderSource(shader, 1, &source, NULL); - glCompileShader(shader); - -#if defined(DEBUG) - GLint isCompiled = 0; - glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled); - if(isCompiled == GL_FALSE) - { - GLint logLength; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); - if (logLength > 0) { - GLchar *log = (GLchar *)malloc((size_t)logLength); - glGetShaderInfoLog(shader, logLength, &logLength, log); - printf("Compile log:\n%s\n", log); - free(log); - } - } -#endif - - return shader; + delete shaderProgram; } - }; static GLenum formatForDepth(unsigned int depth) @@ -74,7 +54,6 @@ static GLenum formatForDepth(unsigned int depth) } } - void CRT::construct_openGL() { _openGL_state = nullptr; @@ -303,32 +282,20 @@ void CRT::prepare_shader() char *vertex_shader = get_vertex_shader(); char *fragment_shader = get_fragment_shader(); - _openGL_state->shaderProgram = glCreateProgram(); - _openGL_state->vertexShader = _openGL_state->compile_shader(vertex_shader, GL_VERTEX_SHADER); - _openGL_state->fragmentShader = _openGL_state->compile_shader(fragment_shader, GL_FRAGMENT_SHADER); + _openGL_state->shaderProgram = new OpenGL::Shader(vertex_shader, fragment_shader); + _openGL_state->shaderProgram->bind(); - delete vertex_shader; - delete fragment_shader; + _openGL_state->positionAttribute = _openGL_state->shaderProgram->get_attrib_location("position"); + _openGL_state->textureCoordinatesAttribute = _openGL_state->shaderProgram->get_attrib_location("srcCoordinates"); + _openGL_state->lateralAttribute = _openGL_state->shaderProgram->get_attrib_location("lateral"); + _openGL_state->alphaUniform = _openGL_state->shaderProgram->get_uniform_location("alpha"); + _openGL_state->textureSizeUniform = _openGL_state->shaderProgram->get_uniform_location("textureSize"); + _openGL_state->windowSizeUniform = _openGL_state->shaderProgram->get_uniform_location("windowSize"); + _openGL_state->boundsSizeUniform = _openGL_state->shaderProgram->get_uniform_location("boundsSize"); + _openGL_state->boundsOriginUniform = _openGL_state->shaderProgram->get_uniform_location("boundsOrigin"); - glAttachShader(_openGL_state->shaderProgram, _openGL_state->vertexShader); - glAttachShader(_openGL_state->shaderProgram, _openGL_state->fragmentShader); - glLinkProgram(_openGL_state->shaderProgram); - - glUseProgram(_openGL_state->shaderProgram); - - _openGL_state->positionAttribute = glGetAttribLocation(_openGL_state->shaderProgram, "position"); - _openGL_state->textureCoordinatesAttribute = glGetAttribLocation(_openGL_state->shaderProgram, "srcCoordinates"); - _openGL_state->lateralAttribute = glGetAttribLocation(_openGL_state->shaderProgram, "lateral"); - _openGL_state->alphaUniform = glGetUniformLocation(_openGL_state->shaderProgram, "alpha"); - _openGL_state->textureSizeUniform = glGetUniformLocation(_openGL_state->shaderProgram, "textureSize"); - _openGL_state->windowSizeUniform = glGetUniformLocation(_openGL_state->shaderProgram, "windowSize"); - _openGL_state->boundsSizeUniform = glGetUniformLocation(_openGL_state->shaderProgram, "boundsSize"); - _openGL_state->boundsOriginUniform = glGetUniformLocation(_openGL_state->shaderProgram, "boundsOrigin"); - - GLint texIDUniform = glGetUniformLocation(_openGL_state->shaderProgram, "texID"); - GLint shadowMaskTexIDUniform = glGetUniformLocation(_openGL_state->shaderProgram, "shadowMaskTexID"); - -// [self pushSizeUniforms]; + GLint texIDUniform = _openGL_state->shaderProgram->get_uniform_location("texID"); + GLint shadowMaskTexIDUniform = _openGL_state->shaderProgram->get_uniform_location("shadowMaskTexID"); glUniform1i(texIDUniform, 0); glUniform1i(shadowMaskTexIDUniform, 1); @@ -342,3 +309,7 @@ void CRT::prepare_shader() glVertexAttribPointer((GLuint)_openGL_state->textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTexCoord); glVertexAttribPointer((GLuint)_openGL_state->lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfLateral); } + +void CRT::set_output_device(OutputDevice output_device) +{ +} diff --git a/Outputs/CRT/Shader.cpp b/Outputs/CRT/Shader.cpp index aae0b835a..1864c3ac2 100644 --- a/Outputs/CRT/Shader.cpp +++ b/Outputs/CRT/Shader.cpp @@ -7,3 +7,64 @@ // #include "Shader.hpp" + +#include +#include + +using namespace OpenGL; + +GLuint Shader::compile_shader(const char *source, GLenum type) +{ + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + +#if defined(DEBUG) + GLint isCompiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled); + if(isCompiled == GL_FALSE) + { + GLint logLength; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc((size_t)logLength); + glGetShaderInfoLog(shader, logLength, &logLength, log); + printf("Compile log:\n%s\n", log); + free(log); + } + } +#endif + + return shader; +} + +Shader::Shader(const char *vertex_shader, const char *fragment_shader) +{ + _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); + glLinkProgram(_shader_program); +} + +Shader::~Shader() +{ + glDeleteProgram(_shader_program); +} + +void Shader::bind() +{ + glUseProgram(_shader_program); +} + +GLint Shader::get_attrib_location(const char *name) +{ + return glGetAttribLocation(_shader_program, name); +} + +GLint Shader::get_uniform_location(const char *name) +{ + return glGetUniformLocation(_shader_program, name); +} diff --git a/Outputs/CRT/Shader.hpp b/Outputs/CRT/Shader.hpp index 1d9046035..2f4706bb0 100644 --- a/Outputs/CRT/Shader.hpp +++ b/Outputs/CRT/Shader.hpp @@ -9,6 +9,8 @@ #ifndef Shader_hpp #define Shader_hpp +#include "OpenGL.hpp" + namespace OpenGL { class Shader { public: @@ -16,6 +18,12 @@ namespace OpenGL { ~Shader(); void bind(); + GLint get_attrib_location(const char *name); + GLint get_uniform_location(const char *name); + + private: + GLuint compile_shader(const char *source, GLenum type); + GLuint _shader_program; }; } From 3a689d14cc8cccc234839a414c8ade7fd8155e70 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Feb 2016 20:29:32 -0500 Subject: [PATCH 092/307] Added padding of supplied data to correct for any potential rounding errors when rendering. --- Outputs/CRT/CRT.hpp | 6 +++--- Outputs/CRT/CRTFrameBuilder.cpp | 21 ++++++++++++++++----- Outputs/CRT/CRTOpenGL.cpp | 1 + 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index bb4c4ddb0..c66574438 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -283,8 +283,8 @@ class CRT { uint8_t *get_next_run(); - void allocate_write_area(int required_length); - void reduce_previous_allocation_to(int actual_length); + void allocate_write_area(size_t required_length); + void reduce_previous_allocation_to(size_t actual_length); uint8_t *get_write_target_for_buffer(int buffer); // a pointer to the section of content buffer currently being @@ -292,7 +292,7 @@ class CRT { uint16_t _next_write_x_position, _next_write_y_position; uint16_t _write_x_position, _write_y_position; size_t _write_target_pointer; - int _last_allocation_amount; + size_t _last_allocation_amount; }; static const int kCRTNumberOfFrames = 4; diff --git a/Outputs/CRT/CRTFrameBuilder.cpp b/Outputs/CRT/CRTFrameBuilder.cpp index a229500ae..930beaad2 100644 --- a/Outputs/CRT/CRTFrameBuilder.cpp +++ b/Outputs/CRT/CRTFrameBuilder.cpp @@ -64,26 +64,37 @@ uint8_t *CRT::CRTFrameBuilder::get_next_run() return next_run; } -void CRT::CRTFrameBuilder::allocate_write_area(int required_length) +void CRT::CRTFrameBuilder::allocate_write_area(size_t required_length) { _last_allocation_amount = required_length; - if (_next_write_x_position + required_length > frame.size.width) + if(_next_write_x_position + required_length + 2 > frame.size.width) { _next_write_x_position = 0; _next_write_y_position = (_next_write_y_position+1)&(frame.size.height-1); frame.dirty_size.height++; } - _write_x_position = _next_write_x_position; + _write_x_position = _next_write_x_position + 1; _write_y_position = _next_write_y_position; _write_target_pointer = (_write_y_position * frame.size.width) + _write_x_position; - _next_write_x_position += required_length; + _next_write_x_position += required_length + 2; frame.dirty_size.width = std::max(frame.dirty_size.width, _next_write_x_position); } -void CRT::CRTFrameBuilder::reduce_previous_allocation_to(int actual_length) +void CRT::CRTFrameBuilder::reduce_previous_allocation_to(size_t actual_length) { + for(int c = 0; c < frame.number_of_buffers; c++) + { + memcpy( &frame.buffers[c].data[(_write_target_pointer - 1) * frame.buffers[c].depth], + &frame.buffers[c].data[_write_target_pointer * frame.buffers[c].depth], + frame.buffers[c].depth); + + memcpy( &frame.buffers[c].data[(_write_target_pointer + actual_length) * frame.buffers[c].depth], + &frame.buffers[c].data[(_write_target_pointer + actual_length - 1) * frame.buffers[c].depth], + frame.buffers[c].depth); + } + _next_write_x_position -= (_last_allocation_amount - actual_length); } diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index e1522e9bc..fba197fa8 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -312,4 +312,5 @@ void CRT::prepare_shader() void CRT::set_output_device(OutputDevice output_device) { + _output_device = output_device; } From b5bcadb8d3f1890096eecaa51342a54d21bc36ee Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Feb 2016 22:18:55 -0500 Subject: [PATCH 093/307] Reinstated clipped CRT output, with more appropriate ownership of the decision. --- Machines/Electron/Electron.cpp | 1 + .../Documents/ElectronDocument.swift | 1 - Outputs/CRT/CRT.cpp | 1 + Outputs/CRT/CRT.hpp | 19 +++++++++++++++++++ Outputs/CRT/CRTOpenGL.cpp | 14 +++++++------- Outputs/CRT/Shader.cpp | 3 ++- 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index af47f4d06..86ddbfd2e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -35,6 +35,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); + _crt.set_visible_area(Outputs::Rect(0.2f, 0.0625f, 0.75f, 0.75f)); memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 4568d6982..449cec717 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -28,7 +28,6 @@ class ElectronDocument: MachineDocument { super.windowControllerDidLoadNib(aController) electron.view = openGLView electron.audioQueue = self.audioQueue -// openGLView.frameBounds = CGRectMake(0.0225, 0.0625, 0.75, 0.75) } override var windowNibName: String? { diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index b80bbc6dc..c854f787c 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -110,6 +110,7 @@ CRT::CRT() : _is_in_hsync(false), _is_in_vsync(false), _current_frame_mutex(new std::mutex), + _visible_area(Rect(0, 0, 1, 1)), _rasterPosition({.x = 0, .y = 0}) { construct_openGL(); diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index c66574438..d9766acd8 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -19,6 +19,20 @@ namespace Outputs { +struct Rect { + struct { + float x, y; + } origin; + + struct { + float width, height; + } size; + + Rect() {} + Rect(float x, float y, float width, float height) : + origin({.x = x, .y = y}), size({.width = width, .height =height}) {} +}; + class CRT { public: ~CRT(); @@ -195,6 +209,10 @@ class CRT { // void set_phase_function(const char *shader); void set_output_device(OutputDevice output_device); + void set_visible_area(Rect visible_area) + { + _visible_area = visible_area; + } private: CRT(); @@ -303,6 +321,7 @@ class CRT { CRTFrame *_current_frame, *_last_drawn_frame; std::shared_ptr _current_frame_mutex; int _frame_read_pointer; + Rect _visible_area; struct OpenGLState; OpenGLState *_openGL_state; diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index fba197fa8..967dbdfba 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -137,19 +137,19 @@ void CRT::push_size_uniforms(unsigned int output_width, unsigned int output_heig glUniform2f(_openGL_state->windowSizeUniform, output_width, output_height); } -// GLfloat outputAspectRatioMultiplier = 1.0;//(viewSize.x / viewSize.y) / (4.0 / 3.0); + GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f); -// _aspectRatioCorrectedBounds = _frameBounds; + Rect _aspect_ratio_corrected_bounds = _visible_area; -// CGFloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * _frameBounds.size.width; -// _aspectRatioCorrectedBounds.origin.x -= bonusWidth * 0.5f * _aspectRatioCorrectedBounds.size.width; -// _aspectRatioCorrectedBounds.size.width *= outputAspectRatioMultiplier; + GLfloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * _visible_area.size.width; + _aspect_ratio_corrected_bounds.origin.x -= bonusWidth * 0.5f * _aspect_ratio_corrected_bounds.size.width; + _aspect_ratio_corrected_bounds.size.width *= outputAspectRatioMultiplier; if(_openGL_state->boundsOriginUniform >= 0) - glUniform2f(_openGL_state->boundsOriginUniform, 0.0, 0.0); //(GLfloat)_aspectRatioCorrectedBounds.origin.x, (GLfloat)_aspectRatioCorrectedBounds.origin.y); + glUniform2f(_openGL_state->boundsOriginUniform, (GLfloat)_aspect_ratio_corrected_bounds.origin.x, (GLfloat)_aspect_ratio_corrected_bounds.origin.y); if(_openGL_state->boundsSizeUniform >= 0) - glUniform2f(_openGL_state->boundsSizeUniform, 1.0, 1.0);//(GLfloat)_aspectRatioCorrectedBounds.size.width, (GLfloat)_aspectRatioCorrectedBounds.size.height); + glUniform2f(_openGL_state->boundsSizeUniform, (GLfloat)_aspect_ratio_corrected_bounds.size.width, (GLfloat)_aspect_ratio_corrected_bounds.size.height); } void CRT::set_composite_sampling_function(const char *shader) diff --git a/Outputs/CRT/Shader.cpp b/Outputs/CRT/Shader.cpp index 1864c3ac2..c89a41c3b 100644 --- a/Outputs/CRT/Shader.cpp +++ b/Outputs/CRT/Shader.cpp @@ -51,7 +51,8 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader) Shader::~Shader() { - glDeleteProgram(_shader_program); + // TODO: ensure this is destructed within the correct context. +// glDeleteProgram(_shader_program); } void Shader::bind() From c66409421e325eb789d8e18b8431c506c9b3f94f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 10 Feb 2016 23:11:25 -0500 Subject: [PATCH 094/307] Switched to generating an interlaced output, that apparently being correct. Enabled a poor man's version of phosphor persistence to smooth things out a little. It's not completely unconvincing. --- Machines/Electron/Electron.cpp | 31 ++++++++++++++++++++++--------- Machines/Electron/Electron.hpp | 1 + Outputs/CRT/CRT.hpp | 2 +- Outputs/CRT/CRTOpenGL.cpp | 12 ++++++++++-- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 86ddbfd2e..86150efd2 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -19,6 +19,7 @@ static const unsigned int crt_cycles_multiplier = 8; static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; const int first_graphics_line = 28; +const int first_graphics_cycle = 33; Machine::Machine() : _interruptControl(0), @@ -27,6 +28,7 @@ Machine::Machine() : _audioOutputPosition(0), _audioOutputPositionError(0), _currentOutputLine(0), + _is_odd_field(false), _crt(Outputs::CRT(crt_cycles_per_line, Outputs::CRT::DisplayType::PAL50, 1, 1)) { _crt.set_rgb_sampling_function( @@ -84,8 +86,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { const int current_line = _frameCycles >> 7; const int line_position = _frameCycles & 127; - if(current_line >= first_graphics_line && current_line < first_graphics_line+256 && line_position >= 24 && line_position < 104) - cycles = (unsigned int)(104 - line_position); + if(current_line >= first_graphics_line && current_line < first_graphics_line+256 && line_position >= first_graphics_cycle && line_position < first_graphics_cycle + 80) + cycles = (unsigned int)(80 + first_graphics_cycle - line_position); } } else @@ -376,20 +378,31 @@ inline void Machine::update_display() // assert sync for the first three lines of the display, with a break at the end for horizontal alignment if(_displayOutputPosition < end_of_hsync) { - for(int c = 0; c < lines_of_hsync; c++) - { - _crt.output_sync(119 * crt_cycles_multiplier); - _crt.output_blank(9 * crt_cycles_multiplier); - } + // on an odd field, output a half line of level data, then 2.5 lines of sync; on an even field + // output 2.5 lines of sync, then half a line of level. +// if (_is_odd_field) +// { + _crt.output_blank(64 * crt_cycles_multiplier); + _crt.output_sync(320 * crt_cycles_multiplier); +// } +// else +// { +// _crt.output_sync(320 * crt_cycles_multiplier); +// _crt.output_blank(64 * crt_cycles_multiplier); +// } + + _is_odd_field ^= true; _displayOutputPosition = end_of_hsync; } while(_displayOutputPosition >= end_of_hsync && _displayOutputPosition < _frameCycles) { - const int current_line = _displayOutputPosition >> 7; - const int line_position = _displayOutputPosition & 127; const int cycles_left = _frameCycles - _displayOutputPosition; + const int fieldOutputPosition = _displayOutputPosition + (_is_odd_field ? 64 : 0); + const int current_line = fieldOutputPosition >> 7; + const int line_position = fieldOutputPosition & 127; + // all lines then start with 9 cycles of sync if(line_position < 9) { diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index af80ed2de..1da9e81c1 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -179,6 +179,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { int _currentOutputLine; unsigned int _currentOutputDivider; uint8_t *_currentLine, *_writePointer; + bool _is_odd_field; // Tape. Tape _tape; diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index d9766acd8..f07dbb186 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -164,7 +164,7 @@ class CRT { /*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state. The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call. */ - void draw_frame(int output_width, int output_height, bool only_if_dirty); + void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty); /*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than the previous. diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 967dbdfba..7da1bb85e 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -68,7 +68,7 @@ void CRT::destruct_openGL() if(_rgb_shader) free(_rgb_shader); } -void CRT::draw_frame(int output_width, int output_height, bool only_if_dirty) +void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { _current_frame_mutex->lock(); @@ -105,6 +105,13 @@ void CRT::draw_frame(int output_width, int output_height, bool only_if_dirty) push_size_uniforms(output_width, output_height); + if(_last_drawn_frame != nullptr) + { + glUniform1f(_openGL_state->alphaUniform, 0.4f); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_last_drawn_frame->number_of_vertices); + } + glUniform1f(_openGL_state->alphaUniform, 1.0f); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW); glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); @@ -121,6 +128,7 @@ void CRT::draw_frame(int output_width, int output_height, bool only_if_dirty) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_current_frame->number_of_vertices); + _last_drawn_frame = _current_frame; } _current_frame_mutex->unlock(); @@ -264,7 +272,7 @@ char *CRT::get_fragment_shader() "void main(void)" "{" - "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, 1.0);" + "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, alpha);" "}" , _rgb_shader); } From a6574d1f9623ec2d92163ac95dbf88f558c284ce Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 11 Feb 2016 22:12:12 -0500 Subject: [PATCH 095/307] Made an attempt to factor out and more clearly to document my implementation of flywheel synchronisation. --- .../Clock Signal.xcodeproj/project.pbxproj | 2 + Outputs/CRT/Flywheel.hpp | 178 ++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 Outputs/CRT/Flywheel.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index b521ea229..33c92c1c2 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -661,6 +661,7 @@ 4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "Clock Signal.entitlements"; sourceTree = ""; }; 4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = ""; }; 4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = ""; }; + 4BEDD0F41C6D70B800F6257C /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -702,6 +703,7 @@ 4B2039951C67E2A3001375C3 /* OpenGL.hpp */, 4B2039971C67FA92001375C3 /* Shader.cpp */, 4B2039981C67FA92001375C3 /* Shader.hpp */, + 4BEDD0F41C6D70B800F6257C /* Flywheel.hpp */, ); name = CRT; path = ../../Outputs/CRT; diff --git a/Outputs/CRT/Flywheel.hpp b/Outputs/CRT/Flywheel.hpp new file mode 100644 index 000000000..5ff1077bc --- /dev/null +++ b/Outputs/CRT/Flywheel.hpp @@ -0,0 +1,178 @@ +// +// Flywheel.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Flywheel_hpp +#define Flywheel_hpp + +namespace Outputs { + +/*! + Provides timing for a two-phase signal consisting of a retrace phase and a scan phase, + announcing the start and end of retrace. + + The flywheel will attempt gradually to converge with the timing implied by + synchronisation requests. +*/ +struct Flywheel +{ + enum SyncEvent { + None, + StartRetrace, + EndRetrace + }; + + /*! + Constructs an instance of @c Flywheel. + + @param standard_period The expected amount of time between one synchronisation and the next. + + @param retrace_time The amount of time it takes to complete a retrace. + */ + Flywheel(unsigned int standard_period, unsigned int retrace_time) : + _standard_period(standard_period), + _retrace_time(retrace_time), + _sync_error_window(standard_period >> 6), + _counter(0), + _expected_next_sync(standard_period), + _counter_before_retrace(standard_period - retrace_time), + _did_detect_sync(false) {} + + /*! + Asks the flywheel for the first synchronisation event that will occur in a given time period, + indicating whether a synchronisation request occurred at the start of the query window. + + @param sync_is_requested @c true indicates that the flywheel should act as though having + received a synchronisation request now; @c false indicates no such event was detected. + + @param cycles_to_run_for The number of cycles to look ahead. + + @param cycles_advanced After this method has completed, contains the amount of time until + the returned synchronisation event. + + @returns The next synchronisation event. + */ + inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) + { + // 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) + { + _did_detect_sync = true; + + unsigned int time_now = (_counter < _sync_error_window) ? _expected_next_sync + _counter : _counter; + _expected_next_sync = (_expected_next_sync + _expected_next_sync + _expected_next_sync + time_now) >> 2; + } + } + + SyncEvent proposed_event = SyncEvent::None; + 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) + { + proposed_sync_time = _retrace_time - _counter; + proposed_event = SyncEvent::EndRetrace; + } + + // will we start a retrace? + if(_counter + proposed_sync_time >= _expected_next_sync) + { + proposed_sync_time = _expected_next_sync - _counter; + proposed_event = SyncEvent::StartRetrace; + } + + *cycles_advanced = proposed_sync_time; + return proposed_event; + } + + /*! + Advances a nominated amount of time, applying a previously returned synchronisation event + at the end of that period. + + @param cycles_advanced The amount of time to run for. + + @param event The synchronisation event to apply after that period. + */ + inline void apply_event(unsigned int cycles_advanced, SyncEvent event) + { + if(_counter <= _sync_error_window && _counter + cycles_advanced > _sync_error_window) + { + if(!_did_detect_sync) + _expected_next_sync = (_expected_next_sync + _standard_period + (_sync_error_window >> 1)) >> 1; + _did_detect_sync = false; + } + + _counter += cycles_advanced; + + switch(event) + { + default: return; + case StartRetrace: + _counter_before_retrace = _counter - _retrace_time; + _counter = 0; + return; + } + } + + /*! + Returns the current output position; while in retrace this will go down towards 0, while in scan + it will go upward. + + @returns The current output position. + */ + inline unsigned int get_current_output_position() + { + 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; + } + + return _counter - _retrace_time; + } + + /*! + Returns the amount of time since retrace last began. Time then counts monotonically up from zero. + */ + inline unsigned int get_current_time() + { + return _counter; + } + + 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 _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) + + bool _did_detect_sync; // stores whether sync was detected at all during the current iteration + + /* + Implementation notes: + + Retrace takes a fixed amount of time and runs during [0, _retrace_time). + + For the current line, scan then occurs from [_retrace_time, _expected_next_sync), at which point + retrace begins and the internal counter is reset. + + All synchronisation events that occur within (-_sync_error_window, _sync_error_window) of the + expected synchronisation time will cause an adjustment in the expected time for the next synchronisation. + + If no synchronisation event is detected within that window then the amount of time spent in scan + will edge towards a period slightly longer than the standard period. + */ +}; + +} + +#endif /* Flywheel_hpp */ From 86017de1fb6b5b0e647cbe99279774d1cfa9f4bf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 11 Feb 2016 22:12:37 -0500 Subject: [PATCH 096/307] Fixed field timing. It's 312.5 lines now, closer to the PAL standard. --- Machines/Electron/Electron.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 86150efd2..ad15d5a2a 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -14,7 +14,7 @@ using namespace Electron; static const unsigned int cycles_per_line = 128; -static const unsigned int cycles_per_frame = 312*cycles_per_line; +static const unsigned int cycles_per_frame = 312*cycles_per_line + 64; static const unsigned int crt_cycles_multiplier = 8; static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; @@ -468,7 +468,7 @@ inline void Machine::update_display() case 1: case 4: case 6: newDivider = 2; break; case 2: case 5: newDivider = 4; break; } - if(newDivider != _currentOutputDivider) + if(newDivider != _currentOutputDivider && _currentLine) { _crt.output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); _currentOutputDivider = newDivider; @@ -570,6 +570,7 @@ inline void Machine::update_display() else _crt.output_data(80 * crt_cycles_multiplier, _currentOutputDivider); _currentLine = nullptr; + _writePointer = nullptr; } } } From c81891ec4392f193b136630cb2688da8eca38cc7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 11 Feb 2016 22:13:38 -0500 Subject: [PATCH 097/307] Minor tidying and at least acknowledged the new flywheel encapsulation. --- Outputs/CRT/CRT.cpp | 6 +++--- Outputs/CRT/CRT.hpp | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index c854f787c..d58e4a246 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -222,8 +222,8 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi while(number_of_cycles) { unsigned int time_until_vertical_sync_event, time_until_horizontal_sync_event; - SyncEvent next_vertical_sync_event = this->get_next_vertical_sync_event(vsync_requested, number_of_cycles, &time_until_vertical_sync_event); - SyncEvent next_horizontal_sync_event = this->get_next_horizontal_sync_event(hsync_requested, time_until_vertical_sync_event, &time_until_horizontal_sync_event); + SyncEvent next_vertical_sync_event = get_next_vertical_sync_event(vsync_requested, number_of_cycles, &time_until_vertical_sync_event); + SyncEvent next_horizontal_sync_event = get_next_horizontal_sync_event(hsync_requested, time_until_vertical_sync_event, &time_until_horizontal_sync_event); // get the next sync event and its timing; hsync request is instantaneous (being edge triggered) so // set it to false for the next run through this loop (if any) @@ -316,7 +316,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // in horizontal sync case SyncEvent::EndHSync: if (!_did_detect_hsync) { - _expected_next_hsync = (_expected_next_hsync + (_hsync_error_window >> 1) + _cycles_per_line) >> 1; + _expected_next_hsync = (_expected_next_hsync + (_hsync_error_window >> 4) + _cycles_per_line) >> 1; } _did_detect_hsync = false; _is_in_hsync = false; diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index f07dbb186..8742f2840 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -16,6 +16,7 @@ #include #include "CRTFrame.h" +#include "Flywheel.hpp" namespace Outputs { From 1d20a29d97ee93e0d2f280b5bf79945410b615ae Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 11 Feb 2016 23:43:08 -0500 Subject: [PATCH 098/307] Made an attempt to switch to the flywheels for both vertical and horizontal sync, and to act more appropriately for out-of-bounds sync requests. Lots of tweaking to do, I fear. --- Machines/Electron/Electron.cpp | 32 ++--- Machines/Electron/Electron.hpp | 4 +- Outputs/CRT/CRT.cpp | 222 +++++++++------------------------ Outputs/CRT/CRT.hpp | 28 ++--- Outputs/CRT/CRTOpenGL.cpp | 10 +- Outputs/CRT/Flywheel.hpp | 25 +++- 6 files changed, 108 insertions(+), 213 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ad15d5a2a..6b0dada86 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -29,15 +29,15 @@ Machine::Machine() : _audioOutputPositionError(0), _currentOutputLine(0), _is_odd_field(false), - _crt(Outputs::CRT(crt_cycles_per_line, Outputs::CRT::DisplayType::PAL50, 1, 1)) + _crt(std::unique_ptr(new Outputs::CRT(crt_cycles_per_line, Outputs::CRT::DisplayType::PAL50, 1, 1))) { - _crt.set_rgb_sampling_function( + _crt->set_rgb_sampling_function( "vec3 rgb_sample(vec2 coordinate)" "{" "float texValue = texture(texID, coordinate).r;" "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); - _crt.set_visible_area(Outputs::Rect(0.2f, 0.0625f, 0.75f, 0.75f)); + _crt->set_visible_area(Outputs::Rect(0.2f, 0.062f, 0.82f, 0.82f)); memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); @@ -382,8 +382,8 @@ inline void Machine::update_display() // output 2.5 lines of sync, then half a line of level. // if (_is_odd_field) // { - _crt.output_blank(64 * crt_cycles_multiplier); - _crt.output_sync(320 * crt_cycles_multiplier); + _crt->output_blank(64 * crt_cycles_multiplier); + _crt->output_sync(320 * crt_cycles_multiplier); // } // else // { @@ -412,7 +412,7 @@ inline void Machine::update_display() if(line_position + remaining_period == 9) { // printf("!%d!", 9); - _crt.output_sync(9 * crt_cycles_multiplier); + _crt->output_sync(9 * crt_cycles_multiplier); } } else @@ -425,7 +425,7 @@ inline void Machine::update_display() if(isBlankLine) { int remaining_period = std::min(128 - line_position, cycles_left); - _crt.output_blank((unsigned int)remaining_period * crt_cycles_multiplier); + _crt->output_blank((unsigned int)remaining_period * crt_cycles_multiplier); // printf(".[%d]", remaining_period); _displayOutputPosition += remaining_period; } @@ -435,7 +435,7 @@ inline void Machine::update_display() if(line_position < 24) { int remaining_period = std::min(24 - line_position, cycles_left); - _crt.output_blank((unsigned int)remaining_period * crt_cycles_multiplier); + _crt->output_blank((unsigned int)remaining_period * crt_cycles_multiplier); // printf("/(%d)(%d)[%d]", 24 - line_position, cycles_left, remaining_period); _displayOutputPosition += remaining_period; @@ -448,8 +448,8 @@ inline void Machine::update_display() case 2: case 5: _currentOutputDivider = 4; break; } - _crt.allocate_write_area(80 * crt_cycles_multiplier / _currentOutputDivider); - _currentLine = _writePointer = (uint8_t *)_crt.get_write_target_for_buffer(0); + _crt->allocate_write_area(80 * crt_cycles_multiplier / _currentOutputDivider); + _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); if(current_line == first_graphics_line) _startLineAddress = _startScreenAddress; @@ -470,10 +470,10 @@ inline void Machine::update_display() } if(newDivider != _currentOutputDivider && _currentLine) { - _crt.output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); + _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); _currentOutputDivider = newDivider; - _crt.allocate_write_area((int)((104 - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); - _currentLine = _writePointer = (uint8_t *)_crt.get_write_target_for_buffer(0); + _crt->allocate_write_area((int)((104 - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); + _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); } @@ -551,7 +551,7 @@ inline void Machine::update_display() if(line_position >= 104) { int pixels_to_output = std::min(128 - line_position, cycles_left); - _crt.output_blank((unsigned int)pixels_to_output * crt_cycles_multiplier); + _crt->output_blank((unsigned int)pixels_to_output * crt_cycles_multiplier); _displayOutputPosition += pixels_to_output; if(line_position + pixels_to_output == 128) @@ -566,9 +566,9 @@ inline void Machine::update_display() _startLineAddress++; if(_writePointer) - _crt.output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); + _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); else - _crt.output_data(80 * crt_cycles_multiplier, _currentOutputDivider); + _crt->output_data(80 * crt_cycles_multiplier, _currentOutputDivider); _currentLine = nullptr; _writePointer = nullptr; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 1da9e81c1..7acededc0 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -146,7 +146,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { void set_key_state(Key key, bool isPressed); - Outputs::CRT *get_crt() { return &_crt; } + Outputs::CRT *get_crt() { return _crt.get(); } Outputs::Speaker *get_speaker() { return &_speaker; } virtual void tape_did_change_interrupt_status(Tape *tape); @@ -185,7 +185,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { Tape _tape; // Outputs. - Outputs::CRT _crt; + std::unique_ptr _crt; Speaker _speaker; }; diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index d58e4a246..1bdee8b5f 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -42,23 +42,24 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di // for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV." _time_multiplier = (1000 + cycles_per_line - 1) / cycles_per_line; - height_of_display += (height_of_display / 20); // this is the overrun area we'll use to // store fundamental display configuration properties - _height_of_display = height_of_display + 5; + _height_of_display = height_of_display; _cycles_per_line = cycles_per_line * _time_multiplier; // generate timing values implied by the given arbuments - _hsync_error_window = _cycles_per_line >> 5; - _sync_capacitor_charge_threshold = ((syncCapacityLineChargeThreshold * _cycles_per_line) * 50) >> 7; - _horizontal_retrace_time = (millisecondsHorizontalRetraceTime * _cycles_per_line) >> 6; const unsigned int vertical_retrace_time = scanlinesVerticalRetraceTime * _cycles_per_line; const float halfLineWidth = (float)_height_of_display * 2.0f; + // creat the two flywheels + unsigned int horizontal_retrace_time = scanlinesVerticalRetraceTime * _cycles_per_line; + _horizontal_flywheel = std::unique_ptr(new Outputs::Flywheel(_cycles_per_line, (millisecondsHorizontalRetraceTime * _cycles_per_line) >> 6)); + _vertical_flywheel = std::unique_ptr(new Outputs::Flywheel(_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * _cycles_per_line)); + for(int c = 0; c < 4; c++) { - _scanSpeed[c].x = (c&kRetraceXMask) ? -(kCRTFixedPointRange / _horizontal_retrace_time) : (kCRTFixedPointRange / _cycles_per_line); + _scanSpeed[c].x = (c&kRetraceXMask) ? -(kCRTFixedPointRange / horizontal_retrace_time) : (kCRTFixedPointRange / _cycles_per_line); _scanSpeed[c].y = (c&kRetraceYMask) ? -(kCRTFixedPointRange / vertical_retrace_time) : (kCRTFixedPointRange / (_height_of_display * _cycles_per_line)); // width should be 1.0 / _height_of_display, rotated to match the direction @@ -66,9 +67,6 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di _beamWidth[c].x = (uint32_t)((sinf(angle) / halfLineWidth) * kCRTFixedPointRange); _beamWidth[c].y = (uint32_t)((cosf(angle) / halfLineWidth) * kCRTFixedPointRange); } - - // reset flywheel sync - _expected_next_hsync = _cycles_per_line; } void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) @@ -104,11 +102,8 @@ void CRT::allocate_buffers(unsigned int number, va_list sizes) CRT::CRT() : _next_scan(0), _frame_read_pointer(0), - _horizontal_counter(0), _sync_capacitor_charge_level(0), _is_receiving_sync(false), - _is_in_hsync(false), - _is_in_vsync(false), _current_frame_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), _rasterPosition({.x = 0, .y = 0}) @@ -147,70 +142,14 @@ CRT::~CRT() #pragma mark - Sync loop -CRT::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) +Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) { - SyncEvent proposedEvent = SyncEvent::None; - unsigned int proposedSyncTime = cycles_to_run_for; - - // will an acceptable vertical sync be triggered? - if (vsync_is_requested && !_is_in_vsync) { - if (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold && _rasterPosition.y >= 3*(kCRTFixedPointRange >> 2)) { - proposedSyncTime = 0; - proposedEvent = SyncEvent::StartVSync; - _did_detect_vsync = true; - } - } - - // have we overrun the maximum permitted number of horizontal syncs for this frame? - if (!_is_in_vsync) { - unsigned int time_until_end_of_frame = (kCRTFixedPointRange - _rasterPosition.y) / _scanSpeed[0].y; - - if(time_until_end_of_frame < proposedSyncTime) { - proposedSyncTime = time_until_end_of_frame; - proposedEvent = SyncEvent::StartVSync; - } - } else { - unsigned int time_until_start_of_frame = _rasterPosition.y / (uint32_t)(-_scanSpeed[kRetraceYMask].y); - - if(time_until_start_of_frame < proposedSyncTime) { - proposedSyncTime = time_until_start_of_frame; - proposedEvent = SyncEvent::EndVSync; - } - } - - *cycles_advanced = proposedSyncTime; - return proposedEvent; + return _vertical_flywheel->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced); } -CRT::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) +Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) { - // do we recognise this hsync, thereby adjusting future time expectations? - if(hsync_is_requested) { - if (_horizontal_counter < _hsync_error_window || _horizontal_counter >= _expected_next_hsync - _hsync_error_window) { - _did_detect_hsync = true; - - unsigned int time_now = (_horizontal_counter < _hsync_error_window) ? _expected_next_hsync + _horizontal_counter : _horizontal_counter; - _expected_next_hsync = (_expected_next_hsync + _expected_next_hsync + _expected_next_hsync + time_now) >> 2; - } - } - - SyncEvent proposedEvent = SyncEvent::None; - unsigned int proposedSyncTime = cycles_to_run_for; - - // will we end an ongoing hsync? - if (_horizontal_counter < _horizontal_retrace_time && _horizontal_counter+proposedSyncTime >= _horizontal_retrace_time) { - proposedSyncTime = _horizontal_retrace_time - _horizontal_counter; - proposedEvent = SyncEvent::EndHSync; - } - - // will we start an hsync? - if (_horizontal_counter + proposedSyncTime >= _expected_next_hsync) { - proposedSyncTime = _expected_next_hsync - _horizontal_counter; - proposedEvent = SyncEvent::StartHSync; - } - - *cycles_advanced = proposedSyncTime; - return proposedEvent; + return _horizontal_flywheel->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced); } void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type, uint16_t tex_x, uint16_t tex_y) @@ -218,12 +157,13 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi number_of_cycles *= _time_multiplier; bool is_output_run = ((type == Type::Level) || (type == Type::Data)); + vsync_requested &= (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold); while(number_of_cycles) { unsigned int time_until_vertical_sync_event, time_until_horizontal_sync_event; - SyncEvent next_vertical_sync_event = get_next_vertical_sync_event(vsync_requested, number_of_cycles, &time_until_vertical_sync_event); - SyncEvent next_horizontal_sync_event = get_next_horizontal_sync_event(hsync_requested, time_until_vertical_sync_event, &time_until_horizontal_sync_event); + Flywheel::SyncEvent next_vertical_sync_event = get_next_vertical_sync_event(vsync_requested, number_of_cycles, &time_until_vertical_sync_event); + Flywheel::SyncEvent next_horizontal_sync_event = get_next_horizontal_sync_event(hsync_requested, time_until_vertical_sync_event, &time_until_horizontal_sync_event); // get the next sync event and its timing; hsync request is instantaneous (being edge triggered) so // set it to false for the next run through this loop (if any) @@ -233,7 +173,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi vsync_requested = false; uint8_t *next_run = (is_output_run && _current_frame_builder && next_run_length) ? _current_frame_builder->get_next_run() : nullptr; - int lengthMask = (_is_in_hsync ? kRetraceXMask : 0) | (_is_in_vsync ? kRetraceYMask : 0); + int lengthMask = (_horizontal_flywheel->is_in_retrace() ? kRetraceXMask : 0) | (_vertical_flywheel->is_in_retrace() ? kRetraceYMask : 0); #define position_x(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfPosition + 0]) #define position_y(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfPosition + 2]) @@ -245,11 +185,14 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(next_run) { + unsigned int x_position = _horizontal_flywheel->get_current_output_position() * (kCRTFixedPointRange / 1024); + unsigned int y_position = (_vertical_flywheel->get_current_output_position() / 312) * (kCRTFixedPointRange / 1024); + // set the type, initial raster position and type of this run - position_x(0) = position_x(4) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.x + _beamWidth[lengthMask].x); - position_y(0) = position_y(4) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.y + _beamWidth[lengthMask].y); - position_x(1) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.x - _beamWidth[lengthMask].x); - position_y(1) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.y - _beamWidth[lengthMask].y); + position_x(0) = position_x(4) = InternalToUInt16(kCRTFixedPointOffset + x_position + _beamWidth[lengthMask].x); + position_y(0) = position_y(4) = InternalToUInt16(kCRTFixedPointOffset + y_position + _beamWidth[lengthMask].y); + position_x(1) = InternalToUInt16(kCRTFixedPointOffset + x_position - _beamWidth[lengthMask].x); + position_y(1) = InternalToUInt16(kCRTFixedPointOffset + y_position - _beamWidth[lengthMask].y); tex_x(0) = tex_x(1) = tex_x(4) = tex_x; @@ -259,28 +202,30 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi lateral(1) = lateral(2) = lateral(3) = 1; } - // advance the raster position as dictated by current sync status - int64_t end_position[2]; - end_position[0] = (int64_t)_rasterPosition.x + (int64_t)next_run_length * (int32_t)_scanSpeed[lengthMask].x; - end_position[1] = (int64_t)_rasterPosition.y + (int64_t)next_run_length * (int32_t)_scanSpeed[lengthMask].y; + // decrement the number of cycles left to run for and increment the + // horizontal counter appropriately + number_of_cycles -= next_run_length; - if (_is_in_hsync) - _rasterPosition.x = (uint32_t)std::max((int64_t)0, end_position[0]); + // either charge or deplete the vertical retrace capacitor (making sure it stops at 0) + if (vsync_charging && !_vertical_flywheel->is_in_retrace()) + _sync_capacitor_charge_level += next_run_length; else - _rasterPosition.x = (uint32_t)std::min((int64_t)kCRTFixedPointRange, end_position[0]); + _sync_capacitor_charge_level = std::max(_sync_capacitor_charge_level - (int)next_run_length, 0); - if (_is_in_vsync) - _rasterPosition.y = (uint32_t)std::max((int64_t)0, end_position[1]); - else - _rasterPosition.y = (uint32_t)std::min((int64_t)kCRTFixedPointRange, end_position[1]); + // 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); if(next_run) { + unsigned int x_position = _horizontal_flywheel->get_current_output_position() * (kCRTFixedPointRange / 1024); + unsigned int y_position = (_vertical_flywheel->get_current_output_position() / 312) * (kCRTFixedPointRange / 1024); + // store the final raster position - position_x(2) = position_x(3) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.x - _beamWidth[lengthMask].x); - position_y(2) = position_y(3) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.y - _beamWidth[lengthMask].y); - position_x(5) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.x + _beamWidth[lengthMask].x); - position_y(5) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.y + _beamWidth[lengthMask].y); + position_x(2) = position_x(3) = InternalToUInt16(kCRTFixedPointOffset + x_position - _beamWidth[lengthMask].x); + position_y(2) = position_y(3) = InternalToUInt16(kCRTFixedPointOffset + y_position - _beamWidth[lengthMask].y); + position_x(5) = InternalToUInt16(kCRTFixedPointOffset + x_position + _beamWidth[lengthMask].x); + position_y(5) = InternalToUInt16(kCRTFixedPointOffset + y_position + _beamWidth[lengthMask].y); // if this is a data run then advance the buffer pointer if(type == Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider); @@ -289,82 +234,27 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi tex_x(2) = tex_x(3) = tex_x(5) = tex_x; } - // decrement the number of cycles left to run for and increment the - // horizontal counter appropriately - number_of_cycles -= next_run_length; - _horizontal_counter += next_run_length; - - // either charge or deplete the vertical retrace capacitor (making sure it stops at 0) - if (vsync_charging && !_is_in_vsync) - _sync_capacitor_charge_level += next_run_length; - else - _sync_capacitor_charge_level = std::max(_sync_capacitor_charge_level - (int)next_run_length, 0); - - // react to the incoming event... - if(next_run_length == time_until_horizontal_sync_event) + if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace) { - switch(next_horizontal_sync_event) + if(_current_frame_builder) { - // start of hsync: zero the scanline counter, note that we're now in - // horizontal sync, increment the lines-in-this-frame counter - case SyncEvent::StartHSync: - _horizontal_counter = 0; - _is_in_hsync = true; - break; - - // end of horizontal sync: update the flywheel's velocity, note that we're no longer - // in horizontal sync - case SyncEvent::EndHSync: - if (!_did_detect_hsync) { - _expected_next_hsync = (_expected_next_hsync + (_hsync_error_window >> 4) + _cycles_per_line) >> 1; - } - _did_detect_hsync = false; - _is_in_hsync = false; - break; - - default: break; + _current_frame_builder->complete(); + _current_frame_mutex->lock(); + _current_frame = &_current_frame_builder->frame; + _current_frame_mutex->unlock(); + // TODO: how to communicate did_detect_vsync? Bring the delegate back? +// _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); } - } - if(next_run_length == time_until_vertical_sync_event) - { - switch(next_vertical_sync_event) - { - // start of vertical sync: reset the lines-in-this-frame counter, - // load the retrace counter with the amount of time it'll take to retrace - case SyncEvent::StartVSync: - _is_in_vsync = true; - _sync_capacitor_charge_level = 0; - break; +// if(_frames_with_delegate < kCRTNumberOfFrames) +// { + _frame_read_pointer = (_frame_read_pointer + 1)%kCRTNumberOfFrames; + _current_frame_builder = _frame_builders[_frame_read_pointer]; + _current_frame_builder->reset(); +// } +// else +// _current_frame_builder = nullptr; - // end of vertical sync: tell the delegate that we finished vertical sync, - // releasing all runs back into the common pool - case SyncEvent::EndVSync: - if(_current_frame_builder) - { - _current_frame_builder->complete(); - _current_frame_mutex->lock(); - _current_frame = &_current_frame_builder->frame; - _current_frame_mutex->unlock(); - // TODO: how to communicate did_detect_vsync? Bring the delegate back? -// _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); - } - -// if(_frames_with_delegate < kCRTNumberOfFrames) - { - _frame_read_pointer = (_frame_read_pointer + 1)%kCRTNumberOfFrames; - _current_frame_builder = _frame_builders[_frame_read_pointer]; - _current_frame_builder->reset(); - } -// else -// _current_frame_builder = nullptr; - - _is_in_vsync = false; - _did_detect_vsync = false; - break; - - default: break; - } } } } @@ -378,7 +268,7 @@ void CRT::output_scan() bool this_is_sync = (scan->type == Type::Sync); bool hsync_requested = !_is_receiving_sync && this_is_sync; - bool vsync_requested = _is_receiving_sync; + bool vsync_requested = _is_receiving_sync && !this_is_sync; _is_receiving_sync = this_is_sync; advance_cycles(scan->number_of_cycles, scan->source_divider, hsync_requested, vsync_requested, this_is_sync, scan->type, scan->tex_x, scan->tex_y); diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 8742f2840..6888c320b 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -233,27 +233,18 @@ class CRT { unsigned int _colour_cycle_denominator; OutputDevice _output_device; - // properties directly derived from there - unsigned int _hsync_error_window; // the permitted window around the expected sync position in which a sync pulse will be recognised; calculated once at init - - // the current scanning position + // the current scanning position (TODO: can I eliminate this in favour of just using the flywheels?) struct Vector { uint32_t x, y; } _rasterPosition, _scanSpeed[4], _beamWidth[4]; - // outer elements of sync separation + // the two flywheels regulating scanning + std::unique_ptr _horizontal_flywheel, _vertical_flywheel; + + // 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) - bool _did_detect_hsync; // true if horizontal sync was detected during this scanline (so, this affects flywheel adjustments) 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 - int _is_in_vsync; - - // components of the flywheel sync - unsigned int _horizontal_counter; // time run since the _start_ of the last horizontal sync - unsigned int _expected_next_hsync; // our current expection of when the next horizontal sync will be encountered (which implies current flywheel velocity) - unsigned int _horizontal_retrace_time; - bool _is_in_hsync; // true for the duration of a horizontal sync — used to determine beam running direction and speed - bool _did_detect_vsync; // true if vertical sync was detected in the input stream rather than forced by emergency measure // the outer entry point for dispatching output_sync, output_blank, output_level and output_data enum Type { @@ -263,13 +254,8 @@ class CRT { // the inner entry point that determines whether and when the next sync event will occur within // the current output window - enum SyncEvent { - None, - StartHSync, EndHSync, - StartVSync, EndVSync - }; - SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced); - SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced); + 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); // each call to output_* generates a scan. A two-slot queue for scans allows edge extensions. struct Scan { diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 7da1bb85e..68db5765c 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -105,11 +105,11 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool push_size_uniforms(output_width, output_height); - if(_last_drawn_frame != nullptr) - { - glUniform1f(_openGL_state->alphaUniform, 0.4f); - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_last_drawn_frame->number_of_vertices); - } +// if(_last_drawn_frame != nullptr) +// { +// glUniform1f(_openGL_state->alphaUniform, 0.4f); +// glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_last_drawn_frame->number_of_vertices); +// } glUniform1f(_openGL_state->alphaUniform, 1.0f); glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW); diff --git a/Outputs/CRT/Flywheel.hpp b/Outputs/CRT/Flywheel.hpp index 5ff1077bc..2dc70703b 100644 --- a/Outputs/CRT/Flywheel.hpp +++ b/Outputs/CRT/Flywheel.hpp @@ -61,13 +61,24 @@ struct Flywheel // do we recognise this hsync, thereby adjusting future time expectations? if(sync_is_requested) { + _did_detect_sync = true; + if(_counter < _sync_error_window || _counter > _expected_next_sync - _sync_error_window) { - _did_detect_sync = true; - unsigned int time_now = (_counter < _sync_error_window) ? _expected_next_sync + _counter : _counter; _expected_next_sync = (_expected_next_sync + _expected_next_sync + _expected_next_sync + time_now) >> 2; } + else + { + if(_counter < _retrace_time + (_expected_next_sync >> 1)) + { + _expected_next_sync = (_expected_next_sync + _standard_period + _sync_error_window) >> 1; + } + else + { + _expected_next_sync = (_expected_next_sync + _standard_period - _sync_error_window) >> 1; + } + } } SyncEvent proposed_event = SyncEvent::None; @@ -139,13 +150,21 @@ struct Flywheel } /*! - Returns the amount of time since retrace last began. Time then counts monotonically up from zero. + @returns the amount of time since retrace last began. Time then counts monotonically up from zero. */ inline unsigned int get_current_time() { return _counter; } + /*! + @returns whether the output is currently retracing. + */ + inline bool is_in_retrace() + { + return _counter < _retrace_time; + } + 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 From 341fafd3c58cfe910674efe9691d14f1d96b2bd5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 12 Feb 2016 19:52:04 -0500 Subject: [PATCH 099/307] Adjusted visible area. --- Machines/Electron/Electron.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 6b0dada86..71dd3ec83 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -37,7 +37,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); - _crt->set_visible_area(Outputs::Rect(0.2f, 0.062f, 0.82f, 0.82f)); + _crt->set_visible_area(Outputs::Rect(0.2f, 0.05f, 0.82f, 0.82f)); memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); From 0a09762be6056d181f9822c506e121d657d1097a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 12 Feb 2016 19:53:49 -0500 Subject: [PATCH 100/307] Fixed output precision compared to the new approach of trusting the flywheel for position, and eliminated fixed-Acorn constants. Also re-enabled the faded previous frame, at least temporarily. --- Outputs/CRT/CRT.cpp | 13 +++++----- Outputs/CRT/CRT.hpp | 2 +- Outputs/CRT/CRTOpenGL.cpp | 10 ++++---- Outputs/CRT/Flywheel.hpp | 52 ++++++++++++++++++--------------------- 4 files changed, 37 insertions(+), 40 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 1bdee8b5f..008a16080 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -181,12 +181,13 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi #define tex_y(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfTexCoord + 2]) #define lateral(v) next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfLateral] -#define InternalToUInt16(v) ((v) + 32768) >> 16 +#define InternalToUInt16(v) ((v) + 32768) >> 16 +#define CounterToInternal(c) (unsigned int)(((uint64_t)c->get_current_output_position() * kCRTFixedPointRange) / c->get_scan_period()) if(next_run) { - unsigned int x_position = _horizontal_flywheel->get_current_output_position() * (kCRTFixedPointRange / 1024); - unsigned int y_position = (_vertical_flywheel->get_current_output_position() / 312) * (kCRTFixedPointRange / 1024); + unsigned int x_position = CounterToInternal(_horizontal_flywheel); + unsigned int y_position = CounterToInternal(_vertical_flywheel); // set the type, initial raster position and type of this run position_x(0) = position_x(4) = InternalToUInt16(kCRTFixedPointOffset + x_position + _beamWidth[lengthMask].x); @@ -218,8 +219,8 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(next_run) { - unsigned int x_position = _horizontal_flywheel->get_current_output_position() * (kCRTFixedPointRange / 1024); - unsigned int y_position = (_vertical_flywheel->get_current_output_position() / 312) * (kCRTFixedPointRange / 1024); + unsigned int x_position = CounterToInternal(_horizontal_flywheel); + unsigned int y_position = CounterToInternal(_vertical_flywheel); // store the final raster position position_x(2) = position_x(3) = InternalToUInt16(kCRTFixedPointOffset + x_position - _beamWidth[lengthMask].x); @@ -323,7 +324,7 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider #pragma mark - Buffer supply -void CRT::allocate_write_area(int required_length) +void CRT::allocate_write_area(size_t required_length) { if(_current_frame_builder) _current_frame_builder->allocate_write_area(required_length); } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 6888c320b..9162eac18 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -153,7 +153,7 @@ class CRT { @param required_length The number of samples to allocate. */ - void allocate_write_area(int required_length); + void allocate_write_area(size_t required_length); /*! Gets a pointer for writing to the area created by the most recent call to @c allocate_write_area for the nominated buffer. diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 68db5765c..7da1bb85e 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -105,11 +105,11 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool push_size_uniforms(output_width, output_height); -// if(_last_drawn_frame != nullptr) -// { -// glUniform1f(_openGL_state->alphaUniform, 0.4f); -// glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_last_drawn_frame->number_of_vertices); -// } + if(_last_drawn_frame != nullptr) + { + glUniform1f(_openGL_state->alphaUniform, 0.4f); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_last_drawn_frame->number_of_vertices); + } glUniform1f(_openGL_state->alphaUniform, 1.0f); glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW); diff --git a/Outputs/CRT/Flywheel.hpp b/Outputs/CRT/Flywheel.hpp index 2dc70703b..c8bf73339 100644 --- a/Outputs/CRT/Flywheel.hpp +++ b/Outputs/CRT/Flywheel.hpp @@ -12,20 +12,14 @@ namespace Outputs { /*! - Provides timing for a two-phase signal consisting of a retrace phase and a scan phase, - announcing the start and end of retrace. + Provides timing for a two-phase signal consisting of a retrace phase followed by a scan phase, + announcing the start and end of retrace and providing the abiliy to read the current + scanning position. - The flywheel will attempt gradually to converge with the timing implied by - synchronisation requests. + The @c Flywheel will attempt to converge with timing implied by synchronisation pulses. */ struct Flywheel { - enum SyncEvent { - None, - StartRetrace, - EndRetrace - }; - /*! Constructs an instance of @c Flywheel. @@ -36,12 +30,19 @@ struct Flywheel Flywheel(unsigned int standard_period, unsigned int retrace_time) : _standard_period(standard_period), _retrace_time(retrace_time), - _sync_error_window(standard_period >> 6), + _sync_error_window(standard_period >> 7), _counter(0), _expected_next_sync(standard_period), - _counter_before_retrace(standard_period - retrace_time), - _did_detect_sync(false) {} + _counter_before_retrace(standard_period - retrace_time) {} + enum SyncEvent { + /// Indicates that no synchronisation events will occur in the queried window. + None, + /// Indicates that the next synchronisation event will be a transition into retrce. + StartRetrace, + /// Indicates that the next synchronisation event will be a transition out of retrace. + EndRetrace + }; /*! Asks the flywheel for the first synchronisation event that will occur in a given time period, indicating whether a synchronisation request occurred at the start of the query window. @@ -61,8 +62,6 @@ struct Flywheel // do we recognise this hsync, thereby adjusting future time expectations? if(sync_is_requested) { - _did_detect_sync = true; - 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; @@ -112,13 +111,6 @@ struct Flywheel */ inline void apply_event(unsigned int cycles_advanced, SyncEvent event) { - if(_counter <= _sync_error_window && _counter + cycles_advanced > _sync_error_window) - { - if(!_did_detect_sync) - _expected_next_sync = (_expected_next_sync + _standard_period + (_sync_error_window >> 1)) >> 1; - _did_detect_sync = false; - } - _counter += cycles_advanced; switch(event) @@ -165,6 +157,14 @@ struct Flywheel return _counter < _retrace_time; } + /*! + @returns the expected length of the scan period. + */ + inline unsigned int get_scan_period() + { + return _standard_period - _retrace_time; + } + 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 @@ -174,8 +174,6 @@ struct Flywheel 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) - bool _did_detect_sync; // stores whether sync was detected at all during the current iteration - /* Implementation notes: @@ -185,10 +183,8 @@ struct Flywheel retrace begins and the internal counter is reset. All synchronisation events that occur within (-_sync_error_window, _sync_error_window) of the - expected synchronisation time will cause an adjustment in the expected time for the next synchronisation. - - If no synchronisation event is detected within that window then the amount of time spent in scan - will edge towards a period slightly longer than the standard period. + expected synchronisation time will cause a proportional adjustment in the expected time for the next + synchronisation. Other synchronisation events are clamped as though they occurred in that range. */ }; From 0527888b1558cf0db746e5f603985c779e45766f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 12 Feb 2016 22:30:06 -0500 Subject: [PATCH 101/307] Endeavoured to ensure safe shutdown. --- .../Documents/ElectronDocument.swift | 17 ++++++++++++++--- .../Mac/Clock Signal/Wrappers/CSMachine.h | 1 + .../Mac/Clock Signal/Wrappers/CSMachine.mm | 4 ++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 449cec717..23c5f68e3 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -11,7 +11,7 @@ import AudioToolbox class ElectronDocument: MachineDocument { - private var electron = CSElectron() + private var electron: CSElectron! = CSElectron() override init() { super.init() self.intendedCyclesPerSecond = 2000000 @@ -55,9 +55,21 @@ class ElectronDocument: MachineDocument { electron.setROM(data, slot: 15) } + override func close() { + objc_sync_enter(self) + electron.sync() + openGLView.invalidate() + openGLView.openGLContext!.makeCurrentContext() + electron = nil + super.close() + objc_sync_exit(self) + } + // MARK: CSOpenGLViewDelegate override func runForNumberOfCycles(numberOfCycles: Int32) { - electron.runForNumberOfCycles(numberOfCycles) + objc_sync_enter(self) + electron?.runForNumberOfCycles(numberOfCycles) + objc_sync_exit(self) } override func openGLView(view: CSCathodeRayView, drawViewOnlyIfDirty onlyIfDirty: Bool) { @@ -78,5 +90,4 @@ class ElectronDocument: MachineDocument { electron.setKey(kVK_Control, isPressed: newModifiers.modifierFlags.contains(.ControlKeyMask)) electron.setKey(kVK_Command, isPressed: newModifiers.modifierFlags.contains(.CommandKeyMask)) } - } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h index 9ce936fe2..e75f14bfb 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -13,6 +13,7 @@ @interface CSMachine : NSObject - (void)runForNumberOfCycles:(int)numberOfCycles; +- (void)sync; @property (nonatomic, weak) CSCathodeRayView *view; @property (nonatomic, weak) AudioQueue *audioQueue; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index a237f1eb7..79a8fcb4e 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -47,6 +47,10 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { } } +- (void)sync { + dispatch_sync(_serialDispatchQueue, ^{}); +} + - (instancetype)init { self = [super init]; From 7bc5a43c366fe0665463c53ecdf711a3d6167df7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 12 Feb 2016 22:30:22 -0500 Subject: [PATCH 102/307] Fixed type warning. --- OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index a48cde887..4d0de1122 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -33,7 +33,7 @@ } - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { - _electron.get_crt()->draw_frame((int)pixelSize.width, (int)pixelSize.height, onlyIfDirty ? true : false); + _electron.get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); } - (BOOL)openUEFAtURL:(NSURL *)URL { From fd2d5c78f8f08ae669df886a2112da01a7449c8f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 12 Feb 2016 22:31:05 -0500 Subject: [PATCH 103/307] Started trying to nudge towards the multistage approach to video decoding. --- Outputs/CRT/CRTFrame.h | 5 +- Outputs/CRT/CRTOpenGL.cpp | 114 ++++++++++++++++++++------------------ 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/Outputs/CRT/CRTFrame.h b/Outputs/CRT/CRTFrame.h index 14ebf3ea2..238a062bc 100644 --- a/Outputs/CRT/CRTFrame.h +++ b/Outputs/CRT/CRTFrame.h @@ -52,11 +52,12 @@ typedef struct { uint8_t *vertices; } CRTFrame; -// TODO: these should be private to whomever builds the shaders +// The height of the intermediate buffers. +static const int kCRTFrameIntermediateBufferHeight = 2048; + static const size_t kCRTVertexOffsetOfPosition = 0; static const size_t kCRTVertexOffsetOfTexCoord = 4; static const size_t kCRTVertexOffsetOfLateral = 8; -static const size_t kCRTVertexOffsetOfPhase = 9; static const int kCRTSizeOfVertex = 10; diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 7da1bb85e..92797c621 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -1,4 +1,3 @@ - // CRTOpenGL.cpp // Clock Signal // @@ -16,7 +15,7 @@ using namespace Outputs; struct CRT::OpenGLState { - OpenGL::Shader *shaderProgram; + std::unique_ptr shaderProgram; GLuint arrayBuffer, vertexArray; GLint positionAttribute; @@ -29,17 +28,13 @@ struct CRT::OpenGLState { GLuint textureName, shadowMaskTextureName; + GLuint defaultFramebuffer; + CRTSize textureSize; - OpenGL::TextureTarget *compositeTexture; - OpenGL::TextureTarget *colourTexture; - OpenGL::TextureTarget *filteredTexture; - - OpenGLState() : shaderProgram(nullptr) {} - ~OpenGLState() - { - delete shaderProgram; - } + std::unique_ptr compositeTexture; // receives raw composite levels + std::unique_ptr filteredYTexture; // receives filtered Y in the R channel plus unfiltered I/U and Q/V in G and B + std::unique_ptr filteredTexture; // receives filtered YIQ or YUV }; static GLenum formatForDepth(unsigned int depth) @@ -63,17 +58,42 @@ void CRT::construct_openGL() void CRT::destruct_openGL() { - delete (OpenGLState *)_openGL_state; + delete _openGL_state; + _openGL_state = nullptr; if(_composite_shader) free(_composite_shader); if(_rgb_shader) free(_rgb_shader); } void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { - _current_frame_mutex->lock(); + // establish essentials + if(!_openGL_state) + { + _openGL_state = new OpenGLState; - GLint defaultFramebuffer; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFramebuffer); + glGenTextures(1, &_openGL_state->textureName); + glBindTexture(GL_TEXTURE_2D, _openGL_state->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); + + glGenVertexArrays(1, &_openGL_state->vertexArray); + glBindVertexArray(_openGL_state->vertexArray); + glGenBuffers(1, &_openGL_state->arrayBuffer); + glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); + + prepare_shader(); + + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); + + _openGL_state->compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); + _openGL_state->filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); + _openGL_state->filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); + } + + // lock down any further work on the current frame + _current_frame_mutex->lock(); if(!_current_frame && !only_if_dirty) { @@ -82,52 +102,37 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool if(_current_frame && (_current_frame != _last_drawn_frame || !only_if_dirty)) { - glClear(GL_COLOR_BUFFER_BIT); - - if(!_openGL_state) - { - _openGL_state = new OpenGLState; - - glGenTextures(1, &_openGL_state->textureName); - glBindTexture(GL_TEXTURE_2D, _openGL_state->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); - - glGenVertexArrays(1, &_openGL_state->vertexArray); - glBindVertexArray(_openGL_state->vertexArray); - glGenBuffers(1, &_openGL_state->arrayBuffer); - glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); - - prepare_shader(); - } - + // update uniforms push_size_uniforms(output_width, output_height); - - if(_last_drawn_frame != nullptr) - { - glUniform1f(_openGL_state->alphaUniform, 0.4f); - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_last_drawn_frame->number_of_vertices); - } glUniform1f(_openGL_state->alphaUniform, 1.0f); - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW); - - glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); - if(_openGL_state->textureSize.width != _current_frame->size.width || _openGL_state->textureSize.height != _current_frame->size.height) + // submit new frame data if required + if (_current_frame != _last_drawn_frame) { - GLenum format = formatForDepth(_current_frame->buffers[0].depth); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); - _openGL_state->textureSize = _current_frame->size; + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW); - if(_openGL_state->textureSizeUniform >= 0) - glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height); + glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); + if(_openGL_state->textureSize.width != _current_frame->size.width || _openGL_state->textureSize.height != _current_frame->size.height) + { + GLenum format = formatForDepth(_current_frame->buffers[0].depth); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); + _openGL_state->textureSize = _current_frame->size; + + if(_openGL_state->textureSizeUniform >= 0) + glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height); + } + else + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); } - else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); + // draw + _openGL_state->compositeTexture->bind_framebuffer(); + + glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer); + glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_current_frame->number_of_vertices); + + _last_drawn_frame = _current_frame; } @@ -136,6 +141,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool void CRT::set_openGL_context_will_change(bool should_delete_resources) { + _openGL_state = nullptr; } void CRT::push_size_uniforms(unsigned int output_width, unsigned int output_height) @@ -290,7 +296,7 @@ void CRT::prepare_shader() char *vertex_shader = get_vertex_shader(); char *fragment_shader = get_fragment_shader(); - _openGL_state->shaderProgram = new OpenGL::Shader(vertex_shader, fragment_shader); + _openGL_state->shaderProgram = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); _openGL_state->shaderProgram->bind(); _openGL_state->positionAttribute = _openGL_state->shaderProgram->get_attrib_location("position"); From f57d6d350b2c5325d59c48d774694e1a158cf11b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 12 Feb 2016 22:35:16 -0500 Subject: [PATCH 104/307] Fixed uninitialised error and incorrect default constructor. --- SignalProcessing/Stepper.hpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/SignalProcessing/Stepper.hpp b/SignalProcessing/Stepper.hpp index 53e046354..f91beb2a0 100644 --- a/SignalProcessing/Stepper.hpp +++ b/SignalProcessing/Stepper.hpp @@ -16,19 +16,15 @@ namespace SignalProcessing { class Stepper { public: - Stepper() - { - Stepper(1, 1); - } + Stepper() : Stepper(1,1) {} - Stepper(uint64_t output_rate, uint64_t input_rate) - { - input_rate_ = input_rate; - output_rate_ = output_rate; - whole_step_ = output_rate / input_rate; - adjustment_up_ = (int64_t)(output_rate % input_rate) << 1; - adjustment_down_ = (int64_t)input_rate << 1; - } + Stepper(uint64_t output_rate, uint64_t input_rate) : + accumulated_error_(0), + input_rate_(input_rate), + output_rate_(output_rate), + whole_step_(output_rate / input_rate), + adjustment_up_((int64_t)(output_rate % input_rate) << 1), + adjustment_down_((int64_t)input_rate << 1) {} inline uint64_t step() { From eeb0e134fd8195f76f8bf3c7d6c9f7f912b447ba Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 13 Feb 2016 20:52:23 -0500 Subject: [PATCH 105/307] Started the transition towards a more natural collection of rolling buffers, with phosphor decay in mind. --- .../Clock Signal.xcodeproj/project.pbxproj | 12 +- Outputs/CRT/CRT.cpp | 101 ++++++++--------- Outputs/CRT/CRT.hpp | 67 +++++++---- Outputs/CRT/CRTBuilders.cpp | 101 +++++++++++++++++ Outputs/CRT/CRTFrame.h | 68 ------------ Outputs/CRT/CRTFrameBuilder.cpp | 105 ------------------ Outputs/CRT/CRTOpenGL.cpp | 72 +++++------- Outputs/CRT/CRTOpenGL.hpp | 25 +++++ 8 files changed, 251 insertions(+), 300 deletions(-) create mode 100644 Outputs/CRT/CRTBuilders.cpp delete mode 100644 Outputs/CRT/CRTFrame.h delete mode 100644 Outputs/CRT/CRTFrameBuilder.cpp create mode 100644 Outputs/CRT/CRTOpenGL.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 33c92c1c2..f3919ab2c 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -31,7 +31,7 @@ 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; - 4B7BFEFE1C6446EF00089C1C /* CRTFrameBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */; }; + 4B7BFEFE1C6446EF00089C1C /* CRTBuilders.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BFEFD1C6446EF00089C1C /* CRTBuilders.cpp */; }; 4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; }; @@ -331,7 +331,6 @@ /* Begin PBXFileReference section */ 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = ""; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = ""; }; - 4B0CCC441C62D0B3001CAC5F /* CRTFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CRTFrame.h; sourceTree = ""; }; 4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = ""; }; 4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioQueue.h; sourceTree = ""; }; 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioQueue.m; sourceTree = ""; }; @@ -373,7 +372,7 @@ 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = ""; }; 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = ""; }; 4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; - 4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTFrameBuilder.cpp; sourceTree = ""; }; + 4B7BFEFD1C6446EF00089C1C /* CRTBuilders.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTBuilders.cpp; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = ""; }; 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = ""; }; 4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = ""; }; @@ -659,6 +658,7 @@ 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clock_SignalUITests.swift; sourceTree = ""; }; 4BB73EC31B587A5100552FC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "Clock Signal.entitlements"; sourceTree = ""; }; + 4BCC142A1C6FFD6F0033C621 /* CRTOpenGL.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTOpenGL.hpp; sourceTree = ""; }; 4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = ""; }; 4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = ""; }; 4BEDD0F41C6D70B800F6257C /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = ""; }; @@ -695,15 +695,15 @@ children = ( 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */, 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */, - 4B0CCC441C62D0B3001CAC5F /* CRTFrame.h */, 4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */, - 4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */, + 4B7BFEFD1C6446EF00089C1C /* CRTBuilders.cpp */, 4B2039921C67E20B001375C3 /* TextureTarget.cpp */, 4B2039931C67E20B001375C3 /* TextureTarget.hpp */, 4B2039951C67E2A3001375C3 /* OpenGL.hpp */, 4B2039971C67FA92001375C3 /* Shader.cpp */, 4B2039981C67FA92001375C3 /* Shader.hpp */, 4BEDD0F41C6D70B800F6257C /* Flywheel.hpp */, + 4BCC142A1C6FFD6F0033C621 /* CRTOpenGL.hpp */, ); name = CRT; path = ../../Outputs/CRT; @@ -1624,7 +1624,7 @@ 4B0CCC471C62D1A8001CAC5F /* CRTOpenGL.cpp in Sources */, 4B2039941C67E20B001375C3 /* TextureTarget.cpp in Sources */, 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, - 4B7BFEFE1C6446EF00089C1C /* CRTFrameBuilder.cpp in Sources */, + 4B7BFEFE1C6446EF00089C1C /* CRTBuilders.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 008a16080..24838c9bb 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -7,6 +7,7 @@ // #include "CRT.hpp" +#include "CRTOpenGL.hpp" #include #include @@ -15,13 +16,6 @@ using namespace Outputs; static const uint32_t kCRTFixedPointRange = 0xf7ffffff; static const uint32_t kCRTFixedPointOffset = 0x04000000; -//static const size_t kCRTVertexOffsetOfPosition = 0; -//static const size_t kCRTVertexOffsetOfTexCoord = 4; -//static const size_t kCRTVertexOffsetOfLateral = 8; -//static const size_t kCRTVertexOffsetOfPhase = 9; -// -//static const int kCRTSizeOfVertex = 10; - #define kRetraceXMask 0x01 #define kRetraceYMask 0x02 @@ -41,7 +35,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di // a TV picture tube or camera tube to the starting point of a line or field. It is about 7 µs // for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV." - _time_multiplier = (1000 + cycles_per_line - 1) / cycles_per_line; + _time_multiplier = (2000 + cycles_per_line - 1) / cycles_per_line; // store fundamental display configuration properties _height_of_display = height_of_display; @@ -85,32 +79,38 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display void CRT::allocate_buffers(unsigned int number, va_list sizes) { - // generate buffers for signal storage as requested — format is - // number of buffers, size of buffer 1, size of buffer 2... - const uint16_t bufferWidth = 2048; - const uint16_t bufferHeight = 2048; - for(int frame = 0; frame < sizeof(_frame_builders) / sizeof(*_frame_builders); frame++) + for(int builder = 0; builder < sizeof(_run_builders) / sizeof(*_run_builders); builder++) { - va_list va; - va_copy(va, sizes); - _frame_builders[frame] = new CRTFrameBuilder(bufferWidth, bufferHeight, number, va); - va_end(va); + _run_builders[builder] = new CRTRunBuilder(); } - _current_frame_builder = _frame_builders[0]; + + va_list va; + va_copy(va, sizes); + _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(number, va)); + va_end(va); } CRT::CRT() : _next_scan(0), - _frame_read_pointer(0), + _run_write_pointer(0), _sync_capacitor_charge_level(0), _is_receiving_sync(false), - _current_frame_mutex(new std::mutex), + _output_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), _rasterPosition({.x = 0, .y = 0}) { construct_openGL(); } +CRT::~CRT() +{ + for(int builder = 0; builder < sizeof(_run_builders) / sizeof(*_run_builders); builder++) + { + delete _run_builders[builder]; + } + destruct_openGL(); +} + CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...) : CRT() { set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator); @@ -131,15 +131,6 @@ CRT::CRT(unsigned int cycles_per_line, DisplayType displayType, unsigned int num va_end(buffer_sizes); } -CRT::~CRT() -{ - for(int frame = 0; frame < sizeof(_frame_builders) / sizeof(*_frame_builders); frame++) - { - delete _frame_builders[frame]; - } - destruct_openGL(); -} - #pragma mark - Sync loop Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) @@ -172,7 +163,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi hsync_requested = false; vsync_requested = false; - uint8_t *next_run = (is_output_run && _current_frame_builder && next_run_length) ? _current_frame_builder->get_next_run() : nullptr; + uint8_t *next_run = (is_output_run && next_run_length) ? _run_builders[_run_write_pointer]->get_next_input_run() : nullptr; int lengthMask = (_horizontal_flywheel->is_in_retrace() ? kRetraceXMask : 0) | (_vertical_flywheel->is_in_retrace() ? kRetraceYMask : 0); #define position_x(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfPosition + 0]) @@ -237,25 +228,11 @@ 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::EndRetrace) { - if(_current_frame_builder) - { - _current_frame_builder->complete(); - _current_frame_mutex->lock(); - _current_frame = &_current_frame_builder->frame; - _current_frame_mutex->unlock(); - // TODO: how to communicate did_detect_vsync? Bring the delegate back? -// _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); - } - -// if(_frames_with_delegate < kCRTNumberOfFrames) -// { - _frame_read_pointer = (_frame_read_pointer + 1)%kCRTNumberOfFrames; - _current_frame_builder = _frame_builders[_frame_read_pointer]; - _current_frame_builder->reset(); -// } -// else -// _current_frame_builder = nullptr; + // TODO: how to communicate did_detect_vsync? Bring the delegate back? +// _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); + _run_write_pointer = (_run_write_pointer + 1)%kCRTNumberOfFrames; + _run_builders[_run_write_pointer]->reset(); } } } @@ -280,57 +257,69 @@ void CRT::output_scan() */ void CRT::output_sync(unsigned int number_of_cycles) { + _output_mutex->lock(); _scans[_next_scan].type = Type::Sync; _scans[_next_scan].number_of_cycles = number_of_cycles; output_scan(); + _output_mutex->unlock(); } void CRT::output_blank(unsigned int number_of_cycles) { + _output_mutex->lock(); _scans[_next_scan].type = Type::Blank; _scans[_next_scan].number_of_cycles = number_of_cycles; output_scan(); + _output_mutex->unlock(); } void CRT::output_level(unsigned int number_of_cycles) { + _output_mutex->lock(); _scans[_next_scan].type = Type::Level; _scans[_next_scan].number_of_cycles = number_of_cycles; - _scans[_next_scan].tex_x = _current_frame_builder ? _current_frame_builder->_write_x_position : 0; - _scans[_next_scan].tex_y = _current_frame_builder ? _current_frame_builder->_write_y_position : 0; + _scans[_next_scan].tex_x = _buffer_builder->_write_x_position; + _scans[_next_scan].tex_y = _buffer_builder->_write_y_position; output_scan(); + _output_mutex->unlock(); } void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t magnitude) { + _output_mutex->lock(); _scans[_next_scan].type = Type::ColourBurst; _scans[_next_scan].number_of_cycles = number_of_cycles; _scans[_next_scan].phase = phase; _scans[_next_scan].magnitude = magnitude; output_scan(); + _output_mutex->unlock(); } void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { - if(_current_frame_builder) _current_frame_builder->reduce_previous_allocation_to(number_of_cycles / source_divider); + _output_mutex->lock(); + _buffer_builder->reduce_previous_allocation_to(number_of_cycles / source_divider); _scans[_next_scan].type = Type::Data; _scans[_next_scan].number_of_cycles = number_of_cycles; - _scans[_next_scan].tex_x = _current_frame_builder ? _current_frame_builder->_write_x_position : 0; - _scans[_next_scan].tex_y = _current_frame_builder ? _current_frame_builder->_write_y_position : 0; + _scans[_next_scan].tex_x = _buffer_builder->_write_x_position; + _scans[_next_scan].tex_y = _buffer_builder->_write_y_position; _scans[_next_scan].source_divider = source_divider; output_scan(); + + _output_mutex->unlock(); } #pragma mark - Buffer supply void CRT::allocate_write_area(size_t required_length) { - if(_current_frame_builder) _current_frame_builder->allocate_write_area(required_length); + _output_mutex->lock(); + _buffer_builder->allocate_write_area(required_length); + _output_mutex->unlock(); } uint8_t *CRT::get_write_target_for_buffer(int buffer) { - if (!_current_frame_builder) return nullptr; - return _current_frame_builder->get_write_target_for_buffer(buffer); + return _buffer_builder->get_write_target_for_buffer(buffer); } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 9162eac18..d320f28aa 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -15,7 +15,6 @@ #include #include -#include "CRTFrame.h" #include "Flywheel.hpp" namespace Outputs { @@ -233,6 +232,9 @@ class CRT { unsigned int _colour_cycle_denominator; OutputDevice _output_device; + // The user-supplied visible area + Rect _visible_area; + // the current scanning position (TODO: can I eliminate this in favour of just using the flywheels?) struct Vector { uint32_t x, y; @@ -274,19 +276,32 @@ class CRT { int _next_scan; void output_scan(); - - struct CRTFrameBuilder { - CRTFrame frame; - - CRTFrameBuilder(uint16_t width, uint16_t height, unsigned int number_of_buffers, va_list buffer_sizes); - ~CRTFrameBuilder(); - - std::vector _all_runs; - + struct CRTRunBuilder { + // Resets the run builder. void reset(); - void complete(); - uint8_t *get_next_run(); + // Getter for new storage plus backing storage; in RGB mode input runs will map directly + // from the input buffer to the screen. In composite mode input runs will map from the + // input buffer to the processing buffer, and output runs will map from the processing + // buffer to the screen. + uint8_t *get_next_input_run(); + std::vector _input_runs; + + uint8_t *get_next_output_run(); + std::vector _output_runs; + + // Container for total length in cycles of all contained runs. + uint32_t duration; + + // Storage for the length of run data uploaded so far; reset to zero by reset but otherwise + // entrusted to the CRT to update. + size_t uploaded_run_data; + size_t number_of_vertices; + }; + + struct CRTInputBufferBuilder { + CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes); + ~CRTInputBufferBuilder(); void allocate_write_area(size_t required_length); void reduce_previous_allocation_to(size_t actual_length); @@ -298,26 +313,38 @@ class CRT { uint16_t _write_x_position, _write_y_position; size_t _write_target_pointer; size_t _last_allocation_amount; + + struct Buffer { + uint8_t *data; + size_t bytes_per_pixel; + } *buffers; + unsigned int number_of_buffers; + + // Storage for the amount of buffer uploaded so far; initialised correctly by the buffer + // builder but otherwise entrusted to the CRT to update. + unsigned int last_uploaded_line; }; + // the run and input data buffers static const int kCRTNumberOfFrames = 4; + std::unique_ptr _buffer_builder; + CRTRunBuilder *_run_builders[kCRTNumberOfFrames]; + int _run_write_pointer; + std::shared_ptr _output_mutex; - // the triple buffer and OpenGL state - CRTFrameBuilder *_frame_builders[kCRTNumberOfFrames]; - CRTFrameBuilder *_current_frame_builder; - CRTFrame *_current_frame, *_last_drawn_frame; - std::shared_ptr _current_frame_mutex; - int _frame_read_pointer; - Rect _visible_area; - + // OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here. struct OpenGLState; OpenGLState *_openGL_state; + // Other things the caller may have provided. char *_composite_shader; char *_rgb_shader; + // Setup and teardown for the OpenGL code void construct_openGL(); void destruct_openGL(); + + // Methods used by the OpenGL code void prepare_shader(); void push_size_uniforms(unsigned int output_width, unsigned int output_height); diff --git a/Outputs/CRT/CRTBuilders.cpp b/Outputs/CRT/CRTBuilders.cpp new file mode 100644 index 000000000..701293aad --- /dev/null +++ b/Outputs/CRT/CRTBuilders.cpp @@ -0,0 +1,101 @@ +// +// CRTFrameBuilder.cpp +// Clock Signal +// +// Created by Thomas Harte on 04/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "CRT.hpp" +#include "CRTOpenGL.hpp" + +using namespace Outputs; + +/* + CRTInputBufferBuilder +*/ + +CRT::CRTInputBufferBuilder::CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes) +{ + this->number_of_buffers = number_of_buffers; + buffers = new CRTInputBufferBuilder::Buffer[number_of_buffers]; + + for(int buffer = 0; buffer < number_of_buffers; buffer++) + { + buffers[buffer].bytes_per_pixel = va_arg(buffer_sizes, unsigned int); + buffers[buffer].data = new uint8_t[CRTInputBufferBuilderWidth * CRTInputBufferBuilderHeight * buffers[buffer].bytes_per_pixel]; + } + + _next_write_x_position = _next_write_y_position = 0; + last_uploaded_line = 0; +} + +CRT::CRTInputBufferBuilder::~CRTInputBufferBuilder() +{ + for(int buffer = 0; buffer < number_of_buffers; buffer++) + delete[] buffers[buffer].data; + delete buffers; +} + + +void CRT::CRTInputBufferBuilder::allocate_write_area(size_t required_length) +{ + _last_allocation_amount = required_length; + + if(_next_write_x_position + required_length + 2 > CRTInputBufferBuilderWidth) + { + _next_write_x_position = 0; + _next_write_y_position = (_next_write_y_position+1)%CRTInputBufferBuilderWidth; + } + + _write_x_position = _next_write_x_position + 1; + _write_y_position = _next_write_y_position; + _write_target_pointer = (_write_y_position * CRTInputBufferBuilderWidth) + _write_x_position; + _next_write_x_position += required_length + 2; +} + +void CRT::CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length) +{ + for(int c = 0; c < number_of_buffers; c++) + { + memcpy( &buffers[c].data[(_write_target_pointer - 1) * buffers[c].bytes_per_pixel], + &buffers[c].data[_write_target_pointer * buffers[c].bytes_per_pixel], + buffers[c].bytes_per_pixel); + + memcpy( &buffers[c].data[(_write_target_pointer + actual_length) * buffers[c].bytes_per_pixel], + &buffers[c].data[(_write_target_pointer + actual_length - 1) * buffers[c].bytes_per_pixel], + buffers[c].bytes_per_pixel); + } + + _next_write_x_position -= (_last_allocation_amount - actual_length); +} + + +uint8_t *CRT::CRTInputBufferBuilder::get_write_target_for_buffer(int buffer) +{ + return &buffers[buffer].data[_write_target_pointer * buffers[buffer].bytes_per_pixel]; +} + +/* + CRTRunBuilder +*/ +void CRT::CRTRunBuilder::reset() +{ + number_of_vertices = 0; +} + +uint8_t *CRT::CRTRunBuilder::get_next_input_run() +{ + const size_t vertices_per_run = 6; + + // get a run from the allocated list, allocating more if we're about to overrun + if((number_of_vertices + vertices_per_run) * kCRTSizeOfVertex >= _input_runs.size()) + { + _input_runs.resize(_input_runs.size() + kCRTSizeOfVertex * vertices_per_run * 100); + } + + uint8_t *next_run = &_input_runs[number_of_vertices * kCRTSizeOfVertex]; + number_of_vertices += vertices_per_run; + + return next_run; +} diff --git a/Outputs/CRT/CRTFrame.h b/Outputs/CRT/CRTFrame.h deleted file mode 100644 index 238a062bc..000000000 --- a/Outputs/CRT/CRTFrame.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// CRTFrame.h -// Clock Signal -// -// Created by Thomas Harte on 24/07/2015. -// Copyright © 2015 Thomas Harte. All rights reserved. -// - -#ifndef CRTFrame_h -#define CRTFrame_h - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - uint8_t *data; - unsigned int depth; -} CRTBuffer; - -typedef struct { - uint16_t width, height; -} CRTSize; - -typedef enum { - CRTGeometryModeTriangles -} CRTGeometryMode; - -typedef struct { - /** The total size, in pixels, of the pixel buffer storage. Guaranteed to be a power of two. */ - CRTSize size; - - /** The portion of the pixel buffer that has been changed since the last time this set of buffers was provided. */ - CRTSize dirty_size; - - /** The number of individual buffers that adds up to the complete pixel buffer. */ - unsigned int number_of_buffers; - - /** A C array of those buffers. */ - CRTBuffer *buffers; - - /** The number of vertices that constitute the output. */ - unsigned int number_of_vertices; - - /** The type of output. */ - CRTGeometryMode geometry_mode; - - /** The size of each vertex in bytes. */ - size_t size_per_vertex; - - /** The vertex data. */ - uint8_t *vertices; -} CRTFrame; - -// The height of the intermediate buffers. -static const int kCRTFrameIntermediateBufferHeight = 2048; - -static const size_t kCRTVertexOffsetOfPosition = 0; -static const size_t kCRTVertexOffsetOfTexCoord = 4; -static const size_t kCRTVertexOffsetOfLateral = 8; - -static const int kCRTSizeOfVertex = 10; - -#ifdef __cplusplus -} -#endif - -#endif /* CRTFrame_h */ diff --git a/Outputs/CRT/CRTFrameBuilder.cpp b/Outputs/CRT/CRTFrameBuilder.cpp deleted file mode 100644 index 930beaad2..000000000 --- a/Outputs/CRT/CRTFrameBuilder.cpp +++ /dev/null @@ -1,105 +0,0 @@ -// -// CRTFrameBuilder.cpp -// Clock Signal -// -// Created by Thomas Harte on 04/02/2016. -// Copyright © 2016 Thomas Harte. All rights reserved. -// - -#include "CRT.hpp" - -using namespace Outputs; - -CRT::CRTFrameBuilder::CRTFrameBuilder(uint16_t width, uint16_t height, unsigned int number_of_buffers, va_list buffer_sizes) -{ - frame.size.width = width; - frame.size.height = height; - frame.number_of_buffers = number_of_buffers; - frame.buffers = new CRTBuffer[number_of_buffers]; - frame.size_per_vertex = kCRTSizeOfVertex; - frame.geometry_mode = CRTGeometryModeTriangles; - - for(int buffer = 0; buffer < number_of_buffers; buffer++) - { - frame.buffers[buffer].depth = va_arg(buffer_sizes, unsigned int); - frame.buffers[buffer].data = new uint8_t[width * height * frame.buffers[buffer].depth]; - } - - reset(); -} - -CRT::CRTFrameBuilder::~CRTFrameBuilder() -{ - for(int buffer = 0; buffer < frame.number_of_buffers; buffer++) - delete[] frame.buffers[buffer].data; - delete frame.buffers; -} - -void CRT::CRTFrameBuilder::reset() -{ - frame.number_of_vertices = 0; - _next_write_x_position = _next_write_y_position = 0; - frame.dirty_size.width = 0; - frame.dirty_size.height = 1; -} - -void CRT::CRTFrameBuilder::complete() -{ - frame.vertices = &_all_runs[0]; -} - -uint8_t *CRT::CRTFrameBuilder::get_next_run() -{ - const size_t vertices_per_run = 6; - - // get a run from the allocated list, allocating more if we're about to overrun - if((frame.number_of_vertices + vertices_per_run) * frame.size_per_vertex >= _all_runs.size()) - { - _all_runs.resize(_all_runs.size() + frame.size_per_vertex * vertices_per_run * 100); - } - - uint8_t *next_run = &_all_runs[frame.number_of_vertices * frame.size_per_vertex]; - frame.number_of_vertices += vertices_per_run; - - return next_run; -} - -void CRT::CRTFrameBuilder::allocate_write_area(size_t required_length) -{ - _last_allocation_amount = required_length; - - if(_next_write_x_position + required_length + 2 > frame.size.width) - { - _next_write_x_position = 0; - _next_write_y_position = (_next_write_y_position+1)&(frame.size.height-1); - frame.dirty_size.height++; - } - - _write_x_position = _next_write_x_position + 1; - _write_y_position = _next_write_y_position; - _write_target_pointer = (_write_y_position * frame.size.width) + _write_x_position; - _next_write_x_position += required_length + 2; - frame.dirty_size.width = std::max(frame.dirty_size.width, _next_write_x_position); -} - -void CRT::CRTFrameBuilder::reduce_previous_allocation_to(size_t actual_length) -{ - for(int c = 0; c < frame.number_of_buffers; c++) - { - memcpy( &frame.buffers[c].data[(_write_target_pointer - 1) * frame.buffers[c].depth], - &frame.buffers[c].data[_write_target_pointer * frame.buffers[c].depth], - frame.buffers[c].depth); - - memcpy( &frame.buffers[c].data[(_write_target_pointer + actual_length) * frame.buffers[c].depth], - &frame.buffers[c].data[(_write_target_pointer + actual_length - 1) * frame.buffers[c].depth], - frame.buffers[c].depth); - } - - _next_write_x_position -= (_last_allocation_amount - actual_length); -} - - -uint8_t *CRT::CRTFrameBuilder::get_write_target_for_buffer(int buffer) -{ - return &frame.buffers[buffer].data[_write_target_pointer * frame.buffers[buffer].depth]; -} diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 92797c621..2dd3e30c2 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -11,6 +11,7 @@ #include "OpenGL.hpp" #include "TextureTarget.hpp" #include "Shader.hpp" +#include "CRTOpenGL.hpp" using namespace Outputs; @@ -30,8 +31,6 @@ struct CRT::OpenGLState { GLuint defaultFramebuffer; - CRTSize textureSize; - std::unique_ptr compositeTexture; // receives raw composite levels std::unique_ptr filteredYTexture; // receives filtered Y in the R channel plus unfiltered I/U and Q/V in G and B std::unique_ptr filteredTexture; // receives filtered YIQ or YUV @@ -52,7 +51,6 @@ static GLenum formatForDepth(unsigned int depth) void CRT::construct_openGL() { _openGL_state = nullptr; - _current_frame = _last_drawn_frame = nullptr; _composite_shader = _rgb_shader = nullptr; } @@ -87,56 +85,40 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); - _openGL_state->compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); - _openGL_state->filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); - _openGL_state->filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); +// _openGL_state->compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); +// _openGL_state->filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); +// _openGL_state->filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); } // lock down any further work on the current frame - _current_frame_mutex->lock(); + _output_mutex->lock(); - if(!_current_frame && !only_if_dirty) + // update uniforms + push_size_uniforms(output_width, output_height); + glUniform1f(_openGL_state->alphaUniform, 1.0f); + + // submit latest frame data if required +/* glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(current_frame->number_of_vertices * current_frame->size_per_vertex), current_frame->vertices, GL_DYNAMIC_DRAW); + + glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); + if(_openGL_state->textureSize.width != _current_frame->size.width || _openGL_state->textureSize.height != _current_frame->size.height) { - glClear(GL_COLOR_BUFFER_BIT); + GLenum format = formatForDepth(_current_frame->buffers[0].depth); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); + _openGL_state->textureSize = _current_frame->size; + + if(_openGL_state->textureSizeUniform >= 0) + glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height); } + else + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); - if(_current_frame && (_current_frame != _last_drawn_frame || !only_if_dirty)) - { - // update uniforms - push_size_uniforms(output_width, output_height); - glUniform1f(_openGL_state->alphaUniform, 1.0f); + // draw + glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_current_frame->number_of_vertices);*/ - // submit new frame data if required - if (_current_frame != _last_drawn_frame) - { - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW); - - glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); - if(_openGL_state->textureSize.width != _current_frame->size.width || _openGL_state->textureSize.height != _current_frame->size.height) - { - GLenum format = formatForDepth(_current_frame->buffers[0].depth); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); - _openGL_state->textureSize = _current_frame->size; - - if(_openGL_state->textureSizeUniform >= 0) - glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height); - } - else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); - } - - // draw - _openGL_state->compositeTexture->bind_framebuffer(); - - glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer); - glClear(GL_COLOR_BUFFER_BIT); - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_current_frame->number_of_vertices); - - - _last_drawn_frame = _current_frame; - } - - _current_frame_mutex->unlock(); + _output_mutex->unlock(); } void CRT::set_openGL_context_will_change(bool should_delete_resources) diff --git a/Outputs/CRT/CRTOpenGL.hpp b/Outputs/CRT/CRTOpenGL.hpp new file mode 100644 index 000000000..d4d1ee8e6 --- /dev/null +++ b/Outputs/CRT/CRTOpenGL.hpp @@ -0,0 +1,25 @@ +// +// CRTOpenGL.hpp +// Clock Signal +// +// Created by Thomas Harte on 13/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef CRTOpenGL_h +#define CRTOpenGL_h + +const size_t kCRTVertexOffsetOfPosition = 0; +const size_t kCRTVertexOffsetOfTexCoord = 2; +const size_t kCRTVertexOffsetOfTimestamp = 4; +const size_t kCRTVertexOffsetOfLateral = 8; + +const size_t kCRTSizeOfVertex = 10; + +const int CRTInputBufferBuilderWidth = 2048; +const int CRTInputBufferBuilderHeight = 1024; + +const int CRTIntermediateBufferWidth = 2048; +const int CRTIntermediateBufferHeight = 2048; + +#endif /* CRTOpenGL_h */ From 7bd237193dbc0e8cc9c6fcdf391653b307562ffa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 13 Feb 2016 20:52:35 -0500 Subject: [PATCH 106/307] Edged closer towards realistic timing. --- Machines/Electron/Electron.cpp | 16 ++++++++-------- Machines/Electron/Electron.hpp | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 71dd3ec83..27b27ed79 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -18,8 +18,8 @@ static const unsigned int cycles_per_frame = 312*cycles_per_line + 64; static const unsigned int crt_cycles_multiplier = 8; static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; -const int first_graphics_line = 28; -const int first_graphics_cycle = 33; +static const int first_graphics_line = 38; +static const int first_graphics_cycle = 33; Machine::Machine() : _interruptControl(0), @@ -37,7 +37,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); - _crt->set_visible_area(Outputs::Rect(0.2f, 0.05f, 0.82f, 0.82f)); +// _crt->set_visible_area(Outputs::Rect(0.2f, 0.05f, 0.82f, 0.82f)); memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); @@ -293,12 +293,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin update_audio(); break; - case 128*128: + case (first_graphics_line + 100)*128: update_audio(); signal_interrupt(Interrupt::RealTimeClock); break; - case 284*128: + case (first_graphics_line + 256)*128: update_audio(); signal_interrupt(Interrupt::DisplayEnd); break; @@ -472,7 +472,7 @@ inline void Machine::update_display() { _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); _currentOutputDivider = newDivider; - _crt->allocate_write_area((int)((104 - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); + _crt->allocate_write_area((size_t)((104 - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); } @@ -653,7 +653,7 @@ inline void Tape::get_next_tape_pulse() _current_pulse = _tape->get_next_pulse(); if(_pulse_stepper == nullptr || _current_pulse.length.clock_rate != _pulse_stepper->get_output_rate()) { - _pulse_stepper = std::shared_ptr(new SignalProcessing::Stepper(_current_pulse.length.clock_rate, 2000000)); + _pulse_stepper = std::unique_ptr(new SignalProcessing::Stepper(_current_pulse.length.clock_rate, 2000000)); } } @@ -723,7 +723,7 @@ inline void Tape::set_is_in_input_mode(bool is_in_input_mode) inline void Tape::set_counter(uint8_t value) { - _pulse_stepper = std::shared_ptr(new SignalProcessing::Stepper(1200, 2000000)); + _pulse_stepper = std::unique_ptr(new SignalProcessing::Stepper(1200, 2000000)); } inline void Tape::set_data_register(uint8_t value) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 7acededc0..4f9193e8d 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -90,7 +90,7 @@ class Tape { std::shared_ptr _tape; Storage::Tape::Pulse _current_pulse; - std::shared_ptr _pulse_stepper; + std::unique_ptr _pulse_stepper; uint32_t _time_into_pulse; bool _is_running; From d0b2d840dafd630aa7ce104678f10b2df6c67e9f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 13 Feb 2016 20:57:41 -0500 Subject: [PATCH 107/307] A minor fix to data queuing. Display still absent. --- Outputs/CRT/CRTBuilders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/CRTBuilders.cpp b/Outputs/CRT/CRTBuilders.cpp index 701293aad..2ecc499b0 100644 --- a/Outputs/CRT/CRTBuilders.cpp +++ b/Outputs/CRT/CRTBuilders.cpp @@ -45,7 +45,7 @@ void CRT::CRTInputBufferBuilder::allocate_write_area(size_t required_length) if(_next_write_x_position + required_length + 2 > CRTInputBufferBuilderWidth) { _next_write_x_position = 0; - _next_write_y_position = (_next_write_y_position+1)%CRTInputBufferBuilderWidth; + _next_write_y_position = (_next_write_y_position+1)%CRTInputBufferBuilderHeight; } _write_x_position = _next_write_x_position + 1; From 697e50c0cc3af0a96cdbce022c453316bf3d97d0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 13 Feb 2016 23:50:18 -0500 Subject: [PATCH 108/307] Screen output is back, no matter how inefficiently. --- Outputs/CRT/CRT.cpp | 12 +++- Outputs/CRT/CRT.hpp | 6 +- Outputs/CRT/CRTBuilders.cpp | 2 + Outputs/CRT/CRTOpenGL.cpp | 126 +++++++++++++++++++++++++----------- Outputs/CRT/CRTOpenGL.hpp | 10 +-- 5 files changed, 109 insertions(+), 47 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 24838c9bb..fef92d957 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -79,7 +79,8 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display void CRT::allocate_buffers(unsigned int number, va_list sizes) { - for(int builder = 0; builder < sizeof(_run_builders) / sizeof(*_run_builders); builder++) + _run_builders = new CRTRunBuilder *[kCRTNumberOfFrames]; + for(int builder = 0; builder < kCRTNumberOfFrames; builder++) { _run_builders[builder] = new CRTRunBuilder(); } @@ -104,10 +105,11 @@ CRT::CRT() : CRT::~CRT() { - for(int builder = 0; builder < sizeof(_run_builders) / sizeof(*_run_builders); builder++) + for(int builder = 0; builder < kCRTNumberOfFrames; builder++) { delete _run_builders[builder]; } + delete[] _run_builders; destruct_openGL(); } @@ -171,6 +173,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi #define tex_x(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfTexCoord + 0]) #define tex_y(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfTexCoord + 2]) #define lateral(v) next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfLateral] +#define timestamp(v) (*(uint32_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfTimestamp]) #define InternalToUInt16(v) ((v) + 32768) >> 16 #define CounterToInternal(c) (unsigned int)(((uint64_t)c->get_current_output_position() * kCRTFixedPointRange) / c->get_scan_period()) @@ -186,6 +189,8 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi position_x(1) = InternalToUInt16(kCRTFixedPointOffset + x_position - _beamWidth[lengthMask].x); position_y(1) = InternalToUInt16(kCRTFixedPointOffset + y_position - _beamWidth[lengthMask].y); + timestamp(0) = timestamp(1) = timestamp(4) = _run_builders[_run_write_pointer]->duration; + tex_x(0) = tex_x(1) = tex_x(4) = tex_x; // these things are constants across the line so just throw them out now @@ -197,6 +202,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // decrement the number of cycles left to run for and increment the // horizontal counter appropriately number_of_cycles -= next_run_length; + _run_builders[_run_write_pointer]->duration += next_run_length; // either charge or deplete the vertical retrace capacitor (making sure it stops at 0) if (vsync_charging && !_vertical_flywheel->is_in_retrace()) @@ -219,6 +225,8 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi position_x(5) = InternalToUInt16(kCRTFixedPointOffset + x_position + _beamWidth[lengthMask].x); position_y(5) = InternalToUInt16(kCRTFixedPointOffset + y_position + _beamWidth[lengthMask].y); + timestamp(2) = timestamp(3) = timestamp(5) = _run_builders[_run_write_pointer]->duration; + // if this is a data run then advance the buffer pointer if(type == Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider); diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index d320f28aa..bfe6463ee 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -295,7 +295,7 @@ class CRT { // Storage for the length of run data uploaded so far; reset to zero by reset but otherwise // entrusted to the CRT to update. - size_t uploaded_run_data; + size_t uploaded_vertices; size_t number_of_vertices; }; @@ -326,9 +326,8 @@ class CRT { }; // the run and input data buffers - static const int kCRTNumberOfFrames = 4; std::unique_ptr _buffer_builder; - CRTRunBuilder *_run_builders[kCRTNumberOfFrames]; + CRTRunBuilder **_run_builders; int _run_write_pointer; std::shared_ptr _output_mutex; @@ -346,6 +345,7 @@ class CRT { // Methods used by the OpenGL code void prepare_shader(); + void prepare_vertex_array(); void push_size_uniforms(unsigned int output_width, unsigned int output_height); char *get_vertex_shader(); diff --git a/Outputs/CRT/CRTBuilders.cpp b/Outputs/CRT/CRTBuilders.cpp index 2ecc499b0..7eebd9433 100644 --- a/Outputs/CRT/CRTBuilders.cpp +++ b/Outputs/CRT/CRTBuilders.cpp @@ -82,6 +82,8 @@ uint8_t *CRT::CRTInputBufferBuilder::get_write_target_for_buffer(int buffer) void CRT::CRTRunBuilder::reset() { number_of_vertices = 0; + uploaded_vertices = 0; + duration = 0; } uint8_t *CRT::CRTRunBuilder::get_next_input_run() diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 2dd3e30c2..373655ef1 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -17,15 +17,15 @@ using namespace Outputs; struct CRT::OpenGLState { std::unique_ptr shaderProgram; - GLuint arrayBuffer, vertexArray; + GLuint arrayBuffers[kCRTNumberOfFrames], vertexArrays[kCRTNumberOfFrames]; GLint positionAttribute; GLint textureCoordinatesAttribute; GLint lateralAttribute; + GLint timestampAttribute; - GLint textureSizeUniform, windowSizeUniform; + GLint windowSizeUniform, timestampBaseUniform; GLint boundsOriginUniform, boundsSizeUniform; - GLint alphaUniform; GLuint textureName, shadowMaskTextureName; @@ -36,7 +36,7 @@ struct CRT::OpenGLState { std::unique_ptr filteredTexture; // receives filtered YIQ or YUV }; -static GLenum formatForDepth(unsigned int depth) +static GLenum formatForDepth(size_t depth) { switch(depth) { @@ -76,13 +76,21 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glGenVertexArrays(1, &_openGL_state->vertexArray); - glBindVertexArray(_openGL_state->vertexArray); - glGenBuffers(1, &_openGL_state->arrayBuffer); - glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); + GLenum format = formatForDepth(_buffer_builder->buffers[0].bytes_per_pixel); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[0].data); + + glGenVertexArrays(kCRTNumberOfFrames, _openGL_state->vertexArrays); + glGenBuffers(kCRTNumberOfFrames, _openGL_state->arrayBuffers); prepare_shader(); + for(int c = 0; c < kCRTNumberOfFrames; c++) + { + glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffers[c]); + glBindVertexArray(_openGL_state->vertexArrays[c]); + prepare_vertex_array(); + } + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); // _openGL_state->compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); @@ -95,28 +103,56 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // update uniforms push_size_uniforms(output_width, output_height); - glUniform1f(_openGL_state->alphaUniform, 1.0f); - // submit latest frame data if required -/* glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(current_frame->number_of_vertices * current_frame->size_per_vertex), current_frame->vertices, GL_DYNAMIC_DRAW); - - glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); - if(_openGL_state->textureSize.width != _current_frame->size.width || _openGL_state->textureSize.height != _current_frame->size.height) - { - GLenum format = formatForDepth(_current_frame->buffers[0].depth); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); - _openGL_state->textureSize = _current_frame->size; - - if(_openGL_state->textureSizeUniform >= 0) - glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height); - } - else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data); - - // draw - glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer); + // clear the buffer glClear(GL_COLOR_BUFFER_BIT); - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_current_frame->number_of_vertices);*/ + + // upload more source pixel data if any; we'll always resubmit the last line submitted last + // time as it may have had extra data appended to it + GLenum format = formatForDepth(_buffer_builder->buffers[0].bytes_per_pixel); +// if(_buffer_builder->_next_write_y_position > _buffer_builder->last_uploaded_line) +// { +// glTexSubImage2D(GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight, format, GL_UNSIGNED_BYTE, &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * CRTInputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); +// _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; +// } +// else +// { + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[0].data); + _buffer_builder->last_uploaded_line = 0; +// } + + // draw all sitting frames + int run = _run_write_pointer; +// printf("%d: %zu v %zu\n", run, _run_builders[run]->uploaded_vertices, _run_builders[run]->number_of_vertices); + GLint total_age = 0; + for(int c = 0; c < kCRTNumberOfFrames; c++) + { + // update the total age at the start of this set of runs + total_age += _run_builders[run]->duration; + + if(_run_builders[run]->number_of_vertices > 0) + { + glUniform1f(_openGL_state->timestampBaseUniform, (GLfloat)total_age); + + // bind the vertex array + glBindVertexArray(_openGL_state->vertexArrays[run]); + + // bind this frame's array buffer + glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffers[run]); + if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) + { + // buffersubdata can only replace existing data, not grow the pool, so we'll just have to take this hit + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_run_builders[run]->number_of_vertices * kCRTSizeOfVertex), &_run_builders[run]->_input_runs[0], GL_DYNAMIC_DRAW); + _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; + } + + // draw this frame + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_run_builders[run]->number_of_vertices); + } + + // advance back in time + run = (run - 1 + kCRTNumberOfFrames) % kCRTNumberOfFrames; + } _output_mutex->unlock(); } @@ -183,14 +219,18 @@ char *CRT::get_vertex_shader() "in vec2 position;" "in vec2 srcCoordinates;" "in float lateral;" + "in float timestamp;" "uniform vec2 boundsOrigin;" "uniform vec2 boundsSize;" "out float lateralVarying;" "out vec2 shadowMaskCoordinates;" + "out float age;" "uniform vec2 textureSize;" + "uniform float timestampBase;" + "uniform float ticksPerFrame;" "const float shadowMaskMultiple = 600;" @@ -202,7 +242,8 @@ char *CRT::get_vertex_shader() "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);" - "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" + "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" + "age = (timestampBase - timestamp) / ticksPerFrame;" "vec2 mappedPosition = (position - boundsOrigin) / boundsSize;" "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);" @@ -247,20 +288,19 @@ char *CRT::get_fragment_shader() "#version 150\n" "in float lateralVarying;" + "in float age;" "in vec2 shadowMaskCoordinates;" "out vec4 fragColour;" "uniform sampler2D texID;" "uniform sampler2D shadowMaskTexID;" - "uniform float alpha;" "in vec2 srcCoordinatesVarying;" - "in float phase;\n" - "%s\n" + "\n%s\n" "void main(void)" "{" - "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, alpha);" + "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, 1.3 - age);" "}" , _rgb_shader); } @@ -284,26 +324,36 @@ void CRT::prepare_shader() _openGL_state->positionAttribute = _openGL_state->shaderProgram->get_attrib_location("position"); _openGL_state->textureCoordinatesAttribute = _openGL_state->shaderProgram->get_attrib_location("srcCoordinates"); _openGL_state->lateralAttribute = _openGL_state->shaderProgram->get_attrib_location("lateral"); - _openGL_state->alphaUniform = _openGL_state->shaderProgram->get_uniform_location("alpha"); - _openGL_state->textureSizeUniform = _openGL_state->shaderProgram->get_uniform_location("textureSize"); + _openGL_state->timestampAttribute = _openGL_state->shaderProgram->get_attrib_location("timestamp"); + _openGL_state->windowSizeUniform = _openGL_state->shaderProgram->get_uniform_location("windowSize"); _openGL_state->boundsSizeUniform = _openGL_state->shaderProgram->get_uniform_location("boundsSize"); _openGL_state->boundsOriginUniform = _openGL_state->shaderProgram->get_uniform_location("boundsOrigin"); + _openGL_state->timestampBaseUniform = _openGL_state->shaderProgram->get_uniform_location("timestampBase"); GLint texIDUniform = _openGL_state->shaderProgram->get_uniform_location("texID"); GLint shadowMaskTexIDUniform = _openGL_state->shaderProgram->get_uniform_location("shadowMaskTexID"); + GLint textureSizeUniform = _openGL_state->shaderProgram->get_uniform_location("textureSize"); + GLint ticksPerFrameUniform = _openGL_state->shaderProgram->get_uniform_location("ticksPerFrame"); glUniform1i(texIDUniform, 0); glUniform1i(shadowMaskTexIDUniform, 1); + glUniform2f(textureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); + glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display * _time_multiplier)); +} +void CRT::prepare_vertex_array() +{ glEnableVertexAttribArray((GLuint)_openGL_state->positionAttribute); glEnableVertexAttribArray((GLuint)_openGL_state->textureCoordinatesAttribute); glEnableVertexAttribArray((GLuint)_openGL_state->lateralAttribute); + glEnableVertexAttribArray((GLuint)_openGL_state->timestampBaseUniform); const GLsizei vertexStride = kCRTSizeOfVertex; - glVertexAttribPointer((GLuint)_openGL_state->positionAttribute, 2, GL_UNSIGNED_SHORT, GL_TRUE, vertexStride, (void *)kCRTVertexOffsetOfPosition); - glVertexAttribPointer((GLuint)_openGL_state->textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTexCoord); - glVertexAttribPointer((GLuint)_openGL_state->lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfLateral); + glVertexAttribPointer((GLuint)_openGL_state->positionAttribute, 2, GL_UNSIGNED_SHORT, GL_TRUE, vertexStride, (void *)kCRTVertexOffsetOfPosition); + glVertexAttribPointer((GLuint)_openGL_state->textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTexCoord); + glVertexAttribPointer((GLuint)_openGL_state->timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTimestamp); + glVertexAttribPointer((GLuint)_openGL_state->lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfLateral); } void CRT::set_output_device(OutputDevice output_device) diff --git a/Outputs/CRT/CRTOpenGL.hpp b/Outputs/CRT/CRTOpenGL.hpp index d4d1ee8e6..bbf6f3df1 100644 --- a/Outputs/CRT/CRTOpenGL.hpp +++ b/Outputs/CRT/CRTOpenGL.hpp @@ -10,11 +10,11 @@ #define CRTOpenGL_h const size_t kCRTVertexOffsetOfPosition = 0; -const size_t kCRTVertexOffsetOfTexCoord = 2; -const size_t kCRTVertexOffsetOfTimestamp = 4; -const size_t kCRTVertexOffsetOfLateral = 8; +const size_t kCRTVertexOffsetOfTexCoord = 4; +const size_t kCRTVertexOffsetOfTimestamp = 8; +const size_t kCRTVertexOffsetOfLateral = 12; -const size_t kCRTSizeOfVertex = 10; +const size_t kCRTSizeOfVertex = 16; const int CRTInputBufferBuilderWidth = 2048; const int CRTInputBufferBuilderHeight = 1024; @@ -22,4 +22,6 @@ const int CRTInputBufferBuilderHeight = 1024; const int CRTIntermediateBufferWidth = 2048; const int CRTIntermediateBufferHeight = 2048; +const int kCRTNumberOfFrames = 3; + #endif /* CRTOpenGL_h */ From 752db78431fcff4ce8ab1a6f27c928aafda71001 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 13 Feb 2016 23:55:02 -0500 Subject: [PATCH 109/307] No doubt still imperfect but killed the repeated whole texture uploads. --- Outputs/CRT/CRTOpenGL.cpp | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 373655ef1..a6b9b070d 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -110,16 +110,25 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it GLenum format = formatForDepth(_buffer_builder->buffers[0].bytes_per_pixel); -// if(_buffer_builder->_next_write_y_position > _buffer_builder->last_uploaded_line) -// { -// glTexSubImage2D(GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight, format, GL_UNSIGNED_BYTE, &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * CRTInputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); -// _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; -// } -// else -// { - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[0].data); - _buffer_builder->last_uploaded_line = 0; -// } + if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, + 0, (GLint)_buffer_builder->last_uploaded_line, + CRTInputBufferBuilderWidth, (GLint)(CRTInputBufferBuilderHeight - _buffer_builder->last_uploaded_line), + format, GL_UNSIGNED_BYTE, + &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * CRTInputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); + _buffer_builder->last_uploaded_line = 0; + } + + if(_buffer_builder->_next_write_y_position > _buffer_builder->last_uploaded_line) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, + 0, (GLint)_buffer_builder->last_uploaded_line, + CRTInputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), + format, GL_UNSIGNED_BYTE, + &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * CRTInputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); + _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; + } // draw all sitting frames int run = _run_write_pointer; From 7580d192d5b8275076f0c4dbe01cba8d87360245 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 14 Feb 2016 19:28:02 -0500 Subject: [PATCH 110/307] Fixes: now setting correct frame time estimate, properly enabling the timestamp attribute, and ensuring that the Electron flushes screen work at least once per host frame update. --- Machines/Electron/Electron.cpp | 6 ++++++ Machines/Electron/Electron.hpp | 2 ++ OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm | 1 + Outputs/CRT/CRTOpenGL.cpp | 14 ++++++++------ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 27b27ed79..df7578a3c 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -318,6 +318,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin return cycles; } +void Machine::update_output() +{ + update_display(); + update_audio(); +} + void Machine::set_tape(std::shared_ptr tape) { _tape.set_tape(tape); diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 4f9193e8d..6a2cb074e 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -151,6 +151,8 @@ class Machine: public CPU6502::Processor, Tape::Delegate { virtual void tape_did_change_interrupt_status(Tape *tape); + void update_output(); + private: inline void update_display(); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 4d0de1122..cc17d6ae4 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -18,6 +18,7 @@ - (void)doRunForNumberOfCycles:(int)numberOfCycles { _electron.run_for_cycles(numberOfCycles); + _electron.update_output(); } - (void)setOSROM:(nonnull NSData *)rom { diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index a6b9b070d..871c847a9 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -150,8 +150,9 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffers[run]); if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) { - // buffersubdata can only replace existing data, not grow the pool, so we'll just have to take this hit - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_run_builders[run]->number_of_vertices * kCRTSizeOfVertex), &_run_builders[run]->_input_runs[0], GL_DYNAMIC_DRAW); + // glBufferSubData can only replace existing data, not grow the pool, so for now we'll just take this hit + uint8_t *data = &_run_builders[run]->_input_runs[0]; + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_run_builders[run]->number_of_vertices * kCRTSizeOfVertex), data, GL_DYNAMIC_DRAW); _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; } @@ -299,17 +300,18 @@ char *CRT::get_fragment_shader() "in float lateralVarying;" "in float age;" "in vec2 shadowMaskCoordinates;" + "in vec2 srcCoordinatesVarying;" + "out vec4 fragColour;" "uniform sampler2D texID;" "uniform sampler2D shadowMaskTexID;" - "in vec2 srcCoordinatesVarying;" "\n%s\n" "void main(void)" "{" - "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, 1.3 - age);" + "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, 2.0 - age);" "}" , _rgb_shader); } @@ -348,7 +350,7 @@ void CRT::prepare_shader() glUniform1i(texIDUniform, 0); glUniform1i(shadowMaskTexIDUniform, 1); glUniform2f(textureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); - glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display * _time_multiplier)); + glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); } void CRT::prepare_vertex_array() @@ -356,7 +358,7 @@ void CRT::prepare_vertex_array() glEnableVertexAttribArray((GLuint)_openGL_state->positionAttribute); glEnableVertexAttribArray((GLuint)_openGL_state->textureCoordinatesAttribute); glEnableVertexAttribArray((GLuint)_openGL_state->lateralAttribute); - glEnableVertexAttribArray((GLuint)_openGL_state->timestampBaseUniform); + glEnableVertexAttribArray((GLuint)_openGL_state->timestampAttribute); const GLsizei vertexStride = kCRTSizeOfVertex; glVertexAttribPointer((GLuint)_openGL_state->positionAttribute, 2, GL_UNSIGNED_SHORT, GL_TRUE, vertexStride, (void *)kCRTVertexOffsetOfPosition); From a01f90ff3e2195d00817889d6d4e4968debd0cbd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 14 Feb 2016 21:57:23 -0500 Subject: [PATCH 111/307] Attempted a switch to the real PAL visible area and to something a bit like a real phosphor decay. --- Machines/Electron/Electron.cpp | 2 +- OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m | 2 +- Outputs/CRT/CRTOpenGL.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index df7578a3c..364a44a49 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -37,7 +37,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); -// _crt->set_visible_area(Outputs::Rect(0.2f, 0.05f, 0.82f, 0.82f)); + _crt->set_visible_area(Outputs::Rect(0.1875f, 0.0f, 0.8125f, 0.98f)); memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 7c223f4de..3134350cf 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -162,7 +162,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt NSOpenGLPFADoubleBuffer, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, NSOpenGLPFASampleBuffers, 1, - NSOpenGLPFASamples, 2, + NSOpenGLPFASamples, 16, 0 }; diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 871c847a9..23c308c66 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -311,7 +311,7 @@ char *CRT::get_fragment_shader() "void main(void)" "{" - "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, 2.0 - age);" + "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, 10.0 * exp(-age * 2.0));" "}" , _rgb_shader); } From bbffbb5dc23965c9abdec105d34586ec268cad3a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Feb 2016 20:35:45 -0500 Subject: [PATCH 112/307] Resolved deadlock if an invalidate call was sent to the Electron while it was in the middle of an update. --- .../Clock Signal/Documents/ElectronDocument.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 23c5f68e3..f68fb0019 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -55,21 +55,24 @@ class ElectronDocument: MachineDocument { electron.setROM(data, slot: 15) } + lazy var actionLock = NSLock() override func close() { - objc_sync_enter(self) + actionLock.lock() electron.sync() openGLView.invalidate() openGLView.openGLContext!.makeCurrentContext() electron = nil + actionLock.unlock() + super.close() - objc_sync_exit(self) } // MARK: CSOpenGLViewDelegate override func runForNumberOfCycles(numberOfCycles: Int32) { - objc_sync_enter(self) - electron?.runForNumberOfCycles(numberOfCycles) - objc_sync_exit(self) + if actionLock.tryLock() { + electron?.runForNumberOfCycles(numberOfCycles) + actionLock.unlock() + } } override func openGLView(view: CSCathodeRayView, drawViewOnlyIfDirty onlyIfDirty: Bool) { From 0b5417ab44c2596d8fe6c6906e7d8adbddcf163a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Feb 2016 20:36:19 -0500 Subject: [PATCH 113/307] Made an attempt to put interrupts in the correct places regardless of even/odd field. --- Machines/Electron/Electron.cpp | 117 +++++++++++++++------------------ Machines/Electron/Electron.hpp | 3 +- 2 files changed, 55 insertions(+), 65 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 364a44a49..be4ef6358 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -23,7 +23,7 @@ static const int first_graphics_cycle = 33; Machine::Machine() : _interruptControl(0), - _frameCycles(0), + _fieldCycles(0), _displayOutputPosition(0), _audioOutputPosition(0), _audioOutputPositionError(0), @@ -37,7 +37,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); - _crt->set_visible_area(Outputs::Rect(0.1875f, 0.0f, 0.8125f, 0.98f)); + _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); @@ -71,7 +71,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // no need to flush the display. Otherwise, output up until now so that any // write doesn't have retroactive effect on the video output. if(!( - (_frameCycles < first_graphics_line * cycles_per_line) || + (_fieldCycles < first_graphics_line * cycles_per_line) || (address < _startLineAddress && address < 0x3000) )) update_display(); @@ -81,11 +81,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // for the entire frame, RAM is accessible only on odd cycles; in modes below 4 // it's also accessible only outside of the pixel regions - cycles += (_frameCycles&1)^1; + cycles += (_fieldCycles&1)^1; if(_screenMode < 4) { - const int current_line = _frameCycles >> 7; - const int line_position = _frameCycles & 127; + const int current_line = _fieldCycles >> 7; + const int line_position = _fieldCycles & 127; if(current_line >= first_graphics_line && current_line < first_graphics_line+256 && line_position >= first_graphics_cycle && line_position < first_graphics_cycle + 80) cycles = (unsigned int)(80 + first_graphics_cycle - line_position); } @@ -96,7 +96,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { if((address & 0xff00) == 0xfe00) { - cycles += (_frameCycles&1)^1; + cycles += (_fieldCycles&1)^1; // printf("%c: %02x: ", isReadOperation(operation) ? 'r' : 'w', *value); switch(address&0xf) @@ -282,31 +282,37 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // if(operation == CPU6502::BusOperation::ReadOpcode) // { -// printf("%04x: %02x (%d)\n", address, *value, _frameCycles); +// printf("%04x: %02x (%d)\n", address, *value, _fieldCycles); // } - _frameCycles += cycles; - switch(_frameCycles) + unsigned int line_position = (unsigned int)get_line_output_position(_fieldCycles); + const unsigned int real_time_clock_interrupt_time = (first_graphics_line + 99)*128 + first_graphics_cycle + 80; + const unsigned int display_end_interrupt_time = (first_graphics_line + 255)*128 + first_graphics_cycle + 80; + + if(line_position < real_time_clock_interrupt_time && line_position + cycles >= real_time_clock_interrupt_time) + { + update_audio(); + signal_interrupt(Interrupt::RealTimeClock); + } + else if(line_position < display_end_interrupt_time && line_position + cycles >= display_end_interrupt_time) + { + update_audio(); + signal_interrupt(Interrupt::DisplayEnd); + } + + _fieldCycles += cycles; + + switch(_fieldCycles) { case 64*128: case 196*128: update_audio(); break; - case (first_graphics_line + 100)*128: - update_audio(); - signal_interrupt(Interrupt::RealTimeClock); - break; - - case (first_graphics_line + 256)*128: - update_audio(); - signal_interrupt(Interrupt::DisplayEnd); - break; - case cycles_per_frame: update_display(); update_audio(); - _frameCycles = 0; + _fieldCycles = 0; _displayOutputPosition = 0; _audioOutputPosition = 0; _currentOutputLine = 0; @@ -368,44 +374,39 @@ inline void Machine::evaluate_interrupts() inline void Machine::update_audio() { - int difference = _frameCycles - _audioOutputPosition; - _audioOutputPosition = _frameCycles; + int difference = _fieldCycles - _audioOutputPosition; + _audioOutputPosition = _fieldCycles; _speaker.run_for_cycles((_audioOutputPositionError + difference) >> 4); _audioOutputPositionError = (_audioOutputPositionError + difference)&15; } +inline int Machine::get_line_output_position(int field_address) +{ + return field_address + (_is_odd_field ? 64 : 0); +} + inline void Machine::update_display() { const int lines_of_hsync = 3; const int end_of_hsync = lines_of_hsync * cycles_per_line; - if(_frameCycles >= end_of_hsync) + if(_fieldCycles >= end_of_hsync) { // assert sync for the first three lines of the display, with a break at the end for horizontal alignment if(_displayOutputPosition < end_of_hsync) { - // on an odd field, output a half line of level data, then 2.5 lines of sync; on an even field - // output 2.5 lines of sync, then half a line of level. -// if (_is_odd_field) -// { - _crt->output_blank(64 * crt_cycles_multiplier); - _crt->output_sync(320 * crt_cycles_multiplier); -// } -// else -// { -// _crt.output_sync(320 * crt_cycles_multiplier); -// _crt.output_blank(64 * crt_cycles_multiplier); -// } + _crt->output_blank(64 * crt_cycles_multiplier); + _crt->output_sync(320 * crt_cycles_multiplier); _is_odd_field ^= true; _displayOutputPosition = end_of_hsync; } - while(_displayOutputPosition >= end_of_hsync && _displayOutputPosition < _frameCycles) + while(_displayOutputPosition >= end_of_hsync && _displayOutputPosition < _fieldCycles) { - const int cycles_left = _frameCycles - _displayOutputPosition; + const int cycles_left = _fieldCycles - _displayOutputPosition; - const int fieldOutputPosition = _displayOutputPosition + (_is_odd_field ? 64 : 0); + const int fieldOutputPosition = get_line_output_position(_displayOutputPosition); const int current_line = fieldOutputPosition >> 7; const int line_position = fieldOutputPosition & 127; @@ -417,35 +418,33 @@ inline void Machine::update_display() if(line_position + remaining_period == 9) { -// printf("!%d!", 9); _crt->output_sync(9 * crt_cycles_multiplier); } } else { bool isBlankLine = - ((_screenMode == 3) || (_screenMode == 6)) ? - ((current_line < first_graphics_line || current_line >= first_graphics_line+248) || (((current_line - first_graphics_line)%10) > 7)) : - ((current_line < first_graphics_line || current_line >= first_graphics_line+256)); + (current_line < first_graphics_line) || + (((_screenMode == 3) || (_screenMode == 6)) ? + ((current_line >= first_graphics_line+248) || (((current_line - first_graphics_line)%10) > 7)) : + ((current_line >= first_graphics_line+256))); if(isBlankLine) { int remaining_period = std::min(128 - line_position, cycles_left); _crt->output_blank((unsigned int)remaining_period * crt_cycles_multiplier); -// printf(".[%d]", remaining_period); _displayOutputPosition += remaining_period; } else { - // there are then 15 cycles of blank, 80 cycles of pixels, and 24 further cycles of blank - if(line_position < 24) + // graphics then assume at first_graphics_cycle + if(line_position < first_graphics_cycle) { - int remaining_period = std::min(24 - line_position, cycles_left); + int remaining_period = std::min(first_graphics_cycle - line_position, cycles_left); _crt->output_blank((unsigned int)remaining_period * crt_cycles_multiplier); -// printf("/(%d)(%d)[%d]", 24 - line_position, cycles_left, remaining_period); _displayOutputPosition += remaining_period; - if(line_position + remaining_period == 24) + if(line_position + remaining_period == first_graphics_cycle) { switch(_screenMode) { @@ -457,13 +456,12 @@ inline void Machine::update_display() _crt->allocate_write_area(80 * crt_cycles_multiplier / _currentOutputDivider); _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); - if(current_line == first_graphics_line) - _startLineAddress = _startScreenAddress; + if(current_line == first_graphics_line) _startLineAddress = _startScreenAddress; _currentScreenAddress = _startLineAddress; } } - if(line_position >= 24 && line_position < 104) + if(line_position >= first_graphics_cycle && line_position < 80+first_graphics_cycle) { // determine whether the pixel clock divider has changed; if so write out the old // data and start a new run @@ -478,17 +476,14 @@ inline void Machine::update_display() { _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); _currentOutputDivider = newDivider; - _crt->allocate_write_area((size_t)((104 - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); + _crt->allocate_write_area((size_t)((80 + first_graphics_cycle - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); } - - int pixels_to_output = std::min(104 - line_position, cycles_left); + int pixels_to_output = std::min(80 + first_graphics_cycle - line_position, cycles_left); _displayOutputPosition += pixels_to_output; -// printf("<- %d ->", pixels_to_output); if(_screenMode >= 4) { - // just shifting wouldn't be enough if both if(_displayOutputPosition&1) pixels_to_output++; pixels_to_output >>= 1; } @@ -554,7 +549,7 @@ inline void Machine::update_display() #undef GetNextPixels - if(line_position >= 104) + if(line_position >= 80+first_graphics_cycle) { int pixels_to_output = std::min(128 - line_position, cycles_left); _crt->output_blank((unsigned int)pixels_to_output * crt_cycles_multiplier); @@ -563,7 +558,6 @@ inline void Machine::update_display() if(line_position + pixels_to_output == 128) { _currentOutputLine++; -// printf("\n%d: ", _currentOutputLine); if(!(_currentOutputLine&7)) { _startLineAddress += ((_screenMode < 4) ? 80 : 40)*8 - 7; @@ -758,11 +752,6 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) { get_next_tape_pulse(); -// if(_crossings[0] != Tape::Recognised) -// { -// reset_tape_input(); -// } - _crossings[0] = _crossings[1]; _crossings[1] = _crossings[2]; _crossings[2] = _crossings[3]; diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 6a2cb074e..9385be331 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -156,6 +156,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { private: inline void update_display(); + inline int get_line_output_position(int field_address); inline void update_audio(); inline void signal_interrupt(Interrupt interrupt); inline void evaluate_interrupts(); @@ -174,7 +175,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { uint16_t _startScreenAddress; // Counters related to simultaneous subsystems; - int _frameCycles, _displayOutputPosition, _audioOutputPosition, _audioOutputPositionError; + int _fieldCycles, _displayOutputPosition, _audioOutputPosition, _audioOutputPositionError; // Display generation. uint16_t _startLineAddress, _currentScreenAddress; From b261e86c6291d8ef1af150d2bafcf3e68bc4fa03 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Feb 2016 20:37:04 -0500 Subject: [PATCH 114/307] Reintroduced lateral as a parameter in CRT drawing, to simulate a rounded raster. --- Outputs/CRT/CRT.cpp | 2 +- Outputs/CRT/CRTOpenGL.cpp | 2 +- Outputs/CRT/CRTOpenGL.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index fef92d957..0d042a48c 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -44,7 +44,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di // generate timing values implied by the given arbuments _sync_capacitor_charge_threshold = ((syncCapacityLineChargeThreshold * _cycles_per_line) * 50) >> 7; const unsigned int vertical_retrace_time = scanlinesVerticalRetraceTime * _cycles_per_line; - const float halfLineWidth = (float)_height_of_display * 2.0f; + const float halfLineWidth = (float)_height_of_display * 2.5f; // creat the two flywheels unsigned int horizontal_retrace_time = scanlinesVerticalRetraceTime * _cycles_per_line; diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 23c308c66..abb31c638 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -311,7 +311,7 @@ char *CRT::get_fragment_shader() "void main(void)" "{" - "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, 10.0 * exp(-age * 2.0));" + "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, 10.0 * exp(-age * 2.0) * sin(lateralVarying));" "}" , _rgb_shader); } diff --git a/Outputs/CRT/CRTOpenGL.hpp b/Outputs/CRT/CRTOpenGL.hpp index bbf6f3df1..b3bfcd55f 100644 --- a/Outputs/CRT/CRTOpenGL.hpp +++ b/Outputs/CRT/CRTOpenGL.hpp @@ -22,6 +22,6 @@ const int CRTInputBufferBuilderHeight = 1024; const int CRTIntermediateBufferWidth = 2048; const int CRTIntermediateBufferHeight = 2048; -const int kCRTNumberOfFrames = 3; +const int kCRTNumberOfFrames = 4; #endif /* CRTOpenGL_h */ From 3ea0d04a1a4b28ee16f9650e957a49848d93817e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Feb 2016 21:47:23 -0500 Subject: [PATCH 115/307] -dealloc now blocks until all audio queue buffers are no longer playing. --- .../Mac/Clock Signal/Wrappers/AudioQueue.m | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m index f2e0235a8..80f14d7e0 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -14,8 +14,9 @@ #define AudioQueueBufferLength 256 enum { - AudioQueueCanWrite, - AudioQueueWait + AudioQueueCanProceed, + AudioQueueWait, + AudioQueueIsInvalidated }; @implementation AudioQueue @@ -25,10 +26,8 @@ enum { unsigned int _audioStreamReadPosition, _audioStreamWritePosition; int16_t _audioStream[AudioQueueStreamLength]; NSConditionLock *_writeLock; - -#ifdef DEBUG_INPUT - NSFileHandle * -#endif + BOOL _isInvalidated; + int _dequeuedCount; } @@ -62,9 +61,20 @@ enum { { memset(buffer->mAudioData, 0, buffer->mAudioDataByteSize); } - AudioQueueEnqueueBuffer(theAudioQueue, buffer, 0, NULL); - [_writeLock unlockWithCondition:AudioQueueCanWrite]; + if(!_isInvalidated) + { + [_writeLock unlockWithCondition:AudioQueueCanProceed]; + AudioQueueEnqueueBuffer(theAudioQueue, buffer, 0, NULL); + } + else + { + _dequeuedCount++; + if(_dequeuedCount == AudioQueueNumAudioBuffers) + [_writeLock unlockWithCondition:AudioQueueIsInvalidated]; + else + [_writeLock unlockWithCondition:AudioQueueCanProceed]; + } } static void audioOutputCallback( @@ -81,7 +91,7 @@ static void audioOutputCallback( if(self) { - _writeLock = [[NSConditionLock alloc] initWithCondition:AudioQueueCanWrite]; + _writeLock = [[NSConditionLock alloc] initWithCondition:AudioQueueCanProceed]; /* Describe a mono, 16bit, 44.1Khz audio format @@ -131,6 +141,13 @@ static void audioOutputCallback( - (void)dealloc { + [_writeLock lock]; + _isInvalidated = YES; + [_writeLock unlock]; + + [_writeLock lockWhenCondition:AudioQueueIsInvalidated]; + [_writeLock unlock]; + int c = AudioQueueNumAudioBuffers; while(c--) AudioQueueFreeBuffer(_audioQueue, _audioBuffers[c]); @@ -142,7 +159,7 @@ static void audioOutputCallback( { while(1) { - [_writeLock lockWhenCondition:AudioQueueCanWrite]; + [_writeLock lockWhenCondition:AudioQueueCanProceed]; if((_audioStreamReadPosition + AudioQueueStreamLength) - _audioStreamWritePosition >= lengthInSamples) { size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamWritePosition % AudioQueueStreamLength); @@ -170,7 +187,7 @@ static void audioOutputCallback( - (NSInteger)writeLockCondition { - return ((_audioStreamWritePosition - _audioStreamReadPosition) < (AudioQueueStreamLength - AudioQueueBufferLength)) ? AudioQueueCanWrite : AudioQueueWait; + return ((_audioStreamWritePosition - _audioStreamReadPosition) < (AudioQueueStreamLength - AudioQueueBufferLength)) ? AudioQueueCanProceed : AudioQueueWait; } @end From ce3c098c28362a16b00b2f2183629e7e82bb5e88 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Feb 2016 21:48:09 -0500 Subject: [PATCH 116/307] Widened beam a little, moved to a linear approximation of age across scans, cut number of stored frames. --- Outputs/CRT/CRT.cpp | 2 +- Outputs/CRT/CRTOpenGL.cpp | 9 +++++---- Outputs/CRT/CRTOpenGL.hpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 0d042a48c..5ef6b13ee 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -44,7 +44,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di // generate timing values implied by the given arbuments _sync_capacitor_charge_threshold = ((syncCapacityLineChargeThreshold * _cycles_per_line) * 50) >> 7; const unsigned int vertical_retrace_time = scanlinesVerticalRetraceTime * _cycles_per_line; - const float halfLineWidth = (float)_height_of_display * 2.5f; + const float halfLineWidth = (float)_height_of_display * 1.94f; // creat the two flywheels unsigned int horizontal_retrace_time = scanlinesVerticalRetraceTime * _cycles_per_line; diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index abb31c638..0cd2db9bd 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -236,7 +236,7 @@ char *CRT::get_vertex_shader() "out float lateralVarying;" "out vec2 shadowMaskCoordinates;" - "out float age;" + "out float alpha;" "uniform vec2 textureSize;" "uniform float timestampBase;" @@ -253,7 +253,8 @@ char *CRT::get_vertex_shader() "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);" "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" - "age = (timestampBase - timestamp) / ticksPerFrame;" + "float age = (timestampBase - timestamp) / ticksPerFrame;" + "alpha = min(10.0 * exp(-age * 2.0), 1.0);" "vec2 mappedPosition = (position - boundsOrigin) / boundsSize;" "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);" @@ -298,7 +299,7 @@ char *CRT::get_fragment_shader() "#version 150\n" "in float lateralVarying;" - "in float age;" + "in float alpha;" "in vec2 shadowMaskCoordinates;" "in vec2 srcCoordinatesVarying;" @@ -311,7 +312,7 @@ char *CRT::get_fragment_shader() "void main(void)" "{" - "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, 10.0 * exp(-age * 2.0) * sin(lateralVarying));" + "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, alpha * sin(lateralVarying));" // "}" , _rgb_shader); } diff --git a/Outputs/CRT/CRTOpenGL.hpp b/Outputs/CRT/CRTOpenGL.hpp index b3bfcd55f..bbf6f3df1 100644 --- a/Outputs/CRT/CRTOpenGL.hpp +++ b/Outputs/CRT/CRTOpenGL.hpp @@ -22,6 +22,6 @@ const int CRTInputBufferBuilderHeight = 1024; const int CRTIntermediateBufferWidth = 2048; const int CRTIntermediateBufferHeight = 2048; -const int kCRTNumberOfFrames = 4; +const int kCRTNumberOfFrames = 3; #endif /* CRTOpenGL_h */ From c7f54d649e9f4713db25379b076de54947b059e9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Feb 2016 23:21:25 -0500 Subject: [PATCH 117/307] Switched back down to two samples per pixel. Though one might do it. Need to investigate. --- OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 3134350cf..7c223f4de 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -162,7 +162,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt NSOpenGLPFADoubleBuffer, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, NSOpenGLPFASampleBuffers, 1, - NSOpenGLPFASamples, 16, + NSOpenGLPFASamples, 2, 0 }; From 570d88a876e66f90be9408dd7eb377f5109369d9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Feb 2016 23:22:06 -0500 Subject: [PATCH 118/307] Added a simple metric for measuring surprise. Which will hopefully allow me to reimplement PAL/NTSC auto-selection for the Atari 2600. --- Outputs/CRT/Flywheel.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Outputs/CRT/Flywheel.hpp b/Outputs/CRT/Flywheel.hpp index c8bf73339..710087eec 100644 --- a/Outputs/CRT/Flywheel.hpp +++ b/Outputs/CRT/Flywheel.hpp @@ -69,6 +69,8 @@ struct Flywheel } else { + _number_of_surprises++; + if(_counter < _retrace_time + (_expected_next_sync >> 1)) { _expected_next_sync = (_expected_next_sync + _standard_period + _sync_error_window) >> 1; @@ -165,6 +167,17 @@ struct Flywheel return _standard_period - _retrace_time; } + /*! + @returns the number of synchronisation events that have seemed surprising since the last time this method was called; + a low number indicates good synchronisation. + */ + inline unsigned int get_and_reset_number_of_surprises() + { + unsigned int result = _number_of_surprises; + _number_of_surprises = 0; + return result; + } + 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 @@ -174,6 +187,8 @@ struct Flywheel 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 + /* Implementation notes: From dce1649fc5932021e9ae1456f7e4caeb0c3ae397 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Feb 2016 23:23:49 -0500 Subject: [PATCH 119/307] Commuted some camelCase variables into more standard C++ underscore names; decided the best fix for the graphics output is to break it down into much simpler chunks. output_pixels is currently a placeholder that simply outputs blank so the current effect is that all data is missing but it should be easy enough now to put pixels back in. --- Machines/Electron/Electron.cpp | 446 ++++++++++++++++++++++----------- Machines/Electron/Electron.hpp | 21 +- 2 files changed, 308 insertions(+), 159 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index be4ef6358..07b64921c 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -22,7 +22,8 @@ static const int first_graphics_line = 38; static const int first_graphics_cycle = 33; Machine::Machine() : - _interruptControl(0), + _interrupt_control(0), + _interrupt_status(Interrupt::PowerOnReset), _fieldCycles(0), _displayOutputPosition(0), _audioOutputPosition(0), @@ -39,9 +40,8 @@ Machine::Machine() : "}"); _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 - memset(_keyStates, 0, sizeof(_keyStates)); + memset(_key_states, 0, sizeof(_key_states)); memset(_palette, 0xf, sizeof(_palette)); - _interruptStatus = 0x02; for(int c = 0; c < 16; c++) memset(_roms[c], 0xff, 16384); @@ -49,10 +49,6 @@ Machine::Machine() : _tape.set_delegate(this); } -Machine::~Machine() -{ -} - unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { unsigned int cycles = 1; @@ -82,7 +78,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // for the entire frame, RAM is accessible only on odd cycles; in modes below 4 // it's also accessible only outside of the pixel regions cycles += (_fieldCycles&1)^1; - if(_screenMode < 4) + if(_screen_mode < 4) { const int current_line = _fieldCycles >> 7; const int line_position = _fieldCycles & 127; @@ -104,12 +100,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x0: if(isReadOperation(operation)) { - *value = _interruptStatus; - _interruptStatus &= ~0x02; + *value = _interrupt_status; + _interrupt_status &= ~0x02; } else { - _interruptControl = *value; + _interrupt_control = *value; evaluate_interrupts(); } break; @@ -143,9 +139,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin const uint8_t interruptDisable = (*value)&0xf0; if( interruptDisable ) { - if( interruptDisable&0x10 ) _interruptStatus &= ~Interrupt::DisplayEnd; - if( interruptDisable&0x20 ) _interruptStatus &= ~Interrupt::RealTimeClock; - if( interruptDisable&0x40 ) _interruptStatus &= ~Interrupt::HighToneDetect; + if( interruptDisable&0x10 ) _interrupt_status &= ~Interrupt::DisplayEnd; + if( interruptDisable&0x20 ) _interrupt_status &= ~Interrupt::RealTimeClock; + if( interruptDisable&0x40 ) _interrupt_status &= ~Interrupt::HighToneDetect; evaluate_interrupts(); // TODO: NMI (?) @@ -159,9 +155,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // _activeRom = (Electron::ROMSlot)(nextROM&0x0e); // printf("%d -> Paged %d\n", nextROM, _activeRom); // } - if(((_activeRom&12) != 8) || (nextROM&8)) + if(((_active_rom&12) != 8) || (nextROM&8)) { - _activeRom = (Electron::ROMSlot)nextROM; + _active_rom = (Electron::ROMSlot)nextROM; } // else // { @@ -185,11 +181,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // update screen mode uint8_t new_screen_mode = ((*value) >> 3)&7; if(new_screen_mode == 7) new_screen_mode = 4; - if(new_screen_mode != _screenMode) + if(new_screen_mode != _screen_mode) { update_display(); - _screenMode = new_screen_mode; - switch(_screenMode) + _screen_mode = new_screen_mode; + switch(_screen_mode) { case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break; case 3: _screenModeBaseAddress = 0x4000; break; @@ -262,18 +258,18 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { if(isReadOperation(operation)) { - switch(_activeRom) + switch(_active_rom) { case ROMSlotKeyboard: case ROMSlotKeyboard+1: *value = 0xf0; for(int address_line = 0; address_line < 14; address_line++) { - if(!(address&(1 << address_line))) *value |= _keyStates[address_line]; + if(!(address&(1 << address_line))) *value |= _key_states[address_line]; } break; default: - *value = _roms[_activeRom][address & 16383]; + *value = _roms[_active_rom][address & 16383]; break; } } @@ -349,27 +345,27 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) inline void Machine::signal_interrupt(Electron::Interrupt interrupt) { - _interruptStatus |= interrupt; + _interrupt_status |= interrupt; evaluate_interrupts(); } void Machine::tape_did_change_interrupt_status(Tape *tape) { - _interruptStatus = (_interruptStatus & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | _tape.get_interrupt_status(); + _interrupt_status = (_interrupt_status & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | _tape.get_interrupt_status(); evaluate_interrupts(); } inline void Machine::evaluate_interrupts() { - if(_interruptStatus & _interruptControl) + if(_interrupt_status & _interrupt_control) { - _interruptStatus |= 1; + _interrupt_status |= 1; } else { - _interruptStatus &= ~1; + _interrupt_status &= ~1; } - set_irq_line(_interruptStatus & 1); + set_irq_line(_interrupt_status & 1); } inline void Machine::update_audio() @@ -385,17 +381,182 @@ inline int Machine::get_line_output_position(int field_address) return field_address + (_is_odd_field ? 64 : 0); } +inline void Machine::reset_pixel_output() +{ + display_x = 0; + display_y = 0; + _startLineAddress = _startScreenAddress; +} + +inline void Machine::output_pixels(int number_of_pixels) +{ + if(number_of_pixels) + { + _crt->output_blank((unsigned int)number_of_pixels * crt_cycles_multiplier); + } +} + +inline void Machine::end_pixel_output() +{ +} + +inline void Machine::update_pixels_to_position(int x, int y) +{ + while((display_x < x) || (display_y < y)) + { + if(display_x < first_graphics_cycle) + { + display_x++; + + if(display_x == first_graphics_cycle) + { + _crt->output_sync(9 * crt_cycles_multiplier); + _crt->output_blank((first_graphics_cycle - 9) * crt_cycles_multiplier); + } + continue; + } + + if(display_x < first_graphics_cycle+80) + { + int cycles_to_output = (display_y < y) ? 80 + first_graphics_cycle - display_x : std::min(80, x - display_x); + output_pixels(cycles_to_output); + display_x += cycles_to_output; + + if(display_x == first_graphics_cycle+80) + end_pixel_output(); + continue; + } + + display_x++; + if(display_x == 128) + { + _crt->output_blank((128 - 80 - first_graphics_cycle) * crt_cycles_multiplier); + display_x = 0; + display_y++; + } + } + +/* while(_displayOutputPosition < end_of_graphics && _displayOutputPosition) + { + + int displayOffset = _displayOutputPosition - end_of_graphics; + int cyclesRemaining = _fieldCycles - _displayOutputPosition; +// int current_line = displayOffset >> 7; + int current_pixel = displayOffset & 127; + + if(current_pixel < 9) + { + if(cyclesRemaining > 9) + { + _crt->output_sync(9 * crt_cycles_multiplier); + cyclesRemaining -= 9; + _displayOutputPosition += 9; + } + else return; + } + + int line_remainder = std::min(119, cyclesRemaining); + _crt->output_blank((unsigned int)line_remainder * crt_cycles_multiplier); + _displayOutputPosition += line_remainder; + current_pixel += line_remainder; + if(current_pixel < 128) return; + }*/ +} + inline void Machine::update_display() { - const int lines_of_hsync = 3; - const int end_of_hsync = lines_of_hsync * cycles_per_line; + /* - if(_fieldCycles >= end_of_hsync) + Odd field: + + |--S--| + |--S--| + |-S-B-| + |--B--| + |--P--| + |--B--| + |-B- + + (2.5 lines of sync; half a line of blank; full blanks; pixels; full blanks; half blank) + + Even field: + + -S-| + |--S--| + |--S--| + |--B--| + |--P--| + |--B--| + + (2.5 lines of sync; full blanks; pixels; full blanks) + + So: + + Top: + 2.5 lines of sync + if odd then half a line of blank + full blanks + Pixels + Bottom: + full blanks + if odd then half a line of blank + + */ + + const int end_of_top = (first_graphics_line * cycles_per_line) + (_is_odd_field ? 64 : 0); + const int end_of_graphics = ((first_graphics_line + 256) * cycles_per_line) + (_is_odd_field ? 64 : 0); + + // does the top region need to be output? + if(_displayOutputPosition < end_of_top && _fieldCycles >= end_of_top) + { + _crt->output_sync(320 * crt_cycles_multiplier); + if(_is_odd_field) _crt->output_blank(64 * crt_cycles_multiplier); + + for(int y = 3; y < first_graphics_line; y++) + { + _crt->output_sync(9 * crt_cycles_multiplier); + _crt->output_blank(119 * crt_cycles_multiplier); + } + + _displayOutputPosition = end_of_top; + + reset_pixel_output(); + } + + // is this the pixel region? + if(_displayOutputPosition >= end_of_top && _displayOutputPosition < end_of_graphics) + { + int final_position = std::min(_fieldCycles, end_of_top + 256 * 128) - end_of_top; + int final_line = final_position >> 7; + int final_pixel = final_position & 127; + update_pixels_to_position(final_pixel, final_line); + _displayOutputPosition = final_position + end_of_top; + } + + // is this the bottom region? + if(_displayOutputPosition < end_of_graphics && _fieldCycles > end_of_graphics) + { + for(int y = first_graphics_line+256; y < 312; y++) + { + _crt->output_sync(9 * crt_cycles_multiplier); + _crt->output_blank(119 * crt_cycles_multiplier); + } + _displayOutputPosition = end_of_graphics; + + if(_is_odd_field) _crt->output_blank(64 * crt_cycles_multiplier); + + _is_odd_field ^= true; + } + + // no? Then let's do some pixels + +/* if(_fieldCycles >= end_of_hsync) { // assert sync for the first three lines of the display, with a break at the end for horizontal alignment if(_displayOutputPosition < end_of_hsync) { - _crt->output_blank(64 * crt_cycles_multiplier); + _crt->output_sync(9 * crt_cycles_multiplier); + _crt->output_blank(55 * crt_cycles_multiplier); _crt->output_sync(320 * crt_cycles_multiplier); _is_odd_field ^= true; @@ -425,158 +586,137 @@ inline void Machine::update_display() { bool isBlankLine = (current_line < first_graphics_line) || - (((_screenMode == 3) || (_screenMode == 6)) ? + (((_screen_mode == 3) || (_screen_mode == 6)) ? ((current_line >= first_graphics_line+248) || (((current_line - first_graphics_line)%10) > 7)) : ((current_line >= first_graphics_line+256))); + bool isBlankPeriod = + (line_position < first_graphics_cycle) || (line_position >= 80+first_graphics_cycle); - if(isBlankLine) + if(isBlankLine || isBlankPeriod) { - int remaining_period = std::min(128 - line_position, cycles_left); + if(_currentLine) + { + _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); + _currentLine = _writePointer = nullptr; + } + + int target = (isBlankLine || (line_position > first_graphics_cycle)) ? 128 : first_graphics_cycle; + int remaining_period = std::min(target - line_position, cycles_left); _crt->output_blank((unsigned int)remaining_period * crt_cycles_multiplier); _displayOutputPosition += remaining_period; + + if(line_position + remaining_period == 128) + { + _currentOutputLine++; + if(!(_currentOutputLine&7)) + { + _startLineAddress += ((_screen_mode < 4) ? 80 : 40)*8 - 7; + } + else + _startLineAddress++; + } } else { - // graphics then assume at first_graphics_cycle - if(line_position < first_graphics_cycle) + if(line_position == first_graphics_cycle) { - int remaining_period = std::min(first_graphics_cycle - line_position, cycles_left); - _crt->output_blank((unsigned int)remaining_period * crt_cycles_multiplier); - _displayOutputPosition += remaining_period; - - if(line_position + remaining_period == first_graphics_cycle) - { - switch(_screenMode) - { - case 0: case 3: _currentOutputDivider = 1; break; - case 1: case 4: case 6: _currentOutputDivider = 2; break; - case 2: case 5: _currentOutputDivider = 4; break; - } - - _crt->allocate_write_area(80 * crt_cycles_multiplier / _currentOutputDivider); - _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); - - if(current_line == first_graphics_line) _startLineAddress = _startScreenAddress; - _currentScreenAddress = _startLineAddress; - } + if(current_line == first_graphics_line) _startLineAddress = _startScreenAddress; + _currentScreenAddress = _startLineAddress; } - if(line_position >= first_graphics_cycle && line_position < 80+first_graphics_cycle) + // determine the pixel clock + unsigned int newDivider = 0; + switch(_screen_mode) { - // determine whether the pixel clock divider has changed; if so write out the old - // data and start a new run - unsigned int newDivider = 0; - switch(_screenMode) - { - case 0: case 3: newDivider = 1; break; - case 1: case 4: case 6: newDivider = 2; break; - case 2: case 5: newDivider = 4; break; - } - if(newDivider != _currentOutputDivider && _currentLine) + case 0: case 3: newDivider = 1; break; + case 1: case 4: case 6: newDivider = 2; break; + case 2: case 5: newDivider = 4; break; + } + + // if the clock has changed or we don't yet have a write pointer, get one + if((newDivider != _currentOutputDivider) || !_currentLine) + { + if(_currentLine) { _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); - _currentOutputDivider = newDivider; - _crt->allocate_write_area((size_t)((80 + first_graphics_cycle - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); - _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); } - int pixels_to_output = std::min(80 + first_graphics_cycle - line_position, cycles_left); - _displayOutputPosition += pixels_to_output; - if(_screenMode >= 4) - { - if(_displayOutputPosition&1) pixels_to_output++; - pixels_to_output >>= 1; - } + _currentOutputDivider = newDivider; + _crt->allocate_write_area((size_t)((80 + first_graphics_cycle - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); + _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); + } + + // determine how many pixels to write + int pixels_to_output = std::min(80 + first_graphics_cycle - line_position, cycles_left); + _displayOutputPosition += pixels_to_output; + if(_screen_mode >= 4) + { + if(_displayOutputPosition&1) pixels_to_output++; + pixels_to_output >>= 1; + } #define GetNextPixels() \ if(_currentScreenAddress&32768)\ {\ - _currentScreenAddress = _screenModeBaseAddress + (_currentScreenAddress&32767);\ + _currentScreenAddress = (_screenModeBaseAddress + _currentScreenAddress)&32767;\ }\ uint8_t pixels = _ram[_currentScreenAddress];\ _currentScreenAddress = _currentScreenAddress+8 - if(pixels_to_output && _writePointer) - { - switch(_screenMode) - { - default: - case 0: case 3: case 4: case 6: - while(pixels_to_output--) - { - GetNextPixels(); - - _writePointer[0] = _palette[(pixels&0x80) >> 4]; - _writePointer[1] = _palette[(pixels&0x40) >> 3]; - _writePointer[2] = _palette[(pixels&0x20) >> 2]; - _writePointer[3] = _palette[(pixels&0x10) >> 1]; - _writePointer[4] = _palette[(pixels&0x08) >> 0]; - _writePointer[5] = _palette[(pixels&0x04) << 1]; - _writePointer[6] = _palette[(pixels&0x02) << 2]; - _writePointer[7] = _palette[(pixels&0x01) << 3]; - - _writePointer += 8; - } - break; - - case 1: - case 5: - while(pixels_to_output--) - { - GetNextPixels(); - - _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x08) >> 2)]; - _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x04) >> 1)]; - _writePointer[2] = _palette[((pixels&0x20) >> 2) | ((pixels&0x02) >> 0)]; - _writePointer[3] = _palette[((pixels&0x10) >> 1) | ((pixels&0x01) << 1)]; - - _writePointer += 4; - } - break; - - case 2: - while(pixels_to_output--) - { - GetNextPixels(); - _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1)]; - _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x10) >> 2) | ((pixels&0x04) >> 1) | ((pixels&0x01) >> 0)]; - _writePointer += 2; - } - break; - } - } - } - -#undef GetNextPixels - - if(line_position >= 80+first_graphics_cycle) + if(pixels_to_output) { - int pixels_to_output = std::min(128 - line_position, cycles_left); - _crt->output_blank((unsigned int)pixels_to_output * crt_cycles_multiplier); - _displayOutputPosition += pixels_to_output; - - if(line_position + pixels_to_output == 128) + switch(_screen_mode) { - _currentOutputLine++; - if(!(_currentOutputLine&7)) - { - _startLineAddress += ((_screenMode < 4) ? 80 : 40)*8 - 7; - } - else - _startLineAddress++; + default: + case 0: case 3: case 4: case 6: + while(pixels_to_output--) + { + GetNextPixels(); - if(_writePointer) - _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); - else - _crt->output_data(80 * crt_cycles_multiplier, _currentOutputDivider); - _currentLine = nullptr; - _writePointer = nullptr; + _writePointer[0] = _palette[(pixels&0x80) >> 4]; + _writePointer[1] = _palette[(pixels&0x40) >> 3]; + _writePointer[2] = _palette[(pixels&0x20) >> 2]; + _writePointer[3] = _palette[(pixels&0x10) >> 1]; + _writePointer[4] = _palette[(pixels&0x08) >> 0]; + _writePointer[5] = _palette[(pixels&0x04) << 1]; + _writePointer[6] = _palette[(pixels&0x02) << 2]; + _writePointer[7] = _palette[(pixels&0x01) << 3]; + + _writePointer += 8; + } + break; + + case 1: + case 5: + while(pixels_to_output--) + { + GetNextPixels(); + + _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x08) >> 2)]; + _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x04) >> 1)]; + _writePointer[2] = _palette[((pixels&0x20) >> 2) | ((pixels&0x02) >> 0)]; + _writePointer[3] = _palette[((pixels&0x10) >> 1) | ((pixels&0x01) << 1)]; + + _writePointer += 4; + } + break; + + case 2: + while(pixels_to_output--) + { + GetNextPixels(); + _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1)]; + _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x10) >> 2) | ((pixels&0x04) >> 1) | ((pixels&0x01) >> 0)]; + _writePointer += 2; + } + break; } } +#undef GetNextPixels } } } - } + }*/ } void Machine::set_key_state(Key key, bool isPressed) @@ -588,9 +728,9 @@ void Machine::set_key_state(Key key, bool isPressed) else { if(isPressed) - _keyStates[key >> 4] |= key&0xf; + _key_states[key >> 4] |= key&0xf; else - _keyStates[key >> 4] &= ~(key&0xf); + _key_states[key >> 4] &= ~(key&0xf); } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 9385be331..32cb1ea7b 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -31,6 +31,7 @@ enum ROMSlot: uint8_t { }; enum Interrupt: uint8_t { + PowerOnReset = 0x02, DisplayEnd = 0x04, RealTimeClock = 0x08, ReceiveDataFull = 0x10, @@ -137,7 +138,6 @@ class Machine: public CPU6502::Processor, Tape::Delegate { public: Machine(); - ~Machine(); unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); @@ -156,26 +156,35 @@ class Machine: public CPU6502::Processor, Tape::Delegate { private: inline void update_display(); + inline void update_pixels_to_position(int x, int y); + inline void output_pixels(int number_of_pixels); + inline void end_pixel_output(); + inline void reset_pixel_output(); + inline int get_line_output_position(int field_address); inline void update_audio(); inline void signal_interrupt(Interrupt interrupt); inline void evaluate_interrupts(); + // Pixel tracker; + int display_x, display_y; + // Things that directly constitute the memory map. uint8_t _roms[16][16384]; uint8_t _os[16384], _ram[32768]; // Things affected by registers, explicitly or otherwise. - uint8_t _interruptStatus, _interruptControl; + uint8_t _interrupt_status, _interrupt_control; uint8_t _palette[16]; - uint8_t _keyStates[14]; - ROMSlot _activeRom; - uint8_t _screenMode; + uint8_t _key_states[14]; + ROMSlot _active_rom; + uint8_t _screen_mode; uint16_t _screenModeBaseAddress; uint16_t _startScreenAddress; // Counters related to simultaneous subsystems; - int _fieldCycles, _displayOutputPosition, _audioOutputPosition, _audioOutputPositionError; + int _fieldCycles, _displayOutputPosition; + int _audioOutputPosition, _audioOutputPositionError; // Display generation. uint16_t _startLineAddress, _currentScreenAddress; From 1954f7bcbd0a1e94f80ebe1c9adadb800ceb18ff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Feb 2016 23:41:09 -0500 Subject: [PATCH 120/307] Got back to a stable image, at least when staying in a mode. Which is what I was trying to fix. Hmmm. --- Machines/Electron/Electron.cpp | 78 +++++++++++++++++++++++++++++----- Machines/Electron/Electron.hpp | 6 ++- Outputs/CRT/CRT.cpp | 11 ++++- Outputs/CRT/CRT.hpp | 7 +++ 4 files changed, 87 insertions(+), 15 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 07b64921c..17d094518 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -38,7 +38,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); - _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 +// _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 memset(_key_states, 0, sizeof(_key_states)); memset(_palette, 0xf, sizeof(_palette)); @@ -388,16 +388,67 @@ inline void Machine::reset_pixel_output() _startLineAddress = _startScreenAddress; } -inline void Machine::output_pixels(int number_of_pixels) +inline void Machine::output_pixels(int start_x, int number_of_pixels) { if(number_of_pixels) { - _crt->output_blank((unsigned int)number_of_pixels * crt_cycles_multiplier); + unsigned int newDivider = 0; + switch(_screen_mode) + { + case 0: case 3: newDivider = 1; break; + case 1: case 4: case 6: newDivider = 2; break; + case 2: case 5: newDivider = 4; break; + } + bool is40Column = (_screen_mode > 3); + + if(!_writePointer || newDivider != _currentOutputDivider) + { + _currentOutputDivider = newDivider; + end_pixel_output(); + _crt->allocate_write_area(640 / newDivider); + _currentLine = _writePointer = _crt->get_write_target_for_buffer(0); + } + + if(is40Column) + { + number_of_pixels = ((start_x + number_of_pixels) >> 1) - (start_x >> 1); + } + + switch(_screen_mode) + { + default: + case 0: case 3: case 4: case 6: + while(number_of_pixels--) + { + _writePointer[0] = 7; + _writePointer += 8; + } + break; + + case 1: case 5: + while(number_of_pixels--) + { + _writePointer += 4; + } + break; + + case 2: + while(number_of_pixels--) + { + _writePointer += 2; + } + break; + } } } inline void Machine::end_pixel_output() { + if(_currentLine != nullptr) + { + _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); + _writePointer = _currentLine = nullptr; + } } inline void Machine::update_pixels_to_position(int x, int y) @@ -419,11 +470,13 @@ inline void Machine::update_pixels_to_position(int x, int y) if(display_x < first_graphics_cycle+80) { int cycles_to_output = (display_y < y) ? 80 + first_graphics_cycle - display_x : std::min(80, x - display_x); - output_pixels(cycles_to_output); + output_pixels(display_x, cycles_to_output); display_x += cycles_to_output; if(display_x == first_graphics_cycle+80) + { end_pixel_output(); + } continue; } @@ -475,13 +528,13 @@ inline void Machine::update_display() |--B--| |--P--| |--B--| - |-B- + (2.5 lines of sync; half a line of blank; full blanks; pixels; full blanks; half blank) Even field: - -S-| + |-B-S-| |--S--| |--S--| |--B--| @@ -493,13 +546,13 @@ inline void Machine::update_display() So: Top: + if even then half a line of blank 2.5 lines of sync if odd then half a line of blank full blanks Pixels Bottom: full blanks - if odd then half a line of blank */ @@ -509,6 +562,8 @@ inline void Machine::update_display() // does the top region need to be output? if(_displayOutputPosition < end_of_top && _fieldCycles >= end_of_top) { +// printf("[1] %d / %d\n", _crt->get_field_cycle() >> 10, (_crt->get_field_cycle() >> 3)&127); + if(!_is_odd_field) _crt->output_blank(64 * crt_cycles_multiplier); _crt->output_sync(320 * crt_cycles_multiplier); if(_is_odd_field) _crt->output_blank(64 * crt_cycles_multiplier); @@ -517,6 +572,7 @@ inline void Machine::update_display() _crt->output_sync(9 * crt_cycles_multiplier); _crt->output_blank(119 * crt_cycles_multiplier); } +// printf("[2] %d / %d\n", _crt->get_field_cycle() >> 10, (_crt->get_field_cycle() >> 3)&127); _displayOutputPosition = end_of_top; @@ -534,17 +590,17 @@ inline void Machine::update_display() } // is this the bottom region? - if(_displayOutputPosition < end_of_graphics && _fieldCycles > end_of_graphics) + if(_displayOutputPosition >= end_of_graphics && _displayOutputPosition < cycles_per_frame) { +// printf("[3] %d / %d\n", _crt->get_field_cycle() >> 10, (_crt->get_field_cycle() >> 3)&127); for(int y = first_graphics_line+256; y < 312; y++) { _crt->output_sync(9 * crt_cycles_multiplier); _crt->output_blank(119 * crt_cycles_multiplier); } - _displayOutputPosition = end_of_graphics; - - if(_is_odd_field) _crt->output_blank(64 * crt_cycles_multiplier); + _displayOutputPosition = cycles_per_frame; +// printf("[4] %d / %d\n", _crt->get_field_cycle() >> 10, (_crt->get_field_cycle() >> 3)&127); _is_odd_field ^= true; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 32cb1ea7b..58054b3b7 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -157,7 +157,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { inline void update_display(); inline void update_pixels_to_position(int x, int y); - inline void output_pixels(int number_of_pixels); + inline void output_pixels(int start_x, int number_of_pixels); inline void end_pixel_output(); inline void reset_pixel_output(); @@ -189,9 +189,11 @@ class Machine: public CPU6502::Processor, Tape::Delegate { // Display generation. uint16_t _startLineAddress, _currentScreenAddress; int _currentOutputLine; + bool _is_odd_field; + + // CRT output unsigned int _currentOutputDivider; uint8_t *_currentLine, *_writePointer; - bool _is_odd_field; // Tape. Tape _tape; diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 5ef6b13ee..13addb82e 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -165,7 +165,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi hsync_requested = false; vsync_requested = false; - uint8_t *next_run = (is_output_run && next_run_length) ? _run_builders[_run_write_pointer]->get_next_input_run() : nullptr; + uint8_t *next_run = ((is_output_run && next_run_length) && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()) ? _run_builders[_run_write_pointer]->get_next_input_run() : nullptr; int lengthMask = (_horizontal_flywheel->is_in_retrace() ? kRetraceXMask : 0) | (_vertical_flywheel->is_in_retrace() ? kRetraceYMask : 0); #define position_x(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfPosition + 0]) @@ -241,6 +241,13 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi _run_write_pointer = (_run_write_pointer + 1)%kCRTNumberOfFrames; _run_builders[_run_write_pointer]->reset(); + + static int fc = 0; + fc++; + if(!(fc&15)) + { + printf("H misses %d; v misses %d\n", _horizontal_flywheel->get_and_reset_number_of_surprises(), _vertical_flywheel->get_and_reset_number_of_surprises()); + } } } } @@ -249,7 +256,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi void CRT::output_scan() { - _next_scan ^= 1; +// _next_scan ^= 1; Scan *scan = &_scans[_next_scan]; bool this_is_sync = (scan->type == Type::Sync); diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index bfe6463ee..c4979836a 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -214,6 +214,13 @@ class CRT { _visible_area = visible_area; } +#ifdef DEBUG + inline uint32_t get_field_cycle() + { + return _run_builders[_run_write_pointer]->duration / _time_multiplier; + } +#endif + private: CRT(); void allocate_buffers(unsigned int number, va_list sizes); From a3432120f375cfcd851971ec5c6f1fe1435ce65f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 19 Feb 2016 23:57:57 -0500 Subject: [PATCH 121/307] =?UTF-8?q?Fixed=20synchronisation=20=E2=80=94=20m?= =?UTF-8?q?y=20two=20fields=20weren't=20of=20equal=20length,=20I=20wasn't?= =?UTF-8?q?=20breaking=20calls=20to=20the=20CRT=20upon=2040/80=20transitio?= =?UTF-8?q?ns,=20I=20had=20the=20wrong=20maximum=20run=20length.=20Now=20I?= =?UTF-8?q?=20just=20need=20to=20put=20real=20pixels=20back=20in.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Machines/Electron/Electron.cpp | 19 ++++++++++++++----- Machines/Electron/Electron.hpp | 1 + 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 17d094518..57bcd4fa0 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -20,6 +20,7 @@ static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_p static const int first_graphics_line = 38; static const int first_graphics_cycle = 33; +static const int last_graphics_cycle = 80 + first_graphics_cycle; Machine::Machine() : _interrupt_control(0), @@ -401,10 +402,11 @@ inline void Machine::output_pixels(int start_x, int number_of_pixels) } bool is40Column = (_screen_mode > 3); - if(!_writePointer || newDivider != _currentOutputDivider) + if(!_writePointer || newDivider != _currentOutputDivider || _isOutputting40Columns != is40Column) { - _currentOutputDivider = newDivider; end_pixel_output(); + _currentOutputDivider = newDivider; + _isOutputting40Columns = is40Column; _crt->allocate_write_area(640 / newDivider); _currentLine = _writePointer = _crt->get_write_target_for_buffer(0); } @@ -453,6 +455,7 @@ inline void Machine::end_pixel_output() inline void Machine::update_pixels_to_position(int x, int y) { + static unsigned int end; while((display_x < x) || (display_y < y)) { if(display_x < first_graphics_cycle) @@ -463,19 +466,25 @@ inline void Machine::update_pixels_to_position(int x, int y) { _crt->output_sync(9 * crt_cycles_multiplier); _crt->output_blank((first_graphics_cycle - 9) * crt_cycles_multiplier); + end = _crt->get_field_cycle(); } continue; } - if(display_x < first_graphics_cycle+80) + if(display_x < last_graphics_cycle) { - int cycles_to_output = (display_y < y) ? 80 + first_graphics_cycle - display_x : std::min(80, x - display_x); + int cycles_to_output = (display_y < y) ? last_graphics_cycle - display_x : std::min(last_graphics_cycle - display_x, x - display_x); output_pixels(display_x, cycles_to_output); display_x += cycles_to_output; - if(display_x == first_graphics_cycle+80) + if(display_x == last_graphics_cycle) { end_pixel_output(); + + if(_crt->get_field_cycle() - end != 640) + { + printf("!!!\n"); + } } continue; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 58054b3b7..e7f13857d 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -193,6 +193,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { // CRT output unsigned int _currentOutputDivider; + bool _isOutputting40Columns; uint8_t *_currentLine, *_writePointer; // Tape. From 518c13434855b9681c8f6517a531e1fd9e55a51c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Feb 2016 00:11:23 -0500 Subject: [PATCH 122/307] Blank lines are missing (if the double negative can be forgiven) but the framebuffer is otherwise seemingly working well again. --- Machines/Electron/Electron.cpp | 58 ++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 57bcd4fa0..b46ef612a 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -386,7 +386,8 @@ inline void Machine::reset_pixel_output() { display_x = 0; display_y = 0; - _startLineAddress = _startScreenAddress; + _currentScreenAddress = _startLineAddress = _startScreenAddress; + _currentOutputLine = 0; } inline void Machine::output_pixels(int start_x, int number_of_pixels) @@ -416,13 +417,30 @@ inline void Machine::output_pixels(int start_x, int number_of_pixels) number_of_pixels = ((start_x + number_of_pixels) >> 1) - (start_x >> 1); } +#define GetNextPixels() \ + if(_currentScreenAddress&32768)\ + {\ + _currentScreenAddress = (_screenModeBaseAddress + _currentScreenAddress)&32767;\ + }\ + uint8_t pixels = _ram[_currentScreenAddress];\ + _currentScreenAddress = _currentScreenAddress+8 switch(_screen_mode) { default: case 0: case 3: case 4: case 6: while(number_of_pixels--) { - _writePointer[0] = 7; + GetNextPixels(); + + _writePointer[0] = _palette[(pixels&0x80) >> 4]; + _writePointer[1] = _palette[(pixels&0x40) >> 3]; + _writePointer[2] = _palette[(pixels&0x20) >> 2]; + _writePointer[3] = _palette[(pixels&0x10) >> 1]; + _writePointer[4] = _palette[(pixels&0x08) >> 0]; + _writePointer[5] = _palette[(pixels&0x04) << 1]; + _writePointer[6] = _palette[(pixels&0x02) << 2]; + _writePointer[7] = _palette[(pixels&0x01) << 3]; + _writePointer += 8; } break; @@ -430,6 +448,13 @@ inline void Machine::output_pixels(int start_x, int number_of_pixels) case 1: case 5: while(number_of_pixels--) { + GetNextPixels(); + + _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x08) >> 2)]; + _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x04) >> 1)]; + _writePointer[2] = _palette[((pixels&0x20) >> 2) | ((pixels&0x02) >> 0)]; + _writePointer[3] = _palette[((pixels&0x10) >> 1) | ((pixels&0x01) << 1)]; + _writePointer += 4; } break; @@ -437,11 +462,17 @@ inline void Machine::output_pixels(int start_x, int number_of_pixels) case 2: while(number_of_pixels--) { + GetNextPixels(); + + _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1)]; + _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x10) >> 2) | ((pixels&0x04) >> 1) | ((pixels&0x01) >> 0)]; + _writePointer += 2; } break; } } +#undef GetNextPixels } inline void Machine::end_pixel_output() @@ -467,6 +498,7 @@ inline void Machine::update_pixels_to_position(int x, int y) _crt->output_sync(9 * crt_cycles_multiplier); _crt->output_blank((first_graphics_cycle - 9) * crt_cycles_multiplier); end = _crt->get_field_cycle(); + _currentScreenAddress = _startLineAddress; } continue; } @@ -481,10 +513,10 @@ inline void Machine::update_pixels_to_position(int x, int y) { end_pixel_output(); - if(_crt->get_field_cycle() - end != 640) - { - printf("!!!\n"); - } +// if(_crt->get_field_cycle() - end != 640) +// { +// printf("!!!\n"); +// } } continue; } @@ -495,6 +527,14 @@ inline void Machine::update_pixels_to_position(int x, int y) _crt->output_blank((128 - 80 - first_graphics_cycle) * crt_cycles_multiplier); display_x = 0; display_y++; + + _startLineAddress++; + _currentOutputLine++; + if(_currentOutputLine == 8) + { + _currentOutputLine = 0; + _startLineAddress += ((_screen_mode > 3) ? 40 : 80) * 8 - 8; + } } } @@ -572,7 +612,11 @@ inline void Machine::update_display() if(_displayOutputPosition < end_of_top && _fieldCycles >= end_of_top) { // printf("[1] %d / %d\n", _crt->get_field_cycle() >> 10, (_crt->get_field_cycle() >> 3)&127); - if(!_is_odd_field) _crt->output_blank(64 * crt_cycles_multiplier); + if(!_is_odd_field) + { + _crt->output_sync(9 * crt_cycles_multiplier); + _crt->output_blank(55 * crt_cycles_multiplier); + } _crt->output_sync(320 * crt_cycles_multiplier); if(_is_odd_field) _crt->output_blank(64 * crt_cycles_multiplier); From da361be3eec146c37cc8a17542e2a3a07f1be755 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Feb 2016 00:13:43 -0500 Subject: [PATCH 123/307] This might be more accurate? Probably not. Who knows? --- Machines/Electron/Electron.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index b46ef612a..dded651cc 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -533,7 +533,7 @@ inline void Machine::update_pixels_to_position(int x, int y) if(_currentOutputLine == 8) { _currentOutputLine = 0; - _startLineAddress += ((_screen_mode > 3) ? 40 : 80) * 8 - 8; + _startLineAddress = _currentScreenAddress - 7; } } } From 68992a62f93d905414e43ab0ed0586a8d1107033 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Feb 2016 00:16:07 -0500 Subject: [PATCH 124/307] Switched to a subtler to out-of-band syncs. --- Outputs/CRT/Flywheel.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/Flywheel.hpp b/Outputs/CRT/Flywheel.hpp index 710087eec..5e495b614 100644 --- a/Outputs/CRT/Flywheel.hpp +++ b/Outputs/CRT/Flywheel.hpp @@ -73,11 +73,11 @@ struct Flywheel if(_counter < _retrace_time + (_expected_next_sync >> 1)) { - _expected_next_sync = (_expected_next_sync + _standard_period + _sync_error_window) >> 1; + _expected_next_sync++; } else { - _expected_next_sync = (_expected_next_sync + _standard_period - _sync_error_window) >> 1; + _expected_next_sync--; } } } From 00f414d7575704ca8e5b20aad757afde773bd016 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Feb 2016 17:32:51 -0500 Subject: [PATCH 125/307] Made an attempt to reintroduce interlacing and screen modes with gaps. Something's wrong with synchronisation though, so most likely I'm outputting a bad signal. --- Machines/Electron/Electron.cpp | 292 +++++---------------------------- Machines/Electron/Electron.hpp | 2 +- Outputs/CRT/CRT.hpp | 10 ++ Outputs/CRT/Flywheel.hpp | 6 +- 4 files changed, 56 insertions(+), 254 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index dded651cc..363d5b030 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -394,6 +394,16 @@ inline void Machine::output_pixels(int start_x, int number_of_pixels) { if(number_of_pixels) { + if( + ((_screen_mode == 3) || (_screen_mode == 6)) && + (((display_y%10) >= 8) || (display_y >= 250)) + ) + { + end_pixel_output(); + _crt->output_blank((unsigned int)number_of_pixels * crt_cycles_multiplier); + return; + } + unsigned int newDivider = 0; switch(_screen_mode) { @@ -512,11 +522,6 @@ inline void Machine::update_pixels_to_position(int x, int y) if(display_x == last_graphics_cycle) { end_pixel_output(); - -// if(_crt->get_field_cycle() - end != 640) -// { -// printf("!!!\n"); -// } } continue; } @@ -528,95 +533,44 @@ inline void Machine::update_pixels_to_position(int x, int y) display_x = 0; display_y++; - _startLineAddress++; - _currentOutputLine++; - if(_currentOutputLine == 8) + + if(((_screen_mode != 3) && (_screen_mode != 6)) || ((display_y%10) < 8)) { - _currentOutputLine = 0; - _startLineAddress = _currentScreenAddress - 7; + _startLineAddress++; + _currentOutputLine++; + + if(_currentOutputLine == 8) + { + _currentOutputLine = 0; + _startLineAddress = (_startLineAddress - 8) + ((_screen_mode < 4) ? 80 : 40)*8; + } } } } - -/* while(_displayOutputPosition < end_of_graphics && _displayOutputPosition) - { - - int displayOffset = _displayOutputPosition - end_of_graphics; - int cyclesRemaining = _fieldCycles - _displayOutputPosition; -// int current_line = displayOffset >> 7; - int current_pixel = displayOffset & 127; - - if(current_pixel < 9) - { - if(cyclesRemaining > 9) - { - _crt->output_sync(9 * crt_cycles_multiplier); - cyclesRemaining -= 9; - _displayOutputPosition += 9; - } - else return; - } - - int line_remainder = std::min(119, cyclesRemaining); - _crt->output_blank((unsigned int)line_remainder * crt_cycles_multiplier); - _displayOutputPosition += line_remainder; - current_pixel += line_remainder; - if(current_pixel < 128) return; - }*/ } inline void Machine::update_display() { /* - Odd field: + Odd field: Even field: - |--S--| - |--S--| - |-S-B-| - |--B--| - |--P--| - |--B--| - - - (2.5 lines of sync; half a line of blank; full blanks; pixels; full blanks; half blank) - - Even field: - - |-B-S-| - |--S--| - |--S--| - |--B--| - |--P--| - |--B--| - - (2.5 lines of sync; full blanks; pixels; full blanks) - - So: - - Top: - if even then half a line of blank - 2.5 lines of sync - if odd then half a line of blank - full blanks - Pixels - Bottom: - full blanks + |--S--| -S-| + |--S--| |--S--| + |-S-B-| = 3 |--S--| = 2.5 + |--B--| |--B--| + |--P--| |--P--| + |--B--| = 312 |--B--| = 312.5 + |-B- */ - const int end_of_top = (first_graphics_line * cycles_per_line) + (_is_odd_field ? 64 : 0); - const int end_of_graphics = ((first_graphics_line + 256) * cycles_per_line) + (_is_odd_field ? 64 : 0); + const unsigned int end_of_top = (first_graphics_line * cycles_per_line) + (_is_odd_field ? 64 : 0); + const unsigned int end_of_graphics = end_of_top + 256 * cycles_per_line; // does the top region need to be output? if(_displayOutputPosition < end_of_top && _fieldCycles >= end_of_top) { -// printf("[1] %d / %d\n", _crt->get_field_cycle() >> 10, (_crt->get_field_cycle() >> 3)&127); - if(!_is_odd_field) - { - _crt->output_sync(9 * crt_cycles_multiplier); - _crt->output_blank(55 * crt_cycles_multiplier); - } _crt->output_sync(320 * crt_cycles_multiplier); if(_is_odd_field) _crt->output_blank(64 * crt_cycles_multiplier); @@ -625,7 +579,6 @@ inline void Machine::update_display() _crt->output_sync(9 * crt_cycles_multiplier); _crt->output_blank(119 * crt_cycles_multiplier); } -// printf("[2] %d / %d\n", _crt->get_field_cycle() >> 10, (_crt->get_field_cycle() >> 3)&127); _displayOutputPosition = end_of_top; @@ -635,197 +588,34 @@ inline void Machine::update_display() // is this the pixel region? if(_displayOutputPosition >= end_of_top && _displayOutputPosition < end_of_graphics) { - int final_position = std::min(_fieldCycles, end_of_top + 256 * 128) - end_of_top; - int final_line = final_position >> 7; - int final_pixel = final_position & 127; - update_pixels_to_position(final_pixel, final_line); + unsigned int final_position = std::min(_fieldCycles, end_of_graphics) - end_of_top; + unsigned int final_line = final_position >> 7; + unsigned int final_pixel = final_position & 127; + update_pixels_to_position((int)final_pixel, (int)final_line); _displayOutputPosition = final_position + end_of_top; } // is this the bottom region? if(_displayOutputPosition >= end_of_graphics && _displayOutputPosition < cycles_per_frame) { -// printf("[3] %d / %d\n", _crt->get_field_cycle() >> 10, (_crt->get_field_cycle() >> 3)&127); - for(int y = first_graphics_line+256; y < 312; y++) + unsigned int remaining_time = cycles_per_frame - _displayOutputPosition; + while(remaining_time >= 128) { _crt->output_sync(9 * crt_cycles_multiplier); _crt->output_blank(119 * crt_cycles_multiplier); + remaining_time -= 128; } - _displayOutputPosition = cycles_per_frame; -// printf("[4] %d / %d\n", _crt->get_field_cycle() >> 10, (_crt->get_field_cycle() >> 3)&127); - _is_odd_field ^= true; - } - - // no? Then let's do some pixels - -/* if(_fieldCycles >= end_of_hsync) - { - // assert sync for the first three lines of the display, with a break at the end for horizontal alignment - if(_displayOutputPosition < end_of_hsync) + if(remaining_time == 64) { _crt->output_sync(9 * crt_cycles_multiplier); _crt->output_blank(55 * crt_cycles_multiplier); - _crt->output_sync(320 * crt_cycles_multiplier); - - _is_odd_field ^= true; - _displayOutputPosition = end_of_hsync; } - while(_displayOutputPosition >= end_of_hsync && _displayOutputPosition < _fieldCycles) - { - const int cycles_left = _fieldCycles - _displayOutputPosition; + _displayOutputPosition = cycles_per_frame; - const int fieldOutputPosition = get_line_output_position(_displayOutputPosition); - const int current_line = fieldOutputPosition >> 7; - const int line_position = fieldOutputPosition & 127; - - // all lines then start with 9 cycles of sync - if(line_position < 9) - { - int remaining_period = std::min(9 - line_position, cycles_left); - _displayOutputPosition += remaining_period; - - if(line_position + remaining_period == 9) - { - _crt->output_sync(9 * crt_cycles_multiplier); - } - } - else - { - bool isBlankLine = - (current_line < first_graphics_line) || - (((_screen_mode == 3) || (_screen_mode == 6)) ? - ((current_line >= first_graphics_line+248) || (((current_line - first_graphics_line)%10) > 7)) : - ((current_line >= first_graphics_line+256))); - bool isBlankPeriod = - (line_position < first_graphics_cycle) || (line_position >= 80+first_graphics_cycle); - - if(isBlankLine || isBlankPeriod) - { - if(_currentLine) - { - _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); - _currentLine = _writePointer = nullptr; - } - - int target = (isBlankLine || (line_position > first_graphics_cycle)) ? 128 : first_graphics_cycle; - int remaining_period = std::min(target - line_position, cycles_left); - _crt->output_blank((unsigned int)remaining_period * crt_cycles_multiplier); - _displayOutputPosition += remaining_period; - - if(line_position + remaining_period == 128) - { - _currentOutputLine++; - if(!(_currentOutputLine&7)) - { - _startLineAddress += ((_screen_mode < 4) ? 80 : 40)*8 - 7; - } - else - _startLineAddress++; - } - } - else - { - if(line_position == first_graphics_cycle) - { - if(current_line == first_graphics_line) _startLineAddress = _startScreenAddress; - _currentScreenAddress = _startLineAddress; - } - - // determine the pixel clock - unsigned int newDivider = 0; - switch(_screen_mode) - { - case 0: case 3: newDivider = 1; break; - case 1: case 4: case 6: newDivider = 2; break; - case 2: case 5: newDivider = 4; break; - } - - // if the clock has changed or we don't yet have a write pointer, get one - if((newDivider != _currentOutputDivider) || !_currentLine) - { - if(_currentLine) - { - _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); - } - - _currentOutputDivider = newDivider; - _crt->allocate_write_area((size_t)((80 + first_graphics_cycle - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); - _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); - } - - // determine how many pixels to write - int pixels_to_output = std::min(80 + first_graphics_cycle - line_position, cycles_left); - _displayOutputPosition += pixels_to_output; - if(_screen_mode >= 4) - { - if(_displayOutputPosition&1) pixels_to_output++; - pixels_to_output >>= 1; - } - -#define GetNextPixels() \ - if(_currentScreenAddress&32768)\ - {\ - _currentScreenAddress = (_screenModeBaseAddress + _currentScreenAddress)&32767;\ - }\ - uint8_t pixels = _ram[_currentScreenAddress];\ - _currentScreenAddress = _currentScreenAddress+8 - - if(pixels_to_output) - { - switch(_screen_mode) - { - default: - case 0: case 3: case 4: case 6: - while(pixels_to_output--) - { - GetNextPixels(); - - _writePointer[0] = _palette[(pixels&0x80) >> 4]; - _writePointer[1] = _palette[(pixels&0x40) >> 3]; - _writePointer[2] = _palette[(pixels&0x20) >> 2]; - _writePointer[3] = _palette[(pixels&0x10) >> 1]; - _writePointer[4] = _palette[(pixels&0x08) >> 0]; - _writePointer[5] = _palette[(pixels&0x04) << 1]; - _writePointer[6] = _palette[(pixels&0x02) << 2]; - _writePointer[7] = _palette[(pixels&0x01) << 3]; - - _writePointer += 8; - } - break; - - case 1: - case 5: - while(pixels_to_output--) - { - GetNextPixels(); - - _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x08) >> 2)]; - _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x04) >> 1)]; - _writePointer[2] = _palette[((pixels&0x20) >> 2) | ((pixels&0x02) >> 0)]; - _writePointer[3] = _palette[((pixels&0x10) >> 1) | ((pixels&0x01) << 1)]; - - _writePointer += 4; - } - break; - - case 2: - while(pixels_to_output--) - { - GetNextPixels(); - _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1)]; - _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x10) >> 2) | ((pixels&0x04) >> 1) | ((pixels&0x01) >> 0)]; - _writePointer += 2; - } - break; - } - } -#undef GetNextPixels - } - } - } - }*/ + _is_odd_field ^= true; + } } void Machine::set_key_state(Key key, bool isPressed) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index e7f13857d..a73ce924d 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -183,7 +183,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { uint16_t _startScreenAddress; // Counters related to simultaneous subsystems; - int _fieldCycles, _displayOutputPosition; + unsigned int _fieldCycles, _displayOutputPosition; int _audioOutputPosition, _audioOutputPositionError; // Display generation. diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index c4979836a..1cbfd0dee 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -219,6 +219,16 @@ class CRT { { return _run_builders[_run_write_pointer]->duration / _time_multiplier; } + + inline uint32_t get_line_cycle() + { + return _horizontal_flywheel->get_current_time() / _time_multiplier; + } + + inline float get_raster_x() + { + return (float)_horizontal_flywheel->get_current_output_position() / (float)_horizontal_flywheel->get_scan_period(); + } #endif private: diff --git a/Outputs/CRT/Flywheel.hpp b/Outputs/CRT/Flywheel.hpp index 5e495b614..5beb4133f 100644 --- a/Outputs/CRT/Flywheel.hpp +++ b/Outputs/CRT/Flywheel.hpp @@ -73,11 +73,13 @@ struct Flywheel if(_counter < _retrace_time + (_expected_next_sync >> 1)) { - _expected_next_sync++; + _expected_next_sync = (_expected_next_sync + _standard_period + _sync_error_window) >> 1; +// _expected_next_sync+=; } else { - _expected_next_sync--; + _expected_next_sync = (_expected_next_sync + _standard_period - _sync_error_window) >> 1; +// _expected_next_sync--; } } } From 574985f9a2e84e432defb80c3702ee7c94a978e1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Feb 2016 20:29:40 -0500 Subject: [PATCH 126/307] Fixed interlaced timing; switched to a more robust detector for horizontal sync; eliminated some test logging. --- Machines/Electron/Electron.cpp | 2 +- Outputs/CRT/CRT.cpp | 17 ++++++----------- Outputs/CRT/CRT.hpp | 1 + Outputs/CRT/Flywheel.hpp | 8 +++----- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 363d5b030..c8e7c586d 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -565,7 +565,7 @@ inline void Machine::update_display() */ - const unsigned int end_of_top = (first_graphics_line * cycles_per_line) + (_is_odd_field ? 64 : 0); + const unsigned int end_of_top = (first_graphics_line * cycles_per_line) - (_is_odd_field ? 0 : 64); const unsigned int end_of_graphics = end_of_top + 256 * cycles_per_line; // does the top region need to be output? diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 13addb82e..5539ea499 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -98,7 +98,8 @@ CRT::CRT() : _is_receiving_sync(false), _output_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), - _rasterPosition({.x = 0, .y = 0}) + _rasterPosition({.x = 0, .y = 0}), + _sync_period(0) { construct_openGL(); } @@ -150,7 +151,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi number_of_cycles *= _time_multiplier; bool is_output_run = ((type == Type::Level) || (type == Type::Data)); - vsync_requested &= (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold); while(number_of_cycles) { @@ -241,13 +241,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi _run_write_pointer = (_run_write_pointer + 1)%kCRTNumberOfFrames; _run_builders[_run_write_pointer]->reset(); - - static int fc = 0; - fc++; - if(!(fc&15)) - { - printf("H misses %d; v misses %d\n", _horizontal_flywheel->get_and_reset_number_of_surprises(), _vertical_flywheel->get_and_reset_number_of_surprises()); - } } } } @@ -260,10 +253,12 @@ void CRT::output_scan() Scan *scan = &_scans[_next_scan]; bool this_is_sync = (scan->type == Type::Sync); - bool hsync_requested = !_is_receiving_sync && this_is_sync; - bool vsync_requested = _is_receiving_sync && !this_is_sync; + bool is_trailing_edge = (_is_receiving_sync && !this_is_sync); + bool hsync_requested = is_trailing_edge && (_sync_period < (_horizontal_flywheel->get_scan_period() >> 2)); + bool vsync_requested = is_trailing_edge && (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold); _is_receiving_sync = this_is_sync; + _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); } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 1cbfd0dee..e935f5b19 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -264,6 +264,7 @@ class CRT { 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; // the outer entry point for dispatching output_sync, output_blank, output_level and output_data enum Type { diff --git a/Outputs/CRT/Flywheel.hpp b/Outputs/CRT/Flywheel.hpp index 5beb4133f..604759123 100644 --- a/Outputs/CRT/Flywheel.hpp +++ b/Outputs/CRT/Flywheel.hpp @@ -65,7 +65,7 @@ struct Flywheel 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 = (_expected_next_sync + _expected_next_sync + _expected_next_sync + time_now) >> 2; + _expected_next_sync = (3*_expected_next_sync + time_now) >> 2; } else { @@ -73,13 +73,11 @@ struct Flywheel if(_counter < _retrace_time + (_expected_next_sync >> 1)) { - _expected_next_sync = (_expected_next_sync + _standard_period + _sync_error_window) >> 1; -// _expected_next_sync+=; + _expected_next_sync = (3*_expected_next_sync + _standard_period + _sync_error_window) >> 2; } else { - _expected_next_sync = (_expected_next_sync + _standard_period - _sync_error_window) >> 1; -// _expected_next_sync--; + _expected_next_sync = (3*_expected_next_sync + _standard_period - _sync_error_window) >> 2; } } } From 3754cf4bce56bc02600b385f30951a5c59c43b6d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Feb 2016 21:24:02 -0500 Subject: [PATCH 127/307] Played about with interrupt timing. --- Machines/Electron/Electron.cpp | 26 +++++++++++++------------- Machines/Electron/Electron.hpp | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c8e7c586d..c74948a41 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -282,16 +282,16 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // printf("%04x: %02x (%d)\n", address, *value, _fieldCycles); // } - unsigned int line_position = (unsigned int)get_line_output_position(_fieldCycles); - const unsigned int real_time_clock_interrupt_time = (first_graphics_line + 99)*128 + first_graphics_cycle + 80; - const unsigned int display_end_interrupt_time = (first_graphics_line + 255)*128 + first_graphics_cycle + 80; + unsigned int start_of_graphics = get_first_graphics_cycle(); + const unsigned int real_time_clock_interrupt_time = start_of_graphics + 99*128; + const unsigned int display_end_interrupt_time = start_of_graphics + 256*128; - if(line_position < real_time_clock_interrupt_time && line_position + cycles >= real_time_clock_interrupt_time) + if(_fieldCycles < real_time_clock_interrupt_time && _fieldCycles + cycles >= real_time_clock_interrupt_time) { update_audio(); signal_interrupt(Interrupt::RealTimeClock); } - else if(line_position < display_end_interrupt_time && line_position + cycles >= display_end_interrupt_time) + else if(_fieldCycles < display_end_interrupt_time && _fieldCycles + cycles >= display_end_interrupt_time) { update_audio(); signal_interrupt(Interrupt::DisplayEnd); @@ -371,17 +371,12 @@ inline void Machine::evaluate_interrupts() inline void Machine::update_audio() { - int difference = _fieldCycles - _audioOutputPosition; - _audioOutputPosition = _fieldCycles; + int difference = (int)_fieldCycles - _audioOutputPosition; + _audioOutputPosition = (int)_fieldCycles; _speaker.run_for_cycles((_audioOutputPositionError + difference) >> 4); _audioOutputPositionError = (_audioOutputPositionError + difference)&15; } -inline int Machine::get_line_output_position(int field_address) -{ - return field_address + (_is_odd_field ? 64 : 0); -} - inline void Machine::reset_pixel_output() { display_x = 0; @@ -549,6 +544,11 @@ inline void Machine::update_pixels_to_position(int x, int y) } } +inline unsigned int Machine::get_first_graphics_cycle() +{ + return (first_graphics_line * cycles_per_line) - (_is_odd_field ? 0 : 64); +} + inline void Machine::update_display() { /* @@ -565,7 +565,7 @@ inline void Machine::update_display() */ - const unsigned int end_of_top = (first_graphics_line * cycles_per_line) - (_is_odd_field ? 0 : 64); + const unsigned int end_of_top = get_first_graphics_cycle(); const unsigned int end_of_graphics = end_of_top + 256 * cycles_per_line; // does the top region need to be output? diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index a73ce924d..d9bca2785 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -160,8 +160,8 @@ class Machine: public CPU6502::Processor, Tape::Delegate { inline void output_pixels(int start_x, int number_of_pixels); inline void end_pixel_output(); inline void reset_pixel_output(); + inline unsigned int get_first_graphics_cycle(); - inline int get_line_output_position(int field_address); inline void update_audio(); inline void signal_interrupt(Interrupt interrupt); inline void evaluate_interrupts(); From ec3cccbb0df2942fd3fa8b7e697f3474c354255f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Feb 2016 22:03:14 -0500 Subject: [PATCH 128/307] Working towards HQ UEF support: fixed bug whereby writing to tape output would reset input pulse stepper; added support for chunk 0116 and approximate support for 0113. --- Machines/Electron/Electron.cpp | 10 +++---- Machines/Electron/Electron.hpp | 3 +- Storage/Tape/Formats/TapeUEF.cpp | 47 ++++++++++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c74948a41..607cc262d 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -690,9 +690,9 @@ inline void Tape::get_next_tape_pulse() { _time_into_pulse = 0; _current_pulse = _tape->get_next_pulse(); - if(_pulse_stepper == nullptr || _current_pulse.length.clock_rate != _pulse_stepper->get_output_rate()) + if(_input_pulse_stepper == nullptr || _current_pulse.length.clock_rate != _input_pulse_stepper->get_output_rate()) { - _pulse_stepper = std::unique_ptr(new SignalProcessing::Stepper(_current_pulse.length.clock_rate, 2000000)); + _input_pulse_stepper = std::unique_ptr(new SignalProcessing::Stepper(_current_pulse.length.clock_rate, 2000000)); } } @@ -762,7 +762,7 @@ inline void Tape::set_is_in_input_mode(bool is_in_input_mode) inline void Tape::set_counter(uint8_t value) { - _pulse_stepper = std::unique_ptr(new SignalProcessing::Stepper(1200, 2000000)); + _output_pulse_stepper = std::unique_ptr(new SignalProcessing::Stepper(1200, 2000000)); } inline void Tape::set_data_register(uint8_t value) @@ -786,7 +786,7 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) { while(number_of_cycles--) { - _time_into_pulse += (unsigned int)_pulse_stepper->step(); + _time_into_pulse += (unsigned int)_input_pulse_stepper->step(); if(_time_into_pulse == _current_pulse.length.length) { get_next_tape_pulse(); @@ -826,7 +826,7 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) { while(number_of_cycles--) { - if(_pulse_stepper->step()) + if(_output_pulse_stepper->step()) { _output_bits_remaining--; if(!_output_bits_remaining) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index d9bca2785..e9fb3c1d0 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -91,7 +91,8 @@ class Tape { std::shared_ptr _tape; Storage::Tape::Pulse _current_pulse; - std::unique_ptr _pulse_stepper; + std::unique_ptr _input_pulse_stepper; + std::unique_ptr _output_pulse_stepper; uint32_t _time_into_pulse; bool _is_running; diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index ba6d4b6de..6796fa5ce 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -8,6 +8,38 @@ #include "TapeUEF.hpp" #include +#include + +static float gzgetfloat(gzFile file) +{ + uint8_t bytes[4]; + bytes[0] = (uint8_t)gzgetc(file); + bytes[1] = (uint8_t)gzgetc(file); + bytes[2] = (uint8_t)gzgetc(file); + bytes[3] = (uint8_t)gzgetc(file); + + /* assume a four byte array named Float exists, where Float[0] + was the first byte read from the UEF, Float[1] the second, etc */ + + /* decode mantissa */ + int mantissa; + mantissa = bytes[0] | (bytes[1] << 8) | ((bytes[2]&0x7f)|0x80) << 16; + + float result = (float)mantissa; + result = (float)ldexp(result, -23); + + /* decode exponent */ + int exponent; + exponent = ((bytes[2]&0x80) >> 7) | (bytes[3]&0x7f) << 1; + exponent -= 127; + result = (float)ldexp(result, exponent); + + /* flip sign if necessary */ + if(bytes[3]&0x80) + result = -result; + + return result; +} Storage::UEF::UEF(const char *file_name) : _chunk_id(0), _chunk_length(0), _chunk_position(0), @@ -117,18 +149,24 @@ void Storage::UEF::find_next_tape_chunk() continue; } + printf("Deal with %04x?\n", _chunk_id); switch(_chunk_id) { case 0x0100: case 0x0102: // implicit and explicit bit patterns return; - case 0x0112: + case 0x0112: // integer gap _chunk_duration.length = (uint16_t)gzgetc(_file); _chunk_duration.length |= (uint16_t)(gzgetc(_file) << 8); _chunk_duration.clock_rate = _time_base; return; - case 0x0116: // gaps + case 0x0116: // floating point gap + { + float length = gzgetfloat(_file); + _chunk_duration.length = (unsigned int)(length * 4000000); + _chunk_duration.clock_rate = 4000000; + } return; case 0x0110: // carrier tone @@ -144,6 +182,11 @@ void Storage::UEF::find_next_tape_chunk() break; case 0x113: // change of base rate + { + // TODO: something smarter than just converting this to an int + float new_time_base = gzgetfloat(_file); + _time_base = (unsigned int)roundf(new_time_base); + } break; default: From f7fc7cb9321556313a7a3b9e641d54b98cbd452f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Feb 2016 22:18:00 -0500 Subject: [PATCH 129/307] Implemented some portion of 0114; ensured I'm safely skipping chunks in all cases. --- Storage/Tape/Formats/TapeUEF.cpp | 27 ++++++++++++++++++++++++--- Storage/Tape/Formats/TapeUEF.hpp | 4 ++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index 6796fa5ce..44f5141aa 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -63,6 +63,7 @@ Storage::UEF::UEF(const char *file_name) : throw ErrorNotUEF; } + _start_of_next_chunk = gztell(_file); find_next_tape_chunk(); } @@ -104,6 +105,7 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() _bit_position = (_bit_position+1)&(_current_bit ? 3 : 1); } break; + case 0x0114: case 0x0110: next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low; next_pulse.length.length = 1; @@ -130,8 +132,11 @@ void Storage::UEF::find_next_tape_chunk() _chunk_position = 0; _bit_position = 0; + while(1) { + gzseek(_file, _start_of_next_chunk, SEEK_SET); + // read chunk ID _chunk_id = (uint16_t)gzgetc(_file); _chunk_id |= (uint16_t)(gzgetc(_file) << 8); @@ -141,6 +146,8 @@ void Storage::UEF::find_next_tape_chunk() _chunk_length |= (uint32_t)(gzgetc(_file) << 16); _chunk_length |= (uint32_t)(gzgetc(_file) << 24); + _start_of_next_chunk = gztell(_file) + _chunk_length; + if(gzeof(_file)) { reset_count++; @@ -174,11 +181,23 @@ void Storage::UEF::find_next_tape_chunk() _chunk_duration.length |= (uint16_t)(gzgetc(_file) << 8); gzseek(_file, _chunk_length - 2, SEEK_CUR); return; - case 0x0111: // carrier tone with dummy byte +// case 0x0111: // carrier tone with dummy byte // TODO: read lengths - return; +// return; case 0x0114: // security cycles - // TODO: read number, Ps and Ws + // read number of cycles + _chunk_duration.length = (uint32_t)gzgetc(_file); + _chunk_duration.length |= (uint32_t)gzgetc(_file) << 8; + _chunk_duration.length |= (uint32_t)gzgetc(_file) << 16; + + // Ps and Ws + _first_is_pulse = gzgetc(_file) == 'P'; + _last_is_pulse = gzgetc(_file) == 'P'; + + if(_first_is_pulse) + _bit_position ^= 1; + + // TODO: last is pulse break; case 0x113: // change of base rate @@ -202,6 +221,7 @@ bool Storage::UEF::chunk_is_finished() { case 0x0100: return (_chunk_position / 10) == _chunk_length; case 0x0102: return (_chunk_position / 8) == _chunk_length; + case 0x0114: case 0x0110: return _chunk_position == _chunk_duration.length; case 0x0112: @@ -231,6 +251,7 @@ bool Storage::UEF::get_next_bit() } break; + case 0x0114: case 0x0102: { uint32_t bit_position = _chunk_position%8; diff --git a/Storage/Tape/Formats/TapeUEF.hpp b/Storage/Tape/Formats/TapeUEF.hpp index e04ee7092..fe86ecaef 100644 --- a/Storage/Tape/Formats/TapeUEF.hpp +++ b/Storage/Tape/Formats/TapeUEF.hpp @@ -30,6 +30,7 @@ class UEF : public Tape { private: gzFile _file; unsigned int _time_base; + z_off_t _start_of_next_chunk; uint16_t _chunk_id; uint32_t _chunk_length; @@ -42,6 +43,9 @@ class UEF : public Tape { Time _chunk_duration; + bool _first_is_pulse; + bool _last_is_pulse; + void find_next_tape_chunk(); bool get_next_bit(); bool chunk_is_finished(); From 2b8fb5b6150394ef7bfd1b6d373e5feedb9b96f5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 20 Feb 2016 23:13:58 -0500 Subject: [PATCH 130/307] Made an attempt to fix my 0114 implementation. --- Storage/Tape/Formats/TapeUEF.cpp | 38 +++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index 44f5141aa..387bb0bf5 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -89,7 +89,6 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() switch(_chunk_id) { case 0x0100: case 0x0102: - { // In the ordinary ("1200 baud") data encoding format, // a zero bit is encoded as one complete cycle at the base frequency. // A one bit is two complete cycles at twice the base frequency. @@ -103,9 +102,8 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() next_pulse.length.length = _current_bit ? 1 : 2; next_pulse.length.clock_rate = _time_base * 4; _bit_position = (_bit_position+1)&(_current_bit ? 3 : 1); - } break; + break; - case 0x0114: case 0x0110: next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low; next_pulse.length.length = 1; @@ -115,6 +113,27 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() if(!_bit_position) _chunk_position++; break; + case 0x0114: + if(!_bit_position) + { + _current_bit = get_next_bit(); + if(_first_is_pulse && !_chunk_position) + { + _bit_position++; + } + } + + next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low; + next_pulse.length.length = _current_bit ? 1 : 2; + next_pulse.length.clock_rate = _time_base * 4; + _bit_position ^= 1; + + if((_chunk_id == 0x0114) && (_chunk_position == _chunk_duration.length-1) && _last_is_pulse) + { + _chunk_position++; + } + break; + case 0x0112: case 0x0116: next_pulse.type = Pulse::Zero; @@ -132,7 +151,6 @@ void Storage::UEF::find_next_tape_chunk() _chunk_position = 0; _bit_position = 0; - while(1) { gzseek(_file, _start_of_next_chunk, SEEK_SET); @@ -156,7 +174,6 @@ void Storage::UEF::find_next_tape_chunk() continue; } - printf("Deal with %04x?\n", _chunk_id); switch(_chunk_id) { case 0x0100: case 0x0102: // implicit and explicit bit patterns @@ -185,6 +202,7 @@ void Storage::UEF::find_next_tape_chunk() // TODO: read lengths // return; case 0x0114: // security cycles + { // read number of cycles _chunk_duration.length = (uint32_t)gzgetc(_file); _chunk_duration.length |= (uint32_t)gzgetc(_file) << 8; @@ -193,11 +211,7 @@ void Storage::UEF::find_next_tape_chunk() // Ps and Ws _first_is_pulse = gzgetc(_file) == 'P'; _last_is_pulse = gzgetc(_file) == 'P'; - - if(_first_is_pulse) - _bit_position ^= 1; - - // TODO: last is pulse + } break; case 0x113: // change of base rate @@ -251,12 +265,14 @@ bool Storage::UEF::get_next_bit() } break; + // TODO: 0x0104, 0x0111 + case 0x0114: case 0x0102: { uint32_t bit_position = _chunk_position%8; _chunk_position++; - if(!bit_position) + if(!bit_position && _chunk_position < _chunk_duration.length) { _current_byte = (uint8_t)gzgetc(_file); } From 4b28e7b9744303810a73aea65356b043ab5e3eee Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 21 Feb 2016 22:54:55 -0500 Subject: [PATCH 131/307] Switched back to a single array and vertex buffer. --- Outputs/CRT/CRTOpenGL.cpp | 52 +++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 0cd2db9bd..0ec8cf021 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -17,7 +17,8 @@ using namespace Outputs; struct CRT::OpenGLState { std::unique_ptr shaderProgram; - GLuint arrayBuffers[kCRTNumberOfFrames], vertexArrays[kCRTNumberOfFrames]; + GLuint arrayBuffer, vertexArray; + size_t verticesPerSlice; GLint positionAttribute; GLint textureCoordinatesAttribute; @@ -79,17 +80,15 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool GLenum format = formatForDepth(_buffer_builder->buffers[0].bytes_per_pixel); glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[0].data); - glGenVertexArrays(kCRTNumberOfFrames, _openGL_state->vertexArrays); - glGenBuffers(kCRTNumberOfFrames, _openGL_state->arrayBuffers); + glGenVertexArrays(1, &_openGL_state->vertexArray); + glGenBuffers(1, &_openGL_state->arrayBuffer); + _openGL_state->verticesPerSlice = 0; prepare_shader(); - for(int c = 0; c < kCRTNumberOfFrames; c++) - { - glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffers[c]); - glBindVertexArray(_openGL_state->vertexArrays[c]); - prepare_vertex_array(); - } + glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); + glBindVertexArray(_openGL_state->vertexArray); + prepare_vertex_array(); glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); @@ -130,8 +129,27 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; } + // ensure array buffer is up to date + size_t max_number_of_vertices = 0; + for(int c = 0; c < kCRTNumberOfFrames; c++) + { + max_number_of_vertices = std::max(max_number_of_vertices, _run_builders[c]->number_of_vertices); + } + if(_openGL_state->verticesPerSlice < max_number_of_vertices) + { + _openGL_state->verticesPerSlice = max_number_of_vertices; + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * kCRTSizeOfVertex * kCRTNumberOfFrames), NULL, GL_DYNAMIC_DRAW); + + for(unsigned int c = 0; c < kCRTNumberOfFrames; c++) + { + uint8_t *data = &_run_builders[c]->_input_runs[0]; + glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * _openGL_state->verticesPerSlice * kCRTSizeOfVertex), (GLsizeiptr)(_run_builders[c]->number_of_vertices * kCRTSizeOfVertex), data); + _run_builders[c]->uploaded_vertices = _run_builders[c]->number_of_vertices; + } + } + // draw all sitting frames - int run = _run_write_pointer; + unsigned int run = (unsigned int)_run_write_pointer; // printf("%d: %zu v %zu\n", run, _run_builders[run]->uploaded_vertices, _run_builders[run]->number_of_vertices); GLint total_age = 0; for(int c = 0; c < kCRTNumberOfFrames; c++) @@ -143,21 +161,17 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool { glUniform1f(_openGL_state->timestampBaseUniform, (GLfloat)total_age); - // bind the vertex array - glBindVertexArray(_openGL_state->vertexArrays[run]); - - // bind this frame's array buffer - glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffers[run]); if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) { - // glBufferSubData can only replace existing data, not grow the pool, so for now we'll just take this hit - uint8_t *data = &_run_builders[run]->_input_runs[0]; - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_run_builders[run]->number_of_vertices * kCRTSizeOfVertex), data, GL_DYNAMIC_DRAW); + uint8_t *data = &_run_builders[run]->_input_runs[_run_builders[run]->uploaded_vertices * kCRTSizeOfVertex]; + glBufferSubData(GL_ARRAY_BUFFER, + (GLsizeiptr)(((run * _openGL_state->verticesPerSlice) + _run_builders[run]->uploaded_vertices) * kCRTSizeOfVertex), + (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * kCRTSizeOfVertex), data); _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; } // draw this frame - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_run_builders[run]->number_of_vertices); + glDrawArrays(GL_TRIANGLES, (GLint)(run * _openGL_state->verticesPerSlice), (GLsizei)_run_builders[run]->number_of_vertices); } // advance back in time From 1eea28b6929ae05b69e2f7b3beea31692cc6b9f8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 22 Feb 2016 23:35:42 -0500 Subject: [PATCH 132/307] Disabled some debugging parts, added some others, marked some things as inline. Ticking over. --- Machines/Electron/Electron.cpp | 3 +-- Outputs/CRT/CRTOpenGL.cpp | 2 +- Processors/6502/CPU6502.hpp | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 607cc262d..4eb61814e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -184,6 +184,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if(new_screen_mode == 7) new_screen_mode = 4; if(new_screen_mode != _screen_mode) { + printf("To mode %d, %d cycles into field (%d)\n", new_screen_mode, _fieldCycles, _fieldCycles >> 7); update_display(); _screen_mode = new_screen_mode; switch(_screen_mode) @@ -491,7 +492,6 @@ inline void Machine::end_pixel_output() inline void Machine::update_pixels_to_position(int x, int y) { - static unsigned int end; while((display_x < x) || (display_y < y)) { if(display_x < first_graphics_cycle) @@ -502,7 +502,6 @@ inline void Machine::update_pixels_to_position(int x, int y) { _crt->output_sync(9 * crt_cycles_multiplier); _crt->output_blank((first_graphics_cycle - 9) * crt_cycles_multiplier); - end = _crt->get_field_cycle(); _currentScreenAddress = _startLineAddress; } continue; diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 0ec8cf021..80c815920 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -138,7 +138,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool if(_openGL_state->verticesPerSlice < max_number_of_vertices) { _openGL_state->verticesPerSlice = max_number_of_vertices; - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * kCRTSizeOfVertex * kCRTNumberOfFrames), NULL, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * kCRTSizeOfVertex * kCRTNumberOfFrames), NULL, GL_STREAM_DRAW); for(unsigned int c = 0; c < kCRTNumberOfFrames; c++) { diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 698ba47fc..4e0bfcef0 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -167,7 +167,7 @@ template class Processor { @param program The program to schedule. */ - void schedule_program(const MicroOp *program) + inline void schedule_program(const MicroOp *program) { _scheduledPrograms[_scheduleProgramsWritePointer] = program; _scheduleProgramsWritePointer = (_scheduleProgramsWritePointer+1)&3; @@ -207,7 +207,7 @@ template class Processor { @param operation The operation code for which to schedule a program. */ - void decode_operation(uint8_t operation) + inline void decode_operation(uint8_t operation) { #define Program(...) {__VA_ARGS__, OperationMoveToNextProgram} @@ -461,7 +461,7 @@ template class Processor { @returns The program representing an RST response. */ - const MicroOp *get_reset_program() { + inline const MicroOp *get_reset_program() { static const MicroOp reset[] = { CycleFetchOperand, CycleFetchOperand, @@ -480,7 +480,7 @@ template class Processor { @returns The program representing an IRQ response. */ - const MicroOp *get_irq_program() { + inline const MicroOp *get_irq_program() { static const MicroOp reset[] = { CyclePushPCH, CyclePushPCL, @@ -1104,7 +1104,7 @@ template class Processor { @param active @c true if the line is logically active; @c false otherwise. */ - void set_ready_line(bool active) + inline void set_ready_line(bool active) { if(active) _ready_line_is_enabled = true; @@ -1120,7 +1120,7 @@ template class Processor { @param active @c true if the line is logically active; @c false otherwise. */ - void set_reset_line(bool active) + inline void set_reset_line(bool active) { _reset_line_is_enabled = active; } @@ -1130,7 +1130,7 @@ template class Processor { @param active @c true if the line is logically active; @c false otherwise. */ - void set_irq_line(bool active) + inline void set_irq_line(bool active) { _irq_line_is_enabled = active; } @@ -1140,7 +1140,7 @@ template class Processor { @param active `true` if the line is logically active; `false` otherwise. */ - void set_nmi_line(bool active) + inline void set_nmi_line(bool active) { // TODO: NMI is edge triggered, not level, and in any case _nmi_line_is_enabled // is not honoured elsewhere. So NMI is yet to be implemented. @@ -1153,7 +1153,7 @@ template class Processor { @returns @c true if the 6502 is jammed; @c false otherwise. */ - bool is_jammed() + inline bool is_jammed() { return _is_jammed; } @@ -1163,7 +1163,7 @@ template class Processor { @param handler The class instance that will be this 6502's jam handler from now on. */ - void set_jam_handler(JamHandler *handler) + inline void set_jam_handler(JamHandler *handler) { _jam_handler = handler; } From db18c8542376cdca53dbaef478ea0e818e823e8f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 25 Feb 2016 22:01:42 -0500 Subject: [PATCH 133/307] Simplified the run builder rather; put a double check of Electron timing in place. --- Machines/Electron/Electron.cpp | 9 ++++++--- Outputs/CRT/CRT.cpp | 4 ++-- Outputs/CRT/CRT.hpp | 12 +++++++----- Outputs/CRT/CRTBuilders.cpp | 8 ++++---- Outputs/CRT/CRTOpenGL.cpp | 4 ++-- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 4eb61814e..3f36eed91 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -10,6 +10,7 @@ #include "TapeUEF.hpp" #include +#include using namespace Electron; @@ -285,7 +286,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin unsigned int start_of_graphics = get_first_graphics_cycle(); const unsigned int real_time_clock_interrupt_time = start_of_graphics + 99*128; - const unsigned int display_end_interrupt_time = start_of_graphics + 256*128; + const unsigned int display_end_interrupt_time = start_of_graphics + 257*128 + 64; if(_fieldCycles < real_time_clock_interrupt_time && _fieldCycles + cycles >= real_time_clock_interrupt_time) { @@ -572,14 +573,16 @@ inline void Machine::update_display() { _crt->output_sync(320 * crt_cycles_multiplier); if(_is_odd_field) _crt->output_blank(64 * crt_cycles_multiplier); + _displayOutputPosition += 320 + (_is_odd_field ? 64 : 0); - for(int y = 3; y < first_graphics_line; y++) + while(_displayOutputPosition < end_of_top) { + _displayOutputPosition += 128; _crt->output_sync(9 * crt_cycles_multiplier); _crt->output_blank(119 * crt_cycles_multiplier); } - _displayOutputPosition = end_of_top; + assert(_displayOutputPosition == end_of_top); reset_pixel_output(); } diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 5539ea499..95284e195 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -82,7 +82,7 @@ void CRT::allocate_buffers(unsigned int number, va_list sizes) _run_builders = new CRTRunBuilder *[kCRTNumberOfFrames]; for(int builder = 0; builder < kCRTNumberOfFrames; builder++) { - _run_builders[builder] = new CRTRunBuilder(); + _run_builders[builder] = new CRTRunBuilder(kCRTSizeOfVertex); } va_list va; @@ -165,7 +165,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi hsync_requested = false; vsync_requested = false; - uint8_t *next_run = ((is_output_run && next_run_length) && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()) ? _run_builders[_run_write_pointer]->get_next_input_run() : nullptr; + uint8_t *next_run = ((is_output_run && next_run_length) && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()) ? _run_builders[_run_write_pointer]->get_next_run() : nullptr; int lengthMask = (_horizontal_flywheel->is_in_retrace() ? kRetraceXMask : 0) | (_vertical_flywheel->is_in_retrace() ? kRetraceYMask : 0); #define position_x(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfPosition + 0]) diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index e935f5b19..121be4eb9 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -295,6 +295,8 @@ class CRT { void output_scan(); struct CRTRunBuilder { + CRTRunBuilder(size_t vertex_size) : _vertex_size(vertex_size) { reset(); } + // Resets the run builder. void reset(); @@ -302,11 +304,8 @@ class CRT { // from the input buffer to the screen. In composite mode input runs will map from the // input buffer to the processing buffer, and output runs will map from the processing // buffer to the screen. - uint8_t *get_next_input_run(); - std::vector _input_runs; - - uint8_t *get_next_output_run(); - std::vector _output_runs; + uint8_t *get_next_run(); + std::vector _runs; // Container for total length in cycles of all contained runs. uint32_t duration; @@ -315,6 +314,9 @@ class CRT { // entrusted to the CRT to update. size_t uploaded_vertices; size_t number_of_vertices; + + private: + size_t _vertex_size; }; struct CRTInputBufferBuilder { diff --git a/Outputs/CRT/CRTBuilders.cpp b/Outputs/CRT/CRTBuilders.cpp index 7eebd9433..7e73d477d 100644 --- a/Outputs/CRT/CRTBuilders.cpp +++ b/Outputs/CRT/CRTBuilders.cpp @@ -86,17 +86,17 @@ void CRT::CRTRunBuilder::reset() duration = 0; } -uint8_t *CRT::CRTRunBuilder::get_next_input_run() +uint8_t *CRT::CRTRunBuilder::get_next_run() { const size_t vertices_per_run = 6; // get a run from the allocated list, allocating more if we're about to overrun - if((number_of_vertices + vertices_per_run) * kCRTSizeOfVertex >= _input_runs.size()) + if((number_of_vertices + vertices_per_run) * _vertex_size >= _runs.size()) { - _input_runs.resize(_input_runs.size() + kCRTSizeOfVertex * vertices_per_run * 100); + _runs.resize(_runs.size() + _vertex_size * vertices_per_run * 100); } - uint8_t *next_run = &_input_runs[number_of_vertices * kCRTSizeOfVertex]; + uint8_t *next_run = &_runs[number_of_vertices * _vertex_size]; number_of_vertices += vertices_per_run; return next_run; diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 80c815920..da53f5b15 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -142,7 +142,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool for(unsigned int c = 0; c < kCRTNumberOfFrames; c++) { - uint8_t *data = &_run_builders[c]->_input_runs[0]; + uint8_t *data = &_run_builders[c]->_runs[0]; glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * _openGL_state->verticesPerSlice * kCRTSizeOfVertex), (GLsizeiptr)(_run_builders[c]->number_of_vertices * kCRTSizeOfVertex), data); _run_builders[c]->uploaded_vertices = _run_builders[c]->number_of_vertices; } @@ -163,7 +163,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) { - uint8_t *data = &_run_builders[run]->_input_runs[_run_builders[run]->uploaded_vertices * kCRTSizeOfVertex]; + uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * kCRTSizeOfVertex]; glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(((run * _openGL_state->verticesPerSlice) + _run_builders[run]->uploaded_vertices) * kCRTSizeOfVertex), (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * kCRTSizeOfVertex), data); From 1c6de7692d6d41b48c46935ee9dc949d954e679d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 27 Feb 2016 20:37:41 -0500 Subject: [PATCH 134/307] Made an attempt to fix tape output interrupts; offloaded raster width and placing conversion to the GPU. --- Machines/Electron/Electron.cpp | 5 +++- Outputs/CRT/CRT.cpp | 49 +++++----------------------------- Outputs/CRT/CRT.hpp | 6 +---- Outputs/CRT/CRTOpenGL.cpp | 17 ++++++++++-- 4 files changed, 27 insertions(+), 50 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 3f36eed91..1124f4528 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -770,7 +770,8 @@ inline void Tape::set_counter(uint8_t value) inline void Tape::set_data_register(uint8_t value) { _data_register = (uint16_t)((value << 2) | 1); - _output_bits_remaining = 9; + printf("Loaded — %03x\n", _data_register); + _bits_since_start = _output_bits_remaining = 9; } inline uint8_t Tape::get_data_register() @@ -831,6 +832,7 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) if(_output_pulse_stepper->step()) { _output_bits_remaining--; + _bits_since_start--; if(!_output_bits_remaining) { _output_bits_remaining = 9; @@ -840,6 +842,7 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) evaluate_interrupts(); _data_register = (_data_register >> 1) | 0x200; + printf("Shifted — %03x\n", _data_register); } } } diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 95284e195..cebcee55f 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -13,12 +13,6 @@ using namespace Outputs; -static const uint32_t kCRTFixedPointRange = 0xf7ffffff; -static const uint32_t kCRTFixedPointOffset = 0x04000000; - -#define kRetraceXMask 0x01 -#define kRetraceYMask 0x02 - 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) { _colour_space = colour_space; @@ -43,24 +37,13 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di // generate timing values implied by the given arbuments _sync_capacitor_charge_threshold = ((syncCapacityLineChargeThreshold * _cycles_per_line) * 50) >> 7; - const unsigned int vertical_retrace_time = scanlinesVerticalRetraceTime * _cycles_per_line; - const float halfLineWidth = (float)_height_of_display * 1.94f; - // creat the two flywheels - unsigned int horizontal_retrace_time = scanlinesVerticalRetraceTime * _cycles_per_line; + // create the two flywheels _horizontal_flywheel = std::unique_ptr(new Outputs::Flywheel(_cycles_per_line, (millisecondsHorizontalRetraceTime * _cycles_per_line) >> 6)); _vertical_flywheel = std::unique_ptr(new Outputs::Flywheel(_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * _cycles_per_line)); - for(int c = 0; c < 4; c++) - { - _scanSpeed[c].x = (c&kRetraceXMask) ? -(kCRTFixedPointRange / horizontal_retrace_time) : (kCRTFixedPointRange / _cycles_per_line); - _scanSpeed[c].y = (c&kRetraceYMask) ? -(kCRTFixedPointRange / vertical_retrace_time) : (kCRTFixedPointRange / (_height_of_display * _cycles_per_line)); - - // width should be 1.0 / _height_of_display, rotated to match the direction - float angle = atan2f(_scanSpeed[c].y, _scanSpeed[c].x); - _beamWidth[c].x = (uint32_t)((sinf(angle) / halfLineWidth) * kCRTFixedPointRange); - _beamWidth[c].y = (uint32_t)((cosf(angle) / halfLineWidth) * kCRTFixedPointRange); - } + // figure out the divisor necessary to get the horizontal flywheel into a 16-bit range + _vertical_flywheel_output_divider = (uint16_t)ceilf(_vertical_flywheel->get_scan_period() / 65536.0f); } void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) @@ -98,7 +81,6 @@ CRT::CRT() : _is_receiving_sync(false), _output_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), - _rasterPosition({.x = 0, .y = 0}), _sync_period(0) { construct_openGL(); @@ -166,7 +148,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi vsync_requested = false; uint8_t *next_run = ((is_output_run && next_run_length) && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()) ? _run_builders[_run_write_pointer]->get_next_run() : nullptr; - int lengthMask = (_horizontal_flywheel->is_in_retrace() ? kRetraceXMask : 0) | (_vertical_flywheel->is_in_retrace() ? kRetraceYMask : 0); #define position_x(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfPosition + 0]) #define position_y(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfPosition + 2]) @@ -175,22 +156,12 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi #define lateral(v) next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfLateral] #define timestamp(v) (*(uint32_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfTimestamp]) -#define InternalToUInt16(v) ((v) + 32768) >> 16 -#define CounterToInternal(c) (unsigned int)(((uint64_t)c->get_current_output_position() * kCRTFixedPointRange) / c->get_scan_period()) - if(next_run) { - unsigned int x_position = CounterToInternal(_horizontal_flywheel); - unsigned int y_position = CounterToInternal(_vertical_flywheel); - // set the type, initial raster position and type of this run - position_x(0) = position_x(4) = InternalToUInt16(kCRTFixedPointOffset + x_position + _beamWidth[lengthMask].x); - position_y(0) = position_y(4) = InternalToUInt16(kCRTFixedPointOffset + y_position + _beamWidth[lengthMask].y); - position_x(1) = InternalToUInt16(kCRTFixedPointOffset + x_position - _beamWidth[lengthMask].x); - position_y(1) = InternalToUInt16(kCRTFixedPointOffset + y_position - _beamWidth[lengthMask].y); - + position_x(0) = position_x(1) = position_x(4) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + position_y(0) = position_y(1) = position_y(4) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); timestamp(0) = timestamp(1) = timestamp(4) = _run_builders[_run_write_pointer]->duration; - tex_x(0) = tex_x(1) = tex_x(4) = tex_x; // these things are constants across the line so just throw them out now @@ -216,15 +187,9 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(next_run) { - unsigned int x_position = CounterToInternal(_horizontal_flywheel); - unsigned int y_position = CounterToInternal(_vertical_flywheel); - // store the final raster position - position_x(2) = position_x(3) = InternalToUInt16(kCRTFixedPointOffset + x_position - _beamWidth[lengthMask].x); - position_y(2) = position_y(3) = InternalToUInt16(kCRTFixedPointOffset + y_position - _beamWidth[lengthMask].y); - position_x(5) = InternalToUInt16(kCRTFixedPointOffset + x_position + _beamWidth[lengthMask].x); - position_y(5) = InternalToUInt16(kCRTFixedPointOffset + y_position + _beamWidth[lengthMask].y); - + position_x(2) = position_x(3) = position_x(5) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + position_y(2) = position_y(3) = position_y(5) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); timestamp(2) = timestamp(3) = timestamp(5) = _run_builders[_run_write_pointer]->duration; // if this is a data run then advance the buffer pointer diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 121be4eb9..eb34dd6e0 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -252,13 +252,9 @@ class CRT { // The user-supplied visible area Rect _visible_area; - // the current scanning position (TODO: can I eliminate this in favour of just using the flywheels?) - struct Vector { - uint32_t x, y; - } _rasterPosition, _scanSpeed[4], _beamWidth[4]; - // the two flywheels regulating scanning std::unique_ptr _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) diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index da53f5b15..bbfee5601 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -7,6 +7,7 @@ #include "CRT.hpp" #include +#include #include "OpenGL.hpp" #include "TextureTarget.hpp" @@ -255,6 +256,8 @@ char *CRT::get_vertex_shader() "uniform vec2 textureSize;" "uniform float timestampBase;" "uniform float ticksPerFrame;" + "uniform vec2 positionConversion;" + "uniform vec2 scanNormal;" "const float shadowMaskMultiple = 600;" @@ -270,7 +273,8 @@ char *CRT::get_vertex_shader() "float age = (timestampBase - timestamp) / ticksPerFrame;" "alpha = min(10.0 * exp(-age * 2.0), 1.0);" - "vec2 mappedPosition = (position - boundsOrigin) / boundsSize;" + "vec2 floatingPosition = (position / positionConversion) + lateral*scanNormal;" + "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);" "}"); } @@ -361,11 +365,20 @@ void CRT::prepare_shader() GLint shadowMaskTexIDUniform = _openGL_state->shaderProgram->get_uniform_location("shadowMaskTexID"); GLint textureSizeUniform = _openGL_state->shaderProgram->get_uniform_location("textureSize"); GLint ticksPerFrameUniform = _openGL_state->shaderProgram->get_uniform_location("ticksPerFrame"); + GLint scanNormalUniform = _openGL_state->shaderProgram->get_uniform_location("scanNormal"); + GLint positionConversionUniform = _openGL_state->shaderProgram->get_uniform_location("positionConversion"); glUniform1i(texIDUniform, 0); glUniform1i(shadowMaskTexIDUniform, 1); glUniform2f(textureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); + glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); + + float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); + float scan_normal[] = { sinf(scan_angle), cosf(scan_angle)}; + scan_normal[0] /= (float)_height_of_display; + scan_normal[1] /= (float)_height_of_display; + glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]); } void CRT::prepare_vertex_array() @@ -376,7 +389,7 @@ void CRT::prepare_vertex_array() glEnableVertexAttribArray((GLuint)_openGL_state->timestampAttribute); const GLsizei vertexStride = kCRTSizeOfVertex; - glVertexAttribPointer((GLuint)_openGL_state->positionAttribute, 2, GL_UNSIGNED_SHORT, GL_TRUE, vertexStride, (void *)kCRTVertexOffsetOfPosition); + glVertexAttribPointer((GLuint)_openGL_state->positionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfPosition); glVertexAttribPointer((GLuint)_openGL_state->textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTexCoord); glVertexAttribPointer((GLuint)_openGL_state->timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTimestamp); glVertexAttribPointer((GLuint)_openGL_state->lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfLateral); From 7839d933443d6fd28d9c855d9f0eab0158180118 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 27 Feb 2016 22:39:01 -0500 Subject: [PATCH 135/307] With the provision of an extra hint to the CRT and, finally, the realisation about why my scans weren't exactly joining up, improved output precision. --- Machines/Atari2600/Atari2600.cpp | 2 +- Machines/Electron/Electron.cpp | 2 +- Outputs/CRT/CRT.cpp | 12 +++++++----- Outputs/CRT/CRT.hpp | 12 +++++++++--- Outputs/CRT/CRTOpenGL.cpp | 7 ++++--- Outputs/CRT/Flywheel.hpp | 10 +++++++++- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 13d370462..4ca0ee466 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -24,7 +24,7 @@ Machine::Machine() : _piaDataValue{0xff, 0xff}, _tiaInputValue{0xff, 0xff} { - _crt = new Outputs::CRT(228, Outputs::CRT::DisplayType::NTSC60, 1, 2); + _crt = new Outputs::CRT(228, 1, Outputs::CRT::DisplayType::NTSC60, 1, 2); _crt->set_composite_sampling_function( "float sample(vec2 coordinate, float phase)\n" "{\n" diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 1124f4528..3f0c1eb6b 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -32,7 +32,7 @@ Machine::Machine() : _audioOutputPositionError(0), _currentOutputLine(0), _is_odd_field(false), - _crt(std::unique_ptr(new Outputs::CRT(crt_cycles_per_line, Outputs::CRT::DisplayType::PAL50, 1, 1))) + _crt(std::unique_ptr(new Outputs::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1, 1))) { _crt->set_rgb_sampling_function( "vec3 rgb_sample(vec2 coordinate)" diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index cebcee55f..3ed7727e3 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -43,7 +43,8 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di _vertical_flywheel = std::unique_ptr(new Outputs::Flywheel(_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * _cycles_per_line)); // figure out the divisor necessary to get the horizontal flywheel into a 16-bit range - _vertical_flywheel_output_divider = (uint16_t)ceilf(_vertical_flywheel->get_scan_period() / 65536.0f); + 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)); } void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) @@ -74,14 +75,15 @@ void CRT::allocate_buffers(unsigned int number, va_list sizes) va_end(va); } -CRT::CRT() : +CRT::CRT(unsigned int common_output_divisor) : _next_scan(0), _run_write_pointer(0), _sync_capacitor_charge_level(0), _is_receiving_sync(false), _output_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), - _sync_period(0) + _sync_period(0), + _common_output_divisor(common_output_divisor) { construct_openGL(); } @@ -96,7 +98,7 @@ CRT::~CRT() destruct_openGL(); } -CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...) : CRT() +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 number_of_buffers, ...) : CRT(common_output_divisor) { set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator); @@ -106,7 +108,7 @@ CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpa va_end(buffer_sizes); } -CRT::CRT(unsigned int cycles_per_line, DisplayType displayType, unsigned int number_of_buffers, ...) : CRT() +CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int number_of_buffers, ...) : CRT(common_output_divisor) { set_new_display_type(cycles_per_line, displayType); diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index eb34dd6e0..8e993bc79 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -58,6 +58,11 @@ class CRT { @param cycles_per_line The clock rate at which this CRT will be driven, specified as the number of cycles expected to take up one whole scanline of the display. + + @param common_output_divisor The greatest a priori common divisor of all cycle counts that will be + supplied to @c output_sync, @c output_data, etc; supply 1 if no greater divisor is known. For many + machines output will run at a fixed multiple of the clock rate; knowing this divisor can improve + internal precision. @param height_of_dispaly The number of lines that nominally form one field of the display, rounded up to the next whole integer. @@ -79,7 +84,7 @@ class CRT { @see @c set_rgb_sampling_function , @c set_composite_sampling_function */ - CRT(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...); + 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 number_of_buffers, ...); /*! Constructs the CRT with the specified clock rate, with the display height and colour subcarrier frequency dictated by a standard display type and with the requested number of @@ -88,7 +93,7 @@ class CRT { Exactly identical to calling the designated constructor with colour subcarrier information looked up by display type. */ - CRT(unsigned int cycles_per_line, DisplayType displayType, unsigned int number_of_buffers, ...); + CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int number_of_buffers, ...); /*! Resets the CRT with new timing information. The CRT then continues as though the new timing had been provided at construction. */ @@ -232,12 +237,13 @@ class CRT { #endif private: - CRT(); + CRT(unsigned int common_output_divisor); void allocate_buffers(unsigned int number, va_list sizes); // 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; diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index bbfee5601..324f3c0a6 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -375,9 +375,10 @@ void CRT::prepare_shader() glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); - float scan_normal[] = { sinf(scan_angle), cosf(scan_angle)}; - scan_normal[0] /= (float)_height_of_display; - scan_normal[1] /= (float)_height_of_display; + float scan_normal[] = { sinf(scan_angle), -cosf(scan_angle)}; + float multiplier = (float)_horizontal_flywheel->get_standard_period() / ((float)_height_of_display * (float)_horizontal_flywheel->get_scan_period()); + scan_normal[0] *= multiplier; + scan_normal[1] *= multiplier; glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]); } diff --git a/Outputs/CRT/Flywheel.hpp b/Outputs/CRT/Flywheel.hpp index 604759123..e72255f1f 100644 --- a/Outputs/CRT/Flywheel.hpp +++ b/Outputs/CRT/Flywheel.hpp @@ -160,13 +160,21 @@ struct Flywheel } /*! - @returns the expected length of the scan period. + @returns the expected length of the scan period (excluding retrace). */ inline unsigned int get_scan_period() { return _standard_period - _retrace_time; } + /*! + @returns the expected length of a complete scan and retrace cycle. + */ + inline unsigned int get_standard_period() + { + return _standard_period; + } + /*! @returns the number of synchronisation events that have seemed surprising since the last time this method was called; a low number indicates good synchronisation. From 60d35fa72b126ba0d03f7db314dc5c4af72a15bd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 27 Feb 2016 22:46:31 -0500 Subject: [PATCH 136/307] Requested multisampling, if available, rather than supersampling. --- OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 7c223f4de..2346026d9 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -161,6 +161,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt { NSOpenGLPFADoubleBuffer, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + NSOpenGLPFAMultisample, NSOpenGLPFASampleBuffers, 1, NSOpenGLPFASamples, 2, 0 From 3449120c24f97f56c30da3d37ec5319fb7bfd764 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 27 Feb 2016 22:51:37 -0500 Subject: [PATCH 137/307] Added a note to future self, put the scan edge generator back to the way round it was before. --- Outputs/CRT/CRT.cpp | 6 ++++++ Outputs/CRT/CRTOpenGL.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 3ed7727e3..4e96929a5 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -158,6 +158,12 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi #define lateral(v) next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfLateral] #define timestamp(v) (*(uint32_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfTimestamp]) + // Vertex output is arranged as: + // + // [0/4] 3 + // + // 1 [2/5] + if(next_run) { // set the type, initial raster position and type of this run diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 324f3c0a6..89513778d 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -375,7 +375,7 @@ void CRT::prepare_shader() glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); - float scan_normal[] = { sinf(scan_angle), -cosf(scan_angle)}; + float scan_normal[] = { sinf(scan_angle), cosf(scan_angle)}; float multiplier = (float)_horizontal_flywheel->get_standard_period() / ((float)_height_of_display * (float)_horizontal_flywheel->get_scan_period()); scan_normal[0] *= multiplier; scan_normal[1] *= multiplier; From 2f7626a5e08427bbec469627afd55715f6c486ba Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 28 Feb 2016 22:00:05 -0500 Subject: [PATCH 138/307] Okay, this definitely definitely is supposed to be this way around. --- Outputs/CRT/CRTOpenGL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 89513778d..70afeff00 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -375,7 +375,7 @@ void CRT::prepare_shader() glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); - float scan_normal[] = { sinf(scan_angle), cosf(scan_angle)}; + float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; float multiplier = (float)_horizontal_flywheel->get_standard_period() / ((float)_height_of_display * (float)_horizontal_flywheel->get_scan_period()); scan_normal[0] *= multiplier; scan_normal[1] *= multiplier; From c754a6e45a10ff0c2687b2cbf32aa67fa0776c86 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 3 Mar 2016 22:12:31 -0500 Subject: [PATCH 139/307] Decoupled execution speed and frame rate; cleaned up. --- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 110 +++++------------- 1 file changed, 29 insertions(+), 81 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 2346026d9..72e48ec95 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -9,34 +9,18 @@ #import "CSCathodeRayView.h" @import CoreVideo; @import GLKit; -//#import -//#import -//#import +typedef NS_ENUM(NSInteger, CSOpenGLViewCondition) { + CSOpenGLViewConditionReadyForUpdate, + CSOpenGLViewConditionUpdating +}; @implementation CSCathodeRayView { CVDisplayLinkRef _displayLink; - CGRect _aspectRatioCorrectedBounds; + NSConditionLock *_runningLock; + dispatch_queue_t _dispatchQueue; } -/*- (GLuint)textureForImageNamed:(NSString *)name -{ - NSImage *const image = [NSImage imageNamed:name]; - NSBitmapImageRep *bitmapRepresentation = [[NSBitmapImageRep alloc] initWithData: [image TIFFRepresentation]]; - - GLuint textureName; - glGenTextures(1, &textureName); - glBindTexture(GL_TEXTURE_2D, textureName); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, (GLsizei)image.size.width, (GLsizei)image.size.height, 0, GL_RGB, GL_UNSIGNED_BYTE, bitmapRepresentation.bitmapData); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glGenerateMipmap(GL_TEXTURE_2D); - - return textureName; -}*/ - - (void)prepareOpenGL { // Synchronize buffer swaps with vertical refresh rate @@ -48,20 +32,17 @@ // Set the renderer output callback function CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void * __nullable)(self)); + + // Create a queue and a condition lock for dispatching to it + _runningLock = [[NSConditionLock alloc] initWithCondition:CSOpenGLViewConditionReadyForUpdate]; + _dispatchQueue = dispatch_queue_create("com.thomasharte.clocksignal.GL", DISPATCH_QUEUE_SERIAL); // Set the display link for the current renderer CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat); - // install the shadow mask texture as the second texture -/* glActiveTexture(GL_TEXTURE1); - _shadowMaskTextureName = [self textureForImageNamed:@"ShadowMask"]; - - // otherwise, we'll be working on the first texture - glActiveTexture(GL_TEXTURE0);*/ - - // get the shader ready, set the clear colour + // set the clear colour [self.openGLContext makeCurrentContext]; glClearColor(0.0, 0.0, 0.0, 1.0); @@ -75,28 +56,36 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { CSCathodeRayView *view = (__bridge CSCathodeRayView *)displayLinkContext; - [view.delegate openGLView:view didUpdateToTime:*now]; - [view drawViewOnlyIfDirty:YES]; + [view drawAtTime:now]; return kCVReturnSuccess; } +- (void)drawAtTime:(const CVTimeStamp *)now +{ + if([_runningLock tryLockWhenCondition:CSOpenGLViewConditionReadyForUpdate]) + { + CVTimeStamp timeStamp = *now; + dispatch_async(_dispatchQueue, ^{ + [_runningLock lockWhenCondition:CSOpenGLViewConditionUpdating]; + [self.delegate openGLView:self didUpdateToTime:timeStamp]; + [self drawViewOnlyIfDirty:YES]; + [_runningLock unlockWithCondition:CSOpenGLViewConditionReadyForUpdate]; + }); + [_runningLock unlockWithCondition:CSOpenGLViewConditionUpdating]; + } +} + - (void)invalidate { CVDisplayLinkStop(_displayLink); + [_runningLock lockWhenCondition:CSOpenGLViewConditionReadyForUpdate]; + [_runningLock unlock]; } - (void)dealloc { // Release the display link CVDisplayLinkRelease(_displayLink); - - // Release OpenGL buffers -// [self.openGLContext makeCurrentContext]; -// glDeleteBuffers(1, &_arrayBuffer); -// glDeleteVertexArrays(1, &_vertexArray); -// glDeleteTextures(1, &_textureName); -// glDeleteTextures(1, &_shadowMaskTextureName); -// glDeleteProgram(_shaderProgram); } - (CGSize)backingSize @@ -114,47 +103,9 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt CGSize viewSize = [self backingSize]; glViewport(0, 0, (GLsizei)viewSize.width, (GLsizei)viewSize.height); -// [self pushSizeUniforms]; - CGLUnlockContext([[self openGLContext] CGLContextObj]); } -//- (void)setFrameBounds:(CGRect)frameBounds -//{ -// _frameBounds = frameBounds; -// -// [self.openGLContext makeCurrentContext]; -// CGLLockContext([[self openGLContext] CGLContextObj]); -// -// [self pushSizeUniforms]; -// -// CGLUnlockContext([[self openGLContext] CGLContextObj]); -//} -// -//- (void)pushSizeUniforms -//{ -// if(_shaderProgram) -// { -// NSPoint viewSize = [self backingViewSize]; -// if(_windowSizeUniform >= 0) -// { -// glUniform2f(_windowSizeUniform, (GLfloat)viewSize.x, (GLfloat)viewSize.y); -// } -// -// CGFloat outputAspectRatioMultiplier = (viewSize.x / viewSize.y) / (4.0 / 3.0); -// -//// NSLog(@"%0.2f v %0.2f", outputAspectRatio, desiredOutputAspectRatio); -// _aspectRatioCorrectedBounds = _frameBounds; -// -// CGFloat bonusWidth = (outputAspectRatioMultiplier - 1.0f) * _frameBounds.size.width; -// _aspectRatioCorrectedBounds.origin.x -= bonusWidth * 0.5f * _aspectRatioCorrectedBounds.size.width; -// _aspectRatioCorrectedBounds.size.width *= outputAspectRatioMultiplier; -// -// if(_boundsOriginUniform >= 0) glUniform2f(_boundsOriginUniform, (GLfloat)_aspectRatioCorrectedBounds.origin.x, (GLfloat)_aspectRatioCorrectedBounds.origin.y); -// if(_boundsSizeUniform >= 0) glUniform2f(_boundsSizeUniform, (GLfloat)_aspectRatioCorrectedBounds.size.width, (GLfloat)_aspectRatioCorrectedBounds.size.height); -// } -//} - - (void)awakeFromNib { NSOpenGLPixelFormatAttribute attributes[] = @@ -182,9 +133,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt self.pixelFormat = pixelFormat; self.openGLContext = context; self.wantsBestResolutionOpenGLSurface = YES; - - // establish default instance variable values -// self.frameBounds = CGRectMake(0.0, 0.0, 1.0, 1.0); } - (void)drawRect:(NSRect)dirtyRect From 95efeb1d56eb70ac1e0aa5661032127ef6c8a4b7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 5 Mar 2016 14:35:47 -0500 Subject: [PATCH 140/307] Ensured all delegate and responder delegate messages occur on the same queue. --- .../Mac/Clock Signal/Views/CSCathodeRayView.m | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m index 72e48ec95..f4ddccf97 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m @@ -137,7 +137,9 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawRect:(NSRect)dirtyRect { - [self drawViewOnlyIfDirty:NO]; + dispatch_sync(_dispatchQueue, ^{ + [self drawViewOnlyIfDirty:NO]; + }); } - (void)drawViewOnlyIfDirty:(BOOL)onlyIfDirty @@ -160,17 +162,23 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)keyDown:(NSEvent *)theEvent { - [self.responderDelegate keyDown:theEvent]; + dispatch_async(_dispatchQueue, ^{ + [self.responderDelegate keyDown:theEvent]; + }); } - (void)keyUp:(NSEvent *)theEvent { - [self.responderDelegate keyUp:theEvent]; + dispatch_async(_dispatchQueue, ^{ + [self.responderDelegate keyUp:theEvent]; + }); } - (void)flagsChanged:(NSEvent *)theEvent { - [self.responderDelegate flagsChanged:theEvent]; + dispatch_async(_dispatchQueue, ^{ + [self.responderDelegate flagsChanged:theEvent]; + }); } @end From 70b6d5145172df7af0687fdd83ebb4069e82621b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 5 Mar 2016 14:36:12 -0500 Subject: [PATCH 141/307] Ever more baby steps back towards composite decoding. --- Outputs/CRT/CRT.cpp | 23 ++++++++++---------- Outputs/CRT/CRT.hpp | 4 ++++ Outputs/CRT/CRTOpenGL.cpp | 44 +++++++++++++++++++++++++-------------- Outputs/CRT/CRTOpenGL.hpp | 26 +++++++++++++++++------ 4 files changed, 64 insertions(+), 33 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 4e96929a5..cfd1a4720 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -63,11 +63,12 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display void CRT::allocate_buffers(unsigned int number, va_list sizes) { - _run_builders = new CRTRunBuilder *[kCRTNumberOfFrames]; - for(int builder = 0; builder < kCRTNumberOfFrames; builder++) + _run_builders = new CRTRunBuilder *[kCRTNumberOfFields]; + for(int builder = 0; builder < kCRTNumberOfFields; builder++) { - _run_builders[builder] = new CRTRunBuilder(kCRTSizeOfVertex); + _run_builders[builder] = new CRTRunBuilder(kCRTOutputVertexSize); } + _composite_src_runs = std::unique_ptr(new CRTRunBuilder(23)); va_list va; va_copy(va, sizes); @@ -90,7 +91,7 @@ CRT::CRT(unsigned int common_output_divisor) : CRT::~CRT() { - for(int builder = 0; builder < kCRTNumberOfFrames; builder++) + for(int builder = 0; builder < kCRTNumberOfFields; builder++) { delete _run_builders[builder]; } @@ -151,12 +152,12 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi uint8_t *next_run = ((is_output_run && next_run_length) && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()) ? _run_builders[_run_write_pointer]->get_next_run() : nullptr; -#define position_x(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfPosition + 0]) -#define position_y(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfPosition + 2]) -#define tex_x(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfTexCoord + 0]) -#define tex_y(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfTexCoord + 2]) -#define lateral(v) next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfLateral] -#define timestamp(v) (*(uint32_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfTimestamp]) +#define position_x(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfPosition + 0]) +#define position_y(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfPosition + 2]) +#define tex_x(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTexCoord + 0]) +#define tex_y(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTexCoord + 2]) +#define lateral(v) next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfLateral] +#define timestamp(v) (*(uint32_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTimestamp]) // Vertex output is arranged as: // @@ -212,7 +213,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // TODO: how to communicate did_detect_vsync? Bring the delegate back? // _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); - _run_write_pointer = (_run_write_pointer + 1)%kCRTNumberOfFrames; + _run_write_pointer = (_run_write_pointer + 1)%kCRTNumberOfFields; _run_builders[_run_write_pointer]->reset(); } } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 8e993bc79..38d5fa2f7 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -353,6 +353,10 @@ class CRT { int _run_write_pointer; std::shared_ptr _output_mutex; + // transient buffers indicating composite data not yet decoded + std::unique_ptr _composite_src_runs; + int _composite_src_output_y; + // OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here. struct OpenGLState; OpenGLState *_openGL_state; diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 70afeff00..2b7026231 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -132,19 +132,19 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // ensure array buffer is up to date size_t max_number_of_vertices = 0; - for(int c = 0; c < kCRTNumberOfFrames; c++) + for(int c = 0; c < kCRTNumberOfFields; c++) { max_number_of_vertices = std::max(max_number_of_vertices, _run_builders[c]->number_of_vertices); } if(_openGL_state->verticesPerSlice < max_number_of_vertices) { _openGL_state->verticesPerSlice = max_number_of_vertices; - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * kCRTSizeOfVertex * kCRTNumberOfFrames), NULL, GL_STREAM_DRAW); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * kCRTOutputVertexSize * kCRTOutputVertexSize), NULL, GL_STREAM_DRAW); - for(unsigned int c = 0; c < kCRTNumberOfFrames; c++) + for(unsigned int c = 0; c < kCRTNumberOfFields; c++) { uint8_t *data = &_run_builders[c]->_runs[0]; - glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * _openGL_state->verticesPerSlice * kCRTSizeOfVertex), (GLsizeiptr)(_run_builders[c]->number_of_vertices * kCRTSizeOfVertex), data); + glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * _openGL_state->verticesPerSlice * kCRTOutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * kCRTOutputVertexSize), data); _run_builders[c]->uploaded_vertices = _run_builders[c]->number_of_vertices; } } @@ -153,7 +153,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool unsigned int run = (unsigned int)_run_write_pointer; // printf("%d: %zu v %zu\n", run, _run_builders[run]->uploaded_vertices, _run_builders[run]->number_of_vertices); GLint total_age = 0; - for(int c = 0; c < kCRTNumberOfFrames; c++) + for(int c = 0; c < kCRTNumberOfFields; c++) { // update the total age at the start of this set of runs total_age += _run_builders[run]->duration; @@ -164,10 +164,10 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) { - uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * kCRTSizeOfVertex]; + uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * kCRTOutputVertexSize]; glBufferSubData(GL_ARRAY_BUFFER, - (GLsizeiptr)(((run * _openGL_state->verticesPerSlice) + _run_builders[run]->uploaded_vertices) * kCRTSizeOfVertex), - (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * kCRTSizeOfVertex), data); + (GLsizeiptr)(((run * _openGL_state->verticesPerSlice) + _run_builders[run]->uploaded_vertices) * kCRTOutputVertexSize), + (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * kCRTOutputVertexSize), data); _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; } @@ -176,7 +176,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool } // advance back in time - run = (run - 1 + kCRTNumberOfFrames) % kCRTNumberOfFrames; + run = (run - 1 + kCRTNumberOfFields) % kCRTNumberOfFields; } _output_mutex->unlock(); @@ -389,14 +389,26 @@ void CRT::prepare_vertex_array() glEnableVertexAttribArray((GLuint)_openGL_state->lateralAttribute); glEnableVertexAttribArray((GLuint)_openGL_state->timestampAttribute); - const GLsizei vertexStride = kCRTSizeOfVertex; - glVertexAttribPointer((GLuint)_openGL_state->positionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfPosition); - glVertexAttribPointer((GLuint)_openGL_state->textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTexCoord); - glVertexAttribPointer((GLuint)_openGL_state->timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfTimestamp); - glVertexAttribPointer((GLuint)_openGL_state->lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTVertexOffsetOfLateral); + const GLsizei vertexStride = kCRTOutputVertexSize; + glVertexAttribPointer((GLuint)_openGL_state->positionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfPosition); + glVertexAttribPointer((GLuint)_openGL_state->textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfTexCoord); + glVertexAttribPointer((GLuint)_openGL_state->timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfTimestamp); + glVertexAttribPointer((GLuint)_openGL_state->lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfLateral); } -void CRT::set_output_device(OutputDevice output_device) +#pragma mark - Configuration + +void CRT::set_output_device(CRT::OutputDevice output_device) { - _output_device = output_device; + if (_output_device != output_device) + { + _output_device = output_device; + + for(int builder = 0; builder < kCRTNumberOfFields; builder++) + { + _run_builders[builder]->reset(); + } + _composite_src_runs->reset(); + _composite_src_output_y = 0; + } } diff --git a/Outputs/CRT/CRTOpenGL.hpp b/Outputs/CRT/CRTOpenGL.hpp index bbf6f3df1..6fd3e516c 100644 --- a/Outputs/CRT/CRTOpenGL.hpp +++ b/Outputs/CRT/CRTOpenGL.hpp @@ -9,19 +9,33 @@ #ifndef CRTOpenGL_h #define CRTOpenGL_h -const size_t kCRTVertexOffsetOfPosition = 0; -const size_t kCRTVertexOffsetOfTexCoord = 4; -const size_t kCRTVertexOffsetOfTimestamp = 8; -const size_t kCRTVertexOffsetOfLateral = 12; +// Output vertices are those used to copy from an input buffer — whether it describes data that maps directly to RGB +// or is one of the intermediate buffers that we've used to convert from composite towards RGB. +const size_t kCRTOutputVertexOffsetOfPosition = 0; +const size_t kCRTOutputVertexOffsetOfTexCoord = 4; +const size_t kCRTOutputVertexOffsetOfTimestamp = 8; +const size_t kCRTOutputVertexOffsetOfLateral = 12; -const size_t kCRTSizeOfVertex = 16; +const size_t kCRTOutputVertexSize = 16; +// Input vertices, used only in composite mode, map from the input buffer to temporary buffer locations; such +// remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour +const size_t kCRTInputVertexOffsetOfInputPosition = 0; +const size_t kCRTInputVertexOffsetOfOutputPosition = 4; + +const size_t kCRTInputVertexSize = 8; + +// These constants hold the size of the rolling buffer to which the CPU writes const int CRTInputBufferBuilderWidth = 2048; const int CRTInputBufferBuilderHeight = 1024; +// This is the size of the intermediate buffers used during composite to RGB conversion const int CRTIntermediateBufferWidth = 2048; const int CRTIntermediateBufferHeight = 2048; -const int kCRTNumberOfFrames = 3; +// Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track +// run age; that therefore creates a discrete number of fields that are stored. This number should be the +// number of historic fields that are required fully to +const int kCRTNumberOfFields = 3; #endif /* CRTOpenGL_h */ From 41c09f8c3f4c927cc517a266f941509cabfcff57 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 5 Mar 2016 14:45:09 -0500 Subject: [PATCH 142/307] Renamed CSCathodeRayView to CSOpenGLView as it no longer has any CRT-related responsibilities. It just does the GL and manages a serial dispatch queue. --- .../Clock Signal.xcodeproj/project.pbxproj | 12 +++--- .../Base.lproj/Atari2600Document.xib | 6 +-- .../Base.lproj/ElectronDocument.xib | 6 +-- .../ClockSignal-Bridging-Header.h | 3 +- .../Documents/ElectronDocument.swift | 2 +- .../Documents/MachineDocument.swift | 8 ++-- .../Mac/Clock Signal/Views/CSCathodeRayView.h | 35 ----------------- .../Mac/Clock Signal/Views/CSOpenGLView.h | 38 +++++++++++++++++++ .../{CSCathodeRayView.m => CSOpenGLView.m} | 8 ++-- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 2 +- .../Mac/Clock Signal/Wrappers/CSMachine.h | 4 +- 11 files changed, 64 insertions(+), 60 deletions(-) delete mode 100644 OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h create mode 100644 OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h rename OSBindings/Mac/Clock Signal/Views/{CSCathodeRayView.m => CSOpenGLView.m} (96%) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f3919ab2c..06a8aad27 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -26,7 +26,7 @@ 4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */; }; 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE561C3B7D360093A61B /* Atari2600Document.swift */; }; 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE571C3B7D360093A61B /* ElectronDocument.swift */; }; - 4B55CE5D1C3B7D6F0093A61B /* CSCathodeRayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSCathodeRayView.m */; }; + 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; }; 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; @@ -364,8 +364,8 @@ 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = ""; }; 4B55CE561C3B7D360093A61B /* Atari2600Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atari2600Document.swift; sourceTree = ""; }; 4B55CE571C3B7D360093A61B /* ElectronDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronDocument.swift; sourceTree = ""; }; - 4B55CE5B1C3B7D6F0093A61B /* CSCathodeRayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSCathodeRayView.h; sourceTree = ""; }; - 4B55CE5C1C3B7D6F0093A61B /* CSCathodeRayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSCathodeRayView.m; sourceTree = ""; }; + 4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = ""; }; + 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = ""; }; 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = ""; }; 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = ""; }; 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = ""; }; @@ -800,8 +800,8 @@ 4B55CE5A1C3B7D6F0093A61B /* Views */ = { isa = PBXGroup; children = ( - 4B55CE5B1C3B7D6F0093A61B /* CSCathodeRayView.h */, - 4B55CE5C1C3B7D6F0093A61B /* CSCathodeRayView.m */, + 4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */, + 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */, ); path = Views; sourceTree = ""; @@ -1632,7 +1632,7 @@ 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4B2039991C67FA92001375C3 /* Shader.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, - 4B55CE5D1C3B7D6F0093A61B /* CSCathodeRayView.m in Sources */, + 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */, 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib b/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib index a98c860c9..b0920d364 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib @@ -1,7 +1,7 @@ - + - + @@ -23,7 +23,7 @@ - + diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib index a98c860c9..b0920d364 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib @@ -1,7 +1,7 @@ - + - + @@ -23,7 +23,7 @@ - + diff --git a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h index 1ad92dd77..552a4cbd4 100644 --- a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h +++ b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h @@ -5,5 +5,6 @@ #import "CSMachine.h" #import "CSAtari2600.h" #import "CSElectron.h" -#import "CSCathodeRayView.h" + +#import "CSOpenGLView.h" #import "AudioQueue.h" diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index f68fb0019..289032701 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -75,7 +75,7 @@ class ElectronDocument: MachineDocument { } } - override func openGLView(view: CSCathodeRayView, drawViewOnlyIfDirty onlyIfDirty: Bool) { + override func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) { electron.drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty) } diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index e1f14f833..3bb2ebac5 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -9,9 +9,9 @@ import Cocoa import AudioToolbox -class MachineDocument: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewResponderDelegate { +class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate { - @IBOutlet weak var openGLView: CSCathodeRayView! { + @IBOutlet weak var openGLView: CSOpenGLView! { didSet { openGLView.delegate = self openGLView.responderDelegate = self @@ -29,7 +29,7 @@ class MachineDocument: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewRes var intendedCyclesPerSecond: Int64 = 0 private var lastCycleCount: Int64? - final func openGLView(view: CSCathodeRayView, didUpdateToTime time: CVTimeStamp) { + final func openGLView(view: CSOpenGLView, didUpdateToTime time: CVTimeStamp) { // TODO: treat time as a delta from old time, work out how many cycles that is plus error // this slightly elaborate dance is to avoid overflow @@ -46,7 +46,7 @@ class MachineDocument: NSDocument, CSCathodeRayViewDelegate, CSCathodeRayViewRes lastCycleCount = cycleCount } - func openGLView(view: CSCathodeRayView, drawViewOnlyIfDirty onlyIfDirty: Bool) {} + func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) {} func runForNumberOfCycles(numberOfCycles: Int32) {} // MARK: CSOpenGLViewResponderDelegate diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h b/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h deleted file mode 100644 index a463953d5..000000000 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// OpenGLView.h -// Clock Signal -// -// Created by Thomas Harte on 16/07/2015. -// Copyright © 2015 Thomas Harte. All rights reserved. -// - -#import -#import - -@class CSCathodeRayView; - -@protocol CSCathodeRayViewDelegate -- (void)openGLView:(nonnull CSCathodeRayView *)view didUpdateToTime:(CVTimeStamp)time; -- (void)openGLView:(nonnull CSCathodeRayView *)view drawViewOnlyIfDirty:(BOOL)onlyIfDirty; -@end - -@protocol CSCathodeRayViewResponderDelegate -- (void)keyDown:(nonnull NSEvent *)event; -- (void)keyUp:(nonnull NSEvent *)event; -- (void)flagsChanged:(nonnull NSEvent *)newModifiers; -@end - - -@interface CSCathodeRayView : NSOpenGLView - -@property (nonatomic, weak) id delegate; -@property (nonatomic, weak) id responderDelegate; - -- (void)invalidate; - -@property (nonatomic, readonly) CGSize backingSize; - -@end diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h new file mode 100644 index 000000000..78ed6867b --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h @@ -0,0 +1,38 @@ +// +// CSOpenGLView.h +// Clock Signal +// +// Created by Thomas Harte on 16/07/2015. +// Copyright © 2015 Thomas Harte. All rights reserved. +// + +#import +#import + +@class CSOpenGLView; + +@protocol CSOpenGLViewDelegate +- (void)openGLView:(nonnull CSOpenGLView *)view didUpdateToTime:(CVTimeStamp)time; +- (void)openGLView:(nonnull CSOpenGLView *)view drawViewOnlyIfDirty:(BOOL)onlyIfDirty; +@end + +@protocol CSOpenGLViewResponderDelegate +- (void)keyDown:(nonnull NSEvent *)event; +- (void)keyUp:(nonnull NSEvent *)event; +- (void)flagsChanged:(nonnull NSEvent *)newModifiers; +@end + +/*! + Provides an OpenGL canvas with a refresh-linked update timer and manages a serial dispatch queue + such that a delegate may produce video and respond to keyboard events. +*/ +@interface CSOpenGLView : NSOpenGLView + +@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) id responderDelegate; + +- (void)invalidate; + +@property (nonatomic, readonly) CGSize backingSize; + +@end diff --git a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m similarity index 96% rename from OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m rename to OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index f4ddccf97..6097bfb10 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSCathodeRayView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -1,12 +1,12 @@ // -// CSCathodeRayView.m +// CSOpenGLView // CLK // // Created by Thomas Harte on 16/07/2015. // Copyright © 2015 Thomas Harte. All rights reserved. // -#import "CSCathodeRayView.h" +#import "CSOpenGLView.h" @import CoreVideo; @import GLKit; @@ -15,7 +15,7 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewCondition) { CSOpenGLViewConditionUpdating }; -@implementation CSCathodeRayView { +@implementation CSOpenGLView { CVDisplayLinkRef _displayLink; NSConditionLock *_runningLock; dispatch_queue_t _dispatchQueue; @@ -55,7 +55,7 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewCondition) { static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { - CSCathodeRayView *view = (__bridge CSCathodeRayView *)displayLinkContext; + CSOpenGLView *view = (__bridge CSOpenGLView *)displayLinkContext; [view drawAtTime:now]; return kCVReturnSuccess; } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index cc17d6ae4..a24c5f788 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -54,7 +54,7 @@ return YES; } -- (void)setView:(CSCathodeRayView *)view { +- (void)setView:(CSOpenGLView *)view { [super setView:view]; } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h index e75f14bfb..f2f50fd26 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -7,7 +7,7 @@ // #import -#import "CSCathodeRayView.h" +#import "CSOpenGLView.h" #import "AudioQueue.h" @interface CSMachine : NSObject @@ -15,7 +15,7 @@ - (void)runForNumberOfCycles:(int)numberOfCycles; - (void)sync; -@property (nonatomic, weak) CSCathodeRayView *view; +@property (nonatomic, weak) CSOpenGLView *view; @property (nonatomic, weak) AudioQueue *audioQueue; @end From 5c8db71c6458e1c48ba16365d8915de76950f8b7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 5 Mar 2016 16:19:10 -0500 Subject: [PATCH 143/307] Corrected some constants, ensured both machines (so far) are setting the output device. --- Machines/Atari2600/Atari2600.cpp | 1 + Machines/Electron/Electron.cpp | 1 + Outputs/CRT/CRT.cpp | 2 +- Outputs/CRT/CRTOpenGL.hpp | 4 +++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 4ca0ee466..8f33622f6 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -33,6 +33,7 @@ Machine::Machine() : "float aOffset = 6.283185308 * (c.y - 3.0 / 16.0) * 1.14285714285714;\n" "return y + step(0.03125, c.y) * 0.1 * cos(phase - aOffset);\n" "}"); + _crt->set_output_device(Outputs::CRT::Television); memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); } diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 3f0c1eb6b..5f791a349 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -40,6 +40,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); + _crt->set_output_device(Outputs::CRT::Monitor); // _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 memset(_key_states, 0, sizeof(_key_states)); diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index cfd1a4720..88777530d 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -68,7 +68,7 @@ void CRT::allocate_buffers(unsigned int number, va_list sizes) { _run_builders[builder] = new CRTRunBuilder(kCRTOutputVertexSize); } - _composite_src_runs = std::unique_ptr(new CRTRunBuilder(23)); + _composite_src_runs = std::unique_ptr(new CRTRunBuilder(kCRTInputVertexSize)); va_list va; va_copy(va, sizes); diff --git a/Outputs/CRT/CRTOpenGL.hpp b/Outputs/CRT/CRTOpenGL.hpp index 6fd3e516c..23ffc4f0c 100644 --- a/Outputs/CRT/CRTOpenGL.hpp +++ b/Outputs/CRT/CRTOpenGL.hpp @@ -22,8 +22,10 @@ const size_t kCRTOutputVertexSize = 16; // remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour const size_t kCRTInputVertexOffsetOfInputPosition = 0; const size_t kCRTInputVertexOffsetOfOutputPosition = 4; +const size_t kCRTInputVertexOffsetOfPhaseAndAmplitude = 8; +const size_t kCRTInputVertexOffsetOfPhaseAge = 12; -const size_t kCRTInputVertexSize = 8; +const size_t kCRTInputVertexSize = 16; // These constants hold the size of the rolling buffer to which the CPU writes const int CRTInputBufferBuilderWidth = 2048; From 23c223e2ed33d80b2f592c44f533664288c97d09 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 5 Mar 2016 17:55:18 -0500 Subject: [PATCH 144/307] Started edging towards completing the CPU side of accumulating enough data for composite decoding. --- Outputs/CRT/CRT.cpp | 191 +++++++++++++++++++++++------------- Outputs/CRT/CRT.hpp | 12 ++- Outputs/CRT/CRTBuilders.cpp | 8 +- Outputs/CRT/CRTOpenGL.hpp | 2 +- 4 files changed, 135 insertions(+), 78 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 88777530d..ea0b78ea6 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -66,9 +66,9 @@ void CRT::allocate_buffers(unsigned int number, va_list sizes) _run_builders = new CRTRunBuilder *[kCRTNumberOfFields]; for(int builder = 0; builder < kCRTNumberOfFields; builder++) { - _run_builders[builder] = new CRTRunBuilder(kCRTOutputVertexSize); + _run_builders[builder] = new CRTRunBuilder(kCRTOutputVertexSize, 6); } - _composite_src_runs = std::unique_ptr(new CRTRunBuilder(kCRTInputVertexSize)); + _composite_src_runs = std::unique_ptr(new CRTRunBuilder(kCRTInputVertexSize, 2)); va_list va; va_copy(va, sizes); @@ -77,14 +77,14 @@ void CRT::allocate_buffers(unsigned int number, va_list sizes) } CRT::CRT(unsigned int common_output_divisor) : - _next_scan(0), _run_write_pointer(0), _sync_capacitor_charge_level(0), _is_receiving_sync(false), _output_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), _sync_period(0), - _common_output_divisor(common_output_divisor) + _common_output_divisor(common_output_divisor), + _composite_src_output_y(0) { construct_openGL(); } @@ -131,6 +131,21 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, return _horizontal_flywheel->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced); } +#define output_position_x(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfPosition + 0]) +#define output_position_y(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfPosition + 2]) +#define output_tex_x(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTexCoord + 0]) +#define output_tex_y(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTexCoord + 2]) +#define output_lateral(v) next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfLateral] +#define output_timestamp(v) (*(uint32_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTimestamp]) + +#define input_input_position_x(v) (*(uint16_t *)&next_run[kCRTInputVertexSize*v + kCRTInputVertexOffsetOfInputPosition + 0]) +#define input_input_position_y(v) (*(uint16_t *)&next_run[kCRTInputVertexSize*v + kCRTInputVertexOffsetOfInputPosition + 2]) +#define input_output_position_x(v) (*(uint16_t *)&next_run[kCRTInputVertexSize*v + kCRTInputVertexOffsetOfOutputPosition + 0]) +#define input_output_position_y(v) (*(uint16_t *)&next_run[kCRTInputVertexSize*v + kCRTInputVertexOffsetOfOutputPosition + 2]) +#define input_phase(v) next_run[kCRTOutputVertexSize*v + kCRTInputVertexOffsetOfPhaseAndAmplitude + 0] +#define input_amplitude(v) next_run[kCRTOutputVertexSize*v + kCRTInputVertexOffsetOfPhaseAndAmplitude + 1] +#define input_phase_time(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTInputVertexOffsetOfPhaseTime]) + void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type, uint16_t tex_x, uint16_t tex_y) { number_of_cycles *= _time_multiplier; @@ -150,33 +165,44 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi hsync_requested = false; vsync_requested = false; - uint8_t *next_run = ((is_output_run && next_run_length) && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()) ? _run_builders[_run_write_pointer]->get_next_run() : nullptr; - -#define position_x(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfPosition + 0]) -#define position_y(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfPosition + 2]) -#define tex_x(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTexCoord + 0]) -#define tex_y(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTexCoord + 2]) -#define lateral(v) next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfLateral] -#define timestamp(v) (*(uint32_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTimestamp]) + 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) + { + _output_mutex->lock(); + next_run = (_output_device == CRT::Monitor) ? _run_builders[_run_write_pointer]->get_next_run() : _composite_src_runs->get_next_run(); + } // Vertex output is arranged as: // // [0/4] 3 // // 1 [2/5] - if(next_run) { - // set the type, initial raster position and type of this run - position_x(0) = position_x(1) = position_x(4) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - position_y(0) = position_y(1) = position_y(4) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - timestamp(0) = timestamp(1) = timestamp(4) = _run_builders[_run_write_pointer]->duration; - tex_x(0) = tex_x(1) = tex_x(4) = tex_x; + if(_output_device == CRT::Monitor) + { + // set the type, initial raster position and type of this run + output_position_x(0) = output_position_x(1) = output_position_x(4) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + output_position_y(0) = output_position_y(1) = output_position_y(4) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); + output_timestamp(0) = output_timestamp(1) = output_timestamp(4) = _run_builders[_run_write_pointer]->duration; + output_tex_x(0) = output_tex_x(1) = output_tex_x(4) = tex_x; - // these things are constants across the line so just throw them out now - tex_y(0) = tex_y(4) = tex_y(1) = tex_y(2) = tex_y(3) = tex_y(5) = tex_y; - lateral(0) = lateral(4) = lateral(5) = 0; - lateral(1) = lateral(2) = lateral(3) = 1; + // these things are constants across the line so just throw them out now + output_tex_y(0) = output_tex_y(4) = output_tex_y(1) = output_tex_y(2) = output_tex_y(3) = output_tex_y(5) = tex_y; + output_lateral(0) = output_lateral(4) = output_lateral(5) = 0; + output_lateral(1) = output_lateral(2) = output_lateral(3) = 1; + } + else + { + input_input_position_x(0) = tex_x; + input_input_position_y(0) = input_input_position_y(1) = tex_y; + input_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + input_output_position_y(0) = input_output_position_y(1) = _composite_src_output_y; + input_phase(0) = input_phase(1) = _colour_burst_phase; + input_amplitude(0) = input_amplitude(1) = _colour_burst_amplitude; + input_phase_time(0) = input_phase_time(1) = _colour_burst_time; + } } // decrement the number of cycles left to run for and increment the @@ -196,18 +222,39 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(next_run) { - // store the final raster position - position_x(2) = position_x(3) = position_x(5) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - position_y(2) = position_y(3) = position_y(5) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - timestamp(2) = timestamp(3) = timestamp(5) = _run_builders[_run_write_pointer]->duration; - // if this is a data run then advance the buffer pointer if(type == Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider); - // if this is a data or level run then store the end point - tex_x(2) = tex_x(3) = tex_x(5) = tex_x; + if(_output_device == CRT::Monitor) + { + // store the final raster position + output_position_x(2) = output_position_x(3) = output_position_x(5) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + output_position_y(2) = output_position_y(3) = output_position_y(5) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); + output_timestamp(2) = output_timestamp(3) = output_timestamp(5) = _run_builders[_run_write_pointer]->duration; + output_tex_x(2) = output_tex_x(3) = output_tex_x(5) = tex_x; + } + else + { + input_input_position_x(1) = tex_x; + input_output_position_x(1) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + } } + if(is_output_segment) + { + _output_mutex->unlock(); + } + + // if this is horizontal retrace then advance the output line counter and bookend an output run + if(_output_device == CRT::Television) + { + if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::EndRetrace) + { + _composite_src_output_y = (_composite_src_output_y + 1) % CRTIntermediateBufferHeight; + } + } + + // 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) { // TODO: how to communicate did_detect_vsync? Bring the delegate back? @@ -219,13 +266,25 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi } } +#undef output_position_x +#undef output_position_y +#undef output_tex_x +#undef output_tex_y +#undef output_lateral +#undef output_timestamp + +#undef input_input_position_x +#undef input_input_position_y +#undef input_output_position_x +#undef input_output_position_y +#undef input_phase +#undef input_amplitude +#undef input_phase_age + #pragma mark - stream feeding methods -void CRT::output_scan() +void CRT::output_scan(Scan *scan) { -// _next_scan ^= 1; - Scan *scan = &_scans[_next_scan]; - bool this_is_sync = (scan->type == Type::Sync); bool is_trailing_edge = (_is_receiving_sync && !this_is_sync); bool hsync_requested = is_trailing_edge && (_sync_period < (_horizontal_flywheel->get_scan_period() >> 2)); @@ -241,57 +300,55 @@ void CRT::output_scan() */ void CRT::output_sync(unsigned int number_of_cycles) { - _output_mutex->lock(); - _scans[_next_scan].type = Type::Sync; - _scans[_next_scan].number_of_cycles = number_of_cycles; - output_scan(); - _output_mutex->unlock(); + Scan scan{ + .type = Type::Sync, + .number_of_cycles = number_of_cycles + }; + output_scan(&scan); } void CRT::output_blank(unsigned int number_of_cycles) { - _output_mutex->lock(); - _scans[_next_scan].type = Type::Blank; - _scans[_next_scan].number_of_cycles = number_of_cycles; - output_scan(); - _output_mutex->unlock(); + Scan scan { + .type = Type::Blank, + .number_of_cycles = number_of_cycles + }; + output_scan(&scan); } void CRT::output_level(unsigned int number_of_cycles) { - _output_mutex->lock(); - _scans[_next_scan].type = Type::Level; - _scans[_next_scan].number_of_cycles = number_of_cycles; - _scans[_next_scan].tex_x = _buffer_builder->_write_x_position; - _scans[_next_scan].tex_y = _buffer_builder->_write_y_position; - output_scan(); - _output_mutex->unlock(); + Scan scan { + .type = Type::Level, + .number_of_cycles = number_of_cycles, + .tex_x = _buffer_builder->_write_x_position, + .tex_y = _buffer_builder->_write_y_position + }; + output_scan(&scan); } void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t magnitude) { - _output_mutex->lock(); - _scans[_next_scan].type = Type::ColourBurst; - _scans[_next_scan].number_of_cycles = number_of_cycles; - _scans[_next_scan].phase = phase; - _scans[_next_scan].magnitude = magnitude; - output_scan(); - _output_mutex->unlock(); + Scan scan { + .type = Type::ColourBurst, + .number_of_cycles = number_of_cycles, + .phase = phase, + .magnitude = magnitude + }; + output_scan(&scan); } void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { - _output_mutex->lock(); - _buffer_builder->reduce_previous_allocation_to(number_of_cycles / source_divider); - _scans[_next_scan].type = Type::Data; - _scans[_next_scan].number_of_cycles = number_of_cycles; - _scans[_next_scan].tex_x = _buffer_builder->_write_x_position; - _scans[_next_scan].tex_y = _buffer_builder->_write_y_position; - _scans[_next_scan].source_divider = source_divider; - output_scan(); - - _output_mutex->unlock(); + Scan scan { + .type = Type::Data, + .number_of_cycles = number_of_cycles, + .tex_x = _buffer_builder->_write_x_position, + .tex_y = _buffer_builder->_write_y_position, + .source_divider = source_divider + }; + output_scan(&scan); } #pragma mark - Buffer supply diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 38d5fa2f7..971eef0e1 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -292,12 +292,11 @@ class CRT { uint8_t phase, magnitude; }; }; - } _scans[2]; - int _next_scan; - void output_scan(); + }; + void output_scan(Scan *scan); struct CRTRunBuilder { - CRTRunBuilder(size_t vertex_size) : _vertex_size(vertex_size) { reset(); } + CRTRunBuilder(size_t vertex_size, int vertices_per_run) : _vertex_size(vertex_size), _vertices_per_run(vertices_per_run) { reset(); } // Resets the run builder. void reset(); @@ -319,6 +318,7 @@ class CRT { private: size_t _vertex_size; + int _vertices_per_run; }; struct CRTInputBufferBuilder { @@ -355,7 +355,9 @@ class CRT { // transient buffers indicating composite data not yet decoded std::unique_ptr _composite_src_runs; - int _composite_src_output_y; + uint16_t _composite_src_output_y; + uint8_t _colour_burst_phase, _colour_burst_amplitude; + uint16_t _colour_burst_time; // OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here. struct OpenGLState; diff --git a/Outputs/CRT/CRTBuilders.cpp b/Outputs/CRT/CRTBuilders.cpp index 7e73d477d..d065b2095 100644 --- a/Outputs/CRT/CRTBuilders.cpp +++ b/Outputs/CRT/CRTBuilders.cpp @@ -88,16 +88,14 @@ void CRT::CRTRunBuilder::reset() uint8_t *CRT::CRTRunBuilder::get_next_run() { - const size_t vertices_per_run = 6; - // get a run from the allocated list, allocating more if we're about to overrun - if((number_of_vertices + vertices_per_run) * _vertex_size >= _runs.size()) + if((number_of_vertices + (size_t)_vertices_per_run) * _vertex_size >= _runs.size()) { - _runs.resize(_runs.size() + _vertex_size * vertices_per_run * 100); + _runs.resize(_runs.size() + _vertex_size * (size_t)_vertices_per_run * 100); } uint8_t *next_run = &_runs[number_of_vertices * _vertex_size]; - number_of_vertices += vertices_per_run; + number_of_vertices += (size_t)_vertices_per_run; return next_run; } diff --git a/Outputs/CRT/CRTOpenGL.hpp b/Outputs/CRT/CRTOpenGL.hpp index 23ffc4f0c..dd28b5f0d 100644 --- a/Outputs/CRT/CRTOpenGL.hpp +++ b/Outputs/CRT/CRTOpenGL.hpp @@ -23,7 +23,7 @@ const size_t kCRTOutputVertexSize = 16; const size_t kCRTInputVertexOffsetOfInputPosition = 0; const size_t kCRTInputVertexOffsetOfOutputPosition = 4; const size_t kCRTInputVertexOffsetOfPhaseAndAmplitude = 8; -const size_t kCRTInputVertexOffsetOfPhaseAge = 12; +const size_t kCRTInputVertexOffsetOfPhaseTime = 12; const size_t kCRTInputVertexSize = 16; From 3f39803d325137624c3ec102a27a44c1364b7235 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 5 Mar 2016 20:47:11 -0500 Subject: [PATCH 145/307] Switched to triangle strips, to eliminate the need for any temporary storage for the composite output path. --- Outputs/CRT/CRT.cpp | 54 +++++++++++++++++++++++-------------- Outputs/CRT/CRT.hpp | 12 ++++----- Outputs/CRT/CRTBuilders.cpp | 8 +++--- Outputs/CRT/CRTOpenGL.cpp | 2 +- 4 files changed, 45 insertions(+), 31 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index ea0b78ea6..4aa54eb86 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -66,9 +66,9 @@ void CRT::allocate_buffers(unsigned int number, va_list sizes) _run_builders = new CRTRunBuilder *[kCRTNumberOfFields]; for(int builder = 0; builder < kCRTNumberOfFields; builder++) { - _run_builders[builder] = new CRTRunBuilder(kCRTOutputVertexSize, 6); + _run_builders[builder] = new CRTRunBuilder(kCRTOutputVertexSize); } - _composite_src_runs = std::unique_ptr(new CRTRunBuilder(kCRTInputVertexSize, 2)); + _composite_src_runs = std::unique_ptr(new CRTRunBuilder(kCRTInputVertexSize)); va_list va; va_copy(va, sizes); @@ -84,7 +84,8 @@ CRT::CRT(unsigned int common_output_divisor) : _visible_area(Rect(0, 0, 1, 1)), _sync_period(0), _common_output_divisor(common_output_divisor), - _composite_src_output_y(0) + _composite_src_output_y(0), + _is_writing_composite_run(false) { construct_openGL(); } @@ -170,28 +171,28 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(is_output_segment) { _output_mutex->lock(); - next_run = (_output_device == CRT::Monitor) ? _run_builders[_run_write_pointer]->get_next_run() : _composite_src_runs->get_next_run(); + next_run = (_output_device == CRT::Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); } - // Vertex output is arranged as: + // Vertex output is arranged for triangle strips, as: // - // [0/4] 3 + // 2 [4/5] // - // 1 [2/5] + // [0/1] 3 if(next_run) { if(_output_device == CRT::Monitor) { // set the type, initial raster position and type of this run - output_position_x(0) = output_position_x(1) = output_position_x(4) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - output_position_y(0) = output_position_y(1) = output_position_y(4) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_timestamp(0) = output_timestamp(1) = output_timestamp(4) = _run_builders[_run_write_pointer]->duration; - output_tex_x(0) = output_tex_x(1) = output_tex_x(4) = tex_x; + output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); + output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _run_builders[_run_write_pointer]->duration; + output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = tex_x; // these things are constants across the line so just throw them out now - output_tex_y(0) = output_tex_y(4) = output_tex_y(1) = output_tex_y(2) = output_tex_y(3) = output_tex_y(5) = tex_y; - output_lateral(0) = output_lateral(4) = output_lateral(5) = 0; - output_lateral(1) = output_lateral(2) = output_lateral(3) = 1; + output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = output_tex_y(3) = output_tex_y(4) = output_tex_y(5) = tex_y; + output_lateral(0) = output_lateral(1) = output_lateral(3) = 0; + output_lateral(2) = output_lateral(4) = output_lateral(5) = 1; } else { @@ -228,10 +229,10 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(_output_device == CRT::Monitor) { // store the final raster position - output_position_x(2) = output_position_x(3) = output_position_x(5) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - output_position_y(2) = output_position_y(3) = output_position_y(5) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_timestamp(2) = output_timestamp(3) = output_timestamp(5) = _run_builders[_run_write_pointer]->duration; - output_tex_x(2) = output_tex_x(3) = output_tex_x(5) = tex_x; + output_position_x(3) = output_position_x(4) = output_position_x(5) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + output_position_y(3) = output_position_y(4) = output_position_y(5) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); + output_timestamp(3) = output_timestamp(4) = output_timestamp(5) = _run_builders[_run_write_pointer]->duration; + output_tex_x(3) = output_tex_x(4) = output_tex_x(5) = tex_x; } else { @@ -291,6 +292,19 @@ void CRT::output_scan(Scan *scan) bool vsync_requested = is_trailing_edge && (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold); _is_receiving_sync = this_is_sync; + // simplified colour burst logic: if it's within the back porch we'll take it + if(scan->type == Type::ColourBurst) + { + if(_horizontal_flywheel->get_current_time() < (_horizontal_flywheel->get_standard_period() * 12) >> 6) + { + _colour_burst_time = (uint16_t)_colour_burst_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; advance_cycles(scan->number_of_cycles, scan->source_divider, hsync_requested, vsync_requested, this_is_sync, scan->type, scan->tex_x, scan->tex_y); } @@ -327,13 +341,13 @@ void CRT::output_level(unsigned int number_of_cycles) output_scan(&scan); } -void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t magnitude) +void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) { Scan scan { .type = Type::ColourBurst, .number_of_cycles = number_of_cycles, .phase = phase, - .magnitude = magnitude + .amplitude = amplitude }; output_scan(&scan); } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 971eef0e1..4a40fe82d 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -141,10 +141,10 @@ class CRT { @param phase The initial phase of the colour burst in a measuring system with 256 units per circle, e.g. 0 = 0 degrees, 128 = 180 degrees, 256 = 360 degree. - @param magnitude The magnitude of the colour burst in 1/256ths of the magnitude of the + @param amplitude The amplitude of the colour burst in 1/256ths of the amplitude of the positive portion of the wave. */ - void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t magnitude); + void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude); /*! Ensures that the given number of output samples are allocated for writing. @@ -289,14 +289,14 @@ class CRT { uint16_t tex_x, tex_y; }; struct { - uint8_t phase, magnitude; + uint8_t phase, amplitude; }; }; }; void output_scan(Scan *scan); struct CRTRunBuilder { - CRTRunBuilder(size_t vertex_size, int vertices_per_run) : _vertex_size(vertex_size), _vertices_per_run(vertices_per_run) { reset(); } + CRTRunBuilder(size_t vertex_size) : _vertex_size(vertex_size) { reset(); } // Resets the run builder. void reset(); @@ -305,7 +305,7 @@ class CRT { // from the input buffer to the screen. In composite mode input runs will map from the // input buffer to the processing buffer, and output runs will map from the processing // buffer to the screen. - uint8_t *get_next_run(); + uint8_t *get_next_run(size_t number_of_vertices); std::vector _runs; // Container for total length in cycles of all contained runs. @@ -318,7 +318,6 @@ class CRT { private: size_t _vertex_size; - int _vertices_per_run; }; struct CRTInputBufferBuilder { @@ -358,6 +357,7 @@ class CRT { uint16_t _composite_src_output_y; uint8_t _colour_burst_phase, _colour_burst_amplitude; uint16_t _colour_burst_time; + bool _is_writing_composite_run; // OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here. struct OpenGLState; diff --git a/Outputs/CRT/CRTBuilders.cpp b/Outputs/CRT/CRTBuilders.cpp index d065b2095..bf4eee8bc 100644 --- a/Outputs/CRT/CRTBuilders.cpp +++ b/Outputs/CRT/CRTBuilders.cpp @@ -86,16 +86,16 @@ void CRT::CRTRunBuilder::reset() duration = 0; } -uint8_t *CRT::CRTRunBuilder::get_next_run() +uint8_t *CRT::CRTRunBuilder::get_next_run(size_t number_of_vertices_in_run) { // get a run from the allocated list, allocating more if we're about to overrun - if((number_of_vertices + (size_t)_vertices_per_run) * _vertex_size >= _runs.size()) + if((number_of_vertices + number_of_vertices_in_run) * _vertex_size >= _runs.size()) { - _runs.resize(_runs.size() + _vertex_size * (size_t)_vertices_per_run * 100); + _runs.resize(_runs.size() + _vertex_size * 100); } uint8_t *next_run = &_runs[number_of_vertices * _vertex_size]; - number_of_vertices += (size_t)_vertices_per_run; + number_of_vertices += number_of_vertices_in_run; return next_run; } diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 2b7026231..c99846ea8 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -172,7 +172,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool } // draw this frame - glDrawArrays(GL_TRIANGLES, (GLint)(run * _openGL_state->verticesPerSlice), (GLsizei)_run_builders[run]->number_of_vertices); + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(run * _openGL_state->verticesPerSlice), (GLsizei)_run_builders[run]->number_of_vertices); } // advance back in time From 6cddb4c9c802a0d30ebe343a09f6237bb35f954a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 5 Mar 2016 21:18:28 -0500 Subject: [PATCH 146/307] Added a first attempt to generate output scans for composite mode. So this in theory completes the CPU side of composite operations. Though I'm sure problems will reveal themselves. --- Outputs/CRT/CRT.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 4aa54eb86..21019fb99 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -249,6 +249,27 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // if this is horizontal retrace then advance the output line counter and bookend an output run if(_output_device == CRT::Television) { + Flywheel::SyncEvent honoured_event = (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : next_horizontal_sync_event; + bool needs_endpoint = + (honoured_event == Flywheel::SyncEvent::StartRetrace && _is_writing_composite_run) || + (honoured_event == Flywheel::SyncEvent::EndRetrace && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()); + + if(needs_endpoint) + { + uint8_t *next_run = _run_builders[_run_write_pointer]->get_next_run(3); + + output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); + output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _run_builders[_run_write_pointer]->duration; + output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = tex_x; + output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = tex_y; + output_lateral(0) = 0; + output_lateral(1) = _is_writing_composite_run ? 1 : 0; + output_lateral(2) = 1; + + _is_writing_composite_run ^= true; + } + if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::EndRetrace) { _composite_src_output_y = (_composite_src_output_y + 1) % CRTIntermediateBufferHeight; From eefd17ed4ce3961e816fba9158227b80a06e9d4a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 5 Mar 2016 21:52:22 -0500 Subject: [PATCH 147/307] Fixed sync response in composite scan output generation and stored texture coordinates. --- Machines/Electron/Electron.cpp | 2 +- Outputs/CRT/CRT.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 5f791a349..4023ceabd 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -40,7 +40,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); - _crt->set_output_device(Outputs::CRT::Monitor); + _crt->set_output_device(Outputs::CRT::Television); // _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 memset(_key_states, 0, sizeof(_key_states)); diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 21019fb99..4f524ad3c 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -249,7 +249,9 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // if this is horizontal retrace then advance the output line counter and bookend an output run if(_output_device == CRT::Television) { - Flywheel::SyncEvent honoured_event = (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : next_horizontal_sync_event; + Flywheel::SyncEvent honoured_event = Flywheel::SyncEvent::None; + if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event != Flywheel::SyncEvent::None) honoured_event = next_vertical_sync_event; + if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) honoured_event = next_horizontal_sync_event; bool needs_endpoint = (honoured_event == Flywheel::SyncEvent::StartRetrace && _is_writing_composite_run) || (honoured_event == Flywheel::SyncEvent::EndRetrace && !_horizontal_flywheel->is_in_retrace() && !_vertical_flywheel->is_in_retrace()); @@ -261,8 +263,8 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _run_builders[_run_write_pointer]->duration; - output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = tex_x; - output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = tex_y; + output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = _composite_src_output_y; output_lateral(0) = 0; output_lateral(1) = _is_writing_composite_run ? 1 : 0; output_lateral(2) = 1; From 3b6c9c15a1d5bd8fcddd7b64a5d17bf803d519ca Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 7 Mar 2016 18:55:15 -0500 Subject: [PATCH 148/307] Switching momentarily back to monitor mode, resolved why I was suddenly getting no output upon creating some texture targets. --- Machines/Electron/Electron.cpp | 2 +- Outputs/CRT/CRTOpenGL.cpp | 84 ++++++++++++++++++++++------------ Outputs/CRT/TextureTarget.cpp | 8 ++-- Outputs/CRT/TextureTarget.hpp | 7 ++- 4 files changed, 66 insertions(+), 35 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 4023ceabd..5f791a349 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -40,7 +40,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" "}"); - _crt->set_output_device(Outputs::CRT::Television); + _crt->set_output_device(Outputs::CRT::Monitor); // _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 memset(_key_states, 0, sizeof(_key_states)); diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index c99846ea8..2e12a788b 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -71,15 +71,20 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool { _openGL_state = new OpenGLState; - glGenTextures(1, &_openGL_state->textureName); - glBindTexture(GL_TEXTURE_2D, _openGL_state->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); + // generate and bind textures for every one of the requested buffers + for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) + { + glGenTextures(1, &_openGL_state->textureName); + glActiveTexture(GL_TEXTURE3 + buffer); + glBindTexture(GL_TEXTURE_2D, _openGL_state->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); - GLenum format = formatForDepth(_buffer_builder->buffers[0].bytes_per_pixel); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[0].data); + GLenum format = formatForDepth(_buffer_builder->buffers[buffer].bytes_per_pixel); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[buffer].data); + } glGenVertexArrays(1, &_openGL_state->vertexArray); glGenBuffers(1, &_openGL_state->arrayBuffer); @@ -91,13 +96,24 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool glBindVertexArray(_openGL_state->vertexArray); prepare_vertex_array(); + // This should return either an actual framebuffer number, if this is a target with a framebuffer intended for output, + // or 0 if no framebuffer is bound, in which case 0 is also what we want to supply to bind the implied framebuffer. So + // it works either way. glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); -// _openGL_state->compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); -// _openGL_state->filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); -// _openGL_state->filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight)); + // Create intermediate textures and bind to slots 0, 1 and 2 + glActiveTexture(GL_TEXTURE0); + _openGL_state->compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(CRTIntermediateBufferWidth, CRTIntermediateBufferHeight)); + glActiveTexture(GL_TEXTURE1); + _openGL_state->filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(CRTIntermediateBufferWidth, CRTIntermediateBufferHeight)); + glActiveTexture(GL_TEXTURE2); + _openGL_state->filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(CRTIntermediateBufferWidth, CRTIntermediateBufferHeight)); } +// glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); +// +// printf("%d", glIsFramebuffer(_openGL_state->defaultFramebuffer)); + // lock down any further work on the current frame _output_mutex->lock(); @@ -107,27 +123,35 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // clear the buffer glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer); +// glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); +// glGetIntegerv(GL_VIEWPORT, results); + // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it - GLenum format = formatForDepth(_buffer_builder->buffers[0].bytes_per_pixel); - if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) + for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) { - glTexSubImage2D(GL_TEXTURE_2D, 0, - 0, (GLint)_buffer_builder->last_uploaded_line, - CRTInputBufferBuilderWidth, (GLint)(CRTInputBufferBuilderHeight - _buffer_builder->last_uploaded_line), - format, GL_UNSIGNED_BYTE, - &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * CRTInputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); - _buffer_builder->last_uploaded_line = 0; - } + glActiveTexture(GL_TEXTURE3 + buffer); + GLenum format = formatForDepth(_buffer_builder->buffers[0].bytes_per_pixel); + if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, + 0, (GLint)_buffer_builder->last_uploaded_line, + CRTInputBufferBuilderWidth, (GLint)(CRTInputBufferBuilderHeight - _buffer_builder->last_uploaded_line), + format, GL_UNSIGNED_BYTE, + &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * CRTInputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); + _buffer_builder->last_uploaded_line = 0; + } - if(_buffer_builder->_next_write_y_position > _buffer_builder->last_uploaded_line) - { - glTexSubImage2D(GL_TEXTURE_2D, 0, - 0, (GLint)_buffer_builder->last_uploaded_line, - CRTInputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), - format, GL_UNSIGNED_BYTE, - &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * CRTInputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); - _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; + if(_buffer_builder->_next_write_y_position > _buffer_builder->last_uploaded_line) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, + 0, (GLint)_buffer_builder->last_uploaded_line, + CRTInputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), + format, GL_UNSIGNED_BYTE, + &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * CRTInputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); + _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; + } } // ensure array buffer is up to date @@ -149,6 +173,8 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool } } + glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer); + // draw all sitting frames unsigned int run = (unsigned int)_run_write_pointer; // printf("%d: %zu v %zu\n", run, _run_builders[run]->uploaded_vertices, _run_builders[run]->number_of_vertices); @@ -368,7 +394,7 @@ void CRT::prepare_shader() GLint scanNormalUniform = _openGL_state->shaderProgram->get_uniform_location("scanNormal"); GLint positionConversionUniform = _openGL_state->shaderProgram->get_uniform_location("positionConversion"); - glUniform1i(texIDUniform, 0); + glUniform1i(texIDUniform, 3); glUniform1i(shadowMaskTexIDUniform, 1); glUniform2f(textureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); diff --git a/Outputs/CRT/TextureTarget.cpp b/Outputs/CRT/TextureTarget.cpp index 8b61dcf7a..60c030f47 100644 --- a/Outputs/CRT/TextureTarget.cpp +++ b/Outputs/CRT/TextureTarget.cpp @@ -10,7 +10,7 @@ using namespace OpenGL; -TextureTarget::TextureTarget(unsigned int width, unsigned int height) +TextureTarget::TextureTarget(GLsizei width, GLsizei height) : _width(width), _height(height) { glGenFramebuffers(1, &_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); @@ -24,9 +24,8 @@ TextureTarget::TextureTarget(unsigned int width, unsigned int height) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); - // TODO: raise an exception if check framebuffer status fails. -// if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) -// return nil; + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + throw ErrorFramebufferIncomplete; } TextureTarget::~TextureTarget() @@ -38,6 +37,7 @@ TextureTarget::~TextureTarget() void TextureTarget::bind_framebuffer() { glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); + glViewport(0, 0, _width, _height); } void TextureTarget::bind_texture() diff --git a/Outputs/CRT/TextureTarget.hpp b/Outputs/CRT/TextureTarget.hpp index b4b77f42d..f4ec3f665 100644 --- a/Outputs/CRT/TextureTarget.hpp +++ b/Outputs/CRT/TextureTarget.hpp @@ -15,14 +15,19 @@ namespace OpenGL { class TextureTarget { public: - TextureTarget(unsigned int width, unsigned int height); + TextureTarget(GLsizei width, GLsizei height); ~TextureTarget(); void bind_framebuffer(); void bind_texture(); + enum { + ErrorFramebufferIncomplete + }; + private: GLuint _framebuffer, _texture; + GLsizei _width, _height; }; } From cd9c62acca3becc3771961379eb197ab535ac8e8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 7 Mar 2016 19:08:26 -0500 Subject: [PATCH 149/307] Still stepping slowly towards a working composite mode, switched the RGB sampling function to returning a `vec4`, which may be of benefit to any machine with a brightness part of an RGB signal, and started edging towards at least having the correct GLSL programs ready for converting composite output. --- Machines/Electron/Electron.cpp | 4 +- Outputs/CRT/CRT.hpp | 15 +++++-- Outputs/CRT/CRTOpenGL.cpp | 71 ++++++++++++++++++++-------------- 3 files changed, 56 insertions(+), 34 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 5f791a349..5031f594d 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -35,10 +35,10 @@ Machine::Machine() : _crt(std::unique_ptr(new Outputs::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1, 1))) { _crt->set_rgb_sampling_function( - "vec3 rgb_sample(vec2 coordinate)" + "vec4 rgb_sample(vec2 coordinate)" "{" "float texValue = texture(texID, coordinate).r;" - "return vec3(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)));" + "return vec4(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)), 1.0);" "}"); _crt->set_output_device(Outputs::CRT::Monitor); // _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 4a40fe82d..946bd4ac5 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -195,7 +195,7 @@ class CRT { format will be applied. @param shader A GLSL fragent including a function with the signature - `vec3 rgb_sample(vec2 coordinate)` that evaluates to an RGB colour as a function of + `vec4 rgb_sample(vec2 coordinate)` that evaluates to an RGBA colour as a function of the source buffer sampling location. The shader may assume a uniform array of sampler2Ds named `buffers` provides access to all input data. */ @@ -372,12 +372,19 @@ class CRT { void destruct_openGL(); // Methods used by the OpenGL code - void prepare_shader(); + void prepare_rgb_output_shader(); void prepare_vertex_array(); void push_size_uniforms(unsigned int output_width, unsigned int output_height); - char *get_vertex_shader(); - char *get_fragment_shader(); + char *get_output_vertex_shader(); + + char *get_output_fragment_shader(const char *sampling_function); + char *get_rgb_output_fragment_shader(); + char *get_composite_output_fragment_shader(); + + char *get_input_vertex_shader(); + char *get_input_fragment_shader(); + char *get_compound_shader(const char *base, const char *insert); }; diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 2e12a788b..d3aa3abed 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -90,7 +90,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool glGenBuffers(1, &_openGL_state->arrayBuffer); _openGL_state->verticesPerSlice = 0; - prepare_shader(); + prepare_rgb_output_shader(); glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); glBindVertexArray(_openGL_state->vertexArray); @@ -245,7 +245,7 @@ void CRT::set_rgb_sampling_function(const char *shader) _rgb_shader = strdup(shader); } -char *CRT::get_vertex_shader() +char *CRT::get_output_vertex_shader() { // the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates @@ -305,8 +305,44 @@ char *CRT::get_vertex_shader() "}"); } -char *CRT::get_fragment_shader() +char *CRT::get_rgb_output_fragment_shader() { + return get_output_fragment_shader(_rgb_shader); +} + +char *CRT::get_composite_output_fragment_shader() +{ + return get_output_fragment_shader( + "vec4 rgb_sample(vec2 coordinate)" + "{" + "return texture(texID, coordinate);" + "}"); +} + +char *CRT::get_output_fragment_shader(const char *sampling_function) +{ + return get_compound_shader( + "#version 150\n" + + "in float lateralVarying;" + "in float alpha;" + "in vec2 shadowMaskCoordinates;" + "in vec2 srcCoordinatesVarying;" + + "out vec4 fragColour;" + + "uniform sampler2D texID;" + "uniform sampler2D shadowMaskTexID;" + + "\n%s\n" + + "void main(void)" + "{" + "fragColour = rgb_sample(srcCoordinatesVarying) * vec4(1.0, 1.0, 1.0, alpha * sin(lateralVarying));" // + "}" + , sampling_function); +} + // assumes y = [0, 1], i and q = [-0.5, 0.5]; therefore i components are multiplied by 1.1914 versus standard matrices, q by 1.0452 // const char *const yiqToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);"; @@ -338,28 +374,7 @@ char *CRT::get_fragment_shader() // "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; // dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; - - return get_compound_shader( - "#version 150\n" - - "in float lateralVarying;" - "in float alpha;" - "in vec2 shadowMaskCoordinates;" - "in vec2 srcCoordinatesVarying;" - - "out vec4 fragColour;" - - "uniform sampler2D texID;" - "uniform sampler2D shadowMaskTexID;" - - "\n%s\n" - - "void main(void)" - "{" - "fragColour = vec4(rgb_sample(srcCoordinatesVarying).rgb, alpha * sin(lateralVarying));" // - "}" - , _rgb_shader); -} +//} char *CRT::get_compound_shader(const char *base, const char *insert) { @@ -369,10 +384,10 @@ char *CRT::get_compound_shader(const char *base, const char *insert) return text; } -void CRT::prepare_shader() +void CRT::prepare_rgb_output_shader() { - char *vertex_shader = get_vertex_shader(); - char *fragment_shader = get_fragment_shader(); + char *vertex_shader = get_output_vertex_shader(); + char *fragment_shader = get_rgb_output_fragment_shader(); _openGL_state->shaderProgram = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); _openGL_state->shaderProgram->bind(); From 2de229152fab0c170b64e09a844a2bca4e9044b0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 7 Mar 2016 19:21:04 -0500 Subject: [PATCH 150/307] Started sketching out shaders to do the first part of the composite conversion. --- Outputs/CRT/CRTOpenGL.cpp | 91 +++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index d3aa3abed..7e5e50fe4 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -245,24 +245,67 @@ void CRT::set_rgb_sampling_function(const char *shader) _rgb_shader = strdup(shader); } +#pragma mark - Input vertex shader (i.e. from source data to intermediate line layout) + +char *CRT::get_input_vertex_shader() +{ + return strdup( + "#version 150\n" + + "in vec2 inputPosition;" + "in vec2 outputPosition;" + "in vec2 phaseAndAmplitude;" + "in float phaseTime;" + + "uniform vec2 textureSize;" + "uniform float phaseCyclesPerTick;" + + "out vec2 inputPositionVerying;" + "out float phaseVarying;" + + "void main(void)" + "{" + "inputPositionVerying = inputPosition;" + "gl_Position = vec4(outputPosition.x * 2.0 / textureSize.width - 1.0, outputPosition.y * 2.0 / textureSize.height - 1.0, 0.0, 1.0);" + "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAndAmplitude.x) * 2.0 * 3.141592654;" + "}"); +} + +char *CRT::get_input_fragment_shader() +{ + const char *composite_shader = _composite_shader; + if(!composite_shader) + { + } + + return get_compound_shader( + "#version 150\n" + + "in vec2 inputPositionVerying;" + "in float phaseVarying;" + + "out vec4 fragColour;" + + "uniform sampler2D texID;" + + "\n%s\n" + + "void main(void)" + "{" + "fragColour = vec4(composite_sample(inputPositionVarying, phaseVarying), 0.0, 0.0, 1.0);" + "}" + , composite_shader); +} + +#pragma mark - Intermediate vertex shaders (i.e. from intermediate line layout to intermediate line layout) + +#pragma mark - Output vertex shader + char *CRT::get_output_vertex_shader() { // the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates - // from integral to floating point; there's also some setup for NTSC, PAL or whatever. - -// const char *const ntscVertexShaderGlobals = -// "out vec2 srcCoordinatesVarying[4];\n" -// "out float phase;\n"; -// -// const char *const ntscVertexShaderBody = -// "phase = srcCoordinates.x * 6.283185308;\n" -// "\n" -// "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" -// "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; + // from integral to floating point. return strdup( "#version 150\n" @@ -305,6 +348,8 @@ char *CRT::get_output_vertex_shader() "}"); } +#pragma mark - Output fragment shaders; RGB and from composite + char *CRT::get_rgb_output_fragment_shader() { return get_output_fragment_shader(_rgb_shader); @@ -343,6 +388,20 @@ char *CRT::get_output_fragment_shader(const char *sampling_function) , sampling_function); } + +// const char *const ntscVertexShaderGlobals = +// "out vec2 srcCoordinatesVarying[4];\n" +// "out float phase;\n"; +// +// const char *const ntscVertexShaderBody = +// "phase = srcCoordinates.x * 6.283185308;\n" +// "\n" +// "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" +// "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; + // assumes y = [0, 1], i and q = [-0.5, 0.5]; therefore i components are multiplied by 1.1914 versus standard matrices, q by 1.0452 // const char *const yiqToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);"; @@ -376,6 +435,8 @@ char *CRT::get_output_fragment_shader(const char *sampling_function) // dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; //} +#pragma mark - Shader utilities + char *CRT::get_compound_shader(const char *base, const char *insert) { size_t totalLength = strlen(base) + strlen(insert) + 1; @@ -384,6 +445,8 @@ char *CRT::get_compound_shader(const char *base, const char *insert) return text; } +#pragma mark - Program compilation + void CRT::prepare_rgb_output_shader() { char *vertex_shader = get_output_vertex_shader(); From 1e5fe2b2c195396029ca35e48b776dcfb62e47eb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 7 Mar 2016 21:04:04 -0500 Subject: [PATCH 151/307] Made an attempt to reestablish the Atari 2600 output connection despite various changes (TODO: generalise that stuff), and to start creating the composite shader. --- Machines/Atari2600/Atari2600.cpp | 4 +- Machines/Electron/Electron.cpp | 2 +- .../Documents/Atari2600Document.swift | 4 + .../Mac/Clock Signal/Wrappers/CSAtari2600.h | 2 + .../Mac/Clock Signal/Wrappers/CSAtari2600.mm | 4 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 4 - Outputs/CRT/CRT.hpp | 1 + Outputs/CRT/CRTOpenGL.cpp | 93 ++++++++++++------- 8 files changed, 73 insertions(+), 41 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 8f33622f6..f76b3033e 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -26,12 +26,12 @@ Machine::Machine() : { _crt = new Outputs::CRT(228, 1, Outputs::CRT::DisplayType::NTSC60, 1, 2); _crt->set_composite_sampling_function( - "float sample(vec2 coordinate, float phase)\n" + "float composite_sample(vec2 coordinate, float phase)\n" "{\n" "vec2 c = texture(texID, coordinate).rg;" "float y = 0.1 + c.x * 0.91071428571429;\n" "float aOffset = 6.283185308 * (c.y - 3.0 / 16.0) * 1.14285714285714;\n" - "return y + step(0.03125, c.y) * 0.1 * cos(phase - aOffset);\n" + "return y + step(0.03125, c.y) * 0.1 * cos((coordinate.x * 2.0 * 3.141592654) - aOffset);\n" "}"); _crt->set_output_device(Outputs::CRT::Television); memset(_collisions, 0xff, sizeof(_collisions)); diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 5031f594d..d0a996ce3 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -40,7 +40,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec4(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)), 1.0);" "}"); - _crt->set_output_device(Outputs::CRT::Monitor); + _crt->set_output_device(Outputs::CRT::Television); // _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 memset(_key_states, 0, sizeof(_key_states)); diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index ec6b1e3ce..c9b385399 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -54,6 +54,10 @@ class Atari2600Document: MachineDocument { atari2600.runForNumberOfCycles(numberOfCycles) } + override func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) { + atari2600.drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty) + } + // MARK: CSOpenGLViewResponderDelegate private func inputForKey(event: NSEvent) -> Atari2600DigitalInput? { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h index 05a0b70c3..c0e097c52 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h @@ -15,4 +15,6 @@ - (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput; - (void)setResetLineEnabled:(BOOL)enabled; +- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm index 80e9d564f..8239d90df 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -47,6 +47,10 @@ _atari2600.run_for_cycles(numberOfCycles); } +- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { + _atari2600.get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); +} + - (void)setROM:(NSData *)rom { [self perform:^{ _atari2600.set_rom(rom.length, (const uint8_t *)rom.bytes); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index a24c5f788..d7dce90ce 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -54,10 +54,6 @@ return YES; } -- (void)setView:(CSOpenGLView *)view { - [super setView:view]; -} - - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { switch(key) { diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 946bd4ac5..560c124ae 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -373,6 +373,7 @@ class CRT { // Methods used by the OpenGL code void prepare_rgb_output_shader(); + void prepare_composite_input_shader(); void prepare_vertex_array(); void push_size_uniforms(unsigned int output_width, unsigned int output_height); diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 7e5e50fe4..7607b826f 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -17,7 +17,7 @@ using namespace Outputs; struct CRT::OpenGLState { - std::unique_ptr shaderProgram; + std::unique_ptr rgb_shader_program, composite_input_shader_program; GLuint arrayBuffer, vertexArray; size_t verticesPerSlice; @@ -38,6 +38,10 @@ struct CRT::OpenGLState { std::unique_ptr filteredTexture; // receives filtered YIQ or YUV }; +namespace { + static const GLenum first_supplied_buffer_texture_unit = 3; +} + static GLenum formatForDepth(size_t depth) { switch(depth) @@ -75,7 +79,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) { glGenTextures(1, &_openGL_state->textureName); - glActiveTexture(GL_TEXTURE3 + buffer); + glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); glBindTexture(GL_TEXTURE_2D, _openGL_state->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); @@ -90,6 +94,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool glGenBuffers(1, &_openGL_state->arrayBuffer); _openGL_state->verticesPerSlice = 0; + prepare_composite_input_shader(); prepare_rgb_output_shader(); glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); @@ -131,7 +136,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // time as it may have had extra data appended to it for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) { - glActiveTexture(GL_TEXTURE3 + buffer); + glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); GLenum format = formatForDepth(_buffer_builder->buffers[0].bytes_per_pixel); if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) { @@ -260,13 +265,13 @@ char *CRT::get_input_vertex_shader() "uniform vec2 textureSize;" "uniform float phaseCyclesPerTick;" - "out vec2 inputPositionVerying;" + "out vec2 inputPositionVarying;" "out float phaseVarying;" "void main(void)" "{" - "inputPositionVerying = inputPosition;" - "gl_Position = vec4(outputPosition.x * 2.0 / textureSize.width - 1.0, outputPosition.y * 2.0 / textureSize.height - 1.0, 0.0, 1.0);" + "inputPositionVarying = inputPosition;" + "gl_Position = vec4(outputPosition.x * 2.0 / textureSize.x - 1.0, outputPosition.y * 2.0 / textureSize.y - 1.0, 0.0, 1.0);" "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAndAmplitude.x) * 2.0 * 3.141592654;" "}"); } @@ -281,7 +286,7 @@ char *CRT::get_input_fragment_shader() return get_compound_shader( "#version 150\n" - "in vec2 inputPositionVerying;" + "in vec2 inputPositionVarying;" "in float phaseVarying;" "out vec4 fragColour;" @@ -439,6 +444,7 @@ char *CRT::get_output_fragment_shader(const char *sampling_function) char *CRT::get_compound_shader(const char *base, const char *insert) { + if(!base || !insert) return nullptr; size_t totalLength = strlen(base) + strlen(insert) + 1; char *text = new char[totalLength]; snprintf(text, totalLength, base, insert); @@ -447,43 +453,62 @@ char *CRT::get_compound_shader(const char *base, const char *insert) #pragma mark - Program compilation +void CRT::prepare_composite_input_shader() +{ + char *vertex_shader = get_input_vertex_shader(); + char *fragment_shader = get_input_fragment_shader(); + if(vertex_shader && fragment_shader) + { + _openGL_state->composite_input_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + } + free(vertex_shader); + free(fragment_shader); +} + void CRT::prepare_rgb_output_shader() { char *vertex_shader = get_output_vertex_shader(); char *fragment_shader = get_rgb_output_fragment_shader(); - _openGL_state->shaderProgram = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); - _openGL_state->shaderProgram->bind(); + if(vertex_shader && fragment_shader) + { + _openGL_state->rgb_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); - _openGL_state->positionAttribute = _openGL_state->shaderProgram->get_attrib_location("position"); - _openGL_state->textureCoordinatesAttribute = _openGL_state->shaderProgram->get_attrib_location("srcCoordinates"); - _openGL_state->lateralAttribute = _openGL_state->shaderProgram->get_attrib_location("lateral"); - _openGL_state->timestampAttribute = _openGL_state->shaderProgram->get_attrib_location("timestamp"); + _openGL_state->rgb_shader_program->bind(); - _openGL_state->windowSizeUniform = _openGL_state->shaderProgram->get_uniform_location("windowSize"); - _openGL_state->boundsSizeUniform = _openGL_state->shaderProgram->get_uniform_location("boundsSize"); - _openGL_state->boundsOriginUniform = _openGL_state->shaderProgram->get_uniform_location("boundsOrigin"); - _openGL_state->timestampBaseUniform = _openGL_state->shaderProgram->get_uniform_location("timestampBase"); + _openGL_state->positionAttribute = _openGL_state->rgb_shader_program->get_attrib_location("position"); + _openGL_state->textureCoordinatesAttribute = _openGL_state->rgb_shader_program->get_attrib_location("srcCoordinates"); + _openGL_state->lateralAttribute = _openGL_state->rgb_shader_program->get_attrib_location("lateral"); + _openGL_state->timestampAttribute = _openGL_state->rgb_shader_program->get_attrib_location("timestamp"); - GLint texIDUniform = _openGL_state->shaderProgram->get_uniform_location("texID"); - GLint shadowMaskTexIDUniform = _openGL_state->shaderProgram->get_uniform_location("shadowMaskTexID"); - GLint textureSizeUniform = _openGL_state->shaderProgram->get_uniform_location("textureSize"); - GLint ticksPerFrameUniform = _openGL_state->shaderProgram->get_uniform_location("ticksPerFrame"); - GLint scanNormalUniform = _openGL_state->shaderProgram->get_uniform_location("scanNormal"); - GLint positionConversionUniform = _openGL_state->shaderProgram->get_uniform_location("positionConversion"); + _openGL_state->windowSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("windowSize"); + _openGL_state->boundsSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsSize"); + _openGL_state->boundsOriginUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsOrigin"); + _openGL_state->timestampBaseUniform = _openGL_state->rgb_shader_program->get_uniform_location("timestampBase"); - glUniform1i(texIDUniform, 3); - glUniform1i(shadowMaskTexIDUniform, 1); - glUniform2f(textureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); - glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); - glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); + GLint texIDUniform = _openGL_state->rgb_shader_program->get_uniform_location("texID"); + GLint shadowMaskTexIDUniform = _openGL_state->rgb_shader_program->get_uniform_location("shadowMaskTexID"); + GLint textureSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("textureSize"); + GLint ticksPerFrameUniform = _openGL_state->rgb_shader_program->get_uniform_location("ticksPerFrame"); + GLint scanNormalUniform = _openGL_state->rgb_shader_program->get_uniform_location("scanNormal"); + GLint positionConversionUniform = _openGL_state->rgb_shader_program->get_uniform_location("positionConversion"); - float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); - float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; - float multiplier = (float)_horizontal_flywheel->get_standard_period() / ((float)_height_of_display * (float)_horizontal_flywheel->get_scan_period()); - scan_normal[0] *= multiplier; - scan_normal[1] *= multiplier; - glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]); + glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); + glUniform1i(shadowMaskTexIDUniform, 1); + glUniform2f(textureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); + glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); + glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); + + float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); + float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; + float multiplier = (float)_horizontal_flywheel->get_standard_period() / ((float)_height_of_display * (float)_horizontal_flywheel->get_scan_period()); + scan_normal[0] *= multiplier; + scan_normal[1] *= multiplier; + glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]); + } + + free(vertex_shader); + free(fragment_shader); } void CRT::prepare_vertex_array() From 6dfe877c43efd02192cec6029fea51dadf74b1d4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 7 Mar 2016 21:22:47 -0500 Subject: [PATCH 152/307] Basic attempts to organise myself into shape for composite output continue. --- Machines/Electron/Electron.cpp | 2 +- Outputs/CRT/CRTOpenGL.cpp | 153 ++++++++++++++++++++------------- 2 files changed, 93 insertions(+), 62 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index d0a996ce3..5031f594d 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -40,7 +40,7 @@ Machine::Machine() : "float texValue = texture(texID, coordinate).r;" "return vec4(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)), 1.0);" "}"); - _crt->set_output_device(Outputs::CRT::Television); + _crt->set_output_device(Outputs::CRT::Monitor); // _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 memset(_key_states, 0, sizeof(_key_states)); diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 7607b826f..ff9fbb0bd 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -17,7 +17,8 @@ using namespace Outputs; struct CRT::OpenGLState { - std::unique_ptr rgb_shader_program, composite_input_shader_program; + std::unique_ptr rgb_shader_program; + std::unique_ptr composite_input_shader_program, composite_output_shader_program; GLuint arrayBuffer, vertexArray; size_t verticesPerSlice; @@ -122,16 +123,6 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // lock down any further work on the current frame _output_mutex->lock(); - // update uniforms - push_size_uniforms(output_width, output_height); - - // clear the buffer - glClear(GL_COLOR_BUFFER_BIT); - - glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer); -// glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); -// glGetIntegerv(GL_VIEWPORT, results); - // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) @@ -159,6 +150,20 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool } } + // check for anything to decode from composite + if(_composite_src_runs->number_of_vertices) + { + _openGL_state->composite_input_shader_program->bind(); + _composite_src_runs->reset(); + } + +// _output_mutex->unlock(); +// return; + + // reinstate the output framebuffer +// glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); +// glGetIntegerv(GL_VIEWPORT, results); + // ensure array buffer is up to date size_t max_number_of_vertices = 0; for(int c = 0; c < kCRTNumberOfFields; c++) @@ -178,8 +183,20 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool } } + // switch to the output shader + if(_openGL_state->rgb_shader_program) + { + _openGL_state->rgb_shader_program->bind(); + + // update uniforms + push_size_uniforms(output_width, output_height); + + // Ensure we're back on the output framebuffer glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer); + // clear the buffer + glClear(GL_COLOR_BUFFER_BIT); + // draw all sitting frames unsigned int run = (unsigned int)_run_write_pointer; // printf("%d: %zu v %zu\n", run, _run_builders[run]->uploaded_vertices, _run_builders[run]->number_of_vertices); @@ -209,6 +226,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // advance back in time run = (run - 1 + kCRTNumberOfFields) % kCRTNumberOfFields; } + } _output_mutex->unlock(); } @@ -262,7 +280,8 @@ char *CRT::get_input_vertex_shader() "in vec2 phaseAndAmplitude;" "in float phaseTime;" - "uniform vec2 textureSize;" + "uniform vec2 outputTextureSize;" + "uniform vec2 inputTextureSize;" "uniform float phaseCyclesPerTick;" "out vec2 inputPositionVarying;" @@ -270,8 +289,8 @@ char *CRT::get_input_vertex_shader() "void main(void)" "{" - "inputPositionVarying = inputPosition;" - "gl_Position = vec4(outputPosition.x * 2.0 / textureSize.x - 1.0, outputPosition.y * 2.0 / textureSize.y - 1.0, 0.0, 1.0);" + "inputPositionVarying = vec2(inputPositionVarying.x / inputTextureSize.x, (inputPositionVarying.y + 0.5) / inputTextureSize.y);" + "gl_Position = vec4(outputPosition.x * 2.0 / outputTextureSize - 1.0, outputPosition.y * 2.0 / outputTextureSize - 1.0, 0.0, 1.0);" "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAndAmplitude.x) * 2.0 * 3.141592654;" "}"); } @@ -281,6 +300,7 @@ char *CRT::get_input_fragment_shader() const char *composite_shader = _composite_shader; if(!composite_shader) { + // TODO: synthesise an RGB -> (selected colour space) shader } return get_compound_shader( @@ -393,53 +413,6 @@ char *CRT::get_output_fragment_shader(const char *sampling_function) , sampling_function); } - -// const char *const ntscVertexShaderGlobals = -// "out vec2 srcCoordinatesVarying[4];\n" -// "out float phase;\n"; -// -// const char *const ntscVertexShaderBody = -// "phase = srcCoordinates.x * 6.283185308;\n" -// "\n" -// "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" -// "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; - - // assumes y = [0, 1], i and q = [-0.5, 0.5]; therefore i components are multiplied by 1.1914 versus standard matrices, q by 1.0452 -// const char *const yiqToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);"; - - // assumes y = [0,1], u and v = [-0.5, 0.5]; therefore u components are multiplied by 1.14678899082569, v by 0.8130081300813 -// const char *const yuvToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.75213899082569, 2.33040137614679, 0.92669105691057, -0.4720325203252, 0.0);"; - -// const char *const ntscFragmentShaderGlobals = -// "in vec2 srcCoordinatesVarying[4];\n" -// "in float phase;\n" -// "\n" -// "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" -// "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; - -// const char *const ntscFragmentShaderBody = -// "vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" -// "vec4 samples = vec4(" -// " sample(srcCoordinatesVarying[0], angles.x)," -// " sample(srcCoordinatesVarying[1], angles.y)," -// " sample(srcCoordinatesVarying[2], angles.z)," -// " sample(srcCoordinatesVarying[3], angles.w)" -// ");\n" -// "\n" -// "float y = dot(vec4(0.25), samples);\n" -// "samples -= vec4(y);\n" -// "\n" -// "float i = dot(cos(angles), samples);\n" -// "float q = dot(sin(angles), samples);\n" -// "\n" -// "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; - -// dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; -//} - #pragma mark - Shader utilities char *CRT::get_compound_shader(const char *base, const char *insert) @@ -460,6 +433,16 @@ void CRT::prepare_composite_input_shader() if(vertex_shader && fragment_shader) { _openGL_state->composite_input_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + + GLint texIDUniform = _openGL_state->composite_input_shader_program->get_uniform_location("texID"); + GLint inputTextureSizeUniform = _openGL_state->composite_input_shader_program->get_uniform_location("inputTextureSize"); + GLint outputTextureSizeUniform = _openGL_state->composite_input_shader_program->get_uniform_location("outputTextureSize"); + GLint phaseCyclesPerTickUniform = _openGL_state->composite_input_shader_program->get_uniform_location("phaseCyclesPerTick"); + + glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); + glUniform2f(outputTextureSizeUniform, CRTIntermediateBufferWidth, CRTIntermediateBufferHeight); + glUniform2f(inputTextureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); + glUniform1f(phaseCyclesPerTickUniform, (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line)); } free(vertex_shader); free(fragment_shader); @@ -541,3 +524,51 @@ void CRT::set_output_device(CRT::OutputDevice output_device) _composite_src_output_y = 0; } } + + +// const char *const ntscVertexShaderGlobals = +// "out vec2 srcCoordinatesVarying[4];\n" +// "out float phase;\n"; +// +// const char *const ntscVertexShaderBody = +// "phase = srcCoordinates.x * 6.283185308;\n" +// "\n" +// "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" +// "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" +// "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; + + // assumes y = [0, 1], i and q = [-0.5, 0.5]; therefore i components are multiplied by 1.1914 versus standard matrices, q by 1.0452 +// const char *const yiqToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);"; + + // assumes y = [0,1], u and v = [-0.5, 0.5]; therefore u components are multiplied by 1.14678899082569, v by 0.8130081300813 +// const char *const yuvToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.75213899082569, 2.33040137614679, 0.92669105691057, -0.4720325203252, 0.0);"; + +// const char *const ntscFragmentShaderGlobals = +// "in vec2 srcCoordinatesVarying[4];\n" +// "in float phase;\n" +// "\n" +// "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" +// "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; + +// const char *const ntscFragmentShaderBody = +// "vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" +// "vec4 samples = vec4(" +// " sample(srcCoordinatesVarying[0], angles.x)," +// " sample(srcCoordinatesVarying[1], angles.y)," +// " sample(srcCoordinatesVarying[2], angles.z)," +// " sample(srcCoordinatesVarying[3], angles.w)" +// ");\n" +// "\n" +// "float y = dot(vec4(0.25), samples);\n" +// "samples -= vec4(y);\n" +// "\n" +// "float i = dot(cos(angles), samples);\n" +// "float q = dot(sin(angles), samples);\n" +// "\n" +// "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; + +// dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; +//} + From bb09a5f58c3cb799cefae79369a06448451ac8c6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 7 Mar 2016 21:42:21 -0500 Subject: [PATCH 153/307] The attribute locations don't really need to be stored. They can be transient. --- Outputs/CRT/CRT.hpp | 2 +- Outputs/CRT/CRTOpenGL.cpp | 103 ++++++++++++++++++++++++++------------ 2 files changed, 72 insertions(+), 33 deletions(-) diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 560c124ae..a9b7b9ee0 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -374,7 +374,7 @@ class CRT { // Methods used by the OpenGL code void prepare_rgb_output_shader(); void prepare_composite_input_shader(); - void prepare_vertex_array(); + void prepare_output_vertex_array(); void push_size_uniforms(unsigned int output_width, unsigned int output_height); char *get_output_vertex_shader(); diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index ff9fbb0bd..728336e98 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -19,13 +19,9 @@ using namespace Outputs; struct CRT::OpenGLState { std::unique_ptr rgb_shader_program; std::unique_ptr composite_input_shader_program, composite_output_shader_program; - GLuint arrayBuffer, vertexArray; - size_t verticesPerSlice; - GLint positionAttribute; - GLint textureCoordinatesAttribute; - GLint lateralAttribute; - GLint timestampAttribute; + GLuint output_array_buffer, output_vertex_array; + size_t output_vertices_per_slice; GLint windowSizeUniform, timestampBaseUniform; GLint boundsOriginUniform, boundsSizeUniform; @@ -91,16 +87,16 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[buffer].data); } - glGenVertexArrays(1, &_openGL_state->vertexArray); - glGenBuffers(1, &_openGL_state->arrayBuffer); - _openGL_state->verticesPerSlice = 0; + glGenVertexArrays(1, &_openGL_state->output_vertex_array); + glGenBuffers(1, &_openGL_state->output_array_buffer); + _openGL_state->output_vertices_per_slice = 0; prepare_composite_input_shader(); prepare_rgb_output_shader(); - glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->arrayBuffer); - glBindVertexArray(_openGL_state->vertexArray); - prepare_vertex_array(); + glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->output_array_buffer); + glBindVertexArray(_openGL_state->output_vertex_array); + prepare_output_vertex_array(); // This should return either an actual framebuffer number, if this is a target with a framebuffer intended for output, // or 0 if no framebuffer is bound, in which case 0 is also what we want to supply to bind the implied framebuffer. So @@ -165,20 +161,21 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // glGetIntegerv(GL_VIEWPORT, results); // ensure array buffer is up to date + glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->output_array_buffer); size_t max_number_of_vertices = 0; for(int c = 0; c < kCRTNumberOfFields; c++) { max_number_of_vertices = std::max(max_number_of_vertices, _run_builders[c]->number_of_vertices); } - if(_openGL_state->verticesPerSlice < max_number_of_vertices) + if(_openGL_state->output_vertices_per_slice < max_number_of_vertices) { - _openGL_state->verticesPerSlice = max_number_of_vertices; + _openGL_state->output_vertices_per_slice = max_number_of_vertices; glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * kCRTOutputVertexSize * kCRTOutputVertexSize), NULL, GL_STREAM_DRAW); for(unsigned int c = 0; c < kCRTNumberOfFields; c++) { uint8_t *data = &_run_builders[c]->_runs[0]; - glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * _openGL_state->verticesPerSlice * kCRTOutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * kCRTOutputVertexSize), data); + glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * _openGL_state->output_vertices_per_slice * kCRTOutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * kCRTOutputVertexSize), data); _run_builders[c]->uploaded_vertices = _run_builders[c]->number_of_vertices; } } @@ -214,13 +211,13 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool { uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * kCRTOutputVertexSize]; glBufferSubData(GL_ARRAY_BUFFER, - (GLsizeiptr)(((run * _openGL_state->verticesPerSlice) + _run_builders[run]->uploaded_vertices) * kCRTOutputVertexSize), + (GLsizeiptr)(((run * _openGL_state->output_vertices_per_slice) + _run_builders[run]->uploaded_vertices) * kCRTOutputVertexSize), (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * kCRTOutputVertexSize), data); _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; } // draw this frame - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(run * _openGL_state->verticesPerSlice), (GLsizei)_run_builders[run]->number_of_vertices); + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(run * _openGL_state->output_vertices_per_slice), (GLsizei)_run_builders[run]->number_of_vertices); } // advance back in time @@ -448,6 +445,45 @@ void CRT::prepare_composite_input_shader() free(fragment_shader); } +/*void CRT::prepare_output_shader(char *fragment_shader) +{ + char *vertex_shader = get_output_vertex_shader(); + if(vertex_shader && fragment_shader) + { + _openGL_state->rgb_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + + _openGL_state->rgb_shader_program->bind(); + + _openGL_state->windowSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("windowSize"); + _openGL_state->boundsSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsSize"); + _openGL_state->boundsOriginUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsOrigin"); + _openGL_state->timestampBaseUniform = _openGL_state->rgb_shader_program->get_uniform_location("timestampBase"); + + GLint texIDUniform = _openGL_state->rgb_shader_program->get_uniform_location("texID"); + GLint shadowMaskTexIDUniform = _openGL_state->rgb_shader_program->get_uniform_location("shadowMaskTexID"); + GLint textureSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("textureSize"); + GLint ticksPerFrameUniform = _openGL_state->rgb_shader_program->get_uniform_location("ticksPerFrame"); + GLint scanNormalUniform = _openGL_state->rgb_shader_program->get_uniform_location("scanNormal"); + GLint positionConversionUniform = _openGL_state->rgb_shader_program->get_uniform_location("positionConversion"); + + glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); + glUniform1i(shadowMaskTexIDUniform, 1); + glUniform2f(textureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); + glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); + glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); + + float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); + float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; + float multiplier = (float)_horizontal_flywheel->get_standard_period() / ((float)_height_of_display * (float)_horizontal_flywheel->get_scan_period()); + scan_normal[0] *= multiplier; + scan_normal[1] *= multiplier; + glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]); + } + + free(vertex_shader); + free(fragment_shader); +}*/ + void CRT::prepare_rgb_output_shader() { char *vertex_shader = get_output_vertex_shader(); @@ -459,11 +495,6 @@ void CRT::prepare_rgb_output_shader() _openGL_state->rgb_shader_program->bind(); - _openGL_state->positionAttribute = _openGL_state->rgb_shader_program->get_attrib_location("position"); - _openGL_state->textureCoordinatesAttribute = _openGL_state->rgb_shader_program->get_attrib_location("srcCoordinates"); - _openGL_state->lateralAttribute = _openGL_state->rgb_shader_program->get_attrib_location("lateral"); - _openGL_state->timestampAttribute = _openGL_state->rgb_shader_program->get_attrib_location("timestamp"); - _openGL_state->windowSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("windowSize"); _openGL_state->boundsSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsSize"); _openGL_state->boundsOriginUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsOrigin"); @@ -494,18 +525,26 @@ void CRT::prepare_rgb_output_shader() free(fragment_shader); } -void CRT::prepare_vertex_array() +void CRT::prepare_output_vertex_array() { - glEnableVertexAttribArray((GLuint)_openGL_state->positionAttribute); - glEnableVertexAttribArray((GLuint)_openGL_state->textureCoordinatesAttribute); - glEnableVertexAttribArray((GLuint)_openGL_state->lateralAttribute); - glEnableVertexAttribArray((GLuint)_openGL_state->timestampAttribute); + if(_openGL_state->rgb_shader_program) + { + GLint positionAttribute = _openGL_state->rgb_shader_program->get_attrib_location("position"); + GLint textureCoordinatesAttribute = _openGL_state->rgb_shader_program->get_attrib_location("srcCoordinates"); + GLint lateralAttribute = _openGL_state->rgb_shader_program->get_attrib_location("lateral"); + GLint timestampAttribute = _openGL_state->rgb_shader_program->get_attrib_location("timestamp"); - const GLsizei vertexStride = kCRTOutputVertexSize; - glVertexAttribPointer((GLuint)_openGL_state->positionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfPosition); - glVertexAttribPointer((GLuint)_openGL_state->textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfTexCoord); - glVertexAttribPointer((GLuint)_openGL_state->timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfTimestamp); - glVertexAttribPointer((GLuint)_openGL_state->lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfLateral); + glEnableVertexAttribArray((GLuint)positionAttribute); + glEnableVertexAttribArray((GLuint)textureCoordinatesAttribute); + glEnableVertexAttribArray((GLuint)lateralAttribute); + glEnableVertexAttribArray((GLuint)timestampAttribute); + + const GLsizei vertexStride = kCRTOutputVertexSize; + glVertexAttribPointer((GLuint)positionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfPosition); + glVertexAttribPointer((GLuint)textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfTexCoord); + glVertexAttribPointer((GLuint)timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfTimestamp); + glVertexAttribPointer((GLuint)lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfLateral); + } } #pragma mark - Configuration From c7976dd6576c51cc14cd43118524cee7fc1d66d4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Mar 2016 20:49:07 -0500 Subject: [PATCH 154/307] Started trying to clean up my Outputs namespace by moving stuff related to the CRT underneath a separate subnamespace. --- Machines/Atari2600/Atari2600.cpp | 2 +- Machines/Atari2600/Atari2600.hpp | 4 +- Machines/Electron/Electron.cpp | 2 +- Machines/Electron/Electron.hpp | 4 +- Outputs/CRT/CRT.cpp | 54 ++++++------ Outputs/CRT/CRT.hpp | 140 ++++++++++++++++--------------- Outputs/CRT/CRTBuilders.cpp | 8 +- Outputs/CRT/CRTOpenGL.cpp | 64 +++++++------- Outputs/CRT/CRTOpenGL.hpp | 36 ++++---- Outputs/CRT/Flywheel.hpp | 2 + 10 files changed, 166 insertions(+), 150 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index f76b3033e..2e2e16755 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -24,7 +24,7 @@ Machine::Machine() : _piaDataValue{0xff, 0xff}, _tiaInputValue{0xff, 0xff} { - _crt = new Outputs::CRT(228, 1, Outputs::CRT::DisplayType::NTSC60, 1, 2); + _crt = new Outputs::CRT::CRT(228, 1, Outputs::CRT::DisplayType::NTSC60, 1, 2); _crt->set_composite_sampling_function( "float composite_sample(vec2 coordinate, float phase)\n" "{\n" diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 8c77f8605..c3b5d6209 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -29,7 +29,7 @@ class Machine: public CPU6502::Processor { void set_digital_input(Atari2600DigitalInput input, bool state); - Outputs::CRT *get_crt() { return _crt; } + Outputs::CRT::CRT *get_crt() { return _crt; } private: uint8_t *_rom, *_romPages[4], _ram[128]; @@ -90,7 +90,7 @@ class Machine: public CPU6502::Processor { void output_pixels(unsigned int count); void get_output_pixel(uint8_t *pixel, int offset); - Outputs::CRT *_crt; + Outputs::CRT::CRT *_crt; // latched output state unsigned int _lastOutputStateDuration; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 5031f594d..90d41a394 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -32,7 +32,7 @@ Machine::Machine() : _audioOutputPositionError(0), _currentOutputLine(0), _is_odd_field(false), - _crt(std::unique_ptr(new Outputs::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1, 1))) + _crt(std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1, 1))) { _crt->set_rgb_sampling_function( "vec4 rgb_sample(vec2 coordinate)" diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index e9fb3c1d0..75758366c 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -147,7 +147,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { void set_key_state(Key key, bool isPressed); - Outputs::CRT *get_crt() { return _crt.get(); } + Outputs::CRT::CRT *get_crt() { return _crt.get(); } Outputs::Speaker *get_speaker() { return &_speaker; } virtual void tape_did_change_interrupt_status(Tape *tape); @@ -201,7 +201,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { Tape _tape; // Outputs. - std::unique_ptr _crt; + std::unique_ptr _crt; Speaker _speaker; }; diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 4f524ad3c..733f1da82 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -11,7 +11,7 @@ #include #include -using namespace Outputs; +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) { @@ -39,8 +39,8 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di _sync_capacitor_charge_threshold = ((syncCapacityLineChargeThreshold * _cycles_per_line) * 50) >> 7; // create the two flywheels - _horizontal_flywheel = std::unique_ptr(new Outputs::Flywheel(_cycles_per_line, (millisecondsHorizontalRetraceTime * _cycles_per_line) >> 6)); - _vertical_flywheel = std::unique_ptr(new Outputs::Flywheel(_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * _cycles_per_line)); + _horizontal_flywheel = std::unique_ptr(new Flywheel(_cycles_per_line, (millisecondsHorizontalRetraceTime * _cycles_per_line) >> 6)); + _vertical_flywheel = std::unique_ptr(new Flywheel(_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * _cycles_per_line)); // 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); @@ -63,12 +63,12 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display void CRT::allocate_buffers(unsigned int number, va_list sizes) { - _run_builders = new CRTRunBuilder *[kCRTNumberOfFields]; - for(int builder = 0; builder < kCRTNumberOfFields; builder++) + _run_builders = new CRTRunBuilder *[NumberOfFields]; + for(int builder = 0; builder < NumberOfFields; builder++) { - _run_builders[builder] = new CRTRunBuilder(kCRTOutputVertexSize); + _run_builders[builder] = new CRTRunBuilder(OutputVertexSize); } - _composite_src_runs = std::unique_ptr(new CRTRunBuilder(kCRTInputVertexSize)); + _composite_src_runs = std::unique_ptr(new CRTRunBuilder(InputVertexSize)); va_list va; va_copy(va, sizes); @@ -92,7 +92,7 @@ CRT::CRT(unsigned int common_output_divisor) : CRT::~CRT() { - for(int builder = 0; builder < kCRTNumberOfFields; builder++) + for(int builder = 0; builder < NumberOfFields; builder++) { delete _run_builders[builder]; } @@ -132,20 +132,20 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, return _horizontal_flywheel->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced); } -#define output_position_x(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfPosition + 0]) -#define output_position_y(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfPosition + 2]) -#define output_tex_x(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTexCoord + 0]) -#define output_tex_y(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTexCoord + 2]) -#define output_lateral(v) next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfLateral] -#define output_timestamp(v) (*(uint32_t *)&next_run[kCRTOutputVertexSize*v + kCRTOutputVertexOffsetOfTimestamp]) +#define output_position_x(v) (*(uint16_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfPosition + 0]) +#define output_position_y(v) (*(uint16_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfPosition + 2]) +#define output_tex_x(v) (*(uint16_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTexCoord + 0]) +#define output_tex_y(v) (*(uint16_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTexCoord + 2]) +#define output_lateral(v) next_run[OutputVertexSize*v + OutputVertexOffsetOfLateral] +#define output_timestamp(v) (*(uint32_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTimestamp]) -#define input_input_position_x(v) (*(uint16_t *)&next_run[kCRTInputVertexSize*v + kCRTInputVertexOffsetOfInputPosition + 0]) -#define input_input_position_y(v) (*(uint16_t *)&next_run[kCRTInputVertexSize*v + kCRTInputVertexOffsetOfInputPosition + 2]) -#define input_output_position_x(v) (*(uint16_t *)&next_run[kCRTInputVertexSize*v + kCRTInputVertexOffsetOfOutputPosition + 0]) -#define input_output_position_y(v) (*(uint16_t *)&next_run[kCRTInputVertexSize*v + kCRTInputVertexOffsetOfOutputPosition + 2]) -#define input_phase(v) next_run[kCRTOutputVertexSize*v + kCRTInputVertexOffsetOfPhaseAndAmplitude + 0] -#define input_amplitude(v) next_run[kCRTOutputVertexSize*v + kCRTInputVertexOffsetOfPhaseAndAmplitude + 1] -#define input_phase_time(v) (*(uint16_t *)&next_run[kCRTOutputVertexSize*v + kCRTInputVertexOffsetOfPhaseTime]) +#define input_input_position_x(v) (*(uint16_t *)&next_run[InputVertexSize*v + InputVertexOffsetOfInputPosition + 0]) +#define input_input_position_y(v) (*(uint16_t *)&next_run[InputVertexSize*v + InputVertexOffsetOfInputPosition + 2]) +#define input_output_position_x(v) (*(uint16_t *)&next_run[InputVertexSize*v + InputVertexOffsetOfOutputPosition + 0]) +#define input_output_position_y(v) (*(uint16_t *)&next_run[InputVertexSize*v + InputVertexOffsetOfOutputPosition + 2]) +#define input_phase(v) next_run[OutputVertexSize*v + InputVertexOffsetOfPhaseAndAmplitude + 0] +#define input_amplitude(v) next_run[OutputVertexSize*v + InputVertexOffsetOfPhaseAndAmplitude + 1] +#define input_phase_time(v) (*(uint16_t *)&next_run[OutputVertexSize*v + InputVertexOffsetOfPhaseTime]) void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type, uint16_t tex_x, uint16_t tex_y) { @@ -171,7 +171,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(is_output_segment) { _output_mutex->lock(); - next_run = (_output_device == CRT::Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); + next_run = (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); } // Vertex output is arranged for triangle strips, as: @@ -181,7 +181,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // [0/1] 3 if(next_run) { - if(_output_device == CRT::Monitor) + if(_output_device == Monitor) { // set the type, initial raster position and type of this run output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); @@ -226,7 +226,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // if this is a data run then advance the buffer pointer if(type == Type::Data && source_divider) tex_x += next_run_length / (_time_multiplier * source_divider); - if(_output_device == CRT::Monitor) + if(_output_device == Monitor) { // store the final raster position output_position_x(3) = output_position_x(4) = output_position_x(5) = (uint16_t)_horizontal_flywheel->get_current_output_position(); @@ -247,7 +247,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi } // if this is horizontal retrace then advance the output line counter and bookend an output run - if(_output_device == CRT::Television) + if(_output_device == Television) { Flywheel::SyncEvent honoured_event = Flywheel::SyncEvent::None; if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event != Flywheel::SyncEvent::None) honoured_event = next_vertical_sync_event; @@ -274,7 +274,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::EndRetrace) { - _composite_src_output_y = (_composite_src_output_y + 1) % CRTIntermediateBufferHeight; + _composite_src_output_y = (_composite_src_output_y + 1) % IntermediateBufferHeight; } } @@ -284,7 +284,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // TODO: how to communicate did_detect_vsync? Bring the delegate back? // _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); - _run_write_pointer = (_run_write_pointer + 1)%kCRTNumberOfFields; + _run_write_pointer = (_run_write_pointer + 1)%NumberOfFields; _run_builders[_run_write_pointer]->reset(); } } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index a9b7b9ee0..434dc6946 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -18,6 +18,7 @@ #include "Flywheel.hpp" namespace Outputs { +namespace CRT { struct Rect { struct { @@ -33,25 +34,78 @@ struct Rect { origin({.x = x, .y = y}), size({.width = width, .height =height}) {} }; +enum DisplayType { + PAL50, + NTSC60 +}; + +enum ColourSpace { + YIQ, + YUV +}; + +enum OutputDevice { + Monitor, + Television +}; + +struct CRTInputBufferBuilder { + CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes); + ~CRTInputBufferBuilder(); + + void allocate_write_area(size_t required_length); + void reduce_previous_allocation_to(size_t actual_length); + uint8_t *get_write_target_for_buffer(int buffer); + + // a pointer to the section of content buffer currently being + // returned and to where the next section will begin + uint16_t _next_write_x_position, _next_write_y_position; + uint16_t _write_x_position, _write_y_position; + size_t _write_target_pointer; + size_t _last_allocation_amount; + + struct Buffer { + uint8_t *data; + size_t bytes_per_pixel; + } *buffers; + unsigned int number_of_buffers; + + // Storage for the amount of buffer uploaded so far; initialised correctly by the buffer + // builder but otherwise entrusted to the CRT to update. + unsigned int last_uploaded_line; +}; + +struct CRTRunBuilder { + CRTRunBuilder(size_t vertex_size) : _vertex_size(vertex_size) { reset(); } + + // Resets the run builder. + void reset(); + + // Getter for new storage plus backing storage; in RGB mode input runs will map directly + // from the input buffer to the screen. In composite mode input runs will map from the + // input buffer to the processing buffer, and output runs will map from the processing + // buffer to the screen. + uint8_t *get_next_run(size_t number_of_vertices); + std::vector _runs; + + // Container for total length in cycles of all contained runs. + uint32_t duration; + + // Storage for the length of run data uploaded so far; reset to zero by reset but otherwise + // entrusted to the CRT to update. + size_t uploaded_vertices; + size_t number_of_vertices; + + private: + size_t _vertex_size; +}; + +struct OpenGLState; + class CRT { public: ~CRT(); - enum DisplayType { - PAL50, - NTSC60 - }; - - enum ColourSpace { - YIQ, - YUV - }; - - enum OutputDevice { - Monitor, - Television - }; - /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. The requested number of buffers, each with the requested number of bytes per pixel, is created for the machine to write raw pixel data to. @@ -259,7 +313,7 @@ class CRT { Rect _visible_area; // the two flywheels regulating scanning - std::unique_ptr _horizontal_flywheel, _vertical_flywheel; + std::unique_ptr _horizontal_flywheel, _vertical_flywheel; uint16_t _vertical_flywheel_output_divider; // elements of sync separation @@ -295,57 +349,6 @@ class CRT { }; void output_scan(Scan *scan); - struct CRTRunBuilder { - CRTRunBuilder(size_t vertex_size) : _vertex_size(vertex_size) { reset(); } - - // Resets the run builder. - void reset(); - - // Getter for new storage plus backing storage; in RGB mode input runs will map directly - // from the input buffer to the screen. In composite mode input runs will map from the - // input buffer to the processing buffer, and output runs will map from the processing - // buffer to the screen. - uint8_t *get_next_run(size_t number_of_vertices); - std::vector _runs; - - // Container for total length in cycles of all contained runs. - uint32_t duration; - - // Storage for the length of run data uploaded so far; reset to zero by reset but otherwise - // entrusted to the CRT to update. - size_t uploaded_vertices; - size_t number_of_vertices; - - private: - size_t _vertex_size; - }; - - struct CRTInputBufferBuilder { - CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes); - ~CRTInputBufferBuilder(); - - void allocate_write_area(size_t required_length); - void reduce_previous_allocation_to(size_t actual_length); - uint8_t *get_write_target_for_buffer(int buffer); - - // a pointer to the section of content buffer currently being - // returned and to where the next section will begin - uint16_t _next_write_x_position, _next_write_y_position; - uint16_t _write_x_position, _write_y_position; - size_t _write_target_pointer; - size_t _last_allocation_amount; - - struct Buffer { - uint8_t *data; - size_t bytes_per_pixel; - } *buffers; - unsigned int number_of_buffers; - - // Storage for the amount of buffer uploaded so far; initialised correctly by the buffer - // builder but otherwise entrusted to the CRT to update. - unsigned int last_uploaded_line; - }; - // the run and input data buffers std::unique_ptr _buffer_builder; CRTRunBuilder **_run_builders; @@ -360,7 +363,6 @@ class CRT { bool _is_writing_composite_run; // OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here. - struct OpenGLState; OpenGLState *_openGL_state; // Other things the caller may have provided. @@ -390,6 +392,6 @@ class CRT { }; } - +} #endif /* CRT_cpp */ diff --git a/Outputs/CRT/CRTBuilders.cpp b/Outputs/CRT/CRTBuilders.cpp index bf4eee8bc..342e3e963 100644 --- a/Outputs/CRT/CRTBuilders.cpp +++ b/Outputs/CRT/CRTBuilders.cpp @@ -23,7 +23,7 @@ CRT::CRTInputBufferBuilder::CRTInputBufferBuilder(unsigned int number_of_buffers for(int buffer = 0; buffer < number_of_buffers; buffer++) { buffers[buffer].bytes_per_pixel = va_arg(buffer_sizes, unsigned int); - buffers[buffer].data = new uint8_t[CRTInputBufferBuilderWidth * CRTInputBufferBuilderHeight * buffers[buffer].bytes_per_pixel]; + buffers[buffer].data = new uint8_t[InputBufferBuilderWidth * InputBufferBuilderHeight * buffers[buffer].bytes_per_pixel]; } _next_write_x_position = _next_write_y_position = 0; @@ -42,15 +42,15 @@ void CRT::CRTInputBufferBuilder::allocate_write_area(size_t required_length) { _last_allocation_amount = required_length; - if(_next_write_x_position + required_length + 2 > CRTInputBufferBuilderWidth) + if(_next_write_x_position + required_length + 2 > InputBufferBuilderWidth) { _next_write_x_position = 0; - _next_write_y_position = (_next_write_y_position+1)%CRTInputBufferBuilderHeight; + _next_write_y_position = (_next_write_y_position+1)%InputBufferBuilderHeight; } _write_x_position = _next_write_x_position + 1; _write_y_position = _next_write_y_position; - _write_target_pointer = (_write_y_position * CRTInputBufferBuilderWidth) + _write_x_position; + _write_target_pointer = (_write_y_position * InputBufferBuilderWidth) + _write_x_position; _next_write_x_position += required_length + 2; } diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/CRTOpenGL.cpp index 728336e98..0b811a199 100644 --- a/Outputs/CRT/CRTOpenGL.cpp +++ b/Outputs/CRT/CRTOpenGL.cpp @@ -14,9 +14,10 @@ #include "Shader.hpp" #include "CRTOpenGL.hpp" -using namespace Outputs; +namespace Outputs { +namespace CRT { -struct CRT::OpenGLState { +struct OpenGLState { std::unique_ptr rgb_shader_program; std::unique_ptr composite_input_shader_program, composite_output_shader_program; @@ -35,6 +36,11 @@ struct CRT::OpenGLState { std::unique_ptr filteredTexture; // receives filtered YIQ or YUV }; +} +} + +using namespace Outputs::CRT; + namespace { static const GLenum first_supplied_buffer_texture_unit = 3; } @@ -84,7 +90,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); GLenum format = formatForDepth(_buffer_builder->buffers[buffer].bytes_per_pixel); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[buffer].data); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[buffer].data); } glGenVertexArrays(1, &_openGL_state->output_vertex_array); @@ -105,11 +111,11 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // Create intermediate textures and bind to slots 0, 1 and 2 glActiveTexture(GL_TEXTURE0); - _openGL_state->compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(CRTIntermediateBufferWidth, CRTIntermediateBufferHeight)); + _openGL_state->compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); glActiveTexture(GL_TEXTURE1); - _openGL_state->filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(CRTIntermediateBufferWidth, CRTIntermediateBufferHeight)); + _openGL_state->filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); glActiveTexture(GL_TEXTURE2); - _openGL_state->filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(CRTIntermediateBufferWidth, CRTIntermediateBufferHeight)); + _openGL_state->filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); } // glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); @@ -129,9 +135,9 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, - CRTInputBufferBuilderWidth, (GLint)(CRTInputBufferBuilderHeight - _buffer_builder->last_uploaded_line), + InputBufferBuilderWidth, (GLint)(InputBufferBuilderHeight - _buffer_builder->last_uploaded_line), format, GL_UNSIGNED_BYTE, - &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * CRTInputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); + &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); _buffer_builder->last_uploaded_line = 0; } @@ -139,9 +145,9 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, - CRTInputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), + InputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), format, GL_UNSIGNED_BYTE, - &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * CRTInputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); + &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; } } @@ -163,19 +169,19 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // ensure array buffer is up to date glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->output_array_buffer); size_t max_number_of_vertices = 0; - for(int c = 0; c < kCRTNumberOfFields; c++) + for(int c = 0; c < NumberOfFields; c++) { max_number_of_vertices = std::max(max_number_of_vertices, _run_builders[c]->number_of_vertices); } if(_openGL_state->output_vertices_per_slice < max_number_of_vertices) { _openGL_state->output_vertices_per_slice = max_number_of_vertices; - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * kCRTOutputVertexSize * kCRTOutputVertexSize), NULL, GL_STREAM_DRAW); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * OutputVertexSize * OutputVertexSize), NULL, GL_STREAM_DRAW); - for(unsigned int c = 0; c < kCRTNumberOfFields; c++) + for(unsigned int c = 0; c < NumberOfFields; c++) { uint8_t *data = &_run_builders[c]->_runs[0]; - glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * _openGL_state->output_vertices_per_slice * kCRTOutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * kCRTOutputVertexSize), data); + glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * _openGL_state->output_vertices_per_slice * OutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * OutputVertexSize), data); _run_builders[c]->uploaded_vertices = _run_builders[c]->number_of_vertices; } } @@ -198,7 +204,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool unsigned int run = (unsigned int)_run_write_pointer; // printf("%d: %zu v %zu\n", run, _run_builders[run]->uploaded_vertices, _run_builders[run]->number_of_vertices); GLint total_age = 0; - for(int c = 0; c < kCRTNumberOfFields; c++) + for(int c = 0; c < NumberOfFields; c++) { // update the total age at the start of this set of runs total_age += _run_builders[run]->duration; @@ -209,10 +215,10 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) { - uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * kCRTOutputVertexSize]; + uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * OutputVertexSize]; glBufferSubData(GL_ARRAY_BUFFER, - (GLsizeiptr)(((run * _openGL_state->output_vertices_per_slice) + _run_builders[run]->uploaded_vertices) * kCRTOutputVertexSize), - (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * kCRTOutputVertexSize), data); + (GLsizeiptr)(((run * _openGL_state->output_vertices_per_slice) + _run_builders[run]->uploaded_vertices) * OutputVertexSize), + (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * OutputVertexSize), data); _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; } @@ -221,7 +227,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool } // advance back in time - run = (run - 1 + kCRTNumberOfFields) % kCRTNumberOfFields; + run = (run - 1 + NumberOfFields) % NumberOfFields; } } @@ -437,8 +443,8 @@ void CRT::prepare_composite_input_shader() GLint phaseCyclesPerTickUniform = _openGL_state->composite_input_shader_program->get_uniform_location("phaseCyclesPerTick"); glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); - glUniform2f(outputTextureSizeUniform, CRTIntermediateBufferWidth, CRTIntermediateBufferHeight); - glUniform2f(inputTextureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); + glUniform2f(outputTextureSizeUniform, IntermediateBufferWidth, IntermediateBufferHeight); + glUniform2f(inputTextureSizeUniform, InputBufferBuilderWidth, InputBufferBuilderHeight); glUniform1f(phaseCyclesPerTickUniform, (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line)); } free(vertex_shader); @@ -509,7 +515,7 @@ void CRT::prepare_rgb_output_shader() glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); glUniform1i(shadowMaskTexIDUniform, 1); - glUniform2f(textureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); + glUniform2f(textureSizeUniform, InputBufferBuilderWidth, InputBufferBuilderHeight); glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); @@ -539,23 +545,23 @@ void CRT::prepare_output_vertex_array() glEnableVertexAttribArray((GLuint)lateralAttribute); glEnableVertexAttribArray((GLuint)timestampAttribute); - const GLsizei vertexStride = kCRTOutputVertexSize; - glVertexAttribPointer((GLuint)positionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfPosition); - glVertexAttribPointer((GLuint)textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfTexCoord); - glVertexAttribPointer((GLuint)timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfTimestamp); - glVertexAttribPointer((GLuint)lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)kCRTOutputVertexOffsetOfLateral); + const GLsizei vertexStride = OutputVertexSize; + glVertexAttribPointer((GLuint)positionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfPosition); + glVertexAttribPointer((GLuint)textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfTexCoord); + glVertexAttribPointer((GLuint)timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfTimestamp); + glVertexAttribPointer((GLuint)lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfLateral); } } #pragma mark - Configuration -void CRT::set_output_device(CRT::OutputDevice output_device) +void CRT::set_output_device(OutputDevice output_device) { if (_output_device != output_device) { _output_device = output_device; - for(int builder = 0; builder < kCRTNumberOfFields; builder++) + for(int builder = 0; builder < NumberOfFields; builder++) { _run_builders[builder]->reset(); } diff --git a/Outputs/CRT/CRTOpenGL.hpp b/Outputs/CRT/CRTOpenGL.hpp index dd28b5f0d..35e340ca9 100644 --- a/Outputs/CRT/CRTOpenGL.hpp +++ b/Outputs/CRT/CRTOpenGL.hpp @@ -9,35 +9,41 @@ #ifndef CRTOpenGL_h #define CRTOpenGL_h +namespace Outputs { +namespace CRT { + // Output vertices are those used to copy from an input buffer — whether it describes data that maps directly to RGB // or is one of the intermediate buffers that we've used to convert from composite towards RGB. -const size_t kCRTOutputVertexOffsetOfPosition = 0; -const size_t kCRTOutputVertexOffsetOfTexCoord = 4; -const size_t kCRTOutputVertexOffsetOfTimestamp = 8; -const size_t kCRTOutputVertexOffsetOfLateral = 12; +const size_t OutputVertexOffsetOfPosition = 0; +const size_t OutputVertexOffsetOfTexCoord = 4; +const size_t OutputVertexOffsetOfTimestamp = 8; +const size_t OutputVertexOffsetOfLateral = 12; -const size_t kCRTOutputVertexSize = 16; +const size_t OutputVertexSize = 16; // Input vertices, used only in composite mode, map from the input buffer to temporary buffer locations; such // remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour -const size_t kCRTInputVertexOffsetOfInputPosition = 0; -const size_t kCRTInputVertexOffsetOfOutputPosition = 4; -const size_t kCRTInputVertexOffsetOfPhaseAndAmplitude = 8; -const size_t kCRTInputVertexOffsetOfPhaseTime = 12; +const size_t InputVertexOffsetOfInputPosition = 0; +const size_t InputVertexOffsetOfOutputPosition = 4; +const size_t InputVertexOffsetOfPhaseAndAmplitude = 8; +const size_t InputVertexOffsetOfPhaseTime = 12; -const size_t kCRTInputVertexSize = 16; +const size_t InputVertexSize = 16; // These constants hold the size of the rolling buffer to which the CPU writes -const int CRTInputBufferBuilderWidth = 2048; -const int CRTInputBufferBuilderHeight = 1024; +const int InputBufferBuilderWidth = 2048; +const int InputBufferBuilderHeight = 1024; // This is the size of the intermediate buffers used during composite to RGB conversion -const int CRTIntermediateBufferWidth = 2048; -const int CRTIntermediateBufferHeight = 2048; +const int IntermediateBufferWidth = 2048; +const int IntermediateBufferHeight = 2048; // Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track // run age; that therefore creates a discrete number of fields that are stored. This number should be the // number of historic fields that are required fully to -const int kCRTNumberOfFields = 3; +const int NumberOfFields = 3; + +} +} #endif /* CRTOpenGL_h */ diff --git a/Outputs/CRT/Flywheel.hpp b/Outputs/CRT/Flywheel.hpp index e72255f1f..e10d621fd 100644 --- a/Outputs/CRT/Flywheel.hpp +++ b/Outputs/CRT/Flywheel.hpp @@ -10,6 +10,7 @@ #define Flywheel_hpp namespace Outputs { +namespace CRT { /*! Provides timing for a two-phase signal consisting of a retrace phase followed by a scan phase, @@ -211,6 +212,7 @@ struct Flywheel */ }; +} } #endif /* Flywheel_hpp */ From 14b2927275bcf7a819d3dd27ebc461f89cfae789 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Mar 2016 20:59:16 -0500 Subject: [PATCH 155/307] Made further attempts to tidy up; trying to demarcate between public interface and private. --- .../Clock Signal.xcodeproj/project.pbxproj | 68 ++++++++++++------- Outputs/CRT/CRT.hpp | 59 ++-------------- .../CRTInputBufferBuilder.cpp} | 48 +++---------- .../CRT/Internals/CRTInputBufferBuilder.hpp | 48 +++++++++++++ Outputs/CRT/{ => Internals}/CRTOpenGL.cpp | 0 Outputs/CRT/{ => Internals}/CRTOpenGL.hpp | 0 Outputs/CRT/Internals/CRTRunBuilder.cpp | 33 +++++++++ Outputs/CRT/Internals/CRTRunBuilder.hpp | 43 ++++++++++++ Outputs/CRT/{ => Internals}/Flywheel.hpp | 0 Outputs/CRT/{ => Internals}/OpenGL.hpp | 0 Outputs/CRT/{ => Internals}/Shader.cpp | 0 Outputs/CRT/{ => Internals}/Shader.hpp | 0 Outputs/CRT/{ => Internals}/TextureTarget.cpp | 0 Outputs/CRT/{ => Internals}/TextureTarget.hpp | 0 14 files changed, 181 insertions(+), 118 deletions(-) rename Outputs/CRT/{CRTBuilders.cpp => Internals/CRTInputBufferBuilder.cpp} (62%) create mode 100644 Outputs/CRT/Internals/CRTInputBufferBuilder.hpp rename Outputs/CRT/{ => Internals}/CRTOpenGL.cpp (100%) rename Outputs/CRT/{ => Internals}/CRTOpenGL.hpp (100%) create mode 100644 Outputs/CRT/Internals/CRTRunBuilder.cpp create mode 100644 Outputs/CRT/Internals/CRTRunBuilder.hpp rename Outputs/CRT/{ => Internals}/Flywheel.hpp (100%) rename Outputs/CRT/{ => Internals}/OpenGL.hpp (100%) rename Outputs/CRT/{ => Internals}/Shader.cpp (100%) rename Outputs/CRT/{ => Internals}/Shader.hpp (100%) rename Outputs/CRT/{ => Internals}/TextureTarget.cpp (100%) rename Outputs/CRT/{ => Internals}/TextureTarget.hpp (100%) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 06a8aad27..049ef77ea 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -8,15 +8,12 @@ /* Begin PBXBuildFile section */ 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; }; - 4B0CCC471C62D1A8001CAC5F /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */; }; 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */; }; 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; }; 4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; }; 4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; }; - 4B2039941C67E20B001375C3 /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2039921C67E20B001375C3 /* TextureTarget.cpp */; }; - 4B2039991C67FA92001375C3 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2039971C67FA92001375C3 /* Shader.cpp */; }; 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; }; 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; @@ -31,7 +28,6 @@ 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; - 4B7BFEFE1C6446EF00089C1C /* CRTBuilders.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BFEFD1C6446EF00089C1C /* CRTBuilders.cpp */; }; 4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; }; @@ -307,6 +303,11 @@ 4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */; }; 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; }; 4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; }; + 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */; }; + 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; }; + 4BBF99161C8FBA6F0075DAFB /* CRTRunBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */; }; + 4BBF99171C8FBA6F0075DAFB /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */; }; + 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; 4BE5F85E1C3E1C2500C43F01 /* basic.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85C1C3E1C2500C43F01 /* basic.rom */; }; 4BE5F85F1C3E1C2500C43F01 /* os.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85D1C3E1C2500C43F01 /* os.rom */; }; /* End PBXBuildFile section */ @@ -331,7 +332,6 @@ /* Begin PBXFileReference section */ 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = ""; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = ""; }; - 4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = ""; }; 4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioQueue.h; sourceTree = ""; }; 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioQueue.m; sourceTree = ""; }; 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = ""; }; @@ -341,11 +341,6 @@ 4B14145A1B58879D00E04248 /* CPU6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502AllRAM.hpp; sourceTree = ""; }; 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = ""; }; 4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = ""; }; - 4B2039921C67E20B001375C3 /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = ""; }; - 4B2039931C67E20B001375C3 /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = ""; }; - 4B2039951C67E2A3001375C3 /* OpenGL.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = ""; }; - 4B2039971C67FA92001375C3 /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = ""; }; - 4B2039981C67FA92001375C3 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = ""; }; 4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = ""; }; 4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = ""; }; 4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = ""; }; @@ -372,7 +367,6 @@ 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = ""; }; 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = ""; }; 4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; - 4B7BFEFD1C6446EF00089C1C /* CRTBuilders.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTBuilders.cpp; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = ""; }; 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = ""; }; 4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = ""; }; @@ -658,10 +652,20 @@ 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clock_SignalUITests.swift; sourceTree = ""; }; 4BB73EC31B587A5100552FC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "Clock Signal.entitlements"; sourceTree = ""; }; - 4BCC142A1C6FFD6F0033C621 /* CRTOpenGL.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTOpenGL.hpp; sourceTree = ""; }; + 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTInputBufferBuilder.cpp; sourceTree = ""; }; + 4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTInputBufferBuilder.hpp; sourceTree = ""; }; + 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = ""; }; + 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTOpenGL.hpp; sourceTree = ""; }; + 4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTRunBuilder.cpp; sourceTree = ""; }; + 4BBF990D1C8FBA6F0075DAFB /* CRTRunBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTRunBuilder.hpp; sourceTree = ""; }; + 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = ""; }; + 4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = ""; }; + 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shader.cpp; sourceTree = ""; }; + 4BBF99111C8FBA6F0075DAFB /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = ""; }; + 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = ""; }; + 4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = ""; }; 4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = ""; }; 4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = ""; }; - 4BEDD0F41C6D70B800F6257C /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -693,17 +697,9 @@ 4B0CCC411C62D0B3001CAC5F /* CRT */ = { isa = PBXGroup; children = ( + 4BBF99071C8FBA6F0075DAFB /* Internals */, 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */, 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */, - 4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */, - 4B7BFEFD1C6446EF00089C1C /* CRTBuilders.cpp */, - 4B2039921C67E20B001375C3 /* TextureTarget.cpp */, - 4B2039931C67E20B001375C3 /* TextureTarget.hpp */, - 4B2039951C67E2A3001375C3 /* OpenGL.hpp */, - 4B2039971C67FA92001375C3 /* Shader.cpp */, - 4B2039981C67FA92001375C3 /* Shader.hpp */, - 4BEDD0F41C6D70B800F6257C /* Flywheel.hpp */, - 4BCC142A1C6FFD6F0033C621 /* CRTOpenGL.hpp */, ); name = CRT; path = ../../Outputs/CRT; @@ -1193,6 +1189,25 @@ path = ../../Processors; sourceTree = ""; }; + 4BBF99071C8FBA6F0075DAFB /* Internals */ = { + isa = PBXGroup; + children = ( + 4BBF99081C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp */, + 4BBF99091C8FBA6F0075DAFB /* CRTInputBufferBuilder.hpp */, + 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */, + 4BBF990B1C8FBA6F0075DAFB /* CRTOpenGL.hpp */, + 4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */, + 4BBF990D1C8FBA6F0075DAFB /* CRTRunBuilder.hpp */, + 4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */, + 4BBF990F1C8FBA6F0075DAFB /* OpenGL.hpp */, + 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */, + 4BBF99111C8FBA6F0075DAFB /* Shader.hpp */, + 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */, + 4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */, + ); + path = Internals; + sourceTree = ""; + }; 4BE5F85A1C3E1C2500C43F01 /* Resources */ = { isa = PBXGroup; children = ( @@ -1616,21 +1631,22 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, + 4BBF99161C8FBA6F0075DAFB /* CRTRunBuilder.cpp in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, - 4B0CCC471C62D1A8001CAC5F /* CRTOpenGL.cpp in Sources */, - 4B2039941C67E20B001375C3 /* TextureTarget.cpp in Sources */, 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, - 4B7BFEFE1C6446EF00089C1C /* CRTBuilders.cpp in Sources */, + 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, + 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, - 4B2039991C67FA92001375C3 /* Shader.cpp in Sources */, + 4BBF99171C8FBA6F0075DAFB /* Shader.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 434dc6946..8a6de7327 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -6,8 +6,8 @@ // Copyright © 2015 Thomas Harte. All rights reserved. // -#ifndef CRT_cpp -#define CRT_cpp +#ifndef CRT_hpp +#define CRT_hpp #include #include @@ -15,7 +15,9 @@ #include #include -#include "Flywheel.hpp" +#include "Internals/Flywheel.hpp" +#include "Internals/CRTInputBufferBuilder.hpp" +#include "Internals/CRTRunBuilder.hpp" namespace Outputs { namespace CRT { @@ -49,57 +51,6 @@ enum OutputDevice { Television }; -struct CRTInputBufferBuilder { - CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes); - ~CRTInputBufferBuilder(); - - void allocate_write_area(size_t required_length); - void reduce_previous_allocation_to(size_t actual_length); - uint8_t *get_write_target_for_buffer(int buffer); - - // a pointer to the section of content buffer currently being - // returned and to where the next section will begin - uint16_t _next_write_x_position, _next_write_y_position; - uint16_t _write_x_position, _write_y_position; - size_t _write_target_pointer; - size_t _last_allocation_amount; - - struct Buffer { - uint8_t *data; - size_t bytes_per_pixel; - } *buffers; - unsigned int number_of_buffers; - - // Storage for the amount of buffer uploaded so far; initialised correctly by the buffer - // builder but otherwise entrusted to the CRT to update. - unsigned int last_uploaded_line; -}; - -struct CRTRunBuilder { - CRTRunBuilder(size_t vertex_size) : _vertex_size(vertex_size) { reset(); } - - // Resets the run builder. - void reset(); - - // Getter for new storage plus backing storage; in RGB mode input runs will map directly - // from the input buffer to the screen. In composite mode input runs will map from the - // input buffer to the processing buffer, and output runs will map from the processing - // buffer to the screen. - uint8_t *get_next_run(size_t number_of_vertices); - std::vector _runs; - - // Container for total length in cycles of all contained runs. - uint32_t duration; - - // Storage for the length of run data uploaded so far; reset to zero by reset but otherwise - // entrusted to the CRT to update. - size_t uploaded_vertices; - size_t number_of_vertices; - - private: - size_t _vertex_size; -}; - struct OpenGLState; class CRT { diff --git a/Outputs/CRT/CRTBuilders.cpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp similarity index 62% rename from Outputs/CRT/CRTBuilders.cpp rename to Outputs/CRT/Internals/CRTInputBufferBuilder.cpp index 342e3e963..75e54ebc4 100644 --- a/Outputs/CRT/CRTBuilders.cpp +++ b/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp @@ -1,21 +1,18 @@ // -// CRTFrameBuilder.cpp +// CRTInputBufferBuilder.cpp // Clock Signal // -// Created by Thomas Harte on 04/02/2016. +// Created by Thomas Harte on 08/03/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#include "CRT.hpp" +#include "CRTInputBufferBuilder.hpp" #include "CRTOpenGL.hpp" +#include -using namespace Outputs; +using namespace Outputs::CRT; -/* - CRTInputBufferBuilder -*/ - -CRT::CRTInputBufferBuilder::CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes) +CRTInputBufferBuilder::CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes) { this->number_of_buffers = number_of_buffers; buffers = new CRTInputBufferBuilder::Buffer[number_of_buffers]; @@ -30,7 +27,7 @@ CRT::CRTInputBufferBuilder::CRTInputBufferBuilder(unsigned int number_of_buffers last_uploaded_line = 0; } -CRT::CRTInputBufferBuilder::~CRTInputBufferBuilder() +CRTInputBufferBuilder::~CRTInputBufferBuilder() { for(int buffer = 0; buffer < number_of_buffers; buffer++) delete[] buffers[buffer].data; @@ -38,7 +35,7 @@ CRT::CRTInputBufferBuilder::~CRTInputBufferBuilder() } -void CRT::CRTInputBufferBuilder::allocate_write_area(size_t required_length) +void CRTInputBufferBuilder::allocate_write_area(size_t required_length) { _last_allocation_amount = required_length; @@ -54,7 +51,7 @@ void CRT::CRTInputBufferBuilder::allocate_write_area(size_t required_length) _next_write_x_position += required_length + 2; } -void CRT::CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length) +void CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length) { for(int c = 0; c < number_of_buffers; c++) { @@ -70,32 +67,7 @@ void CRT::CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_len _next_write_x_position -= (_last_allocation_amount - actual_length); } - -uint8_t *CRT::CRTInputBufferBuilder::get_write_target_for_buffer(int buffer) +uint8_t *CRTInputBufferBuilder::get_write_target_for_buffer(int buffer) { return &buffers[buffer].data[_write_target_pointer * buffers[buffer].bytes_per_pixel]; } - -/* - CRTRunBuilder -*/ -void CRT::CRTRunBuilder::reset() -{ - number_of_vertices = 0; - uploaded_vertices = 0; - duration = 0; -} - -uint8_t *CRT::CRTRunBuilder::get_next_run(size_t number_of_vertices_in_run) -{ - // get a run from the allocated list, allocating more if we're about to overrun - if((number_of_vertices + number_of_vertices_in_run) * _vertex_size >= _runs.size()) - { - _runs.resize(_runs.size() + _vertex_size * 100); - } - - uint8_t *next_run = &_runs[number_of_vertices * _vertex_size]; - number_of_vertices += number_of_vertices_in_run; - - return next_run; -} diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp new file mode 100644 index 000000000..738c02a9b --- /dev/null +++ b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp @@ -0,0 +1,48 @@ +// +// 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 +#include +#include + +namespace Outputs { +namespace CRT { + +struct CRTInputBufferBuilder { + CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes); + ~CRTInputBufferBuilder(); + + void allocate_write_area(size_t required_length); + void reduce_previous_allocation_to(size_t actual_length); + uint8_t *get_write_target_for_buffer(int buffer); + + // a pointer to the section of content buffer currently being + // returned and to where the next section will begin + uint16_t _next_write_x_position, _next_write_y_position; + uint16_t _write_x_position, _write_y_position; + size_t _write_target_pointer; + size_t _last_allocation_amount; + + struct Buffer { + uint8_t *data; + size_t bytes_per_pixel; + } *buffers; + unsigned int number_of_buffers; + + // Storage for the amount of buffer uploaded so far; initialised correctly by the buffer + // builder but otherwise entrusted to the CRT to update. + unsigned int last_uploaded_line; +}; + +} +} + +#endif /* CRTInputBufferBuilder_hpp */ diff --git a/Outputs/CRT/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp similarity index 100% rename from Outputs/CRT/CRTOpenGL.cpp rename to Outputs/CRT/Internals/CRTOpenGL.cpp diff --git a/Outputs/CRT/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp similarity index 100% rename from Outputs/CRT/CRTOpenGL.hpp rename to Outputs/CRT/Internals/CRTOpenGL.hpp diff --git a/Outputs/CRT/Internals/CRTRunBuilder.cpp b/Outputs/CRT/Internals/CRTRunBuilder.cpp new file mode 100644 index 000000000..b67356eb3 --- /dev/null +++ b/Outputs/CRT/Internals/CRTRunBuilder.cpp @@ -0,0 +1,33 @@ +// +// CRTFrameBuilder.cpp +// Clock Signal +// +// Created by Thomas Harte on 04/02/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "CRT.hpp" +#include "CRTOpenGL.hpp" + +using namespace Outputs::CRT; + +void CRTRunBuilder::reset() +{ + number_of_vertices = 0; + uploaded_vertices = 0; + duration = 0; +} + +uint8_t *CRTRunBuilder::get_next_run(size_t number_of_vertices_in_run) +{ + // get a run from the allocated list, allocating more if we're about to overrun + if((number_of_vertices + number_of_vertices_in_run) * _vertex_size >= _runs.size()) + { + _runs.resize(_runs.size() + _vertex_size * 100); + } + + uint8_t *next_run = &_runs[number_of_vertices * _vertex_size]; + number_of_vertices += number_of_vertices_in_run; + + return next_run; +} diff --git a/Outputs/CRT/Internals/CRTRunBuilder.hpp b/Outputs/CRT/Internals/CRTRunBuilder.hpp new file mode 100644 index 000000000..ad67b2689 --- /dev/null +++ b/Outputs/CRT/Internals/CRTRunBuilder.hpp @@ -0,0 +1,43 @@ +// +// CRTRunBuilder.h +// Clock Signal +// +// Created by Thomas Harte on 08/03/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef CRTRunBuilder_h +#define CRTRunBuilder_h + +namespace Outputs { +namespace CRT { + +struct CRTRunBuilder { + CRTRunBuilder(size_t vertex_size) : _vertex_size(vertex_size) { reset(); } + + // Resets the run builder. + void reset(); + + // Getter for new storage plus backing storage; in RGB mode input runs will map directly + // from the input buffer to the screen. In composite mode input runs will map from the + // input buffer to the processing buffer, and output runs will map from the processing + // buffer to the screen. + uint8_t *get_next_run(size_t number_of_vertices); + std::vector _runs; + + // Container for total length in cycles of all contained runs. + uint32_t duration; + + // Storage for the length of run data uploaded so far; reset to zero by reset but otherwise + // entrusted to the CRT to update. + size_t uploaded_vertices; + size_t number_of_vertices; + + private: + size_t _vertex_size; +}; + +} +} + +#endif /* CRTRunBuilder_h */ diff --git a/Outputs/CRT/Flywheel.hpp b/Outputs/CRT/Internals/Flywheel.hpp similarity index 100% rename from Outputs/CRT/Flywheel.hpp rename to Outputs/CRT/Internals/Flywheel.hpp diff --git a/Outputs/CRT/OpenGL.hpp b/Outputs/CRT/Internals/OpenGL.hpp similarity index 100% rename from Outputs/CRT/OpenGL.hpp rename to Outputs/CRT/Internals/OpenGL.hpp diff --git a/Outputs/CRT/Shader.cpp b/Outputs/CRT/Internals/Shader.cpp similarity index 100% rename from Outputs/CRT/Shader.cpp rename to Outputs/CRT/Internals/Shader.cpp diff --git a/Outputs/CRT/Shader.hpp b/Outputs/CRT/Internals/Shader.hpp similarity index 100% rename from Outputs/CRT/Shader.hpp rename to Outputs/CRT/Internals/Shader.hpp diff --git a/Outputs/CRT/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp similarity index 100% rename from Outputs/CRT/TextureTarget.cpp rename to Outputs/CRT/Internals/TextureTarget.cpp diff --git a/Outputs/CRT/TextureTarget.hpp b/Outputs/CRT/Internals/TextureTarget.hpp similarity index 100% rename from Outputs/CRT/TextureTarget.hpp rename to Outputs/CRT/Internals/TextureTarget.hpp From bf5747f83e16c9ab6e127a61a601c65ade72def8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Mar 2016 22:40:23 -0500 Subject: [PATCH 156/307] Made an attempt to chop out all the stuff of building up the OpenGL data from the stuff of parsing input. --- .../Clock Signal.xcodeproj/project.pbxproj | 2 + Outputs/CRT/CRT.cpp | 127 +++------ Outputs/CRT/CRT.hpp | 243 ++++++------------ Outputs/CRT/CRTTypes.hpp | 47 ++++ Outputs/CRT/Internals/CRTOpenGL.cpp | 218 ++++++++-------- Outputs/CRT/Internals/CRTOpenGL.hpp | 187 ++++++++++++++ Outputs/CRT/Internals/CRTRunBuilder.hpp | 2 + 7 files changed, 466 insertions(+), 360 deletions(-) create mode 100644 Outputs/CRT/CRTTypes.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 049ef77ea..7a8130733 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -664,6 +664,7 @@ 4BBF99111C8FBA6F0075DAFB /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = ""; }; 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = ""; }; 4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = ""; }; + 4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = ""; }; 4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = ""; }; 4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = ""; }; /* End PBXFileReference section */ @@ -700,6 +701,7 @@ 4BBF99071C8FBA6F0075DAFB /* Internals */, 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */, 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */, + 4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */, ); name = CRT; path = ../../Outputs/CRT; diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 733f1da82..fd139386e 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -15,9 +15,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) { - _colour_space = colour_space; - _colour_cycle_numerator = colour_cycle_numerator; - _colour_cycle_denominator = colour_cycle_denominator; + _openGL_output_builder->set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator); const unsigned int syncCapacityLineChargeThreshold = 3; const unsigned int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234 @@ -45,6 +43,8 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di // 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)); + + _openGL_output_builder->set_timing(_cycles_per_line, _height_of_display, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period(), _vertical_flywheel_output_divider); } void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) @@ -61,63 +61,31 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display } } -void CRT::allocate_buffers(unsigned int number, va_list sizes) -{ - _run_builders = new CRTRunBuilder *[NumberOfFields]; - for(int builder = 0; builder < NumberOfFields; builder++) - { - _run_builders[builder] = new CRTRunBuilder(OutputVertexSize); - } - _composite_src_runs = std::unique_ptr(new CRTRunBuilder(InputVertexSize)); - - va_list va; - va_copy(va, sizes); - _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(number, va)); - va_end(va); -} - CRT::CRT(unsigned int common_output_divisor) : - _run_write_pointer(0), _sync_capacitor_charge_level(0), _is_receiving_sync(false), - _output_mutex(new std::mutex), - _visible_area(Rect(0, 0, 1, 1)), _sync_period(0), _common_output_divisor(common_output_divisor), - _composite_src_output_y(0), - _is_writing_composite_run(false) -{ - construct_openGL(); -} - -CRT::~CRT() -{ - for(int builder = 0; builder < NumberOfFields; builder++) - { - delete _run_builders[builder]; - } - delete[] _run_builders; - destruct_openGL(); -} + _is_writing_composite_run(false) {} 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 number_of_buffers, ...) : CRT(common_output_divisor) { - set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator); - va_list buffer_sizes; va_start(buffer_sizes, number_of_buffers); - allocate_buffers(number_of_buffers, buffer_sizes); + _openGL_output_builder = std::unique_ptr(new OpenGLOutputBuilder(number_of_buffers, buffer_sizes)); va_end(buffer_sizes); + + 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 number_of_buffers, ...) : CRT(common_output_divisor) { - set_new_display_type(cycles_per_line, displayType); - va_list buffer_sizes; va_start(buffer_sizes, number_of_buffers); - allocate_buffers(number_of_buffers, buffer_sizes); + _openGL_output_builder = std::unique_ptr(new OpenGLOutputBuilder(number_of_buffers, buffer_sizes)); va_end(buffer_sizes); + + set_new_display_type(cycles_per_line, displayType); } #pragma mark - Sync loop @@ -147,11 +115,11 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, #define input_amplitude(v) next_run[OutputVertexSize*v + InputVertexOffsetOfPhaseAndAmplitude + 1] #define input_phase_time(v) (*(uint16_t *)&next_run[OutputVertexSize*v + InputVertexOffsetOfPhaseTime]) -void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type, uint16_t tex_x, uint16_t tex_y) +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; - bool is_output_run = ((type == Type::Level) || (type == Type::Data)); + bool is_output_run = ((type == Scan::Type::Level) || (type == Scan::Type::Data)); while(number_of_cycles) { @@ -170,8 +138,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi uint8_t *next_run = nullptr; if(is_output_segment) { - _output_mutex->lock(); - next_run = (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); + next_run = _openGL_output_builder->get_next_input_run(); } // Vertex output is arranged for triangle strips, as: @@ -181,12 +148,12 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // [0/1] 3 if(next_run) { - if(_output_device == Monitor) + if(_openGL_output_builder->get_output_device() == Monitor) { // set the type, initial raster position and type of this run output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _run_builders[_run_write_pointer]->duration; + output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _openGL_output_builder->get_current_field_time(); output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = tex_x; // these things are constants across the line so just throw them out now @@ -199,7 +166,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi input_input_position_x(0) = tex_x; input_input_position_y(0) = input_input_position_y(1) = tex_y; input_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - input_output_position_y(0) = input_output_position_y(1) = _composite_src_output_y; + input_output_position_y(0) = input_output_position_y(1) = _openGL_output_builder->get_composite_output_y(); input_phase(0) = input_phase(1) = _colour_burst_phase; input_amplitude(0) = input_amplitude(1) = _colour_burst_amplitude; input_phase_time(0) = input_phase_time(1) = _colour_burst_time; @@ -209,7 +176,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // decrement the number of cycles left to run for and increment the // horizontal counter appropriately number_of_cycles -= next_run_length; - _run_builders[_run_write_pointer]->duration += next_run_length; + _openGL_output_builder->add_to_field_time(next_run_length); // either charge or deplete the vertical retrace capacitor (making sure it stops at 0) if (vsync_charging && !_vertical_flywheel->is_in_retrace()) @@ -224,14 +191,14 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(next_run) { // if this is a data run then advance the buffer pointer - if(type == 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); - if(_output_device == Monitor) + if(_openGL_output_builder->get_output_device() == Monitor) { // store the final raster position output_position_x(3) = output_position_x(4) = output_position_x(5) = (uint16_t)_horizontal_flywheel->get_current_output_position(); output_position_y(3) = output_position_y(4) = output_position_y(5) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_timestamp(3) = output_timestamp(4) = output_timestamp(5) = _run_builders[_run_write_pointer]->duration; + output_timestamp(3) = output_timestamp(4) = output_timestamp(5) = _openGL_output_builder->get_current_field_time(); output_tex_x(3) = output_tex_x(4) = output_tex_x(5) = tex_x; } else @@ -243,11 +210,11 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(is_output_segment) { - _output_mutex->unlock(); + _openGL_output_builder->complete_input_run(); } // if this is horizontal retrace then advance the output line counter and bookend an output run - if(_output_device == Television) + if(_openGL_output_builder->get_output_device() == Television) { Flywheel::SyncEvent honoured_event = Flywheel::SyncEvent::None; if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event != Flywheel::SyncEvent::None) honoured_event = next_vertical_sync_event; @@ -258,23 +225,24 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(needs_endpoint) { - uint8_t *next_run = _run_builders[_run_write_pointer]->get_next_run(3); + uint8_t *next_run = _openGL_output_builder->get_next_output_run(); output_position_x(0) = output_position_x(1) = output_position_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); output_position_y(0) = output_position_y(1) = output_position_y(2) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); - output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _run_builders[_run_write_pointer]->duration; + output_timestamp(0) = output_timestamp(1) = output_timestamp(2) = _openGL_output_builder->get_current_field_time(); output_tex_x(0) = output_tex_x(1) = output_tex_x(2) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = _composite_src_output_y; + output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = _openGL_output_builder->get_composite_output_y(); output_lateral(0) = 0; output_lateral(1) = _is_writing_composite_run ? 1 : 0; output_lateral(2) = 1; + _openGL_output_builder->complete_output_run(); _is_writing_composite_run ^= true; } if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::EndRetrace) { - _composite_src_output_y = (_composite_src_output_y + 1) % IntermediateBufferHeight; + _openGL_output_builder->increment_composite_output_y(); } } @@ -284,8 +252,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // TODO: how to communicate did_detect_vsync? Bring the delegate back? // _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); - _run_write_pointer = (_run_write_pointer + 1)%NumberOfFields; - _run_builders[_run_write_pointer]->reset(); + _openGL_output_builder->increment_field(); } } } @@ -309,14 +276,14 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi void CRT::output_scan(Scan *scan) { - bool this_is_sync = (scan->type == Type::Sync); + bool this_is_sync = (scan->type == Scan::Type::Sync); bool is_trailing_edge = (_is_receiving_sync && !this_is_sync); bool hsync_requested = is_trailing_edge && (_sync_period < (_horizontal_flywheel->get_scan_period() >> 2)); bool vsync_requested = is_trailing_edge && (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold); _is_receiving_sync = this_is_sync; // simplified colour burst logic: if it's within the back porch we'll take it - if(scan->type == Type::ColourBurst) + if(scan->type == Scan::Type::ColourBurst) { if(_horizontal_flywheel->get_current_time() < (_horizontal_flywheel->get_standard_period() * 12) >> 6) { @@ -338,7 +305,7 @@ void CRT::output_scan(Scan *scan) void CRT::output_sync(unsigned int number_of_cycles) { Scan scan{ - .type = Type::Sync, + .type = Scan::Type::Sync, .number_of_cycles = number_of_cycles }; output_scan(&scan); @@ -347,7 +314,7 @@ void CRT::output_sync(unsigned int number_of_cycles) void CRT::output_blank(unsigned int number_of_cycles) { Scan scan { - .type = Type::Blank, + .type = Scan::Type::Blank, .number_of_cycles = number_of_cycles }; output_scan(&scan); @@ -356,10 +323,10 @@ void CRT::output_blank(unsigned int number_of_cycles) void CRT::output_level(unsigned int number_of_cycles) { Scan scan { - .type = Type::Level, + .type = Scan::Type::Level, .number_of_cycles = number_of_cycles, - .tex_x = _buffer_builder->_write_x_position, - .tex_y = _buffer_builder->_write_y_position + .tex_x = _openGL_output_builder->get_last_write_x_posiiton(), + .tex_y = _openGL_output_builder->get_last_write_y_posiiton() }; output_scan(&scan); } @@ -367,7 +334,7 @@ void CRT::output_level(unsigned int number_of_cycles) void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) { Scan scan { - .type = Type::ColourBurst, + .type = Scan::Type::ColourBurst, .number_of_cycles = number_of_cycles, .phase = phase, .amplitude = amplitude @@ -377,27 +344,13 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { - _buffer_builder->reduce_previous_allocation_to(number_of_cycles / source_divider); + _openGL_output_builder->reduce_previous_allocation_to(number_of_cycles / source_divider); Scan scan { - .type = Type::Data, + .type = Scan::Type::Data, .number_of_cycles = number_of_cycles, - .tex_x = _buffer_builder->_write_x_position, - .tex_y = _buffer_builder->_write_y_position, + .tex_x = _openGL_output_builder->get_last_write_x_posiiton(), + .tex_y = _openGL_output_builder->get_last_write_y_posiiton(), .source_divider = source_divider }; output_scan(&scan); } - -#pragma mark - Buffer supply - -void CRT::allocate_write_area(size_t required_length) -{ - _output_mutex->lock(); - _buffer_builder->allocate_write_area(required_length); - _output_mutex->unlock(); -} - -uint8_t *CRT::get_write_target_for_buffer(int buffer) -{ - return _buffer_builder->get_write_target_for_buffer(buffer); -} diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 8a6de7327..3ccc46c9d 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -15,48 +15,70 @@ #include #include +#include "CRTTypes.hpp" #include "Internals/Flywheel.hpp" -#include "Internals/CRTInputBufferBuilder.hpp" -#include "Internals/CRTRunBuilder.hpp" +#include "Internals/CRTOpenGL.hpp" namespace Outputs { namespace CRT { -struct Rect { - struct { - float x, y; - } origin; - - struct { - float width, height; - } size; - - Rect() {} - Rect(float x, float y, float width, float height) : - origin({.x = x, .y = y}), size({.width = width, .height =height}) {} -}; - -enum DisplayType { - PAL50, - NTSC60 -}; - -enum ColourSpace { - YIQ, - YUV -}; - -enum OutputDevice { - Monitor, - Television -}; - -struct OpenGLState; - class CRT { - public: - ~CRT(); + private: + CRT(unsigned int common_output_divisor); + // 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; + + // the two flywheels regulating scanning + std::unique_ptr _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; + + // each call to output_* generates a scan. A two-slot queue for scans allows edge extensions. + struct Scan { + enum Type { + Sync, Level, Data, Blank, ColourBurst + } type; + unsigned int number_of_cycles; + union { + struct { + unsigned int source_divider; + uint16_t tex_x, tex_y; + }; + struct { + uint8_t phase, amplitude; + }; + }; + }; + void output_scan(Scan *scan); + + 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); + + // the inner entry point that determines whether and when the next sync event will occur within + // the current output window + 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 _openGL_output_builder; + + public: /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. The requested number of buffers, each with the requested number of bytes per pixel, is created for the machine to write raw pixel data to. @@ -162,19 +184,28 @@ class CRT { @param required_length The number of samples to allocate. */ - void allocate_write_area(size_t required_length); + inline void allocate_write_area(size_t required_length) + { + return _openGL_output_builder->allocate_write_area(required_length); + } /*! Gets a pointer for writing to the area created by the most recent call to @c allocate_write_area for the nominated buffer. @param buffer The buffer to get a write target for. */ - uint8_t *get_write_target_for_buffer(int buffer); + inline uint8_t *get_write_target_for_buffer(int buffer) + { + return _openGL_output_builder->get_write_target_for_buffer(buffer); + } /*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state. The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call. */ - void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty); + 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); + } /*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than the previous. @@ -183,7 +214,10 @@ class CRT { currently held by the CRT will be deleted now via calls to glDeleteTexture and equivalent. If @c false then the references are simply marked as invalid. */ - void set_openGL_context_will_change(bool should_delete_resources); + inline void set_openGL_context_will_change(bool 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. @@ -192,7 +226,10 @@ class CRT { level as a function of a source buffer sampling location and the provided colour carrier phase. The shader may assume a uniform array of sampler2Ds named `buffers` provides access to all input data. */ - void set_composite_sampling_function(const char *shader); + inline void set_composite_sampling_function(const char *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. @@ -204,7 +241,10 @@ class CRT { the source buffer sampling location. The shader may assume a uniform array of sampler2Ds named `buffers` provides access to all input data. */ - void set_rgb_sampling_function(const char *shader); + inline void set_rgb_sampling_function(const char *shader) + { + _openGL_output_builder->set_rgb_sampling_function(shader); + } /*! Optionally sets a function that will map from an input cycle count to a colour carrier phase. @@ -218,128 +258,15 @@ class CRT { */ // void set_phase_function(const char *shader); - void set_output_device(OutputDevice output_device); - void set_visible_area(Rect visible_area) + inline void set_output_device(OutputDevice output_device) { - _visible_area = visible_area; + _openGL_output_builder->set_output_device(output_device); } -#ifdef DEBUG - inline uint32_t get_field_cycle() + inline void set_visible_area(Rect visible_area) { - return _run_builders[_run_write_pointer]->duration / _time_multiplier; + _openGL_output_builder->set_visible_area(visible_area); } - - inline uint32_t get_line_cycle() - { - return _horizontal_flywheel->get_current_time() / _time_multiplier; - } - - inline float get_raster_x() - { - return (float)_horizontal_flywheel->get_current_output_position() / (float)_horizontal_flywheel->get_scan_period(); - } -#endif - - private: - CRT(unsigned int common_output_divisor); - void allocate_buffers(unsigned int number, va_list sizes); - - // 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; - - // colour invormation - ColourSpace _colour_space; - unsigned int _colour_cycle_numerator; - unsigned int _colour_cycle_denominator; - OutputDevice _output_device; - - // The user-supplied visible area - Rect _visible_area; - - // the two flywheels regulating scanning - std::unique_ptr _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; - - // the outer entry point for dispatching output_sync, output_blank, output_level and output_data - enum Type { - Sync, Level, Data, Blank, ColourBurst - } type; - void advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type, uint16_t tex_x, uint16_t tex_y); - - // the inner entry point that determines whether and when the next sync event will occur within - // the current output window - 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); - - // each call to output_* generates a scan. A two-slot queue for scans allows edge extensions. - struct Scan { - Type type; - unsigned int number_of_cycles; - union { - struct { - unsigned int source_divider; - uint16_t tex_x, tex_y; - }; - struct { - uint8_t phase, amplitude; - }; - }; - }; - void output_scan(Scan *scan); - - // the run and input data buffers - std::unique_ptr _buffer_builder; - CRTRunBuilder **_run_builders; - int _run_write_pointer; - std::shared_ptr _output_mutex; - - // transient buffers indicating composite data not yet decoded - std::unique_ptr _composite_src_runs; - uint16_t _composite_src_output_y; - uint8_t _colour_burst_phase, _colour_burst_amplitude; - uint16_t _colour_burst_time; - bool _is_writing_composite_run; - - // OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here. - OpenGLState *_openGL_state; - - // Other things the caller may have provided. - char *_composite_shader; - char *_rgb_shader; - - // Setup and teardown for the OpenGL code - void construct_openGL(); - void destruct_openGL(); - - // Methods used by the OpenGL code - void prepare_rgb_output_shader(); - void prepare_composite_input_shader(); - void prepare_output_vertex_array(); - void push_size_uniforms(unsigned int output_width, unsigned int output_height); - - char *get_output_vertex_shader(); - - char *get_output_fragment_shader(const char *sampling_function); - char *get_rgb_output_fragment_shader(); - char *get_composite_output_fragment_shader(); - - char *get_input_vertex_shader(); - char *get_input_fragment_shader(); - - char *get_compound_shader(const char *base, const char *insert); }; } diff --git a/Outputs/CRT/CRTTypes.hpp b/Outputs/CRT/CRTTypes.hpp new file mode 100644 index 000000000..262f01616 --- /dev/null +++ b/Outputs/CRT/CRTTypes.hpp @@ -0,0 +1,47 @@ +// +// CRTTypes.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/03/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef CRTTypes_h +#define CRTTypes_h + +namespace Outputs { +namespace CRT { + +struct Rect { + struct { + float x, y; + } origin; + + struct { + float width, height; + } size; + + Rect() {} + Rect(float x, float y, float width, float height) : + origin({.x = x, .y = y}), size({.width = width, .height =height}) {} +}; + +enum DisplayType { + PAL50, + NTSC60 +}; + +enum ColourSpace { + YIQ, + YUV +}; + +enum OutputDevice { + Monitor, + Television +}; + +} +} + +#endif /* CRTTypes_h */ diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 0b811a199..3d9ddcddf 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -9,42 +9,47 @@ #include #include -#include "OpenGL.hpp" -#include "TextureTarget.hpp" -#include "Shader.hpp" #include "CRTOpenGL.hpp" -namespace Outputs { -namespace CRT { - -struct OpenGLState { - std::unique_ptr rgb_shader_program; - std::unique_ptr composite_input_shader_program, composite_output_shader_program; - - GLuint output_array_buffer, output_vertex_array; - size_t output_vertices_per_slice; - - GLint windowSizeUniform, timestampBaseUniform; - GLint boundsOriginUniform, boundsSizeUniform; - - GLuint textureName, shadowMaskTextureName; - - GLuint defaultFramebuffer; - - std::unique_ptr compositeTexture; // receives raw composite levels - std::unique_ptr filteredYTexture; // receives filtered Y in the R channel plus unfiltered I/U and Q/V in G and B - std::unique_ptr filteredTexture; // receives filtered YIQ or YUV -}; - -} -} - using namespace Outputs::CRT; namespace { static const GLenum first_supplied_buffer_texture_unit = 3; } +OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int number_of_buffers, va_list sizes) : + _run_write_pointer(0), + _output_mutex(new std::mutex), + _visible_area(Rect(0, 0, 1, 1)), + _composite_src_output_y(0), + _composite_shader(nullptr), + _rgb_shader(nullptr) +{ + _run_builders = new CRTRunBuilder *[NumberOfFields]; + for(int builder = 0; builder < NumberOfFields; builder++) + { + _run_builders[builder] = new CRTRunBuilder(OutputVertexSize); + } + _composite_src_runs = std::unique_ptr(new CRTRunBuilder(InputVertexSize)); + + va_list va; + va_copy(va, sizes); + _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(number_of_buffers, sizes)); + va_end(va); +} + +OpenGLOutputBuilder::~OpenGLOutputBuilder() +{ + for(int builder = 0; builder < NumberOfFields; builder++) + { + delete _run_builders[builder]; + } + delete[] _run_builders; + + free(_composite_shader); + free(_rgb_shader); +} + static GLenum formatForDepth(size_t depth) { switch(depth) @@ -57,33 +62,17 @@ static GLenum formatForDepth(size_t depth) } } -void CRT::construct_openGL() -{ - _openGL_state = nullptr; - _composite_shader = _rgb_shader = nullptr; -} - -void CRT::destruct_openGL() -{ - delete _openGL_state; - _openGL_state = nullptr; - if(_composite_shader) free(_composite_shader); - if(_rgb_shader) free(_rgb_shader); -} - -void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) +void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { // establish essentials - if(!_openGL_state) + if(!composite_input_shader_program && !rgb_shader_program) { - _openGL_state = new OpenGLState; - // generate and bind textures for every one of the requested buffers for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) { - glGenTextures(1, &_openGL_state->textureName); + glGenTextures(1, &textureName); glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); - glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); + 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); @@ -93,29 +82,29 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[buffer].data); } - glGenVertexArrays(1, &_openGL_state->output_vertex_array); - glGenBuffers(1, &_openGL_state->output_array_buffer); - _openGL_state->output_vertices_per_slice = 0; + glGenVertexArrays(1, &output_vertex_array); + glGenBuffers(1, &output_array_buffer); + output_vertices_per_slice = 0; prepare_composite_input_shader(); prepare_rgb_output_shader(); - glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->output_array_buffer); - glBindVertexArray(_openGL_state->output_vertex_array); + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); + glBindVertexArray(output_vertex_array); prepare_output_vertex_array(); // This should return either an actual framebuffer number, if this is a target with a framebuffer intended for output, // or 0 if no framebuffer is bound, in which case 0 is also what we want to supply to bind the implied framebuffer. So // it works either way. - glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&defaultFramebuffer); // Create intermediate textures and bind to slots 0, 1 and 2 glActiveTexture(GL_TEXTURE0); - _openGL_state->compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); glActiveTexture(GL_TEXTURE1); - _openGL_state->filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); glActiveTexture(GL_TEXTURE2); - _openGL_state->filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); } // glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); @@ -155,7 +144,7 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // check for anything to decode from composite if(_composite_src_runs->number_of_vertices) { - _openGL_state->composite_input_shader_program->bind(); + composite_input_shader_program->bind(); _composite_src_runs->reset(); } @@ -167,35 +156,35 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool // glGetIntegerv(GL_VIEWPORT, results); // ensure array buffer is up to date - glBindBuffer(GL_ARRAY_BUFFER, _openGL_state->output_array_buffer); + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); size_t max_number_of_vertices = 0; for(int c = 0; c < NumberOfFields; c++) { max_number_of_vertices = std::max(max_number_of_vertices, _run_builders[c]->number_of_vertices); } - if(_openGL_state->output_vertices_per_slice < max_number_of_vertices) + if(output_vertices_per_slice < max_number_of_vertices) { - _openGL_state->output_vertices_per_slice = max_number_of_vertices; + output_vertices_per_slice = max_number_of_vertices; glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * OutputVertexSize * OutputVertexSize), NULL, GL_STREAM_DRAW); for(unsigned int c = 0; c < NumberOfFields; c++) { uint8_t *data = &_run_builders[c]->_runs[0]; - glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * _openGL_state->output_vertices_per_slice * OutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * OutputVertexSize), data); + glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * output_vertices_per_slice * OutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * OutputVertexSize), data); _run_builders[c]->uploaded_vertices = _run_builders[c]->number_of_vertices; } } // switch to the output shader - if(_openGL_state->rgb_shader_program) + if(rgb_shader_program) { - _openGL_state->rgb_shader_program->bind(); + rgb_shader_program->bind(); // update uniforms push_size_uniforms(output_width, output_height); // Ensure we're back on the output framebuffer - glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); // clear the buffer glClear(GL_COLOR_BUFFER_BIT); @@ -211,19 +200,19 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool if(_run_builders[run]->number_of_vertices > 0) { - glUniform1f(_openGL_state->timestampBaseUniform, (GLfloat)total_age); + glUniform1f(timestampBaseUniform, (GLfloat)total_age); if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) { uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * OutputVertexSize]; glBufferSubData(GL_ARRAY_BUFFER, - (GLsizeiptr)(((run * _openGL_state->output_vertices_per_slice) + _run_builders[run]->uploaded_vertices) * OutputVertexSize), + (GLsizeiptr)(((run * output_vertices_per_slice) + _run_builders[run]->uploaded_vertices) * OutputVertexSize), (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * OutputVertexSize), data); _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; } // draw this frame - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(run * _openGL_state->output_vertices_per_slice), (GLsizei)_run_builders[run]->number_of_vertices); + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(run * output_vertices_per_slice), (GLsizei)_run_builders[run]->number_of_vertices); } // advance back in time @@ -234,16 +223,15 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool _output_mutex->unlock(); } -void CRT::set_openGL_context_will_change(bool should_delete_resources) +void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources) { - _openGL_state = nullptr; } -void CRT::push_size_uniforms(unsigned int output_width, unsigned int output_height) +void OpenGLOutputBuilder::push_size_uniforms(unsigned int output_width, unsigned int output_height) { - if(_openGL_state->windowSizeUniform >= 0) + if(windowSizeUniform >= 0) { - glUniform2f(_openGL_state->windowSizeUniform, output_width, output_height); + glUniform2f(windowSizeUniform, output_width, output_height); } GLfloat outputAspectRatioMultiplier = ((float)output_width / (float)output_height) / (4.0f / 3.0f); @@ -254,26 +242,26 @@ void CRT::push_size_uniforms(unsigned int output_width, unsigned int output_heig _aspect_ratio_corrected_bounds.origin.x -= bonusWidth * 0.5f * _aspect_ratio_corrected_bounds.size.width; _aspect_ratio_corrected_bounds.size.width *= outputAspectRatioMultiplier; - if(_openGL_state->boundsOriginUniform >= 0) - glUniform2f(_openGL_state->boundsOriginUniform, (GLfloat)_aspect_ratio_corrected_bounds.origin.x, (GLfloat)_aspect_ratio_corrected_bounds.origin.y); + if(boundsOriginUniform >= 0) + glUniform2f(boundsOriginUniform, (GLfloat)_aspect_ratio_corrected_bounds.origin.x, (GLfloat)_aspect_ratio_corrected_bounds.origin.y); - if(_openGL_state->boundsSizeUniform >= 0) - glUniform2f(_openGL_state->boundsSizeUniform, (GLfloat)_aspect_ratio_corrected_bounds.size.width, (GLfloat)_aspect_ratio_corrected_bounds.size.height); + if(boundsSizeUniform >= 0) + glUniform2f(boundsSizeUniform, (GLfloat)_aspect_ratio_corrected_bounds.size.width, (GLfloat)_aspect_ratio_corrected_bounds.size.height); } -void CRT::set_composite_sampling_function(const char *shader) +void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader) { _composite_shader = strdup(shader); } -void CRT::set_rgb_sampling_function(const char *shader) +void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader) { _rgb_shader = strdup(shader); } #pragma mark - Input vertex shader (i.e. from source data to intermediate line layout) -char *CRT::get_input_vertex_shader() +char *OpenGLOutputBuilder::get_input_vertex_shader() { return strdup( "#version 150\n" @@ -298,7 +286,7 @@ char *CRT::get_input_vertex_shader() "}"); } -char *CRT::get_input_fragment_shader() +char *OpenGLOutputBuilder::get_input_fragment_shader() { const char *composite_shader = _composite_shader; if(!composite_shader) @@ -329,7 +317,7 @@ char *CRT::get_input_fragment_shader() #pragma mark - Output vertex shader -char *CRT::get_output_vertex_shader() +char *OpenGLOutputBuilder::get_output_vertex_shader() { // the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates @@ -378,12 +366,12 @@ char *CRT::get_output_vertex_shader() #pragma mark - Output fragment shaders; RGB and from composite -char *CRT::get_rgb_output_fragment_shader() +char *OpenGLOutputBuilder::get_rgb_output_fragment_shader() { return get_output_fragment_shader(_rgb_shader); } -char *CRT::get_composite_output_fragment_shader() +char *OpenGLOutputBuilder::get_composite_output_fragment_shader() { return get_output_fragment_shader( "vec4 rgb_sample(vec2 coordinate)" @@ -392,7 +380,7 @@ char *CRT::get_composite_output_fragment_shader() "}"); } -char *CRT::get_output_fragment_shader(const char *sampling_function) +char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_function) { return get_compound_shader( "#version 150\n" @@ -418,7 +406,7 @@ char *CRT::get_output_fragment_shader(const char *sampling_function) #pragma mark - Shader utilities -char *CRT::get_compound_shader(const char *base, const char *insert) +char *OpenGLOutputBuilder::get_compound_shader(const char *base, const char *insert) { if(!base || !insert) return nullptr; size_t totalLength = strlen(base) + strlen(insert) + 1; @@ -429,18 +417,18 @@ char *CRT::get_compound_shader(const char *base, const char *insert) #pragma mark - Program compilation -void CRT::prepare_composite_input_shader() +void OpenGLOutputBuilder::prepare_composite_input_shader() { char *vertex_shader = get_input_vertex_shader(); char *fragment_shader = get_input_fragment_shader(); if(vertex_shader && fragment_shader) { - _openGL_state->composite_input_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + composite_input_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); - GLint texIDUniform = _openGL_state->composite_input_shader_program->get_uniform_location("texID"); - GLint inputTextureSizeUniform = _openGL_state->composite_input_shader_program->get_uniform_location("inputTextureSize"); - GLint outputTextureSizeUniform = _openGL_state->composite_input_shader_program->get_uniform_location("outputTextureSize"); - GLint phaseCyclesPerTickUniform = _openGL_state->composite_input_shader_program->get_uniform_location("phaseCyclesPerTick"); + GLint texIDUniform = composite_input_shader_program->get_uniform_location("texID"); + GLint inputTextureSizeUniform = composite_input_shader_program->get_uniform_location("inputTextureSize"); + GLint outputTextureSizeUniform = composite_input_shader_program->get_uniform_location("outputTextureSize"); + GLint phaseCyclesPerTickUniform = composite_input_shader_program->get_uniform_location("phaseCyclesPerTick"); glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); glUniform2f(outputTextureSizeUniform, IntermediateBufferWidth, IntermediateBufferHeight); @@ -451,7 +439,7 @@ void CRT::prepare_composite_input_shader() free(fragment_shader); } -/*void CRT::prepare_output_shader(char *fragment_shader) +/*void OpenGLOutputBuilder::prepare_output_shader(char *fragment_shader) { char *vertex_shader = get_output_vertex_shader(); if(vertex_shader && fragment_shader) @@ -490,38 +478,38 @@ void CRT::prepare_composite_input_shader() free(fragment_shader); }*/ -void CRT::prepare_rgb_output_shader() +void OpenGLOutputBuilder::prepare_rgb_output_shader() { char *vertex_shader = get_output_vertex_shader(); char *fragment_shader = get_rgb_output_fragment_shader(); if(vertex_shader && fragment_shader) { - _openGL_state->rgb_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + rgb_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); - _openGL_state->rgb_shader_program->bind(); + rgb_shader_program->bind(); - _openGL_state->windowSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("windowSize"); - _openGL_state->boundsSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsSize"); - _openGL_state->boundsOriginUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsOrigin"); - _openGL_state->timestampBaseUniform = _openGL_state->rgb_shader_program->get_uniform_location("timestampBase"); + windowSizeUniform = rgb_shader_program->get_uniform_location("windowSize"); + boundsSizeUniform = rgb_shader_program->get_uniform_location("boundsSize"); + boundsOriginUniform = rgb_shader_program->get_uniform_location("boundsOrigin"); + timestampBaseUniform = rgb_shader_program->get_uniform_location("timestampBase"); - GLint texIDUniform = _openGL_state->rgb_shader_program->get_uniform_location("texID"); - GLint shadowMaskTexIDUniform = _openGL_state->rgb_shader_program->get_uniform_location("shadowMaskTexID"); - GLint textureSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("textureSize"); - GLint ticksPerFrameUniform = _openGL_state->rgb_shader_program->get_uniform_location("ticksPerFrame"); - GLint scanNormalUniform = _openGL_state->rgb_shader_program->get_uniform_location("scanNormal"); - GLint positionConversionUniform = _openGL_state->rgb_shader_program->get_uniform_location("positionConversion"); + GLint texIDUniform = rgb_shader_program->get_uniform_location("texID"); + GLint shadowMaskTexIDUniform = rgb_shader_program->get_uniform_location("shadowMaskTexID"); + GLint textureSizeUniform = rgb_shader_program->get_uniform_location("textureSize"); + GLint ticksPerFrameUniform = rgb_shader_program->get_uniform_location("ticksPerFrame"); + GLint scanNormalUniform = rgb_shader_program->get_uniform_location("scanNormal"); + GLint positionConversionUniform = rgb_shader_program->get_uniform_location("positionConversion"); glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); glUniform1i(shadowMaskTexIDUniform, 1); glUniform2f(textureSizeUniform, InputBufferBuilderWidth, InputBufferBuilderHeight); glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); - glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); + glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; - float multiplier = (float)_horizontal_flywheel->get_standard_period() / ((float)_height_of_display * (float)_horizontal_flywheel->get_scan_period()); + float multiplier = (float)_cycles_per_line / ((float)_height_of_display * (float)_horizontal_scan_period); scan_normal[0] *= multiplier; scan_normal[1] *= multiplier; glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]); @@ -531,14 +519,14 @@ void CRT::prepare_rgb_output_shader() free(fragment_shader); } -void CRT::prepare_output_vertex_array() +void OpenGLOutputBuilder::prepare_output_vertex_array() { - if(_openGL_state->rgb_shader_program) + if(rgb_shader_program) { - GLint positionAttribute = _openGL_state->rgb_shader_program->get_attrib_location("position"); - GLint textureCoordinatesAttribute = _openGL_state->rgb_shader_program->get_attrib_location("srcCoordinates"); - GLint lateralAttribute = _openGL_state->rgb_shader_program->get_attrib_location("lateral"); - GLint timestampAttribute = _openGL_state->rgb_shader_program->get_attrib_location("timestamp"); + GLint positionAttribute = rgb_shader_program->get_attrib_location("position"); + GLint textureCoordinatesAttribute = rgb_shader_program->get_attrib_location("srcCoordinates"); + GLint lateralAttribute = rgb_shader_program->get_attrib_location("lateral"); + GLint timestampAttribute = rgb_shader_program->get_attrib_location("timestamp"); glEnableVertexAttribArray((GLuint)positionAttribute); glEnableVertexAttribArray((GLuint)textureCoordinatesAttribute); @@ -555,7 +543,7 @@ void CRT::prepare_output_vertex_array() #pragma mark - Configuration -void CRT::set_output_device(OutputDevice output_device) +void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) { if (_output_device != output_device) { diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 35e340ca9..0acd9f680 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -9,6 +9,15 @@ #ifndef CRTOpenGL_h #define CRTOpenGL_h +#include "../CRTTypes.hpp" +#include "OpenGL.hpp" +#include "TextureTarget.hpp" +#include "Shader.hpp" +#include "CRTInputBufferBuilder.hpp" +#include "CRTRunBuilder.hpp" + +#include + namespace Outputs { namespace CRT { @@ -43,6 +52,184 @@ const int IntermediateBufferHeight = 2048; // number of historic fields that are required fully to const int NumberOfFields = 3; +class OpenGLOutputBuilder { + private: + // colour information + 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 _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; + + // Other things the caller may have provided. + char *_composite_shader; + char *_rgb_shader; + + // Methods used by the OpenGL code + void prepare_rgb_output_shader(); + void prepare_composite_input_shader(); + void prepare_output_vertex_array(); + void push_size_uniforms(unsigned int output_width, unsigned int output_height); + + // the run and input data buffers + std::unique_ptr _buffer_builder; + CRTRunBuilder **_run_builders; + int _run_write_pointer; + std::shared_ptr _output_mutex; + + // transient buffers indicating composite data not yet decoded + std::unique_ptr _composite_src_runs; + uint16_t _composite_src_output_y; + + char *get_output_vertex_shader(); + + char *get_output_fragment_shader(const char *sampling_function); + char *get_rgb_output_fragment_shader(); + char *get_composite_output_fragment_shader(); + + char *get_input_vertex_shader(); + char *get_input_fragment_shader(); + + char *get_compound_shader(const char *base, const char *insert); + + std::unique_ptr rgb_shader_program; + std::unique_ptr composite_input_shader_program, composite_output_shader_program; + + GLuint output_array_buffer, output_vertex_array; + size_t output_vertices_per_slice; + + GLint windowSizeUniform, timestampBaseUniform; + GLint boundsOriginUniform, boundsSizeUniform; + + GLuint textureName, shadowMaskTextureName; + + GLuint defaultFramebuffer; + + std::unique_ptr compositeTexture; // receives raw composite levels + std::unique_ptr filteredYTexture; // receives filtered Y in the R channel plus unfiltered I/U and Q/V in G and B + std::unique_ptr filteredTexture; // receives filtered YIQ or YUV + + public: + OpenGLOutputBuilder(unsigned int number_of_buffers, va_list sizes); + ~OpenGLOutputBuilder(); + + inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) + { + _colour_space = colour_space; + _colour_cycle_numerator = colour_cycle_numerator; + _colour_cycle_denominator = colour_cycle_denominator; + } + + inline void set_visible_area(Rect visible_area) + { + _visible_area = visible_area; + } + + inline uint8_t *get_next_input_run() + { + _output_mutex->lock(); + return (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); + } + + inline void complete_input_run() + { + _output_mutex->unlock(); + } + + inline uint8_t *get_next_output_run() + { + _output_mutex->lock(); + return (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); + } + + inline void complete_output_run() + { + } + + inline OutputDevice get_output_device() + { + return _output_device; + } + + inline uint32_t get_current_field_time() + { + return _run_builders[_run_write_pointer]->duration; + } + + inline void add_to_field_time(uint32_t amount) + { + _run_builders[_run_write_pointer]->duration += amount; + } + + inline uint16_t get_composite_output_y() + { + return _composite_src_output_y; + } + + inline void increment_composite_output_y() + { + _composite_src_output_y = (_composite_src_output_y + 1) % IntermediateBufferHeight; + } + + inline void increment_field() + { + _run_write_pointer = (_run_write_pointer + 1)%NumberOfFields; + _run_builders[_run_write_pointer]->reset(); + } + + inline void allocate_write_area(size_t required_length) + { + _output_mutex->lock(); + _buffer_builder->allocate_write_area(required_length); + _output_mutex->unlock(); + } + + inline void reduce_previous_allocation_to(size_t actual_length) + { + _buffer_builder->reduce_previous_allocation_to(actual_length); + } + + inline uint8_t *get_write_target_for_buffer(int buffer) + { + return _buffer_builder->get_write_target_for_buffer(buffer); + } + + inline uint16_t get_last_write_x_posiiton() + { + return _buffer_builder->_write_x_position; + } + + inline uint16_t get_last_write_y_posiiton() + { + return _buffer_builder->_write_y_position; + } + + void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty); + void set_openGL_context_will_change(bool should_delete_resources); + void set_composite_sampling_function(const char *shader); + void set_rgb_sampling_function(const char *shader); + void set_output_device(OutputDevice output_device); + inline void set_timing(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) + { + _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; + + // TODO: update related uniforms + } +}; + } } diff --git a/Outputs/CRT/Internals/CRTRunBuilder.hpp b/Outputs/CRT/Internals/CRTRunBuilder.hpp index ad67b2689..f5969a304 100644 --- a/Outputs/CRT/Internals/CRTRunBuilder.hpp +++ b/Outputs/CRT/Internals/CRTRunBuilder.hpp @@ -9,6 +9,8 @@ #ifndef CRTRunBuilder_h #define CRTRunBuilder_h +#import + namespace Outputs { namespace CRT { From 68da673a9520f7629f3f2db186d1f3b08eb6b886 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Mar 2016 22:53:29 -0500 Subject: [PATCH 157/307] Wrapped this up as explicitly only the Mac thing to do. --- Outputs/CRT/Internals/OpenGL.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/Internals/OpenGL.hpp b/Outputs/CRT/Internals/OpenGL.hpp index ed9899546..4d373546b 100644 --- a/Outputs/CRT/Internals/OpenGL.hpp +++ b/Outputs/CRT/Internals/OpenGL.hpp @@ -10,7 +10,12 @@ #define OpenGL_h // TODO: figure out correct include paths for other platforms. -#include -#include +#ifdef __APPLE__ + #if TARGET_OS_IPHONE + #else + #include + #include + #endif +#endif #endif /* OpenGL_h */ From 7255408313791c5abe5c7c67730fc115a1ac4153 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Mar 2016 22:54:05 -0500 Subject: [PATCH 158/307] Experimented with going back to horizontal sync on leading edge; not sure so (temporarily?) disabled. --- Outputs/CRT/CRT.cpp | 13 ++++++++----- Outputs/CRT/CRT.hpp | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index fd139386e..62d4c39d4 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -274,14 +274,17 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi #pragma mark - stream feeding methods -void CRT::output_scan(Scan *scan) +void CRT::output_scan(const Scan *const scan) { - bool this_is_sync = (scan->type == Scan::Type::Sync); - bool is_trailing_edge = (_is_receiving_sync && !this_is_sync); - bool hsync_requested = is_trailing_edge && (_sync_period < (_horizontal_flywheel->get_scan_period() >> 2)); - bool vsync_requested = is_trailing_edge && (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold); + 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 hsync_requested = is_leading_edge; + const bool hsync_requested = is_trailing_edge && (_sync_period < (_horizontal_flywheel->get_scan_period() >> 2)); + 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) { diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 3ccc46c9d..3391fdc4f 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -61,7 +61,7 @@ class CRT { }; }; }; - void output_scan(Scan *scan); + void output_scan(const Scan *scan); uint8_t _colour_burst_phase, _colour_burst_amplitude; uint16_t _colour_burst_time; From f232a12fadeabd3000e65824299b958e8d4e162b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Mar 2016 19:04:43 -0500 Subject: [PATCH 159/307] =?UTF-8?q?Fixed=20RAM=20timings:=20it's=20at=20le?= =?UTF-8?q?ast=20two=20and=20possibly=20three=20cycles=20to=20access=20RAM?= =?UTF-8?q?,=20and=20an=20access=20that=20overlaps=20with=20video=20fetch?= =?UTF-8?q?=20in=20Modes=200=E2=80=933=20will=20cost=20the=20length=20of?= =?UTF-8?q?=20the=20video=20fetch=20rather=20than=20somehow=20finishing=20?= =?UTF-8?q?in=20time.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Machines/Electron/Electron.cpp | 46 +++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 90d41a394..92e04693e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -80,13 +80,13 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // for the entire frame, RAM is accessible only on odd cycles; in modes below 4 // it's also accessible only outside of the pixel regions - cycles += (_fieldCycles&1)^1; + cycles += 1 + ((_fieldCycles&1)); if(_screen_mode < 4) { const int current_line = _fieldCycles >> 7; - const int line_position = _fieldCycles & 127; + const int line_position = (_fieldCycles+cycles) & 127; if(current_line >= first_graphics_line && current_line < first_graphics_line+256 && line_position >= first_graphics_cycle && line_position < first_graphics_cycle + 80) - cycles = (unsigned int)(80 + first_graphics_cycle - line_position); + cycles += (unsigned int)(80 + first_graphics_cycle - line_position); } } else @@ -95,9 +95,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { if((address & 0xff00) == 0xfe00) { - cycles += (_fieldCycles&1)^1; -// printf("%c: %02x: ", isReadOperation(operation) ? 'r' : 'w', *value); - switch(address&0xf) { case 0x0: @@ -294,29 +291,48 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin update_audio(); signal_interrupt(Interrupt::RealTimeClock); } + +// if(_fieldCycles < real_time_clock_interrupt_time+128 && _fieldCycles + cycles >= real_time_clock_interrupt_time+128) +// { +// update_audio(); +// _interrupt_status &= ~Interrupt::RealTimeClock; +// evaluate_interrupts(); +// } + else if(_fieldCycles < display_end_interrupt_time && _fieldCycles + cycles >= display_end_interrupt_time) { update_audio(); signal_interrupt(Interrupt::DisplayEnd); } +// if(_fieldCycles < display_end_interrupt_time+128 && _fieldCycles + cycles >= display_end_interrupt_time+128) +// { +// update_audio(); +// _interrupt_status &= ~Interrupt::DisplayEnd; +// evaluate_interrupts(); +// } + _fieldCycles += cycles; + if(_fieldCycles >= cycles_per_frame) + { + update_display(); + update_audio(); + _fieldCycles -= cycles_per_frame; + _displayOutputPosition = 0; + _audioOutputPosition -= _audioOutputPosition; + _currentOutputLine = 0; + } + switch(_fieldCycles) { case 64*128: case 196*128: update_audio(); break; - - case cycles_per_frame: - update_display(); - update_audio(); - _fieldCycles = 0; - _displayOutputPosition = 0; - _audioOutputPosition = 0; - _currentOutputLine = 0; - break; +// +// case cycles_per_frame: +// break; } _tape.run_for_cycles(cycles); From df3fff51c763623d515161d77cc964f0bf8acd20 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Mar 2016 19:10:53 -0500 Subject: [PATCH 160/307] =?UTF-8?q?These=20interrupts=20apparently=20last?= =?UTF-8?q?=20only=2064=C2=B5s=20at=20most.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Machines/Electron/Electron.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 92e04693e..b54e95180 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -292,12 +292,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin signal_interrupt(Interrupt::RealTimeClock); } -// if(_fieldCycles < real_time_clock_interrupt_time+128 && _fieldCycles + cycles >= real_time_clock_interrupt_time+128) -// { -// update_audio(); -// _interrupt_status &= ~Interrupt::RealTimeClock; -// evaluate_interrupts(); -// } + if(_fieldCycles < real_time_clock_interrupt_time+128 && _fieldCycles + cycles >= real_time_clock_interrupt_time+128) + { + update_audio(); + _interrupt_status &= ~Interrupt::RealTimeClock; + evaluate_interrupts(); + } else if(_fieldCycles < display_end_interrupt_time && _fieldCycles + cycles >= display_end_interrupt_time) { @@ -305,12 +305,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin signal_interrupt(Interrupt::DisplayEnd); } -// if(_fieldCycles < display_end_interrupt_time+128 && _fieldCycles + cycles >= display_end_interrupt_time+128) -// { -// update_audio(); -// _interrupt_status &= ~Interrupt::DisplayEnd; -// evaluate_interrupts(); -// } + if(_fieldCycles < display_end_interrupt_time+128 && _fieldCycles + cycles >= display_end_interrupt_time+128) + { + update_audio(); + _interrupt_status &= ~Interrupt::DisplayEnd; + evaluate_interrupts(); + } _fieldCycles += cycles; From 20ac630e4d0e7a1573e99d9b2553de9cb0d8cf62 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Mar 2016 19:58:50 -0500 Subject: [PATCH 161/307] Some minor optimisations and timing tweaks. Nothing of substance. --- Machines/Electron/Electron.cpp | 6 +++--- Processors/6502/CPU6502.hpp | 38 ++++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index b54e95180..7214a0c23 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -101,7 +101,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if(isReadOperation(operation)) { *value = _interrupt_status; - _interrupt_status &= ~0x02; + _interrupt_status &= ~PowerOnReset; } else { @@ -283,8 +283,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // } unsigned int start_of_graphics = get_first_graphics_cycle(); - const unsigned int real_time_clock_interrupt_time = start_of_graphics + 99*128; - const unsigned int display_end_interrupt_time = start_of_graphics + 257*128 + 64; + const unsigned int real_time_clock_interrupt_time = start_of_graphics + 128*128; + const unsigned int display_end_interrupt_time = start_of_graphics + 264*128; if(_fieldCycles < real_time_clock_interrupt_time && _fieldCycles + cycles >= real_time_clock_interrupt_time) { diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 4e0bfcef0..deccfaca5 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -545,9 +545,15 @@ template class Processor { OperationMoveToNextProgram }; + // These plus program below act to give the compiler permission to update these values + // without touching the class storage (i.e. it explicitly says they need be completely up + // to date in this stack frame only); which saves some complicated addressing + unsigned int scheduleProgramsReadPointer = _scheduleProgramsReadPointer; + unsigned int scheduleProgramProgramCounter = _scheduleProgramProgramCounter; + #define checkSchedule(op) \ - if(!_scheduledPrograms[_scheduleProgramsReadPointer]) {\ - _scheduleProgramsReadPointer = _scheduleProgramsWritePointer = _scheduleProgramProgramCounter = 0;\ + if(!_scheduledPrograms[scheduleProgramsReadPointer]) {\ + scheduleProgramsReadPointer = _scheduleProgramsWritePointer = scheduleProgramProgramCounter = 0;\ if(_reset_line_is_enabled)\ schedule_program(get_reset_program());\ else\ @@ -561,25 +567,26 @@ template class Processor { } checkSchedule(); - _cycles_left_to_run += number_of_cycles; + number_of_cycles += _cycles_left_to_run; + const MicroOp *program = _scheduledPrograms[scheduleProgramsReadPointer]; - while(_cycles_left_to_run > 0) { + while(number_of_cycles > 0) { - while (_ready_is_active && _cycles_left_to_run > 0) { - _cycles_left_to_run -= static_cast(this)->perform_bus_operation(BusOperation::Ready, _busAddress, _busValue); + while (_ready_is_active && number_of_cycles > 0) { + number_of_cycles -= static_cast(this)->perform_bus_operation(BusOperation::Ready, _busAddress, _busValue); } - while (!_ready_is_active && _cycles_left_to_run > 0) { + while (!_ready_is_active && number_of_cycles > 0) { if (_nextBusOperation != BusOperation::None) { _irq_request_history[0] = _irq_request_history[1]; _irq_request_history[1] = _irq_line_is_enabled && !_interruptFlag; - _cycles_left_to_run -= static_cast(this)->perform_bus_operation(_nextBusOperation, _busAddress, _busValue); + number_of_cycles -= static_cast(this)->perform_bus_operation(_nextBusOperation, _busAddress, _busValue); _nextBusOperation = BusOperation::None; } - const MicroOp cycle = _scheduledPrograms[_scheduleProgramsReadPointer][_scheduleProgramProgramCounter]; - _scheduleProgramProgramCounter++; + const MicroOp cycle = program[scheduleProgramProgramCounter]; + scheduleProgramProgramCounter++; #define read_op(val, addr) _nextBusOperation = BusOperation::ReadOpcode; _busAddress = addr; _busValue = &val #define read_mem(val, addr) _nextBusOperation = BusOperation::Read; _busAddress = addr; _busValue = &val @@ -616,10 +623,11 @@ template class Processor { break; case OperationMoveToNextProgram: - _scheduledPrograms[_scheduleProgramsReadPointer] = NULL; - _scheduleProgramsReadPointer = (_scheduleProgramsReadPointer+1)&3; - _scheduleProgramProgramCounter = 0; + _scheduledPrograms[scheduleProgramsReadPointer] = NULL; + scheduleProgramsReadPointer = (scheduleProgramsReadPointer+1)&3; + scheduleProgramProgramCounter = 0; checkSchedule(); + program = _scheduledPrograms[scheduleProgramsReadPointer]; break; #define push(v) \ @@ -1035,6 +1043,10 @@ template class Processor { _ready_is_active = true; } } + + _cycles_left_to_run = number_of_cycles; + _scheduleProgramsReadPointer = scheduleProgramsReadPointer; + _scheduleProgramProgramCounter = scheduleProgramProgramCounter; } } From b836d74e18b9408a797c40607dd46fff6d6d3cc1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Mar 2016 22:08:50 -0500 Subject: [PATCH 162/307] Believing the previous implementation to be overly complicated through premature optimisation, simplified video. It's slightly slower now. Will need work. Immediately to do: figure out how to deal with blank scan lines. --- Machines/Electron/Electron.cpp | 551 ++++++++++++++++++--------------- Machines/Electron/Electron.hpp | 22 +- 2 files changed, 309 insertions(+), 264 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 7214a0c23..130c7d040 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -14,24 +14,35 @@ using namespace Electron; -static const unsigned int cycles_per_line = 128; -static const unsigned int cycles_per_frame = 312*cycles_per_line + 64; -static const unsigned int crt_cycles_multiplier = 8; -static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; +namespace { + static const unsigned int cycles_per_line = 128; + static const unsigned int lines_per_frame = 625; + static const unsigned int cycles_per_frame = lines_per_frame * cycles_per_line; + static const unsigned int crt_cycles_multiplier = 8; + static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; -static const int first_graphics_line = 38; -static const int first_graphics_cycle = 33; -static const int last_graphics_cycle = 80 + first_graphics_cycle; + static const unsigned int field_divider_line = 312; // i.e. the line, simultaneous with which, the first field's sync ends. So if + // the first line with pixels in field 1 is the 20th in the frame, the first line + // with pixels in field 2 will be 20+field_divider_line + static const unsigned int first_graphics_line = 38; + static const unsigned int first_graphics_cycle = 33; + static const unsigned int last_graphics_cycle = 80 + first_graphics_cycle; + + static const unsigned int real_time_clock_interrupt_line = 156; + static const unsigned int display_end_interrupt_line = 256; +} + +#define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) +#define graphics_column(v) ((((v) %127) - first_graphics_cycle) % 127) Machine::Machine() : _interrupt_control(0), _interrupt_status(Interrupt::PowerOnReset), - _fieldCycles(0), + _frameCycles(0), _displayOutputPosition(0), _audioOutputPosition(0), _audioOutputPositionError(0), - _currentOutputLine(0), - _is_odd_field(false), + _current_pixel_line(-1), _crt(std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1, 1))) { _crt->set_rgb_sampling_function( @@ -69,10 +80,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // base address) then there's no way this write can affect the current frame. Sp // no need to flush the display. Otherwise, output up until now so that any // write doesn't have retroactive effect on the video output. - if(!( - (_fieldCycles < first_graphics_line * cycles_per_line) || - (address < _startLineAddress && address < 0x3000) - )) +// if(!( +// (_fieldCycles < first_graphics_line * cycles_per_line) || +// (address < _startLineAddress && address < 0x3000) +// )) update_display(); _ram[address] = *value; @@ -80,13 +91,13 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // for the entire frame, RAM is accessible only on odd cycles; in modes below 4 // it's also accessible only outside of the pixel regions - cycles += 1 + ((_fieldCycles&1)); + cycles += 1 + (_frameCycles&1); if(_screen_mode < 4) { - const int current_line = _fieldCycles >> 7; - const int line_position = (_fieldCycles+cycles) & 127; - if(current_line >= first_graphics_line && current_line < first_graphics_line+256 && line_position >= first_graphics_cycle && line_position < first_graphics_cycle + 80) - cycles += (unsigned int)(80 + first_graphics_cycle - line_position); + const int current_line = graphics_line(_frameCycles + cycles); + const int current_column = graphics_column(_frameCycles + cycles); + if(current_line < 256 && current_column < 80) + cycles += (unsigned int)(80 - current_column); } } else @@ -183,7 +194,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if(new_screen_mode == 7) new_screen_mode = 4; if(new_screen_mode != _screen_mode) { - printf("To mode %d, %d cycles into field (%d)\n", new_screen_mode, _fieldCycles, _fieldCycles >> 7); +// printf("To mode %d, at %d cycles into field (%d)\n", new_screen_mode, _fieldCycles, _fieldCycles >> 7); update_display(); _screen_mode = new_screen_mode; switch(_screen_mode) @@ -282,59 +293,50 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // printf("%04x: %02x (%d)\n", address, *value, _fieldCycles); // } - unsigned int start_of_graphics = get_first_graphics_cycle(); - const unsigned int real_time_clock_interrupt_time = start_of_graphics + 128*128; - const unsigned int display_end_interrupt_time = start_of_graphics + 264*128; + const unsigned int line_before_cycle = graphics_line(_frameCycles); + const unsigned int line_after_cycle = graphics_line(_frameCycles + cycles); - if(_fieldCycles < real_time_clock_interrupt_time && _fieldCycles + cycles >= real_time_clock_interrupt_time) + // implicit assumption here: the number of 2Mhz cycles this bus operation will take + // is never longer than a line. On the Electron, it's a safe one. + switch(line_before_cycle) { - update_audio(); - signal_interrupt(Interrupt::RealTimeClock); + case real_time_clock_interrupt_line: + if(line_after_cycle > real_time_clock_interrupt_line) + signal_interrupt(Interrupt::RealTimeClock); + break; + case real_time_clock_interrupt_line+1: + if(line_after_cycle > real_time_clock_interrupt_line+1) + clear_interrupt(Interrupt::RealTimeClock); + break; + + case display_end_interrupt_line: + if(line_after_cycle > display_end_interrupt_line) + signal_interrupt(Interrupt::DisplayEnd); + break; + case display_end_interrupt_line+1: + if(line_after_cycle > display_end_interrupt_line+1) + clear_interrupt(Interrupt::DisplayEnd); + break; } - if(_fieldCycles < real_time_clock_interrupt_time+128 && _fieldCycles + cycles >= real_time_clock_interrupt_time+128) - { - update_audio(); - _interrupt_status &= ~Interrupt::RealTimeClock; - evaluate_interrupts(); - } + _frameCycles += cycles; - else if(_fieldCycles < display_end_interrupt_time && _fieldCycles + cycles >= display_end_interrupt_time) - { - update_audio(); - signal_interrupt(Interrupt::DisplayEnd); - } - - if(_fieldCycles < display_end_interrupt_time+128 && _fieldCycles + cycles >= display_end_interrupt_time+128) - { - update_audio(); - _interrupt_status &= ~Interrupt::DisplayEnd; - evaluate_interrupts(); - } - - _fieldCycles += cycles; - - if(_fieldCycles >= cycles_per_frame) + // deal with frame wraparound by updating the two dependent subsystems + // as though the exact end of frame had been hit, then reset those + // and allow the frame cycle counter to assume its real value + if(_frameCycles >= cycles_per_frame) { + unsigned int nextFrameCycles = _frameCycles - cycles_per_frame; + _frameCycles = cycles_per_frame; update_display(); update_audio(); - _fieldCycles -= cycles_per_frame; _displayOutputPosition = 0; - _audioOutputPosition -= _audioOutputPosition; - _currentOutputLine = 0; - } - - switch(_fieldCycles) - { - case 64*128: - case 196*128: - update_audio(); - break; -// -// case cycles_per_frame: -// break; + _audioOutputPosition = 0; + _frameCycles = nextFrameCycles; } + if(!(_frameCycles&31)) + update_audio(); _tape.run_for_cycles(cycles); return cycles; @@ -369,6 +371,12 @@ inline void Machine::signal_interrupt(Electron::Interrupt interrupt) evaluate_interrupts(); } +inline void Machine::clear_interrupt(Electron::Interrupt interrupt) +{ + _interrupt_status &= ~interrupt; + evaluate_interrupts(); +} + void Machine::tape_did_change_interrupt_status(Tape *tape) { _interrupt_status = (_interrupt_status & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | _tape.get_interrupt_status(); @@ -390,180 +398,155 @@ inline void Machine::evaluate_interrupts() inline void Machine::update_audio() { - int difference = (int)_fieldCycles - _audioOutputPosition; - _audioOutputPosition = (int)_fieldCycles; + int difference = (int)_frameCycles - _audioOutputPosition; + _audioOutputPosition = (int)_frameCycles; _speaker.run_for_cycles((_audioOutputPositionError + difference) >> 4); _audioOutputPositionError = (_audioOutputPositionError + difference)&15; } -inline void Machine::reset_pixel_output() +inline void Machine::start_pixel_line() { - display_x = 0; - display_y = 0; - _currentScreenAddress = _startLineAddress = _startScreenAddress; - _currentOutputLine = 0; + _current_pixel_line = (_current_pixel_line+1)&255; + if(!(_current_pixel_line&7)) + { + _startLineAddress += ((_screen_mode < 4) ? 80 : 40) * 8 - 8; + } + if(!_current_pixel_line) + { + _startLineAddress = _startScreenAddress; + } + else + { + _startLineAddress++; + } + _currentScreenAddress = _startLineAddress; + _current_pixel_column = 0; + + _crt->allocate_write_area(640); + _currentLine = _crt->get_write_target_for_buffer(0); } -inline void Machine::output_pixels(int start_x, int number_of_pixels) +inline void Machine::end_pixel_line() { - if(number_of_pixels) + _crt->output_data(640, 1); +} + +inline void Machine::output_pixels(unsigned int number_of_cycles) +{ + while(number_of_cycles--) { - if( - ((_screen_mode == 3) || (_screen_mode == 6)) && - (((display_y%10) >= 8) || (display_y >= 250)) - ) + if(!(_current_pixel_column&1) || _screen_mode < 4) { - end_pixel_output(); - _crt->output_blank((unsigned int)number_of_pixels * crt_cycles_multiplier); - return; + if(_currentScreenAddress&32768) + { + _currentScreenAddress = (_screenModeBaseAddress + _currentScreenAddress)&32767; + } + + _last_pixel_byte = _ram[_currentScreenAddress]; + _currentScreenAddress = _currentScreenAddress+8; } - unsigned int newDivider = 0; switch(_screen_mode) { - case 0: case 3: newDivider = 1; break; - case 1: case 4: case 6: newDivider = 2; break; - case 2: case 5: newDivider = 4; break; - } - bool is40Column = (_screen_mode > 3); - - if(!_writePointer || newDivider != _currentOutputDivider || _isOutputting40Columns != is40Column) - { - end_pixel_output(); - _currentOutputDivider = newDivider; - _isOutputting40Columns = is40Column; - _crt->allocate_write_area(640 / newDivider); - _currentLine = _writePointer = _crt->get_write_target_for_buffer(0); - } - - if(is40Column) - { - number_of_pixels = ((start_x + number_of_pixels) >> 1) - (start_x >> 1); - } - -#define GetNextPixels() \ - if(_currentScreenAddress&32768)\ - {\ - _currentScreenAddress = (_screenModeBaseAddress + _currentScreenAddress)&32767;\ - }\ - uint8_t pixels = _ram[_currentScreenAddress];\ - _currentScreenAddress = _currentScreenAddress+8 - switch(_screen_mode) - { - default: - case 0: case 3: case 4: case 6: - while(number_of_pixels--) - { - GetNextPixels(); - - _writePointer[0] = _palette[(pixels&0x80) >> 4]; - _writePointer[1] = _palette[(pixels&0x40) >> 3]; - _writePointer[2] = _palette[(pixels&0x20) >> 2]; - _writePointer[3] = _palette[(pixels&0x10) >> 1]; - _writePointer[4] = _palette[(pixels&0x08) >> 0]; - _writePointer[5] = _palette[(pixels&0x04) << 1]; - _writePointer[6] = _palette[(pixels&0x02) << 2]; - _writePointer[7] = _palette[(pixels&0x01) << 3]; - - _writePointer += 8; - } + case 3: + case 0: + { + _currentLine[0] = _palette[(_last_pixel_byte&0x80) >> 4]; + _currentLine[1] = _palette[(_last_pixel_byte&0x40) >> 3]; + _currentLine[2] = _palette[(_last_pixel_byte&0x20) >> 2]; + _currentLine[3] = _palette[(_last_pixel_byte&0x10) >> 1]; + _currentLine[4] = _palette[(_last_pixel_byte&0x08) >> 0]; + _currentLine[5] = _palette[(_last_pixel_byte&0x04) << 1]; + _currentLine[6] = _palette[(_last_pixel_byte&0x02) << 2]; + _currentLine[7] = _palette[(_last_pixel_byte&0x01) << 3]; + } break; - case 1: case 5: - while(number_of_pixels--) - { - GetNextPixels(); - - _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x08) >> 2)]; - _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x04) >> 1)]; - _writePointer[2] = _palette[((pixels&0x20) >> 2) | ((pixels&0x02) >> 0)]; - _writePointer[3] = _palette[((pixels&0x10) >> 1) | ((pixels&0x01) << 1)]; - - _writePointer += 4; - } + case 1: + { + _currentLine[0] = + _currentLine[1] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; + _currentLine[2] = + _currentLine[3] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; + _currentLine[4] = + _currentLine[5] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; + _currentLine[6] = + _currentLine[7] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; + } break; case 2: - while(number_of_pixels--) + { + _currentLine[0] = + _currentLine[1] = + _currentLine[2] = + _currentLine[3] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x20) >> 3) | ((_last_pixel_byte&0x08) >> 2) | ((_last_pixel_byte&0x02) >> 1)]; + _currentLine[4] = + _currentLine[5] = + _currentLine[6] = + _currentLine[7] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x10) >> 2) | ((_last_pixel_byte&0x04) >> 1) | ((_last_pixel_byte&0x01) >> 0)]; + } + break; + + case 6: + case 4: + { + if(_current_pixel_column&1) { - GetNextPixels(); - - _writePointer[0] = _palette[((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1)]; - _writePointer[1] = _palette[((pixels&0x40) >> 3) | ((pixels&0x10) >> 2) | ((pixels&0x04) >> 1) | ((pixels&0x01) >> 0)]; - - _writePointer += 2; + _currentLine[0] = + _currentLine[1] = _palette[(_last_pixel_byte&0x08) >> 0]; + _currentLine[2] = + _currentLine[3] = _palette[(_last_pixel_byte&0x04) << 1]; + _currentLine[4] = + _currentLine[5] = _palette[(_last_pixel_byte&0x02) << 2]; + _currentLine[6] = + _currentLine[7] = _palette[(_last_pixel_byte&0x01) << 3]; } + else + { + _currentLine[0] = + _currentLine[1] = _palette[(_last_pixel_byte&0x80) >> 4]; + _currentLine[2] = + _currentLine[3] = _palette[(_last_pixel_byte&0x40) >> 3]; + _currentLine[4] = + _currentLine[5] = _palette[(_last_pixel_byte&0x20) >> 2]; + _currentLine[6] = + _currentLine[7] = _palette[(_last_pixel_byte&0x10) >> 1]; + } + } + break; + + case 5: + { + if(_current_pixel_column&1) + { + _currentLine[0] = + _currentLine[1] = + _currentLine[2] = + _currentLine[3] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; + _currentLine[4] = + _currentLine[5] = + _currentLine[6] = + _currentLine[7] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; + } + else + { + _currentLine[0] = + _currentLine[1] = + _currentLine[2] = + _currentLine[3] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; + _currentLine[4] = + _currentLine[5] = + _currentLine[6] = + _currentLine[7] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; + } + } break; } + + _current_pixel_column++; + _currentLine += 8; } -#undef GetNextPixels -} - -inline void Machine::end_pixel_output() -{ - if(_currentLine != nullptr) - { - _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); - _writePointer = _currentLine = nullptr; - } -} - -inline void Machine::update_pixels_to_position(int x, int y) -{ - while((display_x < x) || (display_y < y)) - { - if(display_x < first_graphics_cycle) - { - display_x++; - - if(display_x == first_graphics_cycle) - { - _crt->output_sync(9 * crt_cycles_multiplier); - _crt->output_blank((first_graphics_cycle - 9) * crt_cycles_multiplier); - _currentScreenAddress = _startLineAddress; - } - continue; - } - - if(display_x < last_graphics_cycle) - { - int cycles_to_output = (display_y < y) ? last_graphics_cycle - display_x : std::min(last_graphics_cycle - display_x, x - display_x); - output_pixels(display_x, cycles_to_output); - display_x += cycles_to_output; - - if(display_x == last_graphics_cycle) - { - end_pixel_output(); - } - continue; - } - - display_x++; - if(display_x == 128) - { - _crt->output_blank((128 - 80 - first_graphics_cycle) * crt_cycles_multiplier); - display_x = 0; - display_y++; - - - if(((_screen_mode != 3) && (_screen_mode != 6)) || ((display_y%10) < 8)) - { - _startLineAddress++; - _currentOutputLine++; - - if(_currentOutputLine == 8) - { - _currentOutputLine = 0; - _startLineAddress = (_startLineAddress - 8) + ((_screen_mode < 4) ? 80 : 40)*8; - } - } - } - } -} - -inline unsigned int Machine::get_first_graphics_cycle() -{ - return (first_graphics_line * cycles_per_line) - (_is_odd_field ? 0 : 64); } inline void Machine::update_display() @@ -582,58 +565,126 @@ inline void Machine::update_display() */ - const unsigned int end_of_top = get_first_graphics_cycle(); - const unsigned int end_of_graphics = end_of_top + 256 * cycles_per_line; - - // does the top region need to be output? - if(_displayOutputPosition < end_of_top && _fieldCycles >= end_of_top) + int final_line = _frameCycles >> 7; + while(_displayOutputPosition < _frameCycles) { - _crt->output_sync(320 * crt_cycles_multiplier); - if(_is_odd_field) _crt->output_blank(64 * crt_cycles_multiplier); - _displayOutputPosition += 320 + (_is_odd_field ? 64 : 0); + int line = _displayOutputPosition >> 7; - while(_displayOutputPosition < end_of_top) + // Priority one: sync. + // =================== + + // full sync lines are 0, 1, field_divider_line+1 and field_divider_line+2 + if(line == 0 || line == 1 || line == field_divider_line+1 || line == field_divider_line+2) { + // wait for the line to complete before signalling + if(final_line == line) return; + _crt->output_sync(128 * crt_cycles_multiplier); _displayOutputPosition += 128; + continue; + } + + // line 2 is a left-sync line + if(line == 2) + { + // wait for the line to complete before signalling + if(final_line == line) return; + _crt->output_sync(64 * crt_cycles_multiplier); + _crt->output_blank(64 * crt_cycles_multiplier); + _displayOutputPosition += 128; + continue; + } + + // line field_divider_line is a right-sync line + if(line == field_divider_line) + { + // wait for the line to complete before signalling + if(final_line == line) return; + _crt->output_blank(64 * crt_cycles_multiplier); + _crt->output_sync(64 * crt_cycles_multiplier); + _displayOutputPosition += 128; + continue; + } + + // Priority two: blank lines. + // ========================== + // + // Given that it is not a sync line, this is a blank line if it is less than first_graphics_line, or greater + // than first_graphics_line+255 and less than first_graphics_line+field_divider_line, or greater than + // first_graphics_line+field_divider_line+255 (TODO: or this is Mode 3 or 6 and this should be blank) + if( + line < first_graphics_line || + (line > first_graphics_line+255 && line < first_graphics_line+field_divider_line) || + line > first_graphics_line+field_divider_line+255) + { + if(final_line == line) return; _crt->output_sync(9 * crt_cycles_multiplier); _crt->output_blank(119 * crt_cycles_multiplier); + _displayOutputPosition += 128; + continue; } - assert(_displayOutputPosition == end_of_top); + // Final possibility: this is a pixel line. + // ======================================== - reset_pixel_output(); - } - - // is this the pixel region? - if(_displayOutputPosition >= end_of_top && _displayOutputPosition < end_of_graphics) - { - unsigned int final_position = std::min(_fieldCycles, end_of_graphics) - end_of_top; - unsigned int final_line = final_position >> 7; - unsigned int final_pixel = final_position & 127; - update_pixels_to_position((int)final_pixel, (int)final_line); - _displayOutputPosition = final_position + end_of_top; - } - - // is this the bottom region? - if(_displayOutputPosition >= end_of_graphics && _displayOutputPosition < cycles_per_frame) - { - unsigned int remaining_time = cycles_per_frame - _displayOutputPosition; - while(remaining_time >= 128) + // determine how far we're going from left to right + unsigned int this_cycle = _displayOutputPosition&127; + unsigned int final_cycle = _frameCycles&127; + if(final_line > line) { - _crt->output_sync(9 * crt_cycles_multiplier); - _crt->output_blank(119 * crt_cycles_multiplier); - remaining_time -= 128; + final_cycle = 128; } - if(remaining_time == 64) + // output format is: + // 9 cycles: sync + // ... to 24 cycles: colour burst + // ... to first_graphics_cycle: blank + // ... for 80 cycles: pixels + // ... until end of line: blank + while(this_cycle < final_cycle) { - _crt->output_sync(9 * crt_cycles_multiplier); - _crt->output_blank(55 * crt_cycles_multiplier); + if(this_cycle < 9) + { + if(final_cycle < 9) return; + _crt->output_sync(9 * crt_cycles_multiplier); + _displayOutputPosition += 9; + this_cycle = 9; + } + + if(this_cycle < 24) + { + if(final_cycle < 24) return; + _crt->output_colour_burst((24-9) * crt_cycles_multiplier, 0, 12); + _displayOutputPosition += 24-9; + this_cycle = 24; + // TODO: phase shouldn't be zero on every line + } + + if(this_cycle < first_graphics_cycle) + { + if(final_cycle < first_graphics_cycle) return; + _crt->output_blank((first_graphics_cycle - 24) * crt_cycles_multiplier); + _displayOutputPosition += first_graphics_cycle - 24; + this_cycle = first_graphics_cycle; + start_pixel_line(); + } + + if(this_cycle < first_graphics_cycle + 80) + { + unsigned int length_to_output = std::min(final_cycle, (first_graphics_cycle + 80)) - this_cycle; + output_pixels(length_to_output); + _displayOutputPosition += length_to_output; + this_cycle += length_to_output; + } + + if(this_cycle >= first_graphics_cycle + 80) + { + if(final_cycle < 128) return; + end_pixel_line(); + _crt->output_blank((128 - (first_graphics_cycle + 80)) * crt_cycles_multiplier); + _displayOutputPosition += 128 - (first_graphics_cycle + 80); + this_cycle = 128; + } } - - _displayOutputPosition = cycles_per_frame; - - _is_odd_field ^= true; } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 75758366c..69d6cbba9 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -157,19 +157,15 @@ class Machine: public CPU6502::Processor, Tape::Delegate { private: inline void update_display(); - inline void update_pixels_to_position(int x, int y); - inline void output_pixels(int start_x, int number_of_pixels); - inline void end_pixel_output(); - inline void reset_pixel_output(); - inline unsigned int get_first_graphics_cycle(); + inline void start_pixel_line(); + inline void end_pixel_line(); + inline void output_pixels(unsigned int number_of_cycles); inline void update_audio(); inline void signal_interrupt(Interrupt interrupt); + inline void clear_interrupt(Interrupt interrupt); inline void evaluate_interrupts(); - // Pixel tracker; - int display_x, display_y; - // Things that directly constitute the memory map. uint8_t _roms[16][16384]; uint8_t _os[16384], _ram[32768]; @@ -184,18 +180,16 @@ class Machine: public CPU6502::Processor, Tape::Delegate { uint16_t _startScreenAddress; // Counters related to simultaneous subsystems; - unsigned int _fieldCycles, _displayOutputPosition; + unsigned int _frameCycles, _displayOutputPosition; int _audioOutputPosition, _audioOutputPositionError; // Display generation. uint16_t _startLineAddress, _currentScreenAddress; - int _currentOutputLine; - bool _is_odd_field; + int _current_pixel_line, _current_pixel_column; + uint8_t _last_pixel_byte; // CRT output - unsigned int _currentOutputDivider; - bool _isOutputting40Columns; - uint8_t *_currentLine, *_writePointer; + uint8_t *_currentLine; // Tape. Tape _tape; From a89225987fe3b0c6154d4c32ef88e826607c5b44 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Mar 2016 22:19:54 -0500 Subject: [PATCH 163/307] Blank lines are back. --- Machines/Electron/Electron.cpp | 217 ++++++++++++++++++--------------- Machines/Electron/Electron.hpp | 3 +- 2 files changed, 121 insertions(+), 99 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 130c7d040..590238434 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -407,145 +407,166 @@ inline void Machine::update_audio() inline void Machine::start_pixel_line() { _current_pixel_line = (_current_pixel_line+1)&255; - if(!(_current_pixel_line&7)) - { - _startLineAddress += ((_screen_mode < 4) ? 80 : 40) * 8 - 8; - } if(!_current_pixel_line) { _startLineAddress = _startScreenAddress; + _current_character_row = 0; + _isBlankLine = false; } else { - _startLineAddress++; + bool mode_has_blank_lines = (_screen_mode == 6) || (_screen_mode == 3); + _isBlankLine = (mode_has_blank_lines && ((_current_character_row > 7 && _current_character_row < 10) || (_current_pixel_line > 249))); + + if(!_isBlankLine) + { + _startLineAddress++; + + if(_current_character_row > 7) + { + _startLineAddress += ((_screen_mode < 4) ? 80 : 40) * 8 - 8; + _current_character_row = 0; + } + } } _currentScreenAddress = _startLineAddress; _current_pixel_column = 0; - _crt->allocate_write_area(640); - _currentLine = _crt->get_write_target_for_buffer(0); + if(!_isBlankLine) + { + _crt->allocate_write_area(640); + _currentLine = _crt->get_write_target_for_buffer(0); + } } inline void Machine::end_pixel_line() { - _crt->output_data(640, 1); + if(!_isBlankLine) _crt->output_data(640, 1); + _current_character_row++; } inline void Machine::output_pixels(unsigned int number_of_cycles) { - while(number_of_cycles--) + if(_isBlankLine) { - if(!(_current_pixel_column&1) || _screen_mode < 4) + _crt->output_blank(number_of_cycles * crt_cycles_multiplier); + } + else + { + while(number_of_cycles--) { - if(_currentScreenAddress&32768) + if(!(_current_pixel_column&1) || _screen_mode < 4) { - _currentScreenAddress = (_screenModeBaseAddress + _currentScreenAddress)&32767; - } - - _last_pixel_byte = _ram[_currentScreenAddress]; - _currentScreenAddress = _currentScreenAddress+8; - } - - switch(_screen_mode) - { - case 3: - case 0: - { - _currentLine[0] = _palette[(_last_pixel_byte&0x80) >> 4]; - _currentLine[1] = _palette[(_last_pixel_byte&0x40) >> 3]; - _currentLine[2] = _palette[(_last_pixel_byte&0x20) >> 2]; - _currentLine[3] = _palette[(_last_pixel_byte&0x10) >> 1]; - _currentLine[4] = _palette[(_last_pixel_byte&0x08) >> 0]; - _currentLine[5] = _palette[(_last_pixel_byte&0x04) << 1]; - _currentLine[6] = _palette[(_last_pixel_byte&0x02) << 2]; - _currentLine[7] = _palette[(_last_pixel_byte&0x01) << 3]; - } - break; - - case 1: - { - _currentLine[0] = - _currentLine[1] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; - _currentLine[2] = - _currentLine[3] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; - _currentLine[4] = - _currentLine[5] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; - _currentLine[6] = - _currentLine[7] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; - } - break; - - case 2: - { - _currentLine[0] = - _currentLine[1] = - _currentLine[2] = - _currentLine[3] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x20) >> 3) | ((_last_pixel_byte&0x08) >> 2) | ((_last_pixel_byte&0x02) >> 1)]; - _currentLine[4] = - _currentLine[5] = - _currentLine[6] = - _currentLine[7] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x10) >> 2) | ((_last_pixel_byte&0x04) >> 1) | ((_last_pixel_byte&0x01) >> 0)]; - } - break; - - case 6: - case 4: - { - if(_current_pixel_column&1) + if(_currentScreenAddress&32768) { - _currentLine[0] = - _currentLine[1] = _palette[(_last_pixel_byte&0x08) >> 0]; - _currentLine[2] = - _currentLine[3] = _palette[(_last_pixel_byte&0x04) << 1]; - _currentLine[4] = - _currentLine[5] = _palette[(_last_pixel_byte&0x02) << 2]; - _currentLine[6] = + _currentScreenAddress = (_screenModeBaseAddress + _currentScreenAddress)&32767; + } + + _last_pixel_byte = _ram[_currentScreenAddress]; + _currentScreenAddress = _currentScreenAddress+8; + } + + switch(_screen_mode) + { + case 3: + case 0: + { + _currentLine[0] = _palette[(_last_pixel_byte&0x80) >> 4]; + _currentLine[1] = _palette[(_last_pixel_byte&0x40) >> 3]; + _currentLine[2] = _palette[(_last_pixel_byte&0x20) >> 2]; + _currentLine[3] = _palette[(_last_pixel_byte&0x10) >> 1]; + _currentLine[4] = _palette[(_last_pixel_byte&0x08) >> 0]; + _currentLine[5] = _palette[(_last_pixel_byte&0x04) << 1]; + _currentLine[6] = _palette[(_last_pixel_byte&0x02) << 2]; _currentLine[7] = _palette[(_last_pixel_byte&0x01) << 3]; } - else - { - _currentLine[0] = - _currentLine[1] = _palette[(_last_pixel_byte&0x80) >> 4]; - _currentLine[2] = - _currentLine[3] = _palette[(_last_pixel_byte&0x40) >> 3]; - _currentLine[4] = - _currentLine[5] = _palette[(_last_pixel_byte&0x20) >> 2]; - _currentLine[6] = - _currentLine[7] = _palette[(_last_pixel_byte&0x10) >> 1]; - } - } - break; + break; - case 5: - { - if(_current_pixel_column&1) + case 1: { _currentLine[0] = - _currentLine[1] = + _currentLine[1] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; _currentLine[2] = - _currentLine[3] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; + _currentLine[3] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; _currentLine[4] = - _currentLine[5] = + _currentLine[5] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; _currentLine[6] = _currentLine[7] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; } - else + break; + + case 2: { _currentLine[0] = _currentLine[1] = _currentLine[2] = - _currentLine[3] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; + _currentLine[3] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x20) >> 3) | ((_last_pixel_byte&0x08) >> 2) | ((_last_pixel_byte&0x02) >> 1)]; _currentLine[4] = _currentLine[5] = _currentLine[6] = - _currentLine[7] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; + _currentLine[7] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x10) >> 2) | ((_last_pixel_byte&0x04) >> 1) | ((_last_pixel_byte&0x01) >> 0)]; } - } - break; - } + break; - _current_pixel_column++; - _currentLine += 8; + case 6: + case 4: + { + if(_current_pixel_column&1) + { + _currentLine[0] = + _currentLine[1] = _palette[(_last_pixel_byte&0x08) >> 0]; + _currentLine[2] = + _currentLine[3] = _palette[(_last_pixel_byte&0x04) << 1]; + _currentLine[4] = + _currentLine[5] = _palette[(_last_pixel_byte&0x02) << 2]; + _currentLine[6] = + _currentLine[7] = _palette[(_last_pixel_byte&0x01) << 3]; + } + else + { + _currentLine[0] = + _currentLine[1] = _palette[(_last_pixel_byte&0x80) >> 4]; + _currentLine[2] = + _currentLine[3] = _palette[(_last_pixel_byte&0x40) >> 3]; + _currentLine[4] = + _currentLine[5] = _palette[(_last_pixel_byte&0x20) >> 2]; + _currentLine[6] = + _currentLine[7] = _palette[(_last_pixel_byte&0x10) >> 1]; + } + } + break; + + case 5: + { + if(_current_pixel_column&1) + { + _currentLine[0] = + _currentLine[1] = + _currentLine[2] = + _currentLine[3] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; + _currentLine[4] = + _currentLine[5] = + _currentLine[6] = + _currentLine[7] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; + } + else + { + _currentLine[0] = + _currentLine[1] = + _currentLine[2] = + _currentLine[3] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; + _currentLine[4] = + _currentLine[5] = + _currentLine[6] = + _currentLine[7] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; + } + } + break; + } + + _current_pixel_column++; + _currentLine += 8; + } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 69d6cbba9..20ab62ccb 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -185,8 +185,9 @@ class Machine: public CPU6502::Processor, Tape::Delegate { // Display generation. uint16_t _startLineAddress, _currentScreenAddress; - int _current_pixel_line, _current_pixel_column; + int _current_pixel_line, _current_pixel_column, _current_character_row; uint8_t _last_pixel_byte; + bool _isBlankLine; // CRT output uint8_t *_currentLine; From e305fd932659d3a85f4b41676d5df89b6a011f10 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Mar 2016 22:40:18 -0500 Subject: [PATCH 164/307] Adjusted numbers, empirically. --- Machines/Electron/Electron.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 590238434..ce63bccb9 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -26,10 +26,9 @@ namespace { // with pixels in field 2 will be 20+field_divider_line static const unsigned int first_graphics_line = 38; static const unsigned int first_graphics_cycle = 33; - static const unsigned int last_graphics_cycle = 80 + first_graphics_cycle; - static const unsigned int real_time_clock_interrupt_line = 156; - static const unsigned int display_end_interrupt_line = 256; + static const unsigned int real_time_clock_interrupt_line = 100; + static const unsigned int display_end_interrupt_line = 264; } #define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) From 763d5e8819a8d75332c3c365a21bf730e2112ae8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Mar 2016 23:12:28 -0500 Subject: [PATCH 165/307] Access timing now appears to be exactly on the nail. Disabled automatic interrupt expiration again as I'm not persuaded it's correct for at least the RTC. --- Machines/Electron/Electron.cpp | 35 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ce63bccb9..cde6f6438 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -32,7 +32,7 @@ namespace { } #define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) -#define graphics_column(v) ((((v) %127) - first_graphics_cycle) % 127) +#define graphics_column(v) ((((v) & 127) - first_graphics_cycle + 128) & 127) Machine::Machine() : _interrupt_control(0), @@ -93,9 +93,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin cycles += 1 + (_frameCycles&1); if(_screen_mode < 4) { - const int current_line = graphics_line(_frameCycles + cycles); - const int current_column = graphics_column(_frameCycles + cycles); - if(current_line < 256 && current_column < 80) + update_display(); + const int current_line = graphics_line(_frameCycles); // + cycles + const int current_column = graphics_column(_frameCycles); // + cycles + if(current_line < 256 && current_column < 80 && !_isBlankLine) cycles += (unsigned int)(80 - current_column); } } @@ -297,25 +298,15 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // implicit assumption here: the number of 2Mhz cycles this bus operation will take // is never longer than a line. On the Electron, it's a safe one. - switch(line_before_cycle) + if(line_before_cycle != line_after_cycle) { - case real_time_clock_interrupt_line: - if(line_after_cycle > real_time_clock_interrupt_line) - signal_interrupt(Interrupt::RealTimeClock); - break; - case real_time_clock_interrupt_line+1: - if(line_after_cycle > real_time_clock_interrupt_line+1) - clear_interrupt(Interrupt::RealTimeClock); - break; - - case display_end_interrupt_line: - if(line_after_cycle > display_end_interrupt_line) - signal_interrupt(Interrupt::DisplayEnd); - break; - case display_end_interrupt_line+1: - if(line_after_cycle > display_end_interrupt_line+1) - clear_interrupt(Interrupt::DisplayEnd); - break; + switch(line_before_cycle) + { + case real_time_clock_interrupt_line: signal_interrupt(Interrupt::RealTimeClock); break; +// case real_time_clock_interrupt_line+1: clear_interrupt(Interrupt::RealTimeClock); break; + case display_end_interrupt_line: signal_interrupt(Interrupt::DisplayEnd); break; +// case display_end_interrupt_line+1: clear_interrupt(Interrupt::DisplayEnd); break; + } } _frameCycles += cycles; From cfa616d5937cb2c0b705a84db56ced3babc6f260 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Mar 2016 11:51:20 -0500 Subject: [PATCH 166/307] Fixed: timing issue versus supplied test ROM was failure to include the Plus 1 ROM in the mix. --- .gitignore | 3 +++ Machines/Electron/Electron.cpp | 7 ++++--- OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj | 4 ++++ .../Mac/Clock Signal/Documents/ElectronDocument.swift | 3 +++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 624ccbf1e..c2d42ca10 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ DerivedData *.xcuserstate .DS_Store +# Exclude Electron ROMs +OSBindings/Mac/Clock Signal/Resources/Electron/* + # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index cde6f6438..732b14d22 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -28,7 +28,7 @@ namespace { static const unsigned int first_graphics_cycle = 33; static const unsigned int real_time_clock_interrupt_line = 100; - static const unsigned int display_end_interrupt_line = 264; + static const unsigned int display_end_interrupt_line = 255; } #define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) @@ -293,8 +293,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // printf("%04x: %02x (%d)\n", address, *value, _fieldCycles); // } - const unsigned int line_before_cycle = graphics_line(_frameCycles); - const unsigned int line_after_cycle = graphics_line(_frameCycles + cycles); + const unsigned int pixel_line_clock = _frameCycles + 128 - first_graphics_cycle + 80; + const unsigned int line_before_cycle = graphics_line(pixel_line_clock); + const unsigned int line_after_cycle = graphics_line(pixel_line_clock + cycles); // implicit assumption here: the number of 2Mhz cycles this bus operation will take // is never longer than a line. On the Electron, it's a safe one. diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 7a8130733..9ba0da4f8 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -308,6 +308,7 @@ 4BBF99161C8FBA6F0075DAFB /* CRTRunBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */; }; 4BBF99171C8FBA6F0075DAFB /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */; }; 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; + 4BCB70B41C947DDC005B1712 /* plus1.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BCB70B31C947DDC005B1712 /* plus1.rom */; }; 4BE5F85E1C3E1C2500C43F01 /* basic.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85C1C3E1C2500C43F01 /* basic.rom */; }; 4BE5F85F1C3E1C2500C43F01 /* os.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85D1C3E1C2500C43F01 /* os.rom */; }; /* End PBXBuildFile section */ @@ -665,6 +666,7 @@ 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = ""; }; 4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = ""; }; 4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = ""; }; + 4BCB70B31C947DDC005B1712 /* plus1.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = plus1.rom; sourceTree = ""; }; 4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = ""; }; 4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1221,6 +1223,7 @@ 4BE5F85B1C3E1C2500C43F01 /* Electron */ = { isa = PBXGroup; children = ( + 4BCB70B31C947DDC005B1712 /* plus1.rom */, 4BE5F85C1C3E1C2500C43F01 /* basic.rom */, 4BE5F85D1C3E1C2500C43F01 /* os.rom */, ); @@ -1339,6 +1342,7 @@ 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */, 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, 4BE5F85F1C3E1C2500C43F01 /* os.rom in Resources */, + 4BCB70B41C947DDC005B1712 /* plus1.rom in Resources */, 4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */, 4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, 4BE5F85E1C3E1C2500C43F01 /* basic.rom in Resources */, diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 289032701..46b596615 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -52,6 +52,9 @@ class ElectronDocument: MachineDocument { } override func readFromData(data: NSData, ofType typeName: String) throws { + if let plus1Path = NSBundle.mainBundle().pathForResource("plus1", ofType: "rom") { + electron.setROM(NSData(contentsOfFile: plus1Path)!, slot: 12) + } electron.setROM(data, slot: 15) } From 63273111305f580d956e8c55078efb0aa27fb64a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Mar 2016 20:32:26 -0500 Subject: [PATCH 167/307] Made an attempt to separate and clarify on tape interrupts. --- Machines/Electron/Electron.cpp | 63 +++++++++++++++++----------------- Machines/Electron/Electron.hpp | 15 +++++--- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 732b14d22..3d14430c8 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -760,7 +760,13 @@ void Speaker::set_is_enabled(bool is_enabled) Tape */ -Tape::Tape() : _is_running(false), _data_register(0), _delegate(nullptr), _output_bits_remaining(0), _last_posted_interrupt_status(0), _interrupt_status(0) {} +Tape::Tape() : + _is_running(false), + _data_register(0), + _delegate(nullptr), + _output({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}), + _last_posted_interrupt_status(0), + _interrupt_status(0) {} void Tape::set_tape(std::shared_ptr tape) { @@ -770,22 +776,22 @@ void Tape::set_tape(std::shared_ptr tape) inline void Tape::get_next_tape_pulse() { - _time_into_pulse = 0; - _current_pulse = _tape->get_next_pulse(); - if(_input_pulse_stepper == nullptr || _current_pulse.length.clock_rate != _input_pulse_stepper->get_output_rate()) + _input.time_into_pulse = 0; + _input.current_pulse = _tape->get_next_pulse(); + if(_input.pulse_stepper == nullptr || _input.current_pulse.length.clock_rate != _input.pulse_stepper->get_output_rate()) { - _input_pulse_stepper = std::unique_ptr(new SignalProcessing::Stepper(_current_pulse.length.clock_rate, 2000000)); + _input.pulse_stepper = std::unique_ptr(new SignalProcessing::Stepper(_input.current_pulse.length.clock_rate, 2000000)); } } inline void Tape::push_tape_bit(uint16_t bit) { _data_register = (uint16_t)((_data_register >> 1) | (bit << 10)); - if(_bits_since_start) + if(_input.minimum_bits_until_full) { - _bits_since_start--; + _input.minimum_bits_until_full--; - if(_bits_since_start == 7) + if(_input.minimum_bits_until_full == 7) { _interrupt_status &= ~Interrupt::ReceiveDataFull; } @@ -795,7 +801,7 @@ inline void Tape::push_tape_bit(uint16_t bit) inline void Tape::reset_tape_input() { - _bits_since_start = 0; + _input.minimum_bits_until_full = 0; // _interrupt_status &= ~(Interrupt::ReceiveDataFull | Interrupt::TransmitDataEmpty | Interrupt::HighToneDetect); // // if(_last_posted_interrupt_status != _interrupt_status) @@ -807,12 +813,12 @@ inline void Tape::reset_tape_input() inline void Tape::evaluate_interrupts() { - if(!_bits_since_start) + if(!_input.minimum_bits_until_full) { if((_data_register&0x3) == 0x1) { _interrupt_status |= Interrupt::ReceiveDataFull; - if(_is_in_input_mode) _bits_since_start = 9; + if(_is_in_input_mode) _input.minimum_bits_until_full = 9; } } @@ -844,14 +850,15 @@ inline void Tape::set_is_in_input_mode(bool is_in_input_mode) inline void Tape::set_counter(uint8_t value) { - _output_pulse_stepper = std::unique_ptr(new SignalProcessing::Stepper(1200, 2000000)); + _output.cycles_into_pulse = 0; + _output.bits_remaining_until_empty = 0; } inline void Tape::set_data_register(uint8_t value) { _data_register = (uint16_t)((value << 2) | 1); printf("Loaded — %03x\n", _data_register); - _bits_since_start = _output_bits_remaining = 9; + _output.bits_remaining_until_empty = 9; } inline uint8_t Tape::get_data_register() @@ -869,8 +876,8 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) { while(number_of_cycles--) { - _time_into_pulse += (unsigned int)_input_pulse_stepper->step(); - if(_time_into_pulse == _current_pulse.length.length) + _input.time_into_pulse += (unsigned int)_input.pulse_stepper->step(); + if(_input.time_into_pulse == _input.current_pulse.length.length) { get_next_tape_pulse(); @@ -879,9 +886,9 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) _crossings[2] = _crossings[3]; _crossings[3] = Tape::Unrecognised; - if(_current_pulse.type != Storage::Tape::Pulse::Zero) + if(_input.current_pulse.type != Storage::Tape::Pulse::Zero) { - float pulse_length = (float)_current_pulse.length.length / (float)_current_pulse.length.clock_rate; + float pulse_length = (float)_input.current_pulse.length.length / (float)_input.current_pulse.length.clock_rate; if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) _crossings[3] = Tape::Short; if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) _crossings[3] = Tape::Long; } @@ -907,23 +914,15 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) } else { - while(number_of_cycles--) + _output.cycles_into_pulse += number_of_cycles; + while(_output.cycles_into_pulse > 1666) { - if(_output_pulse_stepper->step()) - { - _output_bits_remaining--; - _bits_since_start--; - if(!_output_bits_remaining) - { - _output_bits_remaining = 9; - _interrupt_status |= Interrupt::TransmitDataEmpty; - } + _output.cycles_into_pulse -= 1666; + if(_output.bits_remaining_until_empty) _output.bits_remaining_until_empty--; + if(!_output.bits_remaining_until_empty) _interrupt_status |= Interrupt::TransmitDataEmpty; + _data_register = (_data_register >> 1) | 0x200; - evaluate_interrupts(); - - _data_register = (_data_register >> 1) | 0x200; - printf("Shifted — %03x\n", _data_register); - } + evaluate_interrupts(); } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 20ab62ccb..c09e249a5 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -90,17 +90,22 @@ class Tape { std::shared_ptr _tape; - Storage::Tape::Pulse _current_pulse; - std::unique_ptr _input_pulse_stepper; - std::unique_ptr _output_pulse_stepper; - uint32_t _time_into_pulse; + struct { + Storage::Tape::Pulse current_pulse; + std::unique_ptr pulse_stepper; + uint32_t time_into_pulse; + int minimum_bits_until_full; + } _input; + struct { + unsigned int cycles_into_pulse; + unsigned int bits_remaining_until_empty; + } _output; bool _is_running; bool _is_enabled; bool _is_in_input_mode; inline void evaluate_interrupts(); - int _bits_since_start, _output_bits_remaining; uint16_t _data_register; uint8_t _interrupt_status, _last_posted_interrupt_status; From cdff90f20df16128765eb79b16cb97c84226e3c1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Mar 2016 22:55:33 -0500 Subject: [PATCH 168/307] Fixed: setting an interrupt control value with the lowest bit set could result in interrupts that can't be disabled. --- Machines/Electron/Electron.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 3d14430c8..c1db966e1 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -116,7 +116,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { - _interrupt_control = *value; + _interrupt_control = (*value) & ~1; evaluate_interrupts(); } break; From 4462bb92f841b76518f410cbddcbc6c0bda7a7bd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Mar 2016 23:18:46 -0500 Subject: [PATCH 169/307] Moved interrupt back down to where it's probably meant to be. --- Machines/Electron/Electron.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c1db966e1..63023ac4c 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -28,7 +28,7 @@ namespace { static const unsigned int first_graphics_cycle = 33; static const unsigned int real_time_clock_interrupt_line = 100; - static const unsigned int display_end_interrupt_line = 255; + static const unsigned int display_end_interrupt_line = 256; } #define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) From 21ca1ef86bde581cb45135ee5f733e45e2e9a5f3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Mar 2016 23:19:10 -0500 Subject: [PATCH 170/307] Started attempting to clarify instance variable usage. --- Storage/Tape/Formats/TapeUEF.cpp | 36 +++++++++++++++++++++----------- Storage/Tape/Formats/TapeUEF.hpp | 12 +++++++++++ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index 387bb0bf5..7ed0d8818 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -176,7 +176,12 @@ void Storage::UEF::find_next_tape_chunk() switch(_chunk_id) { - case 0x0100: case 0x0102: // implicit and explicit bit patterns + case 0x0100: // implicit bit pattern + _implicit_data_chunk.position = 0; + return; + + case 0x0102: // explicit bit patterns + _explicit_data_chunk.position = 0; return; case 0x0112: // integer gap @@ -233,8 +238,8 @@ bool Storage::UEF::chunk_is_finished() { switch(_chunk_id) { - case 0x0100: return (_chunk_position / 10) == _chunk_length; - case 0x0102: return (_chunk_position / 8) == _chunk_length; + case 0x0100: return (_implicit_data_chunk.position / 10) == _chunk_length; + case 0x0102: return (_explicit_data_chunk.position / 8) == _chunk_length; case 0x0114: case 0x0110: return _chunk_position == _chunk_duration.length; @@ -251,16 +256,24 @@ bool Storage::UEF::get_next_bit() { case 0x0100: { - uint32_t bit_position = _chunk_position%10; - _chunk_position++; - if(!bit_position) - { - _current_byte = (uint8_t)gzgetc(_file); - } + uint32_t bit_position = _implicit_data_chunk.position%10; + _implicit_data_chunk.position++; + if(!bit_position) _implicit_data_chunk.current_byte = (uint8_t)gzgetc(_file); if(bit_position == 0) return false; if(bit_position == 9) return true; - bool result = (_current_byte&1) ? true : false; - _current_byte >>= 1; + bool result = (_implicit_data_chunk.current_byte&1) ? true : false; + _implicit_data_chunk.current_byte >>= 1; + return result; + } + break; + + case 0x0102: + { + uint32_t bit_position = _explicit_data_chunk.position%8; + _explicit_data_chunk.position++; + if(!bit_position) _explicit_data_chunk.current_byte = (uint8_t)gzgetc(_file); + bool result = (_explicit_data_chunk.current_byte&1) ? true : false; + _explicit_data_chunk.current_byte >>= 1; return result; } break; @@ -268,7 +281,6 @@ bool Storage::UEF::get_next_bit() // TODO: 0x0104, 0x0111 case 0x0114: - case 0x0102: { uint32_t bit_position = _chunk_position%8; _chunk_position++; diff --git a/Storage/Tape/Formats/TapeUEF.hpp b/Storage/Tape/Formats/TapeUEF.hpp index fe86ecaef..bb7676e41 100644 --- a/Storage/Tape/Formats/TapeUEF.hpp +++ b/Storage/Tape/Formats/TapeUEF.hpp @@ -35,6 +35,18 @@ class UEF : public Tape { uint16_t _chunk_id; uint32_t _chunk_length; + union { + struct { + uint8_t current_byte; + uint32_t position; + } _implicit_data_chunk; + + struct { + uint8_t current_byte; + uint32_t position; + } _explicit_data_chunk; + }; + uint8_t _current_byte; uint32_t _chunk_position; From 67dffc89e097da8a5c487d94783e37ac7f161527 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Mar 2016 13:25:34 -0400 Subject: [PATCH 171/307] Stripped unnecessary includes. --- Outputs/CRT/CRT.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 3391fdc4f..596ccce10 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -10,10 +10,6 @@ #define CRT_hpp #include -#include -#include -#include -#include #include "CRTTypes.hpp" #include "Internals/Flywheel.hpp" From bb0cd89574df56dd07bca6f566729aa3f4ed44c2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Mar 2016 13:38:03 -0400 Subject: [PATCH 172/307] Got explicit about threading guarantees. --- .../Mac/Clock Signal/Views/CSOpenGLView.h | 37 +++++++++++++++++++ .../Mac/Clock Signal/Views/CSOpenGLView.m | 4 +- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h index 78ed6867b..003308243 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h @@ -12,14 +12,46 @@ @class CSOpenGLView; @protocol CSOpenGLViewDelegate +/*! + Tells the delegate that time has advanced. This method will always be called on the same queue + as the @c CSOpenGLViewResponderDelegate methods. + @param view The view sending the message. + @param time The time to which time has advanced. +*/ - (void)openGLView:(nonnull CSOpenGLView *)view didUpdateToTime:(CVTimeStamp)time; + +/*! + Requests that the delegate produce an image of its current output state. May be called on + any queue or thread. + @param view The view makin the request. + @param onlyIfDirty If @c YES then the delegate may decline to redraw if its output would be + identical to the previous frame. If @c NO then the delegate must draw. +*/ - (void)openGLView:(nonnull CSOpenGLView *)view drawViewOnlyIfDirty:(BOOL)onlyIfDirty; @end @protocol CSOpenGLViewResponderDelegate +/*! + Supplies a keyDown event to the delegate. Will always be called on the same queue as the other + @c CSOpenGLViewResponderDelegate methods and as -[CSOpenGLViewDelegate openGLView:didUpdateToTime:]. + @param event The @c NSEvent describing the keyDown. +*/ - (void)keyDown:(nonnull NSEvent *)event; + +/*! + Supplies a keyUp event to the delegate. Will always be called on the same queue as the other + @c CSOpenGLViewResponderDelegate methods and as -[CSOpenGLViewDelegate openGLView:didUpdateToTime:]. + @param event The @c NSEvent describing the keyUp. +*/ - (void)keyUp:(nonnull NSEvent *)event; + +/*! + Supplies a flagsChanged event to the delegate. Will always be called on the same queue as the other + @c CSOpenGLViewResponderDelegate methods and as -[CSOpenGLViewDelegate openGLView:didUpdateToTime:]. + @param event The @c NSEvent describing the flagsChanged. +*/ - (void)flagsChanged:(nonnull NSEvent *)newModifiers; + @end /*! @@ -31,8 +63,13 @@ @property (nonatomic, weak) id delegate; @property (nonatomic, weak) id responderDelegate; +/*! + Ends the timer tracking time; should be called prior to giving up the last owning reference + to ensure that any retain cycles implied by the timer are resolved. +*/ - (void)invalidate; +/// The size in pixels of the OpenGL canvas, factoring in screen pixel density and view size in points. @property (nonatomic, readonly) CGSize backingSize; @end diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 6097bfb10..0e9b133ac 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -137,9 +137,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawRect:(NSRect)dirtyRect { - dispatch_sync(_dispatchQueue, ^{ - [self drawViewOnlyIfDirty:NO]; - }); + [self drawViewOnlyIfDirty:NO]; } - (void)drawViewOnlyIfDirty:(BOOL)onlyIfDirty From 1c4acfb599ecbfe1e95c7f01c89d074c4c963ea7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Mar 2016 17:39:53 -0400 Subject: [PATCH 173/307] I think this is a prima facie acceptable implementation of the fast tape hack. --- Machines/Electron/Electron.cpp | 125 +++++++++++++----- Machines/Electron/Electron.hpp | 4 + .../Documents/ElectronDocument.swift | 1 + .../Mac/Clock Signal/Wrappers/CSElectron.h | 2 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 5 + 5 files changed, 107 insertions(+), 30 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 63023ac4c..f84523ce4 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -42,6 +42,7 @@ Machine::Machine() : _audioOutputPosition(0), _audioOutputPositionError(0), _current_pixel_line(-1), + _use_fast_tape_hack(false), _crt(std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1, 1))) { _crt->set_rgb_sampling_function( @@ -263,7 +264,65 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin else { if(isReadOperation(operation)) - *value = _os[address & 16383]; + { + if( + _use_fast_tape_hack && + (operation == CPU6502::BusOperation::ReadOpcode) && + ( + (address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 + (address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling + (address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM + (address == 0xfa51) || (address == 0xfa52) || // pathway. + + (address == 0xf0a8) // 0xf0a8 is from where a service call would normally be + // dispatched; we can check whether it would be call 14 + // (i.e. read byte) and, if so, whether the OS was about to + // issue a read byte call to a ROM despite being the tape + // FS being selected. If so then this is a get byte that + // we should service synthetically. Put the byte into Y + // and set A to zero to report that action was taken, then + // allow the PC read to return an RTS. + ) + ) + { + uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X); + if(address == 0xf0a8) + { + if(!_ram[0x247] && service_call == 14) + { + _tape.set_delegate(nullptr); + + // TODO: handle tape wrap around. + + int cycles_left_while_plausibly_in_data = 50; + _tape.clear_interrupts(Interrupt::ReceiveDataFull); + while(1) + { + uint8_t tapeStatus = _tape.run_for_input_pulse(); + cycles_left_while_plausibly_in_data--; + if(!cycles_left_while_plausibly_in_data) _fast_load_is_in_data = false; + if( (tapeStatus & Interrupt::ReceiveDataFull) && + (_fast_load_is_in_data || _tape.get_data_register() == 0x2a) + ) break; + } + _tape.set_delegate(this); + + _fast_load_is_in_data = true; + set_value_of_register(CPU6502::Register::A, 0); + set_value_of_register(CPU6502::Register::Y, _tape.get_data_register()); + *value = 0x60; // 0x60 is RTS + } + else + *value = _os[address & 16383]; + } + else + *value = 0xea; + } + else + { + *value = _os[address & 16383]; + } + } } } else @@ -866,6 +925,40 @@ inline uint8_t Tape::get_data_register() return (uint8_t)(_data_register >> 2); } +inline uint8_t Tape::run_for_input_pulse() +{ + get_next_tape_pulse(); + + _crossings[0] = _crossings[1]; + _crossings[1] = _crossings[2]; + _crossings[2] = _crossings[3]; + + _crossings[3] = Tape::Unrecognised; + if(_input.current_pulse.type != Storage::Tape::Pulse::Zero) + { + float pulse_length = (float)_input.current_pulse.length.length / (float)_input.current_pulse.length.clock_rate; + if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) _crossings[3] = Tape::Short; + if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) _crossings[3] = Tape::Long; + } + + if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long) + { + push_tape_bit(0); + _crossings[0] = _crossings[1] = Tape::Recognised; + } + else + { + if(_crossings[0] == Tape::Short && _crossings[1] == Tape::Short && _crossings[2] == Tape::Short && _crossings[3] == Tape::Short) + { + push_tape_bit(1); + _crossings[0] = _crossings[1] = + _crossings[2] = _crossings[3] = Tape::Recognised; + } + } + + return _interrupt_status; +} + inline void Tape::run_for_cycles(unsigned int number_of_cycles) { if(_is_enabled) @@ -879,35 +972,7 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) _input.time_into_pulse += (unsigned int)_input.pulse_stepper->step(); if(_input.time_into_pulse == _input.current_pulse.length.length) { - get_next_tape_pulse(); - - _crossings[0] = _crossings[1]; - _crossings[1] = _crossings[2]; - _crossings[2] = _crossings[3]; - - _crossings[3] = Tape::Unrecognised; - if(_input.current_pulse.type != Storage::Tape::Pulse::Zero) - { - float pulse_length = (float)_input.current_pulse.length.length / (float)_input.current_pulse.length.clock_rate; - if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) _crossings[3] = Tape::Short; - if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) _crossings[3] = Tape::Long; - } - - if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long) - { - push_tape_bit(0); - _crossings[0] = _crossings[1] = Tape::Recognised; - } - else - { - if(_crossings[0] == Tape::Short && _crossings[1] == Tape::Short && _crossings[2] == Tape::Short && _crossings[3] == Tape::Short) - { - push_tape_bit(1); - _crossings[0] = _crossings[1] = - _crossings[2] = _crossings[3] = Tape::Recognised; - } - } - + run_for_input_pulse(); } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index c09e249a5..f5c66d727 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -78,6 +78,7 @@ class Tape { inline void set_delegate(Delegate *delegate) { _delegate = delegate; } inline void run_for_cycles(unsigned int number_of_cycles); + inline uint8_t run_for_input_pulse(); inline void set_is_running(bool is_running) { _is_running = is_running; } inline void set_is_enabled(bool is_enabled) { _is_enabled = is_enabled; } @@ -158,6 +159,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { virtual void tape_did_change_interrupt_status(Tape *tape); void update_output(); + inline void set_use_fast_tape_hack(bool activate) { _use_fast_tape_hack = activate; } private: @@ -199,6 +201,8 @@ class Machine: public CPU6502::Processor, Tape::Delegate { // Tape. Tape _tape; + bool _use_fast_tape_hack; + bool _fast_load_is_in_data; // Outputs. std::unique_ptr _crt; diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 46b596615..9c2a83d61 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -42,6 +42,7 @@ class ElectronDocument: MachineDocument { switch pathExtension.lowercaseString { case "uef": electron.openUEFAtURL(url) + electron.useFastLoadingHack = true return default: break; } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index 7902fee52..e3ab2f8c5 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -20,4 +20,6 @@ - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; +@property (nonatomic, assign) BOOL useFastLoadingHack; + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index d7dce90ce..36cd681b9 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -131,4 +131,9 @@ } } +- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { + _useFastLoadingHack = useFastLoadingHack; + _electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false); +} + @end From bc554dedf73859b8c52b82c7bea5b3d587f100f4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Mar 2016 18:50:23 -0400 Subject: [PATCH 174/307] Fixed: attempting to read the screen start address no longer alters it. --- Machines/Electron/Electron.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index f84523ce4..65019103d 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -124,14 +124,18 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x1: break; case 0x2: - printf("%02x to [2] mutates %04x ", *value, _startScreenAddress); - _startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); - printf("into %04x\n", _startScreenAddress); + if(!isReadOperation(operation)) + { + _startScreenAddress = (_startScreenAddress & 0xfe00) | (uint16_t)(((*value) & 0xe0) << 1); + if(!_startScreenAddress) _startScreenAddress |= 0x8000; + } break; case 0x3: - printf("%02x to [3] mutates %04x ", *value, _startScreenAddress); - _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); - printf("into %04x\n", _startScreenAddress); + if(!isReadOperation(operation)) + { + _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); + if(!_startScreenAddress) _startScreenAddress |= 0x8000; + } break; case 0x4: if(isReadOperation(operation)) From f797e6b5b655698270f9eded254f8b8c5b7a7e38 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Mar 2016 19:45:50 -0400 Subject: [PATCH 175/307] Started taking baby steps towards a genuine unification of the tape interrupt generating code. --- Machines/Electron/Electron.cpp | 36 ++++++++++++---------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 65019103d..4a08e7c10 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -850,21 +850,21 @@ inline void Tape::get_next_tape_pulse() inline void Tape::push_tape_bit(uint16_t bit) { _data_register = (uint16_t)((_data_register >> 1) | (bit << 10)); - if(_input.minimum_bits_until_full) - { - _input.minimum_bits_until_full--; + if(_input.minimum_bits_until_full) _input.minimum_bits_until_full--; + if(_input.minimum_bits_until_full == 7) _interrupt_status &= ~Interrupt::ReceiveDataFull; + + if(_output.bits_remaining_until_empty) _output.bits_remaining_until_empty--; + if(!_output.bits_remaining_until_empty) _interrupt_status |= Interrupt::TransmitDataEmpty; + + if(_data_register == 0x3ff) _interrupt_status |= Interrupt::HighToneDetect; + else _interrupt_status &= ~Interrupt::HighToneDetect; - if(_input.minimum_bits_until_full == 7) - { - _interrupt_status &= ~Interrupt::ReceiveDataFull; - } - } evaluate_interrupts(); } -inline void Tape::reset_tape_input() -{ - _input.minimum_bits_until_full = 0; +//inline void Tape::reset_tape_input() +//{ +// _input.minimum_bits_until_full = 0; // _interrupt_status &= ~(Interrupt::ReceiveDataFull | Interrupt::TransmitDataEmpty | Interrupt::HighToneDetect); // // if(_last_posted_interrupt_status != _interrupt_status) @@ -872,7 +872,7 @@ inline void Tape::reset_tape_input() // _last_posted_interrupt_status = _interrupt_status; // if(_delegate) _delegate->tape_did_change_interrupt_status(this); // } -} +//} inline void Tape::evaluate_interrupts() { @@ -885,11 +885,6 @@ inline void Tape::evaluate_interrupts() } } - if(_data_register == 0x3ff) - _interrupt_status |= Interrupt::HighToneDetect; - else - _interrupt_status &= ~Interrupt::HighToneDetect; - if(_last_posted_interrupt_status != _interrupt_status) { _last_posted_interrupt_status = _interrupt_status; @@ -920,7 +915,6 @@ inline void Tape::set_counter(uint8_t value) inline void Tape::set_data_register(uint8_t value) { _data_register = (uint16_t)((value << 2) | 1); - printf("Loaded — %03x\n", _data_register); _output.bits_remaining_until_empty = 9; } @@ -987,11 +981,7 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) while(_output.cycles_into_pulse > 1666) { _output.cycles_into_pulse -= 1666; - if(_output.bits_remaining_until_empty) _output.bits_remaining_until_empty--; - if(!_output.bits_remaining_until_empty) _interrupt_status |= Interrupt::TransmitDataEmpty; - _data_register = (_data_register >> 1) | 0x200; - - evaluate_interrupts(); + push_tape_bit(1); } } } From fb26b38ff97c0ee5c45f2109649db89220fb6af8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Mar 2016 19:55:20 -0400 Subject: [PATCH 176/307] This is as simplified as things seem to be able to get without breaking at least Northern Star. Joe Blade et al remain stubbornly broken but I'm not immediately able to determine why. --- Machines/Electron/Electron.cpp | 39 +++++++++++----------------------- Machines/Electron/Electron.hpp | 1 - 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 4a08e7c10..1267cc80f 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -850,8 +850,17 @@ inline void Tape::get_next_tape_pulse() inline void Tape::push_tape_bit(uint16_t bit) { _data_register = (uint16_t)((_data_register >> 1) | (bit << 10)); + if(_input.minimum_bits_until_full) _input.minimum_bits_until_full--; - if(_input.minimum_bits_until_full == 7) _interrupt_status &= ~Interrupt::ReceiveDataFull; + if(_input.minimum_bits_until_full == 8) _interrupt_status &= ~Interrupt::ReceiveDataFull; + if(!_input.minimum_bits_until_full) + { + if((_data_register&0x3) == 0x1) + { + _interrupt_status |= Interrupt::ReceiveDataFull; + if(_is_in_input_mode) _input.minimum_bits_until_full = 9; + } + } if(_output.bits_remaining_until_empty) _output.bits_remaining_until_empty--; if(!_output.bits_remaining_until_empty) _interrupt_status |= Interrupt::TransmitDataEmpty; @@ -862,29 +871,8 @@ inline void Tape::push_tape_bit(uint16_t bit) evaluate_interrupts(); } -//inline void Tape::reset_tape_input() -//{ -// _input.minimum_bits_until_full = 0; -// _interrupt_status &= ~(Interrupt::ReceiveDataFull | Interrupt::TransmitDataEmpty | Interrupt::HighToneDetect); -// -// if(_last_posted_interrupt_status != _interrupt_status) -// { -// _last_posted_interrupt_status = _interrupt_status; -// if(_delegate) _delegate->tape_did_change_interrupt_status(this); -// } -//} - inline void Tape::evaluate_interrupts() { - if(!_input.minimum_bits_until_full) - { - if((_data_register&0x3) == 0x1) - { - _interrupt_status |= Interrupt::ReceiveDataFull; - if(_is_in_input_mode) _input.minimum_bits_until_full = 9; - } - } - if(_last_posted_interrupt_status != _interrupt_status) { _last_posted_interrupt_status = _interrupt_status; @@ -894,11 +882,8 @@ inline void Tape::evaluate_interrupts() inline void Tape::clear_interrupts(uint8_t interrupts) { - if(_interrupt_status & interrupts) - { - _interrupt_status &= ~interrupts; - if(_delegate) _delegate->tape_did_change_interrupt_status(this); - } + _interrupt_status &= ~interrupts; + evaluate_interrupts(); } inline void Tape::set_is_in_input_mode(bool is_in_input_mode) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index f5c66d727..288f9aa4f 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -86,7 +86,6 @@ class Tape { private: inline void push_tape_bit(uint16_t bit); - inline void reset_tape_input(void); inline void get_next_tape_pulse(); std::shared_ptr _tape; From 15120d8fb66196e69354278f8b9b4e7035e12909 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Mar 2016 19:29:28 -0400 Subject: [PATCH 177/307] Switched to probably more accurate contended memory, resolved issue of not leaving the tape data received interrupt set when returning a byte from the fast tape, fixing Joe Blade, and tested slightly further to determine that interrupts probably signal upon entry into horizontal sync. --- Machines/Electron/Electron.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 1267cc80f..d9da82ead 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -95,8 +95,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if(_screen_mode < 4) { update_display(); - const int current_line = graphics_line(_frameCycles); // + cycles - const int current_column = graphics_column(_frameCycles); // + cycles + const int current_line = graphics_line(_frameCycles + (_frameCycles&1)); + const int current_column = graphics_column(_frameCycles + (_frameCycles&1)); if(current_line < 256 && current_column < 80 && !_isBlankLine) cycles += (unsigned int)(80 - current_column); } @@ -310,6 +310,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin ) break; } _tape.set_delegate(this); + _interrupt_status |= _tape.get_interrupt_status(); _fast_load_is_in_data = true; set_value_of_register(CPU6502::Register::A, 0); @@ -356,7 +357,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // printf("%04x: %02x (%d)\n", address, *value, _fieldCycles); // } - const unsigned int pixel_line_clock = _frameCycles + 128 - first_graphics_cycle + 80; + const unsigned int pixel_line_clock = _frameCycles;// + 128 - first_graphics_cycle + 80; const unsigned int line_before_cycle = graphics_line(pixel_line_clock); const unsigned int line_after_cycle = graphics_line(pixel_line_clock + cycles); @@ -963,9 +964,9 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles) else { _output.cycles_into_pulse += number_of_cycles; - while(_output.cycles_into_pulse > 1666) - { - _output.cycles_into_pulse -= 1666; + while(_output.cycles_into_pulse > 1664) // 1664 = the closest you can get to 1200 baud if you're looking for something + { // that divides the 125,000Hz clock that the sound divider runs off. + _output.cycles_into_pulse -= 1664; push_tape_bit(1); } } From 9a492ac15fda51b09b4396e891359496fedf5fbc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Mar 2016 22:42:52 -0400 Subject: [PATCH 178/307] Made a further guess at the state interrupts would probably be left; slightly simplified interface. --- Machines/Electron/Electron.cpp | 9 ++++----- Machines/Electron/Electron.hpp | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index d9da82ead..b2b56b9bc 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -302,14 +302,15 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _tape.clear_interrupts(Interrupt::ReceiveDataFull); while(1) { - uint8_t tapeStatus = _tape.run_for_input_pulse(); + _tape.run_for_input_pulse(); cycles_left_while_plausibly_in_data--; if(!cycles_left_while_plausibly_in_data) _fast_load_is_in_data = false; - if( (tapeStatus & Interrupt::ReceiveDataFull) && + if( (_tape.get_interrupt_status() & Interrupt::ReceiveDataFull) && (_fast_load_is_in_data || _tape.get_data_register() == 0x2a) ) break; } _tape.set_delegate(this); + _tape.clear_interrupts(Interrupt::ReceiveDataFull); _interrupt_status |= _tape.get_interrupt_status(); _fast_load_is_in_data = true; @@ -909,7 +910,7 @@ inline uint8_t Tape::get_data_register() return (uint8_t)(_data_register >> 2); } -inline uint8_t Tape::run_for_input_pulse() +inline void Tape::run_for_input_pulse() { get_next_tape_pulse(); @@ -939,8 +940,6 @@ inline uint8_t Tape::run_for_input_pulse() _crossings[2] = _crossings[3] = Tape::Recognised; } } - - return _interrupt_status; } inline void Tape::run_for_cycles(unsigned int number_of_cycles) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 288f9aa4f..bcfe7f916 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -78,7 +78,7 @@ class Tape { inline void set_delegate(Delegate *delegate) { _delegate = delegate; } inline void run_for_cycles(unsigned int number_of_cycles); - inline uint8_t run_for_input_pulse(); + inline void run_for_input_pulse(); inline void set_is_running(bool is_running) { _is_running = is_running; } inline void set_is_enabled(bool is_enabled) { _is_enabled = is_enabled; } From 0edf165401f9103fe7dc12fce4425315523b4403 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 14 Mar 2016 22:52:16 -0400 Subject: [PATCH 179/307] Fixed sound pitch, though I'm not yet exactly sure why. --- Machines/Electron/Electron.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index b2b56b9bc..3754011df 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -802,7 +802,7 @@ void Speaker::skip_samples(unsigned int number_of_samples) while(number_of_samples--) { _counter ++; - if(_counter > _divider) + if(_counter > _divider*2) { _counter = 0; _output_level ^= 8192; From 7694297c83b15da07e226d973ca665c25dafb065 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 15 Mar 2016 21:05:20 -0400 Subject: [PATCH 180/307] Introduced an adapted version of the previous Clock Signal's FIR filter. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 + SignalProcessing/FIRFilter.cpp | 148 ++++++++++++++++++ SignalProcessing/FIRFilter.hpp | 83 ++++++++++ 3 files changed, 237 insertions(+) create mode 100644 SignalProcessing/FIRFilter.cpp create mode 100644 SignalProcessing/FIRFilter.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 9ba0da4f8..6ea4d6d7a 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -308,6 +308,7 @@ 4BBF99161C8FBA6F0075DAFB /* CRTRunBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990C1C8FBA6F0075DAFB /* CRTRunBuilder.cpp */; }; 4BBF99171C8FBA6F0075DAFB /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */; }; 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; + 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; 4BCB70B41C947DDC005B1712 /* plus1.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BCB70B31C947DDC005B1712 /* plus1.rom */; }; 4BE5F85E1C3E1C2500C43F01 /* basic.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85C1C3E1C2500C43F01 /* basic.rom */; }; 4BE5F85F1C3E1C2500C43F01 /* os.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85D1C3E1C2500C43F01 /* os.rom */; }; @@ -666,6 +667,8 @@ 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = ""; }; 4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = ""; }; 4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = ""; }; + 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = ""; }; + 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = ""; }; 4BCB70B31C947DDC005B1712 /* plus1.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = plus1.rom; sourceTree = ""; }; 4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = ""; }; 4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = ""; }; @@ -733,6 +736,8 @@ 4B2409591C45DF85004DA684 /* SignalProcessing */ = { isa = PBXGroup; children = ( + 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */, + 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */, 4B24095A1C45DF85004DA684 /* Stepper.hpp */, ); name = SignalProcessing; @@ -1646,6 +1651,7 @@ 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, + 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, diff --git a/SignalProcessing/FIRFilter.cpp b/SignalProcessing/FIRFilter.cpp new file mode 100644 index 000000000..2668f8a55 --- /dev/null +++ b/SignalProcessing/FIRFilter.cpp @@ -0,0 +1,148 @@ +// +// LinearFilter.c +// Clock Signal +// +// Created by Thomas Harte on 01/10/2011. +// Copyright 2011 Thomas Harte. All rights reserved. +// + +#include "FIRFilter.hpp" +#include + +using namespace SignalProcessing; + +/* + + A Kaiser-Bessel filter is a real time window filter. It looks at the last n samples + of an incoming data source and computes a filtered value, which is the value you'd + get after applying the specified filter, at the centre of the sampling window. + + Hence, if you request a 37 tap filter then filtering introduces a latency of 18 + samples. Suppose you're receiving input at 44,100Hz and using 4097 taps, then you'll + introduce a latency of 2048 samples, which is about 46ms. + + There's a correlation between the number of taps and the quality of the filtering. + More samples = better filtering, at the cost of greater latency. Internally, applying + the filter involves calculating a weighted sum of previous values, so increasing the + number of taps is quite cheap in processing terms. + + Original source for this filter: + + "DIGITAL SIGNAL PROCESSING, II", IEEE Press, pages 123–126. +*/ + + +// our little fixed point scheme +#define kCSKaiserBesselFilterFixedMultiplier 32767.0f +#define kCSKaiserBesselFilterFixedShift 15 + +/* ino evaluates the 0th order Bessel function at a */ +static float csfilter_ino(float a) +{ + float d = 0.0f; + float ds = 1.0f; + float s = 1.0f; + + do + { + d += 2.0f; + ds *= (a * a) / (d * d); + s += ds; + } + while(ds > s*1e-6f); + + return s; +} + +static void csfilter_setIdealisedFilterResponse(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps) +{ + /* calculate alpha, which is the Kaiser-Bessel window shape factor */ + float a; // to take the place of alpha in the normal derivation + + if(attenuation < 21.0f) + a = 0.0f; + else + { + if(attenuation > 50.0f) + a = 0.1102f * (attenuation - 8.7f); + else + a = 0.5842f * powf(attenuation - 21.0f, 0.4f) + 0.7886f * (attenuation - 21.0f); + } + + float *filterCoefficientsFloat = (float *)malloc(sizeof(float) * numberOfTaps); + + /* work out the right hand side of the filter coefficients */ + unsigned int Np = (numberOfTaps - 1) / 2; + float I0 = csfilter_ino(a); + float NpSquared = (float)(Np * Np); + for(unsigned int i = 0; i <= Np; i++) + { + filterCoefficientsFloat[Np + i] = + A[i] * + csfilter_ino(a * sqrtf(1.0f - ((float)(i * i) / NpSquared) )) / + I0; + } + + /* coefficients are symmetrical, so copy from right hand side to left side */ + for(unsigned int i = 0; i < Np; i++) + { + filterCoefficientsFloat[i] = filterCoefficientsFloat[numberOfTaps - 1 - i]; + } + + /* scale back up so that we retain 100% of input volume */ + float coefficientTotal = 0.0f; + for(unsigned int i = 0; i < numberOfTaps; i++) + { + coefficientTotal += filterCoefficientsFloat[i]; + } + + /* we'll also need integer versions, potentially */ + float coefficientMultiplier = 1.0f / coefficientTotal; + for(unsigned int i = 0; i < numberOfTaps; i++) + { + filterCoefficients[i] = (short)(filterCoefficientsFloat[i] * kCSKaiserBesselFilterFixedMultiplier * coefficientMultiplier); + } + + free(filterCoefficientsFloat); +} + +FIRFilter::FIRFilter(unsigned int number_of_taps, unsigned int input_sample_rate, float low_frequency, float high_frequency, float attenuation) +{ + // we must be asked to filter based on an odd number of + // taps, and at least three + if(number_of_taps < 3) number_of_taps = 3; + if(attenuation < 21.0f) attenuation = 21.0f; + + // ensure we have an odd number of taps + number_of_taps |= 1; + + // store instance variables + number_of_taps_ = number_of_taps; + filter_coefficients_ = new short[number_of_taps_]; + + /* calculate idealised filter response */ + unsigned int Np = (number_of_taps - 1) / 2; + float twoOverSampleRate = 2.0f / (float)input_sample_rate; + + float *A = new float[Np+1]; + A[0] = 2.0f * (high_frequency - low_frequency) / (float)input_sample_rate; + for(unsigned int i = 1; i <= Np; i++) + { + float iPi = (float)i * (float)M_PI; + A[i] = + ( + sinf(twoOverSampleRate * iPi * high_frequency) - + sinf(twoOverSampleRate * iPi * low_frequency) + ) / iPi; + } + + csfilter_setIdealisedFilterResponse(filter_coefficients_, A, attenuation, number_of_taps_); + + /* clean up */ + delete[] A; +} + +FIRFilter::~FIRFilter() +{ + delete[] filter_coefficients_; +} diff --git a/SignalProcessing/FIRFilter.hpp b/SignalProcessing/FIRFilter.hpp new file mode 100644 index 000000000..27319d055 --- /dev/null +++ b/SignalProcessing/FIRFilter.hpp @@ -0,0 +1,83 @@ +// +// LinearFilter.h +// Clock Signal +// +// Created by Thomas Harte on 01/10/2011. +// Copyright 2011 Thomas Harte. All rights reserved. +// + +#ifndef FIRFilter_hpp +#define FIRFilter_hpp + +/* + + The FIR filter takes a 1d PCM signal with + a given sample rate and filters it according + to a specified filter (band pass only at + present, more to come if required). The number + of taps (ie, samples considered simultaneously + to make an output sample) is configurable; + smaller numbers permit a filter that operates + more quickly and with less lag but less + effectively. + + FIR filters are window functions; expected use is + to point sample an input that has been subject to + a filter. + +*/ + +#ifdef __APPLE__ +#include +#endif + +namespace SignalProcessing { + +class FIRFilter { + public: + /*! + Creates an instance of @c FIRFilter. + + @param number_of_taps The size of window for input data. + @param input_sample_rate The sampling rate of the input signal. + @param low_frequency The lowest frequency of signal to retain in the output. + @param high_frequency The highest frequency of signal to retain in the output. + @param attenuation The attenuation of the output. + */ + FIRFilter(unsigned int number_of_taps, unsigned int input_sample_rate, float low_frequency, float high_frequency, float attenuation); + + ~FIRFilter(); + + /*! A suggested default attenuation value. */ + const float DefaultAttenuation = 60.0f; + + /*! + Applies the filter to one batch of input samples, returning the net result. + + @param src The source buffer to apply the filter to. + @returns The result of applying the filter. + */ + inline short apply(const short *src) + { + #ifdef __APPLE__ + short result; + vDSP_dotpr_s1_15(filter_coefficients_, 1, src, 1, &result, number_of_taps_); + return result; + #else + int outputValue = 0; + for(unsigned int c = 0; c < number_of_taps_; c++) + { + outputValue += filter_coefficients_[c] * src[c]; + } + return (short)(outputValue >> kCSKaiserBesselFilterFixedShift); + #endif + } + + private: + short *filter_coefficients_; + unsigned int number_of_taps_; +}; + +} + +#endif From a4ec0b023c46a107360926c6d4c1a8d2606cb950 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 15 Mar 2016 21:25:02 -0400 Subject: [PATCH 181/307] Made some type conversions explicit. --- Processors/6502/CPU6502.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index deccfaca5..be27bd198 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -1084,13 +1084,13 @@ template class Processor { void set_value_of_register(Register r, uint16_t value) { switch (r) { - case Register::ProgramCounter: _pc.full = value; break; - case Register::StackPointer: _s = value; break; - case Register::Flags: set_flags(value); break; - case Register::A: _a = value; break; - case Register::X: _x = value; break; - case Register::Y: _y = value; break; - case Register::S: _s = value; break; + case Register::ProgramCounter: _pc.full = value; break; + case Register::StackPointer: _s = (uint8_t)value; break; + case Register::Flags: set_flags((uint8_t)value); break; + case Register::A: _a = (uint8_t)value; break; + case Register::X: _x = (uint8_t)value; break; + case Register::Y: _y = (uint8_t)value; break; + case Register::S: _s = (uint8_t)value; break; default: break; } } From 5b509e53601c4bdea99a8632caf455376f38c183 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 15 Mar 2016 21:33:18 -0400 Subject: [PATCH 182/307] Fixed: const should have been static constexpr; should probably use new and delete rather than malloc and new. --- SignalProcessing/FIRFilter.cpp | 4 ++-- SignalProcessing/FIRFilter.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SignalProcessing/FIRFilter.cpp b/SignalProcessing/FIRFilter.cpp index 2668f8a55..86b1261bf 100644 --- a/SignalProcessing/FIRFilter.cpp +++ b/SignalProcessing/FIRFilter.cpp @@ -69,7 +69,7 @@ static void csfilter_setIdealisedFilterResponse(short *filterCoefficients, float a = 0.5842f * powf(attenuation - 21.0f, 0.4f) + 0.7886f * (attenuation - 21.0f); } - float *filterCoefficientsFloat = (float *)malloc(sizeof(float) * numberOfTaps); + float *filterCoefficientsFloat = new float[numberOfTaps]; /* work out the right hand side of the filter coefficients */ unsigned int Np = (numberOfTaps - 1) / 2; @@ -103,7 +103,7 @@ static void csfilter_setIdealisedFilterResponse(short *filterCoefficients, float filterCoefficients[i] = (short)(filterCoefficientsFloat[i] * kCSKaiserBesselFilterFixedMultiplier * coefficientMultiplier); } - free(filterCoefficientsFloat); + delete[] filterCoefficientsFloat; } FIRFilter::FIRFilter(unsigned int number_of_taps, unsigned int input_sample_rate, float low_frequency, float high_frequency, float attenuation) diff --git a/SignalProcessing/FIRFilter.hpp b/SignalProcessing/FIRFilter.hpp index 27319d055..7fe5dff51 100644 --- a/SignalProcessing/FIRFilter.hpp +++ b/SignalProcessing/FIRFilter.hpp @@ -49,7 +49,7 @@ class FIRFilter { ~FIRFilter(); /*! A suggested default attenuation value. */ - const float DefaultAttenuation = 60.0f; + constexpr static float DefaultAttenuation = 60.0f; /*! Applies the filter to one batch of input samples, returning the net result. From 726c98446aff62a782b98386d14f1c68c3865a53 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 15 Mar 2016 21:34:00 -0400 Subject: [PATCH 183/307] Fixed a couple of memory leaks, at least got as far as instantiating a filter. --- Outputs/Speaker.hpp | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 7a9d11b8e..9f57a9cc3 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -13,6 +13,7 @@ #include #include #include "../SignalProcessing/Stepper.hpp" +#include "../SignalProcessing/FIRFilter.hpp" namespace Outputs { @@ -28,8 +29,7 @@ class Speaker { _output_cycles_per_second = cycles_per_second; if(_buffer_size != buffer_size) { - delete[] _buffer_in_progress; - _buffer_in_progress = new int16_t[buffer_size]; + _buffer_in_progress = std::unique_ptr(new int16_t[buffer_size]); _buffer_size = buffer_size; } set_needs_updated_filter_coefficients(); @@ -55,13 +55,12 @@ class Speaker { Speaker() : _buffer_in_progress_pointer(0) {} protected: - int16_t *_buffer_in_progress; + std::unique_ptr _buffer_in_progress; int _buffer_size; int _buffer_in_progress_pointer; int _number_of_taps; bool _coefficients_are_dirty; Delegate *_delegate; - SignalProcessing::Stepper *_stepper; int _input_cycles_per_second, _output_cycles_per_second; @@ -77,23 +76,12 @@ template class Filter: public Speaker { { if(_coefficients_are_dirty) update_filter_coefficients(); -// _periodic_cycles += input_cycles; -// time_t time_now = time(nullptr); -// if(time_now > _periodic_start) -// { -// printf("input audio samples: %d\n", _periodic_cycles); -// printf("output audio samples: %d\n", _periodic_output); -// _periodic_cycles = 0; -// _periodic_output = 0; -// _periodic_start = time_now; -// } - // point sample for now, as a temporary measure input_cycles += _input_cycles_carry; while(input_cycles > 0) { // get a sample for the current location - static_cast(this)->get_samples(1, &_buffer_in_progress[_buffer_in_progress_pointer]); + static_cast(this)->get_samples(1, &_buffer_in_progress.get()[_buffer_in_progress_pointer]); _buffer_in_progress_pointer++; // announce to delegate if full @@ -102,7 +90,7 @@ template class Filter: public Speaker { _buffer_in_progress_pointer = 0; if(_delegate) { - _delegate->speaker_did_complete_samples(this, _buffer_in_progress, _buffer_size); + _delegate->speaker_did_complete_samples(this, _buffer_in_progress.get(), _buffer_size); } } @@ -111,7 +99,6 @@ template class Filter: public Speaker { if(steps > 1) static_cast(this)->skip_samples((unsigned int)(steps-1)); input_cycles -= steps; -// _periodic_output ++; } _input_cycles_carry = input_cycles; } @@ -119,10 +106,8 @@ template class Filter: public Speaker { Filter() {} // _periodic_cycles(0), _periodic_start(0) private: -// time_t _periodic_start; -// int _periodic_cycles; -// int _periodic_output; - SignalProcessing::Stepper *_stepper; + std::unique_ptr _stepper; + std::unique_ptr _filter; int _input_cycles_carry; void update_filter_coefficients() @@ -130,8 +115,8 @@ template class Filter: public Speaker { _coefficients_are_dirty = false; _buffer_in_progress_pointer = 0; - delete _stepper; - _stepper = new SignalProcessing::Stepper((uint64_t)_input_cycles_per_second, (uint64_t)_output_cycles_per_second); + _stepper = std::unique_ptr(new SignalProcessing::Stepper((uint64_t)_input_cycles_per_second, (uint64_t)_output_cycles_per_second)); + _filter = std::unique_ptr(new SignalProcessing::FIRFilter((unsigned int)_number_of_taps, (unsigned int)_input_cycles_per_second, 0.0, (float)_output_cycles_per_second / 2.0f, SignalProcessing::FIRFilter::DefaultAttenuation)); } }; From 4cd0aa34165e500ce43cce0683b9144e90f4f3b1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 15 Mar 2016 23:37:35 -0400 Subject: [PATCH 184/307] Completed FIR filter based audio output. --- Machines/Electron/Electron.cpp | 11 ++-- .../Clock Signal.xcodeproj/project.pbxproj | 4 ++ Outputs/Speaker.hpp | 51 +++++++++++-------- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 3754011df..a342b0c2a 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -786,15 +786,12 @@ void Machine::set_key_state(Key key, bool isPressed) void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { - if(!_is_enabled) + while(number_of_samples--) { - *target = 0; + *target = _is_enabled ? _output_level : 0; + target++; + skip_samples(1); } - else - { - *target = _output_level; - } - skip_samples(number_of_samples); } void Speaker::skip_samples(unsigned int number_of_samples) diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 6ea4d6d7a..141821bc2 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -309,6 +309,7 @@ 4BBF99171C8FBA6F0075DAFB /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99101C8FBA6F0075DAFB /* Shader.cpp */; }; 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; }; 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; + 4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; 4BCB70B41C947DDC005B1712 /* plus1.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BCB70B31C947DDC005B1712 /* plus1.rom */; }; 4BE5F85E1C3E1C2500C43F01 /* basic.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85C1C3E1C2500C43F01 /* basic.rom */; }; 4BE5F85F1C3E1C2500C43F01 /* os.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85D1C3E1C2500C43F01 /* os.rom */; }; @@ -669,6 +670,7 @@ 4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = ""; }; 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = ""; }; 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = ""; }; + 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; 4BCB70B31C947DDC005B1712 /* plus1.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = plus1.rom; sourceTree = ""; }; 4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = ""; }; 4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = ""; }; @@ -679,6 +681,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */, 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1115,6 +1118,7 @@ 4BB73E951B587A5100552FC2 = { isa = PBXGroup; children = ( + 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */, 4BB73EA01B587A5100552FC2 /* Clock Signal */, 4BB73EB51B587A5100552FC2 /* Clock SignalTests */, 4BB73EC01B587A5100552FC2 /* Clock SignalUITests */, diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 9f57a9cc3..98b2d00c6 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -76,39 +76,45 @@ template class Filter: public Speaker { { if(_coefficients_are_dirty) update_filter_coefficients(); - // point sample for now, as a temporary measure - input_cycles += _input_cycles_carry; - while(input_cycles > 0) + // TODO: what if output rate is greater than input rate? + + // fill up as much of the input buffer as possible + while(input_cycles) { - // get a sample for the current location - static_cast(this)->get_samples(1, &_buffer_in_progress.get()[_buffer_in_progress_pointer]); - _buffer_in_progress_pointer++; + unsigned int cycles_to_read = (unsigned int)std::min(input_cycles, _number_of_taps - _input_buffer_depth); + static_cast(this)->get_samples(cycles_to_read, &_input_buffer.get()[_input_buffer_depth]); + input_cycles -= cycles_to_read; + _input_buffer_depth += cycles_to_read; - // announce to delegate if full - if(_buffer_in_progress_pointer == _buffer_size) + if(_input_buffer_depth == _number_of_taps) { - _buffer_in_progress_pointer = 0; - if(_delegate) + _buffer_in_progress.get()[_buffer_in_progress_pointer] = _filter->apply(_input_buffer.get()); + _buffer_in_progress_pointer++; + + // announce to delegate if full + if(_buffer_in_progress_pointer == _buffer_size) { - _delegate->speaker_did_complete_samples(this, _buffer_in_progress.get(), _buffer_size); + _buffer_in_progress_pointer = 0; + if(_delegate) + { + _delegate->speaker_did_complete_samples(this, _buffer_in_progress.get(), _buffer_size); + } } + + uint64_t steps = _stepper->step(); + int16_t *input_buffer = _input_buffer.get(); + memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)_number_of_taps - (size_t)steps)); + _input_buffer_depth -= steps; } - - // determine how many source samples to step - uint64_t steps = _stepper->step(); - if(steps > 1) - static_cast(this)->skip_samples((unsigned int)(steps-1)); - input_cycles -= steps; } - _input_cycles_carry = input_cycles; } - Filter() {} // _periodic_cycles(0), _periodic_start(0) - private: std::unique_ptr _stepper; std::unique_ptr _filter; - int _input_cycles_carry; + + std::unique_ptr _input_buffer; + int _input_buffer_depth; void update_filter_coefficients() { @@ -117,6 +123,9 @@ template class Filter: public Speaker { _stepper = std::unique_ptr(new SignalProcessing::Stepper((uint64_t)_input_cycles_per_second, (uint64_t)_output_cycles_per_second)); _filter = std::unique_ptr(new SignalProcessing::FIRFilter((unsigned int)_number_of_taps, (unsigned int)_input_cycles_per_second, 0.0, (float)_output_cycles_per_second / 2.0f, SignalProcessing::FIRFilter::DefaultAttenuation)); + + _input_buffer = std::unique_ptr(new int16_t[_number_of_taps]); + _input_buffer_depth = 0; } }; From ca35a7e22201e06ab4c8ca200f744306212f3406 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Mar 2016 20:38:17 -0400 Subject: [PATCH 185/307] Ensured that a much greater input rate than output is handled correctly. --- Outputs/Speaker.hpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 98b2d00c6..4eb6a9f3f 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -101,10 +101,22 @@ template class Filter: public Speaker { } } + // If the next loop around is going to reuse some of the samples just collected, use a memmove to + // preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip + // anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse. uint64_t steps = _stepper->step(); - int16_t *input_buffer = _input_buffer.get(); - memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)_number_of_taps - (size_t)steps)); - _input_buffer_depth -= steps; + if(steps < _number_of_taps) + { + int16_t *input_buffer = _input_buffer.get(); + memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)_number_of_taps - (size_t)steps)); + _input_buffer_depth -= steps; + } + else + { + if(steps > _number_of_taps) + static_cast(this)->skip_samples((unsigned int)steps - (unsigned int)_number_of_taps); + _input_buffer_depth = 0; + } } } } From 0d27d3bb7f7a49a43fe190770ece67ebd65e2019 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Mar 2016 22:29:22 -0400 Subject: [PATCH 186/307] As messy as it is, this use `glMapBufferRange` to avoid explicit buffer submits. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 90 +++++++++++++++---------- Outputs/CRT/Internals/CRTOpenGL.hpp | 27 ++++++-- Outputs/CRT/Internals/CRTRunBuilder.cpp | 3 +- Outputs/CRT/Internals/CRTRunBuilder.hpp | 11 +-- 4 files changed, 82 insertions(+), 49 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 3d9ddcddf..320827d76 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -82,14 +82,18 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[buffer].data); } + prepare_composite_input_shader(); + prepare_rgb_output_shader(); + glGenVertexArrays(1, &output_vertex_array); glGenBuffers(1, &output_array_buffer); output_vertices_per_slice = 0; - prepare_composite_input_shader(); - prepare_rgb_output_shader(); - glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); + + glBufferData(GL_ARRAY_BUFFER, buffer_size, NULL, GL_STREAM_DRAW); + _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, buffer_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _output_buffer_data_pointer = 0; glBindVertexArray(output_vertex_array); prepare_output_vertex_array(); @@ -144,8 +148,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // check for anything to decode from composite if(_composite_src_runs->number_of_vertices) { - composite_input_shader_program->bind(); - _composite_src_runs->reset(); +// composite_input_shader_program->bind(); +// _composite_src_runs->reset(); } // _output_mutex->unlock(); @@ -156,24 +160,24 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // glGetIntegerv(GL_VIEWPORT, results); // ensure array buffer is up to date - glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); - size_t max_number_of_vertices = 0; - for(int c = 0; c < NumberOfFields; c++) - { - max_number_of_vertices = std::max(max_number_of_vertices, _run_builders[c]->number_of_vertices); - } - if(output_vertices_per_slice < max_number_of_vertices) - { - output_vertices_per_slice = max_number_of_vertices; - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * OutputVertexSize * OutputVertexSize), NULL, GL_STREAM_DRAW); - - for(unsigned int c = 0; c < NumberOfFields; c++) - { - uint8_t *data = &_run_builders[c]->_runs[0]; - glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * output_vertices_per_slice * OutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * OutputVertexSize), data); - _run_builders[c]->uploaded_vertices = _run_builders[c]->number_of_vertices; - } - } +// glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); +// size_t max_number_of_vertices = 0; +// for(int c = 0; c < NumberOfFields; c++) +// { +// max_number_of_vertices = std::max(max_number_of_vertices, _run_builders[c]->number_of_vertices); +// } +// if(output_vertices_per_slice < max_number_of_vertices) +// { +// output_vertices_per_slice = max_number_of_vertices; +// glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * OutputVertexSize * OutputVertexSize), NULL, GL_STREAM_DRAW); +// +// for(unsigned int c = 0; c < NumberOfFields; c++) +// { +// uint8_t *data = &_run_builders[c]->_runs[0]; +// glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * output_vertices_per_slice * OutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * OutputVertexSize), data); +// _run_builders[c]->uploaded_vertices = _run_builders[c]->number_of_vertices; +// } +// } // switch to the output shader if(rgb_shader_program) @@ -202,17 +206,29 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out { glUniform1f(timestampBaseUniform, (GLfloat)total_age); - if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) - { - uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * OutputVertexSize]; - glBufferSubData(GL_ARRAY_BUFFER, - (GLsizeiptr)(((run * output_vertices_per_slice) + _run_builders[run]->uploaded_vertices) * OutputVertexSize), - (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * OutputVertexSize), data); - _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; - } +// if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) +// { +// uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * OutputVertexSize]; +// glBufferSubData(GL_ARRAY_BUFFER, +// (GLsizeiptr)(((run * output_vertices_per_slice) + _run_builders[run]->uploaded_vertices) * OutputVertexSize), +// (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * OutputVertexSize), data); +// _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; +// } // draw this frame - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(run * output_vertices_per_slice), (GLsizei)_run_builders[run]->number_of_vertices); +// glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(run * output_vertices_per_slice), (GLsizei)_run_builders[run]->number_of_vertices); + + GLsizei count = (GLsizei)_run_builders[run]->number_of_vertices; + GLsizei max_count = (GLsizei)((buffer_size - _run_builders[run]->start) / InputVertexSize); + if(count < max_count) + { + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(_run_builders[run]->start / InputVertexSize), count); + } + else + { + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(_run_builders[run]->start / InputVertexSize), max_count); + glDrawArrays(GL_TRIANGLE_STRIP, 0, count - max_count); + } } // advance back in time @@ -549,11 +565,11 @@ void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) { _output_device = output_device; - for(int builder = 0; builder < NumberOfFields; builder++) - { - _run_builders[builder]->reset(); - } - _composite_src_runs->reset(); +// for(int builder = 0; builder < NumberOfFields; builder++) +// { +// _run_builders[builder]->reset(); +// } +// _composite_src_runs->reset(); _composite_src_output_y = 0; } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 0acd9f680..f2697cc1b 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -47,6 +47,9 @@ const int InputBufferBuilderHeight = 1024; const int IntermediateBufferWidth = 2048; const int IntermediateBufferHeight = 2048; +const GLsizeiptr buffer_size = (GLsizeiptr)(312 * 6 * 6 * OutputVertexSize); + + // Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track // run age; that therefore creates a discrete number of fields that are stored. This number should be the // number of historic fields that are required fully to @@ -136,23 +139,30 @@ class OpenGLOutputBuilder { inline uint8_t *get_next_input_run() { - _output_mutex->lock(); - return (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); + if (_output_buffer_data_pointer + 6 * InputVertexSize > buffer_size) _output_buffer_data_pointer = 0; + uint8_t *pointer = &_output_buffer_data[_output_buffer_data_pointer]; + _output_buffer_data_pointer += 6 * InputVertexSize; + return pointer; +// _output_mutex->lock(); +// return (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); } inline void complete_input_run() { - _output_mutex->unlock(); + _run_builders[_run_write_pointer]->number_of_vertices += 6; +// _output_mutex->unlock(); } inline uint8_t *get_next_output_run() { - _output_mutex->lock(); - return (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); +// _output_mutex->lock(); +// return (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); + return nullptr; } inline void complete_output_run() { +// _output_mutex->unlock(); } inline OutputDevice get_output_device() @@ -183,7 +193,9 @@ class OpenGLOutputBuilder { inline void increment_field() { _run_write_pointer = (_run_write_pointer + 1)%NumberOfFields; - _run_builders[_run_write_pointer]->reset(); + _run_builders[_run_write_pointer]->start = _output_buffer_data_pointer; + _run_builders[_run_write_pointer]->duration = 0; + _run_builders[_run_write_pointer]->number_of_vertices = 0; } inline void allocate_write_area(size_t required_length) @@ -228,6 +240,9 @@ class OpenGLOutputBuilder { // TODO: update related uniforms } + + uint8_t *_output_buffer_data; + size_t _output_buffer_data_pointer; }; } diff --git a/Outputs/CRT/Internals/CRTRunBuilder.cpp b/Outputs/CRT/Internals/CRTRunBuilder.cpp index b67356eb3..9d6df1384 100644 --- a/Outputs/CRT/Internals/CRTRunBuilder.cpp +++ b/Outputs/CRT/Internals/CRTRunBuilder.cpp @@ -11,7 +11,7 @@ using namespace Outputs::CRT; -void CRTRunBuilder::reset() +/*void CRTRunBuilder::reset() { number_of_vertices = 0; uploaded_vertices = 0; @@ -31,3 +31,4 @@ uint8_t *CRTRunBuilder::get_next_run(size_t number_of_vertices_in_run) return next_run; } +*/ \ No newline at end of file diff --git a/Outputs/CRT/Internals/CRTRunBuilder.hpp b/Outputs/CRT/Internals/CRTRunBuilder.hpp index f5969a304..db7020647 100644 --- a/Outputs/CRT/Internals/CRTRunBuilder.hpp +++ b/Outputs/CRT/Internals/CRTRunBuilder.hpp @@ -15,24 +15,25 @@ namespace Outputs { namespace CRT { struct CRTRunBuilder { - CRTRunBuilder(size_t vertex_size) : _vertex_size(vertex_size) { reset(); } + CRTRunBuilder(size_t vertex_size) : _vertex_size(vertex_size), duration(0), start(0), number_of_vertices(0) {} // reset(); // Resets the run builder. - void reset(); +// void reset(); // Getter for new storage plus backing storage; in RGB mode input runs will map directly // from the input buffer to the screen. In composite mode input runs will map from the // input buffer to the processing buffer, and output runs will map from the processing // buffer to the screen. - uint8_t *get_next_run(size_t number_of_vertices); - std::vector _runs; +// uint8_t *get_next_run(size_t number_of_vertices); +// std::vector _runs; // Container for total length in cycles of all contained runs. uint32_t duration; + size_t start; // Storage for the length of run data uploaded so far; reset to zero by reset but otherwise // entrusted to the CRT to update. - size_t uploaded_vertices; +// size_t uploaded_vertices; size_t number_of_vertices; private: From c8ecfb89d83a7464e653b053d6bc497520a2141a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Mar 2016 22:52:33 -0400 Subject: [PATCH 187/307] Some cleaning. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 103 +++++++++------------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 5 +- 2 files changed, 37 insertions(+), 71 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 320827d76..7a7c56446 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -91,8 +91,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); - glBufferData(GL_ARRAY_BUFFER, buffer_size, NULL, GL_STREAM_DRAW); - _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, buffer_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + glBufferData(GL_ARRAY_BUFFER, InputVertexBufferDataSize, NULL, GL_STREAM_DRAW); + _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _output_buffer_data_pointer = 0; glBindVertexArray(output_vertex_array); prepare_output_vertex_array(); @@ -111,10 +111,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); } -// glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer); -// -// printf("%d", glIsFramebuffer(_openGL_state->defaultFramebuffer)); - // lock down any further work on the current frame _output_mutex->lock(); @@ -159,81 +155,50 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); // glGetIntegerv(GL_VIEWPORT, results); - // ensure array buffer is up to date -// glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); -// size_t max_number_of_vertices = 0; -// for(int c = 0; c < NumberOfFields; c++) -// { -// max_number_of_vertices = std::max(max_number_of_vertices, _run_builders[c]->number_of_vertices); -// } -// if(output_vertices_per_slice < max_number_of_vertices) -// { -// output_vertices_per_slice = max_number_of_vertices; -// glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(max_number_of_vertices * OutputVertexSize * OutputVertexSize), NULL, GL_STREAM_DRAW); -// -// for(unsigned int c = 0; c < NumberOfFields; c++) -// { -// uint8_t *data = &_run_builders[c]->_runs[0]; -// glBufferSubData(GL_ARRAY_BUFFER, (GLsizeiptr)(c * output_vertices_per_slice * OutputVertexSize), (GLsizeiptr)(_run_builders[c]->number_of_vertices * OutputVertexSize), data); -// _run_builders[c]->uploaded_vertices = _run_builders[c]->number_of_vertices; -// } -// } - // switch to the output shader if(rgb_shader_program) { - rgb_shader_program->bind(); + rgb_shader_program->bind(); - // update uniforms - push_size_uniforms(output_width, output_height); + // update uniforms + push_size_uniforms(output_width, output_height); - // Ensure we're back on the output framebuffer - glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); + // Ensure we're back on the output framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); - // clear the buffer - glClear(GL_COLOR_BUFFER_BIT); + // clear the buffer + glClear(GL_COLOR_BUFFER_BIT); - // draw all sitting frames - unsigned int run = (unsigned int)_run_write_pointer; -// printf("%d: %zu v %zu\n", run, _run_builders[run]->uploaded_vertices, _run_builders[run]->number_of_vertices); - GLint total_age = 0; - for(int c = 0; c < NumberOfFields; c++) - { - // update the total age at the start of this set of runs - total_age += _run_builders[run]->duration; - - if(_run_builders[run]->number_of_vertices > 0) + // draw all sitting frames + unsigned int run = (unsigned int)_run_write_pointer; + // printf("%d: %zu v %zu\n", run, _run_builders[run]->uploaded_vertices, _run_builders[run]->number_of_vertices); + GLint total_age = 0; + for(int c = 0; c < NumberOfFields; c++) { - glUniform1f(timestampBaseUniform, (GLfloat)total_age); + // update the total age at the start of this set of runs + total_age += _run_builders[run]->duration; -// if(_run_builders[run]->uploaded_vertices != _run_builders[run]->number_of_vertices) -// { -// uint8_t *data = &_run_builders[run]->_runs[_run_builders[run]->uploaded_vertices * OutputVertexSize]; -// glBufferSubData(GL_ARRAY_BUFFER, -// (GLsizeiptr)(((run * output_vertices_per_slice) + _run_builders[run]->uploaded_vertices) * OutputVertexSize), -// (GLsizeiptr)((_run_builders[run]->number_of_vertices - _run_builders[run]->uploaded_vertices) * OutputVertexSize), data); -// _run_builders[run]->uploaded_vertices = _run_builders[run]->number_of_vertices; -// } - - // draw this frame -// glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(run * output_vertices_per_slice), (GLsizei)_run_builders[run]->number_of_vertices); - - GLsizei count = (GLsizei)_run_builders[run]->number_of_vertices; - GLsizei max_count = (GLsizei)((buffer_size - _run_builders[run]->start) / InputVertexSize); - if(count < max_count) + if(_run_builders[run]->number_of_vertices > 0) { - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(_run_builders[run]->start / InputVertexSize), count); - } - else - { - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(_run_builders[run]->start / InputVertexSize), max_count); - glDrawArrays(GL_TRIANGLE_STRIP, 0, count - max_count); + glUniform1f(timestampBaseUniform, (GLfloat)total_age); + + // draw this frame + GLsizei count = (GLsizei)_run_builders[run]->number_of_vertices; + GLsizei max_count = (GLsizei)((InputVertexBufferDataSize - _run_builders[run]->start) / InputVertexSize); + if(count < max_count) + { + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(_run_builders[run]->start / InputVertexSize), count); + } + else + { + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(_run_builders[run]->start / InputVertexSize), max_count); + glDrawArrays(GL_TRIANGLE_STRIP, 0, count - max_count); + } } + + // advance back in time + run = (run - 1 + NumberOfFields) % NumberOfFields; } - - // advance back in time - run = (run - 1 + NumberOfFields) % NumberOfFields; - } } _output_mutex->unlock(); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index f2697cc1b..11c955f54 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -47,7 +47,8 @@ const int InputBufferBuilderHeight = 1024; const int IntermediateBufferWidth = 2048; const int IntermediateBufferHeight = 2048; -const GLsizeiptr buffer_size = (GLsizeiptr)(312 * 6 * 6 * OutputVertexSize); +// Some internal +const GLsizeiptr InputVertexBufferDataSize = 256 * 1024; // Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track @@ -139,7 +140,7 @@ class OpenGLOutputBuilder { inline uint8_t *get_next_input_run() { - if (_output_buffer_data_pointer + 6 * InputVertexSize > buffer_size) _output_buffer_data_pointer = 0; + if (_output_buffer_data_pointer + 6 * InputVertexSize > InputVertexBufferDataSize) _output_buffer_data_pointer = 0; uint8_t *pointer = &_output_buffer_data[_output_buffer_data_pointer]; _output_buffer_data_pointer += 6 * InputVertexSize; return pointer; From 61e880de0374265f9d0873f9d327fb3d81ca80d0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 18 Mar 2016 21:11:09 -0400 Subject: [PATCH 188/307] Keeping a buffer permanently bound is apparently illegal. So back to copying in on demand. Which I think still improves on synchronisation, but not much. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 46 +++++++++++++++++++------ Outputs/CRT/Internals/CRTOpenGL.hpp | 15 ++++---- Outputs/CRT/Internals/CRTRunBuilder.cpp | 22 ------------ Outputs/CRT/Internals/CRTRunBuilder.hpp | 23 +++++-------- 4 files changed, 52 insertions(+), 54 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 7a7c56446..c8a47f563 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -23,14 +23,15 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int number_of_buffers, va_list _visible_area(Rect(0, 0, 1, 1)), _composite_src_output_y(0), _composite_shader(nullptr), - _rgb_shader(nullptr) + _rgb_shader(nullptr), + _output_buffer_data(nullptr) { _run_builders = new CRTRunBuilder *[NumberOfFields]; for(int builder = 0; builder < NumberOfFields; builder++) { - _run_builders[builder] = new CRTRunBuilder(OutputVertexSize); + _run_builders[builder] = new CRTRunBuilder(); } - _composite_src_runs = std::unique_ptr(new CRTRunBuilder(InputVertexSize)); +// _composite_src_runs = std::unique_ptr(new CRTRunBuilder(InputVertexSize)); va_list va; va_copy(va, sizes); @@ -45,6 +46,7 @@ OpenGLOutputBuilder::~OpenGLOutputBuilder() delete _run_builders[builder]; } delete[] _run_builders; + delete[] _output_buffer_data; free(_composite_shader); free(_rgb_shader); @@ -82,6 +84,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[buffer].data); } + printf("%s\n", glGetString(GL_VERSION)); + prepare_composite_input_shader(); prepare_rgb_output_shader(); @@ -92,7 +96,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); glBufferData(GL_ARRAY_BUFFER, InputVertexBufferDataSize, NULL, GL_STREAM_DRAW); - _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _output_buffer_data = new uint8_t[InputVertexBufferDataSize]; _output_buffer_data_pointer = 0; glBindVertexArray(output_vertex_array); prepare_output_vertex_array(); @@ -142,11 +146,11 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } // check for anything to decode from composite - if(_composite_src_runs->number_of_vertices) - { +// if(_composite_src_runs->number_of_vertices) +// { // composite_input_shader_program->bind(); // _composite_src_runs->reset(); - } +// } // _output_mutex->unlock(); // return; @@ -178,12 +182,32 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // update the total age at the start of this set of runs total_age += _run_builders[run]->duration; - if(_run_builders[run]->number_of_vertices > 0) + if(_run_builders[run]->amount_of_data > 0) { - glUniform1f(timestampBaseUniform, (GLfloat)total_age); + // upload if required + uint8_t *target = nullptr; + if(_run_builders[run]->amount_of_data != _run_builders[run]->amount_of_uploaded_data) + { + target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - // draw this frame - GLsizei count = (GLsizei)_run_builders[run]->number_of_vertices; + size_t start = _run_builders[run]->start + _run_builders[run]->amount_of_uploaded_data; + size_t length = _run_builders[run]->amount_of_data + _run_builders[run]->amount_of_uploaded_data; + + if(start + length > InputVertexBufferDataSize) + { + memcpy(&target[start], &_output_buffer_data[start], InputVertexBufferDataSize - start); + memcpy(target, _output_buffer_data, length - (InputVertexBufferDataSize - start)); + } + else + memcpy(&target[start], &_output_buffer_data[start], length); + + glUnmapBuffer(GL_ARRAY_BUFFER); + _run_builders[run]->amount_of_uploaded_data = _run_builders[run]->amount_of_data; + } + + // draw + glUniform1f(timestampBaseUniform, (GLfloat)total_age); + GLsizei count = (GLsizei)(_run_builders[run]->amount_of_data / InputVertexSize); GLsizei max_count = (GLsizei)((InputVertexBufferDataSize - _run_builders[run]->start) / InputVertexSize); if(count < max_count) { diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 11c955f54..95c1a18c6 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -140,18 +140,17 @@ class OpenGLOutputBuilder { inline uint8_t *get_next_input_run() { - if (_output_buffer_data_pointer + 6 * InputVertexSize > InputVertexBufferDataSize) _output_buffer_data_pointer = 0; + _output_mutex->lock(); uint8_t *pointer = &_output_buffer_data[_output_buffer_data_pointer]; _output_buffer_data_pointer += 6 * InputVertexSize; + if(_output_buffer_data_pointer > InputVertexBufferDataSize) _output_buffer_data_pointer = 0; return pointer; -// _output_mutex->lock(); -// return (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); } inline void complete_input_run() { - _run_builders[_run_write_pointer]->number_of_vertices += 6; -// _output_mutex->unlock(); + _run_builders[_run_write_pointer]->amount_of_data += 6 * InputVertexSize; + _output_mutex->unlock(); } inline uint8_t *get_next_output_run() @@ -193,10 +192,11 @@ class OpenGLOutputBuilder { inline void increment_field() { + _output_mutex->lock(); _run_write_pointer = (_run_write_pointer + 1)%NumberOfFields; _run_builders[_run_write_pointer]->start = _output_buffer_data_pointer; - _run_builders[_run_write_pointer]->duration = 0; - _run_builders[_run_write_pointer]->number_of_vertices = 0; + _run_builders[_run_write_pointer]->reset(); + _output_mutex->unlock(); } inline void allocate_write_area(size_t required_length) @@ -244,6 +244,7 @@ class OpenGLOutputBuilder { uint8_t *_output_buffer_data; size_t _output_buffer_data_pointer; + GLsync _output_buffer_sync; }; } diff --git a/Outputs/CRT/Internals/CRTRunBuilder.cpp b/Outputs/CRT/Internals/CRTRunBuilder.cpp index 9d6df1384..5191a1c84 100644 --- a/Outputs/CRT/Internals/CRTRunBuilder.cpp +++ b/Outputs/CRT/Internals/CRTRunBuilder.cpp @@ -10,25 +10,3 @@ #include "CRTOpenGL.hpp" using namespace Outputs::CRT; - -/*void CRTRunBuilder::reset() -{ - number_of_vertices = 0; - uploaded_vertices = 0; - duration = 0; -} - -uint8_t *CRTRunBuilder::get_next_run(size_t number_of_vertices_in_run) -{ - // get a run from the allocated list, allocating more if we're about to overrun - if((number_of_vertices + number_of_vertices_in_run) * _vertex_size >= _runs.size()) - { - _runs.resize(_runs.size() + _vertex_size * 100); - } - - uint8_t *next_run = &_runs[number_of_vertices * _vertex_size]; - number_of_vertices += number_of_vertices_in_run; - - return next_run; -} -*/ \ No newline at end of file diff --git a/Outputs/CRT/Internals/CRTRunBuilder.hpp b/Outputs/CRT/Internals/CRTRunBuilder.hpp index db7020647..137f4ed85 100644 --- a/Outputs/CRT/Internals/CRTRunBuilder.hpp +++ b/Outputs/CRT/Internals/CRTRunBuilder.hpp @@ -15,17 +15,15 @@ namespace Outputs { namespace CRT { struct CRTRunBuilder { - CRTRunBuilder(size_t vertex_size) : _vertex_size(vertex_size), duration(0), start(0), number_of_vertices(0) {} // reset(); + CRTRunBuilder() : start(0) { reset(); } // Resets the run builder. -// void reset(); - - // Getter for new storage plus backing storage; in RGB mode input runs will map directly - // from the input buffer to the screen. In composite mode input runs will map from the - // input buffer to the processing buffer, and output runs will map from the processing - // buffer to the screen. -// uint8_t *get_next_run(size_t number_of_vertices); -// std::vector _runs; + inline void reset() + { + duration = 0; + amount_of_uploaded_data = 0; + amount_of_data = 0; + } // Container for total length in cycles of all contained runs. uint32_t duration; @@ -33,11 +31,8 @@ struct CRTRunBuilder { // Storage for the length of run data uploaded so far; reset to zero by reset but otherwise // entrusted to the CRT to update. -// size_t uploaded_vertices; - size_t number_of_vertices; - - private: - size_t _vertex_size; + size_t amount_of_uploaded_data; + size_t amount_of_data; }; } From b0748600310ba1fcb6cbf72346c4c157ccb19c51 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 18 Mar 2016 21:11:25 -0400 Subject: [PATCH 189/307] Updated to latest information on interrupt timing. --- Machines/Electron/Electron.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index a342b0c2a..24353dfd9 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -22,13 +22,15 @@ namespace { static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; static const unsigned int field_divider_line = 312; // i.e. the line, simultaneous with which, the first field's sync ends. So if - // the first line with pixels in field 1 is the 20th in the frame, the first line - // with pixels in field 2 will be 20+field_divider_line - static const unsigned int first_graphics_line = 38; + // the first line with pixels in field 1 is the 20th in the frame, the first line + // with pixels in field 2 will be 20+field_divider_line + static const unsigned int first_graphics_line = 31; static const unsigned int first_graphics_cycle = 33; - static const unsigned int real_time_clock_interrupt_line = 100; static const unsigned int display_end_interrupt_line = 256; + + static const unsigned int real_time_clock_interrupt_1 = 16704; + static const unsigned int real_time_clock_interrupt_2 = 56704; } #define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) @@ -358,6 +360,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // printf("%04x: %02x (%d)\n", address, *value, _fieldCycles); // } +// const int end_of_field = +// if (_frameCycles < (256 + first_graphics_line) << 7)) + const unsigned int pixel_line_clock = _frameCycles;// + 128 - first_graphics_cycle + 80; const unsigned int line_before_cycle = graphics_line(pixel_line_clock); const unsigned int line_after_cycle = graphics_line(pixel_line_clock + cycles); @@ -368,13 +373,20 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { switch(line_before_cycle) { - case real_time_clock_interrupt_line: signal_interrupt(Interrupt::RealTimeClock); break; +// case real_time_clock_interrupt_line: signal_interrupt(Interrupt::RealTimeClock); break; // case real_time_clock_interrupt_line+1: clear_interrupt(Interrupt::RealTimeClock); break; case display_end_interrupt_line: signal_interrupt(Interrupt::DisplayEnd); break; // case display_end_interrupt_line+1: clear_interrupt(Interrupt::DisplayEnd); break; } } + if( + (pixel_line_clock < real_time_clock_interrupt_1 && pixel_line_clock + cycles >= real_time_clock_interrupt_1) || + (pixel_line_clock < real_time_clock_interrupt_2 && pixel_line_clock + cycles >= real_time_clock_interrupt_2)) + { + signal_interrupt(Interrupt::RealTimeClock); + } + _frameCycles += cycles; // deal with frame wraparound by updating the two dependent subsystems From 10bc33464df6d29af0dbf90d5baf59ae6d6f08c9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 18 Mar 2016 21:14:06 -0400 Subject: [PATCH 190/307] I don't think this is quite correct, but it's better. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index c8a47f563..41dd060d6 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -185,21 +185,24 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out if(_run_builders[run]->amount_of_data > 0) { // upload if required - uint8_t *target = nullptr; if(_run_builders[run]->amount_of_data != _run_builders[run]->amount_of_uploaded_data) { - target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + uint8_t *target = nullptr; - size_t start = _run_builders[run]->start + _run_builders[run]->amount_of_uploaded_data; + size_t start = (_run_builders[run]->start + _run_builders[run]->amount_of_uploaded_data) % InputVertexBufferDataSize; size_t length = _run_builders[run]->amount_of_data + _run_builders[run]->amount_of_uploaded_data; if(start + length > InputVertexBufferDataSize) { + target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); memcpy(&target[start], &_output_buffer_data[start], InputVertexBufferDataSize - start); memcpy(target, _output_buffer_data, length - (InputVertexBufferDataSize - start)); } else - memcpy(&target[start], &_output_buffer_data[start], length); + { + target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, (GLintptr)start, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + memcpy(target, &_output_buffer_data[start], length); + } glUnmapBuffer(GL_ARRAY_BUFFER); _run_builders[run]->amount_of_uploaded_data = _run_builders[run]->amount_of_data; From 2e4af90e2dc1e87265cb5abc1155b2bd63d14bb2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 18 Mar 2016 21:19:11 -0400 Subject: [PATCH 191/307] Resolved output errors. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 9 ++++++++- Outputs/CRT/Internals/CRTOpenGL.hpp | 3 +-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 41dd060d6..41b261f16 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -24,7 +24,8 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int number_of_buffers, va_list _composite_src_output_y(0), _composite_shader(nullptr), _rgb_shader(nullptr), - _output_buffer_data(nullptr) + _output_buffer_data(nullptr), + _output_buffer_sync(nullptr) { _run_builders = new CRTRunBuilder *[NumberOfFields]; for(int builder = 0; builder < NumberOfFields; builder++) @@ -194,6 +195,12 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out if(start + length > InputVertexBufferDataSize) { + if(_output_buffer_sync) + { + glWaitSync(_output_buffer_sync, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(_output_buffer_sync); + } + _output_buffer_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); memcpy(&target[start], &_output_buffer_data[start], InputVertexBufferDataSize - start); memcpy(target, _output_buffer_data, length - (InputVertexBufferDataSize - start)); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 95c1a18c6..c860244f4 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -142,8 +142,7 @@ class OpenGLOutputBuilder { { _output_mutex->lock(); uint8_t *pointer = &_output_buffer_data[_output_buffer_data_pointer]; - _output_buffer_data_pointer += 6 * InputVertexSize; - if(_output_buffer_data_pointer > InputVertexBufferDataSize) _output_buffer_data_pointer = 0; + _output_buffer_data_pointer = (_output_buffer_data_pointer + 6 * InputVertexSize) % InputVertexBufferDataSize; return pointer; } From 620257e29ec9b3ef7b75aa008d88a64e0ea22e92 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 18 Mar 2016 21:21:35 -0400 Subject: [PATCH 192/307] Switched to a client wait, as I try to figure out what's going on. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 41b261f16..57000f277 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -197,7 +197,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out { if(_output_buffer_sync) { - glWaitSync(_output_buffer_sync, 0, GL_TIMEOUT_IGNORED); + glClientWaitSync(_output_buffer_sync, GL_SYNC_FLUSH_COMMANDS_BIT, ~(GLuint64)0); glDeleteSync(_output_buffer_sync); } _output_buffer_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); From 0b1c9fb2919d9c4237ee5dd3aae08391b52997ab Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 18 Mar 2016 21:28:53 -0400 Subject: [PATCH 193/307] Ensured I don't interrupt triangle strips for the purpose of glDrawArrays. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 23 +++++++++++++++-------- Outputs/CRT/Internals/CRTOpenGL.hpp | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 57000f277..f1fe31b12 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -99,6 +99,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glBufferData(GL_ARRAY_BUFFER, InputVertexBufferDataSize, NULL, GL_STREAM_DRAW); _output_buffer_data = new uint8_t[InputVertexBufferDataSize]; _output_buffer_data_pointer = 0; + glBindVertexArray(output_vertex_array); prepare_output_vertex_array(); @@ -188,8 +189,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // upload if required if(_run_builders[run]->amount_of_data != _run_builders[run]->amount_of_uploaded_data) { - uint8_t *target = nullptr; - size_t start = (_run_builders[run]->start + _run_builders[run]->amount_of_uploaded_data) % InputVertexBufferDataSize; size_t length = _run_builders[run]->amount_of_data + _run_builders[run]->amount_of_uploaded_data; @@ -201,17 +200,25 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glDeleteSync(_output_buffer_sync); } _output_buffer_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - memcpy(&target[start], &_output_buffer_data[start], InputVertexBufferDataSize - start); - memcpy(target, _output_buffer_data, length - (InputVertexBufferDataSize - start)); + uint8_t *target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + if(target) + { + size_t first_size = InputVertexBufferDataSize - start; + memcpy(&target[start], &_output_buffer_data[start], first_size); + memcpy(target, _output_buffer_data, length - first_size); + glUnmapBuffer(GL_ARRAY_BUFFER); + } } else { - target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, (GLintptr)start, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - memcpy(target, &_output_buffer_data[start], length); + uint8_t *target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, (GLintptr)start, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + if(target) + { + memcpy(target, &_output_buffer_data[start], length); + glUnmapBuffer(GL_ARRAY_BUFFER); + } } - glUnmapBuffer(GL_ARRAY_BUFFER); _run_builders[run]->amount_of_uploaded_data = _run_builders[run]->amount_of_data; } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index c860244f4..ab67efdcf 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -48,7 +48,7 @@ const int IntermediateBufferWidth = 2048; const int IntermediateBufferHeight = 2048; // Some internal -const GLsizeiptr InputVertexBufferDataSize = 256 * 1024; +const GLsizeiptr InputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize // Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track From 5b37a651ac7b2dab9bf4170f758c14f74cfad684 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 18 Mar 2016 21:35:52 -0400 Subject: [PATCH 194/307] Made an attemp to deal with `glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE`. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index f1fe31b12..0ea951a22 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -206,7 +206,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out size_t first_size = InputVertexBufferDataSize - start; memcpy(&target[start], &_output_buffer_data[start], first_size); memcpy(target, _output_buffer_data, length - first_size); - glUnmapBuffer(GL_ARRAY_BUFFER); } } else @@ -215,10 +214,16 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out if(target) { memcpy(target, &_output_buffer_data[start], length); - glUnmapBuffer(GL_ARRAY_BUFFER); } } + while(glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE) + { + // "the data store contents are undefined. An application must detect this rare condition and reinitialize the data store." + uint8_t *target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + memcpy(target, _output_buffer_data, InputVertexBufferDataSize); + } + _run_builders[run]->amount_of_uploaded_data = _run_builders[run]->amount_of_data; } From 4ac1f959e995b900e7d9a4cb67e54a6346cbbdcb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Mar 2016 17:07:05 -0400 Subject: [PATCH 195/307] A shortcut here and a shortcut there; this allows me at least to determine whether use of a PBO gains anything. --- .../Clock Signal.xcodeproj/project.pbxproj | 2 + Outputs/CRT/CRT.hpp | 2 +- Outputs/CRT/Internals/CRTConstants.hpp | 57 ++++++++++++ .../CRT/Internals/CRTInputBufferBuilder.cpp | 6 +- .../CRT/Internals/CRTInputBufferBuilder.hpp | 7 ++ Outputs/CRT/Internals/CRTOpenGL.cpp | 88 +++++++------------ Outputs/CRT/Internals/CRTOpenGL.hpp | 42 ++------- 7 files changed, 108 insertions(+), 96 deletions(-) create mode 100644 Outputs/CRT/Internals/CRTConstants.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 141821bc2..ca2fdabaf 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -333,6 +333,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = ""; }; 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = ""; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = ""; }; 4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioQueue.h; sourceTree = ""; }; @@ -1217,6 +1218,7 @@ 4BBF99111C8FBA6F0075DAFB /* Shader.hpp */, 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */, 4BBF99131C8FBA6F0075DAFB /* TextureTarget.hpp */, + 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */, ); path = Internals; sourceTree = ""; diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 596ccce10..92742d59c 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -107,7 +107,7 @@ class CRT { @see @c set_rgb_sampling_function , @c set_composite_sampling_function */ - 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 number_of_buffers, ...); + 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, ...); /*! Constructs the CRT with the specified clock rate, with the display height and colour subcarrier frequency dictated by a standard display type and with the requested number of diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp new file mode 100644 index 000000000..f8480d437 --- /dev/null +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -0,0 +1,57 @@ +// +// CRTContants.hpp +// Clock Signal +// +// Created by Thomas Harte on 19/03/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef CRTConstants_h +#define CRTConstants_h + +#include "OpenGL.hpp" +#include + +namespace Outputs { +namespace CRT { + +// Output vertices are those used to copy from an input buffer — whether it describes data that maps directly to RGB +// or is one of the intermediate buffers that we've used to convert from composite towards RGB. +const size_t OutputVertexOffsetOfPosition = 0; +const size_t OutputVertexOffsetOfTexCoord = 4; +const size_t OutputVertexOffsetOfTimestamp = 8; +const size_t OutputVertexOffsetOfLateral = 12; + +const size_t OutputVertexSize = 16; + +// Input vertices, used only in composite mode, map from the input buffer to temporary buffer locations; such +// remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour +const size_t InputVertexOffsetOfInputPosition = 0; +const size_t InputVertexOffsetOfOutputPosition = 4; +const size_t InputVertexOffsetOfPhaseAndAmplitude = 8; +const size_t InputVertexOffsetOfPhaseTime = 12; + +const size_t InputVertexSize = 16; + +// These constants hold the size of the rolling buffer to which the CPU writes +const int InputBufferBuilderWidth = 2048; +const int InputBufferBuilderHeight = 1024; + +// This is the size of the intermediate buffers used during composite to RGB conversion +const int IntermediateBufferWidth = 2048; +const int IntermediateBufferHeight = 2048; + +// Some internal +const GLsizeiptr InputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize +const GLsizeiptr InputTextureBufferDataSize = InputBufferBuilderWidth*InputBufferBuilderHeight; + + +// Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track +// run age; that therefore creates a discrete number of fields that are stored. This number should be the +// number of historic fields that are required fully to +const int NumberOfFields = 3; + +} +} + +#endif /* CRTContants_h */ diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp index 75e54ebc4..fd3edc5d5 100644 --- a/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp +++ b/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp @@ -41,8 +41,7 @@ void CRTInputBufferBuilder::allocate_write_area(size_t required_length) if(_next_write_x_position + required_length + 2 > InputBufferBuilderWidth) { - _next_write_x_position = 0; - _next_write_y_position = (_next_write_y_position+1)%InputBufferBuilderHeight; + move_to_new_line(); } _write_x_position = _next_write_x_position + 1; @@ -53,6 +52,8 @@ void CRTInputBufferBuilder::allocate_write_area(size_t required_length) void CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length) { + // book end the allocation with duplicates of the first and last pixel, to protect + // against rounding errors when this run is drawn for(int c = 0; c < number_of_buffers; c++) { memcpy( &buffers[c].data[(_write_target_pointer - 1) * buffers[c].bytes_per_pixel], @@ -64,6 +65,7 @@ void CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length) buffers[c].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); } diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp index 738c02a9b..6b8653963 100644 --- a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp +++ b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp @@ -12,6 +12,7 @@ #include #include #include +#include "CRTConstants.hpp" namespace Outputs { namespace CRT { @@ -40,6 +41,12 @@ struct CRTInputBufferBuilder { // Storage for the amount of buffer uploaded so far; initialised correctly by the buffer // builder but otherwise entrusted to the CRT to update. unsigned int last_uploaded_line; + + inline void move_to_new_line() + { + _next_write_x_position = 0; + _next_write_y_position = (_next_write_y_position+1)%InputBufferBuilderHeight; + } }; } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 0ea951a22..fcd96e028 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -25,7 +25,8 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int number_of_buffers, va_list _composite_shader(nullptr), _rgb_shader(nullptr), _output_buffer_data(nullptr), - _output_buffer_sync(nullptr) + _output_buffer_sync(nullptr), + _input_texture_data(nullptr) { _run_builders = new CRTRunBuilder *[NumberOfFields]; for(int builder = 0; builder < NumberOfFields; builder++) @@ -47,7 +48,9 @@ OpenGLOutputBuilder::~OpenGLOutputBuilder() delete _run_builders[builder]; } delete[] _run_builders; - delete[] _output_buffer_data; +// delete[] _input_texture_data; + + glUnmapBuffer(GL_ARRAY_BUFFER); free(_composite_shader); free(_rgb_shader); @@ -82,10 +85,12 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); GLenum format = formatForDepth(_buffer_builder->buffers[buffer].bytes_per_pixel); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, _buffer_builder->buffers[buffer].data); - } + glGenBuffers(1, &_input_texture_array); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _input_texture_array); + glBufferData(GL_PIXEL_UNPACK_BUFFER, InputTextureBufferDataSize, NULL, GL_STREAM_DRAW); - printf("%s\n", glGetString(GL_VERSION)); + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, nullptr); + } prepare_composite_input_shader(); prepare_rgb_output_shader(); @@ -97,7 +102,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); glBufferData(GL_ARRAY_BUFFER, InputVertexBufferDataSize, NULL, GL_STREAM_DRAW); - _output_buffer_data = new uint8_t[InputVertexBufferDataSize]; _output_buffer_data_pointer = 0; glBindVertexArray(output_vertex_array); @@ -109,22 +113,30 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&defaultFramebuffer); // Create intermediate textures and bind to slots 0, 1 and 2 - glActiveTexture(GL_TEXTURE0); - compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); - glActiveTexture(GL_TEXTURE1); - filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); - glActiveTexture(GL_TEXTURE2); - filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); +// glActiveTexture(GL_TEXTURE0); +// compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); +// glActiveTexture(GL_TEXTURE1); +// filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); +// glActiveTexture(GL_TEXTURE2); +// filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); } // lock down any further work on the current frame _output_mutex->lock(); + // release the mapping, giving up on trying to draw if data has been lost + if(glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE) + { + for(int c = 0; c < NumberOfFields; c++) + _run_builders[c]->reset(); + } + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) { - glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); +// glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); GLenum format = formatForDepth(_buffer_builder->buffers[0].bytes_per_pixel); if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) { @@ -132,7 +144,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out 0, (GLint)_buffer_builder->last_uploaded_line, InputBufferBuilderWidth, (GLint)(InputBufferBuilderHeight - _buffer_builder->last_uploaded_line), format, GL_UNSIGNED_BYTE, - &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); + (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel)); _buffer_builder->last_uploaded_line = 0; } @@ -142,7 +154,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out 0, (GLint)_buffer_builder->last_uploaded_line, InputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), format, GL_UNSIGNED_BYTE, - &_buffer_builder->buffers[0].data[_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel]); + (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel)); _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; } } @@ -177,7 +189,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // draw all sitting frames unsigned int run = (unsigned int)_run_write_pointer; - // printf("%d: %zu v %zu\n", run, _run_builders[run]->uploaded_vertices, _run_builders[run]->number_of_vertices); GLint total_age = 0; for(int c = 0; c < NumberOfFields; c++) { @@ -186,47 +197,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out if(_run_builders[run]->amount_of_data > 0) { - // upload if required - if(_run_builders[run]->amount_of_data != _run_builders[run]->amount_of_uploaded_data) - { - size_t start = (_run_builders[run]->start + _run_builders[run]->amount_of_uploaded_data) % InputVertexBufferDataSize; - size_t length = _run_builders[run]->amount_of_data + _run_builders[run]->amount_of_uploaded_data; - - if(start + length > InputVertexBufferDataSize) - { - if(_output_buffer_sync) - { - glClientWaitSync(_output_buffer_sync, GL_SYNC_FLUSH_COMMANDS_BIT, ~(GLuint64)0); - glDeleteSync(_output_buffer_sync); - } - _output_buffer_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - uint8_t *target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - if(target) - { - size_t first_size = InputVertexBufferDataSize - start; - memcpy(&target[start], &_output_buffer_data[start], first_size); - memcpy(target, _output_buffer_data, length - first_size); - } - } - else - { - uint8_t *target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, (GLintptr)start, (GLsizeiptr)length, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - if(target) - { - memcpy(target, &_output_buffer_data[start], length); - } - } - - while(glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE) - { - // "the data store contents are undefined. An application must detect this rare condition and reinitialize the data store." - uint8_t *target = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - memcpy(target, _output_buffer_data, InputVertexBufferDataSize); - } - - _run_builders[run]->amount_of_uploaded_data = _run_builders[run]->amount_of_data; - } - // draw glUniform1f(timestampBaseUniform, (GLfloat)total_age); GLsizei count = (GLsizei)(_run_builders[run]->amount_of_data / InputVertexSize); @@ -247,6 +217,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } } + // drawing commands having been issued, reclaim the array buffer pointer + _buffer_builder->move_to_new_line(); + _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, InputTextureBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _output_mutex->unlock(); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index ab67efdcf..c9dcf45ef 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -10,6 +10,7 @@ #define CRTOpenGL_h #include "../CRTTypes.hpp" +#include "CRTConstants.hpp" #include "OpenGL.hpp" #include "TextureTarget.hpp" #include "Shader.hpp" @@ -21,41 +22,6 @@ namespace Outputs { namespace CRT { -// Output vertices are those used to copy from an input buffer — whether it describes data that maps directly to RGB -// or is one of the intermediate buffers that we've used to convert from composite towards RGB. -const size_t OutputVertexOffsetOfPosition = 0; -const size_t OutputVertexOffsetOfTexCoord = 4; -const size_t OutputVertexOffsetOfTimestamp = 8; -const size_t OutputVertexOffsetOfLateral = 12; - -const size_t OutputVertexSize = 16; - -// Input vertices, used only in composite mode, map from the input buffer to temporary buffer locations; such -// remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour -const size_t InputVertexOffsetOfInputPosition = 0; -const size_t InputVertexOffsetOfOutputPosition = 4; -const size_t InputVertexOffsetOfPhaseAndAmplitude = 8; -const size_t InputVertexOffsetOfPhaseTime = 12; - -const size_t InputVertexSize = 16; - -// These constants hold the size of the rolling buffer to which the CPU writes -const int InputBufferBuilderWidth = 2048; -const int InputBufferBuilderHeight = 1024; - -// This is the size of the intermediate buffers used during composite to RGB conversion -const int IntermediateBufferWidth = 2048; -const int IntermediateBufferHeight = 2048; - -// Some internal -const GLsizeiptr InputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize - - -// Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track -// run age; that therefore creates a discrete number of fields that are stored. This number should be the -// number of historic fields that are required fully to -const int NumberOfFields = 3; - class OpenGLOutputBuilder { private: // colour information @@ -212,7 +178,8 @@ class OpenGLOutputBuilder { inline uint8_t *get_write_target_for_buffer(int buffer) { - return _buffer_builder->get_write_target_for_buffer(buffer); + return &_input_texture_data[_buffer_builder->_write_target_pointer]; // * _buffer_builder->bytes_per_pixel +// return _buffer_builder->get_write_target_for_buffer(buffer); } inline uint16_t get_last_write_x_posiiton() @@ -241,6 +208,9 @@ class OpenGLOutputBuilder { // TODO: update related uniforms } + uint8_t *_input_texture_data; + GLuint _input_texture_array; + uint8_t *_output_buffer_data; size_t _output_buffer_data_pointer; GLsync _output_buffer_sync; From aa8a192c7ed31d45138259c110141d8a97405a00 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Mar 2016 17:37:55 -0400 Subject: [PATCH 196/307] Simplified API down to their being a single texture with a specified depth. --- Machines/Atari2600/Atari2600.cpp | 5 +- Machines/Electron/Electron.cpp | 5 +- Outputs/CRT/CRT.cpp | 15 ++---- Outputs/CRT/CRT.hpp | 28 +++------- .../CRT/Internals/CRTInputBufferBuilder.cpp | 41 +++----------- .../CRT/Internals/CRTInputBufferBuilder.hpp | 18 +++---- Outputs/CRT/Internals/CRTOpenGL.cpp | 54 +++++++++---------- Outputs/CRT/Internals/CRTOpenGL.hpp | 14 ++--- 8 files changed, 61 insertions(+), 119 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 2e2e16755..8b92ad490 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -24,7 +24,7 @@ Machine::Machine() : _piaDataValue{0xff, 0xff}, _tiaInputValue{0xff, 0xff} { - _crt = new Outputs::CRT::CRT(228, 1, Outputs::CRT::DisplayType::NTSC60, 1, 2); + _crt = new Outputs::CRT::CRT(228, 1, Outputs::CRT::DisplayType::NTSC60, 2); _crt->set_composite_sampling_function( "float composite_sample(vec2 coordinate, float phase)\n" "{\n" @@ -226,8 +226,7 @@ void Machine::output_pixels(unsigned int count) if(state == OutputState::Pixel) { - _crt->allocate_write_area(160); - _outputBuffer = _crt->get_write_target_for_buffer(0); + _outputBuffer = _crt->allocate_write_area(160); } else { _outputBuffer = nullptr; } diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 24353dfd9..0e6ce8b88 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -45,7 +45,7 @@ Machine::Machine() : _audioOutputPositionError(0), _current_pixel_line(-1), _use_fast_tape_hack(false), - _crt(std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1, 1))) + _crt(std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1))) { _crt->set_rgb_sampling_function( "vec4 rgb_sample(vec2 coordinate)" @@ -502,8 +502,7 @@ inline void Machine::start_pixel_line() if(!_isBlankLine) { - _crt->allocate_write_area(640); - _currentLine = _crt->get_write_target_for_buffer(0); + _currentLine = _crt->allocate_write_area(640); } } diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 62d4c39d4..c1776df9d 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -68,22 +68,15 @@ CRT::CRT(unsigned int common_output_divisor) : _common_output_divisor(common_output_divisor), _is_writing_composite_run(false) {} -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 number_of_buffers, ...) : 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) { - va_list buffer_sizes; - va_start(buffer_sizes, number_of_buffers); - _openGL_output_builder = std::unique_ptr(new OpenGLOutputBuilder(number_of_buffers, buffer_sizes)); - va_end(buffer_sizes); - + _openGL_output_builder = std::unique_ptr(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 number_of_buffers, ...) : 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) { - va_list buffer_sizes; - va_start(buffer_sizes, number_of_buffers); - _openGL_output_builder = std::unique_ptr(new OpenGLOutputBuilder(number_of_buffers, buffer_sizes)); - va_end(buffer_sizes); + _openGL_output_builder = std::unique_ptr(new OpenGLOutputBuilder(buffer_depth)); set_new_display_type(cycles_per_line, displayType); } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 92742d59c..5303fbe6c 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -95,19 +95,15 @@ class CRT { @param colour_cycle_denominator Specifies the denominator for the per-line frequency of the colour subcarrier. The colour subcarrier is taken to have colour_cycle_numerator/colour_cycle_denominator cycles per line. - @param number_of_buffers The number of source data buffers to create for this machine. Machines - may provide per-clock-cycle data in any form that they consider convenient, supplying a sampling + @param buffer_depth The depth per pixel of source data buffers to create for this machine. Machines + may provide per-clock-cycle data in the depth that they consider convenient, supplying a sampling function to convert between their data format and either a composite or RGB signal, allowing that work to be offloaded onto the GPU and allowing the output signal to be sampled at a rate appropriate to the display size. - @param ... A list of sizes for source data buffers, provided as the number of bytes per sample. - For compatibility with OpenGL ES, samples should be 1–4 bytes in size. If a machine requires more - than 4 bytes/sample then it should use multiple buffers. - @see @c set_rgb_sampling_function , @c set_composite_sampling_function */ - 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(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); /*! Constructs the CRT with the specified clock rate, with the display height and colour subcarrier frequency dictated by a standard display type and with the requested number of @@ -116,7 +112,7 @@ class CRT { Exactly identical to calling the designated constructor with colour subcarrier information looked up by display type. */ - CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int number_of_buffers, ...); + CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth); /*! Resets the CRT with new timing information. The CRT then continues as though the new timing had been provided at construction. */ @@ -171,30 +167,18 @@ class CRT { /*! Ensures that the given number of output samples are allocated for writing. - Following this call, the caller should call @c get_write_target_for_buffer for each - buffer they requested to get the location of the allocated memory. - The beginning of the most recently allocated area is used as the start of data written by a call to @c output_data; it is acceptable to write and to output less data than the amount requested but that may be less efficient. @param required_length The number of samples to allocate. + @returns A pointer to the allocated area. */ - inline void allocate_write_area(size_t required_length) + inline uint8_t *allocate_write_area(size_t required_length) { return _openGL_output_builder->allocate_write_area(required_length); } - /*! Gets a pointer for writing to the area created by the most recent call to @c allocate_write_area - for the nominated buffer. - - @param buffer The buffer to get a write target for. - */ - inline uint8_t *get_write_target_for_buffer(int buffer) - { - return _openGL_output_builder->get_write_target_for_buffer(buffer); - } - /*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state. The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call. */ diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp index fd3edc5d5..6fdaf5ba8 100644 --- a/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp +++ b/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp @@ -12,29 +12,12 @@ using namespace Outputs::CRT; -CRTInputBufferBuilder::CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes) +CRTInputBufferBuilder::CRTInputBufferBuilder(size_t bytes_per_pixel) : bytes_per_pixel(bytes_per_pixel) { - this->number_of_buffers = number_of_buffers; - buffers = new CRTInputBufferBuilder::Buffer[number_of_buffers]; - - for(int buffer = 0; buffer < number_of_buffers; buffer++) - { - buffers[buffer].bytes_per_pixel = va_arg(buffer_sizes, unsigned int); - buffers[buffer].data = new uint8_t[InputBufferBuilderWidth * InputBufferBuilderHeight * buffers[buffer].bytes_per_pixel]; - } - _next_write_x_position = _next_write_y_position = 0; last_uploaded_line = 0; } -CRTInputBufferBuilder::~CRTInputBufferBuilder() -{ - for(int buffer = 0; buffer < number_of_buffers; buffer++) - delete[] buffers[buffer].data; - delete buffers; -} - - void CRTInputBufferBuilder::allocate_write_area(size_t required_length) { _last_allocation_amount = required_length; @@ -50,26 +33,18 @@ void CRTInputBufferBuilder::allocate_write_area(size_t required_length) _next_write_x_position += required_length + 2; } -void CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length) +void CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length, uint8_t *buffer) { // book end the allocation with duplicates of the first and last pixel, to protect // against rounding errors when this run is drawn - for(int c = 0; c < number_of_buffers; c++) - { - memcpy( &buffers[c].data[(_write_target_pointer - 1) * buffers[c].bytes_per_pixel], - &buffers[c].data[_write_target_pointer * buffers[c].bytes_per_pixel], - buffers[c].bytes_per_pixel); + memcpy( &buffer[(_write_target_pointer - 1) * bytes_per_pixel], + &buffer[_write_target_pointer * bytes_per_pixel], + bytes_per_pixel); - memcpy( &buffers[c].data[(_write_target_pointer + actual_length) * buffers[c].bytes_per_pixel], - &buffers[c].data[(_write_target_pointer + actual_length - 1) * buffers[c].bytes_per_pixel], - buffers[c].bytes_per_pixel); - } + memcpy( &buffer[(_write_target_pointer + actual_length) * bytes_per_pixel], + &buffer[(_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_write_target_for_buffer(int buffer) -{ - return &buffers[buffer].data[_write_target_pointer * buffers[buffer].bytes_per_pixel]; -} diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp index 6b8653963..78e665a6b 100644 --- a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp +++ b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp @@ -18,12 +18,10 @@ namespace Outputs { namespace CRT { struct CRTInputBufferBuilder { - CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes); - ~CRTInputBufferBuilder(); + CRTInputBufferBuilder(size_t bytes_per_pixel); void allocate_write_area(size_t required_length); - void reduce_previous_allocation_to(size_t actual_length); - uint8_t *get_write_target_for_buffer(int buffer); + void reduce_previous_allocation_to(size_t actual_length, uint8_t *buffer); // a pointer to the section of content buffer currently being // returned and to where the next section will begin @@ -31,12 +29,7 @@ struct CRTInputBufferBuilder { uint16_t _write_x_position, _write_y_position; size_t _write_target_pointer; size_t _last_allocation_amount; - - struct Buffer { - uint8_t *data; - size_t bytes_per_pixel; - } *buffers; - unsigned int number_of_buffers; + size_t bytes_per_pixel; // Storage for the amount of buffer uploaded so far; initialised correctly by the buffer // builder but otherwise entrusted to the CRT to update. @@ -47,6 +40,11 @@ struct CRTInputBufferBuilder { _next_write_x_position = 0; _next_write_y_position = (_next_write_y_position+1)%InputBufferBuilderHeight; } + + inline uint8_t *get_write_target(uint8_t *buffer) + { + return &buffer[_write_target_pointer * bytes_per_pixel]; + } }; } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index fcd96e028..8c7104a3c 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -17,7 +17,7 @@ namespace { static const GLenum first_supplied_buffer_texture_unit = 3; } -OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int number_of_buffers, va_list sizes) : +OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _run_write_pointer(0), _output_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), @@ -35,10 +35,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int number_of_buffers, va_list } // _composite_src_runs = std::unique_ptr(new CRTRunBuilder(InputVertexSize)); - va_list va; - va_copy(va, sizes); - _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(number_of_buffers, sizes)); - va_end(va); + _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); } OpenGLOutputBuilder::~OpenGLOutputBuilder() @@ -48,9 +45,13 @@ OpenGLOutputBuilder::~OpenGLOutputBuilder() delete _run_builders[builder]; } delete[] _run_builders; -// delete[] _input_texture_data; glUnmapBuffer(GL_ARRAY_BUFFER); + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + glDeleteTextures(1, &textureName); + glDeleteBuffers(1, &_input_texture_array); + glDeleteBuffers(1, &output_array_buffer); + glDeleteVertexArrays(1, &output_vertex_array); free(_composite_shader); free(_rgb_shader); @@ -73,24 +74,21 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // establish essentials if(!composite_input_shader_program && !rgb_shader_program) { - // generate and bind textures for every one of the requested buffers - for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) - { - glGenTextures(1, &textureName); - glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); - 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); + // generate and bind texture for input data + glGenTextures(1, &textureName); + glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_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); - GLenum format = formatForDepth(_buffer_builder->buffers[buffer].bytes_per_pixel); - glGenBuffers(1, &_input_texture_array); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _input_texture_array); - glBufferData(GL_PIXEL_UNPACK_BUFFER, InputTextureBufferDataSize, NULL, GL_STREAM_DRAW); + GLenum format = formatForDepth(_buffer_builder->bytes_per_pixel); + glGenBuffers(1, &_input_texture_array); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _input_texture_array); + glBufferData(GL_PIXEL_UNPACK_BUFFER, InputTextureBufferDataSize, NULL, GL_STREAM_DRAW); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, nullptr); - } + glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, nullptr); prepare_composite_input_shader(); prepare_rgb_output_shader(); @@ -134,17 +132,17 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it - for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) - { +// for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) +// { // glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); - GLenum format = formatForDepth(_buffer_builder->buffers[0].bytes_per_pixel); + GLenum format = formatForDepth(_buffer_builder->bytes_per_pixel); if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, InputBufferBuilderWidth, (GLint)(InputBufferBuilderHeight - _buffer_builder->last_uploaded_line), format, GL_UNSIGNED_BYTE, - (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel)); + (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->bytes_per_pixel)); _buffer_builder->last_uploaded_line = 0; } @@ -154,10 +152,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out 0, (GLint)_buffer_builder->last_uploaded_line, InputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), format, GL_UNSIGNED_BYTE, - (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->buffers[0].bytes_per_pixel)); + (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->bytes_per_pixel)); _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; } - } +// } // check for anything to decode from composite // if(_composite_src_runs->number_of_vertices) diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index c9dcf45ef..0d48a9ed3 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -89,7 +89,7 @@ class OpenGLOutputBuilder { std::unique_ptr filteredTexture; // receives filtered YIQ or YUV public: - OpenGLOutputBuilder(unsigned int number_of_buffers, va_list sizes); + OpenGLOutputBuilder(unsigned int buffer_depth); ~OpenGLOutputBuilder(); inline void set_colour_format(ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator) @@ -164,22 +164,18 @@ class OpenGLOutputBuilder { _output_mutex->unlock(); } - inline void allocate_write_area(size_t required_length) + inline uint8_t *allocate_write_area(size_t required_length) { _output_mutex->lock(); _buffer_builder->allocate_write_area(required_length); + uint8_t *output = _input_texture_data ? _buffer_builder->get_write_target(_input_texture_data) : nullptr; _output_mutex->unlock(); + return output; } inline void reduce_previous_allocation_to(size_t actual_length) { - _buffer_builder->reduce_previous_allocation_to(actual_length); - } - - inline uint8_t *get_write_target_for_buffer(int buffer) - { - return &_input_texture_data[_buffer_builder->_write_target_pointer]; // * _buffer_builder->bytes_per_pixel -// return _buffer_builder->get_write_target_for_buffer(buffer); + _buffer_builder->reduce_previous_allocation_to(actual_length, _input_texture_data); } inline uint16_t get_last_write_x_posiiton() From 9da7716c72e8a34026d01b1ac2c92c628e967482 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Mar 2016 22:46:17 -0400 Subject: [PATCH 197/307] Attempted to simplify threading, thereby allowing machines to be constructed within a valid GL context, and started adding appropriate GL syncs. Which all oddly drops everything to a negligible FPS. Investigation will follow. --- .../Documents/ElectronDocument.swift | 30 ++- .../Mac/Clock Signal/Views/CSOpenGLView.h | 3 + .../Mac/Clock Signal/Views/CSOpenGLView.m | 63 ++---- .../Mac/Clock Signal/Wrappers/CSAtari2600.mm | 6 - .../Mac/Clock Signal/Wrappers/CSElectron.mm | 182 ++++++++++-------- .../Wrappers/CSMachine+Subclassing.h | 6 +- .../Mac/Clock Signal/Wrappers/CSMachine.h | 1 - .../Mac/Clock Signal/Wrappers/CSMachine.mm | 39 +--- .../CRT/Internals/CRTInputBufferBuilder.cpp | 13 +- .../CRT/Internals/CRTInputBufferBuilder.hpp | 10 + Outputs/CRT/Internals/CRTOpenGL.hpp | 1 + 11 files changed, 168 insertions(+), 186 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 9c2a83d61..a815e50d7 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -11,23 +11,21 @@ import AudioToolbox class ElectronDocument: MachineDocument { - private var electron: CSElectron! = CSElectron() - override init() { - super.init() - self.intendedCyclesPerSecond = 2000000 - - if let osPath = NSBundle.mainBundle().pathForResource("os", ofType: "rom") { - electron.setOSROM(NSData(contentsOfFile: osPath)!) - } - if let basicPath = NSBundle.mainBundle().pathForResource("basic", ofType: "rom") { - electron.setBASICROM(NSData(contentsOfFile: basicPath)!) - } - } + private lazy var electron = CSElectron() override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) - electron.view = openGLView - electron.audioQueue = self.audioQueue + self.intendedCyclesPerSecond = 2000000 + openGLView.performWithGLContext({ + if let osPath = NSBundle.mainBundle().pathForResource("os", ofType: "rom") { + self.electron.setOSROM(NSData(contentsOfFile: osPath)!) + } + if let basicPath = NSBundle.mainBundle().pathForResource("basic", ofType: "rom") { + self.electron.setBASICROM(NSData(contentsOfFile: basicPath)!) + } + self.electron.view = self.openGLView + self.electron.audioQueue = self.audioQueue + }) } override var windowNibName: String? { @@ -62,10 +60,8 @@ class ElectronDocument: MachineDocument { lazy var actionLock = NSLock() override func close() { actionLock.lock() - electron.sync() openGLView.invalidate() openGLView.openGLContext!.makeCurrentContext() - electron = nil actionLock.unlock() super.close() @@ -74,7 +70,7 @@ class ElectronDocument: MachineDocument { // MARK: CSOpenGLViewDelegate override func runForNumberOfCycles(numberOfCycles: Int32) { if actionLock.tryLock() { - electron?.runForNumberOfCycles(numberOfCycles) + electron.runForNumberOfCycles(numberOfCycles) actionLock.unlock() } } diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h index 003308243..250689c1b 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h @@ -28,6 +28,7 @@ identical to the previous frame. If @c NO then the delegate must draw. */ - (void)openGLView:(nonnull CSOpenGLView *)view drawViewOnlyIfDirty:(BOOL)onlyIfDirty; + @end @protocol CSOpenGLViewResponderDelegate @@ -72,4 +73,6 @@ /// The size in pixels of the OpenGL canvas, factoring in screen pixel density and view size in points. @property (nonatomic, readonly) CGSize backingSize; +- (void)performWithGLContext:(nonnull dispatch_block_t)action; + @end diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 0e9b133ac..edc816354 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -10,15 +10,8 @@ @import CoreVideo; @import GLKit; -typedef NS_ENUM(NSInteger, CSOpenGLViewCondition) { - CSOpenGLViewConditionReadyForUpdate, - CSOpenGLViewConditionUpdating -}; - @implementation CSOpenGLView { CVDisplayLinkRef _displayLink; - NSConditionLock *_runningLock; - dispatch_queue_t _dispatchQueue; } - (void)prepareOpenGL @@ -33,10 +26,6 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewCondition) { // Set the renderer output callback function CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void * __nullable)(self)); - // Create a queue and a condition lock for dispatching to it - _runningLock = [[NSConditionLock alloc] initWithCondition:CSOpenGLViewConditionReadyForUpdate]; - _dispatchQueue = dispatch_queue_create("com.thomasharte.clocksignal.GL", DISPATCH_QUEUE_SERIAL); - // Set the display link for the current renderer CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; @@ -62,24 +51,14 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawAtTime:(const CVTimeStamp *)now { - if([_runningLock tryLockWhenCondition:CSOpenGLViewConditionReadyForUpdate]) - { - CVTimeStamp timeStamp = *now; - dispatch_async(_dispatchQueue, ^{ - [_runningLock lockWhenCondition:CSOpenGLViewConditionUpdating]; - [self.delegate openGLView:self didUpdateToTime:timeStamp]; - [self drawViewOnlyIfDirty:YES]; - [_runningLock unlockWithCondition:CSOpenGLViewConditionReadyForUpdate]; - }); - [_runningLock unlockWithCondition:CSOpenGLViewConditionUpdating]; - } + NSLog(@"%0.4f", (double)now->videoTime / (double)now->videoTimeScale); + [self.delegate openGLView:self didUpdateToTime:*now]; + [self drawViewOnlyIfDirty:YES]; } - (void)invalidate { CVDisplayLinkStop(_displayLink); - [_runningLock lockWhenCondition:CSOpenGLViewConditionReadyForUpdate]; - [_runningLock unlock]; } - (void)dealloc @@ -97,13 +76,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt { [super reshape]; - [self.openGLContext makeCurrentContext]; - CGLLockContext([[self openGLContext] CGLContextObj]); - - CGSize viewSize = [self backingSize]; - glViewport(0, 0, (GLsizei)viewSize.width, (GLsizei)viewSize.height); - - CGLUnlockContext([[self openGLContext] CGLContextObj]); + [self performWithGLContext:^{ + CGSize viewSize = [self backingSize]; + glViewport(0, 0, (GLsizei)viewSize.width, (GLsizei)viewSize.height); + }]; } - (void)awakeFromNib @@ -141,13 +117,18 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt } - (void)drawViewOnlyIfDirty:(BOOL)onlyIfDirty +{ + [self performWithGLContext:^{ + [self.delegate openGLView:self drawViewOnlyIfDirty:onlyIfDirty]; + CGLFlushDrawable([[self openGLContext] CGLContextObj]); + }]; +} + +- (void)performWithGLContext:(dispatch_block_t)action { [self.openGLContext makeCurrentContext]; CGLLockContext([[self openGLContext] CGLContextObj]); - - [self.delegate openGLView:self drawViewOnlyIfDirty:onlyIfDirty]; - - CGLFlushDrawable([[self openGLContext] CGLContextObj]); + action(); CGLUnlockContext([[self openGLContext] CGLContextObj]); } @@ -160,23 +141,17 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)keyDown:(NSEvent *)theEvent { - dispatch_async(_dispatchQueue, ^{ - [self.responderDelegate keyDown:theEvent]; - }); + [self.responderDelegate keyDown:theEvent]; } - (void)keyUp:(NSEvent *)theEvent { - dispatch_async(_dispatchQueue, ^{ - [self.responderDelegate keyUp:theEvent]; - }); + [self.responderDelegate keyUp:theEvent]; } - (void)flagsChanged:(NSEvent *)theEvent { - dispatch_async(_dispatchQueue, ^{ - [self.responderDelegate flagsChanged:theEvent]; - }); + [self.responderDelegate flagsChanged:theEvent]; } @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm index 8239d90df..2fc2ca00a 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -52,21 +52,15 @@ } - (void)setROM:(NSData *)rom { - [self perform:^{ _atari2600.set_rom(rom.length, (const uint8_t *)rom.bytes); - }]; } - (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput { - [self perform:^{ _atari2600.set_digital_input(digitalInput, state ? true : false); - }]; } - (void)setResetLineEnabled:(BOOL)enabled { - [self perform:^{ _atari2600.set_reset_line(enabled ? true : false); - }]; } @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 36cd681b9..608aa4e73 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -16,124 +16,142 @@ Electron::Machine _electron; } -- (void)doRunForNumberOfCycles:(int)numberOfCycles { - _electron.run_for_cycles(numberOfCycles); - _electron.update_output(); +- (void)runForNumberOfCycles:(int)numberOfCycles { + @synchronized(self) { + _electron.run_for_cycles(numberOfCycles); + _electron.update_output(); + } } - (void)setOSROM:(nonnull NSData *)rom { - _electron.set_rom(Electron::ROMSlotOS, rom.length, (const uint8_t *)rom.bytes); + @synchronized(self) { + _electron.set_rom(Electron::ROMSlotOS, rom.length, (const uint8_t *)rom.bytes); + } } - (void)setBASICROM:(nonnull NSData *)rom { - _electron.set_rom(Electron::ROMSlotBASIC, rom.length, (const uint8_t *)rom.bytes); + @synchronized(self) { + _electron.set_rom(Electron::ROMSlotBASIC, rom.length, (const uint8_t *)rom.bytes); + } } - (void)setROM:(nonnull NSData *)rom slot:(int)slot { - _electron.set_rom((Electron::ROMSlot)slot, rom.length, (const uint8_t *)rom.bytes); + @synchronized(self) { + _electron.set_rom((Electron::ROMSlot)slot, rom.length, (const uint8_t *)rom.bytes); + } } - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { - _electron.get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); + @synchronized(self) { + _electron.get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); + } } - (BOOL)openUEFAtURL:(NSURL *)URL { - try { - std::shared_ptr tape(new Storage::UEF([URL fileSystemRepresentation])); - _electron.set_tape(tape); - return YES; - } catch(int exception) { - return NO; + @synchronized(self) { + try { + std::shared_ptr tape(new Storage::UEF([URL fileSystemRepresentation])); + _electron.set_tape(tape); + return YES; + } catch(int exception) { + return NO; + } } } - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { - _electron.get_speaker()->set_output_rate(sampleRate, 256); - _electron.get_speaker()->set_output_quality(15); - _electron.get_speaker()->set_delegate(delegate); - return YES; + @synchronized(self) { + _electron.get_speaker()->set_output_rate(sampleRate, 256); + _electron.get_speaker()->set_output_quality(15); + _electron.get_speaker()->set_delegate(delegate); + return YES; + } } - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { - switch(key) - { - case kVK_ANSI_0: _electron.set_key_state(Electron::Key::Key0, isPressed); break; - case kVK_ANSI_1: _electron.set_key_state(Electron::Key::Key1, isPressed); break; - case kVK_ANSI_2: _electron.set_key_state(Electron::Key::Key2, isPressed); break; - case kVK_ANSI_3: _electron.set_key_state(Electron::Key::Key3, isPressed); break; - case kVK_ANSI_4: _electron.set_key_state(Electron::Key::Key4, isPressed); break; - case kVK_ANSI_5: _electron.set_key_state(Electron::Key::Key5, isPressed); break; - case kVK_ANSI_6: _electron.set_key_state(Electron::Key::Key6, isPressed); break; - case kVK_ANSI_7: _electron.set_key_state(Electron::Key::Key7, isPressed); break; - case kVK_ANSI_8: _electron.set_key_state(Electron::Key::Key8, isPressed); break; - case kVK_ANSI_9: _electron.set_key_state(Electron::Key::Key9, isPressed); break; + @synchronized(self) { + switch(key) + { + case kVK_ANSI_0: _electron.set_key_state(Electron::Key::Key0, isPressed); break; + case kVK_ANSI_1: _electron.set_key_state(Electron::Key::Key1, isPressed); break; + case kVK_ANSI_2: _electron.set_key_state(Electron::Key::Key2, isPressed); break; + case kVK_ANSI_3: _electron.set_key_state(Electron::Key::Key3, isPressed); break; + case kVK_ANSI_4: _electron.set_key_state(Electron::Key::Key4, isPressed); break; + case kVK_ANSI_5: _electron.set_key_state(Electron::Key::Key5, isPressed); break; + case kVK_ANSI_6: _electron.set_key_state(Electron::Key::Key6, isPressed); break; + case kVK_ANSI_7: _electron.set_key_state(Electron::Key::Key7, isPressed); break; + case kVK_ANSI_8: _electron.set_key_state(Electron::Key::Key8, isPressed); break; + case kVK_ANSI_9: _electron.set_key_state(Electron::Key::Key9, isPressed); break; - case kVK_ANSI_Q: _electron.set_key_state(Electron::Key::KeyQ, isPressed); break; - case kVK_ANSI_W: _electron.set_key_state(Electron::Key::KeyW, isPressed); break; - case kVK_ANSI_E: _electron.set_key_state(Electron::Key::KeyE, isPressed); break; - case kVK_ANSI_R: _electron.set_key_state(Electron::Key::KeyR, isPressed); break; - case kVK_ANSI_T: _electron.set_key_state(Electron::Key::KeyT, isPressed); break; - case kVK_ANSI_Y: _electron.set_key_state(Electron::Key::KeyY, isPressed); break; - case kVK_ANSI_U: _electron.set_key_state(Electron::Key::KeyU, isPressed); break; - case kVK_ANSI_I: _electron.set_key_state(Electron::Key::KeyI, isPressed); break; - case kVK_ANSI_O: _electron.set_key_state(Electron::Key::KeyO, isPressed); break; - case kVK_ANSI_P: _electron.set_key_state(Electron::Key::KeyP, isPressed); break; - case kVK_ANSI_A: _electron.set_key_state(Electron::Key::KeyA, isPressed); break; - case kVK_ANSI_S: _electron.set_key_state(Electron::Key::KeyS, isPressed); break; - case kVK_ANSI_D: _electron.set_key_state(Electron::Key::KeyD, isPressed); break; - case kVK_ANSI_F: _electron.set_key_state(Electron::Key::KeyF, isPressed); break; - case kVK_ANSI_G: _electron.set_key_state(Electron::Key::KeyG, isPressed); break; - case kVK_ANSI_H: _electron.set_key_state(Electron::Key::KeyH, isPressed); break; - case kVK_ANSI_J: _electron.set_key_state(Electron::Key::KeyJ, isPressed); break; - case kVK_ANSI_K: _electron.set_key_state(Electron::Key::KeyK, isPressed); break; - case kVK_ANSI_L: _electron.set_key_state(Electron::Key::KeyL, isPressed); break; - case kVK_ANSI_Z: _electron.set_key_state(Electron::Key::KeyZ, isPressed); break; - case kVK_ANSI_X: _electron.set_key_state(Electron::Key::KeyX, isPressed); break; - case kVK_ANSI_C: _electron.set_key_state(Electron::Key::KeyC, isPressed); break; - case kVK_ANSI_V: _electron.set_key_state(Electron::Key::KeyV, isPressed); break; - case kVK_ANSI_B: _electron.set_key_state(Electron::Key::KeyB, isPressed); break; - case kVK_ANSI_N: _electron.set_key_state(Electron::Key::KeyN, isPressed); break; - case kVK_ANSI_M: _electron.set_key_state(Electron::Key::KeyM, isPressed); break; + case kVK_ANSI_Q: _electron.set_key_state(Electron::Key::KeyQ, isPressed); break; + case kVK_ANSI_W: _electron.set_key_state(Electron::Key::KeyW, isPressed); break; + case kVK_ANSI_E: _electron.set_key_state(Electron::Key::KeyE, isPressed); break; + case kVK_ANSI_R: _electron.set_key_state(Electron::Key::KeyR, isPressed); break; + case kVK_ANSI_T: _electron.set_key_state(Electron::Key::KeyT, isPressed); break; + case kVK_ANSI_Y: _electron.set_key_state(Electron::Key::KeyY, isPressed); break; + case kVK_ANSI_U: _electron.set_key_state(Electron::Key::KeyU, isPressed); break; + case kVK_ANSI_I: _electron.set_key_state(Electron::Key::KeyI, isPressed); break; + case kVK_ANSI_O: _electron.set_key_state(Electron::Key::KeyO, isPressed); break; + case kVK_ANSI_P: _electron.set_key_state(Electron::Key::KeyP, isPressed); break; + case kVK_ANSI_A: _electron.set_key_state(Electron::Key::KeyA, isPressed); break; + case kVK_ANSI_S: _electron.set_key_state(Electron::Key::KeyS, isPressed); break; + case kVK_ANSI_D: _electron.set_key_state(Electron::Key::KeyD, isPressed); break; + case kVK_ANSI_F: _electron.set_key_state(Electron::Key::KeyF, isPressed); break; + case kVK_ANSI_G: _electron.set_key_state(Electron::Key::KeyG, isPressed); break; + case kVK_ANSI_H: _electron.set_key_state(Electron::Key::KeyH, isPressed); break; + case kVK_ANSI_J: _electron.set_key_state(Electron::Key::KeyJ, isPressed); break; + case kVK_ANSI_K: _electron.set_key_state(Electron::Key::KeyK, isPressed); break; + case kVK_ANSI_L: _electron.set_key_state(Electron::Key::KeyL, isPressed); break; + case kVK_ANSI_Z: _electron.set_key_state(Electron::Key::KeyZ, isPressed); break; + case kVK_ANSI_X: _electron.set_key_state(Electron::Key::KeyX, isPressed); break; + case kVK_ANSI_C: _electron.set_key_state(Electron::Key::KeyC, isPressed); break; + case kVK_ANSI_V: _electron.set_key_state(Electron::Key::KeyV, isPressed); break; + case kVK_ANSI_B: _electron.set_key_state(Electron::Key::KeyB, isPressed); break; + case kVK_ANSI_N: _electron.set_key_state(Electron::Key::KeyN, isPressed); break; + case kVK_ANSI_M: _electron.set_key_state(Electron::Key::KeyM, isPressed); break; - case kVK_Space: _electron.set_key_state(Electron::Key::KeySpace, isPressed); break; - case kVK_ANSI_Grave: - case kVK_ANSI_Backslash: - _electron.set_key_state(Electron::Key::KeyCopy, isPressed); break; - case kVK_Return: _electron.set_key_state(Electron::Key::KeyReturn, isPressed); break; - case kVK_ANSI_Minus: _electron.set_key_state(Electron::Key::KeyMinus, isPressed); break; + case kVK_Space: _electron.set_key_state(Electron::Key::KeySpace, isPressed); break; + case kVK_ANSI_Grave: + case kVK_ANSI_Backslash: + _electron.set_key_state(Electron::Key::KeyCopy, isPressed); break; + case kVK_Return: _electron.set_key_state(Electron::Key::KeyReturn, isPressed); break; + case kVK_ANSI_Minus: _electron.set_key_state(Electron::Key::KeyMinus, isPressed); break; - case kVK_RightArrow: _electron.set_key_state(Electron::Key::KeyRight, isPressed); break; - case kVK_LeftArrow: _electron.set_key_state(Electron::Key::KeyLeft, isPressed); break; - case kVK_DownArrow: _electron.set_key_state(Electron::Key::KeyDown, isPressed); break; - case kVK_UpArrow: _electron.set_key_state(Electron::Key::KeyUp, isPressed); break; + case kVK_RightArrow: _electron.set_key_state(Electron::Key::KeyRight, isPressed); break; + case kVK_LeftArrow: _electron.set_key_state(Electron::Key::KeyLeft, isPressed); break; + case kVK_DownArrow: _electron.set_key_state(Electron::Key::KeyDown, isPressed); break; + case kVK_UpArrow: _electron.set_key_state(Electron::Key::KeyUp, isPressed); break; - case kVK_Delete: _electron.set_key_state(Electron::Key::KeyDelete, isPressed); break; - case kVK_Escape: _electron.set_key_state(Electron::Key::KeyEscape, isPressed); break; + case kVK_Delete: _electron.set_key_state(Electron::Key::KeyDelete, isPressed); break; + case kVK_Escape: _electron.set_key_state(Electron::Key::KeyEscape, isPressed); break; - case kVK_ANSI_Comma: _electron.set_key_state(Electron::Key::KeyComma, isPressed); break; - case kVK_ANSI_Period: _electron.set_key_state(Electron::Key::KeyFullStop, isPressed); break; + case kVK_ANSI_Comma: _electron.set_key_state(Electron::Key::KeyComma, isPressed); break; + case kVK_ANSI_Period: _electron.set_key_state(Electron::Key::KeyFullStop, isPressed); break; - case kVK_ANSI_Semicolon: - _electron.set_key_state(Electron::Key::KeySemiColon, isPressed); break; - case kVK_ANSI_Quote: _electron.set_key_state(Electron::Key::KeyColon, isPressed); break; + case kVK_ANSI_Semicolon: + _electron.set_key_state(Electron::Key::KeySemiColon, isPressed); break; + case kVK_ANSI_Quote: _electron.set_key_state(Electron::Key::KeyColon, isPressed); break; - case kVK_ANSI_Slash: _electron.set_key_state(Electron::Key::KeySlash, isPressed); break; + case kVK_ANSI_Slash: _electron.set_key_state(Electron::Key::KeySlash, isPressed); break; - case kVK_Shift: _electron.set_key_state(Electron::Key::KeyShift, isPressed); break; - case kVK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break; - case kVK_Command: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break; + case kVK_Shift: _electron.set_key_state(Electron::Key::KeyShift, isPressed); break; + case kVK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break; + case kVK_Command: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break; - case kVK_F12: _electron.set_key_state(Electron::Key::KeyBreak, isPressed); break; + case kVK_F12: _electron.set_key_state(Electron::Key::KeyBreak, isPressed); break; - default: -// printf("%02x\n", key); - break; + default: +// printf("%02x\n", key); + break; + } } } - (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { - _useFastLoadingHack = useFastLoadingHack; - _electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false); + @synchronized(self) { + _useFastLoadingHack = useFastLoadingHack; + _electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false); + } } @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 0f991da4b..788d1423b 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -13,10 +13,8 @@ @interface CSMachine (Subclassing) - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate; - -- (void)doRunForNumberOfCycles:(int)numberOfCycles; -- (void)perform:(dispatch_block_t)action; - - (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; +- (void)performAsync:(dispatch_block_t)action; +- (void)performSync:(dispatch_block_t)action; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h index f2f50fd26..ee4b6759a 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -13,7 +13,6 @@ @interface CSMachine : NSObject - (void)runForNumberOfCycles:(int)numberOfCycles; -- (void)sync; @property (nonatomic, weak) CSOpenGLView *view; @property (nonatomic, weak) AudioQueue *audioQueue; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index 79a8fcb4e..8f15b603b 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -16,48 +16,20 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { } }; -typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { - CSMachineRunningStateRunning, - CSMachineRunningStateStopped -}; - @implementation CSMachine { SpeakerDelegate _speakerDelegate; - dispatch_queue_t _serialDispatchQueue; - NSConditionLock *_runningLock; -} - -- (void)perform:(dispatch_block_t)action { - dispatch_async(_serialDispatchQueue, action); } - (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length { [self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length]; } -- (void)runForNumberOfCycles:(int)cycles { - if([_runningLock tryLockWhenCondition:CSMachineRunningStateStopped]) { - [_runningLock unlockWithCondition:CSMachineRunningStateRunning]; - dispatch_async(_serialDispatchQueue, ^{ - [_runningLock lockWhenCondition:CSMachineRunningStateRunning]; - [self doRunForNumberOfCycles:cycles]; - [_runningLock unlockWithCondition:CSMachineRunningStateStopped]; - }); - } -} - -- (void)sync { - dispatch_sync(_serialDispatchQueue, ^{}); -} - - (instancetype)init { self = [super init]; if (self) { _serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL); - _runningLock = [[NSConditionLock alloc] initWithCondition:CSMachineRunningStateStopped]; - _speakerDelegate.machine = self; [self setSpeakerDelegate:&_speakerDelegate sampleRate:44100]; } @@ -68,6 +40,15 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) { - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { return NO; } -- (void)doRunForNumberOfCycles:(int)numberOfCycles {} + +- (void)runForNumberOfCycles:(int)numberOfCycles {} + +- (void)performSync:(dispatch_block_t)action { + dispatch_sync(_serialDispatchQueue, action); +} + +- (void)performAsync:(dispatch_block_t)action { + dispatch_async(_serialDispatchQueue, action); +} @end diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp index 6fdaf5ba8..4e4ac0fdd 100644 --- a/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp +++ b/Outputs/CRT/Internals/CRTInputBufferBuilder.cpp @@ -12,10 +12,17 @@ using namespace Outputs::CRT; -CRTInputBufferBuilder::CRTInputBufferBuilder(size_t bytes_per_pixel) : bytes_per_pixel(bytes_per_pixel) +CRTInputBufferBuilder::CRTInputBufferBuilder(size_t bytes_per_pixel) : + bytes_per_pixel(bytes_per_pixel), + _next_write_x_position(0), + _next_write_y_position(0), + last_uploaded_line(0), + _wraparound_sync(glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)) +{} + +CRTInputBufferBuilder::~CRTInputBufferBuilder() { - _next_write_x_position = _next_write_y_position = 0; - last_uploaded_line = 0; + glDeleteSync(_wraparound_sync); } void CRTInputBufferBuilder::allocate_write_area(size_t required_length) diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp index 78e665a6b..440ab178d 100644 --- a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp +++ b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp @@ -13,12 +13,14 @@ #include #include #include "CRTConstants.hpp" +#include "OpenGL.hpp" namespace Outputs { namespace CRT { struct CRTInputBufferBuilder { CRTInputBufferBuilder(size_t bytes_per_pixel); + ~CRTInputBufferBuilder(); void allocate_write_area(size_t required_length); void reduce_previous_allocation_to(size_t actual_length, uint8_t *buffer); @@ -35,10 +37,18 @@ struct CRTInputBufferBuilder { // builder but otherwise entrusted to the CRT to update. unsigned int last_uploaded_line; + GLsync _wraparound_sync; + inline void move_to_new_line() { _next_write_x_position = 0; _next_write_y_position = (_next_write_y_position+1)%InputBufferBuilderHeight; + if(!_next_write_y_position) + { + glClientWaitSync(_wraparound_sync, 0, ~(GLuint64)0); + glDeleteSync(_wraparound_sync); + _wraparound_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } } inline uint8_t *get_write_target(uint8_t *buffer) diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 0d48a9ed3..52ebc154a 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -206,6 +206,7 @@ class OpenGLOutputBuilder { uint8_t *_input_texture_data; GLuint _input_texture_array; + GLsync _input_texture_sync; uint8_t *_output_buffer_data; size_t _output_buffer_data_pointer; From a546277f88fb50926b6f1cc0190b07adc5b91c3c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Mar 2016 22:53:37 -0400 Subject: [PATCH 198/307] Added the simplest mechanism to decouple emulation from the display link queue. --- OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index edc816354..9e08fbd99 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -12,6 +12,7 @@ @implementation CSOpenGLView { CVDisplayLinkRef _displayLink; + uint32_t _updateIsOngoing; } - (void)prepareOpenGL @@ -51,9 +52,15 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawAtTime:(const CVTimeStamp *)now { - NSLog(@"%0.4f", (double)now->videoTime / (double)now->videoTimeScale); - [self.delegate openGLView:self didUpdateToTime:*now]; - [self drawViewOnlyIfDirty:YES]; + const uint32_t activityMask = 0x01; + if(!OSAtomicTestAndSet(activityMask, &_updateIsOngoing)) + { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + [self.delegate openGLView:self didUpdateToTime:*now]; + [self drawViewOnlyIfDirty:YES]; + OSAtomicTestAndClear(activityMask, &_updateIsOngoing); + }); + } } - (void)invalidate From 4c5d66c31772101029f2dc3dc2b03477b9848a86 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Mar 2016 22:54:56 -0400 Subject: [PATCH 199/307] Made sure GL context isn't activated until it's locked. --- OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 9e08fbd99..30d5d6f72 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -133,8 +133,8 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)performWithGLContext:(dispatch_block_t)action { - [self.openGLContext makeCurrentContext]; CGLLockContext([[self openGLContext] CGLContextObj]); + [self.openGLContext makeCurrentContext]; action(); CGLUnlockContext([[self openGLContext] CGLContextObj]); } From eabc382540a2325a9e8ea1ab3dc92843fb33c0ac Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 19 Mar 2016 23:02:42 -0400 Subject: [PATCH 200/307] Fixed: of course I can't rely on the pointer to CVTimeStamp not becoming dangling during a queue jump. --- OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 30d5d6f72..7bd943fe2 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -55,8 +55,9 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt const uint32_t activityMask = 0x01; if(!OSAtomicTestAndSet(activityMask, &_updateIsOngoing)) { + CVTimeStamp time = *now; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - [self.delegate openGLView:self didUpdateToTime:*now]; + [self.delegate openGLView:self didUpdateToTime:time]; [self drawViewOnlyIfDirty:YES]; OSAtomicTestAndClear(activityMask, &_updateIsOngoing); }); From fb6fb5d9483e059fea1ff989f1beff66a0f1a185 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Mar 2016 13:50:13 -0400 Subject: [PATCH 201/307] Switched to two-phase setup to deal with OpenGL scheduling. --- Machines/Electron/Electron.cpp | 16 ++++++++++------ Machines/Electron/Electron.hpp | 1 + .../Mac/Clock Signal/Wrappers/CSElectron.h | 2 ++ .../Mac/Clock Signal/Wrappers/CSElectron.mm | 10 +++++++--- .../Wrappers/CSMachine+Subclassing.h | 1 + .../Mac/Clock Signal/Wrappers/CSMachine.mm | 9 +++++++++ 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 0e6ce8b88..0dd95b24e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -46,6 +46,16 @@ Machine::Machine() : _current_pixel_line(-1), _use_fast_tape_hack(false), _crt(std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1))) +{ + memset(_key_states, 0, sizeof(_key_states)); + memset(_palette, 0xf, sizeof(_palette)); + for(int c = 0; c < 16; c++) + memset(_roms[c], 0xff, 16384); + + _tape.set_delegate(this); +} + +void Machine::setup_output() { _crt->set_rgb_sampling_function( "vec4 rgb_sample(vec2 coordinate)" @@ -56,13 +66,7 @@ Machine::Machine() : _crt->set_output_device(Outputs::CRT::Monitor); // _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 - memset(_key_states, 0, sizeof(_key_states)); - memset(_palette, 0xf, sizeof(_palette)); - for(int c = 0; c < 16; c++) - memset(_roms[c], 0xff, 16384); - _speaker.set_input_rate(125000); - _tape.set_delegate(this); } unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index bcfe7f916..304e7fc81 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -152,6 +152,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { void set_key_state(Key key, bool isPressed); + void setup_output(); Outputs::CRT::CRT *get_crt() { return _crt.get(); } Outputs::Speaker *get_speaker() { return &_speaker; } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index e3ab2f8c5..9cb9b48e6 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -11,6 +11,8 @@ @interface CSElectron : CSMachine +- (void)setupOutput; + - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; - (void)setROM:(nonnull NSData *)rom slot:(int)slot; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 608aa4e73..957318d1d 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -42,9 +42,7 @@ } - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { - @synchronized(self) { - _electron.get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); - } + _electron.get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); } - (BOOL)openUEFAtURL:(NSURL *)URL { @@ -154,4 +152,10 @@ } } +- (void)setupOutput { + @synchronized(self) { + _electron.setup_output(); + } +} + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 788d1423b..2c16901f3 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -16,5 +16,6 @@ - (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; - (void)performAsync:(dispatch_block_t)action; - (void)performSync:(dispatch_block_t)action; +- (void)setupOutput; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index 8f15b603b..8530a595f 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -51,4 +51,13 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { dispatch_async(_serialDispatchQueue, action); } +- (void)setupOutput {} + +- (void)setView:(CSOpenGLView *)view { + _view = view; + [view performWithGLContext:^{ + [self setupOutput]; + }]; +} + @end From 902017a962718ae197b8822c4a2a3e7e9c531418 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Mar 2016 18:42:37 -0400 Subject: [PATCH 202/307] Fixes: drop any processing backlog, try not to allow an Electron document to close mid-draw, perform a frame grab even if the emulated machine is over-processing, really really don't create a CRT until it's safe. --- Machines/Electron/Electron.cpp | 3 ++- .../Mac/Clock Signal/Documents/ElectronDocument.swift | 8 +++++++- .../Mac/Clock Signal/Documents/MachineDocument.swift | 7 ++++++- OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m | 4 ++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 0dd95b24e..a3f16da98 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -45,7 +45,7 @@ Machine::Machine() : _audioOutputPositionError(0), _current_pixel_line(-1), _use_fast_tape_hack(false), - _crt(std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1))) + _crt(nullptr) { memset(_key_states, 0, sizeof(_key_states)); memset(_palette, 0xf, sizeof(_palette)); @@ -57,6 +57,7 @@ Machine::Machine() : void Machine::setup_output() { + _crt = std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1)); _crt->set_rgb_sampling_function( "vec4 rgb_sample(vec2 coordinate)" "{" diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index a815e50d7..588223b72 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -58,11 +58,14 @@ class ElectronDocument: MachineDocument { } lazy var actionLock = NSLock() + lazy var drawLock = NSLock() override func close() { actionLock.lock() + drawLock.lock() openGLView.invalidate() openGLView.openGLContext!.makeCurrentContext() actionLock.unlock() + drawLock.unlock() super.close() } @@ -76,7 +79,10 @@ class ElectronDocument: MachineDocument { } override func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) { - electron.drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty) + if drawLock.tryLock() { + electron.drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty) + drawLock.unlock() + } } // MARK: CSOpenGLViewResponderDelegate diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 3bb2ebac5..062a69158 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -41,7 +41,12 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe let cycleCount = cycleCountLow + cycleCountHigh if let lastCycleCount = lastCycleCount { let elapsedTime = cycleCount - lastCycleCount - runForNumberOfCycles(Int32(elapsedTime)) + // if the emulation has fallen too far behind then silently swallow the request; + // some actions — e.g. the host computer waking after sleep — may give us a + // prohibitive backlog + if elapsedTime < intendedCyclesPerSecond / 25 { + runForNumberOfCycles(Int32(elapsedTime)) + } } lastCycleCount = cycleCount } diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 7bd943fe2..3b04dd760 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -62,6 +62,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt OSAtomicTestAndClear(activityMask, &_updateIsOngoing); }); } + else + { + [self drawViewOnlyIfDirty:YES]; + } } - (void)invalidate From 197cf2a8343f5308788c6a7cb3c193b355a6a608 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Mar 2016 21:42:05 -0400 Subject: [PATCH 203/307] Commented out sync; it can probably be implicit. --- Outputs/CRT/Internals/CRTInputBufferBuilder.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp index 440ab178d..d6eab1ab1 100644 --- a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp +++ b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp @@ -43,12 +43,12 @@ struct CRTInputBufferBuilder { { _next_write_x_position = 0; _next_write_y_position = (_next_write_y_position+1)%InputBufferBuilderHeight; - if(!_next_write_y_position) - { - glClientWaitSync(_wraparound_sync, 0, ~(GLuint64)0); - glDeleteSync(_wraparound_sync); - _wraparound_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - } +// if(!_next_write_y_position) +// { +// glClientWaitSync(_wraparound_sync, 0, ~(GLuint64)0); +// glDeleteSync(_wraparound_sync); +// _wraparound_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); +// } } inline uint8_t *get_write_target(uint8_t *buffer) From 5966ac845fc24c738007ed9ab92404730259d8b2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Mar 2016 22:48:56 -0400 Subject: [PATCH 204/307] Switched symbols and updated all-RAM 6502 in order to fix unit test target. --- .../Documents/ElectronDocument.swift | 6 +- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 112 ++++----- .../Mac/Clock Signal/Wrappers/KeyCodes.h | 229 +++++++++--------- Processors/6502/CPU6502AllRAM.cpp | 5 +- 4 files changed, 177 insertions(+), 175 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 588223b72..14aa80ef1 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -95,8 +95,8 @@ class ElectronDocument: MachineDocument { } override func flagsChanged(newModifiers: NSEvent) { - electron.setKey(kVK_Shift, isPressed: newModifiers.modifierFlags.contains(.ShiftKeyMask)) - electron.setKey(kVK_Control, isPressed: newModifiers.modifierFlags.contains(.ControlKeyMask)) - electron.setKey(kVK_Command, isPressed: newModifiers.modifierFlags.contains(.CommandKeyMask)) + electron.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.ShiftKeyMask)) + electron.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.ControlKeyMask)) + electron.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.CommandKeyMask)) } } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 957318d1d..b98f0b413 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -70,73 +70,73 @@ @synchronized(self) { switch(key) { - case kVK_ANSI_0: _electron.set_key_state(Electron::Key::Key0, isPressed); break; - case kVK_ANSI_1: _electron.set_key_state(Electron::Key::Key1, isPressed); break; - case kVK_ANSI_2: _electron.set_key_state(Electron::Key::Key2, isPressed); break; - case kVK_ANSI_3: _electron.set_key_state(Electron::Key::Key3, isPressed); break; - case kVK_ANSI_4: _electron.set_key_state(Electron::Key::Key4, isPressed); break; - case kVK_ANSI_5: _electron.set_key_state(Electron::Key::Key5, isPressed); break; - case kVK_ANSI_6: _electron.set_key_state(Electron::Key::Key6, isPressed); break; - case kVK_ANSI_7: _electron.set_key_state(Electron::Key::Key7, isPressed); break; - case kVK_ANSI_8: _electron.set_key_state(Electron::Key::Key8, isPressed); break; - case kVK_ANSI_9: _electron.set_key_state(Electron::Key::Key9, isPressed); break; + case VK_ANSI_0: _electron.set_key_state(Electron::Key::Key0, isPressed); break; + case VK_ANSI_1: _electron.set_key_state(Electron::Key::Key1, isPressed); break; + case VK_ANSI_2: _electron.set_key_state(Electron::Key::Key2, isPressed); break; + case VK_ANSI_3: _electron.set_key_state(Electron::Key::Key3, isPressed); break; + case VK_ANSI_4: _electron.set_key_state(Electron::Key::Key4, isPressed); break; + case VK_ANSI_5: _electron.set_key_state(Electron::Key::Key5, isPressed); break; + case VK_ANSI_6: _electron.set_key_state(Electron::Key::Key6, isPressed); break; + case VK_ANSI_7: _electron.set_key_state(Electron::Key::Key7, isPressed); break; + case VK_ANSI_8: _electron.set_key_state(Electron::Key::Key8, isPressed); break; + case VK_ANSI_9: _electron.set_key_state(Electron::Key::Key9, isPressed); break; - case kVK_ANSI_Q: _electron.set_key_state(Electron::Key::KeyQ, isPressed); break; - case kVK_ANSI_W: _electron.set_key_state(Electron::Key::KeyW, isPressed); break; - case kVK_ANSI_E: _electron.set_key_state(Electron::Key::KeyE, isPressed); break; - case kVK_ANSI_R: _electron.set_key_state(Electron::Key::KeyR, isPressed); break; - case kVK_ANSI_T: _electron.set_key_state(Electron::Key::KeyT, isPressed); break; - case kVK_ANSI_Y: _electron.set_key_state(Electron::Key::KeyY, isPressed); break; - case kVK_ANSI_U: _electron.set_key_state(Electron::Key::KeyU, isPressed); break; - case kVK_ANSI_I: _electron.set_key_state(Electron::Key::KeyI, isPressed); break; - case kVK_ANSI_O: _electron.set_key_state(Electron::Key::KeyO, isPressed); break; - case kVK_ANSI_P: _electron.set_key_state(Electron::Key::KeyP, isPressed); break; - case kVK_ANSI_A: _electron.set_key_state(Electron::Key::KeyA, isPressed); break; - case kVK_ANSI_S: _electron.set_key_state(Electron::Key::KeyS, isPressed); break; - case kVK_ANSI_D: _electron.set_key_state(Electron::Key::KeyD, isPressed); break; - case kVK_ANSI_F: _electron.set_key_state(Electron::Key::KeyF, isPressed); break; - case kVK_ANSI_G: _electron.set_key_state(Electron::Key::KeyG, isPressed); break; - case kVK_ANSI_H: _electron.set_key_state(Electron::Key::KeyH, isPressed); break; - case kVK_ANSI_J: _electron.set_key_state(Electron::Key::KeyJ, isPressed); break; - case kVK_ANSI_K: _electron.set_key_state(Electron::Key::KeyK, isPressed); break; - case kVK_ANSI_L: _electron.set_key_state(Electron::Key::KeyL, isPressed); break; - case kVK_ANSI_Z: _electron.set_key_state(Electron::Key::KeyZ, isPressed); break; - case kVK_ANSI_X: _electron.set_key_state(Electron::Key::KeyX, isPressed); break; - case kVK_ANSI_C: _electron.set_key_state(Electron::Key::KeyC, isPressed); break; - case kVK_ANSI_V: _electron.set_key_state(Electron::Key::KeyV, isPressed); break; - case kVK_ANSI_B: _electron.set_key_state(Electron::Key::KeyB, isPressed); break; - case kVK_ANSI_N: _electron.set_key_state(Electron::Key::KeyN, isPressed); break; - case kVK_ANSI_M: _electron.set_key_state(Electron::Key::KeyM, isPressed); break; + case VK_ANSI_Q: _electron.set_key_state(Electron::Key::KeyQ, isPressed); break; + case VK_ANSI_W: _electron.set_key_state(Electron::Key::KeyW, isPressed); break; + case VK_ANSI_E: _electron.set_key_state(Electron::Key::KeyE, isPressed); break; + case VK_ANSI_R: _electron.set_key_state(Electron::Key::KeyR, isPressed); break; + case VK_ANSI_T: _electron.set_key_state(Electron::Key::KeyT, isPressed); break; + case VK_ANSI_Y: _electron.set_key_state(Electron::Key::KeyY, isPressed); break; + case VK_ANSI_U: _electron.set_key_state(Electron::Key::KeyU, isPressed); break; + case VK_ANSI_I: _electron.set_key_state(Electron::Key::KeyI, isPressed); break; + case VK_ANSI_O: _electron.set_key_state(Electron::Key::KeyO, isPressed); break; + case VK_ANSI_P: _electron.set_key_state(Electron::Key::KeyP, isPressed); break; + case VK_ANSI_A: _electron.set_key_state(Electron::Key::KeyA, isPressed); break; + case VK_ANSI_S: _electron.set_key_state(Electron::Key::KeyS, isPressed); break; + case VK_ANSI_D: _electron.set_key_state(Electron::Key::KeyD, isPressed); break; + case VK_ANSI_F: _electron.set_key_state(Electron::Key::KeyF, isPressed); break; + case VK_ANSI_G: _electron.set_key_state(Electron::Key::KeyG, isPressed); break; + case VK_ANSI_H: _electron.set_key_state(Electron::Key::KeyH, isPressed); break; + case VK_ANSI_J: _electron.set_key_state(Electron::Key::KeyJ, isPressed); break; + case VK_ANSI_K: _electron.set_key_state(Electron::Key::KeyK, isPressed); break; + case VK_ANSI_L: _electron.set_key_state(Electron::Key::KeyL, isPressed); break; + case VK_ANSI_Z: _electron.set_key_state(Electron::Key::KeyZ, isPressed); break; + case VK_ANSI_X: _electron.set_key_state(Electron::Key::KeyX, isPressed); break; + case VK_ANSI_C: _electron.set_key_state(Electron::Key::KeyC, isPressed); break; + case VK_ANSI_V: _electron.set_key_state(Electron::Key::KeyV, isPressed); break; + case VK_ANSI_B: _electron.set_key_state(Electron::Key::KeyB, isPressed); break; + case VK_ANSI_N: _electron.set_key_state(Electron::Key::KeyN, isPressed); break; + case VK_ANSI_M: _electron.set_key_state(Electron::Key::KeyM, isPressed); break; - case kVK_Space: _electron.set_key_state(Electron::Key::KeySpace, isPressed); break; - case kVK_ANSI_Grave: - case kVK_ANSI_Backslash: + case VK_Space: _electron.set_key_state(Electron::Key::KeySpace, isPressed); break; + case VK_ANSI_Grave: + case VK_ANSI_Backslash: _electron.set_key_state(Electron::Key::KeyCopy, isPressed); break; - case kVK_Return: _electron.set_key_state(Electron::Key::KeyReturn, isPressed); break; - case kVK_ANSI_Minus: _electron.set_key_state(Electron::Key::KeyMinus, isPressed); break; + case VK_Return: _electron.set_key_state(Electron::Key::KeyReturn, isPressed); break; + case VK_ANSI_Minus: _electron.set_key_state(Electron::Key::KeyMinus, isPressed); break; - case kVK_RightArrow: _electron.set_key_state(Electron::Key::KeyRight, isPressed); break; - case kVK_LeftArrow: _electron.set_key_state(Electron::Key::KeyLeft, isPressed); break; - case kVK_DownArrow: _electron.set_key_state(Electron::Key::KeyDown, isPressed); break; - case kVK_UpArrow: _electron.set_key_state(Electron::Key::KeyUp, isPressed); break; + case VK_RightArrow: _electron.set_key_state(Electron::Key::KeyRight, isPressed); break; + case VK_LeftArrow: _electron.set_key_state(Electron::Key::KeyLeft, isPressed); break; + case VK_DownArrow: _electron.set_key_state(Electron::Key::KeyDown, isPressed); break; + case VK_UpArrow: _electron.set_key_state(Electron::Key::KeyUp, isPressed); break; - case kVK_Delete: _electron.set_key_state(Electron::Key::KeyDelete, isPressed); break; - case kVK_Escape: _electron.set_key_state(Electron::Key::KeyEscape, isPressed); break; + case VK_Delete: _electron.set_key_state(Electron::Key::KeyDelete, isPressed); break; + case VK_Escape: _electron.set_key_state(Electron::Key::KeyEscape, isPressed); break; - case kVK_ANSI_Comma: _electron.set_key_state(Electron::Key::KeyComma, isPressed); break; - case kVK_ANSI_Period: _electron.set_key_state(Electron::Key::KeyFullStop, isPressed); break; + case VK_ANSI_Comma: _electron.set_key_state(Electron::Key::KeyComma, isPressed); break; + case VK_ANSI_Period: _electron.set_key_state(Electron::Key::KeyFullStop, isPressed); break; - case kVK_ANSI_Semicolon: + case VK_ANSI_Semicolon: _electron.set_key_state(Electron::Key::KeySemiColon, isPressed); break; - case kVK_ANSI_Quote: _electron.set_key_state(Electron::Key::KeyColon, isPressed); break; + case VK_ANSI_Quote: _electron.set_key_state(Electron::Key::KeyColon, isPressed); break; - case kVK_ANSI_Slash: _electron.set_key_state(Electron::Key::KeySlash, isPressed); break; + case VK_ANSI_Slash: _electron.set_key_state(Electron::Key::KeySlash, isPressed); break; - case kVK_Shift: _electron.set_key_state(Electron::Key::KeyShift, isPressed); break; - case kVK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break; - case kVK_Command: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break; + case VK_Shift: _electron.set_key_state(Electron::Key::KeyShift, isPressed); break; + case VK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break; + case VK_Command: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break; - case kVK_F12: _electron.set_key_state(Electron::Key::KeyBreak, isPressed); break; + case VK_F12: _electron.set_key_state(Electron::Key::KeyBreak, isPressed); break; default: // printf("%02x\n", key); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/KeyCodes.h b/OSBindings/Mac/Clock Signal/Wrappers/KeyCodes.h index bc30eaaf2..8d103fbbd 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/KeyCodes.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/KeyCodes.h @@ -9,6 +9,11 @@ #ifndef KeyCodes_h #define KeyCodes_h +/* + Carbon somehow still manages to get into the unit test target; I can't figure out how. So I've + renamed these contants from their origina kVK prefixes to just VK. +*/ + /* * Summary: * Virtual keycodes @@ -25,122 +30,122 @@ * keycode. */ enum: uint16_t { - kVK_ANSI_A = 0x00, - kVK_ANSI_S = 0x01, - kVK_ANSI_D = 0x02, - kVK_ANSI_F = 0x03, - kVK_ANSI_H = 0x04, - kVK_ANSI_G = 0x05, - kVK_ANSI_Z = 0x06, - kVK_ANSI_X = 0x07, - kVK_ANSI_C = 0x08, - kVK_ANSI_V = 0x09, - kVK_ANSI_B = 0x0B, - kVK_ANSI_Q = 0x0C, - kVK_ANSI_W = 0x0D, - kVK_ANSI_E = 0x0E, - kVK_ANSI_R = 0x0F, - kVK_ANSI_Y = 0x10, - kVK_ANSI_T = 0x11, - kVK_ANSI_1 = 0x12, - kVK_ANSI_2 = 0x13, - kVK_ANSI_3 = 0x14, - kVK_ANSI_4 = 0x15, - kVK_ANSI_6 = 0x16, - kVK_ANSI_5 = 0x17, - kVK_ANSI_Equal = 0x18, - kVK_ANSI_9 = 0x19, - kVK_ANSI_7 = 0x1A, - kVK_ANSI_Minus = 0x1B, - kVK_ANSI_8 = 0x1C, - kVK_ANSI_0 = 0x1D, - kVK_ANSI_RightBracket = 0x1E, - kVK_ANSI_O = 0x1F, - kVK_ANSI_U = 0x20, - kVK_ANSI_LeftBracket = 0x21, - kVK_ANSI_I = 0x22, - kVK_ANSI_P = 0x23, - kVK_ANSI_L = 0x25, - kVK_ANSI_J = 0x26, - kVK_ANSI_Quote = 0x27, - kVK_ANSI_K = 0x28, - kVK_ANSI_Semicolon = 0x29, - kVK_ANSI_Backslash = 0x2A, - kVK_ANSI_Comma = 0x2B, - kVK_ANSI_Slash = 0x2C, - kVK_ANSI_N = 0x2D, - kVK_ANSI_M = 0x2E, - kVK_ANSI_Period = 0x2F, - kVK_ANSI_Grave = 0x32, - kVK_ANSI_KeypadDecimal = 0x41, - kVK_ANSI_KeypadMultiply = 0x43, - kVK_ANSI_KeypadPlus = 0x45, - kVK_ANSI_KeypadClear = 0x47, - kVK_ANSI_KeypadDivide = 0x4B, - kVK_ANSI_KeypadEnter = 0x4C, - kVK_ANSI_KeypadMinus = 0x4E, - kVK_ANSI_KeypadEquals = 0x51, - kVK_ANSI_Keypad0 = 0x52, - kVK_ANSI_Keypad1 = 0x53, - kVK_ANSI_Keypad2 = 0x54, - kVK_ANSI_Keypad3 = 0x55, - kVK_ANSI_Keypad4 = 0x56, - kVK_ANSI_Keypad5 = 0x57, - kVK_ANSI_Keypad6 = 0x58, - kVK_ANSI_Keypad7 = 0x59, - kVK_ANSI_Keypad8 = 0x5B, - kVK_ANSI_Keypad9 = 0x5C + VK_ANSI_A = 0x00, + VK_ANSI_S = 0x01, + VK_ANSI_D = 0x02, + VK_ANSI_F = 0x03, + VK_ANSI_H = 0x04, + VK_ANSI_G = 0x05, + VK_ANSI_Z = 0x06, + VK_ANSI_X = 0x07, + VK_ANSI_C = 0x08, + VK_ANSI_V = 0x09, + VK_ANSI_B = 0x0B, + VK_ANSI_Q = 0x0C, + VK_ANSI_W = 0x0D, + VK_ANSI_E = 0x0E, + VK_ANSI_R = 0x0F, + VK_ANSI_Y = 0x10, + VK_ANSI_T = 0x11, + VK_ANSI_1 = 0x12, + VK_ANSI_2 = 0x13, + VK_ANSI_3 = 0x14, + VK_ANSI_4 = 0x15, + VK_ANSI_6 = 0x16, + VK_ANSI_5 = 0x17, + VK_ANSI_Equal = 0x18, + VK_ANSI_9 = 0x19, + VK_ANSI_7 = 0x1A, + VK_ANSI_Minus = 0x1B, + VK_ANSI_8 = 0x1C, + VK_ANSI_0 = 0x1D, + VK_ANSI_RightBracket = 0x1E, + VK_ANSI_O = 0x1F, + VK_ANSI_U = 0x20, + VK_ANSI_LeftBracket = 0x21, + VK_ANSI_I = 0x22, + VK_ANSI_P = 0x23, + VK_ANSI_L = 0x25, + VK_ANSI_J = 0x26, + VK_ANSI_Quote = 0x27, + VK_ANSI_K = 0x28, + VK_ANSI_Semicolon = 0x29, + VK_ANSI_Backslash = 0x2A, + VK_ANSI_Comma = 0x2B, + VK_ANSI_Slash = 0x2C, + VK_ANSI_N = 0x2D, + VK_ANSI_M = 0x2E, + VK_ANSI_Period = 0x2F, + VK_ANSI_Grave = 0x32, + VK_ANSI_KeypadDecimal = 0x41, + VK_ANSI_KeypadMultiply = 0x43, + VK_ANSI_KeypadPlus = 0x45, + VK_ANSI_KeypadClear = 0x47, + VK_ANSI_KeypadDivide = 0x4B, + VK_ANSI_KeypadEnter = 0x4C, + VK_ANSI_KeypadMinus = 0x4E, + VK_ANSI_KeypadEquals = 0x51, + VK_ANSI_Keypad0 = 0x52, + VK_ANSI_Keypad1 = 0x53, + VK_ANSI_Keypad2 = 0x54, + VK_ANSI_Keypad3 = 0x55, + VK_ANSI_Keypad4 = 0x56, + VK_ANSI_Keypad5 = 0x57, + VK_ANSI_Keypad6 = 0x58, + VK_ANSI_Keypad7 = 0x59, + VK_ANSI_Keypad8 = 0x5B, + VK_ANSI_Keypad9 = 0x5C }; /* keycodes for keys that are independent of keyboard layout*/ enum: uint16_t { - kVK_Return = 0x24, - kVK_Tab = 0x30, - kVK_Space = 0x31, - kVK_Delete = 0x33, - kVK_Escape = 0x35, - kVK_Command = 0x37, - kVK_Shift = 0x38, - kVK_CapsLock = 0x39, - kVK_Option = 0x3A, - kVK_Control = 0x3B, - kVK_RightShift = 0x3C, - kVK_RightOption = 0x3D, - kVK_RightControl = 0x3E, - kVK_Function = 0x3F, - kVK_F17 = 0x40, - kVK_VolumeUp = 0x48, - kVK_VolumeDown = 0x49, - kVK_Mute = 0x4A, - kVK_F18 = 0x4F, - kVK_F19 = 0x50, - kVK_F20 = 0x5A, - kVK_F5 = 0x60, - kVK_F6 = 0x61, - kVK_F7 = 0x62, - kVK_F3 = 0x63, - kVK_F8 = 0x64, - kVK_F9 = 0x65, - kVK_F11 = 0x67, - kVK_F13 = 0x69, - kVK_F16 = 0x6A, - kVK_F14 = 0x6B, - kVK_F10 = 0x6D, - kVK_F12 = 0x6F, - kVK_F15 = 0x71, - kVK_Help = 0x72, - kVK_Home = 0x73, - kVK_PageUp = 0x74, - kVK_ForwardDelete = 0x75, - kVK_F4 = 0x76, - kVK_End = 0x77, - kVK_F2 = 0x78, - kVK_PageDown = 0x79, - kVK_F1 = 0x7A, - kVK_LeftArrow = 0x7B, - kVK_RightArrow = 0x7C, - kVK_DownArrow = 0x7D, - kVK_UpArrow = 0x7E + VK_Return = 0x24, + VK_Tab = 0x30, + VK_Space = 0x31, + VK_Delete = 0x33, + VK_Escape = 0x35, + VK_Command = 0x37, + VK_Shift = 0x38, + VK_CapsLock = 0x39, + VK_Option = 0x3A, + VK_Control = 0x3B, + VK_RightShift = 0x3C, + VK_RightOption = 0x3D, + VK_RightControl = 0x3E, + VK_Function = 0x3F, + VK_F17 = 0x40, + VK_VolumeUp = 0x48, + VK_VolumeDown = 0x49, + VK_Mute = 0x4A, + VK_F18 = 0x4F, + VK_F19 = 0x50, + VK_F20 = 0x5A, + VK_F5 = 0x60, + VK_F6 = 0x61, + VK_F7 = 0x62, + VK_F3 = 0x63, + VK_F8 = 0x64, + VK_F9 = 0x65, + VK_F11 = 0x67, + VK_F13 = 0x69, + VK_F16 = 0x6A, + VK_F14 = 0x6B, + VK_F10 = 0x6D, + VK_F12 = 0x6F, + VK_F15 = 0x71, + VK_Help = 0x72, + VK_Home = 0x73, + VK_PageUp = 0x74, + VK_ForwardDelete = 0x75, + VK_F4 = 0x76, + VK_End = 0x77, + VK_F2 = 0x78, + VK_PageDown = 0x79, + VK_F1 = 0x7A, + VK_LeftArrow = 0x7B, + VK_RightArrow = 0x7C, + VK_DownArrow = 0x7D, + VK_UpArrow = 0x7E }; #endif /* KeyCodes_h */ diff --git a/Processors/6502/CPU6502AllRAM.cpp b/Processors/6502/CPU6502AllRAM.cpp index e4144744c..95df536c2 100644 --- a/Processors/6502/CPU6502AllRAM.cpp +++ b/Processors/6502/CPU6502AllRAM.cpp @@ -12,10 +12,7 @@ using namespace CPU6502; -AllRAMProcessor::AllRAMProcessor() : _timestamp(0) -{ - setup6502(); -} +AllRAMProcessor::AllRAMProcessor() : _timestamp(0) {} int AllRAMProcessor::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { From 2f174b3a3e8d37156f862e88c54ed15bc5f04dfb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Mar 2016 22:50:16 -0400 Subject: [PATCH 205/307] Moved operand to stack-local storage. --- Processors/6502/CPU6502.hpp | 192 ++++++++++++++++++------------------ 1 file changed, 97 insertions(+), 95 deletions(-) diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index be27bd198..ce414db0a 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -550,6 +550,7 @@ template class Processor { // to date in this stack frame only); which saves some complicated addressing unsigned int scheduleProgramsReadPointer = _scheduleProgramsReadPointer; unsigned int scheduleProgramProgramCounter = _scheduleProgramProgramCounter; + uint8_t operand = _operand; #define checkSchedule(op) \ if(!_scheduledPrograms[scheduleProgramsReadPointer]) {\ @@ -615,7 +616,7 @@ template class Processor { } break; case CycleFetchOperand: - read_mem(_operand, _pc.full); + read_mem(operand, _pc.full); break; case OperationDecodeOperation: @@ -639,7 +640,7 @@ template class Processor { case CycleIncPCPushPCH: _pc.full++; // deliberate fallthrough case CyclePushPCH: push(_pc.bytes.high); break; case CyclePushPCL: push(_pc.bytes.low); break; - case CyclePushOperand: push(_operand); break; + case CyclePushOperand: push(operand); break; case CyclePushA: push(_a); break; #undef push @@ -655,10 +656,10 @@ template class Processor { case CyclePullPCL: _s++; read_mem(_pc.bytes.low, _s | 0x100); break; case CyclePullPCH: _s++; read_mem(_pc.bytes.high, _s | 0x100); break; case CyclePullA: _s++; read_mem(_a, _s | 0x100); break; - case CyclePullOperand: _s++; read_mem(_operand, _s | 0x100); break; - case OperationSetFlagsFromOperand: set_flags(_operand); break; - case OperationSetOperandFromFlagsWithBRKSet: _operand = get_flags() | Flag::Break; break; - case OperationSetOperandFromFlags: _operand = get_flags(); break; + case CyclePullOperand: _s++; read_mem(operand, _s | 0x100); break; + case OperationSetFlagsFromOperand: set_flags(operand); break; + case OperationSetOperandFromFlagsWithBRKSet: operand = get_flags() | Flag::Break; break; + case OperationSetOperandFromFlags: operand = get_flags(); break; case OperationSetFlagsFromA: _zeroResult = _negativeResult = _a; break; case CycleIncrementPCAndReadStack: _pc.full++; throwaway_read(_s | 0x100); break; @@ -686,45 +687,45 @@ template class Processor { #pragma mark - Bitwise - case OperationORA: _a |= _operand; _negativeResult = _zeroResult = _a; break; - case OperationAND: _a &= _operand; _negativeResult = _zeroResult = _a; break; - case OperationEOR: _a ^= _operand; _negativeResult = _zeroResult = _a; break; + case OperationORA: _a |= operand; _negativeResult = _zeroResult = _a; break; + case OperationAND: _a &= operand; _negativeResult = _zeroResult = _a; break; + case OperationEOR: _a ^= operand; _negativeResult = _zeroResult = _a; break; #pragma mark - Load and Store - case OperationLDA: _a = _negativeResult = _zeroResult = _operand; break; - case OperationLDX: _x = _negativeResult = _zeroResult = _operand; break; - case OperationLDY: _y = _negativeResult = _zeroResult = _operand; break; - case OperationLAX: _a = _x = _negativeResult = _zeroResult = _operand; break; + case OperationLDA: _a = _negativeResult = _zeroResult = operand; break; + case OperationLDX: _x = _negativeResult = _zeroResult = operand; break; + case OperationLDY: _y = _negativeResult = _zeroResult = operand; break; + case OperationLAX: _a = _x = _negativeResult = _zeroResult = operand; break; - case OperationSTA: _operand = _a; break; - case OperationSTX: _operand = _x; break; - case OperationSTY: _operand = _y; break; - case OperationSAX: _operand = _a & _x; break; - case OperationSHA: _operand = _a & _x & (_address.bytes.high+1); break; - case OperationSHX: _operand = _x & (_address.bytes.high+1); break; - case OperationSHY: _operand = _y & (_address.bytes.high+1); break; - case OperationSHS: _s = _a & _x; _operand = _s & (_address.bytes.high+1); break; + case OperationSTA: operand = _a; break; + case OperationSTX: operand = _x; break; + case OperationSTY: operand = _y; break; + case OperationSAX: operand = _a & _x; break; + case OperationSHA: operand = _a & _x & (_address.bytes.high+1); break; + case OperationSHX: operand = _x & (_address.bytes.high+1); break; + case OperationSHY: operand = _y & (_address.bytes.high+1); break; + case OperationSHS: _s = _a & _x; operand = _s & (_address.bytes.high+1); break; case OperationLXA: - _a = _x = (_a | 0xee) & _operand; + _a = _x = (_a | 0xee) & operand; _negativeResult = _zeroResult = _a; break; #pragma mark - Compare case OperationCMP: { - const uint16_t temp16 = _a - _operand; + const uint16_t temp16 = _a - operand; _negativeResult = _zeroResult = (uint8_t)temp16; _carryFlag = ((~temp16) >> 8)&1; } break; case OperationCPX: { - const uint16_t temp16 = _x - _operand; + const uint16_t temp16 = _x - operand; _negativeResult = _zeroResult = (uint8_t)temp16; _carryFlag = ((~temp16) >> 8)&1; } break; case OperationCPY: { - const uint16_t temp16 = _y - _operand; + const uint16_t temp16 = _y - operand; _negativeResult = _zeroResult = (uint8_t)temp16; _carryFlag = ((~temp16) >> 8)&1; } break; @@ -732,27 +733,27 @@ template class Processor { #pragma mark - BIT case OperationBIT: - _zeroResult = _operand & _a; - _negativeResult = _operand; - _overflowFlag = _operand&Flag::Overflow; + _zeroResult = operand & _a; + _negativeResult = operand; + _overflowFlag = operand&Flag::Overflow; break; #pragma mark ADC/SBC (and INS) case OperationINS: - _operand++; // deliberate fallthrough + operand++; // deliberate fallthrough case OperationSBC: if(_decimalFlag) { const uint16_t notCarry = _carryFlag ^ 0x1; - const uint16_t decimalResult = (uint16_t)_a - (uint16_t)_operand - notCarry; + const uint16_t decimalResult = (uint16_t)_a - (uint16_t)operand - notCarry; uint16_t temp16; - temp16 = (_a&0xf) - (_operand&0xf) - notCarry; + temp16 = (_a&0xf) - (operand&0xf) - notCarry; if(temp16 > 0xf) temp16 -= 0x6; temp16 = (temp16&0x0f) | ((temp16 > 0x0f) ? 0xfff0 : 0x00); - temp16 += (_a&0xf0) - (_operand&0xf0); + temp16 += (_a&0xf0) - (operand&0xf0); - _overflowFlag = ( ( (decimalResult^_a)&(~decimalResult^_operand) )&0x80) >> 1; + _overflowFlag = ( ( (decimalResult^_a)&(~decimalResult^operand) )&0x80) >> 1; _negativeResult = (uint8_t)temp16; _zeroResult = (uint8_t)decimalResult; @@ -762,20 +763,20 @@ template class Processor { _a = (uint8_t)temp16; break; } else { - _operand = ~_operand; + operand = ~operand; } // deliberate fallthrough case OperationADC: if(_decimalFlag) { - const uint16_t decimalResult = (uint16_t)_a + (uint16_t)_operand + (uint16_t)_carryFlag; + const uint16_t decimalResult = (uint16_t)_a + (uint16_t)operand + (uint16_t)_carryFlag; uint16_t temp16; - temp16 = (_a&0xf) + (_operand&0xf) + _carryFlag; + temp16 = (_a&0xf) + (operand&0xf) + _carryFlag; if(temp16 > 0x9) temp16 += 0x6; - temp16 = (temp16&0x0f) + ((temp16 > 0x0f) ? 0x10 : 0x00) + (_a&0xf0) + (_operand&0xf0); + temp16 = (temp16&0x0f) + ((temp16 > 0x0f) ? 0x10 : 0x00) + (_a&0xf0) + (operand&0xf0); - _overflowFlag = (( (decimalResult^_a)&(decimalResult^_operand) )&0x80) >> 1; + _overflowFlag = (( (decimalResult^_a)&(decimalResult^operand) )&0x80) >> 1; _negativeResult = (uint8_t)temp16; _zeroResult = (uint8_t)decimalResult; @@ -784,79 +785,79 @@ template class Processor { _carryFlag = (temp16 > 0xff) ? Flag::Carry : 0; _a = (uint8_t)temp16; } else { - const uint16_t decimalResult = (uint16_t)_a + (uint16_t)_operand + (uint16_t)_carryFlag; - _overflowFlag = (( (decimalResult^_a)&(decimalResult^_operand) )&0x80) >> 1; + const uint16_t decimalResult = (uint16_t)_a + (uint16_t)operand + (uint16_t)_carryFlag; + _overflowFlag = (( (decimalResult^_a)&(decimalResult^operand) )&0x80) >> 1; _negativeResult = _zeroResult = _a = (uint8_t)decimalResult; _carryFlag = (decimalResult >> 8)&1; } // fix up in case this was INS - if(cycle == OperationINS) _operand = ~_operand; + if(cycle == OperationINS) operand = ~operand; break; #pragma mark - Shifts and Rolls case OperationASL: - _carryFlag = _operand >> 7; - _operand <<= 1; - _negativeResult = _zeroResult = _operand; + _carryFlag = operand >> 7; + operand <<= 1; + _negativeResult = _zeroResult = operand; break; case OperationASO: - _carryFlag = _operand >> 7; - _operand <<= 1; - _a |= _operand; + _carryFlag = operand >> 7; + operand <<= 1; + _a |= operand; _negativeResult = _zeroResult = _a; break; case OperationROL: { - const uint8_t temp8 = (uint8_t)((_operand << 1) | _carryFlag); - _carryFlag = _operand >> 7; - _operand = _negativeResult = _zeroResult = temp8; + const uint8_t temp8 = (uint8_t)((operand << 1) | _carryFlag); + _carryFlag = operand >> 7; + operand = _negativeResult = _zeroResult = temp8; } break; case OperationRLA: { - const uint8_t temp8 = (uint8_t)((_operand << 1) | _carryFlag); - _carryFlag = _operand >> 7; - _operand = temp8; - _a &= _operand; + const uint8_t temp8 = (uint8_t)((operand << 1) | _carryFlag); + _carryFlag = operand >> 7; + operand = temp8; + _a &= operand; _negativeResult = _zeroResult = _a; } break; case OperationLSR: - _carryFlag = _operand & 1; - _operand >>= 1; - _negativeResult = _zeroResult = _operand; + _carryFlag = operand & 1; + operand >>= 1; + _negativeResult = _zeroResult = operand; break; case OperationLSE: - _carryFlag = _operand & 1; - _operand >>= 1; - _a ^= _operand; + _carryFlag = operand & 1; + operand >>= 1; + _a ^= operand; _negativeResult = _zeroResult = _a; break; case OperationASR: - _a &= _operand; + _a &= operand; _carryFlag = _a & 1; _a >>= 1; _negativeResult = _zeroResult = _a; break; case OperationROR: { - const uint8_t temp8 = (uint8_t)((_operand >> 1) | (_carryFlag << 7)); - _carryFlag = _operand & 1; - _operand = _negativeResult = _zeroResult = temp8; + const uint8_t temp8 = (uint8_t)((operand >> 1) | (_carryFlag << 7)); + _carryFlag = operand & 1; + operand = _negativeResult = _zeroResult = temp8; } break; case OperationRRA: { - const uint8_t temp8 = (uint8_t)((_operand >> 1) | (_carryFlag << 7)); - _carryFlag = _operand & 1; - _operand = temp8; + const uint8_t temp8 = (uint8_t)((operand >> 1) | (_carryFlag << 7)); + _carryFlag = operand & 1; + operand = temp8; } break; - case OperationDecrementOperand: _operand--; break; - case OperationIncrementOperand: _operand++; break; + case OperationDecrementOperand: operand--; break; + case OperationIncrementOperand: operand++; break; case OperationCLC: _carryFlag = 0; break; case OperationCLI: _interruptFlag = 0; break; @@ -867,26 +868,26 @@ template class Processor { case OperationSEI: _interruptFlag = Flag::Interrupt; break; case OperationSED: _decimalFlag = Flag::Decimal; break; - case OperationINC: _operand++; _negativeResult = _zeroResult = _operand; break; - case OperationDEC: _operand--; _negativeResult = _zeroResult = _operand; break; + case OperationINC: operand++; _negativeResult = _zeroResult = operand; break; + case OperationDEC: operand--; _negativeResult = _zeroResult = operand; break; case OperationINX: _x++; _negativeResult = _zeroResult = _x; break; case OperationDEX: _x--; _negativeResult = _zeroResult = _x; break; case OperationINY: _y++; _negativeResult = _zeroResult = _y; break; case OperationDEY: _y--; _negativeResult = _zeroResult = _y; break; case OperationANE: - _a = (_a | 0xee) & _operand & _x; + _a = (_a | 0xee) & operand & _x; _negativeResult = _zeroResult = _a; break; case OperationANC: - _a &= _operand; + _a &= operand; _negativeResult = _zeroResult = _a; _carryFlag = _a >> 7; break; case OperationLAS: - _a = _x = _s = _s & _operand; + _a = _x = _s = _s & operand; _negativeResult = _zeroResult = _a; break; @@ -921,58 +922,58 @@ template class Processor { break; case CycleIncrementPCFetchAddressLowFromOperand: _pc.full++; - read_mem(_address.bytes.low, _operand); + read_mem(_address.bytes.low, operand); break; case CycleAddXToOperandFetchAddressLow: - _operand += _x; - read_mem(_address.bytes.low, _operand); + operand += _x; + read_mem(_address.bytes.low, operand); break; case CycleIncrementOperandFetchAddressHigh: - _operand++; - read_mem(_address.bytes.high, _operand); + operand++; + read_mem(_address.bytes.high, operand); break; case CycleIncrementPCReadPCHLoadPCL: // deliberate fallthrough _pc.full++; case CycleReadPCHLoadPCL: { uint16_t oldPC = _pc.full; - _pc.bytes.low = _operand; + _pc.bytes.low = operand; read_mem(_pc.bytes.high, oldPC); } break; case CycleReadAddressHLoadAddressL: - _address.bytes.low = _operand; _pc.full++; + _address.bytes.low = operand; _pc.full++; read_mem(_address.bytes.high, _pc.full); break; case CycleLoadAddressAbsolute: { uint16_t nextPC = _pc.full+1; _pc.full += 2; - _address.bytes.low = _operand; + _address.bytes.low = operand; read_mem(_address.bytes.high, nextPC); } break; case OperationLoadAddressZeroPage: _pc.full++; - _address.full = _operand; + _address.full = operand; break; case CycleLoadAddessZeroX: _pc.full++; - _address.full = (_operand + _x)&0xff; - throwaway_read(_operand); + _address.full = (operand + _x)&0xff; + throwaway_read(operand); break; case CycleLoadAddessZeroY: _pc.full++; - _address.full = (_operand + _y)&0xff; - throwaway_read(_operand); + _address.full = (operand + _y)&0xff; + throwaway_read(operand); break; case OperationIncrementPC: _pc.full++; break; - case CycleFetchOperandFromAddress: read_mem(_operand, _address.full); break; - case CycleWriteOperandToAddress: write_mem(_operand, _address.full); break; - case OperationCopyOperandFromA: _operand = _a; break; - case OperationCopyOperandToA: _a = _operand; break; + case CycleFetchOperandFromAddress: read_mem(operand, _address.full); break; + case CycleWriteOperandToAddress: write_mem(operand, _address.full); break; + case OperationCopyOperandFromA: operand = _a; break; + case OperationCopyOperandToA: _a = operand; break; #pragma mark - Branching @@ -988,7 +989,7 @@ template class Processor { case OperationBEQ: BRA(!_zeroResult); break; case CycleAddSignedOperandToPC: - _nextAddress.full = (uint16_t)(_pc.full + (int8_t)_operand); + _nextAddress.full = (uint16_t)(_pc.full + (int8_t)operand); _pc.bytes.low = _nextAddress.bytes.low; if(_nextAddress.bytes.high != _pc.bytes.high) { uint16_t halfUpdatedPc = _pc.full; @@ -1010,7 +1011,7 @@ template class Processor { case OperationARR: if(_decimalFlag) { - _a &= _operand; + _a &= operand; uint8_t unshiftedA = _a; _a = (uint8_t)((_a >> 1) | (_carryFlag << 7)); _zeroResult = _negativeResult = _a; @@ -1022,7 +1023,7 @@ template class Processor { if (_carryFlag) _a += 0x60; } else { - _a &= _operand; + _a &= operand; _a = (uint8_t)((_a >> 1) | (_carryFlag << 7)); _negativeResult = _zeroResult = _a; _carryFlag = (_a >> 6)&1; @@ -1032,7 +1033,7 @@ template class Processor { case OperationSBX: _x &= _a; - uint16_t difference = _x - _operand; + uint16_t difference = _x - operand; _x = (uint8_t)difference; _negativeResult = _zeroResult = _x; _carryFlag = ((difference >> 8)&1)^1; @@ -1047,6 +1048,7 @@ template class Processor { _cycles_left_to_run = number_of_cycles; _scheduleProgramsReadPointer = scheduleProgramsReadPointer; _scheduleProgramProgramCounter = scheduleProgramProgramCounter; + _operand = operand; } } From 9a82f028aa4323da8122e7d7c7939972efa2df8a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Mar 2016 22:52:49 -0400 Subject: [PATCH 206/307] `operation` is now also stack local. --- Processors/6502/CPU6502.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index ce414db0a..5062c2214 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -551,6 +551,7 @@ template class Processor { unsigned int scheduleProgramsReadPointer = _scheduleProgramsReadPointer; unsigned int scheduleProgramProgramCounter = _scheduleProgramProgramCounter; uint8_t operand = _operand; + uint8_t operation = _operation; #define checkSchedule(op) \ if(!_scheduledPrograms[scheduleProgramsReadPointer]) {\ @@ -601,7 +602,7 @@ template class Processor { case CycleFetchOperation: { _lastOperationPC = _pc; _pc.full++; - read_op(_operation, _lastOperationPC.full); + read_op(operation, _lastOperationPC.full); // static int last_cycles_left_to_run = 0; // static bool printed_map[256] = {false}; @@ -620,7 +621,7 @@ template class Processor { break; case OperationDecodeOperation: - decode_operation(_operation); + decode_operation(operation); break; case OperationMoveToNextProgram: @@ -1049,6 +1050,7 @@ template class Processor { _scheduleProgramsReadPointer = scheduleProgramsReadPointer; _scheduleProgramProgramCounter = scheduleProgramProgramCounter; _operand = operand; + _operation = operation; } } From 5db0f9e2d50af83e6f1ab20fa2b8e44a4ac03ee0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 20 Mar 2016 22:59:21 -0400 Subject: [PATCH 207/307] Shunted a few more things onto the stack. --- Processors/6502/CPU6502.hpp | 102 ++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 5062c2214..c3c999ca0 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -552,6 +552,11 @@ template class Processor { unsigned int scheduleProgramProgramCounter = _scheduleProgramProgramCounter; uint8_t operand = _operand; uint8_t operation = _operation; + RegisterPair address = _address; + RegisterPair nextAddress = _nextAddress; + BusOperation nextBusOperation = _nextBusOperation; + uint16_t busAddress = _busAddress; + uint8_t *busValue = _busValue; #define checkSchedule(op) \ if(!_scheduledPrograms[scheduleProgramsReadPointer]) {\ @@ -575,25 +580,25 @@ template class Processor { while(number_of_cycles > 0) { while (_ready_is_active && number_of_cycles > 0) { - number_of_cycles -= static_cast(this)->perform_bus_operation(BusOperation::Ready, _busAddress, _busValue); + number_of_cycles -= static_cast(this)->perform_bus_operation(BusOperation::Ready, busAddress, busValue); } while (!_ready_is_active && number_of_cycles > 0) { - if (_nextBusOperation != BusOperation::None) { + if (nextBusOperation != BusOperation::None) { _irq_request_history[0] = _irq_request_history[1]; _irq_request_history[1] = _irq_line_is_enabled && !_interruptFlag; - number_of_cycles -= static_cast(this)->perform_bus_operation(_nextBusOperation, _busAddress, _busValue); - _nextBusOperation = BusOperation::None; + number_of_cycles -= static_cast(this)->perform_bus_operation(nextBusOperation, busAddress, busValue); + nextBusOperation = BusOperation::None; } const MicroOp cycle = program[scheduleProgramProgramCounter]; scheduleProgramProgramCounter++; -#define read_op(val, addr) _nextBusOperation = BusOperation::ReadOpcode; _busAddress = addr; _busValue = &val -#define read_mem(val, addr) _nextBusOperation = BusOperation::Read; _busAddress = addr; _busValue = &val -#define throwaway_read(addr) _nextBusOperation = BusOperation::Read; _busAddress = addr; _busValue = &throwaway_target -#define write_mem(val, addr) _nextBusOperation = BusOperation::Write; _busAddress = addr; _busValue = &val +#define read_op(val, addr) nextBusOperation = BusOperation::ReadOpcode; busAddress = addr; busValue = &val +#define read_mem(val, addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &val +#define throwaway_read(addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &throwaway_target +#define write_mem(val, addr) nextBusOperation = BusOperation::Write; busAddress = addr; busValue = &val switch(cycle) { @@ -664,8 +669,8 @@ template class Processor { case OperationSetFlagsFromA: _zeroResult = _negativeResult = _a; break; case CycleIncrementPCAndReadStack: _pc.full++; throwaway_read(_s | 0x100); break; - case CycleReadPCLFromAddress: read_mem(_pc.bytes.low, _address.full); break; - case CycleReadPCHFromAddress: _address.bytes.low++; read_mem(_pc.bytes.high, _address.full); break; + case CycleReadPCLFromAddress: read_mem(_pc.bytes.low, address.full); break; + case CycleReadPCHFromAddress: address.bytes.low++; read_mem(_pc.bytes.high, address.full); break; case CycleReadAndIncrementPC: { uint16_t oldPC = _pc.full; @@ -703,10 +708,10 @@ template class Processor { case OperationSTX: operand = _x; break; case OperationSTY: operand = _y; break; case OperationSAX: operand = _a & _x; break; - case OperationSHA: operand = _a & _x & (_address.bytes.high+1); break; - case OperationSHX: operand = _x & (_address.bytes.high+1); break; - case OperationSHY: operand = _y & (_address.bytes.high+1); break; - case OperationSHS: _s = _a & _x; operand = _s & (_address.bytes.high+1); break; + case OperationSHA: operand = _a & _x & (address.bytes.high+1); break; + case OperationSHX: operand = _x & (address.bytes.high+1); break; + case OperationSHY: operand = _y & (address.bytes.high+1); break; + case OperationSHS: _s = _a & _x; operand = _s & (address.bytes.high+1); break; case OperationLXA: _a = _x = (_a | 0xee) & operand; @@ -895,43 +900,43 @@ template class Processor { #pragma mark - Addressing Mode Work case CycleAddXToAddressLow: - _nextAddress.full = _address.full + _x; - _address.bytes.low = _nextAddress.bytes.low; - if (_address.bytes.high != _nextAddress.bytes.high) { - throwaway_read(_address.full); + nextAddress.full = address.full + _x; + address.bytes.low = nextAddress.bytes.low; + if (address.bytes.high != nextAddress.bytes.high) { + throwaway_read(address.full); } break; case CycleAddXToAddressLowRead: - _nextAddress.full = _address.full + _x; - _address.bytes.low = _nextAddress.bytes.low; - throwaway_read(_address.full); + nextAddress.full = address.full + _x; + address.bytes.low = nextAddress.bytes.low; + throwaway_read(address.full); break; case CycleAddYToAddressLow: - _nextAddress.full = _address.full + _y; - _address.bytes.low = _nextAddress.bytes.low; - if (_address.bytes.high != _nextAddress.bytes.high) { - throwaway_read(_address.full); + nextAddress.full = address.full + _y; + address.bytes.low = nextAddress.bytes.low; + if (address.bytes.high != nextAddress.bytes.high) { + throwaway_read(address.full); } break; case CycleAddYToAddressLowRead: - _nextAddress.full = _address.full + _y; - _address.bytes.low = _nextAddress.bytes.low; - throwaway_read(_address.full); + nextAddress.full = address.full + _y; + address.bytes.low = nextAddress.bytes.low; + throwaway_read(address.full); break; case OperationCorrectAddressHigh: - _address.full = _nextAddress.full; + address.full = nextAddress.full; break; case CycleIncrementPCFetchAddressLowFromOperand: _pc.full++; - read_mem(_address.bytes.low, operand); + read_mem(address.bytes.low, operand); break; case CycleAddXToOperandFetchAddressLow: operand += _x; - read_mem(_address.bytes.low, operand); + read_mem(address.bytes.low, operand); break; case CycleIncrementOperandFetchAddressHigh: operand++; - read_mem(_address.bytes.high, operand); + read_mem(address.bytes.high, operand); break; case CycleIncrementPCReadPCHLoadPCL: // deliberate fallthrough _pc.full++; @@ -942,37 +947,37 @@ template class Processor { } break; case CycleReadAddressHLoadAddressL: - _address.bytes.low = operand; _pc.full++; - read_mem(_address.bytes.high, _pc.full); + address.bytes.low = operand; _pc.full++; + read_mem(address.bytes.high, _pc.full); break; case CycleLoadAddressAbsolute: { uint16_t nextPC = _pc.full+1; _pc.full += 2; - _address.bytes.low = operand; - read_mem(_address.bytes.high, nextPC); + address.bytes.low = operand; + read_mem(address.bytes.high, nextPC); } break; case OperationLoadAddressZeroPage: _pc.full++; - _address.full = operand; + address.full = operand; break; case CycleLoadAddessZeroX: _pc.full++; - _address.full = (operand + _x)&0xff; + address.full = (operand + _x)&0xff; throwaway_read(operand); break; case CycleLoadAddessZeroY: _pc.full++; - _address.full = (operand + _y)&0xff; + address.full = (operand + _y)&0xff; throwaway_read(operand); break; case OperationIncrementPC: _pc.full++; break; - case CycleFetchOperandFromAddress: read_mem(operand, _address.full); break; - case CycleWriteOperandToAddress: write_mem(operand, _address.full); break; + case CycleFetchOperandFromAddress: read_mem(operand, address.full); break; + case CycleWriteOperandToAddress: write_mem(operand, address.full); break; case OperationCopyOperandFromA: operand = _a; break; case OperationCopyOperandToA: _a = operand; break; @@ -990,11 +995,11 @@ template class Processor { case OperationBEQ: BRA(!_zeroResult); break; case CycleAddSignedOperandToPC: - _nextAddress.full = (uint16_t)(_pc.full + (int8_t)operand); - _pc.bytes.low = _nextAddress.bytes.low; - if(_nextAddress.bytes.high != _pc.bytes.high) { + nextAddress.full = (uint16_t)(_pc.full + (int8_t)operand); + _pc.bytes.low = nextAddress.bytes.low; + if(nextAddress.bytes.high != _pc.bytes.high) { uint16_t halfUpdatedPc = _pc.full; - _pc.full = _nextAddress.full; + _pc.full = nextAddress.full; throwaway_read(halfUpdatedPc); } break; @@ -1041,7 +1046,7 @@ template class Processor { break; } - if (isReadOperation(_nextBusOperation) && _ready_line_is_enabled) { + if (isReadOperation(nextBusOperation) && _ready_line_is_enabled) { _ready_is_active = true; } } @@ -1051,6 +1056,11 @@ template class Processor { _scheduleProgramProgramCounter = scheduleProgramProgramCounter; _operand = operand; _operation = operation; + _address = address; + _nextAddress = nextAddress; + _nextBusOperation = nextBusOperation; + _busAddress = busAddress; + _busValue = busValue; } } From 3038704977417968d48cd883541272e1f4d854e0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 21 Mar 2016 22:01:25 -0400 Subject: [PATCH 208/307] Attempted to introduce a lowpass filter to the graphics output, reverted 6502 optimisations as seemingly not working. --- OSBindings/Mac/Clock Signal/Info.plist | 4 +- Outputs/CRT/Internals/CRTOpenGL.cpp | 12 +- Processors/6502/CPU6502.hpp | 270 ++++++++++++------------- SignalProcessing/FIRFilter.cpp | 19 +- SignalProcessing/FIRFilter.hpp | 12 +- 5 files changed, 166 insertions(+), 151 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index d761ab5d5..7c527a7f2 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -35,7 +35,7 @@ CFBundleTypeName Electron/BBC Tape Image CFBundleTypeRole - Viewer + Editor LSItemContentTypes LSTypeIsPackage @@ -51,7 +51,7 @@ CFBundleTypeName Electron/BBC ROM Image CFBundleTypeRole - Editor + Viewer LSItemContentTypes LSTypeIsPackage diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 8c7104a3c..29d6b4f07 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -10,6 +10,7 @@ #include #include "CRTOpenGL.hpp" +#include "../../../SignalProcessing/FIRFilter.hpp" using namespace Outputs::CRT; @@ -342,6 +343,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "uniform float ticksPerFrame;" "uniform vec2 positionConversion;" "uniform vec2 scanNormal;" + "uniform vec3 filterCoefficients;" "const float shadowMaskMultiple = 600;" @@ -355,7 +357,9 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase - timestamp) / ticksPerFrame;" - "alpha = min(10.0 * exp(-age * 2.0), 1.0);" + "vec3 alphas = vec3(10.0 * exp((-age - 0.66) * 2.0), 10.0 * exp(-(age - 0.33) * 2.0), 10.0 * exp(-age * 2.0));" +// "alpha = min(10.0 * exp(-age * 2.0), 1.0);" + "alpha = dot(alphas, filterCoefficients);" "vec2 floatingPosition = (position / positionConversion) + lateral*scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" @@ -499,6 +503,7 @@ void OpenGLOutputBuilder::prepare_rgb_output_shader() GLint ticksPerFrameUniform = rgb_shader_program->get_uniform_location("ticksPerFrame"); GLint scanNormalUniform = rgb_shader_program->get_uniform_location("scanNormal"); GLint positionConversionUniform = rgb_shader_program->get_uniform_location("positionConversion"); + GLint filterCoefficients = rgb_shader_program->get_uniform_location("filterCoefficients"); glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); glUniform1i(shadowMaskTexIDUniform, 1); @@ -506,6 +511,11 @@ void OpenGLOutputBuilder::prepare_rgb_output_shader() glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); + SignalProcessing::FIRFilter filter(3, 3 * 50, 0, 25, SignalProcessing::FIRFilter::DefaultAttenuation); + float coefficients[3]; + filter.get_coefficients(coefficients); + glUniform3fv(filterCoefficients, 1, coefficients); + float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; float multiplier = (float)_cycles_per_line / ((float)_height_of_display * (float)_horizontal_scan_period); diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index c3c999ca0..be27bd198 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -550,13 +550,6 @@ template class Processor { // to date in this stack frame only); which saves some complicated addressing unsigned int scheduleProgramsReadPointer = _scheduleProgramsReadPointer; unsigned int scheduleProgramProgramCounter = _scheduleProgramProgramCounter; - uint8_t operand = _operand; - uint8_t operation = _operation; - RegisterPair address = _address; - RegisterPair nextAddress = _nextAddress; - BusOperation nextBusOperation = _nextBusOperation; - uint16_t busAddress = _busAddress; - uint8_t *busValue = _busValue; #define checkSchedule(op) \ if(!_scheduledPrograms[scheduleProgramsReadPointer]) {\ @@ -580,25 +573,25 @@ template class Processor { while(number_of_cycles > 0) { while (_ready_is_active && number_of_cycles > 0) { - number_of_cycles -= static_cast(this)->perform_bus_operation(BusOperation::Ready, busAddress, busValue); + number_of_cycles -= static_cast(this)->perform_bus_operation(BusOperation::Ready, _busAddress, _busValue); } while (!_ready_is_active && number_of_cycles > 0) { - if (nextBusOperation != BusOperation::None) { + if (_nextBusOperation != BusOperation::None) { _irq_request_history[0] = _irq_request_history[1]; _irq_request_history[1] = _irq_line_is_enabled && !_interruptFlag; - number_of_cycles -= static_cast(this)->perform_bus_operation(nextBusOperation, busAddress, busValue); - nextBusOperation = BusOperation::None; + number_of_cycles -= static_cast(this)->perform_bus_operation(_nextBusOperation, _busAddress, _busValue); + _nextBusOperation = BusOperation::None; } const MicroOp cycle = program[scheduleProgramProgramCounter]; scheduleProgramProgramCounter++; -#define read_op(val, addr) nextBusOperation = BusOperation::ReadOpcode; busAddress = addr; busValue = &val -#define read_mem(val, addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &val -#define throwaway_read(addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &throwaway_target -#define write_mem(val, addr) nextBusOperation = BusOperation::Write; busAddress = addr; busValue = &val +#define read_op(val, addr) _nextBusOperation = BusOperation::ReadOpcode; _busAddress = addr; _busValue = &val +#define read_mem(val, addr) _nextBusOperation = BusOperation::Read; _busAddress = addr; _busValue = &val +#define throwaway_read(addr) _nextBusOperation = BusOperation::Read; _busAddress = addr; _busValue = &throwaway_target +#define write_mem(val, addr) _nextBusOperation = BusOperation::Write; _busAddress = addr; _busValue = &val switch(cycle) { @@ -607,7 +600,7 @@ template class Processor { case CycleFetchOperation: { _lastOperationPC = _pc; _pc.full++; - read_op(operation, _lastOperationPC.full); + read_op(_operation, _lastOperationPC.full); // static int last_cycles_left_to_run = 0; // static bool printed_map[256] = {false}; @@ -622,11 +615,11 @@ template class Processor { } break; case CycleFetchOperand: - read_mem(operand, _pc.full); + read_mem(_operand, _pc.full); break; case OperationDecodeOperation: - decode_operation(operation); + decode_operation(_operation); break; case OperationMoveToNextProgram: @@ -646,7 +639,7 @@ template class Processor { case CycleIncPCPushPCH: _pc.full++; // deliberate fallthrough case CyclePushPCH: push(_pc.bytes.high); break; case CyclePushPCL: push(_pc.bytes.low); break; - case CyclePushOperand: push(operand); break; + case CyclePushOperand: push(_operand); break; case CyclePushA: push(_a); break; #undef push @@ -662,15 +655,15 @@ template class Processor { case CyclePullPCL: _s++; read_mem(_pc.bytes.low, _s | 0x100); break; case CyclePullPCH: _s++; read_mem(_pc.bytes.high, _s | 0x100); break; case CyclePullA: _s++; read_mem(_a, _s | 0x100); break; - case CyclePullOperand: _s++; read_mem(operand, _s | 0x100); break; - case OperationSetFlagsFromOperand: set_flags(operand); break; - case OperationSetOperandFromFlagsWithBRKSet: operand = get_flags() | Flag::Break; break; - case OperationSetOperandFromFlags: operand = get_flags(); break; + case CyclePullOperand: _s++; read_mem(_operand, _s | 0x100); break; + case OperationSetFlagsFromOperand: set_flags(_operand); break; + case OperationSetOperandFromFlagsWithBRKSet: _operand = get_flags() | Flag::Break; break; + case OperationSetOperandFromFlags: _operand = get_flags(); break; case OperationSetFlagsFromA: _zeroResult = _negativeResult = _a; break; case CycleIncrementPCAndReadStack: _pc.full++; throwaway_read(_s | 0x100); break; - case CycleReadPCLFromAddress: read_mem(_pc.bytes.low, address.full); break; - case CycleReadPCHFromAddress: address.bytes.low++; read_mem(_pc.bytes.high, address.full); break; + case CycleReadPCLFromAddress: read_mem(_pc.bytes.low, _address.full); break; + case CycleReadPCHFromAddress: _address.bytes.low++; read_mem(_pc.bytes.high, _address.full); break; case CycleReadAndIncrementPC: { uint16_t oldPC = _pc.full; @@ -693,45 +686,45 @@ template class Processor { #pragma mark - Bitwise - case OperationORA: _a |= operand; _negativeResult = _zeroResult = _a; break; - case OperationAND: _a &= operand; _negativeResult = _zeroResult = _a; break; - case OperationEOR: _a ^= operand; _negativeResult = _zeroResult = _a; break; + case OperationORA: _a |= _operand; _negativeResult = _zeroResult = _a; break; + case OperationAND: _a &= _operand; _negativeResult = _zeroResult = _a; break; + case OperationEOR: _a ^= _operand; _negativeResult = _zeroResult = _a; break; #pragma mark - Load and Store - case OperationLDA: _a = _negativeResult = _zeroResult = operand; break; - case OperationLDX: _x = _negativeResult = _zeroResult = operand; break; - case OperationLDY: _y = _negativeResult = _zeroResult = operand; break; - case OperationLAX: _a = _x = _negativeResult = _zeroResult = operand; break; + case OperationLDA: _a = _negativeResult = _zeroResult = _operand; break; + case OperationLDX: _x = _negativeResult = _zeroResult = _operand; break; + case OperationLDY: _y = _negativeResult = _zeroResult = _operand; break; + case OperationLAX: _a = _x = _negativeResult = _zeroResult = _operand; break; - case OperationSTA: operand = _a; break; - case OperationSTX: operand = _x; break; - case OperationSTY: operand = _y; break; - case OperationSAX: operand = _a & _x; break; - case OperationSHA: operand = _a & _x & (address.bytes.high+1); break; - case OperationSHX: operand = _x & (address.bytes.high+1); break; - case OperationSHY: operand = _y & (address.bytes.high+1); break; - case OperationSHS: _s = _a & _x; operand = _s & (address.bytes.high+1); break; + case OperationSTA: _operand = _a; break; + case OperationSTX: _operand = _x; break; + case OperationSTY: _operand = _y; break; + case OperationSAX: _operand = _a & _x; break; + case OperationSHA: _operand = _a & _x & (_address.bytes.high+1); break; + case OperationSHX: _operand = _x & (_address.bytes.high+1); break; + case OperationSHY: _operand = _y & (_address.bytes.high+1); break; + case OperationSHS: _s = _a & _x; _operand = _s & (_address.bytes.high+1); break; case OperationLXA: - _a = _x = (_a | 0xee) & operand; + _a = _x = (_a | 0xee) & _operand; _negativeResult = _zeroResult = _a; break; #pragma mark - Compare case OperationCMP: { - const uint16_t temp16 = _a - operand; + const uint16_t temp16 = _a - _operand; _negativeResult = _zeroResult = (uint8_t)temp16; _carryFlag = ((~temp16) >> 8)&1; } break; case OperationCPX: { - const uint16_t temp16 = _x - operand; + const uint16_t temp16 = _x - _operand; _negativeResult = _zeroResult = (uint8_t)temp16; _carryFlag = ((~temp16) >> 8)&1; } break; case OperationCPY: { - const uint16_t temp16 = _y - operand; + const uint16_t temp16 = _y - _operand; _negativeResult = _zeroResult = (uint8_t)temp16; _carryFlag = ((~temp16) >> 8)&1; } break; @@ -739,27 +732,27 @@ template class Processor { #pragma mark - BIT case OperationBIT: - _zeroResult = operand & _a; - _negativeResult = operand; - _overflowFlag = operand&Flag::Overflow; + _zeroResult = _operand & _a; + _negativeResult = _operand; + _overflowFlag = _operand&Flag::Overflow; break; #pragma mark ADC/SBC (and INS) case OperationINS: - operand++; // deliberate fallthrough + _operand++; // deliberate fallthrough case OperationSBC: if(_decimalFlag) { const uint16_t notCarry = _carryFlag ^ 0x1; - const uint16_t decimalResult = (uint16_t)_a - (uint16_t)operand - notCarry; + const uint16_t decimalResult = (uint16_t)_a - (uint16_t)_operand - notCarry; uint16_t temp16; - temp16 = (_a&0xf) - (operand&0xf) - notCarry; + temp16 = (_a&0xf) - (_operand&0xf) - notCarry; if(temp16 > 0xf) temp16 -= 0x6; temp16 = (temp16&0x0f) | ((temp16 > 0x0f) ? 0xfff0 : 0x00); - temp16 += (_a&0xf0) - (operand&0xf0); + temp16 += (_a&0xf0) - (_operand&0xf0); - _overflowFlag = ( ( (decimalResult^_a)&(~decimalResult^operand) )&0x80) >> 1; + _overflowFlag = ( ( (decimalResult^_a)&(~decimalResult^_operand) )&0x80) >> 1; _negativeResult = (uint8_t)temp16; _zeroResult = (uint8_t)decimalResult; @@ -769,20 +762,20 @@ template class Processor { _a = (uint8_t)temp16; break; } else { - operand = ~operand; + _operand = ~_operand; } // deliberate fallthrough case OperationADC: if(_decimalFlag) { - const uint16_t decimalResult = (uint16_t)_a + (uint16_t)operand + (uint16_t)_carryFlag; + const uint16_t decimalResult = (uint16_t)_a + (uint16_t)_operand + (uint16_t)_carryFlag; uint16_t temp16; - temp16 = (_a&0xf) + (operand&0xf) + _carryFlag; + temp16 = (_a&0xf) + (_operand&0xf) + _carryFlag; if(temp16 > 0x9) temp16 += 0x6; - temp16 = (temp16&0x0f) + ((temp16 > 0x0f) ? 0x10 : 0x00) + (_a&0xf0) + (operand&0xf0); + temp16 = (temp16&0x0f) + ((temp16 > 0x0f) ? 0x10 : 0x00) + (_a&0xf0) + (_operand&0xf0); - _overflowFlag = (( (decimalResult^_a)&(decimalResult^operand) )&0x80) >> 1; + _overflowFlag = (( (decimalResult^_a)&(decimalResult^_operand) )&0x80) >> 1; _negativeResult = (uint8_t)temp16; _zeroResult = (uint8_t)decimalResult; @@ -791,79 +784,79 @@ template class Processor { _carryFlag = (temp16 > 0xff) ? Flag::Carry : 0; _a = (uint8_t)temp16; } else { - const uint16_t decimalResult = (uint16_t)_a + (uint16_t)operand + (uint16_t)_carryFlag; - _overflowFlag = (( (decimalResult^_a)&(decimalResult^operand) )&0x80) >> 1; + const uint16_t decimalResult = (uint16_t)_a + (uint16_t)_operand + (uint16_t)_carryFlag; + _overflowFlag = (( (decimalResult^_a)&(decimalResult^_operand) )&0x80) >> 1; _negativeResult = _zeroResult = _a = (uint8_t)decimalResult; _carryFlag = (decimalResult >> 8)&1; } // fix up in case this was INS - if(cycle == OperationINS) operand = ~operand; + if(cycle == OperationINS) _operand = ~_operand; break; #pragma mark - Shifts and Rolls case OperationASL: - _carryFlag = operand >> 7; - operand <<= 1; - _negativeResult = _zeroResult = operand; + _carryFlag = _operand >> 7; + _operand <<= 1; + _negativeResult = _zeroResult = _operand; break; case OperationASO: - _carryFlag = operand >> 7; - operand <<= 1; - _a |= operand; + _carryFlag = _operand >> 7; + _operand <<= 1; + _a |= _operand; _negativeResult = _zeroResult = _a; break; case OperationROL: { - const uint8_t temp8 = (uint8_t)((operand << 1) | _carryFlag); - _carryFlag = operand >> 7; - operand = _negativeResult = _zeroResult = temp8; + const uint8_t temp8 = (uint8_t)((_operand << 1) | _carryFlag); + _carryFlag = _operand >> 7; + _operand = _negativeResult = _zeroResult = temp8; } break; case OperationRLA: { - const uint8_t temp8 = (uint8_t)((operand << 1) | _carryFlag); - _carryFlag = operand >> 7; - operand = temp8; - _a &= operand; + const uint8_t temp8 = (uint8_t)((_operand << 1) | _carryFlag); + _carryFlag = _operand >> 7; + _operand = temp8; + _a &= _operand; _negativeResult = _zeroResult = _a; } break; case OperationLSR: - _carryFlag = operand & 1; - operand >>= 1; - _negativeResult = _zeroResult = operand; + _carryFlag = _operand & 1; + _operand >>= 1; + _negativeResult = _zeroResult = _operand; break; case OperationLSE: - _carryFlag = operand & 1; - operand >>= 1; - _a ^= operand; + _carryFlag = _operand & 1; + _operand >>= 1; + _a ^= _operand; _negativeResult = _zeroResult = _a; break; case OperationASR: - _a &= operand; + _a &= _operand; _carryFlag = _a & 1; _a >>= 1; _negativeResult = _zeroResult = _a; break; case OperationROR: { - const uint8_t temp8 = (uint8_t)((operand >> 1) | (_carryFlag << 7)); - _carryFlag = operand & 1; - operand = _negativeResult = _zeroResult = temp8; + const uint8_t temp8 = (uint8_t)((_operand >> 1) | (_carryFlag << 7)); + _carryFlag = _operand & 1; + _operand = _negativeResult = _zeroResult = temp8; } break; case OperationRRA: { - const uint8_t temp8 = (uint8_t)((operand >> 1) | (_carryFlag << 7)); - _carryFlag = operand & 1; - operand = temp8; + const uint8_t temp8 = (uint8_t)((_operand >> 1) | (_carryFlag << 7)); + _carryFlag = _operand & 1; + _operand = temp8; } break; - case OperationDecrementOperand: operand--; break; - case OperationIncrementOperand: operand++; break; + case OperationDecrementOperand: _operand--; break; + case OperationIncrementOperand: _operand++; break; case OperationCLC: _carryFlag = 0; break; case OperationCLI: _interruptFlag = 0; break; @@ -874,112 +867,112 @@ template class Processor { case OperationSEI: _interruptFlag = Flag::Interrupt; break; case OperationSED: _decimalFlag = Flag::Decimal; break; - case OperationINC: operand++; _negativeResult = _zeroResult = operand; break; - case OperationDEC: operand--; _negativeResult = _zeroResult = operand; break; + case OperationINC: _operand++; _negativeResult = _zeroResult = _operand; break; + case OperationDEC: _operand--; _negativeResult = _zeroResult = _operand; break; case OperationINX: _x++; _negativeResult = _zeroResult = _x; break; case OperationDEX: _x--; _negativeResult = _zeroResult = _x; break; case OperationINY: _y++; _negativeResult = _zeroResult = _y; break; case OperationDEY: _y--; _negativeResult = _zeroResult = _y; break; case OperationANE: - _a = (_a | 0xee) & operand & _x; + _a = (_a | 0xee) & _operand & _x; _negativeResult = _zeroResult = _a; break; case OperationANC: - _a &= operand; + _a &= _operand; _negativeResult = _zeroResult = _a; _carryFlag = _a >> 7; break; case OperationLAS: - _a = _x = _s = _s & operand; + _a = _x = _s = _s & _operand; _negativeResult = _zeroResult = _a; break; #pragma mark - Addressing Mode Work case CycleAddXToAddressLow: - nextAddress.full = address.full + _x; - address.bytes.low = nextAddress.bytes.low; - if (address.bytes.high != nextAddress.bytes.high) { - throwaway_read(address.full); + _nextAddress.full = _address.full + _x; + _address.bytes.low = _nextAddress.bytes.low; + if (_address.bytes.high != _nextAddress.bytes.high) { + throwaway_read(_address.full); } break; case CycleAddXToAddressLowRead: - nextAddress.full = address.full + _x; - address.bytes.low = nextAddress.bytes.low; - throwaway_read(address.full); + _nextAddress.full = _address.full + _x; + _address.bytes.low = _nextAddress.bytes.low; + throwaway_read(_address.full); break; case CycleAddYToAddressLow: - nextAddress.full = address.full + _y; - address.bytes.low = nextAddress.bytes.low; - if (address.bytes.high != nextAddress.bytes.high) { - throwaway_read(address.full); + _nextAddress.full = _address.full + _y; + _address.bytes.low = _nextAddress.bytes.low; + if (_address.bytes.high != _nextAddress.bytes.high) { + throwaway_read(_address.full); } break; case CycleAddYToAddressLowRead: - nextAddress.full = address.full + _y; - address.bytes.low = nextAddress.bytes.low; - throwaway_read(address.full); + _nextAddress.full = _address.full + _y; + _address.bytes.low = _nextAddress.bytes.low; + throwaway_read(_address.full); break; case OperationCorrectAddressHigh: - address.full = nextAddress.full; + _address.full = _nextAddress.full; break; case CycleIncrementPCFetchAddressLowFromOperand: _pc.full++; - read_mem(address.bytes.low, operand); + read_mem(_address.bytes.low, _operand); break; case CycleAddXToOperandFetchAddressLow: - operand += _x; - read_mem(address.bytes.low, operand); + _operand += _x; + read_mem(_address.bytes.low, _operand); break; case CycleIncrementOperandFetchAddressHigh: - operand++; - read_mem(address.bytes.high, operand); + _operand++; + read_mem(_address.bytes.high, _operand); break; case CycleIncrementPCReadPCHLoadPCL: // deliberate fallthrough _pc.full++; case CycleReadPCHLoadPCL: { uint16_t oldPC = _pc.full; - _pc.bytes.low = operand; + _pc.bytes.low = _operand; read_mem(_pc.bytes.high, oldPC); } break; case CycleReadAddressHLoadAddressL: - address.bytes.low = operand; _pc.full++; - read_mem(address.bytes.high, _pc.full); + _address.bytes.low = _operand; _pc.full++; + read_mem(_address.bytes.high, _pc.full); break; case CycleLoadAddressAbsolute: { uint16_t nextPC = _pc.full+1; _pc.full += 2; - address.bytes.low = operand; - read_mem(address.bytes.high, nextPC); + _address.bytes.low = _operand; + read_mem(_address.bytes.high, nextPC); } break; case OperationLoadAddressZeroPage: _pc.full++; - address.full = operand; + _address.full = _operand; break; case CycleLoadAddessZeroX: _pc.full++; - address.full = (operand + _x)&0xff; - throwaway_read(operand); + _address.full = (_operand + _x)&0xff; + throwaway_read(_operand); break; case CycleLoadAddessZeroY: _pc.full++; - address.full = (operand + _y)&0xff; - throwaway_read(operand); + _address.full = (_operand + _y)&0xff; + throwaway_read(_operand); break; case OperationIncrementPC: _pc.full++; break; - case CycleFetchOperandFromAddress: read_mem(operand, address.full); break; - case CycleWriteOperandToAddress: write_mem(operand, address.full); break; - case OperationCopyOperandFromA: operand = _a; break; - case OperationCopyOperandToA: _a = operand; break; + case CycleFetchOperandFromAddress: read_mem(_operand, _address.full); break; + case CycleWriteOperandToAddress: write_mem(_operand, _address.full); break; + case OperationCopyOperandFromA: _operand = _a; break; + case OperationCopyOperandToA: _a = _operand; break; #pragma mark - Branching @@ -995,11 +988,11 @@ template class Processor { case OperationBEQ: BRA(!_zeroResult); break; case CycleAddSignedOperandToPC: - nextAddress.full = (uint16_t)(_pc.full + (int8_t)operand); - _pc.bytes.low = nextAddress.bytes.low; - if(nextAddress.bytes.high != _pc.bytes.high) { + _nextAddress.full = (uint16_t)(_pc.full + (int8_t)_operand); + _pc.bytes.low = _nextAddress.bytes.low; + if(_nextAddress.bytes.high != _pc.bytes.high) { uint16_t halfUpdatedPc = _pc.full; - _pc.full = nextAddress.full; + _pc.full = _nextAddress.full; throwaway_read(halfUpdatedPc); } break; @@ -1017,7 +1010,7 @@ template class Processor { case OperationARR: if(_decimalFlag) { - _a &= operand; + _a &= _operand; uint8_t unshiftedA = _a; _a = (uint8_t)((_a >> 1) | (_carryFlag << 7)); _zeroResult = _negativeResult = _a; @@ -1029,7 +1022,7 @@ template class Processor { if (_carryFlag) _a += 0x60; } else { - _a &= operand; + _a &= _operand; _a = (uint8_t)((_a >> 1) | (_carryFlag << 7)); _negativeResult = _zeroResult = _a; _carryFlag = (_a >> 6)&1; @@ -1039,14 +1032,14 @@ template class Processor { case OperationSBX: _x &= _a; - uint16_t difference = _x - operand; + uint16_t difference = _x - _operand; _x = (uint8_t)difference; _negativeResult = _zeroResult = _x; _carryFlag = ((difference >> 8)&1)^1; break; } - if (isReadOperation(nextBusOperation) && _ready_line_is_enabled) { + if (isReadOperation(_nextBusOperation) && _ready_line_is_enabled) { _ready_is_active = true; } } @@ -1054,13 +1047,6 @@ template class Processor { _cycles_left_to_run = number_of_cycles; _scheduleProgramsReadPointer = scheduleProgramsReadPointer; _scheduleProgramProgramCounter = scheduleProgramProgramCounter; - _operand = operand; - _operation = operation; - _address = address; - _nextAddress = nextAddress; - _nextBusOperation = nextBusOperation; - _busAddress = busAddress; - _busValue = busValue; } } diff --git a/SignalProcessing/FIRFilter.cpp b/SignalProcessing/FIRFilter.cpp index 86b1261bf..7eae4e2b8 100644 --- a/SignalProcessing/FIRFilter.cpp +++ b/SignalProcessing/FIRFilter.cpp @@ -37,7 +37,7 @@ using namespace SignalProcessing; #define kCSKaiserBesselFilterFixedShift 15 /* ino evaluates the 0th order Bessel function at a */ -static float csfilter_ino(float a) +float FIRFilter::ino(float a) { float d = 0.0f; float ds = 1.0f; @@ -54,7 +54,8 @@ static float csfilter_ino(float a) return s; } -static void csfilter_setIdealisedFilterResponse(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps) +//static void csfilter_setIdealisedFilterResponse(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps) +void FIRFilter::coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps) { /* calculate alpha, which is the Kaiser-Bessel window shape factor */ float a; // to take the place of alpha in the normal derivation @@ -73,13 +74,13 @@ static void csfilter_setIdealisedFilterResponse(short *filterCoefficients, float /* work out the right hand side of the filter coefficients */ unsigned int Np = (numberOfTaps - 1) / 2; - float I0 = csfilter_ino(a); + float I0 = ino(a); float NpSquared = (float)(Np * Np); for(unsigned int i = 0; i <= Np; i++) { filterCoefficientsFloat[Np + i] = A[i] * - csfilter_ino(a * sqrtf(1.0f - ((float)(i * i) / NpSquared) )) / + ino(a * sqrtf(1.0f - ((float)(i * i) / NpSquared) )) / I0; } @@ -106,6 +107,14 @@ static void csfilter_setIdealisedFilterResponse(short *filterCoefficients, float delete[] filterCoefficientsFloat; } +void FIRFilter::get_coefficients(float *coefficients) +{ + for(unsigned int i = 0; i < number_of_taps_; i++) + { + coefficients[i] = (float)filter_coefficients_[i] / kCSKaiserBesselFilterFixedMultiplier; + } +} + FIRFilter::FIRFilter(unsigned int number_of_taps, unsigned int input_sample_rate, float low_frequency, float high_frequency, float attenuation) { // we must be asked to filter based on an odd number of @@ -136,7 +145,7 @@ FIRFilter::FIRFilter(unsigned int number_of_taps, unsigned int input_sample_rate ) / iPi; } - csfilter_setIdealisedFilterResponse(filter_coefficients_, A, attenuation, number_of_taps_); + FIRFilter::coefficients_for_idealised_filter_response(filter_coefficients_, A, attenuation, number_of_taps_); /* clean up */ delete[] A; diff --git a/SignalProcessing/FIRFilter.hpp b/SignalProcessing/FIRFilter.hpp index 7fe5dff51..71f6c1b0c 100644 --- a/SignalProcessing/FIRFilter.hpp +++ b/SignalProcessing/FIRFilter.hpp @@ -42,7 +42,7 @@ class FIRFilter { @param input_sample_rate The sampling rate of the input signal. @param low_frequency The lowest frequency of signal to retain in the output. @param high_frequency The highest frequency of signal to retain in the output. - @param attenuation The attenuation of the output. + @param attenuation The attenuation of the discarded frequencies. */ FIRFilter(unsigned int number_of_taps, unsigned int input_sample_rate, float low_frequency, float high_frequency, float attenuation); @@ -73,9 +73,19 @@ class FIRFilter { #endif } + inline unsigned int get_number_of_taps() + { + return number_of_taps_; + } + + void get_coefficients(float *coefficients); + private: short *filter_coefficients_; unsigned int number_of_taps_; + + static void coefficients_for_idealised_filter_response(short *filterCoefficients, float *A, float attenuation, unsigned int numberOfTaps); + static float ino(float a); }; } From 8cd5d40e005032dad9aaecc4c54a9a9ca4b3df75 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 22 Mar 2016 21:16:32 -0400 Subject: [PATCH 209/307] Made an attempt to reduce the amount of data heading to the GPU. --- Machines/Electron/Electron.cpp | 111 +++++++++++-------------- Machines/Electron/Electron.hpp | 3 +- Outputs/CRT/Internals/CRTConstants.hpp | 1 - Outputs/CRT/Internals/CRTOpenGL.cpp | 5 +- Outputs/CRT/Internals/CRTOpenGL.hpp | 1 + 5 files changed, 54 insertions(+), 67 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index a3f16da98..35dfaf718 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -504,16 +504,12 @@ inline void Machine::start_pixel_line() } _currentScreenAddress = _startLineAddress; _current_pixel_column = 0; - - if(!_isBlankLine) - { - _currentLine = _crt->allocate_write_area(640); - } + _current_output_target = nullptr; } inline void Machine::end_pixel_line() { - if(!_isBlankLine) _crt->output_data(640, 1); + if(_current_output_target) _crt->output_data((unsigned int)((_current_output_target - _initial_output_target) * _current_output_divider), _current_output_divider); _current_character_row++; } @@ -525,6 +521,21 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) } else { + unsigned int divider = 0; + switch(_screen_mode) + { + case 0: case 3: divider = 1; break; + case 1: case 4: case 6: divider = 2; break; + case 2: case 5: divider = 4; break; + } + + if(!_current_output_target || divider != _current_output_divider) + { + if(_current_output_target) _crt->output_data((unsigned int)((_current_output_target - _initial_output_target) * _current_output_divider), _current_output_divider); + _current_output_divider = divider; + _initial_output_target = _current_output_target = _crt->allocate_write_area(640 / _current_output_divider); + } + while(number_of_cycles--) { if(!(_current_pixel_column&1) || _screen_mode < 4) @@ -543,40 +554,33 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) case 3: case 0: { - _currentLine[0] = _palette[(_last_pixel_byte&0x80) >> 4]; - _currentLine[1] = _palette[(_last_pixel_byte&0x40) >> 3]; - _currentLine[2] = _palette[(_last_pixel_byte&0x20) >> 2]; - _currentLine[3] = _palette[(_last_pixel_byte&0x10) >> 1]; - _currentLine[4] = _palette[(_last_pixel_byte&0x08) >> 0]; - _currentLine[5] = _palette[(_last_pixel_byte&0x04) << 1]; - _currentLine[6] = _palette[(_last_pixel_byte&0x02) << 2]; - _currentLine[7] = _palette[(_last_pixel_byte&0x01) << 3]; + _current_output_target[0] = _palette[(_last_pixel_byte&0x80) >> 4]; + _current_output_target[1] = _palette[(_last_pixel_byte&0x40) >> 3]; + _current_output_target[2] = _palette[(_last_pixel_byte&0x20) >> 2]; + _current_output_target[3] = _palette[(_last_pixel_byte&0x10) >> 1]; + _current_output_target[4] = _palette[(_last_pixel_byte&0x08) >> 0]; + _current_output_target[5] = _palette[(_last_pixel_byte&0x04) << 1]; + _current_output_target[6] = _palette[(_last_pixel_byte&0x02) << 2]; + _current_output_target[7] = _palette[(_last_pixel_byte&0x01) << 3]; + _current_output_target += 8; } break; case 1: { - _currentLine[0] = - _currentLine[1] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; - _currentLine[2] = - _currentLine[3] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; - _currentLine[4] = - _currentLine[5] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; - _currentLine[6] = - _currentLine[7] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; + _current_output_target[0] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; + _current_output_target[1] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; + _current_output_target[2] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; + _current_output_target[3] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; + _current_output_target += 4; } break; case 2: { - _currentLine[0] = - _currentLine[1] = - _currentLine[2] = - _currentLine[3] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x20) >> 3) | ((_last_pixel_byte&0x08) >> 2) | ((_last_pixel_byte&0x02) >> 1)]; - _currentLine[4] = - _currentLine[5] = - _currentLine[6] = - _currentLine[7] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x10) >> 2) | ((_last_pixel_byte&0x04) >> 1) | ((_last_pixel_byte&0x01) >> 0)]; + _current_output_target[0] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x20) >> 3) | ((_last_pixel_byte&0x08) >> 2) | ((_last_pixel_byte&0x02) >> 1)]; + _current_output_target[1] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x10) >> 2) | ((_last_pixel_byte&0x04) >> 1) | ((_last_pixel_byte&0x01) >> 0)]; + _current_output_target += 2; } break; @@ -585,26 +589,19 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) { if(_current_pixel_column&1) { - _currentLine[0] = - _currentLine[1] = _palette[(_last_pixel_byte&0x08) >> 0]; - _currentLine[2] = - _currentLine[3] = _palette[(_last_pixel_byte&0x04) << 1]; - _currentLine[4] = - _currentLine[5] = _palette[(_last_pixel_byte&0x02) << 2]; - _currentLine[6] = - _currentLine[7] = _palette[(_last_pixel_byte&0x01) << 3]; + _current_output_target[0] = _palette[(_last_pixel_byte&0x08) >> 0]; + _current_output_target[1] = _palette[(_last_pixel_byte&0x04) << 1]; + _current_output_target[2] = _palette[(_last_pixel_byte&0x02) << 2]; + _current_output_target[3] = _palette[(_last_pixel_byte&0x01) << 3]; } else { - _currentLine[0] = - _currentLine[1] = _palette[(_last_pixel_byte&0x80) >> 4]; - _currentLine[2] = - _currentLine[3] = _palette[(_last_pixel_byte&0x40) >> 3]; - _currentLine[4] = - _currentLine[5] = _palette[(_last_pixel_byte&0x20) >> 2]; - _currentLine[6] = - _currentLine[7] = _palette[(_last_pixel_byte&0x10) >> 1]; + _current_output_target[0] = _palette[(_last_pixel_byte&0x80) >> 4]; + _current_output_target[1] = _palette[(_last_pixel_byte&0x40) >> 3]; + _current_output_target[2] = _palette[(_last_pixel_byte&0x20) >> 2]; + _current_output_target[3] = _palette[(_last_pixel_byte&0x10) >> 1]; } + _current_output_target += 4; } break; @@ -612,32 +609,20 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) { if(_current_pixel_column&1) { - _currentLine[0] = - _currentLine[1] = - _currentLine[2] = - _currentLine[3] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; - _currentLine[4] = - _currentLine[5] = - _currentLine[6] = - _currentLine[7] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; + _current_output_target[0] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; + _current_output_target[1] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; } else { - _currentLine[0] = - _currentLine[1] = - _currentLine[2] = - _currentLine[3] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; - _currentLine[4] = - _currentLine[5] = - _currentLine[6] = - _currentLine[7] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; + _current_output_target[0] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; + _current_output_target[1] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; } + _current_output_target += 2; } break; } _current_pixel_column++; - _currentLine += 8; } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 304e7fc81..6148cfbab 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -197,7 +197,8 @@ class Machine: public CPU6502::Processor, Tape::Delegate { bool _isBlankLine; // CRT output - uint8_t *_currentLine; + uint8_t *_current_output_target, *_initial_output_target; + unsigned int _current_output_divider; // Tape. Tape _tape; diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index f8480d437..46d4d7209 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -43,7 +43,6 @@ const int IntermediateBufferHeight = 2048; // Some internal const GLsizeiptr InputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize -const GLsizeiptr InputTextureBufferDataSize = InputBufferBuilderWidth*InputBufferBuilderHeight; // Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 29d6b4f07..c6c73a6dd 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -87,7 +87,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out GLenum format = formatForDepth(_buffer_builder->bytes_per_pixel); glGenBuffers(1, &_input_texture_array); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _input_texture_array); - glBufferData(GL_PIXEL_UNPACK_BUFFER, InputTextureBufferDataSize, NULL, GL_STREAM_DRAW); + _input_texture_array_size = (GLsizeiptr)(InputBufferBuilderWidth * InputBufferBuilderHeight * _buffer_builder->bytes_per_pixel); + glBufferData(GL_PIXEL_UNPACK_BUFFER, _input_texture_array_size, NULL, GL_STREAM_DRAW); glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, nullptr); @@ -219,7 +220,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // drawing commands having been issued, reclaim the array buffer pointer _buffer_builder->move_to_new_line(); _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, InputTextureBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _output_mutex->unlock(); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 52ebc154a..c68402267 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -207,6 +207,7 @@ class OpenGLOutputBuilder { uint8_t *_input_texture_data; GLuint _input_texture_array; GLsync _input_texture_sync; + GLsizeiptr _input_texture_array_size; uint8_t *_output_buffer_data; size_t _output_buffer_data_pointer; From 3ca58b2a5728930492fefdb7a0c487f0f0d86183 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 22 Mar 2016 22:07:30 -0400 Subject: [PATCH 210/307] Attemted further to diminish jumpiness. --- Machines/Electron/Electron.cpp | 16 ++++++---------- Outputs/CRT/Internals/CRTConstants.hpp | 2 +- Outputs/CRT/Internals/CRTOpenGL.cpp | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 35dfaf718..703c505f7 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -82,15 +82,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { - // If we're still before the display will start to be painted, or the address is - // less than both the current line address and 0x3000, (the minimum screen mode - // base address) then there's no way this write can affect the current frame. Sp - // no need to flush the display. Otherwise, output up until now so that any - // write doesn't have retroactive effect on the video output. -// if(!( -// (_fieldCycles < first_graphics_line * cycles_per_line) || -// (address < _startLineAddress && address < 0x3000) -// )) + if( + ( + ((_frameCycles >= first_graphics_line * cycles_per_line) && (_frameCycles < (first_graphics_line + 256) * cycles_per_line)) || + ((_frameCycles >= (first_graphics_line + field_divider_line) * cycles_per_line) && (_frameCycles < (first_graphics_line + 256 + field_divider_line) * cycles_per_line)) + ) + ) update_display(); _ram[address] = *value; @@ -101,7 +98,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin cycles += 1 + (_frameCycles&1); if(_screen_mode < 4) { - update_display(); const int current_line = graphics_line(_frameCycles + (_frameCycles&1)); const int current_column = graphics_column(_frameCycles + (_frameCycles&1)); if(current_line < 256 && current_column < 80 && !_isBlankLine) diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index 46d4d7209..ae9f4abc7 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -48,7 +48,7 @@ const GLsizeiptr InputVertexBufferDataSize = 262080; // a multiple of 6 * Output // Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track // run age; that therefore creates a discrete number of fields that are stored. This number should be the // number of historic fields that are required fully to -const int NumberOfFields = 3; +const int NumberOfFields = 4; } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index c6c73a6dd..de9159273 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -358,7 +358,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase - timestamp) / ticksPerFrame;" - "vec3 alphas = vec3(10.0 * exp((-age - 0.66) * 2.0), 10.0 * exp(-(age - 0.33) * 2.0), 10.0 * exp(-age * 2.0));" + "vec3 alphas = vec3(10.0 * exp((-age - 1.33) * 2.0), 10.0 * exp(-(age - 0.66) * 2.0), 10.0 * exp(-age * 2.0));" // "alpha = min(10.0 * exp(-age * 2.0), 1.0);" "alpha = dot(alphas, filterCoefficients);" @@ -512,7 +512,7 @@ void OpenGLOutputBuilder::prepare_rgb_output_shader() glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); - SignalProcessing::FIRFilter filter(3, 3 * 50, 0, 25, SignalProcessing::FIRFilter::DefaultAttenuation); + SignalProcessing::FIRFilter filter(3, 6 * 50, 0, 25, SignalProcessing::FIRFilter::DefaultAttenuation); float coefficients[3]; filter.get_coefficients(coefficients); glUniform3fv(filterCoefficients, 1, coefficients); From 738186e3234ada7150bcf7cdb985b8ae1285d602 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 24 Mar 2016 19:17:44 -0400 Subject: [PATCH 211/307] Edging back towards shifting things to the stack. --- Processors/6502/CPU6502.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index be27bd198..7ab2d1a59 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -550,6 +550,7 @@ template class Processor { // to date in this stack frame only); which saves some complicated addressing unsigned int scheduleProgramsReadPointer = _scheduleProgramsReadPointer; unsigned int scheduleProgramProgramCounter = _scheduleProgramProgramCounter; + uint8_t operation = _operation; #define checkSchedule(op) \ if(!_scheduledPrograms[scheduleProgramsReadPointer]) {\ @@ -600,7 +601,7 @@ template class Processor { case CycleFetchOperation: { _lastOperationPC = _pc; _pc.full++; - read_op(_operation, _lastOperationPC.full); + read_op(operation, _lastOperationPC.full); // static int last_cycles_left_to_run = 0; // static bool printed_map[256] = {false}; @@ -619,7 +620,7 @@ template class Processor { break; case OperationDecodeOperation: - decode_operation(_operation); + decode_operation(operation); break; case OperationMoveToNextProgram: @@ -1047,6 +1048,7 @@ template class Processor { _cycles_left_to_run = number_of_cycles; _scheduleProgramsReadPointer = scheduleProgramsReadPointer; _scheduleProgramProgramCounter = scheduleProgramProgramCounter; + _operation = operation; } } From f1caf62ff2bf05deb222e4936b9df834021165e7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 24 Mar 2016 19:31:41 -0400 Subject: [PATCH 212/307] Continuing the switch around. --- Processors/6502/CPU6502.hpp | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 7ab2d1a59..c777f4456 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -550,7 +550,7 @@ template class Processor { // to date in this stack frame only); which saves some complicated addressing unsigned int scheduleProgramsReadPointer = _scheduleProgramsReadPointer; unsigned int scheduleProgramProgramCounter = _scheduleProgramProgramCounter; - uint8_t operation = _operation; + RegisterPair nextAddress = _nextAddress; #define checkSchedule(op) \ if(!_scheduledPrograms[scheduleProgramsReadPointer]) {\ @@ -601,7 +601,7 @@ template class Processor { case CycleFetchOperation: { _lastOperationPC = _pc; _pc.full++; - read_op(operation, _lastOperationPC.full); + read_op(_operation, _lastOperationPC.full); // static int last_cycles_left_to_run = 0; // static bool printed_map[256] = {false}; @@ -620,7 +620,7 @@ template class Processor { break; case OperationDecodeOperation: - decode_operation(operation); + decode_operation(_operation); break; case OperationMoveToNextProgram: @@ -894,31 +894,31 @@ template class Processor { #pragma mark - Addressing Mode Work case CycleAddXToAddressLow: - _nextAddress.full = _address.full + _x; - _address.bytes.low = _nextAddress.bytes.low; - if (_address.bytes.high != _nextAddress.bytes.high) { + nextAddress.full = _address.full + _x; + _address.bytes.low = nextAddress.bytes.low; + if (_address.bytes.high != nextAddress.bytes.high) { throwaway_read(_address.full); } break; case CycleAddXToAddressLowRead: - _nextAddress.full = _address.full + _x; - _address.bytes.low = _nextAddress.bytes.low; + nextAddress.full = _address.full + _x; + _address.bytes.low = nextAddress.bytes.low; throwaway_read(_address.full); break; case CycleAddYToAddressLow: - _nextAddress.full = _address.full + _y; - _address.bytes.low = _nextAddress.bytes.low; - if (_address.bytes.high != _nextAddress.bytes.high) { + nextAddress.full = _address.full + _y; + _address.bytes.low = nextAddress.bytes.low; + if (_address.bytes.high != nextAddress.bytes.high) { throwaway_read(_address.full); } break; case CycleAddYToAddressLowRead: - _nextAddress.full = _address.full + _y; - _address.bytes.low = _nextAddress.bytes.low; + nextAddress.full = _address.full + _y; + _address.bytes.low = nextAddress.bytes.low; throwaway_read(_address.full); break; case OperationCorrectAddressHigh: - _address.full = _nextAddress.full; + _address.full = nextAddress.full; break; case CycleIncrementPCFetchAddressLowFromOperand: _pc.full++; @@ -989,11 +989,11 @@ template class Processor { case OperationBEQ: BRA(!_zeroResult); break; case CycleAddSignedOperandToPC: - _nextAddress.full = (uint16_t)(_pc.full + (int8_t)_operand); - _pc.bytes.low = _nextAddress.bytes.low; - if(_nextAddress.bytes.high != _pc.bytes.high) { + nextAddress.full = (uint16_t)(_pc.full + (int8_t)_operand); + _pc.bytes.low = nextAddress.bytes.low; + if(nextAddress.bytes.high != _pc.bytes.high) { uint16_t halfUpdatedPc = _pc.full; - _pc.full = _nextAddress.full; + _pc.full = nextAddress.full; throwaway_read(halfUpdatedPc); } break; @@ -1048,7 +1048,7 @@ template class Processor { _cycles_left_to_run = number_of_cycles; _scheduleProgramsReadPointer = scheduleProgramsReadPointer; _scheduleProgramProgramCounter = scheduleProgramProgramCounter; - _operation = operation; + _nextAddress = nextAddress; } } From a6ef78862cce8a423bfb5af29e4e13020d02e7dd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 24 Mar 2016 22:20:00 -0400 Subject: [PATCH 213/307] This'll probably do for now. --- Processors/6502/CPU6502.hpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index c777f4456..62bba1ef9 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -551,6 +551,9 @@ template class Processor { unsigned int scheduleProgramsReadPointer = _scheduleProgramsReadPointer; unsigned int scheduleProgramProgramCounter = _scheduleProgramProgramCounter; RegisterPair nextAddress = _nextAddress; + BusOperation nextBusOperation = _nextBusOperation; + uint16_t busAddress = _busAddress; + uint8_t *busValue = _busValue; #define checkSchedule(op) \ if(!_scheduledPrograms[scheduleProgramsReadPointer]) {\ @@ -574,25 +577,25 @@ template class Processor { while(number_of_cycles > 0) { while (_ready_is_active && number_of_cycles > 0) { - number_of_cycles -= static_cast(this)->perform_bus_operation(BusOperation::Ready, _busAddress, _busValue); + number_of_cycles -= static_cast(this)->perform_bus_operation(BusOperation::Ready, busAddress, busValue); } while (!_ready_is_active && number_of_cycles > 0) { - if (_nextBusOperation != BusOperation::None) { + if (nextBusOperation != BusOperation::None) { _irq_request_history[0] = _irq_request_history[1]; _irq_request_history[1] = _irq_line_is_enabled && !_interruptFlag; - number_of_cycles -= static_cast(this)->perform_bus_operation(_nextBusOperation, _busAddress, _busValue); - _nextBusOperation = BusOperation::None; + number_of_cycles -= static_cast(this)->perform_bus_operation(nextBusOperation, busAddress, busValue); + nextBusOperation = BusOperation::None; } const MicroOp cycle = program[scheduleProgramProgramCounter]; scheduleProgramProgramCounter++; -#define read_op(val, addr) _nextBusOperation = BusOperation::ReadOpcode; _busAddress = addr; _busValue = &val -#define read_mem(val, addr) _nextBusOperation = BusOperation::Read; _busAddress = addr; _busValue = &val -#define throwaway_read(addr) _nextBusOperation = BusOperation::Read; _busAddress = addr; _busValue = &throwaway_target -#define write_mem(val, addr) _nextBusOperation = BusOperation::Write; _busAddress = addr; _busValue = &val +#define read_op(val, addr) nextBusOperation = BusOperation::ReadOpcode; busAddress = addr; busValue = &val +#define read_mem(val, addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &val +#define throwaway_read(addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &throwaway_target +#define write_mem(val, addr) nextBusOperation = BusOperation::Write; busAddress = addr; busValue = &val switch(cycle) { @@ -1040,7 +1043,7 @@ template class Processor { break; } - if (isReadOperation(_nextBusOperation) && _ready_line_is_enabled) { + if (isReadOperation(nextBusOperation) && _ready_line_is_enabled) { _ready_is_active = true; } } @@ -1049,6 +1052,9 @@ template class Processor { _scheduleProgramsReadPointer = scheduleProgramsReadPointer; _scheduleProgramProgramCounter = scheduleProgramProgramCounter; _nextAddress = nextAddress; + _nextBusOperation = nextBusOperation; + _busAddress = busAddress; + _busValue = busValue; } } From 729fddea1c63466e9052a8ac530c14066eb6bfeb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Apr 2016 19:39:01 -0400 Subject: [PATCH 214/307] Switched to integer textures, likely to simplify for most use cases. --- Machines/Electron/Electron.cpp | 5 ++--- Outputs/CRT/Internals/CRTOpenGL.cpp | 34 +++++++++++++++++++---------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 703c505f7..0274155cf 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -61,11 +61,10 @@ void Machine::setup_output() _crt->set_rgb_sampling_function( "vec4 rgb_sample(vec2 coordinate)" "{" - "float texValue = texture(texID, coordinate).r;" - "return vec4(step(4.0/256.0, mod(texValue, 8.0/256.0)), step(2.0/256.0, mod(texValue, 4.0/256.0)), step(1.0/256.0, mod(texValue, 2.0/256.0)), 1.0);" + "uint texValue = texture(texID, coordinate).r;" + "return vec4(texValue & 4u, texValue & 2u, texValue & 1u, 1.0);" "}"); _crt->set_output_device(Outputs::CRT::Monitor); -// _crt->set_visible_area(Outputs::Rect(0.23108f, 0.0f, 0.8125f, 0.98f)); //1875 _speaker.set_input_rate(125000); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index de9159273..1c44a03ce 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -58,15 +58,27 @@ OpenGLOutputBuilder::~OpenGLOutputBuilder() free(_rgb_shader); } -static GLenum formatForDepth(size_t depth) +static const GLint internalFormatForDepth(size_t depth) { switch(depth) { default: return GL_FALSE; - case 1: return GL_RED; - case 2: return GL_RG; - case 3: return GL_RGB; - case 4: return GL_RGBA; + 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; } } @@ -84,13 +96,12 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - GLenum format = formatForDepth(_buffer_builder->bytes_per_pixel); glGenBuffers(1, &_input_texture_array); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _input_texture_array); _input_texture_array_size = (GLsizeiptr)(InputBufferBuilderWidth * InputBufferBuilderHeight * _buffer_builder->bytes_per_pixel); glBufferData(GL_PIXEL_UNPACK_BUFFER, _input_texture_array_size, NULL, GL_STREAM_DRAW); - glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, InputBufferBuilderWidth, InputBufferBuilderHeight, 0, format, GL_UNSIGNED_BYTE, nullptr); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(_buffer_builder->bytes_per_pixel), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, nullptr); prepare_composite_input_shader(); prepare_rgb_output_shader(); @@ -137,13 +148,12 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) // { // glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); - GLenum format = formatForDepth(_buffer_builder->bytes_per_pixel); if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, InputBufferBuilderWidth, (GLint)(InputBufferBuilderHeight - _buffer_builder->last_uploaded_line), - format, GL_UNSIGNED_BYTE, + formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->bytes_per_pixel)); _buffer_builder->last_uploaded_line = 0; } @@ -153,7 +163,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glTexSubImage2D(GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, InputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), - format, GL_UNSIGNED_BYTE, + formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->bytes_per_pixel)); _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; } @@ -303,7 +313,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "out vec4 fragColour;" - "uniform sampler2D texID;" + "uniform usampler2D texID;" "\n%s\n" @@ -396,7 +406,7 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "out vec4 fragColour;" - "uniform sampler2D texID;" + "uniform usampler2D texID;" "uniform sampler2D shadowMaskTexID;" "\n%s\n" From 4595741ab069decd82549be9c0f7ba988cdb38ab Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Apr 2016 20:22:33 -0400 Subject: [PATCH 215/307] Made an attempt not to interrupt the display link queue too much when emulation is running slowly. --- OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 3b04dd760..3514e9001 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -13,6 +13,7 @@ @implementation CSOpenGLView { CVDisplayLinkRef _displayLink; uint32_t _updateIsOngoing; + int _missedFrames; } - (void)prepareOpenGL @@ -55,6 +56,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt const uint32_t activityMask = 0x01; if(!OSAtomicTestAndSet(activityMask, &_updateIsOngoing)) { + _missedFrames = 0; CVTimeStamp time = *now; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self.delegate openGLView:self didUpdateToTime:time]; @@ -64,7 +66,14 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt } else { - [self drawViewOnlyIfDirty:YES]; + _missedFrames++; + if(_missedFrames == 10) + { + _missedFrames = 0; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + [self drawViewOnlyIfDirty:YES]; + }); + } } } From c0cd1ed89e101d3fad709a20395ae582649cec4c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Apr 2016 21:57:27 -0400 Subject: [PATCH 216/307] Made an attempt to consolidate timestamp bases to a single vector and hence to lump all geometry into one or two calls, with no repetitive setting of a uniform. I'm not sure the result is correct yet. --- Outputs/CRT/CRT.cpp | 2 + Outputs/CRT/Internals/CRTConstants.hpp | 1 + Outputs/CRT/Internals/CRTOpenGL.cpp | 80 +++++++++++++------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 5 ++ 4 files changed, 47 insertions(+), 41 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index c1776df9d..fa783a679 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -98,6 +98,7 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, #define output_tex_x(v) (*(uint16_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTexCoord + 0]) #define output_tex_y(v) (*(uint16_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTexCoord + 2]) #define output_lateral(v) next_run[OutputVertexSize*v + OutputVertexOffsetOfLateral] +#define output_frame_id(v) next_run[OutputVertexSize*v + OutputVertexOffsetOfFrameID] #define output_timestamp(v) (*(uint32_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTimestamp]) #define input_input_position_x(v) (*(uint16_t *)&next_run[InputVertexSize*v + InputVertexOffsetOfInputPosition + 0]) @@ -153,6 +154,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi output_tex_y(0) = output_tex_y(1) = output_tex_y(2) = output_tex_y(3) = output_tex_y(4) = output_tex_y(5) = tex_y; output_lateral(0) = output_lateral(1) = output_lateral(3) = 0; output_lateral(2) = output_lateral(4) = output_lateral(5) = 1; + output_frame_id(0) = output_frame_id(1) = output_frame_id(2) = output_frame_id(3) = output_frame_id(4) = output_frame_id(5) = (uint8_t)_openGL_output_builder->get_current_field(); } else { diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index ae9f4abc7..0aafc5986 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -21,6 +21,7 @@ const size_t OutputVertexOffsetOfPosition = 0; const size_t OutputVertexOffsetOfTexCoord = 4; const size_t OutputVertexOffsetOfTimestamp = 8; const size_t OutputVertexOffsetOfLateral = 12; +const size_t OutputVertexOffsetOfFrameID = 13; const size_t OutputVertexSize = 16; diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 1c44a03ce..86075d479 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -150,7 +150,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) { - glTexSubImage2D(GL_TEXTURE_2D, 0, + glTexSubImage2D( GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, InputBufferBuilderWidth, (GLint)(InputBufferBuilderHeight - _buffer_builder->last_uploaded_line), formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, @@ -160,7 +160,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out if(_buffer_builder->_next_write_y_position > _buffer_builder->last_uploaded_line) { - glTexSubImage2D(GL_TEXTURE_2D, 0, + glTexSubImage2D( GL_TEXTURE_2D, 0, 0, (GLint)_buffer_builder->last_uploaded_line, InputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, @@ -200,35 +200,37 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // draw all sitting frames unsigned int run = (unsigned int)_run_write_pointer; GLint total_age = 0; + float timestampBases[4]; + size_t start = 0, count = 0; for(int c = 0; c < NumberOfFields; c++) { - // update the total age at the start of this set of runs total_age += _run_builders[run]->duration; - - if(_run_builders[run]->amount_of_data > 0) - { - // draw - glUniform1f(timestampBaseUniform, (GLfloat)total_age); - GLsizei count = (GLsizei)(_run_builders[run]->amount_of_data / InputVertexSize); - GLsizei max_count = (GLsizei)((InputVertexBufferDataSize - _run_builders[run]->start) / InputVertexSize); - if(count < max_count) - { - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(_run_builders[run]->start / InputVertexSize), count); - } - else - { - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(_run_builders[run]->start / InputVertexSize), max_count); - glDrawArrays(GL_TRIANGLE_STRIP, 0, count - max_count); - } - } - - // advance back in time + timestampBases[run] = (float)total_age; + count += _run_builders[run]->amount_of_data; + start = _run_builders[run]->start; run = (run - 1 + NumberOfFields) % NumberOfFields; } + glUniform4fv(timestampBaseUniform, 1, timestampBases); + + if(count > 0) + { + // draw + GLsizei primitive_count = (GLsizei)(count / InputVertexSize); + GLsizei max_count = (GLsizei)((InputVertexBufferDataSize - start) / InputVertexSize); + if(primitive_count < max_count) + { + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(start / InputVertexSize), primitive_count); + } + else + { + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(start / InputVertexSize), max_count); + glDrawArrays(GL_TRIANGLE_STRIP, 0, primitive_count - max_count); + } + } } // drawing commands having been issued, reclaim the array buffer pointer - _buffer_builder->move_to_new_line(); +// _buffer_builder->move_to_new_line(); _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _output_mutex->unlock(); @@ -282,8 +284,6 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "in vec2 phaseAndAmplitude;" "in float phaseTime;" - "uniform vec2 outputTextureSize;" - "uniform vec2 inputTextureSize;" "uniform float phaseCyclesPerTick;" "out vec2 inputPositionVarying;" @@ -339,7 +339,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "in vec2 position;" "in vec2 srcCoordinates;" - "in float lateral;" + "in vec2 lateralAndTimestampBaseOffset;" "in float timestamp;" "uniform vec2 boundsOrigin;" @@ -349,30 +349,34 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "out vec2 shadowMaskCoordinates;" "out float alpha;" - "uniform vec2 textureSize;" - "uniform float timestampBase;" + "uniform vec4 timestampBase;" "uniform float ticksPerFrame;" "uniform vec2 positionConversion;" "uniform vec2 scanNormal;" "uniform vec3 filterCoefficients;" + "uniform usampler2D texID;" + "uniform sampler2D shadowMaskTexID;" + "const float shadowMaskMultiple = 600;" "out vec2 srcCoordinatesVarying;" "void main(void)" "{" - "lateralVarying = lateral + 1.0707963267949;" + "lateralVarying = lateralAndTimestampBaseOffset.x + 1.0707963267949;" "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);" + "ivec2 textureSize = textureSize(texID, 0);" "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" - "float age = (timestampBase - timestamp) / ticksPerFrame;" - "vec3 alphas = vec3(10.0 * exp((-age - 1.33) * 2.0), 10.0 * exp(-(age - 0.66) * 2.0), 10.0 * exp(-age * 2.0));" + "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" + "alpha = 10.0 * exp(-age * 2.0);" +// "vec3 alphas = vec3(10.0 * exp((-age - 1.33) * 2.0), 10.0 * exp(-(age - 0.66) * 2.0), 10.0 * exp(-age * 2.0));" // "alpha = min(10.0 * exp(-age * 2.0), 1.0);" - "alpha = dot(alphas, filterCoefficients);" +// "alpha = dot(alphas, filterCoefficients);" - "vec2 floatingPosition = (position / positionConversion) + lateral*scanNormal;" + "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);" "}"); @@ -440,13 +444,9 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() composite_input_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); GLint texIDUniform = composite_input_shader_program->get_uniform_location("texID"); - GLint inputTextureSizeUniform = composite_input_shader_program->get_uniform_location("inputTextureSize"); - GLint outputTextureSizeUniform = composite_input_shader_program->get_uniform_location("outputTextureSize"); GLint phaseCyclesPerTickUniform = composite_input_shader_program->get_uniform_location("phaseCyclesPerTick"); glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); - glUniform2f(outputTextureSizeUniform, IntermediateBufferWidth, IntermediateBufferHeight); - glUniform2f(inputTextureSizeUniform, InputBufferBuilderWidth, InputBufferBuilderHeight); glUniform1f(phaseCyclesPerTickUniform, (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line)); } free(vertex_shader); @@ -510,7 +510,6 @@ void OpenGLOutputBuilder::prepare_rgb_output_shader() GLint texIDUniform = rgb_shader_program->get_uniform_location("texID"); GLint shadowMaskTexIDUniform = rgb_shader_program->get_uniform_location("shadowMaskTexID"); - GLint textureSizeUniform = rgb_shader_program->get_uniform_location("textureSize"); GLint ticksPerFrameUniform = rgb_shader_program->get_uniform_location("ticksPerFrame"); GLint scanNormalUniform = rgb_shader_program->get_uniform_location("scanNormal"); GLint positionConversionUniform = rgb_shader_program->get_uniform_location("positionConversion"); @@ -518,7 +517,6 @@ void OpenGLOutputBuilder::prepare_rgb_output_shader() glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); glUniform1i(shadowMaskTexIDUniform, 1); - glUniform2f(textureSizeUniform, InputBufferBuilderWidth, InputBufferBuilderHeight); glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); @@ -545,7 +543,7 @@ void OpenGLOutputBuilder::prepare_output_vertex_array() { GLint positionAttribute = rgb_shader_program->get_attrib_location("position"); GLint textureCoordinatesAttribute = rgb_shader_program->get_attrib_location("srcCoordinates"); - GLint lateralAttribute = rgb_shader_program->get_attrib_location("lateral"); + GLint lateralAttribute = rgb_shader_program->get_attrib_location("lateralAndTimestampBaseOffset"); GLint timestampAttribute = rgb_shader_program->get_attrib_location("timestamp"); glEnableVertexAttribArray((GLuint)positionAttribute); @@ -557,7 +555,7 @@ void OpenGLOutputBuilder::prepare_output_vertex_array() glVertexAttribPointer((GLuint)positionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfPosition); glVertexAttribPointer((GLuint)textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfTexCoord); glVertexAttribPointer((GLuint)timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfTimestamp); - glVertexAttribPointer((GLuint)lateralAttribute, 1, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfLateral); + glVertexAttribPointer((GLuint)lateralAttribute, 2, GL_UNSIGNED_BYTE, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfLateral); } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index c68402267..ce621c9ad 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -164,6 +164,11 @@ class OpenGLOutputBuilder { _output_mutex->unlock(); } + inline int get_current_field() + { + return _run_write_pointer; + } + inline uint8_t *allocate_write_area(size_t required_length) { _output_mutex->lock(); From 7838507a7ac946c0c9cc1ac9aaa6659f625e46f2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Apr 2016 22:22:19 -0400 Subject: [PATCH 217/307] Simplified sound generation. --- Machines/Electron/Electron.cpp | 35 ++++++++++++++++++---------------- Machines/Electron/Electron.hpp | 1 - 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 0274155cf..75f160022 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -66,7 +66,11 @@ void Machine::setup_output() "}"); _crt->set_output_device(Outputs::CRT::Monitor); - _speaker.set_input_rate(125000); + // The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; + // nevertheless sampling only at 62500Hz may introduce aliasing errors as setting the speaker on or off can happen on + // any 2Mhz cycle, and I've no idea whether it has an immediate effect or happens only on the next clock. If it turns + // out that the former is the case, I'll need to turn this up to 2000000. + _speaker.set_input_rate(62500); } unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) @@ -468,8 +472,8 @@ inline void Machine::update_audio() { int difference = (int)_frameCycles - _audioOutputPosition; _audioOutputPosition = (int)_frameCycles; - _speaker.run_for_cycles((_audioOutputPositionError + difference) >> 4); - _audioOutputPositionError = (_audioOutputPositionError + difference)&15; + _speaker.run_for_cycles((_audioOutputPositionError + difference) >> 5); + _audioOutputPositionError = (_audioOutputPositionError + difference)&31; } inline void Machine::start_pixel_line() @@ -782,25 +786,24 @@ void Machine::set_key_state(Key key, bool isPressed) void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { - while(number_of_samples--) + if(_is_enabled) { - *target = _is_enabled ? _output_level : 0; - target++; - skip_samples(1); + while(number_of_samples--) + { + *target = (int16_t)((_counter / (_divider+1)) * 8192); + target++; + _counter = (_counter + 1) % ((_divider+1) * 2); + } + } + else + { + memset(target, 0, sizeof(int16_t) * number_of_samples); } } void Speaker::skip_samples(unsigned int number_of_samples) { - while(number_of_samples--) - { - _counter ++; - if(_counter > _divider*2) - { - _counter = 0; - _output_level ^= 8192; - } - } + _counter = (_counter + number_of_samples) % ((_divider+1) * 2); } void Speaker::set_divider(uint8_t divider) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 6148cfbab..ab5efc0ed 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -130,7 +130,6 @@ class Speaker: public ::Outputs::Filter { unsigned int _counter; uint8_t _divider; bool _is_enabled; - int16_t _output_level; }; /*! From 6dbd20ffdec45cab3bbfde8a079eb1b38e7ce5b6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 5 Apr 2016 20:32:55 -0400 Subject: [PATCH 218/307] Expanded interface to rgb_sample to eliminate its need to make assumptions about globals, used expanded interface to compact Electron data to two pixels per byte. --- Machines/Electron/Electron.cpp | 64 ++++++++++++----------------- Outputs/CRT/Internals/CRTOpenGL.cpp | 5 ++- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 75f160022..d96e0f151 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -59,10 +59,10 @@ void Machine::setup_output() { _crt = std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1)); _crt->set_rgb_sampling_function( - "vec4 rgb_sample(vec2 coordinate)" + "vec4 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" "{" - "uint texValue = texture(texID, coordinate).r;" - "return vec4(texValue & 4u, texValue & 2u, texValue & 1u, 1.0);" + "uint texValue = texture(sampler, coordinate).r;" + "return mix(vec4(texValue & 64u, texValue & 32u, texValue & 16u, 1.0), vec4(texValue & 4u, texValue & 2u, texValue & 1u, 1.0), int(icoordinate.x * 2) & 1);" "}"); _crt->set_output_device(Outputs::CRT::Monitor); @@ -523,9 +523,9 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) unsigned int divider = 0; switch(_screen_mode) { - case 0: case 3: divider = 1; break; - case 1: case 4: case 6: divider = 2; break; - case 2: case 5: divider = 4; break; + case 0: case 3: divider = 2; break; + case 1: case 4: case 6: divider = 4; break; + case 2: case 5: divider = 8; break; } if(!_current_output_target || divider != _current_output_divider) @@ -535,6 +535,7 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) _initial_output_target = _current_output_target = _crt->allocate_write_area(640 / _current_output_divider); } +#define pack(a, b) (uint8_t)((a << 4) | (b)) while(number_of_cycles--) { if(!(_current_pixel_column&1) || _screen_mode < 4) @@ -553,33 +554,27 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) case 3: case 0: { - _current_output_target[0] = _palette[(_last_pixel_byte&0x80) >> 4]; - _current_output_target[1] = _palette[(_last_pixel_byte&0x40) >> 3]; - _current_output_target[2] = _palette[(_last_pixel_byte&0x20) >> 2]; - _current_output_target[3] = _palette[(_last_pixel_byte&0x10) >> 1]; - _current_output_target[4] = _palette[(_last_pixel_byte&0x08) >> 0]; - _current_output_target[5] = _palette[(_last_pixel_byte&0x04) << 1]; - _current_output_target[6] = _palette[(_last_pixel_byte&0x02) << 2]; - _current_output_target[7] = _palette[(_last_pixel_byte&0x01) << 3]; - _current_output_target += 8; + _current_output_target[0] = pack(_palette[(_last_pixel_byte&0x80) >> 4], _palette[(_last_pixel_byte&0x40) >> 3]); + _current_output_target[1] = pack(_palette[(_last_pixel_byte&0x20) >> 2], _palette[(_last_pixel_byte&0x10) >> 1]); + _current_output_target[2] = pack(_palette[(_last_pixel_byte&0x08) >> 0], _palette[(_last_pixel_byte&0x04) << 1]); + _current_output_target[3] = pack(_palette[(_last_pixel_byte&0x02) << 2], _palette[(_last_pixel_byte&0x01) << 3]); + _current_output_target += 4; } break; case 1: { - _current_output_target[0] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; - _current_output_target[1] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; - _current_output_target[2] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; - _current_output_target[3] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; - _current_output_target += 4; + _current_output_target[0] = pack(_palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)], _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]); + _current_output_target[1] = pack(_palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)], _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]); + _current_output_target += 2; } break; case 2: { - _current_output_target[0] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x20) >> 3) | ((_last_pixel_byte&0x08) >> 2) | ((_last_pixel_byte&0x02) >> 1)]; - _current_output_target[1] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x10) >> 2) | ((_last_pixel_byte&0x04) >> 1) | ((_last_pixel_byte&0x01) >> 0)]; - _current_output_target += 2; + _current_output_target[0] = pack( _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x20) >> 3) | ((_last_pixel_byte&0x08) >> 2) | ((_last_pixel_byte&0x02) >> 1)], + _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x10) >> 2) | ((_last_pixel_byte&0x04) >> 1) | ((_last_pixel_byte&0x01) >> 0)]); + _current_output_target += 1; } break; @@ -588,19 +583,15 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) { if(_current_pixel_column&1) { - _current_output_target[0] = _palette[(_last_pixel_byte&0x08) >> 0]; - _current_output_target[1] = _palette[(_last_pixel_byte&0x04) << 1]; - _current_output_target[2] = _palette[(_last_pixel_byte&0x02) << 2]; - _current_output_target[3] = _palette[(_last_pixel_byte&0x01) << 3]; + _current_output_target[0] = pack(_palette[(_last_pixel_byte&0x08) >> 0], _palette[(_last_pixel_byte&0x04) << 1]); + _current_output_target[1] = pack(_palette[(_last_pixel_byte&0x02) << 2], _palette[(_last_pixel_byte&0x01) << 3]); } else { - _current_output_target[0] = _palette[(_last_pixel_byte&0x80) >> 4]; - _current_output_target[1] = _palette[(_last_pixel_byte&0x40) >> 3]; - _current_output_target[2] = _palette[(_last_pixel_byte&0x20) >> 2]; - _current_output_target[3] = _palette[(_last_pixel_byte&0x10) >> 1]; + _current_output_target[0] = pack(_palette[(_last_pixel_byte&0x80) >> 4], _palette[(_last_pixel_byte&0x40) >> 3]); + _current_output_target[1] = pack(_palette[(_last_pixel_byte&0x20) >> 2], _palette[(_last_pixel_byte&0x10) >> 1]); } - _current_output_target += 4; + _current_output_target += 2; } break; @@ -608,18 +599,17 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) { if(_current_pixel_column&1) { - _current_output_target[0] = _palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)]; - _current_output_target[1] = _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]; + _current_output_target[0] = pack(_palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)], _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]); } else { - _current_output_target[0] = _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)]; - _current_output_target[1] = _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]; + _current_output_target[0] = pack(_palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)], _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]); } - _current_output_target += 2; + _current_output_target += 1; } break; } +#undef pack _current_pixel_column++; } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 86075d479..bc9c90f44 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -361,6 +361,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "const float shadowMaskMultiple = 600;" "out vec2 srcCoordinatesVarying;" + "out vec2 iSrcCoordinatesVarying;" "void main(void)" "{" @@ -369,6 +370,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);" "ivec2 textureSize = textureSize(texID, 0);" + "iSrcCoordinatesVarying = srcCoordinates;" "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" "alpha = 10.0 * exp(-age * 2.0);" @@ -407,6 +409,7 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "in float alpha;" "in vec2 shadowMaskCoordinates;" "in vec2 srcCoordinatesVarying;" + "in vec2 iSrcCoordinatesVarying;" "out vec4 fragColour;" @@ -417,7 +420,7 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "void main(void)" "{" - "fragColour = rgb_sample(srcCoordinatesVarying) * vec4(1.0, 1.0, 1.0, alpha * sin(lateralVarying));" // + "fragColour = rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying) * vec4(1.0, 1.0, 1.0, alpha);" // * sin(lateralVarying) "}" , sampling_function); } From e35456612a8a480d57f6b6a24e9376bb2d58ae6b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 5 Apr 2016 20:44:05 -0400 Subject: [PATCH 219/307] Simplified 40-column to pixel conversion. --- Machines/Electron/Electron.cpp | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index d96e0f151..67c678700 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -581,30 +581,17 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) case 6: case 4: { - if(_current_pixel_column&1) - { - _current_output_target[0] = pack(_palette[(_last_pixel_byte&0x08) >> 0], _palette[(_last_pixel_byte&0x04) << 1]); - _current_output_target[1] = pack(_palette[(_last_pixel_byte&0x02) << 2], _palette[(_last_pixel_byte&0x01) << 3]); - } - else - { - _current_output_target[0] = pack(_palette[(_last_pixel_byte&0x80) >> 4], _palette[(_last_pixel_byte&0x40) >> 3]); - _current_output_target[1] = pack(_palette[(_last_pixel_byte&0x20) >> 2], _palette[(_last_pixel_byte&0x10) >> 1]); - } + _current_output_target[0] = pack(_palette[(_last_pixel_byte&0x80) >> 4], _palette[(_last_pixel_byte&0x40) >> 3]); + _current_output_target[1] = pack(_palette[(_last_pixel_byte&0x20) >> 2], _palette[(_last_pixel_byte&0x10) >> 1]); + _last_pixel_byte <<= 4; _current_output_target += 2; } break; case 5: { - if(_current_pixel_column&1) - { - _current_output_target[0] = pack(_palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)], _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]); - } - else - { - _current_output_target[0] = pack(_palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)], _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]); - } + _current_output_target[0] = pack(_palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)], _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]); + _last_pixel_byte <<= 2; _current_output_target += 1; } break; From 8f87973a96a72d86fd29f08e994724b2e9ada146 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 5 Apr 2016 22:07:10 -0400 Subject: [PATCH 220/307] Switched to table-based byte to output conversion. --- Machines/Electron/Electron.cpp | 40 ++++++++++++++++++++++++---------- Machines/Electron/Electron.hpp | 8 +++++++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 67c678700..00dfbb73a 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -266,6 +266,30 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _palette[registers[index][0]] = (_palette[registers[index][0]]&5) | ((colour >> 2)&2); _palette[registers[index][1]] = (_palette[registers[index][1]]&5) | ((colour >> 1)&2); } + + // regenerate all palette tables for now +#define pack(a, b) (uint8_t)((a << 4) | (b)) + for(int byte = 0; byte < 256; byte++) + { + uint8_t *target = (uint8_t *)&_paletteTables.forty1bpp[byte]; + target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); + target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); + + target = (uint8_t *)&_paletteTables.eighty2bpp[byte]; + target[0] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); + target[1] = pack(_palette[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], _palette[((byte&0x10) >> 1) | ((byte&0x01) << 1)]); + + target = (uint8_t *)&_paletteTables.eighty1bpp[byte]; + target[0] = pack(_palette[(byte&0x80) >> 4], _palette[(byte&0x40) >> 3]); + target[1] = pack(_palette[(byte&0x20) >> 2], _palette[(byte&0x10) >> 1]); + target[2] = pack(_palette[(byte&0x08) >> 0], _palette[(byte&0x04) << 1]); + target[3] = pack(_palette[(byte&0x02) << 2], _palette[(byte&0x01) << 3]); + + _paletteTables.forty2bpp[byte] = pack(_palette[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], _palette[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]); + _paletteTables.eighty4bpp[byte] = pack( _palette[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)], + _palette[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]); + } +#undef pack } } break; @@ -554,26 +578,21 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) case 3: case 0: { - _current_output_target[0] = pack(_palette[(_last_pixel_byte&0x80) >> 4], _palette[(_last_pixel_byte&0x40) >> 3]); - _current_output_target[1] = pack(_palette[(_last_pixel_byte&0x20) >> 2], _palette[(_last_pixel_byte&0x10) >> 1]); - _current_output_target[2] = pack(_palette[(_last_pixel_byte&0x08) >> 0], _palette[(_last_pixel_byte&0x04) << 1]); - _current_output_target[3] = pack(_palette[(_last_pixel_byte&0x02) << 2], _palette[(_last_pixel_byte&0x01) << 3]); + *(uint32_t *)_current_output_target = _paletteTables.eighty1bpp[_last_pixel_byte]; _current_output_target += 4; } break; case 1: { - _current_output_target[0] = pack(_palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)], _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]); - _current_output_target[1] = pack(_palette[((_last_pixel_byte&0x20) >> 2) | ((_last_pixel_byte&0x02) >> 0)], _palette[((_last_pixel_byte&0x10) >> 1) | ((_last_pixel_byte&0x01) << 1)]); + *(uint16_t *)_current_output_target = _paletteTables.eighty2bpp[_last_pixel_byte]; _current_output_target += 2; } break; case 2: { - _current_output_target[0] = pack( _palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x20) >> 3) | ((_last_pixel_byte&0x08) >> 2) | ((_last_pixel_byte&0x02) >> 1)], - _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x10) >> 2) | ((_last_pixel_byte&0x04) >> 1) | ((_last_pixel_byte&0x01) >> 0)]); + *_current_output_target = _paletteTables.eighty4bpp[_last_pixel_byte]; _current_output_target += 1; } break; @@ -581,8 +600,7 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) case 6: case 4: { - _current_output_target[0] = pack(_palette[(_last_pixel_byte&0x80) >> 4], _palette[(_last_pixel_byte&0x40) >> 3]); - _current_output_target[1] = pack(_palette[(_last_pixel_byte&0x20) >> 2], _palette[(_last_pixel_byte&0x10) >> 1]); + *(uint16_t *)_current_output_target = _paletteTables.forty1bpp[_last_pixel_byte]; _last_pixel_byte <<= 4; _current_output_target += 2; } @@ -590,7 +608,7 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) case 5: { - _current_output_target[0] = pack(_palette[((_last_pixel_byte&0x80) >> 4) | ((_last_pixel_byte&0x08) >> 2)], _palette[((_last_pixel_byte&0x40) >> 3) | ((_last_pixel_byte&0x04) >> 1)]); + *_current_output_target = _paletteTables.forty2bpp[_last_pixel_byte]; _last_pixel_byte <<= 2; _current_output_target += 1; } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index ab5efc0ed..dd5bf2a77 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -189,6 +189,14 @@ class Machine: public CPU6502::Processor, Tape::Delegate { unsigned int _frameCycles, _displayOutputPosition; int _audioOutputPosition, _audioOutputPositionError; + struct { + uint16_t forty1bpp[256]; + uint8_t forty2bpp[256]; + uint32_t eighty1bpp[256]; + uint16_t eighty2bpp[256]; + uint8_t eighty4bpp[256]; + } _paletteTables; + // Display generation. uint16_t _startLineAddress, _currentScreenAddress; int _current_pixel_line, _current_pixel_column, _current_character_row; From 2248769df926e1708c49f6970b3680c75df6cd9e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 5 Apr 2016 22:17:44 -0400 Subject: [PATCH 221/307] Pulled a bunch of selection parts outside of the loops. Probably exhausting this angle of attack. --- Machines/Electron/Electron.cpp | 115 +++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 33 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 00dfbb73a..ba75d4e34 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -538,6 +538,8 @@ inline void Machine::end_pixel_line() inline void Machine::output_pixels(unsigned int number_of_cycles) { + if(!number_of_cycles) return; + if(_isBlankLine) { _crt->output_blank(number_of_cycles * crt_cycles_multiplier); @@ -559,65 +561,112 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) _initial_output_target = _current_output_target = _crt->allocate_write_area(640 / _current_output_divider); } -#define pack(a, b) (uint8_t)((a << 4) | (b)) - while(number_of_cycles--) +#define get_pixel() \ + if(_currentScreenAddress&32768)\ + {\ + _currentScreenAddress = (_screenModeBaseAddress + _currentScreenAddress)&32767;\ + }\ + _last_pixel_byte = _ram[_currentScreenAddress];\ + _currentScreenAddress = _currentScreenAddress+8 + + switch(_screen_mode) { - if(!(_current_pixel_column&1) || _screen_mode < 4) - { - if(_currentScreenAddress&32768) - { - _currentScreenAddress = (_screenModeBaseAddress + _currentScreenAddress)&32767; - } - - _last_pixel_byte = _ram[_currentScreenAddress]; - _currentScreenAddress = _currentScreenAddress+8; - } - - switch(_screen_mode) - { - case 3: - case 0: + case 0: case 3: + while(number_of_cycles--) { + get_pixel(); *(uint32_t *)_current_output_target = _paletteTables.eighty1bpp[_last_pixel_byte]; _current_output_target += 4; + _current_pixel_column++; } - break; + break; - case 1: + case 1: + while(number_of_cycles--) { + get_pixel(); *(uint16_t *)_current_output_target = _paletteTables.eighty2bpp[_last_pixel_byte]; _current_output_target += 2; + _current_pixel_column++; } - break; + break; - case 2: + case 2: + while(number_of_cycles--) { + get_pixel(); *_current_output_target = _paletteTables.eighty4bpp[_last_pixel_byte]; _current_output_target += 1; + _current_pixel_column++; } - break; + break; - case 6: - case 4: + case 4: case 6: + if(_current_pixel_column&1) { *(uint16_t *)_current_output_target = _paletteTables.forty1bpp[_last_pixel_byte]; - _last_pixel_byte <<= 4; _current_output_target += 2; - } - break; - case 5: + number_of_cycles--; + _current_pixel_column++; + } + while(number_of_cycles > 1) { + get_pixel(); + *(uint16_t *)_current_output_target = _paletteTables.forty1bpp[_last_pixel_byte]; + _current_output_target += 2; + _last_pixel_byte <<= 4; + + *(uint16_t *)_current_output_target = _paletteTables.forty1bpp[_last_pixel_byte]; + _current_output_target += 2; + + number_of_cycles -= 2; + _current_pixel_column+=2; + } + if(number_of_cycles) + { + get_pixel(); + *(uint16_t *)_current_output_target = _paletteTables.forty1bpp[_last_pixel_byte]; + _current_output_target += 2; + _last_pixel_byte <<= 4; + _current_pixel_column++; + } + break; + + case 5: + if(_current_pixel_column&1) + { + *_current_output_target = _paletteTables.forty2bpp[_last_pixel_byte]; + _current_output_target += 1; + + number_of_cycles--; + _current_pixel_column++; + } + while(number_of_cycles > 1) + { + get_pixel(); *_current_output_target = _paletteTables.forty2bpp[_last_pixel_byte]; _last_pixel_byte <<= 2; _current_output_target += 1; - } - break; - } -#undef pack - _current_pixel_column++; + *_current_output_target = _paletteTables.forty2bpp[_last_pixel_byte]; + _current_output_target += 1; + + number_of_cycles -= 2; + _current_pixel_column+=2; + } + if(number_of_cycles) + { + get_pixel(); + *_current_output_target = _paletteTables.forty2bpp[_last_pixel_byte]; + _last_pixel_byte <<= 2; + _current_output_target += 1; + _current_pixel_column++; + } + break; } + +#undef get_pixel } } From 5aa6da221f3d043c4bad6966a4595a27888edf53 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 5 Apr 2016 22:19:14 -0400 Subject: [PATCH 222/307] Shifted responsibility for byte shifts, probably to a more logical place. --- Machines/Electron/Electron.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ba75d4e34..8eae7e05c 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -604,6 +604,7 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) case 4: case 6: if(_current_pixel_column&1) { + _last_pixel_byte <<= 4; *(uint16_t *)_current_output_target = _paletteTables.forty1bpp[_last_pixel_byte]; _current_output_target += 2; @@ -615,8 +616,8 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) get_pixel(); *(uint16_t *)_current_output_target = _paletteTables.forty1bpp[_last_pixel_byte]; _current_output_target += 2; - _last_pixel_byte <<= 4; + _last_pixel_byte <<= 4; *(uint16_t *)_current_output_target = _paletteTables.forty1bpp[_last_pixel_byte]; _current_output_target += 2; @@ -628,7 +629,6 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) get_pixel(); *(uint16_t *)_current_output_target = _paletteTables.forty1bpp[_last_pixel_byte]; _current_output_target += 2; - _last_pixel_byte <<= 4; _current_pixel_column++; } break; @@ -636,6 +636,7 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) case 5: if(_current_pixel_column&1) { + _last_pixel_byte <<= 2; *_current_output_target = _paletteTables.forty2bpp[_last_pixel_byte]; _current_output_target += 1; @@ -646,9 +647,9 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) { get_pixel(); *_current_output_target = _paletteTables.forty2bpp[_last_pixel_byte]; - _last_pixel_byte <<= 2; _current_output_target += 1; + _last_pixel_byte <<= 2; *_current_output_target = _paletteTables.forty2bpp[_last_pixel_byte]; _current_output_target += 1; @@ -659,7 +660,6 @@ inline void Machine::output_pixels(unsigned int number_of_cycles) { get_pixel(); *_current_output_target = _paletteTables.forty2bpp[_last_pixel_byte]; - _last_pixel_byte <<= 2; _current_output_target += 1; _current_pixel_column++; } From e885438363a8a947b3066266eee62dd9e084f4ce Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 6 Apr 2016 19:34:34 -0400 Subject: [PATCH 223/307] The penny has finally dropped that I can drive the CPU stuff in a manner completely decoupled from the GPU stuff. For much improved parallelisation. --- .../Documents/MachineDocument.swift | 6 ++---- .../Mac/Clock Signal/Views/CSOpenGLView.m | 19 ++++--------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 062a69158..cc72a643f 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -41,12 +41,10 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe let cycleCount = cycleCountLow + cycleCountHigh if let lastCycleCount = lastCycleCount { let elapsedTime = cycleCount - lastCycleCount - // if the emulation has fallen too far behind then silently swallow the request; + // if the emulation has fallen too far behind then silently limit the request; // some actions — e.g. the host computer waking after sleep — may give us a // prohibitive backlog - if elapsedTime < intendedCyclesPerSecond / 25 { - runForNumberOfCycles(Int32(elapsedTime)) - } + runForNumberOfCycles(max(Int32(elapsedTime), Int32(intendedCyclesPerSecond / 25))) } lastCycleCount = cycleCount } diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 3514e9001..5f50e1ec8 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -13,7 +13,6 @@ @implementation CSOpenGLView { CVDisplayLinkRef _displayLink; uint32_t _updateIsOngoing; - int _missedFrames; } - (void)prepareOpenGL @@ -53,28 +52,18 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawAtTime:(const CVTimeStamp *)now { + // Always post a -didUpdateToTime:. This is the hook upon which the substantial processing occurs. + [self.delegate openGLView:self didUpdateToTime:*now]; + + // Draw the display only if a previous draw is not still ongoing. const uint32_t activityMask = 0x01; if(!OSAtomicTestAndSet(activityMask, &_updateIsOngoing)) { - _missedFrames = 0; - CVTimeStamp time = *now; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - [self.delegate openGLView:self didUpdateToTime:time]; [self drawViewOnlyIfDirty:YES]; OSAtomicTestAndClear(activityMask, &_updateIsOngoing); }); } - else - { - _missedFrames++; - if(_missedFrames == 10) - { - _missedFrames = 0; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - [self drawViewOnlyIfDirty:YES]; - }); - } - } } - (void)invalidate From fd1f6a7e1f4e45f2b948302fb734fa06295b02c2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 6 Apr 2016 19:35:53 -0400 Subject: [PATCH 224/307] Expanded on thinking. --- OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 5f50e1ec8..abbdbff4b 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -52,10 +52,12 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawAtTime:(const CVTimeStamp *)now { - // Always post a -didUpdateToTime:. This is the hook upon which the substantial processing occurs. + // Always post a -openGLView:didUpdateToTime:. This is the hook upon which the substantial processing occurs. [self.delegate openGLView:self didUpdateToTime:*now]; - // Draw the display only if a previous draw is not still ongoing. + // Draw the display only if a previous draw is not still ongoing. -drawViewOnlyIfDirty: is guaranteed + // to be safe to call concurrently with -openGLView:updateToTime: so there's no need to worry about + // the above interrupting the below or vice versa. const uint32_t activityMask = 0x01; if(!OSAtomicTestAndSet(activityMask, &_updateIsOngoing)) { From d4be4c476977a096d653641503b61901a40723f2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 6 Apr 2016 20:27:48 -0400 Subject: [PATCH 225/307] Of course, I want `min`. Not `max`. --- OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index cc72a643f..a499de76f 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -44,7 +44,7 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe // if the emulation has fallen too far behind then silently limit the request; // some actions — e.g. the host computer waking after sleep — may give us a // prohibitive backlog - runForNumberOfCycles(max(Int32(elapsedTime), Int32(intendedCyclesPerSecond / 25))) + runForNumberOfCycles(min(Int32(elapsedTime), Int32(intendedCyclesPerSecond / 25))) } lastCycleCount = cycleCount } From 28e65172346fd6992d370302b7a3faf469980013 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 6 Apr 2016 20:46:00 -0400 Subject: [PATCH 226/307] Switched away from tracking absolute time to tracking relative. --- .../Documents/MachineDocument.swift | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index a499de76f..65ea23402 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -28,25 +28,23 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe } var intendedCyclesPerSecond: Int64 = 0 - private var lastCycleCount: Int64? + private var cycleCountError: Int64 = 0 + private var lastTime: CVTimeStamp? final func openGLView(view: CSOpenGLView, didUpdateToTime time: CVTimeStamp) { - // TODO: treat time as a delta from old time, work out how many cycles that is plus error + if let lastTime = lastTime { + // perform (time passed in seconds) * (intended cycles per second), converting and + // maintaining an error count to deal with underflow + let videoTimeScale64 = Int64(time.videoTimeScale) + let videoTimeCount = ((time.videoTime - lastTime.videoTime) * intendedCyclesPerSecond) + cycleCountError + cycleCountError = videoTimeCount % videoTimeScale64 + let numberOfCycles = videoTimeCount / videoTimeScale64 - // this slightly elaborate dance is to avoid overflow - let videoTimeScale64 = Int64(time.videoTimeScale) - - let cycleCountLow = ((time.videoTime % videoTimeScale64) * intendedCyclesPerSecond) / videoTimeScale64 - let cycleCountHigh = (time.videoTime / videoTimeScale64) * intendedCyclesPerSecond - - let cycleCount = cycleCountLow + cycleCountHigh - if let lastCycleCount = lastCycleCount { - let elapsedTime = cycleCount - lastCycleCount // if the emulation has fallen too far behind then silently limit the request; // some actions — e.g. the host computer waking after sleep — may give us a // prohibitive backlog - runForNumberOfCycles(min(Int32(elapsedTime), Int32(intendedCyclesPerSecond / 25))) + runForNumberOfCycles(min(Int32(numberOfCycles), Int32(intendedCyclesPerSecond / 25))) } - lastCycleCount = cycleCount + lastTime = time } func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) {} From e617bd2bb33e272b08cf6f1e7ec98d2a68dd8818 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 6 Apr 2016 21:12:22 -0400 Subject: [PATCH 227/307] Turned audio quality up to the maximum, at least for now. --- Machines/Electron/Electron.cpp | 14 ++++++-------- Machines/Electron/Electron.hpp | 2 +- OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 8eae7e05c..e68efbab1 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -67,10 +67,9 @@ void Machine::setup_output() _crt->set_output_device(Outputs::CRT::Monitor); // The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; - // nevertheless sampling only at 62500Hz may introduce aliasing errors as setting the speaker on or off can happen on - // any 2Mhz cycle, and I've no idea whether it has an immediate effect or happens only on the next clock. If it turns - // out that the former is the case, I'll need to turn this up to 2000000. - _speaker.set_input_rate(62500); + // however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So + // run the speaker at a 2000000Hz input rate, at least for the time being. + _speaker.set_input_rate(2000000); } unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) @@ -431,7 +430,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _frameCycles = nextFrameCycles; } - if(!(_frameCycles&31)) + if(!(_frameCycles&16383)) update_audio(); _tape.run_for_cycles(cycles); @@ -495,9 +494,8 @@ inline void Machine::evaluate_interrupts() inline void Machine::update_audio() { int difference = (int)_frameCycles - _audioOutputPosition; + _speaker.run_for_cycles(difference); _audioOutputPosition = (int)_frameCycles; - _speaker.run_for_cycles((_audioOutputPositionError + difference) >> 5); - _audioOutputPositionError = (_audioOutputPositionError + difference)&31; } inline void Machine::start_pixel_line() @@ -852,7 +850,7 @@ void Speaker::skip_samples(unsigned int number_of_samples) void Speaker::set_divider(uint8_t divider) { - _divider = divider; + _divider = divider * 32; } void Speaker::set_is_enabled(bool is_enabled) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index dd5bf2a77..9a7b44172 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -128,7 +128,7 @@ class Speaker: public ::Outputs::Filter { private: unsigned int _counter; - uint8_t _divider; + unsigned int _divider; bool _is_enabled; }; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index b98f0b413..4809a2a27 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -60,7 +60,7 @@ - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { @synchronized(self) { _electron.get_speaker()->set_output_rate(sampleRate, 256); - _electron.get_speaker()->set_output_quality(15); + _electron.get_speaker()->set_output_quality(47); _electron.get_speaker()->set_delegate(delegate); return YES; } From fc5530b513abebe6f7e610474e297994f1c947ac Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Apr 2016 15:58:43 -0400 Subject: [PATCH 228/307] Realising I've managed to confuse input and output, started shift to 'source' for what 'input' was. --- Outputs/CRT/CRT.cpp | 36 +++++++++++++------------- Outputs/CRT/Internals/CRTConstants.hpp | 10 +++---- Outputs/CRT/Internals/CRTOpenGL.cpp | 8 +++--- Outputs/CRT/Internals/CRTOpenGL.hpp | 21 +++++++-------- 4 files changed, 36 insertions(+), 39 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index fa783a679..e34fcf442 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -101,13 +101,13 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, #define output_frame_id(v) next_run[OutputVertexSize*v + OutputVertexOffsetOfFrameID] #define output_timestamp(v) (*(uint32_t *)&next_run[OutputVertexSize*v + OutputVertexOffsetOfTimestamp]) -#define input_input_position_x(v) (*(uint16_t *)&next_run[InputVertexSize*v + InputVertexOffsetOfInputPosition + 0]) -#define input_input_position_y(v) (*(uint16_t *)&next_run[InputVertexSize*v + InputVertexOffsetOfInputPosition + 2]) -#define input_output_position_x(v) (*(uint16_t *)&next_run[InputVertexSize*v + InputVertexOffsetOfOutputPosition + 0]) -#define input_output_position_y(v) (*(uint16_t *)&next_run[InputVertexSize*v + InputVertexOffsetOfOutputPosition + 2]) -#define input_phase(v) next_run[OutputVertexSize*v + InputVertexOffsetOfPhaseAndAmplitude + 0] -#define input_amplitude(v) next_run[OutputVertexSize*v + InputVertexOffsetOfPhaseAndAmplitude + 1] -#define input_phase_time(v) (*(uint16_t *)&next_run[OutputVertexSize*v + InputVertexOffsetOfPhaseTime]) +#define source_input_position_x(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfInputPosition + 0]) +#define source_input_position_y(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfInputPosition + 2]) +#define source_output_position_x(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfOutputPosition + 0]) +#define source_output_position_y(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfOutputPosition + 2]) +#define source_phase(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAndAmplitude + 0] +#define source_amplitude(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAndAmplitude + 1] +#define source_phase_time(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseTime]) 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) { @@ -132,7 +132,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi uint8_t *next_run = nullptr; if(is_output_segment) { - next_run = _openGL_output_builder->get_next_input_run(); + next_run = _openGL_output_builder->get_next_output_run(); } // Vertex output is arranged for triangle strips, as: @@ -158,13 +158,13 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi } else { - input_input_position_x(0) = tex_x; - input_input_position_y(0) = input_input_position_y(1) = tex_y; - input_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position(); - input_output_position_y(0) = input_output_position_y(1) = _openGL_output_builder->get_composite_output_y(); - input_phase(0) = input_phase(1) = _colour_burst_phase; - input_amplitude(0) = input_amplitude(1) = _colour_burst_amplitude; - input_phase_time(0) = input_phase_time(1) = _colour_burst_time; + source_input_position_x(0) = tex_x; + source_input_position_y(0) = source_input_position_y(1) = tex_y; + source_output_position_x(0) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + source_output_position_y(0) = source_output_position_y(1) = _openGL_output_builder->get_composite_output_y(); + source_phase(0) = source_phase(1) = _colour_burst_phase; + source_amplitude(0) = source_amplitude(1) = _colour_burst_amplitude; + source_phase_time(0) = source_phase_time(1) = _colour_burst_time; } } @@ -198,14 +198,14 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi } else { - input_input_position_x(1) = tex_x; - input_output_position_x(1) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + source_input_position_x(1) = tex_x; + source_output_position_x(1) = (uint16_t)_horizontal_flywheel->get_current_output_position(); } } if(is_output_segment) { - _openGL_output_builder->complete_input_run(); + _openGL_output_builder->complete_output_run(); } // if this is horizontal retrace then advance the output line counter and bookend an output run diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index 0aafc5986..c82540243 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -27,12 +27,12 @@ const size_t OutputVertexSize = 16; // Input vertices, used only in composite mode, map from the input buffer to temporary buffer locations; such // remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour -const size_t InputVertexOffsetOfInputPosition = 0; -const size_t InputVertexOffsetOfOutputPosition = 4; -const size_t InputVertexOffsetOfPhaseAndAmplitude = 8; -const size_t InputVertexOffsetOfPhaseTime = 12; +const size_t SourceVertexOffsetOfInputPosition = 0; +const size_t SourceVertexOffsetOfOutputPosition = 4; +const size_t SourceVertexOffsetOfPhaseAndAmplitude = 8; +const size_t SourceVertexOffsetOfPhaseTime = 12; -const size_t InputVertexSize = 16; +const size_t SourceVertexSize = 16; // These constants hold the size of the rolling buffer to which the CPU writes const int InputBufferBuilderWidth = 2048; diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index bc9c90f44..57235d5bf 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -215,15 +215,15 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out if(count > 0) { // draw - GLsizei primitive_count = (GLsizei)(count / InputVertexSize); - GLsizei max_count = (GLsizei)((InputVertexBufferDataSize - start) / InputVertexSize); + GLsizei primitive_count = (GLsizei)(count / OutputVertexSize); + GLsizei max_count = (GLsizei)((InputVertexBufferDataSize - start) / OutputVertexSize); if(primitive_count < max_count) { - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(start / InputVertexSize), primitive_count); + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(start / OutputVertexSize), primitive_count); } else { - glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(start / InputVertexSize), max_count); + glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(start / OutputVertexSize), max_count); glDrawArrays(GL_TRIANGLE_STRIP, 0, primitive_count - max_count); } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index ce621c9ad..a2086d4f8 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -104,30 +104,27 @@ class OpenGLOutputBuilder { _visible_area = visible_area; } - inline uint8_t *get_next_input_run() + inline uint8_t *get_next_source_run() { - _output_mutex->lock(); - uint8_t *pointer = &_output_buffer_data[_output_buffer_data_pointer]; - _output_buffer_data_pointer = (_output_buffer_data_pointer + 6 * InputVertexSize) % InputVertexBufferDataSize; - return pointer; + return nullptr; } - inline void complete_input_run() + inline void complete_source_run() { - _run_builders[_run_write_pointer]->amount_of_data += 6 * InputVertexSize; - _output_mutex->unlock(); } inline uint8_t *get_next_output_run() { -// _output_mutex->lock(); -// return (_output_device == Monitor) ? _run_builders[_run_write_pointer]->get_next_run(6) : _composite_src_runs->get_next_run(2); - return nullptr; + _output_mutex->lock(); + uint8_t *pointer = &_output_buffer_data[_output_buffer_data_pointer]; + _output_buffer_data_pointer = (_output_buffer_data_pointer + 6 * OutputVertexSize) % InputVertexBufferDataSize; + return pointer; } inline void complete_output_run() { -// _output_mutex->unlock(); + _run_builders[_run_write_pointer]->amount_of_data += 6 * OutputVertexSize; + _output_mutex->unlock(); } inline OutputDevice get_output_device() From 870136627705aca27748068f3701f7342987989f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Apr 2016 21:34:40 -0400 Subject: [PATCH 229/307] Switched horizontal sync detection back to differential, switched the Electron to an XOR-style sync output (believed to be accurate, but I need to check), fixed some latent issues around vertical sync detection. --- Machines/Electron/Electron.cpp | 12 ++++++++---- Outputs/CRT/CRT.cpp | 17 +++++++++++------ Outputs/CRT/Internals/CRTOpenGL.cpp | 5 +---- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index e68efbab1..29fbbb90e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -62,7 +62,8 @@ void Machine::setup_output() "vec4 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" "{" "uint texValue = texture(sampler, coordinate).r;" - "return mix(vec4(texValue & 64u, texValue & 32u, texValue & 16u, 1.0), vec4(texValue & 4u, texValue & 2u, texValue & 1u, 1.0), int(icoordinate.x * 2) & 1);" + "texValue >>= 4 - (int(icoordinate.x * 2) & 1)*4;" + "return vec4(texValue & 4u, texValue & 2u, texValue & 1u, 1.0);" "}"); _crt->set_output_device(Outputs::CRT::Monitor); @@ -697,7 +698,8 @@ inline void Machine::update_display() { // wait for the line to complete before signalling if(final_line == line) return; - _crt->output_sync(128 * crt_cycles_multiplier); + _crt->output_blank(9 * crt_cycles_multiplier); + _crt->output_sync(119 * crt_cycles_multiplier); _displayOutputPosition += 128; continue; } @@ -707,7 +709,8 @@ inline void Machine::update_display() { // wait for the line to complete before signalling if(final_line == line) return; - _crt->output_sync(64 * crt_cycles_multiplier); + _crt->output_blank(9 * crt_cycles_multiplier); + _crt->output_sync(55 * crt_cycles_multiplier); _crt->output_blank(64 * crt_cycles_multiplier); _displayOutputPosition += 128; continue; @@ -718,7 +721,8 @@ inline void Machine::update_display() { // wait for the line to complete before signalling if(final_line == line) return; - _crt->output_blank(64 * crt_cycles_multiplier); + _crt->output_sync(9 * crt_cycles_multiplier); + _crt->output_blank(55 * crt_cycles_multiplier); _crt->output_sync(64 * crt_cycles_multiplier); _displayOutputPosition += 128; continue; diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index e34fcf442..b3407d69e 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -17,7 +17,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di { _openGL_output_builder->set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator); - const unsigned int syncCapacityLineChargeThreshold = 3; + const unsigned int syncCapacityLineChargeThreshold = 2; const unsigned int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234 const unsigned int scanlinesVerticalRetraceTime = 10; // source: ibid @@ -34,7 +34,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di _cycles_per_line = cycles_per_line * _time_multiplier; // generate timing values implied by the given arbuments - _sync_capacitor_charge_threshold = ((syncCapacityLineChargeThreshold * _cycles_per_line) * 50) >> 7; + _sync_capacitor_charge_threshold = (int)(syncCapacityLineChargeThreshold * _cycles_per_line); // create the two flywheels _horizontal_flywheel = std::unique_ptr(new Flywheel(_cycles_per_line, (millisecondsHorizontalRetraceTime * _cycles_per_line) >> 6)); @@ -174,7 +174,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi _openGL_output_builder->add_to_field_time(next_run_length); // either charge or deplete the vertical retrace capacitor (making sure it stops at 0) - if (vsync_charging && !_vertical_flywheel->is_in_retrace()) + if (vsync_charging) _sync_capacitor_charge_level += next_run_length; else _sync_capacitor_charge_level = std::max(_sync_capacitor_charge_level - (int)next_run_length, 0); @@ -273,13 +273,18 @@ 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); + const bool is_leading_edge = (!_is_receiving_sync && this_is_sync); _is_receiving_sync = this_is_sync; -// const bool hsync_requested = is_leading_edge; - const bool hsync_requested = is_trailing_edge && (_sync_period < (_horizontal_flywheel->get_scan_period() >> 2)); + const bool hsync_requested = is_leading_edge; +// const bool hsync_requested = is_trailing_edge && (_sync_period < (_horizontal_flywheel->get_scan_period() >> 2)); const bool vsync_requested = is_trailing_edge && (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold); +// if(is_trailing_edge && _sync_capacitor_charge_threshold - _sync_capacitor_charge_level < 3000) +// { +// printf("%d\n", _sync_capacitor_charge_threshold - _sync_capacitor_charge_level); +// } + // simplified colour burst logic: if it's within the back porch we'll take it if(scan->type == Scan::Type::ColourBurst) { diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 57235d5bf..0afd28d18 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -374,9 +374,6 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" "alpha = 10.0 * exp(-age * 2.0);" -// "vec3 alphas = vec3(10.0 * exp((-age - 1.33) * 2.0), 10.0 * exp(-(age - 0.66) * 2.0), 10.0 * exp(-age * 2.0));" -// "alpha = min(10.0 * exp(-age * 2.0), 1.0);" -// "alpha = dot(alphas, filterCoefficients);" "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" @@ -420,7 +417,7 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "void main(void)" "{" - "fragColour = rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying) * vec4(1.0, 1.0, 1.0, alpha);" // * sin(lateralVarying) + "fragColour = rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying) * vec4(1.0, 1.0, 1.0, alpha*sin(lateralVarying));" // "}" , sampling_function); } From bdb99ba92f647054b14d2897f508a16aa60ec444 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Apr 2016 22:56:52 -0400 Subject: [PATCH 230/307] Corrections back towards composite output: fixed misnamed constant, ensured the CRT doesn't write nonsense to the output buffer, ensured it grows three vertices at a time rather than six when desired. Net effect should be that the output stage is working again, with the input processing remaining to fill in. --- Machines/Electron/Electron.cpp | 1 + Outputs/CRT/CRT.cpp | 17 +++++++++++------ Outputs/CRT/Internals/CRTConstants.hpp | 2 +- Outputs/CRT/Internals/CRTOpenGL.cpp | 6 +++--- Outputs/CRT/Internals/CRTOpenGL.hpp | 6 +++--- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 29fbbb90e..7d82e8e30 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -64,6 +64,7 @@ void Machine::setup_output() "uint texValue = texture(sampler, coordinate).r;" "texValue >>= 4 - (int(icoordinate.x * 2) & 1)*4;" "return vec4(texValue & 4u, texValue & 2u, texValue & 1u, 1.0);" +// "return vec4(1.0);" "}"); _crt->set_output_device(Outputs::CRT::Monitor); diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index b3407d69e..d7bf891c0 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -132,7 +132,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi uint8_t *next_run = nullptr; if(is_output_segment) { - next_run = _openGL_output_builder->get_next_output_run(); + next_run = (_openGL_output_builder->get_output_device() == Monitor) ? _openGL_output_builder->get_next_output_run() : _openGL_output_builder->get_next_source_run(); } // Vertex output is arranged for triangle strips, as: @@ -195,18 +195,22 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi output_position_y(3) = output_position_y(4) = output_position_y(5) = (uint16_t)(_vertical_flywheel->get_current_output_position() / _vertical_flywheel_output_divider); output_timestamp(3) = output_timestamp(4) = output_timestamp(5) = _openGL_output_builder->get_current_field_time(); output_tex_x(3) = output_tex_x(4) = output_tex_x(5) = tex_x; + + _openGL_output_builder->complete_output_run(6); } else { source_input_position_x(1) = tex_x; source_output_position_x(1) = (uint16_t)_horizontal_flywheel->get_current_output_position(); + + _openGL_output_builder->complete_source_run(); } } - if(is_output_segment) - { - _openGL_output_builder->complete_output_run(); - } +// if(is_output_segment) +// { +// _openGL_output_builder->complete_output_run(6); +// } // if this is horizontal retrace then advance the output line counter and bookend an output run if(_openGL_output_builder->get_output_device() == Television) @@ -230,8 +234,9 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi output_lateral(0) = 0; output_lateral(1) = _is_writing_composite_run ? 1 : 0; output_lateral(2) = 1; + output_frame_id(0) = output_frame_id(1) = output_frame_id(2) = (uint8_t)_openGL_output_builder->get_current_field(); - _openGL_output_builder->complete_output_run(); + _openGL_output_builder->complete_output_run(3); _is_writing_composite_run ^= true; } diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index c82540243..d14e7aee5 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -43,7 +43,7 @@ const int IntermediateBufferWidth = 2048; const int IntermediateBufferHeight = 2048; // Some internal -const GLsizeiptr InputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize +const GLsizeiptr OutputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize // Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 0afd28d18..acd334fc5 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -112,7 +112,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); - glBufferData(GL_ARRAY_BUFFER, InputVertexBufferDataSize, NULL, GL_STREAM_DRAW); + glBufferData(GL_ARRAY_BUFFER, OutputVertexBufferDataSize, NULL, GL_STREAM_DRAW); _output_buffer_data_pointer = 0; glBindVertexArray(output_vertex_array); @@ -216,7 +216,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out { // draw GLsizei primitive_count = (GLsizei)(count / OutputVertexSize); - GLsizei max_count = (GLsizei)((InputVertexBufferDataSize - start) / OutputVertexSize); + GLsizei max_count = (GLsizei)((OutputVertexBufferDataSize - start) / OutputVertexSize); if(primitive_count < max_count) { glDrawArrays(GL_TRIANGLE_STRIP, (GLint)(start / OutputVertexSize), primitive_count); @@ -231,7 +231,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // drawing commands having been issued, reclaim the array buffer pointer // _buffer_builder->move_to_new_line(); - _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, InputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _output_mutex->unlock(); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index a2086d4f8..1144bd4ea 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -117,13 +117,13 @@ class OpenGLOutputBuilder { { _output_mutex->lock(); uint8_t *pointer = &_output_buffer_data[_output_buffer_data_pointer]; - _output_buffer_data_pointer = (_output_buffer_data_pointer + 6 * OutputVertexSize) % InputVertexBufferDataSize; return pointer; } - inline void complete_output_run() + inline void complete_output_run(size_t vertices_written) { - _run_builders[_run_write_pointer]->amount_of_data += 6 * OutputVertexSize; + _run_builders[_run_write_pointer]->amount_of_data += vertices_written * OutputVertexSize; + _output_buffer_data_pointer = (_output_buffer_data_pointer + vertices_written * OutputVertexSize) % OutputVertexBufferDataSize; _output_mutex->unlock(); } From e45fe4380180bbcecf3f7d2d50ff1adb443ec9ac Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Apr 2016 23:02:34 -0400 Subject: [PATCH 231/307] Removed some dead caveman debugging. --- Outputs/CRT/CRT.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index d7bf891c0..86c909722 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -285,11 +285,6 @@ void CRT::output_scan(const Scan *const scan) // const bool hsync_requested = is_trailing_edge && (_sync_period < (_horizontal_flywheel->get_scan_period() >> 2)); const bool vsync_requested = is_trailing_edge && (_sync_capacitor_charge_level >= _sync_capacitor_charge_threshold); -// if(is_trailing_edge && _sync_capacitor_charge_threshold - _sync_capacitor_charge_level < 3000) -// { -// printf("%d\n", _sync_capacitor_charge_threshold - _sync_capacitor_charge_level); -// } - // simplified colour burst logic: if it's within the back porch we'll take it if(scan->type == Scan::Type::ColourBurst) { From a3c2cd880ea7f152b43c84f08c254c76ed60815d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Apr 2016 21:01:09 -0400 Subject: [PATCH 232/307] I discovered a further post on this on the STH forums; apparently the Electron simply asserts sync continuously. No breaks. --- Machines/Electron/Electron.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 7d82e8e30..0e51c89bf 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -699,8 +699,7 @@ inline void Machine::update_display() { // wait for the line to complete before signalling if(final_line == line) return; - _crt->output_blank(9 * crt_cycles_multiplier); - _crt->output_sync(119 * crt_cycles_multiplier); + _crt->output_sync(128 * crt_cycles_multiplier); _displayOutputPosition += 128; continue; } @@ -710,8 +709,7 @@ inline void Machine::update_display() { // wait for the line to complete before signalling if(final_line == line) return; - _crt->output_blank(9 * crt_cycles_multiplier); - _crt->output_sync(55 * crt_cycles_multiplier); + _crt->output_sync(64 * crt_cycles_multiplier); _crt->output_blank(64 * crt_cycles_multiplier); _displayOutputPosition += 128; continue; From 7276a06cc00bbcc26abc2b524c3c9db885715789 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Apr 2016 21:47:23 -0400 Subject: [PATCH 233/307] Added a helper to calculate a visible rect based on output timings, used it to scale the Electron output up to the full window size. --- Machines/Electron/Electron.cpp | 1 + .../Documents/Atari2600Document.swift | 1 - Outputs/CRT/CRT.cpp | 38 +++++++++++++++++++ Outputs/CRT/CRT.hpp | 2 + Outputs/CRT/CRTTypes.hpp | 2 +- 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 0e51c89bf..16ccfa9a1 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -67,6 +67,7 @@ void Machine::setup_output() // "return vec4(1.0);" "}"); _crt->set_output_device(Outputs::CRT::Monitor); + _crt->set_visible_area(_crt->get_rect_for_area(first_graphics_line - 3, 256, first_graphics_cycle * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); // The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; // however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index c9b385399..8e42b1186 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -19,7 +19,6 @@ class Atari2600Document: MachineDocument { override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) atari2600.view = openGLView -// openGLView.frameBounds = CGRectMake(0.1, 0.1, 0.8, 0.8) } override class func autosavesInPlace() -> Bool { diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 86c909722..d5c99866c 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -357,3 +357,41 @@ void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider }; output_scan(&scan); } + +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; + + // 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_retrace_period = horizontal_period - horizontal_scan_period; + + float start_x = (float)((unsigned)first_cycle_after_sync - horizontal_retrace_period) / (float)horizontal_scan_period; + 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_retrace_period = vertical_period - vertical_scan_period; + float start_y = (float)(((unsigned)first_line_after_sync * horizontal_period) - vertical_retrace_period) / (float)vertical_scan_period; + float height = (float)((unsigned)number_of_lines * horizontal_period) / vertical_scan_period; + + // adjust to ensure aspect ratio is correct + float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f); + float ideal_width = height * adjusted_aspect_ratio; + if(ideal_width > width) + { + start_x -= (ideal_width - width) * 0.5f; + width = ideal_width; + } + else + { + float ideal_height = width / adjusted_aspect_ratio; + start_y -= (ideal_height - height) * 0.5f; + height = ideal_height; + } + + return Rect(start_x, start_y, width, height); +} diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 5303fbe6c..2f2dadc26 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -247,6 +247,8 @@ class CRT { { _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); }; } diff --git a/Outputs/CRT/CRTTypes.hpp b/Outputs/CRT/CRTTypes.hpp index 262f01616..842c2f9f3 100644 --- a/Outputs/CRT/CRTTypes.hpp +++ b/Outputs/CRT/CRTTypes.hpp @@ -23,7 +23,7 @@ struct Rect { Rect() {} Rect(float x, float y, float width, float height) : - origin({.x = x, .y = y}), size({.width = width, .height =height}) {} + origin({.x = x, .y = y}), size({.width = width, .height = height}) {} }; enum DisplayType { From 2cc72169ffcbeee4e4b8b5210c115c33e3dfbc26 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Apr 2016 23:12:56 -0400 Subject: [PATCH 234/307] Ensured machines can nominate their own aspect ratio windows. Switched to 11/10 for the Electron. --- Machines/Electron/Electron.cpp | 2 +- Machines/Electron/Electron.hpp | 2 +- .../Mac/Clock Signal/Base.lproj/ElectronDocument.xib | 8 ++++---- .../Mac/Clock Signal/Documents/Atari2600Document.swift | 2 +- .../Mac/Clock Signal/Documents/ElectronDocument.swift | 3 ++- OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h | 2 -- OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm | 4 ++-- .../Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h | 2 +- OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h | 2 +- OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm | 7 +++---- 10 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 16ccfa9a1..5062c7298 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -55,7 +55,7 @@ Machine::Machine() : _tape.set_delegate(this); } -void Machine::setup_output() +void Machine::setup_output(float aspect_ratio) { _crt = std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1)); _crt->set_rgb_sampling_function( diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 9a7b44172..b95b947c9 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -151,7 +151,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { void set_key_state(Key key, bool isPressed); - void setup_output(); + void setup_output(float aspect_ratio); Outputs::CRT::CRT *get_crt() { return _crt.get(); } Outputs::Speaker *get_speaker() { return &_speaker; } diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib index b0920d364..520b3577b 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib @@ -1,5 +1,5 @@ - + @@ -16,15 +16,15 @@ - + - + - + diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index 8e42b1186..6c586c360 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -18,7 +18,7 @@ class Atari2600Document: MachineDocument { override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) - atari2600.view = openGLView + atari2600.setView(openGLView, aspectRatio: 4.0 / 3.0) } override class func autosavesInPlace() -> Bool { diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 14aa80ef1..31ba148cb 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -16,6 +16,7 @@ class ElectronDocument: MachineDocument { override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) self.intendedCyclesPerSecond = 2000000 + aController.window?.contentAspectRatio = NSSize(width: 11.0, height: 10.0) openGLView.performWithGLContext({ if let osPath = NSBundle.mainBundle().pathForResource("os", ofType: "rom") { self.electron.setOSROM(NSData(contentsOfFile: osPath)!) @@ -23,7 +24,7 @@ class ElectronDocument: MachineDocument { if let basicPath = NSBundle.mainBundle().pathForResource("basic", ofType: "rom") { self.electron.setBASICROM(NSData(contentsOfFile: basicPath)!) } - self.electron.view = self.openGLView + self.electron.setView(self.openGLView, aspectRatio: 11.0 / 10.0) self.electron.audioQueue = self.audioQueue }) } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index 9cb9b48e6..e3ab2f8c5 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -11,8 +11,6 @@ @interface CSElectron : CSMachine -- (void)setupOutput; - - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; - (void)setROM:(nonnull NSData *)rom slot:(int)slot; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 4809a2a27..559caafdb 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -152,9 +152,9 @@ } } -- (void)setupOutput { +- (void)setupOutputWithAspectRatio:(float)aspectRatio { @synchronized(self) { - _electron.setup_output(); + _electron.setup_output(aspectRatio); } } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 2c16901f3..be409e6be 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -16,6 +16,6 @@ - (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; - (void)performAsync:(dispatch_block_t)action; - (void)performSync:(dispatch_block_t)action; -- (void)setupOutput; +- (void)setupOutputWithAspectRatio:(float)aspectRatio; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h index ee4b6759a..88bb5b682 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -13,8 +13,8 @@ @interface CSMachine : NSObject - (void)runForNumberOfCycles:(int)numberOfCycles; +- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio; -@property (nonatomic, weak) CSOpenGLView *view; @property (nonatomic, weak) AudioQueue *audioQueue; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index 8530a595f..cf19e4b9f 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -51,12 +51,11 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { dispatch_async(_serialDispatchQueue, action); } -- (void)setupOutput {} +- (void)setupOutputWithAspectRatio:(float)aspectRatio {} -- (void)setView:(CSOpenGLView *)view { - _view = view; +- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio { [view performWithGLContext:^{ - [self setupOutput]; + [self setupOutputWithAspectRatio:aspectRatio]; }]; } From d100c755b7598ced8364e4bed3b96c062f054f4a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Apr 2016 23:13:14 -0400 Subject: [PATCH 235/307] The final line runs on a diagonal. Leave room for that. --- Outputs/CRT/CRT.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index d5c99866c..55157b83c 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -362,6 +362,7 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_ { first_cycle_after_sync *= _time_multiplier; number_of_cycles *= _time_multiplier; + number_of_lines++; // determine prima facie x extent unsigned int horizontal_period = _horizontal_flywheel->get_standard_period(); From 069ec2e8891b93c032653691dfaa5cf858431765 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Apr 2016 23:13:54 -0400 Subject: [PATCH 236/307] In search of the cause of performance issues again, excised the per-pixel sin until it can be further evaluated. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index acd334fc5..407b0013e 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -417,7 +417,7 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "void main(void)" "{" - "fragColour = rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying) * vec4(1.0, 1.0, 1.0, alpha*sin(lateralVarying));" // + "fragColour = rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying) * vec4(1.0, 1.0, 1.0, alpha);" //*sin(lateralVarying) "}" , sampling_function); } From fe8b0ebc7efa34b16de915dc8a834a26f2c80c61 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Apr 2016 22:31:13 -0400 Subject: [PATCH 237/307] Tidied up a little, mostly bumping things out of just-in-time creation that I can just do well in advance. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 201 ++++++++++++++-------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 1 - 2 files changed, 100 insertions(+), 102 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 407b0013e..21022ad03 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -12,52 +12,6 @@ #include "CRTOpenGL.hpp" #include "../../../SignalProcessing/FIRFilter.hpp" -using namespace Outputs::CRT; - -namespace { - static const GLenum first_supplied_buffer_texture_unit = 3; -} - -OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : - _run_write_pointer(0), - _output_mutex(new std::mutex), - _visible_area(Rect(0, 0, 1, 1)), - _composite_src_output_y(0), - _composite_shader(nullptr), - _rgb_shader(nullptr), - _output_buffer_data(nullptr), - _output_buffer_sync(nullptr), - _input_texture_data(nullptr) -{ - _run_builders = new CRTRunBuilder *[NumberOfFields]; - for(int builder = 0; builder < NumberOfFields; builder++) - { - _run_builders[builder] = new CRTRunBuilder(); - } -// _composite_src_runs = std::unique_ptr(new CRTRunBuilder(InputVertexSize)); - - _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); -} - -OpenGLOutputBuilder::~OpenGLOutputBuilder() -{ - for(int builder = 0; builder < NumberOfFields; builder++) - { - delete _run_builders[builder]; - } - delete[] _run_builders; - - glUnmapBuffer(GL_ARRAY_BUFFER); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - glDeleteTextures(1, &textureName); - glDeleteBuffers(1, &_input_texture_array); - glDeleteBuffers(1, &output_array_buffer); - glDeleteVertexArrays(1, &output_vertex_array); - - free(_composite_shader); - free(_rgb_shader); -} - static const GLint internalFormatForDepth(size_t depth) { switch(depth) @@ -82,46 +36,62 @@ static const GLenum formatForDepth(size_t depth) } } -void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) + +using namespace Outputs::CRT; + +namespace { + static const GLenum first_supplied_buffer_texture_unit = 3; +} + +OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : + _run_write_pointer(0), + _output_mutex(new std::mutex), + _visible_area(Rect(0, 0, 1, 1)), + _composite_src_output_y(0), + _composite_shader(nullptr), + _rgb_shader(nullptr), + _output_buffer_data(nullptr), + _output_buffer_sync(nullptr), + _input_texture_data(nullptr), + _output_buffer_data_pointer(0) { - // establish essentials - if(!composite_input_shader_program && !rgb_shader_program) + _run_builders = new CRTRunBuilder *[NumberOfFields]; + for(int builder = 0; builder < NumberOfFields; builder++) { - // generate and bind texture for input data - glGenTextures(1, &textureName); - glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_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); + _run_builders[builder] = new CRTRunBuilder(); + } + _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); - glGenBuffers(1, &_input_texture_array); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _input_texture_array); - _input_texture_array_size = (GLsizeiptr)(InputBufferBuilderWidth * InputBufferBuilderHeight * _buffer_builder->bytes_per_pixel); - glBufferData(GL_PIXEL_UNPACK_BUFFER, _input_texture_array_size, NULL, GL_STREAM_DRAW); + // create the surce texture + glGenTextures(1, &textureName); + glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_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->bytes_per_pixel), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, nullptr); - glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(_buffer_builder->bytes_per_pixel), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, nullptr); + // create a pixel unpack buffer + glGenBuffers(1, &_input_texture_array); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _input_texture_array); + _input_texture_array_size = (GLsizeiptr)(InputBufferBuilderWidth * InputBufferBuilderHeight * _buffer_builder->bytes_per_pixel); + glBufferData(GL_PIXEL_UNPACK_BUFFER, _input_texture_array_size, NULL, GL_STREAM_DRAW); - prepare_composite_input_shader(); - prepare_rgb_output_shader(); + // map the buffer for clients + _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - glGenVertexArrays(1, &output_vertex_array); - glGenBuffers(1, &output_array_buffer); - output_vertices_per_slice = 0; + // create the output vertex array + glGenVertexArrays(1, &output_vertex_array); + glBindVertexArray(output_vertex_array); - glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); + // 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); - glBufferData(GL_ARRAY_BUFFER, OutputVertexBufferDataSize, NULL, GL_STREAM_DRAW); - _output_buffer_data_pointer = 0; - - glBindVertexArray(output_vertex_array); - prepare_output_vertex_array(); - - // This should return either an actual framebuffer number, if this is a target with a framebuffer intended for output, - // or 0 if no framebuffer is bound, in which case 0 is also what we want to supply to bind the implied framebuffer. So - // it works either way. - glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&defaultFramebuffer); + // map that buffer too, for any CRT activity that may occur before the first draw + _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); // Create intermediate textures and bind to slots 0, 1 and 2 // glActiveTexture(GL_TEXTURE0); @@ -130,6 +100,40 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); // glActiveTexture(GL_TEXTURE2); // filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); +} + +OpenGLOutputBuilder::~OpenGLOutputBuilder() +{ + for(int builder = 0; builder < NumberOfFields; builder++) + { + delete _run_builders[builder]; + } + delete[] _run_builders; + + glUnmapBuffer(GL_ARRAY_BUFFER); + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + glDeleteTextures(1, &textureName); + glDeleteBuffers(1, &_input_texture_array); + glDeleteBuffers(1, &output_array_buffer); + glDeleteVertexArrays(1, &output_vertex_array); + + free(_composite_shader); + free(_rgb_shader); +} + +void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) +{ + // establish essentials + if(!composite_input_shader_program && !rgb_shader_program) + { + prepare_composite_input_shader(); + prepare_rgb_output_shader(); + prepare_output_vertex_array(); + + // This should return either an actual framebuffer number, if this is a target with a framebuffer intended for output, + // or 0 if no framebuffer is bound, in which case 0 is also what we want to supply to bind the implied framebuffer. So + // it works either way. + glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&defaultFramebuffer); } // lock down any further work on the current frame @@ -145,29 +149,25 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it -// for(unsigned int buffer = 0; buffer < _buffer_builder->number_of_buffers; buffer++) -// { -// glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit + buffer); - if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) - { - glTexSubImage2D( GL_TEXTURE_2D, 0, - 0, (GLint)_buffer_builder->last_uploaded_line, - InputBufferBuilderWidth, (GLint)(InputBufferBuilderHeight - _buffer_builder->last_uploaded_line), - formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, - (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->bytes_per_pixel)); - _buffer_builder->last_uploaded_line = 0; - } + if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) + { + glTexSubImage2D( GL_TEXTURE_2D, 0, + 0, (GLint)_buffer_builder->last_uploaded_line, + InputBufferBuilderWidth, (GLint)(InputBufferBuilderHeight - _buffer_builder->last_uploaded_line), + formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, + (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->bytes_per_pixel)); + _buffer_builder->last_uploaded_line = 0; + } - if(_buffer_builder->_next_write_y_position > _buffer_builder->last_uploaded_line) - { - glTexSubImage2D( GL_TEXTURE_2D, 0, - 0, (GLint)_buffer_builder->last_uploaded_line, - InputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), - formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, - (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->bytes_per_pixel)); - _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; - } -// } + if(_buffer_builder->_next_write_y_position > _buffer_builder->last_uploaded_line) + { + glTexSubImage2D( GL_TEXTURE_2D, 0, + 0, (GLint)_buffer_builder->last_uploaded_line, + InputBufferBuilderWidth, (GLint)(1 + _buffer_builder->_next_write_y_position - _buffer_builder->last_uploaded_line), + formatForDepth(_buffer_builder->bytes_per_pixel), GL_UNSIGNED_BYTE, + (void *)(_buffer_builder->last_uploaded_line * InputBufferBuilderWidth * _buffer_builder->bytes_per_pixel)); + _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; + } // check for anything to decode from composite // if(_composite_src_runs->number_of_vertices) @@ -230,7 +230,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } // drawing commands having been issued, reclaim the array buffer pointer -// _buffer_builder->move_to_new_line(); _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _output_mutex->unlock(); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 1144bd4ea..07866d68b 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -75,7 +75,6 @@ class OpenGLOutputBuilder { std::unique_ptr composite_input_shader_program, composite_output_shader_program; GLuint output_array_buffer, output_vertex_array; - size_t output_vertices_per_slice; GLint windowSizeUniform, timestampBaseUniform; GLint boundsOriginUniform, boundsSizeUniform; From 4d889d9c7fe196ff9dfa045267398a96e6282b1d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Apr 2016 22:35:11 -0400 Subject: [PATCH 238/307] Made an attempt slightly to simplify the fragment processor, at both ends. --- Machines/Electron/Electron.cpp | 6 +++--- Outputs/CRT/Internals/CRTOpenGL.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 5062c7298..278ef365a 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -59,12 +59,12 @@ void Machine::setup_output(float aspect_ratio) { _crt = std::unique_ptr(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1)); _crt->set_rgb_sampling_function( - "vec4 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" + "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" "{" "uint texValue = texture(sampler, coordinate).r;" "texValue >>= 4 - (int(icoordinate.x * 2) & 1)*4;" - "return vec4(texValue & 4u, texValue & 2u, texValue & 1u, 1.0);" -// "return vec4(1.0);" + "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" +// "return vec3(1.0);" "}"); _crt->set_output_device(Outputs::CRT::Monitor); _crt->set_visible_area(_crt->get_rect_for_area(first_graphics_line - 3, 256, first_graphics_cycle * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 21022ad03..fc11a12ad 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -416,7 +416,7 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "void main(void)" "{" - "fragColour = rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying) * vec4(1.0, 1.0, 1.0, alpha);" //*sin(lateralVarying) + "fragColour = vec4(rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying), alpha);" //*sin(lateralVarying) "}" , sampling_function); } From 4be8053ba9ec4322b47bd77e2821e2fa74967b08 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Apr 2016 22:38:49 -0400 Subject: [PATCH 239/307] Updated documentation. --- Outputs/CRT/CRT.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 2f2dadc26..25c2482c9 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -217,9 +217,12 @@ class CRT { format will be applied. @param shader A GLSL fragent including a function with the signature - `vec4 rgb_sample(vec2 coordinate)` that evaluates to an RGBA colour as a function of - the source buffer sampling location. - The shader may assume a uniform array of sampler2Ds named `buffers` provides access to all input data. + `vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)` that evaluates to an RGB colour + as a function of: + + * `usampler2D sampler` representing the source buffer; + * `vec2 coordinate` representing the source buffer location to sample from in the range [0, 1); and + * `vec2 icoordinate` representing the source buffer location to sample from as a pixel count, for easier multiple-pixels-per-byte unpacking. */ inline void set_rgb_sampling_function(const char *shader) { From 04d1b65c93221195d818187061a419274c1be684 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Apr 2016 22:48:47 -0400 Subject: [PATCH 240/307] Reinstated texture target creation, cut them down to merely RGB. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 19 +++++++++++-------- Outputs/CRT/Internals/TextureTarget.cpp | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index fc11a12ad..b21dc0e22 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -62,6 +62,14 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : } _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); + // Create intermediate textures and bind to slots 0, 1 and 2 + glActiveTexture(GL_TEXTURE0); + compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + glActiveTexture(GL_TEXTURE1); + filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + glActiveTexture(GL_TEXTURE2); + filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + // create the surce texture glGenTextures(1, &textureName); glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit); @@ -92,14 +100,6 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : // map that buffer too, for any CRT activity that may occur before the first draw _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - - // Create intermediate textures and bind to slots 0, 1 and 2 -// glActiveTexture(GL_TEXTURE0); -// compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); -// glActiveTexture(GL_TEXTURE1); -// filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); -// glActiveTexture(GL_TEXTURE2); -// filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); } OpenGLOutputBuilder::~OpenGLOutputBuilder() @@ -134,6 +134,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // or 0 if no framebuffer is bound, in which case 0 is also what we want to supply to bind the implied framebuffer. So // it works either way. glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&defaultFramebuffer); + + // TODO: is this sustainable, cross-platform? If so, why store it at all? + defaultFramebuffer = 0; } // lock down any further work on the current frame diff --git a/Outputs/CRT/Internals/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp index 60c030f47..5a622e09b 100644 --- a/Outputs/CRT/Internals/TextureTarget.cpp +++ b/Outputs/CRT/Internals/TextureTarget.cpp @@ -18,8 +18,8 @@ TextureTarget::TextureTarget(GLsizei width, GLsizei height) : _width(width), _he glGenTextures(1, &_texture); glBindTexture(GL_TEXTURE_2D, _texture); - uint8_t *emptySpace = new uint8_t[width*height*4]; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, emptySpace); + uint8_t *emptySpace = new uint8_t[width*height*3]; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, (GLsizei)width, (GLsizei)height, 0, GL_RGB, GL_UNSIGNED_BYTE, emptySpace); delete[] emptySpace; glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); From 955e1857907455864d7682978ed39e475043a0be Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 13 Apr 2016 19:19:04 -0400 Subject: [PATCH 241/307] Minor simplification. --- Machines/Electron/Electron.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 278ef365a..14333d068 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -62,7 +62,7 @@ void Machine::setup_output(float aspect_ratio) "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" "{" "uint texValue = texture(sampler, coordinate).r;" - "texValue >>= 4 - (int(icoordinate.x * 2) & 1)*4;" + "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" // "return vec3(1.0);" "}"); From 2ea02ed1273a58dc92d48756bd1dc00c4748abf7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 13 Apr 2016 22:14:18 -0400 Subject: [PATCH 242/307] A source array buffer is also now created, mapped, unmapped, etc. --- Machines/Electron/Electron.cpp | 1 - Outputs/CRT/Internals/CRTConstants.hpp | 3 ++- Outputs/CRT/Internals/CRTOpenGL.cpp | 21 +++++++++++++++++++-- Outputs/CRT/Internals/CRTOpenGL.hpp | 5 ++++- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 14333d068..81c466d41 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -64,7 +64,6 @@ void Machine::setup_output(float aspect_ratio) "uint texValue = texture(sampler, coordinate).r;" "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" -// "return vec3(1.0);" "}"); _crt->set_output_device(Outputs::CRT::Monitor); _crt->set_visible_area(_crt->get_rect_for_area(first_graphics_line - 3, 256, first_graphics_cycle * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index d14e7aee5..d343164f8 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -42,8 +42,9 @@ const int InputBufferBuilderHeight = 1024; const int IntermediateBufferWidth = 2048; const int IntermediateBufferHeight = 2048; -// Some internal +// Some internal buffer sizes const GLsizeiptr OutputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize +const GLsizeiptr SourceVertexBufferDataSize = 87360; // a multiple of 2 * OutputVertexSize // Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index b21dc0e22..5ab01bb02 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -51,7 +51,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _composite_shader(nullptr), _rgb_shader(nullptr), _output_buffer_data(nullptr), - _output_buffer_sync(nullptr), + _source_buffer_data(nullptr), _input_texture_data(nullptr), _output_buffer_data_pointer(0) { @@ -100,6 +100,14 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : // map that buffer too, for any CRT activity that may occur before the first draw _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + + // 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); + + // map that buffer too, for any CRT activity that may occur before the first draw + _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); } OpenGLOutputBuilder::~OpenGLOutputBuilder() @@ -115,6 +123,7 @@ OpenGLOutputBuilder::~OpenGLOutputBuilder() glDeleteTextures(1, &textureName); glDeleteBuffers(1, &_input_texture_array); glDeleteBuffers(1, &output_array_buffer); + glDeleteBuffers(1, &source_array_buffer); glDeleteVertexArrays(1, &output_vertex_array); free(_composite_shader); @@ -143,11 +152,14 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _output_mutex->lock(); // release the mapping, giving up on trying to draw if data has been lost + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); if(glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE) { for(int c = 0; c < NumberOfFields; c++) _run_builders[c]->reset(); } + glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); + glUnmapBuffer(GL_ARRAY_BUFFER); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); // upload more source pixel data if any; we'll always resubmit the last line submitted last @@ -194,8 +206,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // update uniforms push_size_uniforms(output_width, output_height); - // Ensure we're back on the output framebuffer + // Ensure we're back on the output framebuffer, drawing from the output array buffer glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); +// glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); // clear the buffer glClear(GL_COLOR_BUFFER_BIT); @@ -233,7 +246,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } // drawing commands having been issued, reclaim the array buffer pointer + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); + _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); _output_mutex->unlock(); } @@ -554,6 +570,7 @@ void OpenGLOutputBuilder::prepare_output_vertex_array() glEnableVertexAttribArray((GLuint)timestampAttribute); const GLsizei vertexStride = OutputVertexSize; + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); glVertexAttribPointer((GLuint)positionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfPosition); glVertexAttribPointer((GLuint)textureCoordinatesAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfTexCoord); glVertexAttribPointer((GLuint)timestampAttribute, 4, GL_UNSIGNED_INT, GL_FALSE, vertexStride, (void *)OutputVertexOffsetOfTimestamp); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 07866d68b..a6c8837b1 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -75,6 +75,7 @@ class OpenGLOutputBuilder { std::unique_ptr composite_input_shader_program, composite_output_shader_program; GLuint output_array_buffer, output_vertex_array; + GLuint source_array_buffer; GLint windowSizeUniform, timestampBaseUniform; GLint boundsOriginUniform, boundsSizeUniform; @@ -212,7 +213,9 @@ class OpenGLOutputBuilder { uint8_t *_output_buffer_data; size_t _output_buffer_data_pointer; - GLsync _output_buffer_sync; + + uint8_t *_source_buffer_data; + size_t _source_buffer_data_pointer; }; } From 026ce0255f57650650b27a9d5f16bc1a1d810ff4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 13 Apr 2016 22:20:13 -0400 Subject: [PATCH 243/307] Source runs are now captured, and that buffer appropriately reset. --- Machines/Electron/Electron.cpp | 2 +- Outputs/CRT/Internals/CRTOpenGL.cpp | 4 ++++ Outputs/CRT/Internals/CRTOpenGL.hpp | 8 +++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 81c466d41..cd8bf65c3 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -65,7 +65,7 @@ void Machine::setup_output(float aspect_ratio) "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" "}"); - _crt->set_output_device(Outputs::CRT::Monitor); + _crt->set_output_device(Outputs::CRT::Television); _crt->set_visible_area(_crt->get_rect_for_area(first_graphics_line - 3, 256, first_graphics_cycle * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); // The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 5ab01bb02..2987c7547 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -248,9 +248,13 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // drawing commands having been issued, reclaim the array buffer pointer glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _source_buffer_data_pointer = 0; + _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + _output_mutex->unlock(); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index a6c8837b1..4407b3e00 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -106,18 +106,20 @@ class OpenGLOutputBuilder { inline uint8_t *get_next_source_run() { - return nullptr; + _output_mutex->lock(); + return &_source_buffer_data[_source_buffer_data_pointer]; } inline void complete_source_run() { + _source_buffer_data_pointer += 2 * SourceVertexSize; + _output_mutex->unlock(); } inline uint8_t *get_next_output_run() { _output_mutex->lock(); - uint8_t *pointer = &_output_buffer_data[_output_buffer_data_pointer]; - return pointer; + return &_output_buffer_data[_output_buffer_data_pointer]; } inline void complete_output_run(size_t vertices_written) From 6a17c2992db7339365063f6797e67fd86168523e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 13 Apr 2016 22:31:59 -0400 Subject: [PATCH 244/307] Introduced a compile-time configurable audio divider, set it arbitrarily to '8' for now. Discovered why my graphics aren't centred and added a TODO. --- Machines/Electron/Electron.cpp | 20 ++++++++++++-------- Machines/Electron/Electron.hpp | 2 +- Outputs/Speaker.hpp | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index cd8bf65c3..81226b084 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -31,6 +31,8 @@ namespace { static const unsigned int real_time_clock_interrupt_1 = 16704; static const unsigned int real_time_clock_interrupt_2 = 56704; + + static const unsigned int clock_rate_audio_divider = 8; } #define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) @@ -42,7 +44,6 @@ Machine::Machine() : _frameCycles(0), _displayOutputPosition(0), _audioOutputPosition(0), - _audioOutputPositionError(0), _current_pixel_line(-1), _use_fast_tape_hack(false), _crt(nullptr) @@ -65,13 +66,15 @@ void Machine::setup_output(float aspect_ratio) "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" "}"); - _crt->set_output_device(Outputs::CRT::Television); - _crt->set_visible_area(_crt->get_rect_for_area(first_graphics_line - 3, 256, first_graphics_cycle * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); + _crt->set_output_device(Outputs::CRT::Monitor); + + // TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. + _crt->set_visible_area(_crt->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); // The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that; // however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So // run the speaker at a 2000000Hz input rate, at least for the time being. - _speaker.set_input_rate(2000000); + _speaker.set_input_rate(2000000 / clock_rate_audio_divider); } unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) @@ -495,9 +498,10 @@ inline void Machine::evaluate_interrupts() inline void Machine::update_audio() { - int difference = (int)_frameCycles - _audioOutputPosition; - _speaker.run_for_cycles(difference); - _audioOutputPosition = (int)_frameCycles; + unsigned int difference = _frameCycles - _audioOutputPosition; + _audioOutputPosition = _frameCycles; + _speaker.run_for_cycles(difference / clock_rate_audio_divider); + _audioOutputPositionError = difference % clock_rate_audio_divider; } inline void Machine::start_pixel_line() @@ -853,7 +857,7 @@ void Speaker::skip_samples(unsigned int number_of_samples) void Speaker::set_divider(uint8_t divider) { - _divider = divider * 32; + _divider = divider * 32 / clock_rate_audio_divider; } void Speaker::set_is_enabled(bool is_enabled) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index b95b947c9..afafba2ee 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -187,7 +187,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { // Counters related to simultaneous subsystems; unsigned int _frameCycles, _displayOutputPosition; - int _audioOutputPosition, _audioOutputPositionError; + unsigned int _audioOutputPosition, _audioOutputPositionError; struct { uint16_t forty1bpp[256]; diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 4eb6a9f3f..125b97663 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -72,7 +72,7 @@ class Speaker { template class Filter: public Speaker { public: - void run_for_cycles(int input_cycles) + void run_for_cycles(unsigned int input_cycles) { if(_coefficients_are_dirty) update_filter_coefficients(); @@ -81,7 +81,7 @@ template class Filter: public Speaker { // fill up as much of the input buffer as possible while(input_cycles) { - unsigned int cycles_to_read = (unsigned int)std::min(input_cycles, _number_of_taps - _input_buffer_depth); + unsigned int cycles_to_read = (unsigned int)std::min((int)input_cycles, _number_of_taps - _input_buffer_depth); static_cast(this)->get_samples(cycles_to_read, &_input_buffer.get()[_input_buffer_depth]); input_cycles -= cycles_to_read; _input_buffer_depth += cycles_to_read; From 323f1a24db925cabf2f71e0c81a85001b6646e38 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Apr 2016 20:30:45 -0400 Subject: [PATCH 245/307] Introduced blackout period solution to vertical sync confusing horizontal sync. Need to find out whether it's accurate. --- Outputs/CRT/CRT.cpp | 7 +++++-- Outputs/CRT/Internals/Flywheel.hpp | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 55157b83c..7e1199b7a 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -281,8 +281,11 @@ void CRT::output_scan(const Scan *const scan) const bool is_leading_edge = (!_is_receiving_sync && this_is_sync); _is_receiving_sync = this_is_sync; - const bool hsync_requested = is_leading_edge; -// const bool hsync_requested = is_trailing_edge && (_sync_period < (_horizontal_flywheel->get_scan_period() >> 2)); + // 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); // simplified colour burst logic: if it's within the back porch we'll take it diff --git a/Outputs/CRT/Internals/Flywheel.hpp b/Outputs/CRT/Internals/Flywheel.hpp index e10d621fd..c3a00a3aa 100644 --- a/Outputs/CRT/Internals/Flywheel.hpp +++ b/Outputs/CRT/Internals/Flywheel.hpp @@ -9,6 +9,8 @@ #ifndef Flywheel_hpp #define Flywheel_hpp +#include + namespace Outputs { namespace CRT { @@ -187,6 +189,14 @@ struct Flywheel return result; } + /*! + @returns `true` if a sync is expected soon or the time at which it was expected was recent. + */ + inline bool is_near_expected_sync() + { + 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 From 104f44f27f9d48b8d9f059b89969dcef2deb584a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Apr 2016 22:20:47 -0400 Subject: [PATCH 246/307] Attempted to improve deinterlacing, gave the CRT full control over blend mode, switched back to 2000000Mhz audio. --- Machines/Electron/Electron.cpp | 2 +- OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m | 3 --- Outputs/CRT/Internals/CRTConstants.hpp | 3 ++- Outputs/CRT/Internals/CRTOpenGL.cpp | 9 ++++++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 81226b084..9005bc3da 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -32,7 +32,7 @@ namespace { static const unsigned int real_time_clock_interrupt_1 = 16704; static const unsigned int real_time_clock_interrupt_2 = 56704; - static const unsigned int clock_rate_audio_divider = 8; + static const unsigned int clock_rate_audio_divider = 1; } #define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index abbdbff4b..797b0f1b0 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -38,9 +38,6 @@ // Activate the display link CVDisplayLinkStart(_displayLink); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); } static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index d343164f8..b2f6b9c69 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -49,7 +49,8 @@ const GLsizeiptr SourceVertexBufferDataSize = 87360; // a multiple of 2 * Output // Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track // run age; that therefore creates a discrete number of fields that are stored. This number should be the -// number of historic fields that are required fully to +// number of historic fields that are required fully to complete a frame. It should be at least two and not +// more than four. const int NumberOfFields = 4; } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 2987c7547..73318df59 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -62,6 +62,9 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : } _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + // Create intermediate textures and bind to slots 0, 1 and 2 glActiveTexture(GL_TEXTURE0); compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); @@ -226,11 +229,11 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out start = _run_builders[run]->start; run = (run - 1 + NumberOfFields) % NumberOfFields; } - glUniform4fv(timestampBaseUniform, 1, timestampBases); if(count > 0) { // draw + glUniform4fv(timestampBaseUniform, 1, timestampBases); GLsizei primitive_count = (GLsizei)(count / OutputVertexSize); GLsizei max_count = (GLsizei)((OutputVertexBufferDataSize - start) / OutputVertexSize); if(primitive_count < max_count) @@ -395,7 +398,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "iSrcCoordinatesVarying = srcCoordinates;" "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" - "alpha = 10.0 * exp(-age * 2.0);" + "alpha = exp(-age) + 0.2;" "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" @@ -439,7 +442,7 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "void main(void)" "{" - "fragColour = vec4(rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying), alpha);" //*sin(lateralVarying) + "fragColour = vec4(rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying), clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" // "}" , sampling_function); } From 6ff9ffba6c2e0ce6e0151f1093015703ee2c34f3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Apr 2016 15:51:28 -0400 Subject: [PATCH 247/307] Switching temporarily to an attempt to draw input runs as if RGB: shader compiles, fixed a race condition on out-of-bounds accesses for the source buffer. --- Machines/Electron/Electron.cpp | 2 +- Outputs/CRT/Internals/CRTConstants.hpp | 2 +- Outputs/CRT/Internals/CRTOpenGL.cpp | 20 ++++++++++++++------ Outputs/CRT/Internals/CRTOpenGL.hpp | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 9005bc3da..7ac235056 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -66,7 +66,7 @@ void Machine::setup_output(float aspect_ratio) "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" "}"); - _crt->set_output_device(Outputs::CRT::Monitor); + _crt->set_output_device(Outputs::CRT::Television); // TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. _crt->set_visible_area(_crt->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index b2f6b9c69..c42a7fbb9 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -44,7 +44,7 @@ const int IntermediateBufferHeight = 2048; // Some internal buffer sizes const GLsizeiptr OutputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize -const GLsizeiptr SourceVertexBufferDataSize = 87360; // a multiple of 2 * OutputVertexSize +const GLsizeiptr SourceVertexBufferDataSize = 87360; // a multiple of 2 * SourceVertexSize // Runs are divided discretely by vertical syncs in order to put a usable bounds on the uniform used to track diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 73318df59..554d10f75 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -53,7 +53,8 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _output_buffer_data(nullptr), _source_buffer_data(nullptr), _input_texture_data(nullptr), - _output_buffer_data_pointer(0) + _output_buffer_data_pointer(0), + _source_buffer_data_pointer(0) { _run_builders = new CRTRunBuilder *[NumberOfFields]; for(int builder = 0; builder < NumberOfFields; builder++) @@ -211,7 +212,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // Ensure we're back on the output framebuffer, drawing from the output array buffer glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); -// glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); + glBindVertexArray(output_vertex_array); // clear the buffer glClear(GL_COLOR_BUFFER_BIT); @@ -254,7 +255,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - _source_buffer_data_pointer = 0; _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); @@ -310,14 +310,18 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "in float phaseTime;" "uniform float phaseCyclesPerTick;" + "uniform usampler2D texID;" + "uniform ivec2 outputTextureSize;" "out vec2 inputPositionVarying;" "out float phaseVarying;" "void main(void)" "{" - "inputPositionVarying = vec2(inputPositionVarying.x / inputTextureSize.x, (inputPositionVarying.y + 0.5) / inputTextureSize.y);" - "gl_Position = vec4(outputPosition.x * 2.0 / outputTextureSize - 1.0, outputPosition.y * 2.0 / outputTextureSize - 1.0, 0.0, 1.0);" + "ivec2 textureSize = textureSize(texID, 0);" + "inputPositionVarying = vec2(inputPositionVarying.x / textureSize.x, (inputPositionVarying.y + 0.5) / textureSize.y);" + + "gl_Position = vec4(outputPosition / outputTextureSize, 0.0, 1.0);" "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAndAmplitude.x) * 2.0 * 3.141592654;" "}"); } @@ -327,6 +331,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() const char *composite_shader = _composite_shader; if(!composite_shader) { + composite_shader = _rgb_shader; // TODO: synthesise an RGB -> (selected colour space) shader } @@ -344,7 +349,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "void main(void)" "{" - "fragColour = vec4(composite_sample(inputPositionVarying, phaseVarying), 0.0, 0.0, 1.0);" + "fragColour = vec4(rgb_sample(texID, inputPositionVarying, inputPositionVarying), 1.0);" // composite "}" , composite_shader); } @@ -470,9 +475,12 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() GLint texIDUniform = composite_input_shader_program->get_uniform_location("texID"); GLint phaseCyclesPerTickUniform = composite_input_shader_program->get_uniform_location("phaseCyclesPerTick"); + GLint outputTextureSizeUniform = composite_input_shader_program->get_uniform_location("outputTextureSize"); + composite_input_shader_program->bind(); glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); glUniform1f(phaseCyclesPerTickUniform, (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line)); + glUniform2i(outputTextureSizeUniform, IntermediateBufferWidth, IntermediateBufferHeight); } free(vertex_shader); free(fragment_shader); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 4407b3e00..9e92675bd 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -112,7 +112,7 @@ class OpenGLOutputBuilder { inline void complete_source_run() { - _source_buffer_data_pointer += 2 * SourceVertexSize; + _source_buffer_data_pointer = (_source_buffer_data_pointer + 2 * SourceVertexSize) % SourceVertexBufferDataSize; _output_mutex->unlock(); } From abce0ed3c4e1bad3b2fcbc75673b3204b271ad04 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Apr 2016 16:17:23 -0400 Subject: [PATCH 248/307] Added setup of the source vertex array. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 28 +++++++++++++++++++++++++++- Outputs/CRT/Internals/CRTOpenGL.hpp | 1 + 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 554d10f75..367da1859 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -140,6 +140,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out if(!composite_input_shader_program && !rgb_shader_program) { prepare_composite_input_shader(); + prepare_source_vertex_array(); + prepare_rgb_output_shader(); prepare_output_vertex_array(); @@ -319,7 +321,7 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "void main(void)" "{" "ivec2 textureSize = textureSize(texID, 0);" - "inputPositionVarying = vec2(inputPositionVarying.x / textureSize.x, (inputPositionVarying.y + 0.5) / textureSize.y);" + "inputPositionVarying = vec2(inputPosition.x / textureSize.x, (inputPosition.y + 0.5) / textureSize.y);" "gl_Position = vec4(outputPosition / outputTextureSize, 0.0, 1.0);" "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAndAmplitude.x) * 2.0 * 3.141592654;" @@ -486,6 +488,30 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() free(fragment_shader); } +void OpenGLOutputBuilder::prepare_source_vertex_array() +{ + if(composite_input_shader_program) + { + GLint inputPositionAttribute = composite_input_shader_program->get_attrib_location("inputPosition"); + GLint outputPositionAttribute = composite_input_shader_program->get_attrib_location("outputPosition"); + GLint phaseAndAmplitudeAttribute = composite_input_shader_program->get_attrib_location("phaseAndAmplitude"); + GLint phaseTimeAttribute = composite_input_shader_program->get_attrib_location("phaseTime"); + + glEnableVertexAttribArray((GLuint)inputPositionAttribute); + glEnableVertexAttribArray((GLuint)outputPositionAttribute); + glEnableVertexAttribArray((GLuint)phaseAndAmplitudeAttribute); + glEnableVertexAttribArray((GLuint)phaseTimeAttribute); + + const GLsizei vertexStride = SourceVertexSize; + glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); + glVertexAttribPointer((GLuint)inputPositionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfInputPosition); + glVertexAttribPointer((GLuint)outputPositionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfOutputPosition); + glVertexAttribPointer((GLuint)phaseAndAmplitudeAttribute, 2, GL_UNSIGNED_BYTE, GL_TRUE, vertexStride, (void *)SourceVertexOffsetOfPhaseAndAmplitude); + glVertexAttribPointer((GLuint)phaseTimeAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfPhaseTime); + } +} + + /*void OpenGLOutputBuilder::prepare_output_shader(char *fragment_shader) { char *vertex_shader = get_output_vertex_shader(); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 9e92675bd..8378fdf99 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -48,6 +48,7 @@ class OpenGLOutputBuilder { void prepare_rgb_output_shader(); void prepare_composite_input_shader(); void prepare_output_vertex_array(); + void prepare_source_vertex_array(); void push_size_uniforms(unsigned int output_width, unsigned int output_height); // the run and input data buffers From ece51917fa34f6ea89c40f04b5cc7b83adc4f36c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Apr 2016 17:17:59 -0400 Subject: [PATCH 249/307] Added first attempt at performing the first step of television output: mapping from source data to the first of the processing buffers. It's immediately obvious that my eye coordinates are off. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 42 ++++++++++++++++++++++++++++- Outputs/CRT/Internals/CRTOpenGL.hpp | 7 ++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 367da1859..3b93a5760 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -95,7 +95,6 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : // create the output vertex array glGenVertexArrays(1, &output_vertex_array); - glBindVertexArray(output_vertex_array); // create a buffer for output vertex attributes glGenBuffers(1, &output_array_buffer); @@ -105,6 +104,9 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : // map that buffer too, for any CRT activity that may occur before the first draw _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + // 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); @@ -170,6 +172,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it + glBindTexture(GL_TEXTURE_2D, textureName); if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) { glTexSubImage2D( GL_TEXTURE_2D, 0, @@ -204,6 +207,39 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); // glGetIntegerv(GL_VIEWPORT, results); + if(_output_device == Television) + { + composite_input_shader_program->bind(); + + glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); + glBindVertexArray(source_vertex_array); + + // clear the buffer + glClear(GL_COLOR_BUFFER_BIT); + + // decide how much to draw + if(_drawn_source_buffer_data_pointer != _source_buffer_data_pointer) + { + size_t new_data_size = _drawn_source_buffer_data_pointer - _source_buffer_data_pointer; + size_t new_data_start = _drawn_source_buffer_data_pointer; + _source_buffer_data_pointer %= SourceVertexBufferDataSize; + _drawn_source_buffer_data_pointer = _source_buffer_data_pointer; + + if(new_data_size >= SourceVertexBufferDataSize) + { + new_data_size = SourceVertexBufferDataSize; + new_data_start = 0; + } + + size_t first_data_length = std::max(SourceVertexBufferDataSize - new_data_start, new_data_size); + glDrawArrays(GL_LINES, (GLint)(new_data_start / SourceVertexSize), (GLsizei)(first_data_length / SourceVertexSize)); + if(new_data_size > first_data_length) + { + glDrawArrays(GL_LINES, 0, (GLsizei)((new_data_size - first_data_length) / SourceVertexSize)); + } + } + } else + // switch to the output shader if(rgb_shader_program) { @@ -497,6 +533,8 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() GLint phaseAndAmplitudeAttribute = composite_input_shader_program->get_attrib_location("phaseAndAmplitude"); GLint phaseTimeAttribute = composite_input_shader_program->get_attrib_location("phaseTime"); + glBindVertexArray(source_vertex_array); + glEnableVertexAttribArray((GLuint)inputPositionAttribute); glEnableVertexAttribArray((GLuint)outputPositionAttribute); glEnableVertexAttribArray((GLuint)phaseAndAmplitudeAttribute); @@ -605,6 +643,8 @@ void OpenGLOutputBuilder::prepare_output_vertex_array() GLint lateralAttribute = rgb_shader_program->get_attrib_location("lateralAndTimestampBaseOffset"); GLint timestampAttribute = rgb_shader_program->get_attrib_location("timestamp"); + glBindVertexArray(output_vertex_array); + glEnableVertexAttribArray((GLuint)positionAttribute); glEnableVertexAttribArray((GLuint)textureCoordinatesAttribute); glEnableVertexAttribArray((GLuint)lateralAttribute); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 8378fdf99..504b1d750 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -76,7 +76,7 @@ class OpenGLOutputBuilder { std::unique_ptr composite_input_shader_program, composite_output_shader_program; GLuint output_array_buffer, output_vertex_array; - GLuint source_array_buffer; + GLuint source_array_buffer, source_vertex_array; GLint windowSizeUniform, timestampBaseUniform; GLint boundsOriginUniform, boundsSizeUniform; @@ -108,12 +108,12 @@ class OpenGLOutputBuilder { inline uint8_t *get_next_source_run() { _output_mutex->lock(); - return &_source_buffer_data[_source_buffer_data_pointer]; + return &_source_buffer_data[_source_buffer_data_pointer % SourceVertexBufferDataSize]; } inline void complete_source_run() { - _source_buffer_data_pointer = (_source_buffer_data_pointer + 2 * SourceVertexSize) % SourceVertexBufferDataSize; + _source_buffer_data_pointer += 2 * SourceVertexSize;; _output_mutex->unlock(); } @@ -219,6 +219,7 @@ class OpenGLOutputBuilder { uint8_t *_source_buffer_data; size_t _source_buffer_data_pointer; + size_t _drawn_source_buffer_data_pointer; }; } From a8fbd82a3d0dba74c354243cce09b5927b6c1f8e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Apr 2016 17:21:24 -0400 Subject: [PATCH 250/307] Made an attempt at correctly mapping to eye coordinates. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 3b93a5760..947af265b 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -359,7 +359,8 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "ivec2 textureSize = textureSize(texID, 0);" "inputPositionVarying = vec2(inputPosition.x / textureSize.x, (inputPosition.y + 0.5) / textureSize.y);" - "gl_Position = vec4(outputPosition / outputTextureSize, 0.0, 1.0);" + "vec2 eyePosition = 2.0*(outputPosition / outputTextureSize) - vec2(1.0);" + "gl_Position = vec4(eyePosition, 0.0, 1.0);" "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAndAmplitude.x) * 2.0 * 3.141592654;" "}"); } From 43cae267f937e89078ee6018ff639122c9011038 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Apr 2016 18:08:05 -0400 Subject: [PATCH 251/307] Shuffled further in an attempt to get as far as having static but incorrectly-interpreted pixel data on screen. It's not currently static. So work to do. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 141 +++++++++++++++------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 4 + 2 files changed, 80 insertions(+), 65 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 947af265b..b6a60734c 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -69,10 +69,13 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : // Create intermediate textures and bind to slots 0, 1 and 2 glActiveTexture(GL_TEXTURE0); compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + compositeTexture->bind_texture(); glActiveTexture(GL_TEXTURE1); filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + filteredYTexture->bind_texture(); glActiveTexture(GL_TEXTURE2); filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); + filteredTexture->bind_texture(); // create the surce texture glGenTextures(1, &textureName); @@ -144,6 +147,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out prepare_composite_input_shader(); prepare_source_vertex_array(); + prepare_composite_output_shader(); prepare_rgb_output_shader(); prepare_output_vertex_array(); @@ -172,7 +176,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it - glBindTexture(GL_TEXTURE_2D, textureName); if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) { glTexSubImage2D( GL_TEXTURE_2D, 0, @@ -193,29 +196,16 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _buffer_builder->last_uploaded_line = _buffer_builder->_next_write_y_position; } - // check for anything to decode from composite -// if(_composite_src_runs->number_of_vertices) -// { -// composite_input_shader_program->bind(); -// _composite_src_runs->reset(); -// } - -// _output_mutex->unlock(); -// return; - - // reinstate the output framebuffer -// glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName); -// glGetIntegerv(GL_VIEWPORT, results); - + // for television, update intermediate buffers and then draw; for a monitor, just draw if(_output_device == Television) { composite_input_shader_program->bind(); + compositeTexture->bind_framebuffer(); glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); glBindVertexArray(source_vertex_array); - // clear the buffer - glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_BLEND); // decide how much to draw if(_drawn_source_buffer_data_pointer != _source_buffer_data_pointer) @@ -238,12 +228,30 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glDrawArrays(GL_LINES, 0, (GLsizei)((new_data_size - first_data_length) / SourceVertexSize)); } } - } else - // switch to the output shader - if(rgb_shader_program) + // transfer to screen + perform_output_stage(output_width, output_height, rgb_shader_program.get()); + } + else + perform_output_stage(output_width, output_height, rgb_shader_program.get()); + + // drawing commands having been issued, reclaim the array buffer pointer + glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); + _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + + glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); + _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + + _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + + _output_mutex->unlock(); +} + +void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsigned int output_height, OpenGL::Shader *const shader) +{ + if(shader) { - rgb_shader_program->bind(); + shader->bind(); // update uniforms push_size_uniforms(output_width, output_height); @@ -254,6 +262,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // clear the buffer glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_BLEND); // draw all sitting frames unsigned int run = (unsigned int)_run_write_pointer; @@ -286,17 +295,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } } } - - // drawing commands having been issued, reclaim the array buffer pointer - glBindBuffer(GL_ARRAY_BUFFER, output_array_buffer); - _output_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, OutputVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - - glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); - _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - - _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); - - _output_mutex->unlock(); } void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_resources) @@ -415,19 +413,18 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "uniform vec2 boundsSize;" "out float lateralVarying;" - "out vec2 shadowMaskCoordinates;" +// "out vec2 shadowMaskCoordinates;" "out float alpha;" "uniform vec4 timestampBase;" "uniform float ticksPerFrame;" "uniform vec2 positionConversion;" "uniform vec2 scanNormal;" - "uniform vec3 filterCoefficients;" "uniform usampler2D texID;" - "uniform sampler2D shadowMaskTexID;" +// "uniform sampler2D shadowMaskTexID;" - "const float shadowMaskMultiple = 600;" +// "const float shadowMaskMultiple = 600;" "out vec2 srcCoordinatesVarying;" "out vec2 iSrcCoordinatesVarying;" @@ -436,7 +433,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "{" "lateralVarying = lateralAndTimestampBaseOffset.x + 1.0707963267949;" - "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);" +// "shadowMaskCoordinates = position * vec2(shadowMaskMultiple, shadowMaskMultiple * 0.85057471264368);" "ivec2 textureSize = textureSize(texID, 0);" "iSrcCoordinatesVarying = srcCoordinates;" @@ -459,11 +456,22 @@ char *OpenGLOutputBuilder::get_rgb_output_fragment_shader() char *OpenGLOutputBuilder::get_composite_output_fragment_shader() { - return get_output_fragment_shader( - "vec4 rgb_sample(vec2 coordinate)" + return strdup( + "#version 150\n" + + "in float lateralVarying;" + "in float alpha;" + "in vec2 srcCoordinatesVarying;" + + "out vec4 fragColour;" + + "uniform sampler2D texID;" + + "void main(void)" "{" - "return texture(texID, coordinate);" - "}"); + "fragColour = vec4(texture(texID, srcCoordinatesVarying).rgb, clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" // + "}" + ); } char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_function) @@ -473,14 +481,14 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "in float lateralVarying;" "in float alpha;" - "in vec2 shadowMaskCoordinates;" +// "in vec2 shadowMaskCoordinates;" "in vec2 srcCoordinatesVarying;" "in vec2 iSrcCoordinatesVarying;" "out vec4 fragColour;" "uniform usampler2D texID;" - "uniform sampler2D shadowMaskTexID;" +// "uniform sampler2D shadowMaskTexID;" "\n%s\n" @@ -497,7 +505,7 @@ char *OpenGLOutputBuilder::get_compound_shader(const char *base, const char *ins { if(!base || !insert) return nullptr; size_t totalLength = strlen(base) + strlen(insert) + 1; - char *text = new char[totalLength]; + char *text = (char *)malloc(totalLength); snprintf(text, totalLength, base, insert); return text; } @@ -590,39 +598,30 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() free(fragment_shader); }*/ -void OpenGLOutputBuilder::prepare_rgb_output_shader() +std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char *fragment_shader) { char *vertex_shader = get_output_vertex_shader(); - char *fragment_shader = get_rgb_output_fragment_shader(); + std::unique_ptr shader_program; if(vertex_shader && fragment_shader) { - rgb_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + shader_program->bind(); - rgb_shader_program->bind(); + windowSizeUniform = shader_program->get_uniform_location("windowSize"); + boundsSizeUniform = shader_program->get_uniform_location("boundsSize"); + boundsOriginUniform = shader_program->get_uniform_location("boundsOrigin"); + timestampBaseUniform = shader_program->get_uniform_location("timestampBase"); - windowSizeUniform = rgb_shader_program->get_uniform_location("windowSize"); - boundsSizeUniform = rgb_shader_program->get_uniform_location("boundsSize"); - boundsOriginUniform = rgb_shader_program->get_uniform_location("boundsOrigin"); - timestampBaseUniform = rgb_shader_program->get_uniform_location("timestampBase"); - - GLint texIDUniform = rgb_shader_program->get_uniform_location("texID"); - GLint shadowMaskTexIDUniform = rgb_shader_program->get_uniform_location("shadowMaskTexID"); - GLint ticksPerFrameUniform = rgb_shader_program->get_uniform_location("ticksPerFrame"); - GLint scanNormalUniform = rgb_shader_program->get_uniform_location("scanNormal"); - GLint positionConversionUniform = rgb_shader_program->get_uniform_location("positionConversion"); - GLint filterCoefficients = rgb_shader_program->get_uniform_location("filterCoefficients"); + GLint texIDUniform = shader_program->get_uniform_location("texID"); + GLint ticksPerFrameUniform = shader_program->get_uniform_location("ticksPerFrame"); + GLint scanNormalUniform = shader_program->get_uniform_location("scanNormal"); + GLint positionConversionUniform = shader_program->get_uniform_location("positionConversion"); glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); - glUniform1i(shadowMaskTexIDUniform, 1); glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); - SignalProcessing::FIRFilter filter(3, 6 * 50, 0, 25, SignalProcessing::FIRFilter::DefaultAttenuation); - float coefficients[3]; - filter.get_coefficients(coefficients); - glUniform3fv(filterCoefficients, 1, coefficients); - float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; float multiplier = (float)_cycles_per_line / ((float)_height_of_display * (float)_horizontal_scan_period); @@ -633,6 +632,18 @@ void OpenGLOutputBuilder::prepare_rgb_output_shader() free(vertex_shader); free(fragment_shader); + + return shader_program; +} + +void OpenGLOutputBuilder::prepare_rgb_output_shader() +{ + rgb_shader_program = prepare_output_shader(get_rgb_output_fragment_shader()); +} + +void OpenGLOutputBuilder::prepare_composite_output_shader() +{ + composite_output_shader_program = prepare_output_shader(get_composite_output_fragment_shader()); } void OpenGLOutputBuilder::prepare_output_vertex_array() diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 504b1d750..caddfb290 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -46,6 +46,8 @@ class OpenGLOutputBuilder { // Methods used by the OpenGL code void prepare_rgb_output_shader(); + void prepare_composite_output_shader(); + std::unique_ptr prepare_output_shader(char *fragment_shader); void prepare_composite_input_shader(); void prepare_output_vertex_array(); void prepare_source_vertex_array(); @@ -89,6 +91,8 @@ class OpenGLOutputBuilder { std::unique_ptr filteredYTexture; // receives filtered Y in the R channel plus unfiltered I/U and Q/V in G and B std::unique_ptr filteredTexture; // receives filtered YIQ or YUV + void perform_output_stage(unsigned int output_width, unsigned int output_height, OpenGL::Shader *const shader); + public: OpenGLOutputBuilder(unsigned int buffer_depth); ~OpenGLOutputBuilder(); From 9ede284eb6d6d9b12b6bc2550ddb98c8b3746b45 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Apr 2016 20:34:52 -0400 Subject: [PATCH 252/307] Added blanking source runs, ensuring the rolling buffer is appropriately cleared. --- Outputs/CRT/CRT.cpp | 14 ++++++++-- Outputs/CRT/Internals/CRTConstants.hpp | 2 +- Outputs/CRT/Internals/CRTOpenGL.cpp | 36 +++++++++++++++----------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 7e1199b7a..c78b4b3b6 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -105,8 +105,9 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, #define source_input_position_y(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfInputPosition + 2]) #define source_output_position_x(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfOutputPosition + 0]) #define source_output_position_y(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfOutputPosition + 2]) -#define source_phase(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAndAmplitude + 0] -#define source_amplitude(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAndAmplitude + 1] +#define source_phase(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndAlpha + 0] +#define source_amplitude(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndAlpha + 1] +#define source_alpha(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndAlpha + 2] #define source_phase_time(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseTime]) 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) @@ -165,6 +166,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi source_phase(0) = source_phase(1) = _colour_burst_phase; source_amplitude(0) = source_amplitude(1) = _colour_burst_amplitude; source_phase_time(0) = source_phase_time(1) = _colour_burst_time; + source_alpha(0) = source_alpha(1) = 255; } } @@ -243,6 +245,14 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::EndRetrace) { _openGL_output_builder->increment_composite_output_y(); + + // store a run to clear the new line + next_run = _openGL_output_builder->get_next_source_run(); + source_output_position_x(0) = 0; + source_output_position_x(1) = IntermediateBufferWidth; + source_output_position_y(0) = source_output_position_y(1) = _openGL_output_builder->get_composite_output_y(); + source_alpha(0) = source_alpha(1) = 0; + _openGL_output_builder->complete_source_run(); } } diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index c42a7fbb9..27b45cb1a 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -29,7 +29,7 @@ const size_t OutputVertexSize = 16; // remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour const size_t SourceVertexOffsetOfInputPosition = 0; const size_t SourceVertexOffsetOfOutputPosition = 4; -const size_t SourceVertexOffsetOfPhaseAndAmplitude = 8; +const size_t SourceVertexOffsetOfPhaseAmplitudeAndAlpha = 8; const size_t SourceVertexOffsetOfPhaseTime = 12; const size_t SourceVertexSize = 16; diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index b6a60734c..6e11f5433 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -147,7 +147,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out prepare_composite_input_shader(); prepare_source_vertex_array(); - prepare_composite_output_shader(); +// prepare_composite_output_shader(); prepare_rgb_output_shader(); prepare_output_vertex_array(); @@ -201,7 +201,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out { composite_input_shader_program->bind(); - compositeTexture->bind_framebuffer(); +// compositeTexture->bind_framebuffer(); glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); glBindVertexArray(source_vertex_array); @@ -230,7 +230,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } // transfer to screen - perform_output_stage(output_width, output_height, rgb_shader_program.get()); +// perform_output_stage(output_width, output_height, rgb_shader_program.get()); } else perform_output_stage(output_width, output_height, rgb_shader_program.get()); @@ -255,6 +255,7 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign // update uniforms push_size_uniforms(output_width, output_height); + glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); // Ensure we're back on the output framebuffer, drawing from the output array buffer glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); @@ -342,7 +343,7 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "in vec2 inputPosition;" "in vec2 outputPosition;" - "in vec2 phaseAndAmplitude;" + "in vec3 phaseAmplitudeAndAlpha;" "in float phaseTime;" "uniform float phaseCyclesPerTick;" @@ -351,15 +352,18 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "out vec2 inputPositionVarying;" "out float phaseVarying;" + "out float alphaVarying;" "void main(void)" "{" "ivec2 textureSize = textureSize(texID, 0);" "inputPositionVarying = vec2(inputPosition.x / textureSize.x, (inputPosition.y + 0.5) / textureSize.y);" + "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAmplitudeAndAlpha.x) * 2.0 * 3.141592654;" + "alphaVarying = phaseAmplitudeAndAlpha.z;" + "vec2 eyePosition = 2.0*(outputPosition / outputTextureSize) - vec2(1.0);" "gl_Position = vec4(eyePosition, 0.0, 1.0);" - "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAndAmplitude.x) * 2.0 * 3.141592654;" "}"); } @@ -377,6 +381,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "in vec2 inputPositionVarying;" "in float phaseVarying;" + "in float alphaVarying;" "out vec4 fragColour;" @@ -386,7 +391,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "void main(void)" "{" - "fragColour = vec4(rgb_sample(texID, inputPositionVarying, inputPositionVarying), 1.0);" // composite + "fragColour = vec4(rgb_sample(texID, inputPositionVarying, inputPositionVarying) * alphaVarying, 1.0);" // composite "}" , composite_shader); } @@ -537,24 +542,24 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() { if(composite_input_shader_program) { - GLint inputPositionAttribute = composite_input_shader_program->get_attrib_location("inputPosition"); - GLint outputPositionAttribute = composite_input_shader_program->get_attrib_location("outputPosition"); - GLint phaseAndAmplitudeAttribute = composite_input_shader_program->get_attrib_location("phaseAndAmplitude"); - GLint phaseTimeAttribute = composite_input_shader_program->get_attrib_location("phaseTime"); + GLint inputPositionAttribute = composite_input_shader_program->get_attrib_location("inputPosition"); + GLint outputPositionAttribute = composite_input_shader_program->get_attrib_location("outputPosition"); + GLint phaseAmplitudeAndAlphaAttribute = composite_input_shader_program->get_attrib_location("phaseAmplitudeAndAlpha"); + GLint phaseTimeAttribute = composite_input_shader_program->get_attrib_location("phaseTime"); glBindVertexArray(source_vertex_array); glEnableVertexAttribArray((GLuint)inputPositionAttribute); glEnableVertexAttribArray((GLuint)outputPositionAttribute); - glEnableVertexAttribArray((GLuint)phaseAndAmplitudeAttribute); + glEnableVertexAttribArray((GLuint)phaseAmplitudeAndAlphaAttribute); glEnableVertexAttribArray((GLuint)phaseTimeAttribute); const GLsizei vertexStride = SourceVertexSize; glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); - glVertexAttribPointer((GLuint)inputPositionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfInputPosition); - glVertexAttribPointer((GLuint)outputPositionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfOutputPosition); - glVertexAttribPointer((GLuint)phaseAndAmplitudeAttribute, 2, GL_UNSIGNED_BYTE, GL_TRUE, vertexStride, (void *)SourceVertexOffsetOfPhaseAndAmplitude); - glVertexAttribPointer((GLuint)phaseTimeAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfPhaseTime); + glVertexAttribPointer((GLuint)inputPositionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfInputPosition); + glVertexAttribPointer((GLuint)outputPositionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfOutputPosition); + glVertexAttribPointer((GLuint)phaseAmplitudeAndAlphaAttribute, 3, GL_UNSIGNED_BYTE, GL_TRUE, vertexStride, (void *)SourceVertexOffsetOfPhaseAmplitudeAndAlpha); + glVertexAttribPointer((GLuint)phaseTimeAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfPhaseTime); } } @@ -643,6 +648,7 @@ void OpenGLOutputBuilder::prepare_rgb_output_shader() void OpenGLOutputBuilder::prepare_composite_output_shader() { +// rgb_shader_program = prepare_output_shader(get_composite_output_fragment_shader()); composite_output_shader_program = prepare_output_shader(get_composite_output_fragment_shader()); } From 499f7ace0739a331ad44d937b55be83865e7fd0b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Apr 2016 20:43:20 -0400 Subject: [PATCH 253/307] Re-enabled working video output for the Electron for the day and consolidated the rough metric I'm using to pick a number of taps for the audio filter. --- Machines/Electron/Electron.cpp | 2 +- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 2 +- Outputs/Speaker.hpp | 15 ++++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 7ac235056..9005bc3da 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -66,7 +66,7 @@ void Machine::setup_output(float aspect_ratio) "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" "}"); - _crt->set_output_device(Outputs::CRT::Television); + _crt->set_output_device(Outputs::CRT::Monitor); // TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. _crt->set_visible_area(_crt->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 559caafdb..f4a93d26d 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -60,7 +60,7 @@ - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { @synchronized(self) { _electron.get_speaker()->set_output_rate(sampleRate, 256); - _electron.get_speaker()->set_output_quality(47); +// _electron.get_speaker()->set_output_quality(47); _electron.get_speaker()->set_delegate(delegate); return YES; } diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 125b97663..304ba504b 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -35,11 +35,11 @@ class Speaker { set_needs_updated_filter_coefficients(); } - void set_output_quality(int number_of_taps) - { - _number_of_taps = number_of_taps; - set_needs_updated_filter_coefficients(); - } +// void set_output_quality(int number_of_taps) +// { +// _number_of_taps = number_of_taps; +// set_needs_updated_filter_coefficients(); +// } void set_delegate(Delegate *delegate) { @@ -130,6 +130,11 @@ template class Filter: public Speaker { void update_filter_coefficients() { + // make a guess at a good number of taps + _number_of_taps = (_input_cycles_per_second + _output_cycles_per_second) / _output_cycles_per_second; + _number_of_taps *= 2; + _number_of_taps |= 1; + _coefficients_are_dirty = false; _buffer_in_progress_pointer = 0; From 5980f5e991aa896f0918f6f8e92a131f7a465dd6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Apr 2016 20:45:57 -0400 Subject: [PATCH 254/307] Number of taps can be specified explicitly if you desire. --- Outputs/Speaker.hpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 304ba504b..759d04472 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -35,11 +35,11 @@ class Speaker { set_needs_updated_filter_coefficients(); } -// void set_output_quality(int number_of_taps) -// { -// _number_of_taps = number_of_taps; -// set_needs_updated_filter_coefficients(); -// } + void set_output_quality(int number_of_taps) + { + _requested_number_of_taps = number_of_taps; + set_needs_updated_filter_coefficients(); + } void set_delegate(Delegate *delegate) { @@ -52,13 +52,13 @@ class Speaker { set_needs_updated_filter_coefficients(); } - Speaker() : _buffer_in_progress_pointer(0) {} + Speaker() : _buffer_in_progress_pointer(0), _requested_number_of_taps(0) {} protected: std::unique_ptr _buffer_in_progress; int _buffer_size; int _buffer_in_progress_pointer; - int _number_of_taps; + int _number_of_taps, _requested_number_of_taps; bool _coefficients_are_dirty; Delegate *_delegate; @@ -130,10 +130,17 @@ template class Filter: public Speaker { void update_filter_coefficients() { - // make a guess at a good number of taps - _number_of_taps = (_input_cycles_per_second + _output_cycles_per_second) / _output_cycles_per_second; - _number_of_taps *= 2; - _number_of_taps |= 1; + // make a guess at a good number of taps if this hasn't been provided explicitly + if(_requested_number_of_taps) + { + _number_of_taps = _requested_number_of_taps; + } + else + { + _number_of_taps = (_input_cycles_per_second + _output_cycles_per_second) / _output_cycles_per_second; + _number_of_taps *= 2; + _number_of_taps |= 1; + } _coefficients_are_dirty = false; _buffer_in_progress_pointer = 0; From 6158275ea741598aa11313aaab03b80f1d88d6ce Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 17 Apr 2016 21:43:39 -0400 Subject: [PATCH 255/307] Started adding an options panel. --- .../Base.lproj/ElectronDocument.xib | 59 +++++++++++++++---- .../Mac/Clock Signal/Base.lproj/MainMenu.xib | 14 ++--- .../Documents/MachineDocument.swift | 5 ++ 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib index 520b3577b..8dc82d624 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib @@ -6,7 +6,8 @@ - + + @@ -19,24 +20,56 @@ - + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib b/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib index 1a562be09..ab625aa30 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib @@ -1,7 +1,7 @@ - + - + @@ -608,16 +608,10 @@ - + - - - - - - - + diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 65ea23402..1b55f1f26 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -18,6 +18,11 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe } } + @IBOutlet weak var optionsPanel: NSPanel! + @IBAction func showOptions(sender: AnyObject?) { + optionsPanel?.setIsVisible(true) + } + lazy var audioQueue = AudioQueue() override func windowControllerDidLoadNib(aController: NSWindowController) { From 94f148e2126e1926e0287f307d9b86e0f7875aa2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Apr 2016 08:21:00 -0400 Subject: [PATCH 256/307] Wired in options, at least getting as far as the Objective-C bridge. Then fast loading makes it to the emulated machine, display output type doesn't. --- Machines/Electron/Electron.cpp | 2 +- .../Base.lproj/ElectronDocument.xib | 20 +++++++---- .../Documents/ElectronDocument.swift | 36 ++++++++++++++++++- .../Documents/MachineDocument.swift | 2 +- .../Mac/Clock Signal/Wrappers/CSElectron.h | 1 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 16 ++++----- 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 9005bc3da..81226b084 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -32,7 +32,7 @@ namespace { static const unsigned int real_time_clock_interrupt_1 = 16704; static const unsigned int real_time_clock_interrupt_2 = 56704; - static const unsigned int clock_rate_audio_divider = 1; + static const unsigned int clock_rate_audio_divider = 8; } #define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line) diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib index 8dc82d624..36a52be09 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib @@ -4,8 +4,10 @@ - + + + @@ -28,8 +30,8 @@ - - + + @@ -45,19 +47,25 @@ + + + - + - + - + + + + diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 31ba148cb..02f9a37b6 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -27,6 +27,7 @@ class ElectronDocument: MachineDocument { self.electron.setView(self.openGLView, aspectRatio: 11.0 / 10.0) self.electron.audioQueue = self.audioQueue }) + establishStoredOptions() } override var windowNibName: String? { @@ -41,7 +42,6 @@ class ElectronDocument: MachineDocument { switch pathExtension.lowercaseString { case "uef": electron.openUEFAtURL(url) - electron.useFastLoadingHack = true return default: break; } @@ -71,6 +71,40 @@ class ElectronDocument: MachineDocument { super.close() } + // MARK: IBActions + @IBOutlet var displayTypeButton: NSPopUpButton! + @IBAction func setDisplayType(sender: NSPopUpButton!) { + switch sender.indexOfSelectedItem { + case 1: electron.useTelevisionOutput = false + default: electron.useTelevisionOutput = true + } + NSUserDefaults.standardUserDefaults().setInteger(sender.indexOfSelectedItem, forKey: self.displayTypeUserDefaultsKey) + } + + @IBOutlet var fastLoadingButton: NSButton! + @IBAction func setFastLoading(sender: NSButton!) { + electron.useFastLoadingHack = sender.state == NSOnState + NSUserDefaults.standardUserDefaults().setBool(electron.useFastLoadingHack, forKey: self.fastLoadingUserDefaultsKey) + } + + private let displayTypeUserDefaultsKey = "electron.displayType" + private let fastLoadingUserDefaultsKey = "electron.fastLoading" + private func establishStoredOptions() { + let standardUserDefaults = NSUserDefaults.standardUserDefaults() + standardUserDefaults.registerDefaults([ + displayTypeUserDefaultsKey: 0, + fastLoadingUserDefaultsKey: true + ]) + + let useFastLoadingHack = standardUserDefaults.boolForKey(self.fastLoadingUserDefaultsKey) + electron.useFastLoadingHack = useFastLoadingHack + self.fastLoadingButton.state = useFastLoadingHack ? NSOnState : NSOffState + + let displayType = standardUserDefaults.integerForKey(self.displayTypeUserDefaultsKey) + electron.useTelevisionOutput = (displayType == 1) + self.displayTypeButton.selectItemAtIndex(displayType) + } + // MARK: CSOpenGLViewDelegate override func runForNumberOfCycles(numberOfCycles: Int32) { if actionLock.tryLock() { diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 1b55f1f26..f35970b48 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -19,7 +19,7 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe } @IBOutlet weak var optionsPanel: NSPanel! - @IBAction func showOptions(sender: AnyObject?) { + @IBAction func showOptions(sender: AnyObject!) { optionsPanel?.setIsVisible(true) } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index e3ab2f8c5..8e9f5530f 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -21,5 +21,6 @@ - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; @property (nonatomic, assign) BOOL useFastLoadingHack; +@property (nonatomic, assign) BOOL useTelevisionOutput; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index f4a93d26d..b31d919d6 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -112,25 +112,25 @@ case VK_ANSI_Grave: case VK_ANSI_Backslash: _electron.set_key_state(Electron::Key::KeyCopy, isPressed); break; - case VK_Return: _electron.set_key_state(Electron::Key::KeyReturn, isPressed); break; - case VK_ANSI_Minus: _electron.set_key_state(Electron::Key::KeyMinus, isPressed); break; + case VK_Return: _electron.set_key_state(Electron::Key::KeyReturn, isPressed); break; + case VK_ANSI_Minus: _electron.set_key_state(Electron::Key::KeyMinus, isPressed); break; - case VK_RightArrow: _electron.set_key_state(Electron::Key::KeyRight, isPressed); break; + case VK_RightArrow: _electron.set_key_state(Electron::Key::KeyRight, isPressed); break; case VK_LeftArrow: _electron.set_key_state(Electron::Key::KeyLeft, isPressed); break; case VK_DownArrow: _electron.set_key_state(Electron::Key::KeyDown, isPressed); break; case VK_UpArrow: _electron.set_key_state(Electron::Key::KeyUp, isPressed); break; - case VK_Delete: _electron.set_key_state(Electron::Key::KeyDelete, isPressed); break; - case VK_Escape: _electron.set_key_state(Electron::Key::KeyEscape, isPressed); break; + case VK_Delete: _electron.set_key_state(Electron::Key::KeyDelete, isPressed); break; + case VK_Escape: _electron.set_key_state(Electron::Key::KeyEscape, isPressed); break; - case VK_ANSI_Comma: _electron.set_key_state(Electron::Key::KeyComma, isPressed); break; + case VK_ANSI_Comma: _electron.set_key_state(Electron::Key::KeyComma, isPressed); break; case VK_ANSI_Period: _electron.set_key_state(Electron::Key::KeyFullStop, isPressed); break; case VK_ANSI_Semicolon: _electron.set_key_state(Electron::Key::KeySemiColon, isPressed); break; - case VK_ANSI_Quote: _electron.set_key_state(Electron::Key::KeyColon, isPressed); break; + case VK_ANSI_Quote: _electron.set_key_state(Electron::Key::KeyColon, isPressed); break; - case VK_ANSI_Slash: _electron.set_key_state(Electron::Key::KeySlash, isPressed); break; + case VK_ANSI_Slash: _electron.set_key_state(Electron::Key::KeySlash, isPressed); break; case VK_Shift: _electron.set_key_state(Electron::Key::KeyShift, isPressed); break; case VK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break; From 3d53f157dede7fa553294fdd50e0dcfd45ba9a8e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Apr 2016 08:27:58 -0400 Subject: [PATCH 257/307] Output selection now takes effect. So I can stop accidentally committing changes back and forth. --- Machines/Electron/Electron.cpp | 1 - OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 81226b084..a1e2465b6 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -66,7 +66,6 @@ void Machine::setup_output(float aspect_ratio) "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" "}"); - _crt->set_output_device(Outputs::CRT::Monitor); // TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate. _crt->set_visible_area(_crt->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f)); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index b31d919d6..7900a4776 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -152,6 +152,13 @@ } } +- (void)setUseTelevisionOutput:(BOOL)useTelevisionOutput { + @synchronized(self) { + _useTelevisionOutput = useTelevisionOutput; + _electron.get_crt()->set_output_device(useTelevisionOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor); + } +} + - (void)setupOutputWithAspectRatio:(float)aspectRatio { @synchronized(self) { _electron.setup_output(aspectRatio); From bf29c8e2bf16f952cd0411cbe3ec1d3e8ac92ec6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Apr 2016 19:01:15 -0400 Subject: [PATCH 258/307] Fixed mismatch in television/monitor selection, ticked view for drawing concurrently, since it can, removed stray space. --- OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib | 2 +- OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib | 2 +- OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift | 5 +---- Outputs/CRT/Internals/CRTOpenGL.cpp | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib b/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib index b0920d364..9e73f7195 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib @@ -1,5 +1,5 @@ - + diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib index 36a52be09..c54bc86ff 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib @@ -22,7 +22,7 @@ - + diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 02f9a37b6..92c66b45d 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -74,10 +74,7 @@ class ElectronDocument: MachineDocument { // MARK: IBActions @IBOutlet var displayTypeButton: NSPopUpButton! @IBAction func setDisplayType(sender: NSPopUpButton!) { - switch sender.indexOfSelectedItem { - case 1: electron.useTelevisionOutput = false - default: electron.useTelevisionOutput = true - } + electron.useTelevisionOutput = (sender.indexOfSelectedItem == 1) NSUserDefaults.standardUserDefaults().setInteger(sender.indexOfSelectedItem, forKey: self.displayTypeUserDefaultsKey) } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 6e11f5433..8816e72bb 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -681,7 +681,7 @@ 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; From f89308e913ca7d1be0a825283e86b26d5ed4bd91 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Apr 2016 19:14:58 -0400 Subject: [PATCH 259/307] Set the 'options' windows to hide on application deactivation so that they don't cluelessly sit on top of the rest of the system. --- OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib index c54bc86ff..24ef1b95c 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib @@ -30,7 +30,7 @@ - + From 86626bbd72360b23f3011917d9d0960bf887ed22 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Apr 2016 19:20:26 -0400 Subject: [PATCH 260/307] Switched key that maps to FUNC from command to option. Logic is that command may frequently be used for performing an action which changes the key window, in which case we won't realise if it is released. --- OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift | 1 + OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 92c66b45d..bed4fa3d1 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -130,5 +130,6 @@ class ElectronDocument: MachineDocument { electron.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.ShiftKeyMask)) electron.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.ControlKeyMask)) electron.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.CommandKeyMask)) + electron.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.AlternateKeyMask)) } } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 7900a4776..3943dc360 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -134,7 +134,7 @@ case VK_Shift: _electron.set_key_state(Electron::Key::KeyShift, isPressed); break; case VK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break; - case VK_Command: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break; + case VK_Option: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break; case VK_F12: _electron.set_key_state(Electron::Key::KeyBreak, isPressed); break; From d5bac2f04f156ab763db8182deb7b99ddb9a5781 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Apr 2016 21:32:48 -0400 Subject: [PATCH 261/307] Spotted error was in texture target all along. This now gets as far as showing something a lot like the correct display, but precision is way off. Way off. --- .../Base.lproj/ElectronDocument.xib | 2 +- Outputs/CRT/CRT.cpp | 4 ++- Outputs/CRT/Internals/CRTOpenGL.cpp | 30 ++++++++++++------- Outputs/CRT/Internals/Shader.cpp | 16 ++++++++++ Outputs/CRT/Internals/TextureTarget.cpp | 7 ++--- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib index 24ef1b95c..f5c6a8e5a 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/ElectronDocument.xib @@ -31,7 +31,7 @@ - + diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index c78b4b3b6..7ff10bce0 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -77,7 +77,6 @@ CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, unsig CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) : CRT(common_output_divisor) { _openGL_output_builder = std::unique_ptr(new OpenGLOutputBuilder(buffer_depth)); - set_new_display_type(cycles_per_line, displayType); } @@ -238,6 +237,9 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi output_lateral(2) = 1; output_frame_id(0) = output_frame_id(1) = output_frame_id(2) = (uint8_t)_openGL_output_builder->get_current_field(); +// printf("%d", _horizontal_flywheel->get_current_output_position()); +// if(_is_writing_composite_run) printf("\n"); else printf(" -> "); + _openGL_output_builder->complete_output_run(3); _is_writing_composite_run ^= true; } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 8816e72bb..81ee128ac 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -176,6 +176,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it + glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit); + glBindTexture(GL_TEXTURE_2D, textureName); if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) { glTexSubImage2D( GL_TEXTURE_2D, 0, @@ -201,8 +203,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out { composite_input_shader_program->bind(); -// compositeTexture->bind_framebuffer(); - glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); + compositeTexture->bind_framebuffer(); glBindVertexArray(source_vertex_array); glDisable(GL_BLEND); @@ -230,7 +231,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } // transfer to screen -// perform_output_stage(output_width, output_height, rgb_shader_program.get()); + glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); + compositeTexture->bind_texture(); + perform_output_stage(output_width, output_height, rgb_shader_program.get()); } else perform_output_stage(output_width, output_height, rgb_shader_program.get()); @@ -244,6 +247,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); +// printf("%04x\n", glGetError()); _output_mutex->unlock(); } @@ -251,16 +255,18 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign { if(shader) { - shader->bind(); - - // update uniforms - push_size_uniforms(output_width, output_height); + // definitively establish the viewport glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); // Ensure we're back on the output framebuffer, drawing from the output array buffer glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); glBindVertexArray(output_vertex_array); + shader->bind(); + + // update uniforms + push_size_uniforms(output_width, output_height); + // clear the buffer glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_BLEND); @@ -426,7 +432,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "uniform vec2 positionConversion;" "uniform vec2 scanNormal;" - "uniform usampler2D texID;" + "uniform sampler2D texID;" // "uniform sampler2D shadowMaskTexID;" // "const float shadowMaskMultiple = 600;" @@ -492,14 +498,18 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "out vec4 fragColour;" - "uniform usampler2D texID;" +// "uniform usampler2D texID;" + "uniform sampler2D texID;" // "uniform sampler2D shadowMaskTexID;" "\n%s\n" "void main(void)" "{" - "fragColour = vec4(rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying), clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" // +// "fragColour = vec4(srcCoordinatesVarying.rg, 0.0, 1.0);" // + "fragColour = texture(texID, srcCoordinatesVarying).rgba;" // +// "fragColour = vec4(srcCoordinatesVarying.y / 4.0, 0.0, 0.0, 1.0);"//texture(texID, srcCoordinatesVarying).rgba;" // +// "fragColour = vec4(rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying), clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" // "}" , sampling_function); } diff --git a/Outputs/CRT/Internals/Shader.cpp b/Outputs/CRT/Internals/Shader.cpp index c89a41c3b..4bf63c8c6 100644 --- a/Outputs/CRT/Internals/Shader.cpp +++ b/Outputs/CRT/Internals/Shader.cpp @@ -47,6 +47,22 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader) glAttachShader(_shader_program, vertex); glAttachShader(_shader_program, fragment); glLinkProgram(_shader_program); + +#if defined(DEBUG) + GLint didLink = 0; + glGetProgramiv(_shader_program, GL_LINK_STATUS, &didLink); + if(didLink == GL_FALSE) + { + GLint 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); + printf("Link log:\n%s\n", log); + free(log); + } + } +#endif } Shader::~Shader() diff --git a/Outputs/CRT/Internals/TextureTarget.cpp b/Outputs/CRT/Internals/TextureTarget.cpp index 5a622e09b..a57eaa26c 100644 --- a/Outputs/CRT/Internals/TextureTarget.cpp +++ b/Outputs/CRT/Internals/TextureTarget.cpp @@ -17,10 +17,9 @@ TextureTarget::TextureTarget(GLsizei width, GLsizei height) : _width(width), _he glGenTextures(1, &_texture); glBindTexture(GL_TEXTURE_2D, _texture); - - uint8_t *emptySpace = new uint8_t[width*height*3]; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, (GLsizei)width, (GLsizei)height, 0, GL_RGB, GL_UNSIGNED_BYTE, emptySpace); - delete[] emptySpace; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); From 9580dde3adaa12aee10d5918632a9289cbfce0e9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Apr 2016 21:40:23 -0400 Subject: [PATCH 262/307] Fixed left/right shuffling. Was simply failing to supply the integer version of coordinates. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 81ee128ac..7d7cf6184 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -63,7 +63,6 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : } _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); - glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); // Create intermediate textures and bind to slots 0, 1 and 2 @@ -269,7 +268,7 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign // clear the buffer glClear(GL_COLOR_BUFFER_BIT); - glEnable(GL_BLEND); +// glEnable(GL_BLEND); // draw all sitting frames unsigned int run = (unsigned int)_run_write_pointer; @@ -357,12 +356,14 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "uniform ivec2 outputTextureSize;" "out vec2 inputPositionVarying;" + "out vec2 iInputPositionVarying;" "out float phaseVarying;" "out float alphaVarying;" "void main(void)" "{" "ivec2 textureSize = textureSize(texID, 0);" + "iInputPositionVarying = inputPosition;" "inputPositionVarying = vec2(inputPosition.x / textureSize.x, (inputPosition.y + 0.5) / textureSize.y);" "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAmplitudeAndAlpha.x) * 2.0 * 3.141592654;" @@ -386,6 +387,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "#version 150\n" "in vec2 inputPositionVarying;" + "in vec2 iInputPositionVarying;" "in float phaseVarying;" "in float alphaVarying;" @@ -397,7 +399,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "void main(void)" "{" - "fragColour = vec4(rgb_sample(texID, inputPositionVarying, inputPositionVarying) * alphaVarying, 1.0);" // composite + "fragColour = vec4(rgb_sample(texID, inputPositionVarying, iInputPositionVarying) * alphaVarying, 1.0);" // composite "}" , composite_shader); } From cf55a0c42337b55b00e1ccb435dac307cf4a47bc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Apr 2016 21:49:45 -0400 Subject: [PATCH 263/307] Fixed: composite output y is now incremented upon the start of retrace, that causing it to have different values at either edge of scans. --- Outputs/CRT/CRT.cpp | 5 +---- Outputs/CRT/Internals/CRTOpenGL.cpp | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 7ff10bce0..50ef7a3a1 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -237,14 +237,11 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi output_lateral(2) = 1; output_frame_id(0) = output_frame_id(1) = output_frame_id(2) = (uint8_t)_openGL_output_builder->get_current_field(); -// printf("%d", _horizontal_flywheel->get_current_output_position()); -// if(_is_writing_composite_run) printf("\n"); else printf(" -> "); - _openGL_output_builder->complete_output_run(3); _is_writing_composite_run ^= true; } - if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::EndRetrace) + if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) { _openGL_output_builder->increment_composite_output_y(); diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 7d7cf6184..57dd765ec 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -268,7 +268,7 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign // clear the buffer glClear(GL_COLOR_BUFFER_BIT); -// glEnable(GL_BLEND); + glEnable(GL_BLEND); // draw all sitting frames unsigned int run = (unsigned int)_run_write_pointer; @@ -509,7 +509,7 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "void main(void)" "{" // "fragColour = vec4(srcCoordinatesVarying.rg, 0.0, 1.0);" // - "fragColour = texture(texID, srcCoordinatesVarying).rgba;" // + "fragColour = vec4(texture(texID, srcCoordinatesVarying).rgb, clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" // // "fragColour = vec4(srcCoordinatesVarying.y / 4.0, 0.0, 0.0, 1.0);"//texture(texID, srcCoordinatesVarying).rgba;" // // "fragColour = vec4(rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying), clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" // "}" From 9d39c1475216651a3bca6aa043725765656fa3b5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 18 Apr 2016 22:18:03 -0400 Subject: [PATCH 264/307] Nothing of substance different; main current mystery: why do old frames recur and why does other jumpiness occur? Do I need some explicit synchronistion? --- Outputs/CRT/Internals/CRTOpenGL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 57dd765ec..84df9f966 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -364,7 +364,7 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "{" "ivec2 textureSize = textureSize(texID, 0);" "iInputPositionVarying = inputPosition;" - "inputPositionVarying = vec2(inputPosition.x / textureSize.x, (inputPosition.y + 0.5) / textureSize.y);" + "inputPositionVarying = inputPosition / vec2(textureSize);" // + 0.5 "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAmplitudeAndAlpha.x) * 2.0 * 3.141592654;" "alphaVarying = phaseAmplitudeAndAlpha.z;" From bcc784bda9cbbb03f485602cbc304682ce89f809 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 07:23:15 -0400 Subject: [PATCH 265/307] Introduced an interface for specifying attribute bindings, taking the opportunity to document the interface and introduce exceptions. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 5 ++--- Outputs/CRT/Internals/Shader.cpp | 16 ++++++++++++--- Outputs/CRT/Internals/Shader.hpp | 32 ++++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 84df9f966..a951973d4 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -246,7 +246,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _input_texture_data = (uint8_t *)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, _input_texture_array_size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); -// printf("%04x\n", glGetError()); _output_mutex->unlock(); } @@ -535,7 +534,7 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() char *fragment_shader = get_input_fragment_shader(); if(vertex_shader && fragment_shader) { - composite_input_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + composite_input_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader, nullptr)); GLint texIDUniform = composite_input_shader_program->get_uniform_location("texID"); GLint phaseCyclesPerTickUniform = composite_input_shader_program->get_uniform_location("phaseCyclesPerTick"); @@ -622,7 +621,7 @@ std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char if(vertex_shader && fragment_shader) { - shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); + shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader, nullptr)); shader_program->bind(); windowSizeUniform = shader_program->get_uniform_location("windowSize"); diff --git a/Outputs/CRT/Internals/Shader.cpp b/Outputs/CRT/Internals/Shader.cpp index 4bf63c8c6..6c7e621c1 100644 --- a/Outputs/CRT/Internals/Shader.cpp +++ b/Outputs/CRT/Internals/Shader.cpp @@ -38,7 +38,7 @@ GLuint Shader::compile_shader(const char *source, GLenum type) return shader; } -Shader::Shader(const char *vertex_shader, const char *fragment_shader) +Shader::Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings) { _shader_program = glCreateProgram(); GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER); @@ -46,6 +46,16 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader) glAttachShader(_shader_program, vertex); glAttachShader(_shader_program, fragment); + + if(attribute_bindings) + { + while(attribute_bindings->name) + { + glBindAttribLocation(_shader_program, attribute_bindings->index, attribute_bindings->name); + attribute_bindings++; + } + } + glLinkProgram(_shader_program); #if defined(DEBUG) @@ -76,12 +86,12 @@ void Shader::bind() glUseProgram(_shader_program); } -GLint Shader::get_attrib_location(const char *name) +GLint Shader::get_attrib_location(const GLchar *name) { return glGetAttribLocation(_shader_program, name); } -GLint Shader::get_uniform_location(const char *name) +GLint Shader::get_uniform_location(const GLchar *name) { return glGetUniformLocation(_shader_program, name); } diff --git a/Outputs/CRT/Internals/Shader.hpp b/Outputs/CRT/Internals/Shader.hpp index 2f4706bb0..cb1eddc7d 100644 --- a/Outputs/CRT/Internals/Shader.hpp +++ b/Outputs/CRT/Internals/Shader.hpp @@ -14,12 +14,38 @@ namespace OpenGL { class Shader { public: - Shader(const char *vertex_shader, const char *fragment_shader); + struct AttributeBinding { + const GLchar *name; + GLuint index; + }; + + /*! + Constructs a shader, comprised of: + @param vertex_shader The vertex shader source code. + @param fragment_shader The fragment shader source code. + @param attribute_bindings Either @c nullptr or an array terminated by an entry with a @c nullptr-name of attribute bindings. + */ + Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings); ~Shader(); + /*! + Performs an @c glUseProgram to make this the active shader. + */ void bind(); - GLint get_attrib_location(const char *name); - GLint get_uniform_location(const char *name); + + /*! + Performs a @c glGetAttribLocation call. + @param name The name of the attribute to locate. + @returns The location of the requested attribute. + */ + GLint get_attrib_location(const GLchar *name); + + /*! + Performs a @c glGetUniformLocation call. + @param name The name of the uniform to locate. + @returns The location of the requested uniform. + */ + GLint get_uniform_location(const GLchar *name); private: GLuint compile_shader(const char *source, GLenum type); From 6c9bcfa637ef4befa2a6cfb571f31bbbe08ec4cb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 08:05:43 -0400 Subject: [PATCH 266/307] Got rigorous on exceptions, started working towards having a working 'composite' shader at the same time as having a working RGB shader. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 63 ++++++++++++++--------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 2 +- Outputs/CRT/Internals/Shader.cpp | 5 ++- Outputs/CRT/Internals/Shader.hpp | 9 ++++- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index a951973d4..f3fd2be31 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -232,7 +232,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // transfer to screen glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); compositeTexture->bind_texture(); - perform_output_stage(output_width, output_height, rgb_shader_program.get()); + perform_output_stage(output_width, output_height, composite_output_shader_program.get()); } else perform_output_stage(output_width, output_height, rgb_shader_program.get()); @@ -463,32 +463,17 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() char *OpenGLOutputBuilder::get_rgb_output_fragment_shader() { - return get_output_fragment_shader(_rgb_shader); + return get_output_fragment_shader(_rgb_shader, "uniform usampler2D texID;", "rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying)"); } char *OpenGLOutputBuilder::get_composite_output_fragment_shader() { - return strdup( - "#version 150\n" - - "in float lateralVarying;" - "in float alpha;" - "in vec2 srcCoordinatesVarying;" - - "out vec4 fragColour;" - - "uniform sampler2D texID;" - - "void main(void)" - "{" - "fragColour = vec4(texture(texID, srcCoordinatesVarying).rgb, clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" // - "}" - ); + return get_output_fragment_shader("", "uniform sampler2D texID;", "texture(texID, srcCoordinatesVarying).rgb"); } -char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_function) +char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_function, const char *header, const char *fragColour_function) { - return get_compound_shader( + char *complete_header = get_compound_shader( "#version 150\n" "in float lateralVarying;" @@ -499,20 +484,25 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "out vec4 fragColour;" -// "uniform usampler2D texID;" - "uniform sampler2D texID;" -// "uniform sampler2D shadowMaskTexID;" - - "\n%s\n" +// "uniform sampler2D shadowMaskTexID;", + "%s\n%%s\n", + header); + char *complete_body = get_compound_shader( + "%%s\n" "void main(void)" "{" -// "fragColour = vec4(srcCoordinatesVarying.rg, 0.0, 1.0);" // - "fragColour = vec4(texture(texID, srcCoordinatesVarying).rgb, clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" // -// "fragColour = vec4(srcCoordinatesVarying.y / 4.0, 0.0, 0.0, 1.0);"//texture(texID, srcCoordinatesVarying).rgba;" // -// "fragColour = vec4(rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying), clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" // - "}" - , sampling_function); + "fragColour = vec4(%s, clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" + "}", + fragColour_function); + + char *top = get_compound_shader(complete_header, sampling_function); + free(complete_header); + + char *result = get_compound_shader(complete_body, top); + free(complete_body); + + return result; } #pragma mark - Shader utilities @@ -621,7 +611,15 @@ std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char if(vertex_shader && fragment_shader) { - shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader, nullptr)); + OpenGL::Shader::AttributeBinding bindings[] = + { + {"position", 0}, + {"srcCoordinates", 1}, + {"lateralAndTimestampBaseOffset", 2}, + {"timestamp", 3}, + {nullptr} + }; + shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader, bindings)); shader_program->bind(); windowSizeUniform = shader_program->get_uniform_location("windowSize"); @@ -659,7 +657,6 @@ void OpenGLOutputBuilder::prepare_rgb_output_shader() void OpenGLOutputBuilder::prepare_composite_output_shader() { -// rgb_shader_program = prepare_output_shader(get_composite_output_fragment_shader()); composite_output_shader_program = prepare_output_shader(get_composite_output_fragment_shader()); } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index caddfb290..e51d857a7 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -65,7 +65,7 @@ class OpenGLOutputBuilder { char *get_output_vertex_shader(); - char *get_output_fragment_shader(const char *sampling_function); + char *get_output_fragment_shader(const char *sampling_function, const char *header, const char *fragColour_function); char *get_rgb_output_fragment_shader(); char *get_composite_output_fragment_shader(); diff --git a/Outputs/CRT/Internals/Shader.cpp b/Outputs/CRT/Internals/Shader.cpp index 6c7e621c1..48eec0468 100644 --- a/Outputs/CRT/Internals/Shader.cpp +++ b/Outputs/CRT/Internals/Shader.cpp @@ -26,12 +26,14 @@ GLuint Shader::compile_shader(const char *source, GLenum type) { GLint logLength; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); - if (logLength > 0) { + if(logLength > 0) { GLchar *log = (GLchar *)malloc((size_t)logLength); glGetShaderInfoLog(shader, logLength, &logLength, log); printf("Compile log:\n%s\n", log); free(log); } + + throw (type == GL_VERTEX_SHADER) ? VertexShaderCompilationError : FragmentShaderCompilationError; } #endif @@ -71,6 +73,7 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att printf("Link log:\n%s\n", log); free(log); } + throw ProgramLinkageError; } #endif } diff --git a/Outputs/CRT/Internals/Shader.hpp b/Outputs/CRT/Internals/Shader.hpp index cb1eddc7d..a585b7819 100644 --- a/Outputs/CRT/Internals/Shader.hpp +++ b/Outputs/CRT/Internals/Shader.hpp @@ -14,13 +14,19 @@ namespace OpenGL { class Shader { public: + enum { + VertexShaderCompilationError, + FragmentShaderCompilationError, + ProgramLinkageError + }; + struct AttributeBinding { const GLchar *name; GLuint index; }; /*! - Constructs a shader, comprised of: + Attempts to compile a shader, throwing @c VertexShaderCompilationError, @c FragmentShaderCompilationError or @c ProgramLinkageError upon failure. @param vertex_shader The vertex shader source code. @param fragment_shader The fragment shader source code. @param attribute_bindings Either @c nullptr or an array terminated by an entry with a @c nullptr-name of attribute bindings. @@ -47,6 +53,7 @@ namespace OpenGL { */ GLint get_uniform_location(const GLchar *name); + private: GLuint compile_shader(const char *source, GLenum type); GLuint _shader_program; From b90a487a16c7653df02fb798e75ab8ddaf63f4ea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 19:30:50 -0400 Subject: [PATCH 267/307] Attempted to enable both monitor and television output paths in the same codebase. The television route seems to be broken. Investigation required. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 61 +++++++++++++---------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 8 ++-- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index f3fd2be31..1c8e0010d 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -36,7 +36,6 @@ static const GLenum formatForDepth(size_t depth) } } - using namespace Outputs::CRT; namespace { @@ -382,7 +381,8 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() // TODO: synthesise an RGB -> (selected colour space) shader } - return get_compound_shader( + char *result; + asprintf(&result, "#version 150\n" "in vec2 inputPositionVarying;" @@ -401,19 +401,21 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "fragColour = vec4(rgb_sample(texID, inputPositionVarying, iInputPositionVarying) * alphaVarying, 1.0);" // composite "}" , composite_shader); + return result; } #pragma mark - Intermediate vertex shaders (i.e. from intermediate line layout to intermediate line layout) #pragma mark - Output vertex shader -char *OpenGLOutputBuilder::get_output_vertex_shader() +char *OpenGLOutputBuilder::get_output_vertex_shader(const char *header) { // the main job of the vertex shader is just to map from an input area of [0,1]x[0,1], with the origin in the // top left to OpenGL's [-1,1]x[-1,1] with the origin in the lower left, and to convert input data coordinates // from integral to floating point. - return strdup( + char *result; + asprintf(&result, "#version 150\n" "in vec2 position;" @@ -433,7 +435,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "uniform vec2 positionConversion;" "uniform vec2 scanNormal;" - "uniform sampler2D texID;" + "\n%s\n" // "uniform sampler2D shadowMaskTexID;" // "const float shadowMaskMultiple = 600;" @@ -456,7 +458,18 @@ char *OpenGLOutputBuilder::get_output_vertex_shader() "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" "gl_Position = vec4(mappedPosition.x * 2.0 - 1.0, 1.0 - mappedPosition.y * 2.0, 0.0, 1.0);" - "}"); + "}", header); + return result; +} + +char *OpenGLOutputBuilder::get_rgb_output_vertex_shader() +{ + return get_output_vertex_shader("uniform usampler2D texID;"); +} + +char *OpenGLOutputBuilder::get_composite_output_vertex_shader() +{ + return get_output_vertex_shader("uniform sampler2D texID;"); } #pragma mark - Output fragment shaders; RGB and from composite @@ -473,7 +486,8 @@ char *OpenGLOutputBuilder::get_composite_output_fragment_shader() char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_function, const char *header, const char *fragColour_function) { - char *complete_header = get_compound_shader( + char *result; + asprintf(&result, "#version 150\n" "in float lateralVarying;" @@ -485,37 +499,17 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "out vec4 fragColour;" // "uniform sampler2D shadowMaskTexID;", - "%s\n%%s\n", - header); - - char *complete_body = get_compound_shader( - "%%s\n" + "%s\n" + "%s\n" "void main(void)" "{" "fragColour = vec4(%s, clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" "}", - fragColour_function); - - char *top = get_compound_shader(complete_header, sampling_function); - free(complete_header); - - char *result = get_compound_shader(complete_body, top); - free(complete_body); + header, sampling_function, fragColour_function); return result; } -#pragma mark - Shader utilities - -char *OpenGLOutputBuilder::get_compound_shader(const char *base, const char *insert) -{ - if(!base || !insert) return nullptr; - size_t totalLength = strlen(base) + strlen(insert) + 1; - char *text = (char *)malloc(totalLength); - snprintf(text, totalLength, base, insert); - return text; -} - #pragma mark - Program compilation void OpenGLOutputBuilder::prepare_composite_input_shader() @@ -604,9 +598,8 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() free(fragment_shader); }*/ -std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char *fragment_shader) +std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char *vertex_shader, char *fragment_shader) { - char *vertex_shader = get_output_vertex_shader(); std::unique_ptr shader_program; if(vertex_shader && fragment_shader) @@ -652,12 +645,12 @@ std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char void OpenGLOutputBuilder::prepare_rgb_output_shader() { - rgb_shader_program = prepare_output_shader(get_rgb_output_fragment_shader()); + rgb_shader_program = prepare_output_shader(get_rgb_output_vertex_shader(), get_rgb_output_fragment_shader()); } void OpenGLOutputBuilder::prepare_composite_output_shader() { - composite_output_shader_program = prepare_output_shader(get_composite_output_fragment_shader()); + composite_output_shader_program = prepare_output_shader(get_composite_output_vertex_shader(), get_composite_output_fragment_shader()); } void OpenGLOutputBuilder::prepare_output_vertex_array() diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index e51d857a7..5356b8ab4 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -47,7 +47,7 @@ class OpenGLOutputBuilder { // Methods used by the OpenGL code void prepare_rgb_output_shader(); void prepare_composite_output_shader(); - std::unique_ptr prepare_output_shader(char *fragment_shader); + std::unique_ptr prepare_output_shader(char *vertex_shader, char *fragment_shader); void prepare_composite_input_shader(); void prepare_output_vertex_array(); void prepare_source_vertex_array(); @@ -63,7 +63,9 @@ class OpenGLOutputBuilder { std::unique_ptr _composite_src_runs; uint16_t _composite_src_output_y; - char *get_output_vertex_shader(); + char *get_output_vertex_shader(const char *header); + char *get_rgb_output_vertex_shader(); + char *get_composite_output_vertex_shader(); char *get_output_fragment_shader(const char *sampling_function, const char *header, const char *fragColour_function); char *get_rgb_output_fragment_shader(); @@ -72,8 +74,6 @@ class OpenGLOutputBuilder { char *get_input_vertex_shader(); char *get_input_fragment_shader(); - char *get_compound_shader(const char *base, const char *insert); - std::unique_ptr rgb_shader_program; std::unique_ptr composite_input_shader_program, composite_output_shader_program; From 6f52ed14d64b5cb6943f9acc16a74c3d4b48d1e1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 19:32:52 -0400 Subject: [PATCH 268/307] =?UTF-8?q?Trivially=20fixed.=20Appears=20to=20con?= =?UTF-8?q?firm=20an=20off-by-one=20error=20in=20the=20television=20implem?= =?UTF-8?q?entation.=20Which=20would=20explain=20one=20of=20the=20visual?= =?UTF-8?q?=20phenomena=20=E2=80=94=20a=20repeating=20corrupted=20line.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Outputs/CRT/Internals/CRTOpenGL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 1c8e0010d..6a0d12199 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -145,7 +145,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out prepare_composite_input_shader(); prepare_source_vertex_array(); -// prepare_composite_output_shader(); + prepare_composite_output_shader(); prepare_rgb_output_shader(); prepare_output_vertex_array(); From 20aa9e291d8dc3e75e67bf5bffe20d5ab90548ea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 20:51:34 -0400 Subject: [PATCH 269/307] Attempted to deal with the precision issues causing 'television' output currently to differ from 'monitor' output. Documented TextureTarget while I'm here. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 5 +++-- Outputs/CRT/Internals/TextureTarget.hpp | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 6a0d12199..dfe3a67eb 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -229,6 +229,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } // transfer to screen + glFinish(); glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); compositeTexture->bind_texture(); perform_output_stage(output_width, output_height, composite_output_shader_program.get()); @@ -362,12 +363,12 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "{" "ivec2 textureSize = textureSize(texID, 0);" "iInputPositionVarying = inputPosition;" - "inputPositionVarying = inputPosition / vec2(textureSize);" // + 0.5 + "inputPositionVarying = (inputPosition + vec2(0.0, 0.5)) / vec2(textureSize);" "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAmplitudeAndAlpha.x) * 2.0 * 3.141592654;" "alphaVarying = phaseAmplitudeAndAlpha.z;" - "vec2 eyePosition = 2.0*(outputPosition / outputTextureSize) - vec2(1.0);" + "vec2 eyePosition = 2.0*(outputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;" "gl_Position = vec4(eyePosition, 0.0, 1.0);" "}"); } diff --git a/Outputs/CRT/Internals/TextureTarget.hpp b/Outputs/CRT/Internals/TextureTarget.hpp index f4ec3f665..471caae4b 100644 --- a/Outputs/CRT/Internals/TextureTarget.hpp +++ b/Outputs/CRT/Internals/TextureTarget.hpp @@ -13,12 +13,31 @@ namespace OpenGL { +/*! + A @c TextureTarget is a framebuffer that can be bound as a texture. So this class + handles render-to-texture framebuffer objects. +*/ class TextureTarget { public: + /*! + Creates a new texture target. + + Throws ErrorFramebufferIncomplete if creation fails. + + @param width The width of target to create. + @param height The height of target to create. + */ TextureTarget(GLsizei width, GLsizei height); ~TextureTarget(); + /*! + Binds this target as a framebuffer and sets the @c glViewport accordingly. + */ void bind_framebuffer(); + + /*! + Binds this target as a texture. + */ void bind_texture(); enum { From 88e237b8de6855f96638121c4cd72b85a2b3c5c9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 20:53:55 -0400 Subject: [PATCH 270/307] Standardised indentation and added one extra piece of documentation. --- Outputs/CRT/Internals/Shader.hpp | 85 +++++++++++++++++--------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/Outputs/CRT/Internals/Shader.hpp b/Outputs/CRT/Internals/Shader.hpp index a585b7819..5a4dc4117 100644 --- a/Outputs/CRT/Internals/Shader.hpp +++ b/Outputs/CRT/Internals/Shader.hpp @@ -12,52 +12,59 @@ #include "OpenGL.hpp" namespace OpenGL { - class Shader { - public: - enum { - VertexShaderCompilationError, - FragmentShaderCompilationError, - ProgramLinkageError - }; - struct AttributeBinding { - const GLchar *name; - GLuint index; - }; +/*! + A @c Shader compiles and holds a shader object, based on a single + vertex program and a single fragment program. Attribute bindings + may be supplied if desired. +*/ +class Shader { +public: + enum { + VertexShaderCompilationError, + FragmentShaderCompilationError, + ProgramLinkageError + }; - /*! - Attempts to compile a shader, throwing @c VertexShaderCompilationError, @c FragmentShaderCompilationError or @c ProgramLinkageError upon failure. - @param vertex_shader The vertex shader source code. - @param fragment_shader The fragment shader source code. - @param attribute_bindings Either @c nullptr or an array terminated by an entry with a @c nullptr-name of attribute bindings. - */ - Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings); - ~Shader(); + struct AttributeBinding { + const GLchar *name; + GLuint index; + }; - /*! - Performs an @c glUseProgram to make this the active shader. - */ - void bind(); + /*! + Attempts to compile a shader, throwing @c VertexShaderCompilationError, @c FragmentShaderCompilationError or @c ProgramLinkageError upon failure. + @param vertex_shader The vertex shader source code. + @param fragment_shader The fragment shader source code. + @param attribute_bindings Either @c nullptr or an array terminated by an entry with a @c nullptr-name of attribute bindings. + */ + Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings); + ~Shader(); - /*! - Performs a @c glGetAttribLocation call. - @param name The name of the attribute to locate. - @returns The location of the requested attribute. - */ - GLint get_attrib_location(const GLchar *name); + /*! + Performs an @c glUseProgram to make this the active shader. + */ + void bind(); - /*! - Performs a @c glGetUniformLocation call. - @param name The name of the uniform to locate. - @returns The location of the requested uniform. - */ - GLint get_uniform_location(const GLchar *name); + /*! + Performs a @c glGetAttribLocation call. + @param name The name of the attribute to locate. + @returns The location of the requested attribute. + */ + GLint get_attrib_location(const GLchar *name); + + /*! + Performs a @c glGetUniformLocation call. + @param name The name of the uniform to locate. + @returns The location of the requested uniform. + */ + GLint get_uniform_location(const GLchar *name); - private: - GLuint compile_shader(const char *source, GLenum type); - GLuint _shader_program; - }; +private: + GLuint compile_shader(const char *source, GLenum type); + GLuint _shader_program; +}; + } #endif /* Shader_hpp */ From 66f2c10c044a7a77bbf336929969f5f2c4876408 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 21:00:48 -0400 Subject: [PATCH 271/307] Resolved potential crash if tape requests are made without one inserted. Softened glFinish to glFlush, though I'm still not sure I should strictly need even that. --- Machines/Electron/Electron.cpp | 10 +++++++++- Machines/Electron/Electron.hpp | 4 ++++ Outputs/CRT/Internals/CRTOpenGL.cpp | 3 ++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index a1e2465b6..a153b8144 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -304,6 +304,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin { if( _use_fast_tape_hack && + _tape.has_tape() && (operation == CPU6502::BusOperation::ReadOpcode) && ( (address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51 @@ -886,7 +887,14 @@ void Tape::set_tape(std::shared_ptr tape) inline void Tape::get_next_tape_pulse() { _input.time_into_pulse = 0; - _input.current_pulse = _tape->get_next_pulse(); + if(_tape) + _input.current_pulse = _tape->get_next_pulse(); + else + { + _input.current_pulse.length.length = 1; + _input.current_pulse.length.clock_rate = 1; + _input.current_pulse.type = Storage::Tape::Pulse::Zero; + } if(_input.pulse_stepper == nullptr || _input.current_pulse.length.clock_rate != _input.pulse_stepper->get_output_rate()) { _input.pulse_stepper = std::unique_ptr(new SignalProcessing::Stepper(_input.current_pulse.length.clock_rate, 2000000)); diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index afafba2ee..dfd8818ac 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -63,6 +63,10 @@ class Tape { Tape(); void set_tape(std::shared_ptr tape); + inline bool has_tape() + { + return (bool)_tape; + } inline uint8_t get_data_register(); inline void set_data_register(uint8_t value); diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index dfe3a67eb..cc007e43d 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -229,7 +229,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } // transfer to screen - glFinish(); glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); compositeTexture->bind_texture(); perform_output_stage(output_width, output_height, composite_output_shader_program.get()); @@ -287,6 +286,8 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign { // draw glUniform4fv(timestampBaseUniform, 1, timestampBases); + glFlush(); + GLsizei primitive_count = (GLsizei)(count / OutputVertexSize); GLsizei max_count = (GLsizei)((OutputVertexBufferDataSize - start) / OutputVertexSize); if(primitive_count < max_count) From 7aa87723a93f5bdf18e331a0030a322f56db4350 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 21:29:10 -0400 Subject: [PATCH 272/307] Added: key states are all cleared if the window loses focus. Which resolves sticky key issues. Allowing me just to use command+option+O for options and map both command and option as FUNC. --- Machines/Electron/Electron.cpp | 5 + Machines/Electron/Electron.hpp | 1 + .../Mac/Clock Signal/Base.lproj/MainMenu.xib | 262 ------------------ .../Documents/ElectronDocument.swift | 5 + .../Documents/MachineDocument.swift | 2 +- .../Mac/Clock Signal/Wrappers/CSElectron.h | 1 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 7 + 7 files changed, 20 insertions(+), 263 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index a153b8144..b85797814 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -814,6 +814,11 @@ inline void Machine::update_display() } } +void Machine::clear_all_keys() +{ + memset(_key_states, 0, sizeof(_key_states)); +} + void Machine::set_key_state(Key key, bool isPressed) { if(key == KeyBreak) diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index dfd8818ac..34db2abe4 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -154,6 +154,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { void set_tape(std::shared_ptr tape); void set_key_state(Key key, bool isPressed); + void clear_all_keys(); void setup_output(float aspect_ratio); Outputs::CRT::CRT *get_crt() { return _crt.get(); } diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib b/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib index ab625aa30..012c3874d 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/MainMenu.xib @@ -342,268 +342,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index bed4fa3d1..4d08e603a 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -117,6 +117,11 @@ class ElectronDocument: MachineDocument { } } + // MARK: NSWindowDelegate + func windowDidResignKey(notification: NSNotification) { + electron.clearAllKeys() + } + // MARK: CSOpenGLViewResponderDelegate override func keyDown(event: NSEvent) { electron.setKey(event.keyCode, isPressed: true) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index f35970b48..4801f6c86 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -9,7 +9,7 @@ import Cocoa import AudioToolbox -class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate { +class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate, NSWindowDelegate { @IBOutlet weak var openGLView: CSOpenGLView! { didSet { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index 8e9f5530f..3b4d45ff3 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -17,6 +17,7 @@ - (BOOL)openUEFAtURL:(nonnull NSURL *)URL; - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; +- (void)clearAllKeys; - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 3943dc360..52607f738 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -66,6 +66,12 @@ } } +- (void)clearAllKeys { + @synchronized(self) { + _electron.clear_all_keys(); + } +} + - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { @synchronized(self) { switch(key) @@ -134,6 +140,7 @@ case VK_Shift: _electron.set_key_state(Electron::Key::KeyShift, isPressed); break; case VK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break; + case VK_Command: case VK_Option: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break; case VK_F12: _electron.set_key_state(Electron::Key::KeyBreak, isPressed); break; From 145c0b3b71185e00d391cd76d5d4d3b3249613a8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 22:21:26 -0400 Subject: [PATCH 273/307] Smoothed output device changes, killing some dead state. Added an explicit glFinish to suggest that the problem is synchronisation rather than any sort of data or shader error. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 12 ++++++------ Outputs/CRT/Internals/CRTOpenGL.hpp | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index cc007e43d..30e3636b2 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -226,6 +226,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out { glDrawArrays(GL_LINES, 0, (GLsizei)((new_data_size - first_data_length) / SourceVertexSize)); } + glFinish(); } // transfer to screen @@ -286,7 +287,6 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign { // draw glUniform4fv(timestampBaseUniform, 1, timestampBases); - glFlush(); GLsizei primitive_count = (GLsizei)(count / OutputVertexSize); GLsizei max_count = (GLsizei)((OutputVertexBufferDataSize - start) / OutputVertexSize); @@ -688,11 +688,11 @@ void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) { _output_device = output_device; -// for(int builder = 0; builder < NumberOfFields; builder++) -// { -// _run_builders[builder]->reset(); -// } -// _composite_src_runs->reset(); + for(int builder = 0; builder < NumberOfFields; builder++) + { + _run_builders[builder]->reset(); + } + _composite_src_output_y = 0; } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 5356b8ab4..236d3e361 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -60,7 +60,6 @@ class OpenGLOutputBuilder { std::shared_ptr _output_mutex; // transient buffers indicating composite data not yet decoded - std::unique_ptr _composite_src_runs; uint16_t _composite_src_output_y; char *get_output_vertex_shader(const char *header); From 3d7a3ce995b382541e559e13c681e7cebba35ebe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 22:30:05 -0400 Subject: [PATCH 274/307] Eliminated some redundant texture binds. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 36 +++++++++++++---------------- Outputs/CRT/Internals/CRTOpenGL.hpp | 2 +- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 30e3636b2..c60f515d7 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -174,8 +174,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it - glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit); - glBindTexture(GL_TEXTURE_2D, textureName); +// glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit); +// glBindTexture(GL_TEXTURE_2D, textureName); if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) { glTexSubImage2D( GL_TEXTURE_2D, 0, @@ -199,16 +199,16 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // for television, update intermediate buffers and then draw; for a monitor, just draw if(_output_device == Television) { - composite_input_shader_program->bind(); - - compositeTexture->bind_framebuffer(); - glBindVertexArray(source_vertex_array); - - glDisable(GL_BLEND); - // decide how much to draw if(_drawn_source_buffer_data_pointer != _source_buffer_data_pointer) { + composite_input_shader_program->bind(); + + compositeTexture->bind_framebuffer(); + glBindVertexArray(source_vertex_array); + + glDisable(GL_BLEND); + size_t new_data_size = _drawn_source_buffer_data_pointer - _source_buffer_data_pointer; size_t new_data_start = _drawn_source_buffer_data_pointer; _source_buffer_data_pointer %= SourceVertexBufferDataSize; @@ -226,12 +226,12 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out { glDrawArrays(GL_LINES, 0, (GLsizei)((new_data_size - first_data_length) / SourceVertexSize)); } - glFinish(); + + glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); + glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); } // transfer to screen - glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); - compositeTexture->bind_texture(); perform_output_stage(output_width, output_height, composite_output_shader_program.get()); } else @@ -253,11 +253,7 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign { if(shader) { - // definitively establish the viewport - glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); - // Ensure we're back on the output framebuffer, drawing from the output array buffer - glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); glBindVertexArray(output_vertex_array); shader->bind(); @@ -600,7 +596,7 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() free(fragment_shader); }*/ -std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char *vertex_shader, char *fragment_shader) +std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char *vertex_shader, char *fragment_shader, GLint source_texture_unit) { std::unique_ptr shader_program; @@ -627,7 +623,7 @@ std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char GLint scanNormalUniform = shader_program->get_uniform_location("scanNormal"); GLint positionConversionUniform = shader_program->get_uniform_location("positionConversion"); - glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); + glUniform1i(texIDUniform, source_texture_unit); glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); @@ -647,12 +643,12 @@ std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char void OpenGLOutputBuilder::prepare_rgb_output_shader() { - rgb_shader_program = prepare_output_shader(get_rgb_output_vertex_shader(), get_rgb_output_fragment_shader()); + rgb_shader_program = prepare_output_shader(get_rgb_output_vertex_shader(), get_rgb_output_fragment_shader(), first_supplied_buffer_texture_unit); } void OpenGLOutputBuilder::prepare_composite_output_shader() { - composite_output_shader_program = prepare_output_shader(get_composite_output_vertex_shader(), get_composite_output_fragment_shader()); + composite_output_shader_program = prepare_output_shader(get_composite_output_vertex_shader(), get_composite_output_fragment_shader(), 0); } void OpenGLOutputBuilder::prepare_output_vertex_array() diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 236d3e361..ae9897487 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -47,7 +47,7 @@ class OpenGLOutputBuilder { // Methods used by the OpenGL code void prepare_rgb_output_shader(); void prepare_composite_output_shader(); - std::unique_ptr prepare_output_shader(char *vertex_shader, char *fragment_shader); + std::unique_ptr prepare_output_shader(char *vertex_shader, char *fragment_shader, GLint source_texture_unit); void prepare_composite_input_shader(); void prepare_output_vertex_array(); void prepare_source_vertex_array(); From ef83595af38d831eacfb0b444d4c6ea5f4a9bf4c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 22:38:11 -0400 Subject: [PATCH 275/307] Fixed: issue was (i) always thinking the entire source run buffer needed to be issued; and (ii) having fixed that, always thinking that the block that needs redrawing doesn't overflow the buffer. Fixed both. 'Television' mode is now working without explicit synchronisation (and with a lot less work). --- Outputs/CRT/Internals/CRTOpenGL.cpp | 4 ++-- Outputs/CRT/Internals/CRTOpenGL.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index c60f515d7..244857dff 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -209,7 +209,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glDisable(GL_BLEND); - size_t new_data_size = _drawn_source_buffer_data_pointer - _source_buffer_data_pointer; + size_t new_data_size = _source_buffer_data_pointer - _drawn_source_buffer_data_pointer; size_t new_data_start = _drawn_source_buffer_data_pointer; _source_buffer_data_pointer %= SourceVertexBufferDataSize; _drawn_source_buffer_data_pointer = _source_buffer_data_pointer; @@ -220,7 +220,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out new_data_start = 0; } - size_t first_data_length = std::max(SourceVertexBufferDataSize - new_data_start, new_data_size); + size_t first_data_length = std::min(SourceVertexBufferDataSize - new_data_start, new_data_size); glDrawArrays(GL_LINES, (GLint)(new_data_start / SourceVertexSize), (GLsizei)(first_data_length / SourceVertexSize)); if(new_data_size > first_data_length) { diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index ae9897487..26307df82 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -116,7 +116,7 @@ class OpenGLOutputBuilder { inline void complete_source_run() { - _source_buffer_data_pointer += 2 * SourceVertexSize;; + _source_buffer_data_pointer += 2 * SourceVertexSize; _output_mutex->unlock(); } From e2da77fb8b9d1f304c24f317e240b5eeb7501e1a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Apr 2016 22:53:39 -0400 Subject: [PATCH 276/307] Clarified constants, ensured monitor mode works from startup. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 44 ++++++++++++++++------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 244857dff..f887a0456 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -39,7 +39,10 @@ static const GLenum formatForDepth(size_t depth) using namespace Outputs::CRT; namespace { - static const GLenum first_supplied_buffer_texture_unit = 3; + static const GLenum composite_texture_unit = GL_TEXTURE0; + static const GLenum filtered_y_texture_unit = GL_TEXTURE1; + static const GLenum filtered_texture_unit = GL_TEXTURE2; + static const GLenum source_data_texture_unit = GL_TEXTURE3; } OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : @@ -65,19 +68,19 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : glBlendFunc(GL_SRC_ALPHA, GL_ONE); // Create intermediate textures and bind to slots 0, 1 and 2 - glActiveTexture(GL_TEXTURE0); + glActiveTexture(composite_texture_unit); compositeTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); compositeTexture->bind_texture(); - glActiveTexture(GL_TEXTURE1); + glActiveTexture(filtered_y_texture_unit); filteredYTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); filteredYTexture->bind_texture(); - glActiveTexture(GL_TEXTURE2); + glActiveTexture(filtered_texture_unit); filteredTexture = std::unique_ptr(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight)); filteredTexture->bind_texture(); // create the surce texture glGenTextures(1, &textureName); - glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit); + 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); @@ -115,6 +118,9 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : // map that buffer too, for any CRT activity that may occur before the first draw _source_buffer_data = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, SourceVertexBufferDataSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + + // map back the default framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); } OpenGLOutputBuilder::~OpenGLOutputBuilder() @@ -174,8 +180,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // upload more source pixel data if any; we'll always resubmit the last line submitted last // time as it may have had extra data appended to it -// glActiveTexture(GL_TEXTURE0 + first_supplied_buffer_texture_unit); -// glBindTexture(GL_TEXTURE_2D, textureName); if(_buffer_builder->_next_write_y_position < _buffer_builder->last_uploaded_line) { glTexSubImage2D( GL_TEXTURE_2D, 0, @@ -253,17 +257,8 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign { if(shader) { - // Ensure we're back on the output framebuffer, drawing from the output array buffer - glBindVertexArray(output_vertex_array); - - shader->bind(); - - // update uniforms - push_size_uniforms(output_width, output_height); - // clear the buffer glClear(GL_COLOR_BUFFER_BIT); - glEnable(GL_BLEND); // draw all sitting frames unsigned int run = (unsigned int)_run_write_pointer; @@ -281,6 +276,15 @@ void OpenGLOutputBuilder::perform_output_stage(unsigned int output_width, unsign if(count > 0) { + glEnable(GL_BLEND); + + // Ensure we're back on the output framebuffer, drawing from the output array buffer + glBindVertexArray(output_vertex_array); + shader->bind(); + + // update uniforms + push_size_uniforms(output_width, output_height); + // draw glUniform4fv(timestampBaseUniform, 1, timestampBases); @@ -523,7 +527,7 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() GLint outputTextureSizeUniform = composite_input_shader_program->get_uniform_location("outputTextureSize"); composite_input_shader_program->bind(); - glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); + glUniform1i(texIDUniform, source_data_texture_unit - GL_TEXTURE0); glUniform1f(phaseCyclesPerTickUniform, (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line)); glUniform2i(outputTextureSizeUniform, IntermediateBufferWidth, IntermediateBufferHeight); } @@ -623,7 +627,7 @@ std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char GLint scanNormalUniform = shader_program->get_uniform_location("scanNormal"); GLint positionConversionUniform = shader_program->get_uniform_location("positionConversion"); - glUniform1i(texIDUniform, source_texture_unit); + glUniform1i(texIDUniform, source_texture_unit - GL_TEXTURE0); glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); @@ -643,12 +647,12 @@ std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char void OpenGLOutputBuilder::prepare_rgb_output_shader() { - rgb_shader_program = prepare_output_shader(get_rgb_output_vertex_shader(), get_rgb_output_fragment_shader(), first_supplied_buffer_texture_unit); + rgb_shader_program = prepare_output_shader(get_rgb_output_vertex_shader(), get_rgb_output_fragment_shader(), source_data_texture_unit); } void OpenGLOutputBuilder::prepare_composite_output_shader() { - composite_output_shader_program = prepare_output_shader(get_composite_output_vertex_shader(), get_composite_output_fragment_shader(), 0); + composite_output_shader_program = prepare_output_shader(get_composite_output_vertex_shader(), get_composite_output_fragment_shader(), composite_texture_unit); } void OpenGLOutputBuilder::prepare_output_vertex_array() From 8232ed765e468da77c0c9c18035b344d24ab546e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 20 Apr 2016 20:44:25 -0400 Subject: [PATCH 277/307] Eliminated use of zero-alpha runs to clear new lines in the intermediate buffer in favour of a scissored glClear. It's just an easier way to scale the current approach to three intermediate buffers. --- Outputs/CRT/CRT.cpp | 10 ----- Outputs/CRT/Internals/CRTOpenGL.cpp | 61 +++++++++++++++++++++++++---- Outputs/CRT/Internals/CRTOpenGL.hpp | 6 +-- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 50ef7a3a1..91567e77c 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -106,7 +106,6 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, #define source_output_position_y(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfOutputPosition + 2]) #define source_phase(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndAlpha + 0] #define source_amplitude(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndAlpha + 1] -#define source_alpha(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndAlpha + 2] #define source_phase_time(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseTime]) 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) @@ -165,7 +164,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi source_phase(0) = source_phase(1) = _colour_burst_phase; source_amplitude(0) = source_amplitude(1) = _colour_burst_amplitude; source_phase_time(0) = source_phase_time(1) = _colour_burst_time; - source_alpha(0) = source_alpha(1) = 255; } } @@ -244,14 +242,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) { _openGL_output_builder->increment_composite_output_y(); - - // store a run to clear the new line - next_run = _openGL_output_builder->get_next_source_run(); - source_output_position_x(0) = 0; - source_output_position_x(1) = IntermediateBufferWidth; - source_output_position_y(0) = source_output_position_y(1) = _openGL_output_builder->get_composite_output_y(); - source_alpha(0) = source_alpha(1) = 0; - _openGL_output_builder->complete_source_run(); } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index f887a0456..a9ea0d8b0 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -206,12 +206,59 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // decide how much to draw if(_drawn_source_buffer_data_pointer != _source_buffer_data_pointer) { + // determine how many lines are newly reclaimed; they'll need to be cleared + uint16_t clearing_zones[4]; + int number_of_clearing_zones = 0; + + if(_composite_src_output_y != _cleared_composite_output_y) + { + uint16_t lines_to_clear = _composite_src_output_y - _cleared_composite_output_y; + if(lines_to_clear > IntermediateBufferHeight) + { + number_of_clearing_zones = 1; + clearing_zones[0] = 0; + clearing_zones[1] = IntermediateBufferHeight; + } + else + { + clearing_zones[0] = (_cleared_composite_output_y+1)%IntermediateBufferHeight; + if(clearing_zones[0]+lines_to_clear < IntermediateBufferHeight) + { + clearing_zones[1] = lines_to_clear; + number_of_clearing_zones = 1; + } + else + { + clearing_zones[1] = IntermediateBufferHeight - clearing_zones[0]; + clearing_zones[2] = 0; + clearing_zones[3] = lines_to_clear - clearing_zones[1]; + number_of_clearing_zones = 2; + } + } + + _composite_src_output_y %= IntermediateBufferHeight; + _cleared_composite_output_y = _composite_src_output_y; + } + + // all drawing will be from the source vertex array and without blending + glBindVertexArray(source_vertex_array); + glDisable(GL_BLEND); + + // switch to the initial texture + compositeTexture->bind_framebuffer(); composite_input_shader_program->bind(); - compositeTexture->bind_framebuffer(); - glBindVertexArray(source_vertex_array); - - glDisable(GL_BLEND); + // clear as desired + if(number_of_clearing_zones) + { + glEnable(GL_SCISSOR_TEST); + for(int c = 0; c < number_of_clearing_zones; c++) + { + glScissor(0, clearing_zones[c*2], IntermediateBufferWidth, clearing_zones[c*2 + 1]); + glClear(GL_COLOR_BUFFER_BIT); + } + glDisable(GL_SCISSOR_TEST); + } size_t new_data_size = _source_buffer_data_pointer - _drawn_source_buffer_data_pointer; size_t new_data_start = _drawn_source_buffer_data_pointer; @@ -231,6 +278,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glDrawArrays(GL_LINES, 0, (GLsizei)((new_data_size - first_data_length) / SourceVertexSize)); } + // switch back to screen output glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); } @@ -358,7 +406,6 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "out vec2 inputPositionVarying;" "out vec2 iInputPositionVarying;" "out float phaseVarying;" - "out float alphaVarying;" "void main(void)" "{" @@ -367,7 +414,6 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "inputPositionVarying = (inputPosition + vec2(0.0, 0.5)) / vec2(textureSize);" "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAmplitudeAndAlpha.x) * 2.0 * 3.141592654;" - "alphaVarying = phaseAmplitudeAndAlpha.z;" "vec2 eyePosition = 2.0*(outputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;" "gl_Position = vec4(eyePosition, 0.0, 1.0);" @@ -390,7 +436,6 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "in vec2 inputPositionVarying;" "in vec2 iInputPositionVarying;" "in float phaseVarying;" - "in float alphaVarying;" "out vec4 fragColour;" @@ -400,7 +445,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "void main(void)" "{" - "fragColour = vec4(rgb_sample(texID, inputPositionVarying, iInputPositionVarying) * alphaVarying, 1.0);" // composite + "fragColour = vec4(rgb_sample(texID, inputPositionVarying, iInputPositionVarying), 1.0);" "}" , composite_shader); return result; diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 26307df82..6da878397 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -60,7 +60,7 @@ class OpenGLOutputBuilder { std::shared_ptr _output_mutex; // transient buffers indicating composite data not yet decoded - uint16_t _composite_src_output_y; + uint16_t _composite_src_output_y, _cleared_composite_output_y; char *get_output_vertex_shader(const char *header); char *get_rgb_output_vertex_shader(); @@ -150,12 +150,12 @@ class OpenGLOutputBuilder { inline uint16_t get_composite_output_y() { - return _composite_src_output_y; + return _composite_src_output_y % IntermediateBufferHeight; } inline void increment_composite_output_y() { - _composite_src_output_y = (_composite_src_output_y + 1) % IntermediateBufferHeight; + _composite_src_output_y++; } inline void increment_field() From d9a9dffe637b3cb351479f823ea48aafeb941973 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 20 Apr 2016 21:05:32 -0400 Subject: [PATCH 278/307] Factored out wraparound buffer addressing where it's otherwise going to get really repetitive; need to look at using the same approach for the glTexSubImage2D step at least but that probably means properly encapsulating the buffer builder's state. --- Outputs/CRT/Internals/CRTConstants.hpp | 30 +++---- .../CRT/Internals/CRTInputBufferBuilder.hpp | 6 -- Outputs/CRT/Internals/CRTOpenGL.cpp | 84 +++++++++---------- Outputs/CRT/Internals/CRTOpenGL.hpp | 12 +-- 4 files changed, 59 insertions(+), 73 deletions(-) diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index 27b45cb1a..6151ecc79 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -17,30 +17,30 @@ namespace CRT { // Output vertices are those used to copy from an input buffer — whether it describes data that maps directly to RGB // or is one of the intermediate buffers that we've used to convert from composite towards RGB. -const size_t OutputVertexOffsetOfPosition = 0; -const size_t OutputVertexOffsetOfTexCoord = 4; -const size_t OutputVertexOffsetOfTimestamp = 8; -const size_t OutputVertexOffsetOfLateral = 12; -const size_t OutputVertexOffsetOfFrameID = 13; +const GLsizei OutputVertexOffsetOfPosition = 0; +const GLsizei OutputVertexOffsetOfTexCoord = 4; +const GLsizei OutputVertexOffsetOfTimestamp = 8; +const GLsizei OutputVertexOffsetOfLateral = 12; +const GLsizei OutputVertexOffsetOfFrameID = 13; -const size_t OutputVertexSize = 16; +const GLsizei OutputVertexSize = 16; // Input vertices, used only in composite mode, map from the input buffer to temporary buffer locations; such // remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour -const size_t SourceVertexOffsetOfInputPosition = 0; -const size_t SourceVertexOffsetOfOutputPosition = 4; -const size_t SourceVertexOffsetOfPhaseAmplitudeAndAlpha = 8; -const size_t SourceVertexOffsetOfPhaseTime = 12; +const GLsizei SourceVertexOffsetOfInputPosition = 0; +const GLsizei SourceVertexOffsetOfOutputPosition = 4; +const GLsizei SourceVertexOffsetOfPhaseAmplitudeAndAlpha = 8; +const GLsizei SourceVertexOffsetOfPhaseTime = 12; -const size_t SourceVertexSize = 16; +const GLsizei SourceVertexSize = 16; // These constants hold the size of the rolling buffer to which the CPU writes -const int InputBufferBuilderWidth = 2048; -const int InputBufferBuilderHeight = 1024; +const GLsizei InputBufferBuilderWidth = 2048; +const GLsizei InputBufferBuilderHeight = 1024; // This is the size of the intermediate buffers used during composite to RGB conversion -const int IntermediateBufferWidth = 2048; -const int IntermediateBufferHeight = 2048; +const GLsizei IntermediateBufferWidth = 2048; +const GLsizei IntermediateBufferHeight = 2048; // Some internal buffer sizes const GLsizeiptr OutputVertexBufferDataSize = 262080; // a multiple of 6 * OutputVertexSize diff --git a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp index d6eab1ab1..4e074f2bc 100644 --- a/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp +++ b/Outputs/CRT/Internals/CRTInputBufferBuilder.hpp @@ -43,12 +43,6 @@ struct CRTInputBufferBuilder { { _next_write_x_position = 0; _next_write_y_position = (_next_write_y_position+1)%InputBufferBuilderHeight; -// if(!_next_write_y_position) -// { -// glClientWaitSync(_wraparound_sync, 0, ~(GLuint64)0); -// glDeleteSync(_wraparound_sync); -// _wraparound_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); -// } } inline uint8_t *get_write_target(uint8_t *buffer) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index a9ea0d8b0..706368e6f 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -36,6 +36,34 @@ static const GLenum formatForDepth(size_t depth) } } +static int getCircularRanges(GLsizei start, GLsizei end, GLsizei buffer_length, GLsizei *ranges) +{ + GLsizei length = end - start; + if(!length) return 0; + if(length > buffer_length) + { + ranges[0] = 0; + ranges[1] = buffer_length; + return 1; + } + else + { + ranges[0] = start % buffer_length; + if(ranges[0]+length < buffer_length) + { + ranges[1] = length; + return 1; + } + else + { + ranges[1] = buffer_length - ranges[0]; + ranges[2] = 0; + ranges[3] = length - ranges[1]; + return 2; + } + } +} + using namespace Outputs::CRT; namespace { @@ -207,38 +235,14 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out if(_drawn_source_buffer_data_pointer != _source_buffer_data_pointer) { // determine how many lines are newly reclaimed; they'll need to be cleared - uint16_t clearing_zones[4]; - int number_of_clearing_zones = 0; + GLsizei clearing_zones[4], drawing_zones[4]; + int number_of_clearing_zones = getCircularRanges(_cleared_composite_output_y+1, _composite_src_output_y+1, IntermediateBufferHeight, clearing_zones); + int number_of_drawing_zones = getCircularRanges(_drawn_source_buffer_data_pointer, _source_buffer_data_pointer, SourceVertexBufferDataSize, drawing_zones); - if(_composite_src_output_y != _cleared_composite_output_y) - { - uint16_t lines_to_clear = _composite_src_output_y - _cleared_composite_output_y; - if(lines_to_clear > IntermediateBufferHeight) - { - number_of_clearing_zones = 1; - clearing_zones[0] = 0; - clearing_zones[1] = IntermediateBufferHeight; - } - else - { - clearing_zones[0] = (_cleared_composite_output_y+1)%IntermediateBufferHeight; - if(clearing_zones[0]+lines_to_clear < IntermediateBufferHeight) - { - clearing_zones[1] = lines_to_clear; - number_of_clearing_zones = 1; - } - else - { - clearing_zones[1] = IntermediateBufferHeight - clearing_zones[0]; - clearing_zones[2] = 0; - clearing_zones[3] = lines_to_clear - clearing_zones[1]; - number_of_clearing_zones = 2; - } - } - - _composite_src_output_y %= IntermediateBufferHeight; - _cleared_composite_output_y = _composite_src_output_y; - } + _composite_src_output_y %= IntermediateBufferHeight; + _cleared_composite_output_y = _composite_src_output_y; + _source_buffer_data_pointer %= SourceVertexBufferDataSize; + _drawn_source_buffer_data_pointer = _source_buffer_data_pointer; // all drawing will be from the source vertex array and without blending glBindVertexArray(source_vertex_array); @@ -260,22 +264,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glDisable(GL_SCISSOR_TEST); } - size_t new_data_size = _source_buffer_data_pointer - _drawn_source_buffer_data_pointer; - size_t new_data_start = _drawn_source_buffer_data_pointer; - _source_buffer_data_pointer %= SourceVertexBufferDataSize; - _drawn_source_buffer_data_pointer = _source_buffer_data_pointer; - - if(new_data_size >= SourceVertexBufferDataSize) + // draw as desired + for(int c = 0; c < number_of_drawing_zones; c++) { - new_data_size = SourceVertexBufferDataSize; - new_data_start = 0; - } - - size_t first_data_length = std::min(SourceVertexBufferDataSize - new_data_start, new_data_size); - glDrawArrays(GL_LINES, (GLint)(new_data_start / SourceVertexSize), (GLsizei)(first_data_length / SourceVertexSize)); - if(new_data_size > first_data_length) - { - glDrawArrays(GL_LINES, 0, (GLsizei)((new_data_size - first_data_length) / SourceVertexSize)); + glDrawArrays(GL_LINES, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); } // switch back to screen output diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 6da878397..ced737db9 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -126,9 +126,9 @@ class OpenGLOutputBuilder { return &_output_buffer_data[_output_buffer_data_pointer]; } - inline void complete_output_run(size_t vertices_written) + inline void complete_output_run(GLsizei vertices_written) { - _run_builders[_run_write_pointer]->amount_of_data += vertices_written * OutputVertexSize; + _run_builders[_run_write_pointer]->amount_of_data += (size_t)(vertices_written * OutputVertexSize); _output_buffer_data_pointer = (_output_buffer_data_pointer + vertices_written * OutputVertexSize) % OutputVertexBufferDataSize; _output_mutex->unlock(); } @@ -162,7 +162,7 @@ class OpenGLOutputBuilder { { _output_mutex->lock(); _run_write_pointer = (_run_write_pointer + 1)%NumberOfFields; - _run_builders[_run_write_pointer]->start = _output_buffer_data_pointer; + _run_builders[_run_write_pointer]->start = (size_t)_output_buffer_data_pointer; _run_builders[_run_write_pointer]->reset(); _output_mutex->unlock(); } @@ -218,11 +218,11 @@ class OpenGLOutputBuilder { GLsizeiptr _input_texture_array_size; uint8_t *_output_buffer_data; - size_t _output_buffer_data_pointer; + GLsizei _output_buffer_data_pointer; uint8_t *_source_buffer_data; - size_t _source_buffer_data_pointer; - size_t _drawn_source_buffer_data_pointer; + GLsizei _source_buffer_data_pointer; + GLsizei _drawn_source_buffer_data_pointer; }; } From 9c89b668aebcc51dbcaf2c419dcd45d4de0154ff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 20 Apr 2016 22:35:46 -0400 Subject: [PATCH 279/307] Made further attempts to improve synchronisation and interrelated timing between display and machine update. --- .../Documents/MachineDocument.swift | 18 +++++++++--- .../Mac/Clock Signal/Views/CSOpenGLView.h | 6 ++-- .../Mac/Clock Signal/Views/CSOpenGLView.m | 28 +++++++++++++------ 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 4801f6c86..473e81aad 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -35,19 +35,29 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe var intendedCyclesPerSecond: Int64 = 0 private var cycleCountError: Int64 = 0 private var lastTime: CVTimeStamp? - final func openGLView(view: CSOpenGLView, didUpdateToTime time: CVTimeStamp) { + private var skippedFrames = 0 + final func openGLView(view: CSOpenGLView, didUpdateToTime time: CVTimeStamp, didSkipPreviousUpdate : Bool, frequency : Double) { if let lastTime = lastTime { // perform (time passed in seconds) * (intended cycles per second), converting and // maintaining an error count to deal with underflow let videoTimeScale64 = Int64(time.videoTimeScale) let videoTimeCount = ((time.videoTime - lastTime.videoTime) * intendedCyclesPerSecond) + cycleCountError cycleCountError = videoTimeCount % videoTimeScale64 - let numberOfCycles = videoTimeCount / videoTimeScale64 + var numberOfCycles = videoTimeCount / videoTimeScale64 - // if the emulation has fallen too far behind then silently limit the request; + // if the emulation has fallen behind then silently limit the request; // some actions — e.g. the host computer waking after sleep — may give us a // prohibitive backlog - runForNumberOfCycles(min(Int32(numberOfCycles), Int32(intendedCyclesPerSecond / 25))) + if didSkipPreviousUpdate { + skippedFrames++ + } else { + skippedFrames = 0 + } + + if skippedFrames > 4 { + numberOfCycles = min(numberOfCycles, Int64(Double(intendedCyclesPerSecond) * frequency)) + } + runForNumberOfCycles(Int32(numberOfCycles)) } lastTime = time } diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h index 250689c1b..f5c821fe8 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h @@ -13,12 +13,12 @@ @protocol CSOpenGLViewDelegate /*! - Tells the delegate that time has advanced. This method will always be called on the same queue - as the @c CSOpenGLViewResponderDelegate methods. + Tells the delegate that time has advanced. @param view The view sending the message. @param time The time to which time has advanced. + @param didSkipPreviousUpdate @c YES if the previous update that would have occurred was skipped because a didUpdateToTime: call prior to that was still ongoing; @c NO otherwise. */ -- (void)openGLView:(nonnull CSOpenGLView *)view didUpdateToTime:(CVTimeStamp)time; +- (void)openGLView:(nonnull CSOpenGLView *)view didUpdateToTime:(CVTimeStamp)time didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate frequency:(double)frequency; /*! Requests that the delegate produce an image of its current output state. May be called on diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 797b0f1b0..8e41cc50e 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -13,6 +13,7 @@ @implementation CSOpenGLView { CVDisplayLinkRef _displayLink; uint32_t _updateIsOngoing; + BOOL _hasSkipped; } - (void)prepareOpenGL @@ -23,7 +24,7 @@ // Create a display link capable of being used with all active displays CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); - + // Set the renderer output callback function CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void * __nullable)(self)); @@ -42,25 +43,36 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { - CSOpenGLView *view = (__bridge CSOpenGLView *)displayLinkContext; - [view drawAtTime:now]; + CSOpenGLView *const view = (__bridge CSOpenGLView *)displayLinkContext; + [view drawAtTime:now frequency:CVDisplayLinkGetActualOutputVideoRefreshPeriod(displayLink)]; return kCVReturnSuccess; } -- (void)drawAtTime:(const CVTimeStamp *)now +- (void)drawAtTime:(const CVTimeStamp *)now frequency:(double)frequency { + const uint32_t processingMask = 0x01; + const uint32_t drawingMask = 0x02; + // Always post a -openGLView:didUpdateToTime:. This is the hook upon which the substantial processing occurs. - [self.delegate openGLView:self didUpdateToTime:*now]; + if(!OSAtomicTestAndSet(processingMask, &_updateIsOngoing)) + { + CVTimeStamp time = *now; + BOOL didSkip = _hasSkipped; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + [self.delegate openGLView:self didUpdateToTime:time didSkipPreviousUpdate:didSkip frequency:frequency]; + OSAtomicTestAndClear(processingMask, &_updateIsOngoing); + }); + _hasSkipped = NO; + } else _hasSkipped = YES; // Draw the display only if a previous draw is not still ongoing. -drawViewOnlyIfDirty: is guaranteed // to be safe to call concurrently with -openGLView:updateToTime: so there's no need to worry about // the above interrupting the below or vice versa. - const uint32_t activityMask = 0x01; - if(!OSAtomicTestAndSet(activityMask, &_updateIsOngoing)) + if(!OSAtomicTestAndSet(drawingMask, &_updateIsOngoing)) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self drawViewOnlyIfDirty:YES]; - OSAtomicTestAndClear(activityMask, &_updateIsOngoing); + OSAtomicTestAndClear(drawingMask, &_updateIsOngoing); }); } } From 638b08302b0ebf397ab35d0054642db97083a508 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Apr 2016 19:37:32 -0400 Subject: [PATCH 280/307] The initial composite buffer is now genuinely a composite signal. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 706368e6f..47082f4d8 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -405,7 +405,7 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "iInputPositionVarying = inputPosition;" "inputPositionVarying = (inputPosition + vec2(0.0, 0.5)) / vec2(textureSize);" - "phaseVarying = (phaseCyclesPerTick * phaseTime + phaseAmplitudeAndAlpha.x) * 2.0 * 3.141592654;" + "phaseVarying = (phaseCyclesPerTick * (outputPosition.x - phaseTime) + phaseAmplitudeAndAlpha.x) * 2.0 * 3.141592654;" "vec2 eyePosition = 2.0*(outputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;" "gl_Position = vec4(eyePosition, 0.0, 1.0);" @@ -414,11 +414,21 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() char *OpenGLOutputBuilder::get_input_fragment_shader() { - const char *composite_shader = _composite_shader; + char *composite_shader = _composite_shader; if(!composite_shader) { - composite_shader = _rgb_shader; - // TODO: synthesise an RGB -> (selected colour space) shader + asprintf(&composite_shader, + "%s\n" + "const mat3 rgbToYUV = mat3(0.299, -0.14713, 0.615, 0.587, -0.28886, -0.51499, 0.114, 0.436, -0.10001);" + "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" + "{" + "vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));" + "vec3 yuvColour = rgbToYUV * rgbColour;" + "vec2 quadrature = vec2(sin(phase), cos(phase)) * 0.1;" + "return dot(yuvColour, vec3(0.9, quadrature));" + "}", + _rgb_shader); + // TODO: use YIQ if this is NTSC } char *result; @@ -437,9 +447,12 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "void main(void)" "{" - "fragColour = vec4(rgb_sample(texID, inputPositionVarying, iInputPositionVarying), 1.0);" + "fragColour = vec4(composite_sample(texID, inputPositionVarying, iInputPositionVarying, phaseVarying));" "}" , composite_shader); + + if(!_composite_shader) free(composite_shader); + return result; } From 4b9985626d7b333e6246503d12991cd55a02e285 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Apr 2016 19:53:41 -0400 Subject: [PATCH 281/307] Started commuting alpha to direction. The incoming amplitude is now honoured. --- Outputs/CRT/CRT.cpp | 4 ++-- Outputs/CRT/Internals/CRTConstants.hpp | 2 +- Outputs/CRT/Internals/CRTOpenGL.cpp | 21 ++++++++++++--------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 91567e77c..0c5dd47d8 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -104,8 +104,8 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, #define source_input_position_y(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfInputPosition + 2]) #define source_output_position_x(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfOutputPosition + 0]) #define source_output_position_y(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfOutputPosition + 2]) -#define source_phase(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndAlpha + 0] -#define source_amplitude(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndAlpha + 1] +#define source_phase(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndOffset + 0] +#define source_amplitude(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndOffset + 1] #define source_phase_time(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseTime]) 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) diff --git a/Outputs/CRT/Internals/CRTConstants.hpp b/Outputs/CRT/Internals/CRTConstants.hpp index 6151ecc79..857a3eef9 100644 --- a/Outputs/CRT/Internals/CRTConstants.hpp +++ b/Outputs/CRT/Internals/CRTConstants.hpp @@ -29,7 +29,7 @@ const GLsizei OutputVertexSize = 16; // remapping occurs to ensure a continous stream of data for each scan, giving correct out-of-bounds behaviour const GLsizei SourceVertexOffsetOfInputPosition = 0; const GLsizei SourceVertexOffsetOfOutputPosition = 4; -const GLsizei SourceVertexOffsetOfPhaseAmplitudeAndAlpha = 8; +const GLsizei SourceVertexOffsetOfPhaseAmplitudeAndOffset = 8; const GLsizei SourceVertexOffsetOfPhaseTime = 12; const GLsizei SourceVertexSize = 16; diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 47082f4d8..df92494dc 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -388,7 +388,7 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "in vec2 inputPosition;" "in vec2 outputPosition;" - "in vec3 phaseAmplitudeAndAlpha;" + "in vec3 phaseAmplitudeAndOffset;" "in float phaseTime;" "uniform float phaseCyclesPerTick;" @@ -398,6 +398,7 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "out vec2 inputPositionVarying;" "out vec2 iInputPositionVarying;" "out float phaseVarying;" + "out float amplitudeVarying;" "void main(void)" "{" @@ -405,7 +406,8 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "iInputPositionVarying = inputPosition;" "inputPositionVarying = (inputPosition + vec2(0.0, 0.5)) / vec2(textureSize);" - "phaseVarying = (phaseCyclesPerTick * (outputPosition.x - phaseTime) + phaseAmplitudeAndAlpha.x) * 2.0 * 3.141592654;" + "phaseVarying = (phaseCyclesPerTick * (outputPosition.x - phaseTime) + phaseAmplitudeAndOffset.x) * 2.0 * 3.141592654;" + "amplitudeVarying = phaseAmplitudeAndOffset.y;" "vec2 eyePosition = 2.0*(outputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;" "gl_Position = vec4(eyePosition, 0.0, 1.0);" @@ -420,12 +422,12 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() asprintf(&composite_shader, "%s\n" "const mat3 rgbToYUV = mat3(0.299, -0.14713, 0.615, 0.587, -0.28886, -0.51499, 0.114, 0.436, -0.10001);" - "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)" + "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" "{" "vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));" "vec3 yuvColour = rgbToYUV * rgbColour;" - "vec2 quadrature = vec2(sin(phase), cos(phase)) * 0.1;" - "return dot(yuvColour, vec3(0.9, quadrature));" + "vec2 quadrature = vec2(sin(phase), cos(phase)) * amplitude;" + "return dot(yuvColour, vec3(1.0 - amplitude, quadrature));" "}", _rgb_shader); // TODO: use YIQ if this is NTSC @@ -438,6 +440,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "in vec2 inputPositionVarying;" "in vec2 iInputPositionVarying;" "in float phaseVarying;" + "in float amplitudeVarying;" "out vec4 fragColour;" @@ -447,7 +450,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() "void main(void)" "{" - "fragColour = vec4(composite_sample(texID, inputPositionVarying, iInputPositionVarying, phaseVarying));" + "fragColour = vec4(composite_sample(texID, inputPositionVarying, iInputPositionVarying, phaseVarying, amplitudeVarying));" "}" , composite_shader); @@ -591,21 +594,21 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() { GLint inputPositionAttribute = composite_input_shader_program->get_attrib_location("inputPosition"); GLint outputPositionAttribute = composite_input_shader_program->get_attrib_location("outputPosition"); - GLint phaseAmplitudeAndAlphaAttribute = composite_input_shader_program->get_attrib_location("phaseAmplitudeAndAlpha"); + GLint phaseAmplitudeAndOffsetAttribute = composite_input_shader_program->get_attrib_location("phaseAmplitudeAndOffset"); GLint phaseTimeAttribute = composite_input_shader_program->get_attrib_location("phaseTime"); glBindVertexArray(source_vertex_array); glEnableVertexAttribArray((GLuint)inputPositionAttribute); glEnableVertexAttribArray((GLuint)outputPositionAttribute); - glEnableVertexAttribArray((GLuint)phaseAmplitudeAndAlphaAttribute); + glEnableVertexAttribArray((GLuint)phaseAmplitudeAndOffsetAttribute); glEnableVertexAttribArray((GLuint)phaseTimeAttribute); const GLsizei vertexStride = SourceVertexSize; glBindBuffer(GL_ARRAY_BUFFER, source_array_buffer); glVertexAttribPointer((GLuint)inputPositionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfInputPosition); glVertexAttribPointer((GLuint)outputPositionAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfOutputPosition); - glVertexAttribPointer((GLuint)phaseAmplitudeAndAlphaAttribute, 3, GL_UNSIGNED_BYTE, GL_TRUE, vertexStride, (void *)SourceVertexOffsetOfPhaseAmplitudeAndAlpha); + glVertexAttribPointer((GLuint)phaseAmplitudeAndOffsetAttribute, 3, GL_UNSIGNED_BYTE, GL_TRUE, vertexStride, (void *)SourceVertexOffsetOfPhaseAmplitudeAndOffset); glVertexAttribPointer((GLuint)phaseTimeAttribute, 2, GL_UNSIGNED_SHORT, GL_FALSE, vertexStride, (void *)SourceVertexOffsetOfPhaseTime); } } From 23ce032ba191cb784082f39dae3451ee40f35e00 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Apr 2016 20:21:34 -0400 Subject: [PATCH 282/307] Started edging towards the necessary flexibility for the other two intermediate shaders. --- Outputs/CRT/CRT.cpp | 3 ++ Outputs/CRT/Internals/CRTOpenGL.cpp | 58 +++++++++++++++++++++-------- Outputs/CRT/Internals/CRTOpenGL.hpp | 5 ++- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 0c5dd47d8..6e6e11c90 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -106,6 +106,7 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, #define source_output_position_y(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfOutputPosition + 2]) #define source_phase(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndOffset + 0] #define source_amplitude(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndOffset + 1] +#define source_offset(v) next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseAmplitudeAndOffset + 2] #define source_phase_time(v) (*(uint16_t *)&next_run[SourceVertexSize*v + SourceVertexOffsetOfPhaseTime]) 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) @@ -164,6 +165,8 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi source_phase(0) = source_phase(1) = _colour_burst_phase; source_amplitude(0) = source_amplitude(1) = _colour_burst_amplitude; source_phase_time(0) = source_phase_time(1) = _colour_burst_time; + source_offset(0) = 0; + source_offset(1) = 255; } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index df92494dc..cb5037928 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -381,9 +381,10 @@ void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader) #pragma mark - Input vertex shader (i.e. from source data to intermediate line layout) -char *OpenGLOutputBuilder::get_input_vertex_shader() +char *OpenGLOutputBuilder::get_input_vertex_shader(const char *input_position) { - return strdup( + char *result; + asprintf(&result, "#version 150\n" "in vec2 inputPosition;" @@ -394,6 +395,7 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "uniform float phaseCyclesPerTick;" "uniform usampler2D texID;" "uniform ivec2 outputTextureSize;" + "uniform float extension;" "out vec2 inputPositionVarying;" "out vec2 iInputPositionVarying;" @@ -402,16 +404,21 @@ char *OpenGLOutputBuilder::get_input_vertex_shader() "void main(void)" "{" + "vec2 extensionVector = vec2(extension, 0.0) * 2.0 * (phaseAmplitudeAndOffset.z - 0.5);" + "vec2 extendedInputPosition = %s + extensionVector;" + "vec2 extendedOutputPosition = outputPosition + extensionVector;" + "ivec2 textureSize = textureSize(texID, 0);" - "iInputPositionVarying = inputPosition;" - "inputPositionVarying = (inputPosition + vec2(0.0, 0.5)) / vec2(textureSize);" + "iInputPositionVarying = extendedInputPosition;" + "inputPositionVarying = (extendedInputPosition + vec2(0.0, 0.5)) / vec2(textureSize);" "phaseVarying = (phaseCyclesPerTick * (outputPosition.x - phaseTime) + phaseAmplitudeAndOffset.x) * 2.0 * 3.141592654;" "amplitudeVarying = phaseAmplitudeAndOffset.y;" - "vec2 eyePosition = 2.0*(outputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;" + "vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;" "gl_Position = vec4(eyePosition, 0.0, 1.0);" - "}"); + "}", input_position); + return result; } char *OpenGLOutputBuilder::get_input_fragment_shader() @@ -567,25 +574,44 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct #pragma mark - Program compilation -void OpenGLOutputBuilder::prepare_composite_input_shader() +std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader(const char *input_position, char *fragment_shader, GLenum texture_unit, bool extends) { - char *vertex_shader = get_input_vertex_shader(); - char *fragment_shader = get_input_fragment_shader(); + std::unique_ptr shader; + char *vertex_shader = get_input_vertex_shader(input_position); if(vertex_shader && fragment_shader) { - composite_input_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader, nullptr)); + OpenGL::Shader::AttributeBinding bindings[] = + { + {"inputPosition", 0}, + {"outputPosition", 1}, + {"phaseAmplitudeAndOffset", 2}, + {"phaseTime", 3}, + {nullptr} + }; + shader = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader, bindings)); - GLint texIDUniform = composite_input_shader_program->get_uniform_location("texID"); - GLint phaseCyclesPerTickUniform = composite_input_shader_program->get_uniform_location("phaseCyclesPerTick"); - GLint outputTextureSizeUniform = composite_input_shader_program->get_uniform_location("outputTextureSize"); + GLint texIDUniform = shader->get_uniform_location("texID"); + GLint phaseCyclesPerTickUniform = shader->get_uniform_location("phaseCyclesPerTick"); + GLint outputTextureSizeUniform = shader->get_uniform_location("outputTextureSize"); + GLint extensionUniform = shader->get_uniform_location("extension"); - composite_input_shader_program->bind(); - glUniform1i(texIDUniform, source_data_texture_unit - GL_TEXTURE0); - glUniform1f(phaseCyclesPerTickUniform, (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line)); + shader->bind(); + glUniform1i(texIDUniform, (GLint)(texture_unit - GL_TEXTURE0)); glUniform2i(outputTextureSizeUniform, IntermediateBufferWidth, IntermediateBufferHeight); + + float phaseCyclesPerTick = (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line); + glUniform1f(phaseCyclesPerTickUniform, phaseCyclesPerTick); + glUniform1f(extensionUniform, extends ? ceilf(1.0f / phaseCyclesPerTick) : 0.0f); } free(vertex_shader); free(fragment_shader); + + return shader; +} + +void OpenGLOutputBuilder::prepare_composite_input_shader() +{ + composite_input_shader_program = prepare_intermediate_shader("inputPosition", get_input_fragment_shader(), source_data_texture_unit, false); } void OpenGLOutputBuilder::prepare_source_vertex_array() diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index ced737db9..237e98864 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -48,7 +48,10 @@ class OpenGLOutputBuilder { void prepare_rgb_output_shader(); void prepare_composite_output_shader(); std::unique_ptr prepare_output_shader(char *vertex_shader, char *fragment_shader, GLint source_texture_unit); + void prepare_composite_input_shader(); + std::unique_ptr prepare_intermediate_shader(const char *input_position, char *fragment_shader, GLenum texture_unit, bool extends); + void prepare_output_vertex_array(); void prepare_source_vertex_array(); void push_size_uniforms(unsigned int output_width, unsigned int output_height); @@ -70,7 +73,7 @@ class OpenGLOutputBuilder { char *get_rgb_output_fragment_shader(); char *get_composite_output_fragment_shader(); - char *get_input_vertex_shader(); + char *get_input_vertex_shader(const char *input_position); char *get_input_fragment_shader(); std::unique_ptr rgb_shader_program; From ae2760e034e4acadbcc68434126afcec90370a05 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Apr 2016 21:07:29 -0400 Subject: [PATCH 283/307] Colour phase is now updated and varies per line. --- Machines/Electron/Electron.cpp | 7 +++++-- Machines/Electron/Electron.hpp | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index b85797814..8c29dc591 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -46,7 +46,8 @@ Machine::Machine() : _audioOutputPosition(0), _current_pixel_line(-1), _use_fast_tape_hack(false), - _crt(nullptr) + _crt(nullptr), + _phase(0) { memset(_key_states, 0, sizeof(_key_states)); memset(_palette, 0xf, sizeof(_palette)); @@ -421,6 +422,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _frameCycles += cycles; + if(!(_frameCycles&127)) _phase += 64; + // deal with frame wraparound by updating the two dependent subsystems // as though the exact end of frame had been hit, then reset those // and allow the frame cycle counter to assume its real value @@ -779,7 +782,7 @@ inline void Machine::update_display() if(this_cycle < 24) { if(final_cycle < 24) return; - _crt->output_colour_burst((24-9) * crt_cycles_multiplier, 0, 12); + _crt->output_colour_burst((24-9) * crt_cycles_multiplier, _phase, 12); _displayOutputPosition += 24-9; this_cycle = 24; // TODO: phase shouldn't be zero on every line diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 34db2abe4..17b59c1bd 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -190,9 +190,10 @@ class Machine: public CPU6502::Processor, Tape::Delegate { uint16_t _screenModeBaseAddress; uint16_t _startScreenAddress; - // Counters related to simultaneous subsystems; + // Counters related to simultaneous subsystems unsigned int _frameCycles, _displayOutputPosition; unsigned int _audioOutputPosition, _audioOutputPositionError; + uint8_t _phase; struct { uint16_t forty1bpp[256]; From e7ed1224a57dfbafb0db79c8d1060cc3d8ec3e92 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Apr 2016 21:32:36 -0400 Subject: [PATCH 284/307] Introduced the second filtering stage, albeit that it presently does nothing whatsoever. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 80 ++++++++++++++++++++--------- Outputs/CRT/Internals/CRTOpenGL.hpp | 7 +-- 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index cb5037928..6c205a11d 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -248,26 +248,38 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glBindVertexArray(source_vertex_array); glDisable(GL_BLEND); - // switch to the initial texture - compositeTexture->bind_framebuffer(); - composite_input_shader_program->bind(); - - // clear as desired - if(number_of_clearing_zones) + OpenGL::TextureTarget *targets[] = { + compositeTexture.get(), + filteredYTexture.get(), + filteredTexture.get() + }; + OpenGL::Shader *shaders[] = { + composite_input_shader_program.get(), + composite_y_filter_shader_program.get() + }; + for(int stage = 0; stage < 2; stage++) { - glEnable(GL_SCISSOR_TEST); - for(int c = 0; c < number_of_clearing_zones; c++) + // switch to the initial texture + targets[stage]->bind_framebuffer(); + shaders[stage]->bind(); + + // clear as desired + if(number_of_clearing_zones) { - glScissor(0, clearing_zones[c*2], IntermediateBufferWidth, clearing_zones[c*2 + 1]); - glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_SCISSOR_TEST); + for(int c = 0; c < number_of_clearing_zones; c++) + { + glScissor(0, clearing_zones[c*2], IntermediateBufferWidth, clearing_zones[c*2 + 1]); + glClear(GL_COLOR_BUFFER_BIT); + } + glDisable(GL_SCISSOR_TEST); } - glDisable(GL_SCISSOR_TEST); - } - // draw as desired - for(int c = 0; c < number_of_drawing_zones; c++) - { - glDrawArrays(GL_LINES, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); + // draw as desired + for(int c = 0; c < number_of_drawing_zones; c++) + { + glDrawArrays(GL_LINES, drawing_zones[c*2] / SourceVertexSize, drawing_zones[c*2 + 1] / SourceVertexSize); + } } // switch back to screen output @@ -381,7 +393,7 @@ void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader) #pragma mark - Input vertex shader (i.e. from source data to intermediate line layout) -char *OpenGLOutputBuilder::get_input_vertex_shader(const char *input_position) +char *OpenGLOutputBuilder::get_input_vertex_shader(const char *input_position, const char *header) { char *result; asprintf(&result, @@ -393,10 +405,11 @@ char *OpenGLOutputBuilder::get_input_vertex_shader(const char *input_position) "in float phaseTime;" "uniform float phaseCyclesPerTick;" - "uniform usampler2D texID;" "uniform ivec2 outputTextureSize;" "uniform float extension;" + "\n%s\n" + "out vec2 inputPositionVarying;" "out vec2 iInputPositionVarying;" "out float phaseVarying;" @@ -417,7 +430,7 @@ char *OpenGLOutputBuilder::get_input_vertex_shader(const char *input_position) "vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;" "gl_Position = vec4(eyePosition, 0.0, 1.0);" - "}", input_position); + "}", header, input_position); return result; } @@ -466,6 +479,26 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() return result; } +char *OpenGLOutputBuilder::get_y_filter_fragment_shader() +{ + return strdup( + "#version 150\n" + + "in vec2 inputPositionVarying;" + "in vec2 iInputPositionVarying;" + "in float phaseVarying;" + "in float amplitudeVarying;" + + "out vec4 fragColour;" + + "uniform sampler2D texID;" + + "void main(void)" + "{" + "fragColour = texture(texID, inputPositionVarying);" + "}"); +} + #pragma mark - Intermediate vertex shaders (i.e. from intermediate line layout to intermediate line layout) #pragma mark - Output vertex shader @@ -574,10 +607,10 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct #pragma mark - Program compilation -std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader(const char *input_position, char *fragment_shader, GLenum texture_unit, bool extends) +std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader(const char *input_position, const char *header, char *fragment_shader, GLenum texture_unit, bool extends) { std::unique_ptr shader; - char *vertex_shader = get_input_vertex_shader(input_position); + char *vertex_shader = get_input_vertex_shader(input_position, header); if(vertex_shader && fragment_shader) { OpenGL::Shader::AttributeBinding bindings[] = @@ -611,7 +644,8 @@ std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader void OpenGLOutputBuilder::prepare_composite_input_shader() { - composite_input_shader_program = prepare_intermediate_shader("inputPosition", get_input_fragment_shader(), source_data_texture_unit, false); + composite_input_shader_program = prepare_intermediate_shader("inputPosition", "uniform usampler2D texID;", get_input_fragment_shader(), source_data_texture_unit, false); + composite_y_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_y_filter_fragment_shader(), composite_texture_unit, true); } void OpenGLOutputBuilder::prepare_source_vertex_array() @@ -731,7 +765,7 @@ void OpenGLOutputBuilder::prepare_rgb_output_shader() void OpenGLOutputBuilder::prepare_composite_output_shader() { - composite_output_shader_program = prepare_output_shader(get_composite_output_vertex_shader(), get_composite_output_fragment_shader(), composite_texture_unit); + composite_output_shader_program = prepare_output_shader(get_composite_output_vertex_shader(), get_composite_output_fragment_shader(), filtered_y_texture_unit); } void OpenGLOutputBuilder::prepare_output_vertex_array() diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 237e98864..8f5beb59a 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -50,7 +50,7 @@ class OpenGLOutputBuilder { std::unique_ptr prepare_output_shader(char *vertex_shader, char *fragment_shader, GLint source_texture_unit); void prepare_composite_input_shader(); - std::unique_ptr prepare_intermediate_shader(const char *input_position, char *fragment_shader, GLenum texture_unit, bool extends); + std::unique_ptr prepare_intermediate_shader(const char *input_position, const char *header, char *fragment_shader, GLenum texture_unit, bool extends); void prepare_output_vertex_array(); void prepare_source_vertex_array(); @@ -73,11 +73,12 @@ class OpenGLOutputBuilder { char *get_rgb_output_fragment_shader(); char *get_composite_output_fragment_shader(); - char *get_input_vertex_shader(const char *input_position); + char *get_input_vertex_shader(const char *input_position, const char *header); char *get_input_fragment_shader(); + char *get_y_filter_fragment_shader(); std::unique_ptr rgb_shader_program; - std::unique_ptr composite_input_shader_program, composite_output_shader_program; + std::unique_ptr composite_input_shader_program, composite_y_filter_shader_program, composite_output_shader_program; GLuint output_array_buffer, output_vertex_array; GLuint source_array_buffer, source_vertex_array; From cc7bf97a9c6f579c7e970c8759bbe26f2d107ad5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Apr 2016 22:32:30 -0400 Subject: [PATCH 285/307] This now attempts to filter y. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 61 ++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 6c205a11d..ca2c063c0 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -414,6 +414,7 @@ char *OpenGLOutputBuilder::get_input_vertex_shader(const char *input_position, c "out vec2 iInputPositionVarying;" "out float phaseVarying;" "out float amplitudeVarying;" + "out vec2 inputPositionsVarying[11];" "void main(void)" "{" @@ -421,9 +422,24 @@ char *OpenGLOutputBuilder::get_input_vertex_shader(const char *input_position, c "vec2 extendedInputPosition = %s + extensionVector;" "vec2 extendedOutputPosition = outputPosition + extensionVector;" - "ivec2 textureSize = textureSize(texID, 0);" + "vec2 textureSize = vec2(textureSize(texID, 0));" "iInputPositionVarying = extendedInputPosition;" - "inputPositionVarying = (extendedInputPosition + vec2(0.0, 0.5)) / vec2(textureSize);" + "inputPositionVarying = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;" + + "textureSize = textureSize * vec2(1.0);" + "inputPositionsVarying[0] = inputPositionVarying - (vec2(5.0, 0.0) / textureSize);" + "inputPositionsVarying[1] = inputPositionVarying - (vec2(4.0, 0.0) / textureSize);" + "inputPositionsVarying[2] = inputPositionVarying - (vec2(3.0, 0.0) / textureSize);" + "inputPositionsVarying[3] = inputPositionVarying - (vec2(2.0, 0.0) / textureSize);" + "inputPositionsVarying[4] = inputPositionVarying - (vec2(1.0, 0.0) / textureSize);" + + "inputPositionsVarying[5] = inputPositionVarying;" + + "inputPositionsVarying[6] = inputPositionVarying + (vec2(1.0, 0.0) / textureSize);" + "inputPositionsVarying[7] = inputPositionVarying + (vec2(2.0, 0.0) / textureSize);" + "inputPositionsVarying[8] = inputPositionVarying + (vec2(3.0, 0.0) / textureSize);" + "inputPositionsVarying[9] = inputPositionVarying + (vec2(4.0, 0.0) / textureSize);" + "inputPositionsVarying[10] = inputPositionVarying + (vec2(5.0, 0.0) / textureSize);" "phaseVarying = (phaseCyclesPerTick * (outputPosition.x - phaseTime) + phaseAmplitudeAndOffset.x) * 2.0 * 3.141592654;" "amplitudeVarying = phaseAmplitudeAndOffset.y;" @@ -484,18 +500,45 @@ char *OpenGLOutputBuilder::get_y_filter_fragment_shader() return strdup( "#version 150\n" - "in vec2 inputPositionVarying;" - "in vec2 iInputPositionVarying;" "in float phaseVarying;" "in float amplitudeVarying;" + "in vec2 inputPositionsVarying[11];" + "uniform vec4 weights[3];" + "out vec4 fragColour;" "uniform sampler2D texID;" "void main(void)" "{" - "fragColour = texture(texID, inputPositionVarying);" + "vec4 samples[3] = vec4[](" + "vec4(" + "texture(texID, inputPositionsVarying[0]).r," + "texture(texID, inputPositionsVarying[1]).r," + "texture(texID, inputPositionsVarying[2]).r," + "texture(texID, inputPositionsVarying[3]).r" + ")," + "vec4(" + "texture(texID, inputPositionsVarying[4]).r," + "texture(texID, inputPositionsVarying[5]).r," + "texture(texID, inputPositionsVarying[6]).r," + "texture(texID, inputPositionsVarying[7]).r" + ")," + "vec4(" + "texture(texID, inputPositionsVarying[8]).r," + "texture(texID, inputPositionsVarying[9]).r," + "texture(texID, inputPositionsVarying[10]).r," + "0.0" + ")" + ");" + "fragColour = vec4(" + "dot(vec3(" + "dot(samples[0], weights[0])," + "dot(samples[1], weights[1])," + "dot(samples[2], weights[2])" + "), vec3(1.0)));" +// "fragColour = vec4(dot(samples[0], weights[0]));" "}"); } @@ -645,7 +688,15 @@ std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader void OpenGLOutputBuilder::prepare_composite_input_shader() { composite_input_shader_program = prepare_intermediate_shader("inputPosition", "uniform usampler2D texID;", get_input_fragment_shader(), source_data_texture_unit, false); + + float colour_subcarrier_frequency = (float)_colour_cycle_numerator / (float)_colour_cycle_denominator; + SignalProcessing::FIRFilter luminance_filter(11, _cycles_per_line, 0.0f, colour_subcarrier_frequency - 50, SignalProcessing::FIRFilter::DefaultAttenuation); composite_y_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_y_filter_fragment_shader(), composite_texture_unit, true); + composite_y_filter_shader_program->bind(); + GLint weightsUniform = composite_y_filter_shader_program->get_uniform_location("weights"); + float weights[12]; + luminance_filter.get_coefficients(weights); + glUniform4fv(weightsUniform, 3, weights); } void OpenGLOutputBuilder::prepare_source_vertex_array() From 59c07f7dddfae51c4c553e35296bcacec6d41e43 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Apr 2016 23:01:54 -0400 Subject: [PATCH 286/307] This, at least, is an attempt to decode chrominance, as yet unfiltered, and not commuted to RGB (and possibly packed incorrectly). --- Outputs/CRT/Internals/CRTOpenGL.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index ca2c063c0..6b7a58b02 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -506,7 +506,7 @@ char *OpenGLOutputBuilder::get_y_filter_fragment_shader() "in vec2 inputPositionsVarying[11];" "uniform vec4 weights[3];" - "out vec4 fragColour;" + "out vec3 fragColour;" "uniform sampler2D texID;" @@ -532,13 +532,15 @@ char *OpenGLOutputBuilder::get_y_filter_fragment_shader() "0.0" ")" ");" - "fragColour = vec4(" + "vec2 quadrature = vec2(sin(phaseVarying), cos(phaseVarying)) * 0.5;" + "float luminance = " "dot(vec3(" "dot(samples[0], weights[0])," "dot(samples[1], weights[1])," "dot(samples[2], weights[2])" - "), vec3(1.0)));" -// "fragColour = vec4(dot(samples[0], weights[0]));" + "), vec3(1.0));" + "float chrominance = (samples[1].y - luminance) / amplitudeVarying;" + "fragColour = vec3(luminance, vec2(0.5) + chrominance * quadrature );" "}"); } From 23311d633bb4a7cad6cecd0b819c25305d2382bf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Apr 2016 23:15:48 -0400 Subject: [PATCH 287/307] Doubled phosphor life, added comment on required final stage fix for tomorrow. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 6b7a58b02..576dcaf7d 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -593,7 +593,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader(const char *header) "iSrcCoordinatesVarying = srcCoordinates;" "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" - "alpha = exp(-age) + 0.2;" + "alpha = exp(-age*0.5) + 0.2;" "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" @@ -621,6 +621,7 @@ char *OpenGLOutputBuilder::get_rgb_output_fragment_shader() char *OpenGLOutputBuilder::get_composite_output_fragment_shader() { + // "const mat3 yuvToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.39465, 2.03211, 1.13983, -0.58060, 0.0);" return get_output_fragment_shader("", "uniform sampler2D texID;", "texture(texID, srcCoordinatesVarying).rgb"); } From 95639f1189acc18b615623779ed8056949666f58 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 22 Apr 2016 19:15:59 -0400 Subject: [PATCH 288/307] Made attempt to introduce final filtering stage and output. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 150 ++++++++++++++++++---------- Outputs/CRT/Internals/CRTOpenGL.hpp | 4 +- 2 files changed, 99 insertions(+), 55 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 576dcaf7d..18d1828a7 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -255,9 +255,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out }; OpenGL::Shader *shaders[] = { composite_input_shader_program.get(), - composite_y_filter_shader_program.get() + composite_y_filter_shader_program.get(), + composite_chrominance_filter_shader_program.get() }; - for(int stage = 0; stage < 2; stage++) + for(int stage = 0; stage < 3; stage++) { // switch to the initial texture targets[stage]->bind_framebuffer(); @@ -532,7 +533,8 @@ char *OpenGLOutputBuilder::get_y_filter_fragment_shader() "0.0" ")" ");" - "vec2 quadrature = vec2(sin(phaseVarying), cos(phaseVarying)) * 0.5;" + + "vec2 quadrature = vec2(sin(phaseVarying), cos(phaseVarying)) * vec2(0.5);" "float luminance = " "dot(vec3(" "dot(samples[0], weights[0])," @@ -540,10 +542,70 @@ char *OpenGLOutputBuilder::get_y_filter_fragment_shader() "dot(samples[2], weights[2])" "), vec3(1.0));" "float chrominance = (samples[1].y - luminance) / amplitudeVarying;" - "fragColour = vec3(luminance, vec2(0.5) + chrominance * quadrature );" + "fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));" "}"); } +char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() +{ + return strdup( + "#version 150\n" + + "in float phaseVarying;" + "in float amplitudeVarying;" + + "in vec2 inputPositionsVarying[11];" + "uniform vec4 weights[3];" + + "out vec3 fragColour;" + + "uniform sampler2D texID;" + + "void main(void)" + "{" +// "fragColour = texture(texID, inputPositionsVarying[5]).rgb;" + "vec3 centreSample = texture(texID, inputPositionsVarying[5]).rgb;" + "vec2 samples[] = vec2[](" + "texture(texID, inputPositionsVarying[0]).gb," + "texture(texID, inputPositionsVarying[1]).gb," + "texture(texID, inputPositionsVarying[2]).gb," + "texture(texID, inputPositionsVarying[3]).gb," + "texture(texID, inputPositionsVarying[4]).gb," + "centreSample.gb," + "texture(texID, inputPositionsVarying[6]).gb," + "texture(texID, inputPositionsVarying[7]).gb," + "texture(texID, inputPositionsVarying[8]).gb," + "texture(texID, inputPositionsVarying[9]).gb," + "texture(texID, inputPositionsVarying[10]).gb" + ");" + + "vec4 channel1[3] = vec4[](" + "vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r)," + "vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r)," + "vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)" + ");" + "vec4 channel2[3] = vec4[](" + "vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g)," + "vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g)," + "vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)" + ");" + + "fragColour = vec3(centreSample.r," + "dot(vec3(" + "dot(channel1[0], weights[0])," + "dot(channel1[1], weights[1])," + "dot(channel1[2], weights[2])" + "), vec3(1.0, 1.0, 1.0))," + "dot(vec3(" + "dot(channel1[0], weights[0])," + "dot(channel1[1], weights[1])," + "dot(channel1[2], weights[2])" + "), vec3(1.0, 1.0, 1.0))" + ");" + "}"); +} + + #pragma mark - Intermediate vertex shaders (i.e. from intermediate line layout to intermediate line layout) #pragma mark - Output vertex shader @@ -593,7 +655,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader(const char *header) "iSrcCoordinatesVarying = srcCoordinates;" "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" - "alpha = exp(-age*0.5) + 0.2;" + "alpha = 15.0*exp(-age*3.0);" //+ 0.2 "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" @@ -616,13 +678,20 @@ char *OpenGLOutputBuilder::get_composite_output_vertex_shader() char *OpenGLOutputBuilder::get_rgb_output_fragment_shader() { - return get_output_fragment_shader(_rgb_shader, "uniform usampler2D texID;", "rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying)"); + return get_output_fragment_shader(_rgb_shader, "uniform usampler2D texID;", + "vec3 colour = rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying);"); } char *OpenGLOutputBuilder::get_composite_output_fragment_shader() { - // "const mat3 yuvToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.39465, 2.03211, 1.13983, -0.58060, 0.0);" - return get_output_fragment_shader("", "uniform sampler2D texID;", "texture(texID, srcCoordinatesVarying).rgb"); + return get_output_fragment_shader("", + "uniform sampler2D texID;" + "const mat3 yuvToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.39465, 2.03211, 1.13983, -0.58060, 0.0);", + + "vec3 srcColour = texture(texID, srcCoordinatesVarying).rgb;" + "vec3 yuvColour = vec3(srcColour.r, (srcColour.gb - vec2(0.5, 0.5)) * vec2(2.0, 2.0));" + "vec3 colour = yuvToRGB * yuvColour; " + ); } char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_function, const char *header, const char *fragColour_function) @@ -644,7 +713,8 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct "%s\n" "void main(void)" "{" - "fragColour = vec4(%s, clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" + "\n%s\n" + "fragColour = vec4(colour, clamp(alpha, 0.0, 1.0)*sin(lateralVarying));" "}", header, sampling_function, fragColour_function); @@ -655,6 +725,9 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader(const char *input_position, const char *header, char *fragment_shader, GLenum texture_unit, bool extends) { + // TODO: at the minute, allowing extensions seems to throw the colour phase out of whack between encoding and decoding. Figure out why. + extends = false; + std::unique_ptr shader; char *vertex_shader = get_input_vertex_shader(input_position, header); if(vertex_shader && fragment_shader) @@ -693,13 +766,22 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() composite_input_shader_program = prepare_intermediate_shader("inputPosition", "uniform usampler2D texID;", get_input_fragment_shader(), source_data_texture_unit, false); float colour_subcarrier_frequency = (float)_colour_cycle_numerator / (float)_colour_cycle_denominator; - SignalProcessing::FIRFilter luminance_filter(11, _cycles_per_line, 0.0f, colour_subcarrier_frequency - 50, SignalProcessing::FIRFilter::DefaultAttenuation); - composite_y_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_y_filter_fragment_shader(), composite_texture_unit, true); - composite_y_filter_shader_program->bind(); - GLint weightsUniform = composite_y_filter_shader_program->get_uniform_location("weights"); + GLint weightsUniform; float weights[12]; + + composite_y_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_y_filter_fragment_shader(), composite_texture_unit, true); + SignalProcessing::FIRFilter luminance_filter(11, _cycles_per_line, 0.0f, colour_subcarrier_frequency - 50, SignalProcessing::FIRFilter::DefaultAttenuation); + composite_y_filter_shader_program->bind(); + weightsUniform = composite_y_filter_shader_program->get_uniform_location("weights"); luminance_filter.get_coefficients(weights); glUniform4fv(weightsUniform, 3, weights); + + composite_chrominance_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_chrominance_filter_fragment_shader(), filtered_y_texture_unit, false); + SignalProcessing::FIRFilter chrominance_filter(11, _cycles_per_line, 0.0f, colour_subcarrier_frequency * 0.5f, SignalProcessing::FIRFilter::DefaultAttenuation); + composite_chrominance_filter_shader_program->bind(); + weightsUniform = composite_chrominance_filter_shader_program->get_uniform_location("weights"); + chrominance_filter.get_coefficients(weights); + glUniform4fv(weightsUniform, 3, weights); } void OpenGLOutputBuilder::prepare_source_vertex_array() @@ -727,46 +809,6 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() } } - -/*void OpenGLOutputBuilder::prepare_output_shader(char *fragment_shader) -{ - char *vertex_shader = get_output_vertex_shader(); - if(vertex_shader && fragment_shader) - { - _openGL_state->rgb_shader_program = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader)); - - _openGL_state->rgb_shader_program->bind(); - - _openGL_state->windowSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("windowSize"); - _openGL_state->boundsSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsSize"); - _openGL_state->boundsOriginUniform = _openGL_state->rgb_shader_program->get_uniform_location("boundsOrigin"); - _openGL_state->timestampBaseUniform = _openGL_state->rgb_shader_program->get_uniform_location("timestampBase"); - - GLint texIDUniform = _openGL_state->rgb_shader_program->get_uniform_location("texID"); - GLint shadowMaskTexIDUniform = _openGL_state->rgb_shader_program->get_uniform_location("shadowMaskTexID"); - GLint textureSizeUniform = _openGL_state->rgb_shader_program->get_uniform_location("textureSize"); - GLint ticksPerFrameUniform = _openGL_state->rgb_shader_program->get_uniform_location("ticksPerFrame"); - GLint scanNormalUniform = _openGL_state->rgb_shader_program->get_uniform_location("scanNormal"); - GLint positionConversionUniform = _openGL_state->rgb_shader_program->get_uniform_location("positionConversion"); - - glUniform1i(texIDUniform, first_supplied_buffer_texture_unit); - glUniform1i(shadowMaskTexIDUniform, 1); - glUniform2f(textureSizeUniform, CRTInputBufferBuilderWidth, CRTInputBufferBuilderHeight); - glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); - glUniform2f(positionConversionUniform, _horizontal_flywheel->get_scan_period(), _vertical_flywheel->get_scan_period() / (unsigned int)_vertical_flywheel_output_divider); - - float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); - float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; - float multiplier = (float)_horizontal_flywheel->get_standard_period() / ((float)_height_of_display * (float)_horizontal_flywheel->get_scan_period()); - scan_normal[0] *= multiplier; - scan_normal[1] *= multiplier; - glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]); - } - - free(vertex_shader); - free(fragment_shader); -}*/ - std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char *vertex_shader, char *fragment_shader, GLint source_texture_unit) { std::unique_ptr shader_program; @@ -819,7 +861,7 @@ void OpenGLOutputBuilder::prepare_rgb_output_shader() void OpenGLOutputBuilder::prepare_composite_output_shader() { - composite_output_shader_program = prepare_output_shader(get_composite_output_vertex_shader(), get_composite_output_fragment_shader(), filtered_y_texture_unit); + composite_output_shader_program = prepare_output_shader(get_composite_output_vertex_shader(), get_composite_output_fragment_shader(), filtered_texture_unit); } void OpenGLOutputBuilder::prepare_output_vertex_array() diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 8f5beb59a..52c0bfa2e 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -75,10 +75,12 @@ class OpenGLOutputBuilder { char *get_input_vertex_shader(const char *input_position, const char *header); char *get_input_fragment_shader(); + char *get_y_filter_fragment_shader(); + char *get_chrominance_filter_fragment_shader(); std::unique_ptr rgb_shader_program; - std::unique_ptr composite_input_shader_program, composite_y_filter_shader_program, composite_output_shader_program; + std::unique_ptr composite_input_shader_program, composite_y_filter_shader_program, composite_chrominance_filter_shader_program, composite_output_shader_program; GLuint output_array_buffer, output_vertex_array; GLuint source_array_buffer, source_vertex_array; From 8ff1f820082bd1c2600dc32567f186780d1e28a3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 22 Apr 2016 19:18:28 -0400 Subject: [PATCH 289/307] Fixed accidental channel duplication. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 18d1828a7..a9ddca095 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -563,7 +563,6 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "void main(void)" "{" -// "fragColour = texture(texID, inputPositionsVarying[5]).rgb;" "vec3 centreSample = texture(texID, inputPositionsVarying[5]).rgb;" "vec2 samples[] = vec2[](" "texture(texID, inputPositionsVarying[0]).gb," @@ -579,12 +578,12 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "texture(texID, inputPositionsVarying[10]).gb" ");" - "vec4 channel1[3] = vec4[](" + "vec4 channel1[] = vec4[](" "vec4(samples[0].r, samples[1].r, samples[2].r, samples[3].r)," "vec4(samples[4].r, samples[5].r, samples[6].r, samples[7].r)," "vec4(samples[8].r, samples[9].r, samples[10].r, 0.0)" ");" - "vec4 channel2[3] = vec4[](" + "vec4 channel2[] = vec4[](" "vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g)," "vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g)," "vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)" @@ -597,9 +596,9 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "dot(channel1[2], weights[2])" "), vec3(1.0, 1.0, 1.0))," "dot(vec3(" - "dot(channel1[0], weights[0])," - "dot(channel1[1], weights[1])," - "dot(channel1[2], weights[2])" + "dot(channel2[0], weights[0])," + "dot(channel2[1], weights[1])," + "dot(channel2[2], weights[2])" "), vec3(1.0, 1.0, 1.0))" ");" "}"); From a4889074b8b22fcbf19dc418736b47dbead91409 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 22 Apr 2016 21:29:27 -0400 Subject: [PATCH 290/307] Moved final colour space conversion out of the innermost loop. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index a9ddca095..ea92c979a 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -258,6 +258,11 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out composite_y_filter_shader_program.get(), composite_chrominance_filter_shader_program.get() }; + float clear_colours[][3] = { + {0.0, 0.0, 0.0}, + {0.0, 0.5, 0.5}, + {0.0, 0.0, 0.0} + }; for(int stage = 0; stage < 3; stage++) { // switch to the initial texture @@ -268,6 +273,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out if(number_of_clearing_zones) { glEnable(GL_SCISSOR_TEST); + glClearColor(clear_colours[stage][0], clear_colours[stage][1], clear_colours[stage][2], 1.0); for(int c = 0; c < number_of_clearing_zones; c++) { glScissor(0, clearing_zones[c*2], IntermediateBufferWidth, clearing_zones[c*2 + 1]); @@ -286,6 +292,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out // switch back to screen output glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); glViewport(0, 0, (GLsizei)output_width, (GLsizei)output_height); + glClearColor(0.0, 0.0, 0.0, 1.0); } // transfer to screen @@ -534,14 +541,16 @@ char *OpenGLOutputBuilder::get_y_filter_fragment_shader() ")" ");" - "vec2 quadrature = vec2(sin(phaseVarying), cos(phaseVarying)) * vec2(0.5);" "float luminance = " "dot(vec3(" "dot(samples[0], weights[0])," "dot(samples[1], weights[1])," "dot(samples[2], weights[2])" "), vec3(1.0));" - "float chrominance = (samples[1].y - luminance) / amplitudeVarying;" + + "float chrominance = 0.5 * (samples[1].y - luminance) / amplitudeVarying;" + "vec2 quadrature = vec2(sin(phaseVarying), cos(phaseVarying));" + "fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));" "}"); } @@ -560,6 +569,7 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "out vec3 fragColour;" "uniform sampler2D texID;" + "const mat3 yuvToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.39465, 2.03211, 1.13983, -0.58060, 0.0);" "void main(void)" "{" @@ -589,7 +599,7 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)" ");" - "fragColour = vec3(centreSample.r," + "vec3 yuvColour = vec3(centreSample.r," "dot(vec3(" "dot(channel1[0], weights[0])," "dot(channel1[1], weights[1])," @@ -601,6 +611,9 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "dot(channel2[2], weights[2])" "), vec3(1.0, 1.0, 1.0))" ");" + + "vec3 yuvColourInRange = (yuvColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);" + "fragColour = yuvToRGB * yuvColourInRange; " "}"); } @@ -684,12 +697,8 @@ char *OpenGLOutputBuilder::get_rgb_output_fragment_shader() char *OpenGLOutputBuilder::get_composite_output_fragment_shader() { return get_output_fragment_shader("", - "uniform sampler2D texID;" - "const mat3 yuvToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.39465, 2.03211, 1.13983, -0.58060, 0.0);", - - "vec3 srcColour = texture(texID, srcCoordinatesVarying).rgb;" - "vec3 yuvColour = vec3(srcColour.r, (srcColour.gb - vec2(0.5, 0.5)) * vec2(2.0, 2.0));" - "vec3 colour = yuvToRGB * yuvColour; " + "uniform sampler2D texID;", + "vec3 colour = texture(texID, srcCoordinatesVarying).rgb;" ); } From c123f3bf743d19f1aaed2ca6c9696472c0d1c8a5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 23 Apr 2016 14:16:49 -0400 Subject: [PATCH 291/307] Fixed run extension, temporarily forced colour amplitude. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 13 +++++-------- SignalProcessing/FIRFilter.cpp | 6 +++--- SignalProcessing/FIRFilter.hpp | 2 +- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index ea92c979a..7edb4b7ca 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -449,8 +449,8 @@ char *OpenGLOutputBuilder::get_input_vertex_shader(const char *input_position, c "inputPositionsVarying[9] = inputPositionVarying + (vec2(4.0, 0.0) / textureSize);" "inputPositionsVarying[10] = inputPositionVarying + (vec2(5.0, 0.0) / textureSize);" - "phaseVarying = (phaseCyclesPerTick * (outputPosition.x - phaseTime) + phaseAmplitudeAndOffset.x) * 2.0 * 3.141592654;" - "amplitudeVarying = phaseAmplitudeAndOffset.y;" + "phaseVarying = (phaseCyclesPerTick * (extendedOutputPosition.x - phaseTime) + phaseAmplitudeAndOffset.x) * 2.0 * 3.141592654;" + "amplitudeVarying = 0.33;" // phaseAmplitudeAndOffset.y "vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(0.5)/textureSize;" "gl_Position = vec4(eyePosition, 0.0, 1.0);" @@ -613,7 +613,7 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() ");" "vec3 yuvColourInRange = (yuvColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);" - "fragColour = yuvToRGB * yuvColourInRange; " + "fragColour = yuvToRGB * yuvColourInRange;" "}"); } @@ -667,7 +667,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader(const char *header) "iSrcCoordinatesVarying = srcCoordinates;" "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" - "alpha = 15.0*exp(-age*3.0);" //+ 0.2 + "alpha = 15.0*exp(-age*3.0);" "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" @@ -733,9 +733,6 @@ char *OpenGLOutputBuilder::get_output_fragment_shader(const char *sampling_funct std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader(const char *input_position, const char *header, char *fragment_shader, GLenum texture_unit, bool extends) { - // TODO: at the minute, allowing extensions seems to throw the colour phase out of whack between encoding and decoding. Figure out why. - extends = false; - std::unique_ptr shader; char *vertex_shader = get_input_vertex_shader(input_position, header); if(vertex_shader && fragment_shader) @@ -778,7 +775,7 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() float weights[12]; composite_y_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_y_filter_fragment_shader(), composite_texture_unit, true); - SignalProcessing::FIRFilter luminance_filter(11, _cycles_per_line, 0.0f, colour_subcarrier_frequency - 50, SignalProcessing::FIRFilter::DefaultAttenuation); + SignalProcessing::FIRFilter luminance_filter(11, _cycles_per_line, 0.0f, colour_subcarrier_frequency, SignalProcessing::FIRFilter::DefaultAttenuation); composite_y_filter_shader_program->bind(); weightsUniform = composite_y_filter_shader_program->get_uniform_location("weights"); luminance_filter.get_coefficients(weights); diff --git a/SignalProcessing/FIRFilter.cpp b/SignalProcessing/FIRFilter.cpp index 7eae4e2b8..66db42376 100644 --- a/SignalProcessing/FIRFilter.cpp +++ b/SignalProcessing/FIRFilter.cpp @@ -115,7 +115,7 @@ void FIRFilter::get_coefficients(float *coefficients) } } -FIRFilter::FIRFilter(unsigned int number_of_taps, unsigned int input_sample_rate, float low_frequency, float high_frequency, float attenuation) +FIRFilter::FIRFilter(unsigned int number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation) { // we must be asked to filter based on an odd number of // taps, and at least three @@ -131,10 +131,10 @@ FIRFilter::FIRFilter(unsigned int number_of_taps, unsigned int input_sample_rate /* calculate idealised filter response */ unsigned int Np = (number_of_taps - 1) / 2; - float twoOverSampleRate = 2.0f / (float)input_sample_rate; + float twoOverSampleRate = 2.0f / input_sample_rate; float *A = new float[Np+1]; - A[0] = 2.0f * (high_frequency - low_frequency) / (float)input_sample_rate; + A[0] = 2.0f * (high_frequency - low_frequency) / input_sample_rate; for(unsigned int i = 1; i <= Np; i++) { float iPi = (float)i * (float)M_PI; diff --git a/SignalProcessing/FIRFilter.hpp b/SignalProcessing/FIRFilter.hpp index 71f6c1b0c..4f3c95588 100644 --- a/SignalProcessing/FIRFilter.hpp +++ b/SignalProcessing/FIRFilter.hpp @@ -44,7 +44,7 @@ class FIRFilter { @param high_frequency The highest frequency of signal to retain in the output. @param attenuation The attenuation of the discarded frequencies. */ - FIRFilter(unsigned int number_of_taps, unsigned int input_sample_rate, float low_frequency, float high_frequency, float attenuation); + FIRFilter(unsigned int number_of_taps, float input_sample_rate, float low_frequency, float high_frequency, float attenuation); ~FIRFilter(); From bdaf4cee433721af8e0722d39a13b3579410dfff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 06:16:41 -0400 Subject: [PATCH 292/307] Switched to an in-framebuffer approach to phosphor decay, which might be acceptable now that every single pixel is being painted, re-emphasises luminance and stretched sampling period for the two FIR filters as well as decreasing the amount of signal that is retained, subjectively to improve output. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 7edb4b7ca..f69ec2d08 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -93,7 +93,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : } _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Create intermediate textures and bind to slots 0, 1 and 2 glActiveTexture(composite_texture_unit); @@ -435,19 +435,19 @@ char *OpenGLOutputBuilder::get_input_vertex_shader(const char *input_position, c "inputPositionVarying = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;" "textureSize = textureSize * vec2(1.0);" - "inputPositionsVarying[0] = inputPositionVarying - (vec2(5.0, 0.0) / textureSize);" - "inputPositionsVarying[1] = inputPositionVarying - (vec2(4.0, 0.0) / textureSize);" - "inputPositionsVarying[2] = inputPositionVarying - (vec2(3.0, 0.0) / textureSize);" - "inputPositionsVarying[3] = inputPositionVarying - (vec2(2.0, 0.0) / textureSize);" - "inputPositionsVarying[4] = inputPositionVarying - (vec2(1.0, 0.0) / textureSize);" + "inputPositionsVarying[0] = inputPositionVarying - (vec2(10.0, 0.0) / textureSize);" + "inputPositionsVarying[1] = inputPositionVarying - (vec2(8.0, 0.0) / textureSize);" + "inputPositionsVarying[2] = inputPositionVarying - (vec2(6.0, 0.0) / textureSize);" + "inputPositionsVarying[3] = inputPositionVarying - (vec2(4.0, 0.0) / textureSize);" + "inputPositionsVarying[4] = inputPositionVarying - (vec2(2.0, 0.0) / textureSize);" "inputPositionsVarying[5] = inputPositionVarying;" - "inputPositionsVarying[6] = inputPositionVarying + (vec2(1.0, 0.0) / textureSize);" - "inputPositionsVarying[7] = inputPositionVarying + (vec2(2.0, 0.0) / textureSize);" - "inputPositionsVarying[8] = inputPositionVarying + (vec2(3.0, 0.0) / textureSize);" - "inputPositionsVarying[9] = inputPositionVarying + (vec2(4.0, 0.0) / textureSize);" - "inputPositionsVarying[10] = inputPositionVarying + (vec2(5.0, 0.0) / textureSize);" + "inputPositionsVarying[6] = inputPositionVarying + (vec2(2.0, 0.0) / textureSize);" + "inputPositionsVarying[7] = inputPositionVarying + (vec2(4.0, 0.0) / textureSize);" + "inputPositionsVarying[8] = inputPositionVarying + (vec2(6.0, 0.0) / textureSize);" + "inputPositionsVarying[9] = inputPositionVarying + (vec2(8.0, 0.0) / textureSize);" + "inputPositionsVarying[10] = inputPositionVarying + (vec2(10.0, 0.0) / textureSize);" "phaseVarying = (phaseCyclesPerTick * (extendedOutputPosition.x - phaseTime) + phaseAmplitudeAndOffset.x) * 2.0 * 3.141592654;" "amplitudeVarying = 0.33;" // phaseAmplitudeAndOffset.y @@ -546,7 +546,7 @@ char *OpenGLOutputBuilder::get_y_filter_fragment_shader() "dot(samples[0], weights[0])," "dot(samples[1], weights[1])," "dot(samples[2], weights[2])" - "), vec3(1.0));" + "), vec3(1.0)) / (1.0 - amplitudeVarying);" "float chrominance = 0.5 * (samples[1].y - luminance) / amplitudeVarying;" "vec2 quadrature = vec2(sin(phaseVarying), cos(phaseVarying));" @@ -575,17 +575,17 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "{" "vec3 centreSample = texture(texID, inputPositionsVarying[5]).rgb;" "vec2 samples[] = vec2[](" - "texture(texID, inputPositionsVarying[0]).gb," - "texture(texID, inputPositionsVarying[1]).gb," - "texture(texID, inputPositionsVarying[2]).gb," - "texture(texID, inputPositionsVarying[3]).gb," - "texture(texID, inputPositionsVarying[4]).gb," - "centreSample.gb," - "texture(texID, inputPositionsVarying[6]).gb," - "texture(texID, inputPositionsVarying[7]).gb," - "texture(texID, inputPositionsVarying[8]).gb," - "texture(texID, inputPositionsVarying[9]).gb," - "texture(texID, inputPositionsVarying[10]).gb" + "texture(texID, inputPositionsVarying[0]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[1]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[2]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[3]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[4]).gb - vec2(0.5)," + "centreSample.gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[6]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[7]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[8]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[9]).gb - vec2(0.5)," + "texture(texID, inputPositionsVarying[10]).gb - vec2(0.5)" ");" "vec4 channel1[] = vec4[](" @@ -604,12 +604,12 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "dot(channel1[0], weights[0])," "dot(channel1[1], weights[1])," "dot(channel1[2], weights[2])" - "), vec3(1.0, 1.0, 1.0))," + "), vec3(1.0)) + 0.5," "dot(vec3(" "dot(channel2[0], weights[0])," "dot(channel2[1], weights[1])," "dot(channel2[2], weights[2])" - "), vec3(1.0, 1.0, 1.0))" + "), vec3(1.0)) + 0.5" ");" "vec3 yuvColourInRange = (yuvColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);" @@ -667,7 +667,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader(const char *header) "iSrcCoordinatesVarying = srcCoordinates;" "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" - "alpha = 15.0*exp(-age*3.0);" + "alpha = 0.75;"//15.0*exp(-age*3.0);" "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" @@ -775,14 +775,14 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() float weights[12]; composite_y_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_y_filter_fragment_shader(), composite_texture_unit, true); - SignalProcessing::FIRFilter luminance_filter(11, _cycles_per_line, 0.0f, colour_subcarrier_frequency, SignalProcessing::FIRFilter::DefaultAttenuation); + SignalProcessing::FIRFilter luminance_filter(11, _cycles_per_line * 0.5f, 0.0f, colour_subcarrier_frequency * 0.5f, SignalProcessing::FIRFilter::DefaultAttenuation); composite_y_filter_shader_program->bind(); weightsUniform = composite_y_filter_shader_program->get_uniform_location("weights"); luminance_filter.get_coefficients(weights); glUniform4fv(weightsUniform, 3, weights); composite_chrominance_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_chrominance_filter_fragment_shader(), filtered_y_texture_unit, false); - SignalProcessing::FIRFilter chrominance_filter(11, _cycles_per_line, 0.0f, colour_subcarrier_frequency * 0.5f, SignalProcessing::FIRFilter::DefaultAttenuation); + SignalProcessing::FIRFilter chrominance_filter(11, _cycles_per_line * 0.5f, 0.0f, colour_subcarrier_frequency * 0.5f, SignalProcessing::FIRFilter::DefaultAttenuation); composite_chrominance_filter_shader_program->bind(); weightsUniform = composite_chrominance_filter_shader_program->get_uniform_location("weights"); chrominance_filter.get_coefficients(weights); From 80a3169674994e17a8d1843934c781c719dab4de Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 06:56:08 -0400 Subject: [PATCH 293/307] Started reviving the Atari 2600 emulation. Put new startup sequence into place. --- Machines/Atari2600/Atari2600.cpp | 25 +++++++++++-------- Machines/Atari2600/Atari2600.hpp | 1 + .../Mac/Clock Signal/Wrappers/CSAtari2600.mm | 18 +++++++++++-- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 1 - Outputs/CRT/CRT.hpp | 6 ++--- Outputs/CRT/Internals/CRTOpenGL.cpp | 3 ++- 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 8b92ad490..9a9463b6f 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -24,20 +24,25 @@ Machine::Machine() : _piaDataValue{0xff, 0xff}, _tiaInputValue{0xff, 0xff} { - _crt = new Outputs::CRT::CRT(228, 1, Outputs::CRT::DisplayType::NTSC60, 2); - _crt->set_composite_sampling_function( - "float composite_sample(vec2 coordinate, float phase)\n" - "{\n" - "vec2 c = texture(texID, coordinate).rg;" - "float y = 0.1 + c.x * 0.91071428571429;\n" - "float aOffset = 6.283185308 * (c.y - 3.0 / 16.0) * 1.14285714285714;\n" - "return y + step(0.03125, c.y) * 0.1 * cos((coordinate.x * 2.0 * 3.141592654) - aOffset);\n" - "}"); - _crt->set_output_device(Outputs::CRT::Television); memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); } +void Machine::setup_output(float aspect_ratio) +{ + _crt = new Outputs::CRT::CRT(228, 1, Outputs::CRT::DisplayType::NTSC60, 2); + _crt->set_composite_sampling_function( + "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)\n" + "{\n" + "return 0.9;" +// "vec2 c = vec2(1.0);"//vec2(texture(texID, coordinate).rg) / vec2(255.0);" +// "float y = 0.1 + c.x * 0.91071428571429;\n" +// "float aOffset = 6.283185308 * (c.y - 3.0 / 16.0) * 1.14285714285714;\n" +// "return y + step(0.03125, c.y) * 0.1 * cos((coordinate.x * 2.0 * 3.141592654) - aOffset);\n" + "}"); + _crt->set_output_device(Outputs::CRT::Television); +} + Machine::~Machine() { delete[] _rom; diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index c3b5d6209..e0b2653b1 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -30,6 +30,7 @@ class Machine: public CPU6502::Processor { void set_digital_input(Atari2600DigitalInput input, bool state); Outputs::CRT::CRT *get_crt() { return _crt; } + void setup_output(float aspect_ratio); private: uint8_t *_rom, *_romPages[4], _ram[128]; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm index 2fc2ca00a..74cc46b7f 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -43,8 +43,10 @@ [super crt:crt didEndFrame:frame didDetectVSync:didDetectVSync]; }*/ -- (void)doRunForNumberOfCycles:(int)numberOfCycles { - _atari2600.run_for_cycles(numberOfCycles); +- (void)runForNumberOfCycles:(int)numberOfCycles { + @synchronized(self) { + _atari2600.run_for_cycles(numberOfCycles); + } } - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { @@ -52,15 +54,27 @@ } - (void)setROM:(NSData *)rom { + @synchronized(self) { _atari2600.set_rom(rom.length, (const uint8_t *)rom.bytes); + } } - (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput { + @synchronized(self) { _atari2600.set_digital_input(digitalInput, state ? true : false); + } } - (void)setResetLineEnabled:(BOOL)enabled { + @synchronized(self) { _atari2600.set_reset_line(enabled ? true : false); + } +} + +- (void)setupOutputWithAspectRatio:(float)aspectRatio { + @synchronized(self) { + _atari2600.setup_output(aspectRatio); + } } @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 52607f738..fe03ee17a 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -60,7 +60,6 @@ - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { @synchronized(self) { _electron.get_speaker()->set_output_rate(sampleRate, 256); -// _electron.get_speaker()->set_output_quality(47); _electron.get_speaker()->set_delegate(delegate); return YES; } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index 25c2482c9..ed4ca7fbd 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -202,9 +202,9 @@ class CRT { /*! Sets a function that will map from whatever data the machine provided to a composite signal. @param shader A GLSL fragment including a function with the signature - `float composite_sample(vec2 coordinate, float phase)` that evaluates to the composite signal - level as a function of a source buffer sampling location and the provided colour carrier phase. - The shader may assume a uniform array of sampler2Ds named `buffers` provides access to all input data. + `float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)` + that evaluates to the composite signal level as a function of a source buffer, sampling location, colour + carrier phase and amplitude. */ inline void set_composite_sampling_function(const char *shader) { diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index f69ec2d08..a9afeb42e 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -861,7 +861,8 @@ std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char void OpenGLOutputBuilder::prepare_rgb_output_shader() { - rgb_shader_program = prepare_output_shader(get_rgb_output_vertex_shader(), get_rgb_output_fragment_shader(), source_data_texture_unit); + if(_rgb_shader) + rgb_shader_program = prepare_output_shader(get_rgb_output_vertex_shader(), get_rgb_output_fragment_shader(), source_data_texture_unit); } void OpenGLOutputBuilder::prepare_composite_output_shader() From 775fc270e606cc73aff8e39899ff9a6a446ea183 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 07:00:22 -0400 Subject: [PATCH 294/307] Fixed bug whereby first frame drawn has random collection of data. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index a9afeb42e..5b7b07a37 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -78,13 +78,15 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _output_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(nullptr), _source_buffer_data(nullptr), _input_texture_data(nullptr), _output_buffer_data_pointer(0), - _source_buffer_data_pointer(0) + _source_buffer_data_pointer(0), + _drawn_source_buffer_data_pointer(0) { _run_builders = new CRTRunBuilder *[NumberOfFields]; for(int builder = 0; builder < NumberOfFields; builder++) @@ -229,7 +231,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out } // for television, update intermediate buffers and then draw; for a monitor, just draw - if(_output_device == Television) + if(_output_device == Television || !rgb_shader_program) { // decide how much to draw if(_drawn_source_buffer_data_pointer != _source_buffer_data_pointer) From d0f9a6c2f5116d3ec5b9cc9d6b017eadfc94665b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 17:35:25 -0400 Subject: [PATCH 295/307] Added a dump-the-input default RGB output shader. Which at least proves that the Atari 2600 is generally still working. --- Machines/Atari2600/Atari2600.cpp | 2 +- Outputs/CRT/Internals/CRTOpenGL.cpp | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 9a9463b6f..2650539b3 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -40,7 +40,7 @@ void Machine::setup_output(float aspect_ratio) // "float aOffset = 6.283185308 * (c.y - 3.0 / 16.0) * 1.14285714285714;\n" // "return y + step(0.03125, c.y) * 0.1 * cos((coordinate.x * 2.0 * 3.141592654) - aOffset);\n" "}"); - _crt->set_output_device(Outputs::CRT::Television); + _crt->set_output_device(Outputs::CRT::Monitor); } Machine::~Machine() diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 5b7b07a37..b577d0d23 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -692,8 +692,20 @@ char *OpenGLOutputBuilder::get_composite_output_vertex_shader() char *OpenGLOutputBuilder::get_rgb_output_fragment_shader() { - return get_output_fragment_shader(_rgb_shader, "uniform usampler2D texID;", + const char *rgb_shader = _rgb_shader; + if(!_rgb_shader) + { + rgb_shader = + "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" + "{" + "return texture(sampler, coordinate).rgb / vec3(255.0);" + "}"; + } + + char *result = get_output_fragment_shader(rgb_shader, "uniform usampler2D texID;", "vec3 colour = rgb_sample(texID, srcCoordinatesVarying, iSrcCoordinatesVarying);"); + + return result; } char *OpenGLOutputBuilder::get_composite_output_fragment_shader() @@ -863,8 +875,7 @@ std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char void OpenGLOutputBuilder::prepare_rgb_output_shader() { - if(_rgb_shader) - rgb_shader_program = prepare_output_shader(get_rgb_output_vertex_shader(), get_rgb_output_fragment_shader(), source_data_texture_unit); + rgb_shader_program = prepare_output_shader(get_rgb_output_vertex_shader(), get_rgb_output_fragment_shader(), source_data_texture_unit); } void OpenGLOutputBuilder::prepare_composite_output_shader() From 9260d97b034952dd2b3702d1bd315be524f9d6b3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 17:56:36 -0400 Subject: [PATCH 296/307] Oh, of course, it's that the output vertex array doesn't get initialised unless there's an RGB shader program. Silly oversight. Fixed. And switched back to TV output for correct colours. --- Machines/Atari2600/Atari2600.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 2650539b3..803370799 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -34,13 +34,12 @@ void Machine::setup_output(float aspect_ratio) _crt->set_composite_sampling_function( "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)\n" "{\n" - "return 0.9;" -// "vec2 c = vec2(1.0);"//vec2(texture(texID, coordinate).rg) / vec2(255.0);" -// "float y = 0.1 + c.x * 0.91071428571429;\n" -// "float aOffset = 6.283185308 * (c.y - 3.0 / 16.0) * 1.14285714285714;\n" -// "return y + step(0.03125, c.y) * 0.1 * cos((coordinate.x * 2.0 * 3.141592654) - aOffset);\n" + "vec2 c = vec2(texture(texID, coordinate).rg) / vec2(255.0);" + "float y = 0.1 + c.x * 0.91071428571429;\n" + "float aOffset = 6.283185308 * (c.y - 3.0 / 16.0) * 1.14285714285714;\n" + "return y + step(0.03125, c.y) * 0.1 * cos(phase - aOffset);\n" "}"); - _crt->set_output_device(Outputs::CRT::Monitor); + _crt->set_output_device(Outputs::CRT::Television); } Machine::~Machine() From 082003ed0aa635e457b6785d889713ca92248120 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 18:36:22 -0400 Subject: [PATCH 297/307] Reintroduced the CRT delegate and the 2600's automatic region switching. Albeit that the CRT itself doesn't adjust properly to new timings yet. --- .../Mac/Clock Signal/Wrappers/CSAtari2600.mm | 37 ++++++++++--------- Outputs/CRT/CRT.cpp | 20 ++++++---- Outputs/CRT/CRT.hpp | 16 ++++++++ Outputs/CRT/Internals/Flywheel.hpp | 3 +- 4 files changed, 49 insertions(+), 27 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm index 74cc46b7f..2ded5ede0 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -11,37 +11,36 @@ #import "Atari2600.hpp" #import "CSMachine+Subclassing.h" +@interface CSAtari2600 () +- (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs; +@end + +struct CRTDelegate: public Outputs::CRT::Delegate { + __weak CSAtari2600 *atari2600; + void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) { + [atari2600 crt:crt didEndBatchOfFrames:number_of_frames withUnexpectedVerticalSyncs:number_of_unexpected_vertical_syncs]; + } +}; + @implementation CSAtari2600 { Atari2600::Machine _atari2600; + CRTDelegate _crtDelegate; int _frameCount; int _hitCount; BOOL _didDecideRegion; } -/*- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync { +- (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs { if(!_didDecideRegion) { - _frameCount++; - _hitCount += didDetectVSync ? 1 : 0; - - if(_frameCount > 30) + _didDecideRegion = YES; + if(numberOfUnexpectedSyncs >= numberOfFrames >> 1) { - if(_hitCount < _frameCount >> 1) - { - _atari2600.switch_region(); - _didDecideRegion = YES; - } - - if(_hitCount > (_frameCount * 7) >> 3) - { - _didDecideRegion = YES; - } + _atari2600.switch_region(); } } - - [super crt:crt didEndFrame:frame didDetectVSync:didDetectVSync]; -}*/ +} - (void)runForNumberOfCycles:(int)numberOfCycles { @synchronized(self) { @@ -74,6 +73,8 @@ - (void)setupOutputWithAspectRatio:(float)aspectRatio { @synchronized(self) { _atari2600.setup_output(aspectRatio); + _atari2600.get_crt()->set_delegate(&_crtDelegate); + _crtDelegate.atari2600 = self; } } diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 6e6e11c90..290174298 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -66,7 +66,9 @@ CRT::CRT(unsigned int common_output_divisor) : _is_receiving_sync(false), _sync_period(0), _common_output_divisor(common_output_divisor), - _is_writing_composite_run(false) {} + _is_writing_composite_run(false), + _delegate(nullptr), + _frames_since_last_delegate_call(0) {} 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) { @@ -209,11 +211,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi } } -// if(is_output_segment) -// { -// _openGL_output_builder->complete_output_run(6); -// } - // if this is horizontal retrace then advance the output line counter and bookend an output run if(_openGL_output_builder->get_output_device() == Television) { @@ -251,8 +248,15 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi // 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) { - // TODO: how to communicate did_detect_vsync? Bring the delegate back? -// _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync); + if(_delegate) + { + _frames_since_last_delegate_call++; + if(_frames_since_last_delegate_call == 100) + { + _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; + } + } _openGL_output_builder->increment_field(); } diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index ed4ca7fbd..ed0852666 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -18,6 +18,13 @@ namespace Outputs { namespace CRT { +class CRT; + +class Delegate { + public: + virtual void crt_did_end_batch_of_frames(CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) = 0; +}; + class CRT { private: CRT(unsigned int common_output_divisor); @@ -74,6 +81,10 @@ class CRT { // OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here. std::unique_ptr _openGL_output_builder; + // The delegate; + Delegate *_delegate; + unsigned int _frames_since_last_delegate_call; + public: /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. The requested number of buffers, each with the requested number of bytes per pixel, @@ -252,6 +263,11 @@ class CRT { } 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; + } }; } diff --git a/Outputs/CRT/Internals/Flywheel.hpp b/Outputs/CRT/Internals/Flywheel.hpp index c3a00a3aa..e9bd87ed1 100644 --- a/Outputs/CRT/Internals/Flywheel.hpp +++ b/Outputs/CRT/Internals/Flywheel.hpp @@ -36,7 +36,8 @@ struct Flywheel _sync_error_window(standard_period >> 7), _counter(0), _expected_next_sync(standard_period), - _counter_before_retrace(standard_period - retrace_time) {} + _counter_before_retrace(standard_period - retrace_time), + _number_of_surprises(0) {} enum SyncEvent { /// Indicates that no synchronisation events will occur in the queried window. From 929cfc49cb8c191cac48d7611f4f22b4b98c71a8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 18:58:31 -0400 Subject: [PATCH 298/307] Extended window for picking output frequency, attempted to consolidate CRT OpenGL timing uniforms for approprate resetting. --- .../Mac/Clock Signal/Wrappers/CSAtari2600.mm | 11 +- Outputs/CRT/Internals/CRTOpenGL.cpp | 155 +++++++++--------- Outputs/CRT/Internals/CRTOpenGL.hpp | 12 +- 3 files changed, 91 insertions(+), 87 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm index 2ded5ede0..8a48379f3 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -29,15 +29,20 @@ struct CRTDelegate: public Outputs::CRT::Delegate { int _frameCount; int _hitCount; BOOL _didDecideRegion; + int _batchesReceived; } - (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs { if(!_didDecideRegion) { - _didDecideRegion = YES; - if(numberOfUnexpectedSyncs >= numberOfFrames >> 1) + _batchesReceived++; + if(_batchesReceived == 2) { - _atari2600.switch_region(); + _didDecideRegion = YES; + if(numberOfUnexpectedSyncs >= numberOfFrames >> 1) + { + _atari2600.switch_region(); + } } } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index b577d0d23..0514f4448 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -185,6 +185,8 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out prepare_rgb_output_shader(); prepare_output_vertex_array(); + set_timing_uniforms(); + // This should return either an actual framebuffer number, if this is a target with a framebuffer intended for output, // or 0 if no framebuffer is bound, in which case 0 is also what we want to supply to bind the implied framebuffer. So // it works either way. @@ -762,17 +764,11 @@ std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader shader = std::unique_ptr(new OpenGL::Shader(vertex_shader, fragment_shader, bindings)); GLint texIDUniform = shader->get_uniform_location("texID"); - GLint phaseCyclesPerTickUniform = shader->get_uniform_location("phaseCyclesPerTick"); GLint outputTextureSizeUniform = shader->get_uniform_location("outputTextureSize"); - GLint extensionUniform = shader->get_uniform_location("extension"); shader->bind(); glUniform1i(texIDUniform, (GLint)(texture_unit - GL_TEXTURE0)); glUniform2i(outputTextureSizeUniform, IntermediateBufferWidth, IntermediateBufferHeight); - - float phaseCyclesPerTick = (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line); - glUniform1f(phaseCyclesPerTickUniform, phaseCyclesPerTick); - glUniform1f(extensionUniform, extends ? ceilf(1.0f / phaseCyclesPerTick) : 0.0f); } free(vertex_shader); free(fragment_shader); @@ -784,23 +780,10 @@ void OpenGLOutputBuilder::prepare_composite_input_shader() { composite_input_shader_program = prepare_intermediate_shader("inputPosition", "uniform usampler2D texID;", get_input_fragment_shader(), source_data_texture_unit, false); - float colour_subcarrier_frequency = (float)_colour_cycle_numerator / (float)_colour_cycle_denominator; - GLint weightsUniform; - float weights[12]; composite_y_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_y_filter_fragment_shader(), composite_texture_unit, true); - SignalProcessing::FIRFilter luminance_filter(11, _cycles_per_line * 0.5f, 0.0f, colour_subcarrier_frequency * 0.5f, SignalProcessing::FIRFilter::DefaultAttenuation); - composite_y_filter_shader_program->bind(); - weightsUniform = composite_y_filter_shader_program->get_uniform_location("weights"); - luminance_filter.get_coefficients(weights); - glUniform4fv(weightsUniform, 3, weights); - composite_chrominance_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_chrominance_filter_fragment_shader(), filtered_y_texture_unit, false); - SignalProcessing::FIRFilter chrominance_filter(11, _cycles_per_line * 0.5f, 0.0f, colour_subcarrier_frequency * 0.5f, SignalProcessing::FIRFilter::DefaultAttenuation); - composite_chrominance_filter_shader_program->bind(); - weightsUniform = composite_chrominance_filter_shader_program->get_uniform_location("weights"); - chrominance_filter.get_coefficients(weights); - glUniform4fv(weightsUniform, 3, weights); + composite_chrominance_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_chrominance_filter_fragment_shader(), filtered_y_texture_unit, true); } void OpenGLOutputBuilder::prepare_source_vertex_array() @@ -851,20 +834,7 @@ std::unique_ptr OpenGLOutputBuilder::prepare_output_shader(char timestampBaseUniform = shader_program->get_uniform_location("timestampBase"); GLint texIDUniform = shader_program->get_uniform_location("texID"); - GLint ticksPerFrameUniform = shader_program->get_uniform_location("ticksPerFrame"); - GLint scanNormalUniform = shader_program->get_uniform_location("scanNormal"); - GLint positionConversionUniform = shader_program->get_uniform_location("positionConversion"); - glUniform1i(texIDUniform, source_texture_unit - GL_TEXTURE0); - glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); - glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); - - float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); - float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; - float multiplier = (float)_cycles_per_line / ((float)_height_of_display * (float)_horizontal_scan_period); - scan_normal[0] *= multiplier; - scan_normal[1] *= multiplier; - glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]); } free(vertex_shader); @@ -908,7 +878,7 @@ void OpenGLOutputBuilder::prepare_output_vertex_array() } } -#pragma mark - Configuration +#pragma mark - Public Configuration void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) { @@ -925,50 +895,87 @@ void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) } } +void OpenGLOutputBuilder::set_timing(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) +{ + _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; -// const char *const ntscVertexShaderGlobals = -// "out vec2 srcCoordinatesVarying[4];\n" -// "out float phase;\n"; -// -// const char *const ntscVertexShaderBody = -// "phase = srcCoordinates.x * 6.283185308;\n" -// "\n" -// "srcCoordinatesVarying[0] = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);\n" -// "srcCoordinatesVarying[3] = srcCoordinatesVarying[0] + vec2(0.375 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[2] = srcCoordinatesVarying[0] + vec2(0.125 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[1] = srcCoordinatesVarying[0] - vec2(0.125 / textureSize.x, 0.0);\n" -// "srcCoordinatesVarying[0] = srcCoordinatesVarying[0] - vec2(0.325 / textureSize.x, 0.0);\n"; + set_timing_uniforms(); +} - // assumes y = [0, 1], i and q = [-0.5, 0.5]; therefore i components are multiplied by 1.1914 versus standard matrices, q by 1.0452 -// const char *const yiqToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);"; +#pragma mark - Internal Configuration - // assumes y = [0,1], u and v = [-0.5, 0.5]; therefore u components are multiplied by 1.14678899082569, v by 0.8130081300813 -// const char *const yuvToRGB = "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.75213899082569, 2.33040137614679, 0.92669105691057, -0.4720325203252, 0.0);"; +void OpenGLOutputBuilder::set_timing_uniforms() +{ + OpenGL::Shader *intermediate_shaders[] = { + composite_input_shader_program.get(), + composite_y_filter_shader_program.get(), + composite_chrominance_filter_shader_program.get() + }; + bool extends = false; + for(int c = 0; c < 3; c++) + { + if(intermediate_shaders[c]) + { + intermediate_shaders[c]->bind(); + GLint phaseCyclesPerTickUniform = intermediate_shaders[c]->get_uniform_location("phaseCyclesPerTick"); + GLint extensionUniform = intermediate_shaders[c]->get_uniform_location("extension"); -// const char *const ntscFragmentShaderGlobals = -// "in vec2 srcCoordinatesVarying[4];\n" -// "in float phase;\n" -// "\n" -// "// for conversion from i and q are in the range [-0.5, 0.5] (so i needs to be multiplied by 1.1914 and q by 1.0452)\n" -// "const mat3 yiqToRGB = mat3(1.0, 1.0, 1.0, 1.1389784, -0.3240608, -1.3176884, 0.6490692, -0.6762444, 1.7799756);\n"; + float phaseCyclesPerTick = (float)_colour_cycle_numerator / (float)(_colour_cycle_denominator * _cycles_per_line); + glUniform1f(phaseCyclesPerTickUniform, phaseCyclesPerTick); + glUniform1f(extensionUniform, extends ? ceilf(1.0f / phaseCyclesPerTick) : 0.0f); + } + extends = true; + } -// const char *const ntscFragmentShaderBody = -// "vec4 angles = vec4(phase) + vec4(-2.35619449019234, -0.78539816339745, 0.78539816339745, 2.35619449019234);\n" -// "vec4 samples = vec4(" -// " sample(srcCoordinatesVarying[0], angles.x)," -// " sample(srcCoordinatesVarying[1], angles.y)," -// " sample(srcCoordinatesVarying[2], angles.z)," -// " sample(srcCoordinatesVarying[3], angles.w)" -// ");\n" -// "\n" -// "float y = dot(vec4(0.25), samples);\n" -// "samples -= vec4(y);\n" -// "\n" -// "float i = dot(cos(angles), samples);\n" -// "float q = dot(sin(angles), samples);\n" -// "\n" -// "fragColour = 5.0 * texture(shadowMaskTexID, shadowMaskCoordinates) * vec4(yiqToRGB * vec3(y, i, q), 1.0);//sin(lateralVarying));\n"; + OpenGL::Shader *output_shaders[] = { + rgb_shader_program.get(), + composite_output_shader_program.get() + }; + for(int c = 0; c < 2; c++) + { + if(output_shaders[c]) + { + output_shaders[c]->bind(); -// dot(vec3(1.0/6.0, 2.0/3.0, 1.0/6.0), vec3(sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0]), sample(srcCoordinatesVarying[0])));//sin(lateralVarying));\n"; -//} + GLint ticksPerFrameUniform = output_shaders[c]->get_uniform_location("ticksPerFrame"); + GLint scanNormalUniform = output_shaders[c]->get_uniform_location("scanNormal"); + GLint positionConversionUniform = output_shaders[c]->get_uniform_location("positionConversion"); + glUniform1f(ticksPerFrameUniform, (GLfloat)(_cycles_per_line * _height_of_display)); + float scan_angle = atan2f(1.0f / (float)_height_of_display, 1.0f); + float scan_normal[] = { -sinf(scan_angle), cosf(scan_angle)}; + float multiplier = (float)_cycles_per_line / ((float)_height_of_display * (float)_horizontal_scan_period); + scan_normal[0] *= multiplier; + scan_normal[1] *= multiplier; + glUniform2f(scanNormalUniform, scan_normal[0], scan_normal[1]); + + glUniform2f(positionConversionUniform, _horizontal_scan_period, _vertical_scan_period / (unsigned int)_vertical_period_divider); + } + } + + float colour_subcarrier_frequency = (float)_colour_cycle_numerator / (float)_colour_cycle_denominator; + GLint weightsUniform; + float weights[12]; + + if(composite_y_filter_shader_program) + { + SignalProcessing::FIRFilter luminance_filter(11, _cycles_per_line * 0.5f, 0.0f, colour_subcarrier_frequency * 0.5f, SignalProcessing::FIRFilter::DefaultAttenuation); + composite_y_filter_shader_program->bind(); + weightsUniform = composite_y_filter_shader_program->get_uniform_location("weights"); + luminance_filter.get_coefficients(weights); + glUniform4fv(weightsUniform, 3, weights); + } + + if(composite_chrominance_filter_shader_program) + { + SignalProcessing::FIRFilter chrominance_filter(11, _cycles_per_line * 0.5f, 0.0f, colour_subcarrier_frequency * 0.5f, SignalProcessing::FIRFilter::DefaultAttenuation); + composite_chrominance_filter_shader_program->bind(); + weightsUniform = composite_chrominance_filter_shader_program->get_uniform_location("weights"); + chrominance_filter.get_coefficients(weights); + glUniform4fv(weightsUniform, 3, weights); + } +} diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index 52c0bfa2e..dc250b15b 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -97,6 +97,7 @@ class OpenGLOutputBuilder { std::unique_ptr filteredTexture; // receives filtered YIQ or YUV void perform_output_stage(unsigned int output_width, unsigned int output_height, OpenGL::Shader *const shader); + void set_timing_uniforms(); public: OpenGLOutputBuilder(unsigned int buffer_depth); @@ -207,16 +208,7 @@ class OpenGLOutputBuilder { void set_composite_sampling_function(const char *shader); void set_rgb_sampling_function(const char *shader); void set_output_device(OutputDevice output_device); - inline void set_timing(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider) - { - _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; - - // TODO: update related uniforms - } + void set_timing(unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider); uint8_t *_input_texture_data; GLuint _input_texture_array; From 9b2b7a09eb99cbdddbc5d6425b300aed7c8cd00f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 19:16:23 -0400 Subject: [PATCH 299/307] Ensured that colour space changes take effect. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 52 ++++++++++++++++++++++++++--- Outputs/CRT/Internals/CRTOpenGL.hpp | 2 ++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 0514f4448..442ea1611 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -186,6 +186,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out prepare_output_vertex_array(); set_timing_uniforms(); + set_colour_space_uniforms(); // This should return either an actual framebuffer number, if this is a target with a framebuffer intended for output, // or 0 if no framebuffer is bound, in which case 0 is also what we want to supply to bind the implied framebuffer. So @@ -469,7 +470,7 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() { asprintf(&composite_shader, "%s\n" - "const mat3 rgbToYUV = mat3(0.299, -0.14713, 0.615, 0.587, -0.28886, -0.51499, 0.114, 0.436, -0.10001);" + "uniform mat3 rgbToYUV;" "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" "{" "vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));" @@ -573,7 +574,7 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "out vec3 fragColour;" "uniform sampler2D texID;" - "const mat3 yuvToRGB = mat3(1.0, 1.0, 1.0, 0.0, -0.39465, 2.03211, 1.13983, -0.58060, 0.0);" + "uniform mat3 yuvToRGB;" "void main(void)" "{" @@ -779,10 +780,7 @@ std::unique_ptr OpenGLOutputBuilder::prepare_intermediate_shader void OpenGLOutputBuilder::prepare_composite_input_shader() { composite_input_shader_program = prepare_intermediate_shader("inputPosition", "uniform usampler2D texID;", get_input_fragment_shader(), source_data_texture_unit, false); - - composite_y_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_y_filter_fragment_shader(), composite_texture_unit, true); - composite_chrominance_filter_shader_program = prepare_intermediate_shader("outputPosition", "uniform sampler2D texID;", get_chrominance_filter_fragment_shader(), filtered_y_texture_unit, true); } @@ -908,6 +906,50 @@ void OpenGLOutputBuilder::set_timing(unsigned int cycles_per_line, unsigned int #pragma mark - Internal Configuration +void OpenGLOutputBuilder::set_colour_space_uniforms() +{ + GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f}; + GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f}; + + GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, -0.312f}; + GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f}; + + GLfloat *fromRGB, *toRGB; + + switch(_colour_space) + { + case ColourSpace::YIQ: + fromRGB = rgbToYIQ; + toRGB = yiqToRGB; + break; + + case ColourSpace::YUV: + fromRGB = rgbToYUV; + toRGB = yuvToRGB; + break; + } + + if(composite_input_shader_program) + { + composite_input_shader_program->bind(); + GLint rgbToYUVUniform = composite_input_shader_program->get_uniform_location("rgbToYUV"); + if(rgbToYUVUniform >= 0) + { + glUniformMatrix3fv(rgbToYUVUniform, 1, GL_FALSE, fromRGB); + } + } + + if(composite_chrominance_filter_shader_program) + { + composite_chrominance_filter_shader_program->bind(); + GLint yuvToRGBUniform = composite_chrominance_filter_shader_program->get_uniform_location("yuvToRGB"); + if(yuvToRGBUniform >= 0) + { + glUniformMatrix3fv(yuvToRGBUniform, 1, GL_FALSE, toRGB); + } + } +} + void OpenGLOutputBuilder::set_timing_uniforms() { OpenGL::Shader *intermediate_shaders[] = { diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index dc250b15b..fc5144770 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -98,6 +98,7 @@ class OpenGLOutputBuilder { void perform_output_stage(unsigned int output_width, unsigned int output_height, OpenGL::Shader *const shader); void set_timing_uniforms(); + void set_colour_space_uniforms(); public: OpenGLOutputBuilder(unsigned int buffer_depth); @@ -108,6 +109,7 @@ class OpenGLOutputBuilder { _colour_space = colour_space; _colour_cycle_numerator = colour_cycle_numerator; _colour_cycle_denominator = colour_cycle_denominator; + set_colour_space_uniforms(); } inline void set_visible_area(Rect visible_area) From db908a75473cb0bccecc4bcae60b18f3753ba082 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 19:23:30 -0400 Subject: [PATCH 300/307] Adjusted Atari to using its non-standard line lengths. --- Machines/Atari2600/Atari2600.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 803370799..aaf474140 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -30,7 +30,7 @@ Machine::Machine() : void Machine::setup_output(float aspect_ratio) { - _crt = new Outputs::CRT::CRT(228, 1, Outputs::CRT::DisplayType::NTSC60, 2); + _crt = new Outputs::CRT::CRT(228, 1, 263, Outputs::CRT::ColourSpace::YIQ, 228, 1, 2); _crt->set_composite_sampling_function( "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)\n" "{\n" @@ -50,7 +50,7 @@ Machine::~Machine() void Machine::switch_region() { - _crt->set_new_display_type(228, Outputs::CRT::DisplayType::PAL50); + _crt->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1); } void Machine::get_output_pixel(uint8_t *pixel, int offset) From d393776677f09603e7b29a967e71e3bfd6fee900 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 19:29:30 -0400 Subject: [PATCH 301/307] Fixed an incorrect sign. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 442ea1611..f9f021f94 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -911,7 +911,7 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f}; GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f}; - GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, -0.312f}; + GLfloat rgbToYIQ[] = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f}; GLfloat yiqToRGB[] = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f}; GLfloat *fromRGB, *toRGB; From acab22d95a62b61e97820c1064173586a4815270 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 20:27:03 -0400 Subject: [PATCH 302/307] Removed any improper talk of YUV. Switched to (cos, -sin) formulation of the quadrature vector, which I now believe to be correct. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index f9f021f94..779db5608 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -470,13 +470,13 @@ char *OpenGLOutputBuilder::get_input_fragment_shader() { asprintf(&composite_shader, "%s\n" - "uniform mat3 rgbToYUV;" + "uniform mat3 rgbToLumaChroma;" "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" "{" "vec3 rgbColour = clamp(rgb_sample(texID, coordinate, iCoordinate), vec3(0.0), vec3(1.0));" - "vec3 yuvColour = rgbToYUV * rgbColour;" - "vec2 quadrature = vec2(sin(phase), cos(phase)) * amplitude;" - "return dot(yuvColour, vec3(1.0 - amplitude, quadrature));" + "vec3 lumaChromaColour = rgbToLumaChroma * rgbColour;" + "vec2 quadrature = vec2(cos(phase), -sin(phase)) * amplitude;" + "return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));" "}", _rgb_shader); // TODO: use YIQ if this is NTSC @@ -554,7 +554,7 @@ char *OpenGLOutputBuilder::get_y_filter_fragment_shader() "), vec3(1.0)) / (1.0 - amplitudeVarying);" "float chrominance = 0.5 * (samples[1].y - luminance) / amplitudeVarying;" - "vec2 quadrature = vec2(sin(phaseVarying), cos(phaseVarying));" + "vec2 quadrature = vec2(cos(phaseVarying), -sin(phaseVarying));" "fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));" "}"); @@ -574,7 +574,7 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "out vec3 fragColour;" "uniform sampler2D texID;" - "uniform mat3 yuvToRGB;" + "uniform mat3 lumaChromaToRGB;" "void main(void)" "{" @@ -604,7 +604,7 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)" ");" - "vec3 yuvColour = vec3(centreSample.r," + "vec3 lumaChromaColour = vec3(centreSample.r," "dot(vec3(" "dot(channel1[0], weights[0])," "dot(channel1[1], weights[1])," @@ -617,8 +617,8 @@ char *OpenGLOutputBuilder::get_chrominance_filter_fragment_shader() "), vec3(1.0)) + 0.5" ");" - "vec3 yuvColourInRange = (yuvColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);" - "fragColour = yuvToRGB * yuvColourInRange;" + "vec3 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);" + "fragColour = lumaChromaToRGB * lumaChromaColourInRange;" "}"); } @@ -932,20 +932,20 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() if(composite_input_shader_program) { composite_input_shader_program->bind(); - GLint rgbToYUVUniform = composite_input_shader_program->get_uniform_location("rgbToYUV"); - if(rgbToYUVUniform >= 0) + GLint uniform = composite_input_shader_program->get_uniform_location("rgbToLumaChroma"); + if(uniform >= 0) { - glUniformMatrix3fv(rgbToYUVUniform, 1, GL_FALSE, fromRGB); + glUniformMatrix3fv(uniform, 1, GL_FALSE, fromRGB); } } if(composite_chrominance_filter_shader_program) { composite_chrominance_filter_shader_program->bind(); - GLint yuvToRGBUniform = composite_chrominance_filter_shader_program->get_uniform_location("yuvToRGB"); - if(yuvToRGBUniform >= 0) + GLint uniform = composite_chrominance_filter_shader_program->get_uniform_location("lumaChromaToRGB"); + if(uniform >= 0) { - glUniformMatrix3fv(yuvToRGBUniform, 1, GL_FALSE, toRGB); + glUniformMatrix3fv(uniform, 1, GL_FALSE, toRGB); } } } From d221c712b0dea6cfc608135818578c4e147a1777 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 20:34:25 -0400 Subject: [PATCH 303/307] Ensured GL context is active when destructing CRTOpenGL. --- Machines/Atari2600/Atari2600.cpp | 8 +++++++- Machines/Atari2600/Atari2600.hpp | 1 + Machines/Electron/Electron.cpp | 5 +++++ Machines/Electron/Electron.hpp | 1 + OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm | 6 ++++++ OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm | 6 ++++++ .../Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h | 1 + OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h | 1 + OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm | 9 +++++++++ Outputs/CRT/Internals/Shader.cpp | 3 +-- 10 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index aaf474140..461f3ac02 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -42,10 +42,16 @@ void Machine::setup_output(float aspect_ratio) _crt->set_output_device(Outputs::CRT::Television); } +void Machine::close_output() +{ + delete _crt; + _crt = nullptr; +} + Machine::~Machine() { delete[] _rom; - delete _crt; + close_output(); } void Machine::switch_region() diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index e0b2653b1..a7dcfc20e 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -31,6 +31,7 @@ class Machine: public CPU6502::Processor { Outputs::CRT::CRT *get_crt() { return _crt; } void setup_output(float aspect_ratio); + void close_output(); private: uint8_t *_rom, *_romPages[4], _ram[128]; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 8c29dc591..43d48cdf5 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -77,6 +77,11 @@ void Machine::setup_output(float aspect_ratio) _speaker.set_input_rate(2000000 / clock_rate_audio_divider); } +void Machine::close_output() +{ + _crt = nullptr; +} + unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { unsigned int cycles = 1; diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 17b59c1bd..791256d9a 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -157,6 +157,7 @@ class Machine: public CPU6502::Processor, Tape::Delegate { void clear_all_keys(); void setup_output(float aspect_ratio); + void close_output(); Outputs::CRT::CRT *get_crt() { return _crt.get(); } Outputs::Speaker *get_speaker() { return &_speaker; } diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm index 8a48379f3..de8a51f84 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -83,4 +83,10 @@ struct CRTDelegate: public Outputs::CRT::Delegate { } } +- (void)closeOutput { + @synchronized(self) { + _atari2600.close_output(); + } +} + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index fe03ee17a..aee6483f5 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -171,4 +171,10 @@ } } +- (void)closeOutput { + @synchronized(self) { + _electron.close_output(); + } +} + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index be409e6be..96d090697 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -17,5 +17,6 @@ - (void)performAsync:(dispatch_block_t)action; - (void)performSync:(dispatch_block_t)action; - (void)setupOutputWithAspectRatio:(float)aspectRatio; +- (void)closeOutput; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h index 88bb5b682..89467de11 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -16,5 +16,6 @@ - (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio; @property (nonatomic, weak) AudioQueue *audioQueue; +@property (nonatomic, readonly) CSOpenGLView *view; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index cf19e4b9f..4beb59ad6 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -37,6 +37,12 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { return self; } +- (void)dealloc { + [_view performWithGLContext:^{ + [self closeOutput]; + }]; +} + - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { return NO; } @@ -53,7 +59,10 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { - (void)setupOutputWithAspectRatio:(float)aspectRatio {} +- (void)closeOutput {} + - (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio { + _view = view; [view performWithGLContext:^{ [self setupOutputWithAspectRatio:aspectRatio]; }]; diff --git a/Outputs/CRT/Internals/Shader.cpp b/Outputs/CRT/Internals/Shader.cpp index 48eec0468..6b7179f3d 100644 --- a/Outputs/CRT/Internals/Shader.cpp +++ b/Outputs/CRT/Internals/Shader.cpp @@ -80,8 +80,7 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att Shader::~Shader() { - // TODO: ensure this is destructed within the correct context. -// glDeleteProgram(_shader_program); + glDeleteProgram(_shader_program); } void Shader::bind() From 2bd656676892848cee9ca489f7c99da546da826b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 20:47:24 -0400 Subject: [PATCH 304/307] Oh, of course, I can use GL_CONSTANT_ALPHA so that new colours can be painted at alpha 1.0 while possibly saturating with old colours. --- Outputs/CRT/Internals/CRTOpenGL.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 779db5608..4253df122 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -95,7 +95,8 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : } _buffer_builder = std::unique_ptr(new CRTInputBufferBuilder(buffer_depth)); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_ALPHA); + glBlendColor(1.0f, 1.0f, 1.0f, 0.33f); // Create intermediate textures and bind to slots 0, 1 and 2 glActiveTexture(composite_texture_unit); @@ -672,7 +673,7 @@ char *OpenGLOutputBuilder::get_output_vertex_shader(const char *header) "iSrcCoordinatesVarying = srcCoordinates;" "srcCoordinatesVarying = vec2(srcCoordinates.x / textureSize.x, (srcCoordinates.y + 0.5) / textureSize.y);" "float age = (timestampBase[int(lateralAndTimestampBaseOffset.y)] - timestamp) / ticksPerFrame;" - "alpha = 0.75;"//15.0*exp(-age*3.0);" + "alpha = 1.0;"//15.0*exp(-age*3.0);" "vec2 floatingPosition = (position / positionConversion) + lateralAndTimestampBaseOffset.x * scanNormal;" "vec2 mappedPosition = (floatingPosition - boundsOrigin) / boundsSize;" From 884755c560141a72bc3a4fab3bf1bedf0076d28b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 20:56:21 -0400 Subject: [PATCH 305/307] Ensured the GL context is available when switching Atari 2600 output, and that it locks the output. --- OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm | 4 +++- Outputs/CRT/Internals/CRTOpenGL.cpp | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm index de8a51f84..4dab3724c 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -41,7 +41,9 @@ struct CRTDelegate: public Outputs::CRT::Delegate { _didDecideRegion = YES; if(numberOfUnexpectedSyncs >= numberOfFrames >> 1) { - _atari2600.switch_region(); + [self.view performWithGLContext:^{ + _atari2600.switch_region(); + }]; } } } diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 4253df122..5ebbc25d5 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -909,6 +909,7 @@ void OpenGLOutputBuilder::set_timing(unsigned int cycles_per_line, unsigned int void OpenGLOutputBuilder::set_colour_space_uniforms() { + _output_mutex->lock(); GLfloat rgbToYUV[] = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f}; GLfloat yuvToRGB[] = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f}; @@ -949,10 +950,12 @@ void OpenGLOutputBuilder::set_colour_space_uniforms() glUniformMatrix3fv(uniform, 1, GL_FALSE, toRGB); } } + _output_mutex->unlock(); } void OpenGLOutputBuilder::set_timing_uniforms() { + _output_mutex->lock(); OpenGL::Shader *intermediate_shaders[] = { composite_input_shader_program.get(), composite_y_filter_shader_program.get(), @@ -1021,4 +1024,5 @@ void OpenGLOutputBuilder::set_timing_uniforms() chrominance_filter.get_coefficients(weights); glUniform4fv(weightsUniform, 3, weights); } + _output_mutex->unlock(); } From 7a8d1008615871b3b3ab2accdf832b8b64150421 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 22:02:00 -0400 Subject: [PATCH 306/307] Found a new working formula for NTSC Atari 2600 colours. --- Machines/Atari2600/Atari2600.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 461f3ac02..f7dfe2dff 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -35,9 +35,9 @@ void Machine::setup_output(float aspect_ratio) "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)\n" "{\n" "vec2 c = vec2(texture(texID, coordinate).rg) / vec2(255.0);" - "float y = 0.1 + c.x * 0.91071428571429;\n" - "float aOffset = 6.283185308 * (c.y - 3.0 / 16.0) * 1.14285714285714;\n" - "return y + step(0.03125, c.y) * 0.1 * cos(phase - aOffset);\n" + "float y = 0.1 + c.x * 0.91071428571429;" + "float aOffset = 6.283185308 * (2.0/16.0 - c.y);" // - 3.0 / 16.0 + "return y + step(0.03125, c.y) * 0.1 * cos(phase - aOffset);" "}"); _crt->set_output_device(Outputs::CRT::Television); } From 9b64f64db72dbaf4ea64950e20a5afc3404af278 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 24 Apr 2016 22:32:24 -0400 Subject: [PATCH 307/307] Attempted to normalise some style decisions.` --- Machines/Atari2600/Atari2600.cpp | 68 ++++++++----------- Machines/Electron/Electron.cpp | 2 +- .../Mac/Clock Signal/Wrappers/CSMachine.mm | 2 +- .../Mac/Clock SignalTests/TestMachine.mm | 2 +- Outputs/CRT/CRT.cpp | 2 +- Outputs/CRT/Internals/Shader.cpp | 2 +- Processors/6502/CPU6502.hpp | 25 +++---- 7 files changed, 45 insertions(+), 58 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index f7dfe2dff..26a840246 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -37,7 +37,7 @@ void Machine::setup_output(float aspect_ratio) "vec2 c = vec2(texture(texID, coordinate).rg) / vec2(255.0);" "float y = 0.1 + c.x * 0.91071428571429;" "float aOffset = 6.283185308 * (2.0/16.0 - c.y);" // - 3.0 / 16.0 - "return y + step(0.03125, c.y) * 0.1 * cos(phase - aOffset);" + "return y + step(0.03125, c.y) * amplitude * cos(phase - aOffset);" "}"); _crt->set_output_device(Outputs::CRT::Television); } @@ -70,8 +70,7 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset) for(int c = 0; c < 2; c++) { const uint8_t repeatMask = _playerAndMissileSize[c]&7; - if(_playerGraphics[c]) - { + if(_playerGraphics[c]) { // figure out player colour int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; @@ -80,9 +79,9 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset) { case 0: break; default: - if (repeatMask&4 && relativeTimer >= 64) relativeTimer -= 64; - else if (repeatMask&2 && relativeTimer >= 32) relativeTimer -= 32; - else if (repeatMask&1 && relativeTimer >= 16) relativeTimer -= 16; + if(repeatMask&4 && relativeTimer >= 64) relativeTimer -= 64; + else if(repeatMask&2 && relativeTimer >= 32) relativeTimer -= 32; + else if(repeatMask&1 && relativeTimer >= 16) relativeTimer -= 16; break; case 5: relativeTimer >>= 1; @@ -97,16 +96,15 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset) } // figure out missile colour - if((_missileGraphicsEnable[c]&2) && !(_missileGraphicsReset[c]&2)) - { + if((_missileGraphicsEnable[c]&2) && !(_missileGraphicsReset[c]&2)) { int missileIndex = _objectCounter[2+c] - 4; switch (repeatMask) { case 0: break; default: - if (repeatMask&4 && missileIndex >= 64) missileIndex -= 64; - else if (repeatMask&2 && missileIndex >= 32) missileIndex -= 32; - else if (repeatMask&1 && missileIndex >= 16) missileIndex -= 16; + if(repeatMask&4 && missileIndex >= 64) missileIndex -= 64; + else if(repeatMask&2 && missileIndex >= 32) missileIndex -= 32; + else if(repeatMask&1 && missileIndex >= 16) missileIndex -= 16; break; case 5: missileIndex >>= 1; @@ -122,16 +120,14 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset) // get the ball proposed colour uint8_t ballPixel = 0; - if(_ballGraphicsEnable&2) - { + if(_ballGraphicsEnable&2) { int ballIndex = _objectCounter[4] - 4; int ballSize = 1 << ((_playfieldControl >> 4)&3); ballPixel = (ballIndex >= 0 && ballIndex < ballSize) ? 1 : 0; } // accumulate collisions - if(playerPixels[0] | playerPixels[1]) - { + if(playerPixels[0] | playerPixels[1]) { _collisions[0] |= ((missilePixels[0] & playerPixels[1]) << 7) | ((missilePixels[0] & playerPixels[0]) << 6); _collisions[1] |= ((missilePixels[1] & playerPixels[0]) << 7) | ((missilePixels[1] & playerPixels[1]) << 6); @@ -141,8 +137,7 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset) _collisions[7] |= ((playerPixels[0] & playerPixels[1]) << 7); } - if(playfieldPixel | ballPixel) - { + if(playfieldPixel | ballPixel) { _collisions[4] |= ((playfieldPixel & missilePixels[0]) << 7) | ((ballPixel & missilePixels[0]) << 6); _collisions[5] |= ((playfieldPixel & missilePixels[1]) << 7) | ((ballPixel & missilePixels[1]) << 6); @@ -157,8 +152,8 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset) uint8_t outputColour = playfieldPixel ? playfieldColour : _backgroundColour; if(!(_playfieldControl&0x04) || !playfieldPixel) { - if (playerPixels[1] || missilePixels[1]) outputColour = _playerColour[1]; - if (playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; + if(playerPixels[1] || missilePixels[1]) outputColour = _playerColour[1]; + if(playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; } // map that colour to separate Y and phase components @@ -180,13 +175,13 @@ void Machine::output_pixels(unsigned int count) OutputState state; // update hmove - if (!(_horizontalTimer&3)) { + if(!(_horizontalTimer&3)) { if(_hMoveFlags) { const uint8_t counterValue = _hMoveCounter ^ 0x7; for(int c = 0; c < 5; c++) { - if (counterValue == (_objectMotion[c] >> 4)) _hMoveFlags &= ~(1 << c); - if (_hMoveFlags&(1 << c)) increment_object_counter(c); + if(counterValue == (_objectMotion[c] >> 4)) _hMoveFlags &= ~(1 << c); + if(_hMoveFlags&(1 << c)) increment_object_counter(c); } } @@ -206,15 +201,15 @@ void Machine::output_pixels(unsigned int count) // it'll be about 43 cycles from start of hsync to start of visible frame, so... // guesses, until I can find information: 26 cycles blank, 16 sync, 40 blank, 160 pixels - if (_horizontalTimer < (_vBlankExtend ? 152 : 160)) { + if(_horizontalTimer < (_vBlankExtend ? 152 : 160)) { if(_vBlankEnabled) { state = OutputState::Blank; } else { state = OutputState::Pixel; } } - else if (_horizontalTimer < end_of_sync) state = OutputState::Blank; - else if (_horizontalTimer < start_of_sync) state = OutputState::Sync; + else if(_horizontalTimer < end_of_sync) state = OutputState::Blank; + else if(_horizontalTimer < start_of_sync) state = OutputState::Sync; else state = OutputState::Blank; // logic: if vsync is enabled, output the opposite of the automatic hsync output @@ -223,10 +218,8 @@ void Machine::output_pixels(unsigned int count) } _lastOutputStateDuration++; - if(state != _lastOutputState) - { - switch(_lastOutputState) - { + if(state != _lastOutputState) { + switch(_lastOutputState) { case OutputState::Blank: _crt->output_blank(_lastOutputStateDuration); break; case OutputState::Sync: _crt->output_sync(_lastOutputStateDuration); break; case OutputState::Pixel: _crt->output_data(_lastOutputStateDuration, 1); break; @@ -234,16 +227,14 @@ void Machine::output_pixels(unsigned int count) _lastOutputStateDuration = 0; _lastOutputState = state; - if(state == OutputState::Pixel) - { + if(state == OutputState::Pixel) { _outputBuffer = _crt->allocate_write_area(160); } else { _outputBuffer = nullptr; } } - if(_horizontalTimer < (_vBlankExtend ? 152 : 160)) - { + if(_horizontalTimer < (_vBlankExtend ? 152 : 160)) { if(_outputBuffer) get_output_pixel(&_outputBuffer[_lastOutputStateDuration << 1], 159 - _horizontalTimer); @@ -318,13 +309,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } // check for a ROM read - if ((address&0x1000) && isReadOperation(operation)) { + if((address&0x1000) && isReadOperation(operation)) { returnValue &= _romPages[(address >> 10)&3][address&1023]; } // check for a RAM access - if ((address&0x1280) == 0x80) { - + if((address&0x1280) == 0x80) { if(isReadOperation(operation)) { returnValue &= _ram[address&0x7f]; } else { @@ -333,7 +323,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } // check for a TIA access - if (!(address&0x1080)) { + if(!(address&0x1080)) { if(isReadOperation(operation)) { const uint16_t decodedAddress = address & 0xf; switch(decodedAddress) { @@ -478,7 +468,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x28: case 0x29: - if (!(*value&0x02) && _missileGraphicsReset[decodedAddress - 0x28]&0x02) + if(!(*value&0x02) && _missileGraphicsReset[decodedAddress - 0x28]&0x02) _objectCounter[decodedAddress - 0x26] = _objectCounter[decodedAddress - 0x28]; // TODO: +3 for normal, +6 for double, +10 for quad _missileGraphicsReset[decodedAddress - 0x28] = *value; break; @@ -505,7 +495,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } // check for a PIA access - if ((address&0x1280) == 0x280) { + if((address&0x1280) == 0x280) { if(isReadOperation(operation)) { const uint8_t decodedAddress = address & 0xf; switch(address & 0xf) { diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 43d48cdf5..e2453dfd2 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -399,7 +399,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // } // const int end_of_field = -// if (_frameCycles < (256 + first_graphics_line) << 7)) +// if(_frameCycles < (256 + first_graphics_line) << 7)) const unsigned int pixel_line_clock = _frameCycles;// + 128 - first_graphics_cycle + 80; const unsigned int line_before_cycle = graphics_line(pixel_line_clock); diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index 4beb59ad6..7c5507218 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -28,7 +28,7 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { - (instancetype)init { self = [super init]; - if (self) { + if(self) { _serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL); _speakerDelegate.machine = self; [self setSpeakerDelegate:&_speakerDelegate sampleRate:44100]; diff --git a/OSBindings/Mac/Clock SignalTests/TestMachine.mm b/OSBindings/Mac/Clock SignalTests/TestMachine.mm index 0995be556..b8e1ba310 100644 --- a/OSBindings/Mac/Clock SignalTests/TestMachine.mm +++ b/OSBindings/Mac/Clock SignalTests/TestMachine.mm @@ -83,7 +83,7 @@ class MachineJamHandler: public CPU6502::AllRAMProcessor::JamHandler { - (instancetype)init { self = [super init]; - if (self) { + if(self) { _cppJamHandler = new MachineJamHandler(self); _processor.set_jam_handler(_cppJamHandler); } diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 290174298..6b53f3e16 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -178,7 +178,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi _openGL_output_builder->add_to_field_time(next_run_length); // either charge or deplete the vertical retrace capacitor (making sure it stops at 0) - if (vsync_charging) + if(vsync_charging) _sync_capacitor_charge_level += next_run_length; else _sync_capacitor_charge_level = std::max(_sync_capacitor_charge_level - (int)next_run_length, 0); diff --git a/Outputs/CRT/Internals/Shader.cpp b/Outputs/CRT/Internals/Shader.cpp index 6b7179f3d..8d8a01677 100644 --- a/Outputs/CRT/Internals/Shader.cpp +++ b/Outputs/CRT/Internals/Shader.cpp @@ -67,7 +67,7 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att { GLint logLength; glGetProgramiv(_shader_program, GL_INFO_LOG_LENGTH, &logLength); - if (logLength > 0) { + if(logLength > 0) { GLchar *log = (GLchar *)malloc((size_t)logLength); glGetProgramInfoLog(_shader_program, logLength, &logLength, log); printf("Link log:\n%s\n", log); diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 62bba1ef9..4e9ff2748 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -558,10 +558,9 @@ template class Processor { #define checkSchedule(op) \ if(!_scheduledPrograms[scheduleProgramsReadPointer]) {\ scheduleProgramsReadPointer = _scheduleProgramsWritePointer = scheduleProgramProgramCounter = 0;\ - if(_reset_line_is_enabled)\ + if(_reset_line_is_enabled) {\ schedule_program(get_reset_program());\ - else\ - {\ + } else {\ if(_irq_request_history[0])\ schedule_program(get_irq_program());\ else\ @@ -582,7 +581,7 @@ template class Processor { while (!_ready_is_active && number_of_cycles > 0) { - if (nextBusOperation != BusOperation::None) { + if(nextBusOperation != BusOperation::None) { _irq_request_history[0] = _irq_request_history[1]; _irq_request_history[1] = _irq_line_is_enabled && !_interruptFlag; number_of_cycles -= static_cast(this)->perform_bus_operation(nextBusOperation, busAddress, busValue); @@ -682,7 +681,7 @@ template class Processor { static const MicroOp jam[] = JAM; schedule_program(jam); - if (_jam_handler) { + if(_jam_handler) { _jam_handler->processor_did_jam(this, _pc.full - 1); checkSchedule(_is_jammed = false); } @@ -899,7 +898,7 @@ template class Processor { case CycleAddXToAddressLow: nextAddress.full = _address.full + _x; _address.bytes.low = nextAddress.bytes.low; - if (_address.bytes.high != nextAddress.bytes.high) { + if(_address.bytes.high != nextAddress.bytes.high) { throwaway_read(_address.full); } break; @@ -911,7 +910,7 @@ template class Processor { case CycleAddYToAddressLow: nextAddress.full = _address.full + _y; _address.bytes.low = nextAddress.bytes.low; - if (_address.bytes.high != nextAddress.bytes.high) { + if(_address.bytes.high != nextAddress.bytes.high) { throwaway_read(_address.full); } break; @@ -1020,11 +1019,10 @@ template class Processor { _zeroResult = _negativeResult = _a; _overflowFlag = (_a^(_a << 1))&Flag::Overflow; - if ((unshiftedA&0xf) + (unshiftedA&0x1) > 5) _a = ((_a + 6)&0xf) | (_a & 0xf0); + if((unshiftedA&0xf) + (unshiftedA&0x1) > 5) _a = ((_a + 6)&0xf) | (_a & 0xf0); _carryFlag = ((unshiftedA&0xf0) + (unshiftedA&0x10) > 0x50) ? 1 : 0; - if (_carryFlag) _a += 0x60; - + if(_carryFlag) _a += 0x60; } else { _a &= _operand; _a = (uint8_t)((_a >> 1) | (_carryFlag << 7)); @@ -1043,7 +1041,7 @@ template class Processor { break; } - if (isReadOperation(nextBusOperation) && _ready_line_is_enabled) { + if(isReadOperation(nextBusOperation) && _ready_line_is_enabled) { _ready_is_active = true; } } @@ -1126,10 +1124,9 @@ template class Processor { */ inline void set_ready_line(bool active) { - if(active) + if(active) { _ready_line_is_enabled = true; - else - { + } else { _ready_line_is_enabled = false; _ready_is_active = false; }