1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

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.

This commit is contained in:
Thomas Harte 2016-01-03 20:41:43 -05:00
parent 96503a3ac5
commit aa0714fe27
6 changed files with 211 additions and 79 deletions

View File

@ -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 = "<group>"; };
4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = "<group>"; };
4B2632551B631A510082A461 /* CRTFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CRTFrame.h; path = ../../Outputs/CRTFrame.h; sourceTree = "<group>"; };
4B2E2D911C399B9900138695 /* ElectronDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronDocument.swift; sourceTree = "<group>"; };
4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = "<group>"; };
4B366DFA1B5C165A0026627B /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRT.cpp; path = ../../Outputs/CRT.cpp; sourceTree = "<group>"; };
4B366DFB1B5C165A0026627B /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRT.hpp; path = ../../Outputs/CRT.hpp; sourceTree = "<group>"; };
4B6D7F921B58822000787C9A /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 = "<group>";
};
4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */ = {
isa = PBXVariantGroup;
children = (

View File

@ -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 {

View File

@ -22,6 +22,11 @@
- (void)flagsChanged:(nonnull NSEvent *)newModifiers;
@end
typedef NS_ENUM(NSInteger, CSCathodeRayViewSignalType) {
CSCathodeRayViewSignalTypeNTSC,
CSCathodeRayViewSignalTypeRGB
};
@interface CSCathodeRayView : NSOpenGLView
@property (nonatomic, weak) id <CSCathodeRayViewDelegate> 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

View File

@ -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];
}

View File

@ -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) {}
}

View File

@ -27,6 +27,20 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).Atari2600Document</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>uef</string>
</array>
<key>CFBundleTypeName</key>
<string>Electron/BBC Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).ElectronDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>