1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-30 14:16:04 +00:00

Compare commits

...

50 Commits

Author SHA1 Message Date
Thomas Harte
acadfbabec Merge pull request #97 from TomHarte/Icons
Introduces some file association icons
2017-01-27 21:36:13 -05:00
Thomas Harte
9001cc3fc2 Added a cartridge image. 2017-01-27 21:26:11 -05:00
Thomas Harte
015b2b49f9 Introduced an incomplete set of file association icons. 2017-01-26 22:21:55 -05:00
Thomas Harte
92f928ca42 Merge pull request #96 from TomHarte/PhaseAlignedSampling
Optimises existing composite flow
2017-01-25 21:51:11 -05:00
Thomas Harte
6d087ca054 Restored 2600 audio. 2017-01-25 21:29:19 -05:00
Thomas Harte
c2d7e36c8f Ensured logic for whether composite output is in use is consistent. 2017-01-25 21:25:03 -05:00
Thomas Harte
4d6e78e641 Reinstated temporary Oric-related fix. 2017-01-24 22:16:15 -05:00
Thomas Harte
5761c8267b [Re-]Eliminated connection between colour subcarrier frequency and monitor output mode. 2017-01-24 20:48:54 -05:00
Thomas Harte
a66a8c31b2 Merge branch 'master' into PhaseAlignedSampling 2017-01-24 07:29:18 -05:00
Thomas Harte
19e4ee12e1 Merge branch 'PhaseAlignedSampling' of github.com:TomHarte/CLK into PhaseAlignedSampling 2017-01-24 07:29:14 -05:00
Thomas Harte
4871572a33 Optimised images. 2017-01-23 21:28:13 -05:00
Thomas Harte
2e744a95e4 Merge branch 'master' into PhaseAlignedSampling 2017-01-23 21:11:14 -05:00
Thomas Harte
ff87f1390d Merge pull request #95 from TomHarte/ReadmeImages
Added some example composite images
2017-01-23 20:49:16 -05:00
Thomas Harte
76ca30c26d This version works better. 2017-01-23 20:47:48 -05:00
Thomas Harte
7c2685cb34 Made an attempt at reducing displayed image size. 2017-01-23 20:46:35 -05:00
Thomas Harte
8cf25a2d70 Went tabular. 2017-01-23 20:44:42 -05:00
Thomas Harte
8d69dd30f3 Testing a table. 2017-01-23 20:43:43 -05:00
Thomas Harte
ae8068b86f Added Stormlord images. 2017-01-23 20:38:30 -05:00
Thomas Harte
baeb0ee89f Reduced image sizes. 2017-01-23 20:34:15 -05:00
Thomas Harte
c07993bb0a Added more images. 2017-01-23 20:33:00 -05:00
Thomas Harte
7680cbf9c3 Testing this Markdown implementation for image sizing support. 2017-01-23 20:26:57 -05:00
Thomas Harte
4920fe6701 Added a grab of the Repton title screen. 2017-01-23 20:23:49 -05:00
Thomas Harte
55fe0176bd Added a space. Probably need to hold for a better example though. 2017-01-12 22:12:37 -05:00
Thomas Harte
99fcbb55d1 Attempted to improve layout. 2017-01-12 22:11:25 -05:00
Thomas Harte
6f78ecd12b Added a small pictorial example. Hardly the best, but a step in the right direction. 2017-01-12 22:06:45 -05:00
Thomas Harte
ced644b103 It seems likely that an AY divides its clock by 8, not 16. I had conflated wave frequency and counter clock. 2017-01-11 22:03:01 -05:00
Thomas Harte
be1cb2a551 Fixed NTSC phase. 2017-01-11 21:31:24 -05:00
Thomas Harte
b4159295f6 Switched to using quads for intermediate draws. The specific concern is the flexibility offered in the GL spec as to line drawing algorithms. And even if a driver implements exactly to spec then it should omit the final pixel. 2017-01-11 21:18:41 -05:00
Thomas Harte
d0a93409e6 Made an attempt to simplify in-shader phase calculation, now that output position is a direct multiple of phase. 2017-01-11 08:18:00 -05:00
Thomas Harte
4c3669f210 Reduced precision of input phase, but I'm not necessarily persuaded by it as a move. However it's clear that something is off in that whole area. But if phase is locked by output position, do I need to retain this level of complexity? Also ensured that intermediate buffers prior to the final are sampled using the nearest sampling mode, also to reduce precision errors. 2017-01-10 22:08:07 -05:00
Thomas Harte
eeb646868b Switched off filtering, at least temporarily, to try to ensure that sampling is all where it should be. 2017-01-08 19:53:08 -05:00
Thomas Harte
3d789732a2 Switched back to full buffer clearing. Until I can figure out the source of noise. 2017-01-08 19:50:31 -05:00
Thomas Harte
d2a7d39749 Ensured the output lock isn't held while talking to the delegate. 2017-01-08 19:49:21 -05:00
Thomas Harte
9521718120 Colour phase is multiplied by 255, not 256. 2017-01-08 17:21:26 -05:00
Thomas Harte
28909e33ca Eliminated phaseCyclesPerTick as implied. 2017-01-08 16:48:02 -05:00
Thomas Harte
79632b1d34 Instituted de-escalating phase-related extensions, definitively to kill rounding error edges. 2017-01-08 16:24:22 -05:00
Thomas Harte
cf6d03e35c Merge branch 'master' into PhaseAlignedSampling 2017-01-08 14:49:40 -05:00
Thomas Harte
4a4b31a15c Merge pull request #94 from TomHarte/ElectronDisks
Fixes the Electron's ability automatically to launch a disk
2017-01-08 14:48:58 -05:00
Thomas Harte
f3d9aec8fc Fixed Electron's support for automatically booting floppy disks. 2017-01-08 14:47:41 -05:00
Thomas Harte
7ad64ff16b Made further efforts to support throughput via memory barrier. 2017-01-08 14:47:16 -05:00
Thomas Harte
6153ada33b Fixed Electron's support for automatically booting floppy disks. 2017-01-08 14:46:19 -05:00
Thomas Harte
be48c950b4 Started taking steps towards using a texture barrier where possible to reduce all of my framebuffer binds. Some output appears, but it's not correct. 2017-01-08 11:13:20 -05:00
Thomas Harte
0487b8c178 Definitively eliminated the additional y filtering step; if I'm going to work to ensure always four samples per colour cycle, I can put the channel separation coefficients directly into their shaders, cutting down on samples. 2017-01-07 16:02:33 -05:00
Thomas Harte
5740015f56 Temporarily disabled composite processing to show the pure stream. Fixed both automatic calculations of phase — per line and, at input, per pixel. 2017-01-07 12:38:00 -05:00
Thomas Harte
c84004bfa3 Fixed: colour_cycle_numerator_ doesn't need to be multiplied by the time multiplier because it'll get that for free from the calculation of next_run_length. 2017-01-06 21:36:19 -05:00
Thomas Harte
c746a3711f Temporarily disabled my attempt to be clever with bilinear filtering when applying a lowpass filter. Will need to investigate. 2017-01-04 08:06:18 -05:00
Thomas Harte
aa7774a9a6 Experimental: up the chroma accuracy, just let the luma go straight through. Subject to figuring out how I'm still losing so much precision. 2017-01-03 22:41:34 -05:00
Thomas Harte
a836120945 Restored proper colour separation, but somewhere a massive hit in horizontal resolution is happening — much greater than one would expect from the sample size picked. So investigation to come. 2017-01-03 22:32:07 -05:00
Thomas Harte
7d60df9075 Added the option for both intermediate and output shaders to use only a portion of the input/output texture; made an attempt to pick an appropriate proportion in order to align signal sampling with the colour subcarrier. 2017-01-03 22:16:52 -05:00
Thomas Harte
f2b8b26bc4 Started throwing some comments into my shaders. 2017-01-03 21:16:38 -05:00
34 changed files with 397 additions and 276 deletions

View File

@@ -84,7 +84,7 @@ void AY38910::set_clock_rate(double clock_rate)
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
{
int c = 0;
while((master_divider_&15) && c < number_of_samples)
while((master_divider_&7) && c < number_of_samples)
{
target[c] = output_volume_;
master_divider_++;
@@ -131,7 +131,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
evaluate_output_volume();
for(int ic = 0; ic < 16 && c < number_of_samples; ic++)
for(int ic = 0; ic < 8 && c < number_of_samples; ic++)
{
target[c] = output_volume_;
c++;
@@ -139,7 +139,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
}
}
master_divider_ &= 15;
master_divider_ &= 7;
}
void AY38910::evaluate_output_volume()

View File

@@ -71,7 +71,7 @@ void Machine::setup_output(float aspect_ratio)
"uint y = c & 14u;"
"uint iPhase = (c >> 4);"
"float phaseOffset = 6.283185308 * float(iPhase - 1u) / 13.0;"
"float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;"
"return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);"
"}");
speaker_->set_input_rate((float)(get_clock_rate() / 38.0));
@@ -777,5 +777,6 @@ void Machine::update_audio()
void Machine::synchronise()
{
update_audio();
speaker_->flush();
}

View File

@@ -61,6 +61,7 @@ std::shared_ptr<Outputs::Speaker> Machine::get_speaker()
void Machine::clear_all_keys()
{
memset(key_states_, 0, sizeof(key_states_));
if(is_holding_shift_) set_key_state(KeyShift, true);
}
void Machine::set_key_state(uint16_t key, bool isPressed)
@@ -116,10 +117,9 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target)
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.acorn.should_hold_shift)
if(target.acorn.should_shift_restart)
{
set_key_state(KeyShift, true);
is_holding_shift_ = true;
shift_restart_counter_ = 1000000;
}
}
@@ -398,6 +398,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
if(typer_) typer_->update((int)cycles);
if(plus3_) plus3_->run_for_cycles(4*cycles);
if(shift_restart_counter_)
{
shift_restart_counter_ -= cycles;
if(shift_restart_counter_ <= 0)
{
shift_restart_counter_ = 0;
set_power_on(true);
set_key_state(KeyShift, true);
is_holding_shift_ = true;
}
}
return cycles;
}

View File

@@ -144,6 +144,7 @@ class Machine:
// Disk
std::unique_ptr<Plus3> plus3_;
bool is_holding_shift_;
int shift_restart_counter_;
// Outputs
std::unique_ptr<VideoOutput> video_output_;

View File

@@ -20,6 +20,7 @@
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; };
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; };
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; };
4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; };
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; };
@@ -31,6 +32,7 @@
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; };
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; };
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; };
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; };
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; };
@@ -67,6 +69,9 @@
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; };
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; };
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4431E3AF38600141F11 /* floppy525.png */; };
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; };
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; };
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; };
@@ -447,6 +452,7 @@
4B1E85741D170228001EF87D /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = "<group>"; };
4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = "<group>"; };
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
@@ -471,6 +477,7 @@
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = "<group>"; };
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = "<group>"; };
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Oric/Video.hpp; sourceTree = "<group>"; };
4B2C45411E3C3896002A2389 /* cartridge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cartridge.png; sourceTree = "<group>"; };
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = "<group>"; };
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
@@ -538,6 +545,9 @@
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; };
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; };
4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = "<group>"; };
4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = "<group>"; };
4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = "<group>"; };
4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = "<group>"; };
4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = "<group>"; };
4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = "<group>"; };
@@ -1011,6 +1021,18 @@
path = 6532;
sourceTree = "<group>";
};
4B1EDB411E39A0AC009D6819 /* Icons */ = {
isa = PBXGroup;
children = (
4B2C45411E3C3896002A2389 /* cartridge.png */,
4B79E4411E3AF38600141F11 /* cassette.png */,
4B79E4421E3AF38600141F11 /* floppy35.png */,
4B79E4431E3AF38600141F11 /* floppy525.png */,
4B1EDB431E39A0AC009D6819 /* chip.png */,
);
path = Icons;
sourceTree = "<group>";
};
4B2409591C45DF85004DA684 /* SignalProcessing */ = {
isa = PBXGroup;
children = (
@@ -1899,6 +1921,7 @@
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
isa = PBXGroup;
children = (
4B1EDB411E39A0AC009D6819 /* Icons */,
4BC9DF441D044FCA00F44158 /* ROMImages */,
);
path = Resources;
@@ -2052,13 +2075,18 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */,
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */,
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */,
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */,
4B79E4441E3AF38600141F11 /* cassette.png in Resources */,
4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */,
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */,
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@@ -13,7 +13,7 @@
<string>bin</string>
</array>
<key>CFBundleTypeIconFile</key>
<string></string>
<string>cartridge</string>
<key>CFBundleTypeName</key>
<string>Atari 2600 Cartridge</string>
<key>CFBundleTypeOSTypes</key>
@@ -27,27 +27,13 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>uef</string>
</array>
<key>CFBundleTypeName</key>
<string>Electron/BBC Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array/>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>rom</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>chip</string>
<key>CFBundleTypeName</key>
<string>ROM Image</string>
<key>CFBundleTypeRole</key>
@@ -65,6 +51,8 @@
<string>uef</string>
<string>uef.gz</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>Electron/BBC UEF Image</string>
<key>CFBundleTypeRole</key>
@@ -79,6 +67,8 @@
<array>
<string>prg</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<key>CFBundleTypeName</key>
<string>Commodore Program</string>
<key>CFBundleTypeRole</key>
@@ -93,6 +83,8 @@
<array>
<string>tap</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>Tape Image</string>
<key>CFBundleTypeRole</key>
@@ -107,6 +99,8 @@
<array>
<string>g64</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<key>CFBundleTypeName</key>
<string>Commodore Disk</string>
<key>CFBundleTypeRole</key>
@@ -121,6 +115,8 @@
<array>
<string>d64</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525</string>
<key>CFBundleTypeName</key>
<string>Commodore 1540/1 Disk</string>
<key>CFBundleTypeRole</key>
@@ -139,6 +135,8 @@
<string>adl</string>
<string>adm</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<key>CFBundleTypeName</key>
<string>Electron/BBC Disk Image</string>
<key>CFBundleTypeRole</key>
@@ -151,6 +149,8 @@
<array>
<string>dsk</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<key>CFBundleTypeName</key>
<string>Disk Image</string>
<key>CFBundleTypeRole</key>

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@@ -29,9 +29,9 @@ 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_ = IntermediateBufferWidth / cycles_per_line;
phase_denominator_ = cycles_per_line * colour_cycle_denominator;
phase_denominator_ = cycles_per_line * colour_cycle_denominator * time_multiplier_;
phase_numerator_ = 0;
colour_cycle_numerator_ = colour_cycle_numerator * time_multiplier_;
colour_cycle_numerator_ = colour_cycle_numerator;
phase_alternates_ = should_alternate;
is_alernate_line_ &= phase_alternates_;
unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
@@ -112,7 +112,6 @@ Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested,
#define source_output_position_x2() (*(uint16_t *)&next_run[SourceVertexOffsetOfEnds + 2])
#define source_phase() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0]
#define source_amplitude() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 2]
#define source_phase_time() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 1]
void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Scan::Type type)
{
@@ -149,7 +148,6 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
source_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position();
source_phase() = colour_burst_phase_;
source_amplitude() = colour_burst_amplitude_;
source_phase_time() = (uint8_t)colour_burst_time_; // assumption: burst was within the first 1/16 of the line
}
// decrement the number of cycles left to run for and increment the
@@ -242,7 +240,9 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
frames_since_last_delegate_call_++;
if(frames_since_last_delegate_call_ == 20)
{
output_lock.unlock();
delegate_->crt_did_end_batch_of_frames(this, frames_since_last_delegate_call_, vertical_flywheel_->get_and_reset_number_of_surprises());
output_lock.lock();
frames_since_last_delegate_call_ = 0;
}
}
@@ -286,9 +286,11 @@ void CRT::output_scan(const Scan *const scan)
{
if(horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6)
{
colour_burst_time_ = (uint16_t)horizontal_flywheel_->get_current_time();
colour_burst_phase_ = scan->phase;
unsigned int position_phase = (horizontal_flywheel_->get_current_time() * colour_cycle_numerator_ * 256) / phase_denominator_;
colour_burst_phase_ = (position_phase + scan->phase) & 255;
colour_burst_amplitude_ = scan->amplitude;
colour_burst_phase_ = (colour_burst_phase_ & ~63) + 32;
}
}
@@ -344,7 +346,7 @@ void CRT::output_default_colour_burst(unsigned int number_of_cycles)
Scan scan {
.type = Scan::Type::ColourBurst,
.number_of_cycles = number_of_cycles,
.phase = (uint8_t)((phase_numerator_ * 255) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)),
.phase = (uint8_t)((phase_numerator_ * 256) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)),
.amplitude = 32
};
output_scan(&scan);

View File

@@ -60,7 +60,6 @@ class CRT {
void output_scan(const Scan *scan);
uint8_t colour_burst_phase_, colour_burst_amplitude_;
uint16_t colour_burst_time_;
bool is_writing_composite_run_;
unsigned int phase_denominator_, phase_numerator_, colour_cycle_numerator_;

View File

@@ -36,7 +36,7 @@ const GLsizei InputBufferBuilderWidth = 2048;
const GLsizei InputBufferBuilderHeight = 512;
// This is the size of the intermediate buffers used during composite to RGB conversion
const GLsizei IntermediateBufferWidth = 4096;
const GLsizei IntermediateBufferWidth = 2048;
const GLsizei IntermediateBufferHeight = 512;
// Some internal buffer sizes

View File

@@ -6,8 +6,10 @@
//
#include "CRT.hpp"
#include <stdlib.h>
#include <math.h>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include "CRTOpenGL.hpp"
#include "../../../SignalProcessing/FIRFilter.hpp"
@@ -16,12 +18,14 @@
using namespace Outputs::CRT;
namespace {
static const GLenum composite_texture_unit = GL_TEXTURE0;
static const GLenum separated_texture_unit = GL_TEXTURE1;
static const GLenum filtered_y_texture_unit = GL_TEXTURE2;
static const GLenum filtered_texture_unit = GL_TEXTURE3;
static const GLenum source_data_texture_unit = GL_TEXTURE4;
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE5;
static const GLenum source_data_texture_unit = GL_TEXTURE0;
static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE1;
static const GLenum composite_texture_unit = GL_TEXTURE2;
static const GLenum separated_texture_unit = GL_TEXTURE3;
static const GLenum filtered_texture_unit = GL_TEXTURE4;
static const GLenum work_texture_unit = GL_TEXTURE2;
}
OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
@@ -33,11 +37,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
last_output_height_(0),
fence_(nullptr),
texture_builder(bytes_per_pixel, source_data_texture_unit),
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize),
composite_texture_(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit),
separated_texture_(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit),
filtered_y_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_y_texture_unit),
filtered_texture_(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit)
array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize)
{
glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR);
glBlendColor(0.6f, 0.6f, 0.6f, 1.0f);
@@ -47,6 +47,32 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) :
// create the source vertex array
glGenVertexArrays(1, &source_vertex_array_);
bool supports_texture_barrier = false;
#ifdef GL_NV_texture_barrier
GLint number_of_extensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &number_of_extensions);
for(GLuint c = 0; c < (GLuint)number_of_extensions; c++)
{
const char *extension_name = (const char *)glGetStringi(GL_EXTENSIONS, c);
if(!strcmp(extension_name, "GL_NV_texture_barrier"))
{
supports_texture_barrier = true;
}
}
#endif
// if(supports_texture_barrier)
// {
// work_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight*2, work_texture_unit));
// }
// else
{
composite_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, composite_texture_unit, GL_NEAREST));
separated_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, separated_texture_unit, GL_NEAREST));
filtered_texture_.reset(new OpenGL::TextureTarget(IntermediateBufferWidth, IntermediateBufferHeight, filtered_texture_unit, GL_LINEAR));
}
}
OpenGLOutputBuilder::~OpenGLOutputBuilder()
@@ -57,6 +83,11 @@ OpenGLOutputBuilder::~OpenGLOutputBuilder()
free(rgb_shader_);
}
bool OpenGLOutputBuilder::get_is_television_output()
{
return output_device_ == Television || !rgb_input_shader_program_;
}
void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty)
{
// lock down any other draw_frames
@@ -91,7 +122,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
// make sure there's a target to draw to
if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width)
{
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit));
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit, GL_LINEAR));
if(framebuffer_)
{
new_framebuffer->bind_framebuffer();
@@ -123,30 +154,29 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
output_mutex_.unlock();
struct RenderStage {
OpenGL::TextureTarget *const target;
OpenGL::Shader *const shader;
OpenGL::TextureTarget *const target;
float clear_colour[3];
};
// for composite video, go through four steps to get to something that can be painted to the output
RenderStage composite_render_stages[] =
{
{&composite_texture_, composite_input_shader_program_.get(), {0.0, 0.0, 0.0}},
{&separated_texture_, composite_separation_filter_program_.get(), {0.0, 0.5, 0.5}},
{&filtered_y_texture_, composite_y_filter_shader_program_.get(), {0.0, 0.5, 0.5}},
{&filtered_texture_, composite_chrominance_filter_shader_program_.get(), {0.0, 0.0, 0.0}},
{composite_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
{composite_separation_filter_program_.get(), separated_texture_.get(), {0.0, 0.5, 0.5}},
{composite_chrominance_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
{nullptr}
};
// for RGB video, there's only two steps
RenderStage rgb_render_stages[] =
{
{&composite_texture_, rgb_input_shader_program_.get(), {0.0, 0.0, 0.0}},
{&filtered_texture_, rgb_filter_shader_program_.get(), {0.0, 0.0, 0.0}},
{rgb_input_shader_program_.get(), composite_texture_.get(), {0.0, 0.0, 0.0}},
{rgb_filter_shader_program_.get(), filtered_texture_.get(), {0.0, 0.0, 0.0}},
{nullptr}
};
RenderStage *active_pipeline = (output_device_ == Television || !rgb_input_shader_program_) ? composite_render_stages : rgb_render_stages;
RenderStage *active_pipeline = get_is_television_output() ? composite_render_stages : rgb_render_stages;
if(array_submission.input_size || array_submission.output_size)
{
@@ -154,24 +184,39 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
glBindVertexArray(source_vertex_array_);
glDisable(GL_BLEND);
while(active_pipeline->target)
#ifdef GL_NV_texture_barrier
if(work_texture_)
{
work_texture_->bind_framebuffer();
glClear(GL_COLOR_BUFFER_BIT);
}
#endif
while(active_pipeline->shader)
{
// switch to the framebuffer and shader associated with this stage
active_pipeline->shader->bind();
if(!work_texture_)
{
active_pipeline->target->bind_framebuffer();
// if this is the final stage before painting to the CRT, clear the framebuffer before drawing in order to blank out
// those portions for which no input was provided
if(!active_pipeline[1].target)
{
// if(!active_pipeline[1].shader)
// {
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// }
}
// draw
glDrawArraysInstanced(GL_LINES, 0, 2, (GLsizei)array_submission.input_size / SourceVertexSize);
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.input_size / SourceVertexSize);
active_pipeline++;
#ifdef GL_NV_texture_barrier
glTextureBarrierNV();
#endif
}
// prepare to transfer to framebuffer
@@ -194,6 +239,10 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)array_submission.output_size / OutputVertexSize);
}
#ifdef GL_NV_texture_barrier
glTextureBarrierNV();
#endif
// copy framebuffer to the intended place
glDisable(GL_BLEND);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
@@ -211,7 +260,6 @@ void OpenGLOutputBuilder::reset_all_OpenGL_state()
{
composite_input_shader_program_ = nullptr;
composite_separation_filter_program_ = nullptr;
composite_y_filter_shader_program_ = nullptr;
composite_chrominance_filter_shader_program_ = nullptr;
rgb_input_shader_program_ = nullptr;
rgb_filter_shader_program_ = nullptr;
@@ -229,18 +277,16 @@ void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_reso
void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader)
{
output_mutex_.lock();
std::lock_guard<std::mutex> lock_guard(output_mutex_);
composite_shader_ = strdup(shader);
reset_all_OpenGL_state();
output_mutex_.unlock();
}
void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader)
{
output_mutex_.lock();
std::lock_guard<std::mutex> lock_guard(output_mutex_);
rgb_shader_ = strdup(shader);
reset_all_OpenGL_state();
output_mutex_.unlock();
}
#pragma mark - Program compilation
@@ -252,16 +298,25 @@ void OpenGLOutputBuilder::prepare_composite_input_shaders()
composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_separation_filter_program_ = OpenGL::IntermediateShader::make_chroma_luma_separation_shader();
composite_separation_filter_program_->set_source_texture_unit(composite_texture_unit);
composite_separation_filter_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : composite_texture_unit);
composite_separation_filter_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_y_filter_shader_program_ = OpenGL::IntermediateShader::make_luma_filter_shader();
composite_y_filter_shader_program_->set_source_texture_unit(separated_texture_unit);
composite_y_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
composite_chrominance_filter_shader_program_ = OpenGL::IntermediateShader::make_chroma_filter_shader();
composite_chrominance_filter_shader_program_->set_source_texture_unit(filtered_y_texture_unit);
composite_chrominance_filter_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : separated_texture_unit);
composite_chrominance_filter_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight);
if(work_texture_)
{
composite_input_shader_program_->set_is_double_height(true, 0.0f, 0.0f);
composite_separation_filter_program_->set_is_double_height(true, 0.0f, 0.5f);
composite_chrominance_filter_shader_program_->set_is_double_height(true, 0.5f, 0.0f);
}
else
{
composite_input_shader_program_->set_is_double_height(false);
composite_separation_filter_program_->set_is_double_height(false);
composite_chrominance_filter_shader_program_->set_is_double_height(false);
}
}
void OpenGLOutputBuilder::prepare_rgb_input_shaders()
@@ -295,7 +350,9 @@ void OpenGLOutputBuilder::prepare_source_vertex_array()
void OpenGLOutputBuilder::prepare_output_shader()
{
output_shader_program_ = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false);
output_shader_program_->set_source_texture_unit(filtered_texture_unit);
output_shader_program_->set_source_texture_unit(work_texture_ ? work_texture_unit : filtered_texture_unit);
// output_shader_program_->set_source_texture_unit(composite_texture_unit);
output_shader_program_->set_origin_is_double_height(!!work_texture_);
}
void OpenGLOutputBuilder::prepare_output_vertex_array()
@@ -319,6 +376,7 @@ void OpenGLOutputBuilder::set_output_device(OutputDevice output_device)
composite_src_output_y_ = 0;
last_output_width_ = 0;
last_output_height_ = 0;
set_output_shader_width();
}
}
@@ -362,30 +420,58 @@ void OpenGLOutputBuilder::set_colour_space_uniforms()
}
if(composite_input_shader_program_) composite_input_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
if(composite_separation_filter_program_) composite_separation_filter_program_->set_colour_conversion_matrices(fromRGB, toRGB);
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_colour_conversion_matrices(fromRGB, toRGB);
}
float OpenGLOutputBuilder::get_composite_output_width() const
{
return ((float)colour_cycle_numerator_ * 4.0f) / (float)(colour_cycle_denominator_ * IntermediateBufferWidth);
}
void OpenGLOutputBuilder::set_output_shader_width()
{
if(output_shader_program_)
{
const float width = get_is_television_output() ? get_composite_output_width() : 1.0f;
output_shader_program_->set_input_width_scaler(width);
}
}
void OpenGLOutputBuilder::set_timing_uniforms()
{
OpenGL::IntermediateShader *intermediate_shaders[] = {
composite_input_shader_program_.get(),
composite_separation_filter_program_.get(),
composite_y_filter_shader_program_.get(),
composite_chrominance_filter_shader_program_.get()
};
bool extends = false;
float phaseCyclesPerTick = (float)colour_cycle_numerator_ / (float)(colour_cycle_denominator_ * cycles_per_line_);
for(int c = 0; c < 3; c++)
const float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_;
const float output_width = get_composite_output_width();
const float sample_cycles_per_line = cycles_per_line_ / output_width;
if(composite_separation_filter_program_)
{
if(intermediate_shaders[c]) intermediate_shaders[c]->set_phase_cycles_per_sample(phaseCyclesPerTick, extends);
extends = true;
composite_separation_filter_program_->set_width_scalers(output_width, output_width);
composite_separation_filter_program_->set_separation_frequency(sample_cycles_per_line, colour_subcarrier_frequency);
composite_separation_filter_program_->set_extension(6.0f);
}
if(composite_chrominance_filter_shader_program_)
{
composite_chrominance_filter_shader_program_->set_width_scalers(output_width, output_width);
composite_chrominance_filter_shader_program_->set_extension(5.0f);
}
if(rgb_filter_shader_program_)
{
rgb_filter_shader_program_->set_width_scalers(1.0f, 1.0f);
rgb_filter_shader_program_->set_filter_coefficients(sample_cycles_per_line, (float)input_frequency_ * 0.5f);
}
if(output_shader_program_)
{
set_output_shader_width();
output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_);
}
if(composite_input_shader_program_)
{
composite_input_shader_program_->set_width_scalers(1.0f, output_width);
composite_input_shader_program_->set_extension(0.0f);
}
if(rgb_input_shader_program_)
{
rgb_input_shader_program_->set_width_scalers(1.0f, 1.0f);
}
if(output_shader_program_) output_shader_program_->set_timing(height_of_display_, cycles_per_line_, horizontal_scan_period_, vertical_scan_period_, vertical_period_divider_);
float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_;
if(composite_separation_filter_program_) composite_separation_filter_program_->set_separation_frequency(cycles_per_line_, colour_subcarrier_frequency);
if(composite_y_filter_shader_program_) composite_y_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.25f);
if(composite_chrominance_filter_shader_program_) composite_chrominance_filter_shader_program_->set_filter_coefficients(cycles_per_line_, colour_subcarrier_frequency * 0.5f);
if(rgb_filter_shader_program_) rgb_filter_shader_program_->set_filter_coefficients(cycles_per_line_, (float)input_frequency_ * 0.5f);
}

View File

@@ -66,13 +66,19 @@ class OpenGLOutputBuilder {
GLsizei composite_src_output_y_;
std::unique_ptr<OpenGL::OutputShader> output_shader_program_;
std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_, composite_separation_filter_program_, composite_y_filter_shader_program_, composite_chrominance_filter_shader_program_;
std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program_, rgb_filter_shader_program_;
OpenGL::TextureTarget composite_texture_; // receives raw composite levels
OpenGL::TextureTarget separated_texture_; // receives unfiltered Y in the R channel plus unfiltered but demodulated chrominance in G and B
OpenGL::TextureTarget filtered_y_texture_; // receives filtered Y in the R channel plus unfiltered chrominance in G and B
OpenGL::TextureTarget filtered_texture_; // receives filtered YIQ or YUV
std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_;
std::unique_ptr<OpenGL::IntermediateShader> composite_separation_filter_program_;
std::unique_ptr<OpenGL::IntermediateShader> composite_chrominance_filter_shader_program_;
std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program_;
std::unique_ptr<OpenGL::IntermediateShader> rgb_filter_shader_program_;
std::unique_ptr<OpenGL::TextureTarget> composite_texture_; // receives raw composite levels
std::unique_ptr<OpenGL::TextureTarget> separated_texture_; // receives filtered Y in the R channel plus unfiltered but demodulated chrominance in G and B
std::unique_ptr<OpenGL::TextureTarget> filtered_texture_; // receives filtered YIQ or YUV
std::unique_ptr<OpenGL::TextureTarget> work_texture_; // used for all intermediate rendering if texture fences are supported
std::unique_ptr<OpenGL::TextureTarget> framebuffer_; // the current pixel output
@@ -88,6 +94,9 @@ class OpenGLOutputBuilder {
void reset_all_OpenGL_state();
GLsync fence_;
float get_composite_output_width() const;
void set_output_shader_width();
bool get_is_television_output();
public:
// These two are protected by output_mutex_.

View File

@@ -15,6 +15,7 @@
#else
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl3.h>
#include <OpenGL/gl3ext.h>
#endif
#endif

View File

@@ -40,11 +40,14 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
"in vec2 ends;"
"in vec3 phaseTimeAndAmplitude;"
"uniform float phaseCyclesPerTick;"
"uniform ivec2 outputTextureSize;"
"uniform float extension;"
"uniform %s texID;"
"uniform float offsets[5];"
"uniform vec2 widthScalers;"
"uniform float inputVerticalOffset;"
"uniform float outputVerticalOffset;"
"uniform float textureHeightDivisor;"
"out vec2 phaseAndAmplitudeVarying;"
"out vec2 inputPositionsVarying[11];"
@@ -53,35 +56,51 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char *
"void main(void)"
"{"
// odd vertices are on the left, even on the right
"float extent = float(gl_VertexID & 1);"
"float longitudinal = float((gl_VertexID & 2) >> 1);"
"vec2 inputPosition = vec2(mix(inputStart.x, ends.x, extent), inputStart.y);"
"vec2 outputPosition = vec2(mix(outputStart.x, ends.y, extent), outputStart.y);"
// inputPosition.x is either inputStart.x or ends.x, depending on whether it is on the left or the right;
// outputPosition.x is either outputStart.x or ends.y;
// .ys are inputStart.y and outputStart.y respectively
"vec2 inputPosition = vec2(mix(inputStart.x, ends.x, extent)*widthScalers[0], inputStart.y + inputVerticalOffset);"
"vec2 outputPosition = vec2(mix(outputStart.x, ends.y, extent)*widthScalers[1], outputStart.y + outputVerticalOffset);"
"inputPosition.y += longitudinal;"
"outputPosition.y += longitudinal;"
// extension is the amount to extend both the input and output by to add a full colour cycle at each end
"vec2 extensionVector = vec2(extension, 0.0) * 2.0 * (extent - 0.5);"
// extended[Input/Output]Position are [input/output]Position with the necessary applied extension
"vec2 extendedInputPosition = %s + extensionVector;"
"vec2 extendedOutputPosition = outputPosition + extensionVector;"
// keep iInputPositionVarying in whole source pixels, scale mappedInputPosition to the ordinary normalised range
"vec2 textureSize = vec2(textureSize(texID, 0));"
"iInputPositionVarying = extendedInputPosition;"
"vec2 mappedInputPosition = (extendedInputPosition + vec2(0.0, 0.5)) / textureSize;"
"vec2 mappedInputPosition = extendedInputPosition / textureSize;" // + vec2(0.0, 0.5)
"inputPositionsVarying[0] = mappedInputPosition - (vec2(offsets[0], 0.0) / textureSize);"
"inputPositionsVarying[1] = mappedInputPosition - (vec2(offsets[1], 0.0) / textureSize);"
"inputPositionsVarying[2] = mappedInputPosition - (vec2(offsets[2], 0.0) / textureSize);"
"inputPositionsVarying[3] = mappedInputPosition - (vec2(offsets[3], 0.0) / textureSize);"
"inputPositionsVarying[4] = mappedInputPosition - (vec2(offsets[4], 0.0) / textureSize);"
// setup input positions spaced as per the supplied offsets; these are for filtering where required
"inputPositionsVarying[0] = mappedInputPosition - (vec2(5.0, 0.0) / textureSize);"
"inputPositionsVarying[1] = mappedInputPosition - (vec2(4.0, 0.0) / textureSize);"
"inputPositionsVarying[2] = mappedInputPosition - (vec2(3.0, 0.0) / textureSize);"
"inputPositionsVarying[3] = mappedInputPosition - (vec2(2.0, 0.0) / textureSize);"
"inputPositionsVarying[4] = mappedInputPosition - (vec2(1.0, 0.0) / textureSize);"
"inputPositionsVarying[5] = mappedInputPosition;"
"inputPositionsVarying[6] = mappedInputPosition + (vec2(offsets[4], 0.0) / textureSize);"
"inputPositionsVarying[7] = mappedInputPosition + (vec2(offsets[3], 0.0) / textureSize);"
"inputPositionsVarying[8] = mappedInputPosition + (vec2(offsets[2], 0.0) / textureSize);"
"inputPositionsVarying[9] = mappedInputPosition + (vec2(offsets[1], 0.0) / textureSize);"
"inputPositionsVarying[10] = mappedInputPosition + (vec2(offsets[0], 0.0) / textureSize);"
"inputPositionsVarying[6] = mappedInputPosition + (vec2(1.0, 0.0) / textureSize);"
"inputPositionsVarying[7] = mappedInputPosition + (vec2(2.0, 0.0) / textureSize);"
"inputPositionsVarying[8] = mappedInputPosition + (vec2(3.0, 0.0) / textureSize);"
"inputPositionsVarying[9] = mappedInputPosition + (vec2(4.0, 0.0) / textureSize);"
"inputPositionsVarying[10] = mappedInputPosition + (vec2(5.0, 0.0) / textureSize);"
"delayLinePositionVarying = mappedInputPosition - vec2(0.0, 1.0);"
"phaseAndAmplitudeVarying.x = (phaseCyclesPerTick * (extendedOutputPosition.x - phaseTimeAndAmplitude.y) + (phaseTimeAndAmplitude.x / 256.0)) * 2.0 * 3.141592654;"
// setup phaseAndAmplitudeVarying.x as colour burst subcarrier phase, in radians;
// setup phaseAndAmplitudeVarying.x as colour burst amplitude
"phaseAndAmplitudeVarying.x = (extendedOutputPosition.x + (phaseTimeAndAmplitude.x / 64.0)) * 0.5 * 3.141592654;"
"phaseAndAmplitudeVarying.y = 0.33;" // TODO: reinstate connection with (phaseTimeAndAmplitude.y/256.0)
// determine output position by scaling the output position according to the texture size
"vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(1.0)/outputTextureSize;"
"gl_Position = vec4(eyePosition, 0.0, 1.0);"
"}", sampler_type, input_variable);
@@ -172,7 +191,6 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat
"in vec2 phaseAndAmplitudeVarying;"
"in vec2 inputPositionsVarying[11];"
"uniform vec4 weights[3];"
"out vec3 fragColour;"
@@ -180,37 +198,20 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat
"void main(void)"
"{"
"vec4 samples[3] = vec4[]("
"vec4("
"texture(texID, inputPositionsVarying[0]).r,"
"texture(texID, inputPositionsVarying[1]).r,"
"texture(texID, inputPositionsVarying[2]).r,"
"texture(texID, inputPositionsVarying[3]).r"
"),"
"vec4("
"vec4 samples = vec4("
"texture(texID, inputPositionsVarying[3]).r,"
"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"
")"
"texture(texID, inputPositionsVarying[6]).r"
");"
"float luminance = dot(samples, vec4(0.25));"
"float luminance = "
"dot(vec3("
"dot(samples[0], weights[0]),"
"dot(samples[1], weights[1]),"
"dot(samples[2], weights[2])"
"), vec3(1.0));"
"float chrominance = 0.5 * (samples[1].y - luminance) / phaseAndAmplitudeVarying.y;"
// define chroma to be whatever was here, minus luma
"float chrominance = 0.5 * (samples.z - luminance) / phaseAndAmplitudeVarying.y;"
"luminance /= (1.0 - phaseAndAmplitudeVarying.y);"
// split choma colours here, as the most direct place, writing out
// RGB = (luma, chroma.x, chroma.y)
"vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x));"
"fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));"
"}",false, false);
@@ -232,41 +233,18 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade
"void main(void)"
"{"
"vec3 samples[] = vec3[]("
"texture(texID, inputPositionsVarying[0]).rgb,"
"texture(texID, inputPositionsVarying[1]).rgb,"
"texture(texID, inputPositionsVarying[2]).rgb,"
"texture(texID, inputPositionsVarying[3]).rgb,"
"texture(texID, inputPositionsVarying[4]).rgb,"
"texture(texID, inputPositionsVarying[5]).rgb,"
"texture(texID, inputPositionsVarying[6]).rgb,"
"texture(texID, inputPositionsVarying[7]).rgb,"
"texture(texID, inputPositionsVarying[8]).rgb,"
"texture(texID, inputPositionsVarying[9]).rgb,"
"texture(texID, inputPositionsVarying[10]).rgb"
"texture(texID, inputPositionsVarying[6]).rgb"
");"
"vec4 chromaChannel1[] = vec4[]("
"vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g),"
"vec4(samples[4].g, samples[5].g, samples[6].g, samples[7].g),"
"vec4(samples[8].g, samples[9].g, samples[10].g, 0.0)"
");"
"vec4 chromaChannel2[] = vec4[]("
"vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b),"
"vec4(samples[4].b, samples[5].b, samples[6].b, samples[7].b),"
"vec4(samples[8].b, samples[9].b, samples[10].b, 0.0)"
");"
"vec4 chromaChannel1 = vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g);"
"vec4 chromaChannel2 = vec4(samples[0].b, samples[1].b, samples[2].b, samples[3].b);"
"vec3 lumaChromaColour = vec3(samples[5].r,"
"dot(vec3("
"dot(chromaChannel1[0], weights[0]),"
"dot(chromaChannel1[1], weights[1]),"
"dot(chromaChannel1[2], weights[2])"
"), vec3(1.0)),"
"dot(vec3("
"dot(chromaChannel2[0], weights[0]),"
"dot(chromaChannel2[1], weights[1]),"
"dot(chromaChannel2[2], weights[2])"
"), vec3(1.0))"
"vec3 lumaChromaColour = vec3(samples[2].r,"
"dot(chromaChannel1, vec4(0.25)),"
"dot(chromaChannel2, vec4(0.25))"
");"
"vec3 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);"
@@ -274,52 +252,6 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade
"}", false, false);
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_luma_filter_shader()
{
return make_shader(
"#version 150\n"
"in vec2 inputPositionsVarying[11];"
"uniform vec4 weights[3];"
"out vec3 fragColour;"
"uniform sampler2D texID;"
"uniform mat3 lumaChromaToRGB;"
"void main(void)"
"{"
"vec3 samples[] = vec3[]("
"texture(texID, inputPositionsVarying[0]).rgb,"
"texture(texID, inputPositionsVarying[1]).rgb,"
"texture(texID, inputPositionsVarying[2]).rgb,"
"texture(texID, inputPositionsVarying[3]).rgb,"
"texture(texID, inputPositionsVarying[4]).rgb,"
"texture(texID, inputPositionsVarying[5]).rgb,"
"texture(texID, inputPositionsVarying[6]).rgb,"
"texture(texID, inputPositionsVarying[7]).rgb,"
"texture(texID, inputPositionsVarying[8]).rgb,"
"texture(texID, inputPositionsVarying[9]).rgb,"
"texture(texID, inputPositionsVarying[10]).rgb"
");"
"vec4 luminance[] = 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)"
");"
"fragColour = vec3("
"dot(vec3("
"dot(luminance[0], weights[0]),"
"dot(luminance[1], weights[1]),"
"dot(luminance[2], weights[2])"
"), vec3(1.0)),"
"samples[5].gb"
");"
"}", false, false);
}
std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader()
{
return make_shader(
@@ -404,44 +336,53 @@ void IntermediateShader::set_filter_coefficients(float sampling_rate, float cuto
// Perform a linear search for the highest number of taps we can use with 11 samples.
GLfloat weights[12];
GLfloat offsets[5];
unsigned int taps = 21;
unsigned int taps = 11;
// unsigned int taps = 21;
while(1)
{
float coefficients[21];
SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation);
luminance_filter.get_coefficients(coefficients);
int sample = 0;
int c = 0;
// int sample = 0;
// int c = 0;
memset(weights, 0, sizeof(float)*12);
memset(offsets, 0, sizeof(float)*5);
int halfSize = (taps >> 1);
while(c < halfSize && sample < 5)
for(int c = 0; c < taps; c++)
{
offsets[sample] = (float)(halfSize - c);
if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1))
{
weights[sample] = coefficients[c] + coefficients[c+1];
offsets[sample] -= (coefficients[c+1] / weights[sample]);
c += 2;
}
else
{
weights[sample] = coefficients[c];
c++;
}
sample ++;
}
if(c == halfSize) // i.e. we finished combining inputs before we ran out of space
{
weights[sample] = coefficients[c];
for(int c = 0; c < sample; c++)
{
weights[sample+c+1] = weights[sample-c-1];
if(c < 5) offsets[c] = (halfSize - c);
weights[c] = coefficients[c];
}
break;
}
// int halfSize = (taps >> 1);
// while(c < halfSize && sample < 5)
// {
// offsets[sample] = (float)(halfSize - c);
// if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1))
// {
// weights[sample] = coefficients[c] + coefficients[c+1];
// offsets[sample] -= (coefficients[c+1] / weights[sample]);
// c += 2;
// }
// else
// {
// weights[sample] = coefficients[c];
// c++;
// }
// sample ++;
// }
// if(c == halfSize) // i.e. we finished combining inputs before we ran out of space
// {
// weights[sample] = coefficients[c];
// for(int c = 0; c < sample; c++)
// {
// weights[sample+c+1] = weights[sample-c-1];
// }
// break;
// }
taps -= 2;
}
@@ -454,10 +395,9 @@ void IntermediateShader::set_separation_frequency(float sampling_rate, float col
set_filter_coefficients(sampling_rate, colour_burst_frequency);
}
void IntermediateShader::set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle)
void IntermediateShader::set_extension(float extension)
{
set_uniform("phaseCyclesPerTick", (GLfloat)phase_cycles_per_sample);
set_uniform("extension", extend_runs_to_full_cycle ? ceilf(1.0f / phase_cycles_per_sample) : 0.0f);
set_uniform("extension", extension);
}
void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB)
@@ -465,3 +405,15 @@ void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *t
set_uniform_matrix("lumaChromaToRGB", 3, false, toRGB);
set_uniform_matrix("rgbToLumaChroma", 3, false, fromRGB);
}
void IntermediateShader::set_width_scalers(float input_scaler, float output_scaler)
{
set_uniform("widthScalers", input_scaler, output_scaler);
}
void IntermediateShader::set_is_double_height(bool is_double_height, float input_offset, float output_offset)
{
set_uniform("textureHeightDivisor", is_double_height ? 2.0f : 1.0f);
set_uniform("inputVerticalOffset", input_offset);
set_uniform("outputVerticalOffset", output_offset);
}

View File

@@ -44,11 +44,6 @@ public:
*/
static std::unique_ptr<IntermediateShader> make_chroma_filter_shader();
/*!
Constructs and returns an intermediate shader that will filter R while passing through G and B unchanged.
*/
static std::unique_ptr<IntermediateShader> make_luma_filter_shader();
/*!
Constructs and returns an intermediate shader that will filter R, G and B.
*/
@@ -81,13 +76,24 @@ public:
geometry should be extended so that a complete colour cycle is included at both the beginning and end,
to occur upon the next `bind`.
*/
void set_phase_cycles_per_sample(float phase_cycles_per_sample, bool extend_runs_to_full_cycle);
void set_extension(float extension);
/*!
Queues setting the matrices that convert between RGB and chrominance/luminance to occur on the next `bind`.
*/
void set_colour_conversion_matrices(float *fromRGB, float *toRGB);
/*!
Sets the proportions of the input and output areas that should be considered the whole width — 1.0 means use all available
space, 0.5 means use half, etc.
*/
void set_width_scalers(float input_scaler, float output_scaler);
/*!
Sets source and target vertical offsets.
*/
void set_is_double_height(bool is_double_height, float input_offset = 0.0f, float output_offset = 0.0f);
private:
static std::unique_ptr<IntermediateShader> make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition);
};

View File

@@ -38,6 +38,8 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
"uniform vec2 positionConversion;"
"uniform vec2 scanNormal;"
"uniform %s texID;"
"uniform float inputScaler;"
"uniform int textureHeightDivisor;"
"out float lateralVarying;"
"out vec2 srcCoordinatesVarying;"
@@ -52,9 +54,10 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met
"lateralVarying = lateral - 0.5;"
"vec2 vSrcCoordinates = vec2(x, vertical.y);"
"ivec2 textureSize = textureSize(texID, 0);"
"ivec2 textureSize = textureSize(texID, 0) * ivec2(1, textureHeightDivisor);"
"iSrcCoordinatesVarying = vSrcCoordinates;"
"srcCoordinatesVarying = vec2(vSrcCoordinates.x / textureSize.x, (vSrcCoordinates.y + 0.5) / textureSize.y);"
"srcCoordinatesVarying = vec2(inputScaler * vSrcCoordinates.x / textureSize.x, (vSrcCoordinates.y + 0.5) / textureSize.y);"
"srcCoordinatesVarying.x = srcCoordinatesVarying.x - mod(srcCoordinatesVarying.x, 1.0 / textureSize.x);"
"vec2 vPosition = vec2(x, vertical.x);"
"vec2 floatingPosition = (vPosition / positionConversion) + lateral * scanNormal;"
@@ -117,3 +120,13 @@ void OutputShader::set_timing(unsigned int height_of_display, unsigned int cycle
set_uniform("scanNormal", scan_normal[0], scan_normal[1]);
set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider);
}
void OutputShader::set_input_width_scaler(float input_scaler)
{
set_uniform("inputScaler", input_scaler);
}
void OutputShader::set_origin_is_double_height(bool is_double_height)
{
set_uniform("textureHeightDivisor", is_double_height ? 2 : 1);
}

View File

@@ -58,6 +58,16 @@ public:
to occur upon the next `bind`.
*/
void set_timing(unsigned int height_of_display, unsigned int cycles_per_line, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider);
/*!
*/
void set_origin_is_double_height(bool is_double_height);
/*!
Sets the proportion of the input area that should be considered the whole width — 1.0 means use all available
space, 0.5 means use half, etc.
*/
void set_input_width_scaler(float input_scaler);
};
}

View File

@@ -156,7 +156,8 @@ void Shader::set_uniform(const std::string &name, GLint value1, GLint value2)
void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2)
{
enqueue_function([name, value1, value2, this] {
glUniform2f(location(), value1, value2);
GLint location = location();
glUniform2f(location, value1, value2);
});
}

View File

@@ -12,7 +12,7 @@
using namespace OpenGL;
TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit) :
TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter) :
_width(width),
_height(height),
_pixel_shader(nullptr),
@@ -33,8 +33,8 @@ TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit)
uint8_t *blank_buffer = (uint8_t *)calloc((size_t)(_expanded_width * _expanded_height), 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)_expanded_width, (GLsizei)_expanded_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, blank_buffer);
free(blank_buffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);

View File

@@ -30,7 +30,7 @@ class TextureTarget {
@param height The height of target to create.
@param texture_unit A texture unit on which to bind the texture.
*/
TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit);
TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter);
~TextureTarget();
/*!

View File

@@ -30,6 +30,16 @@ Similar effort is put into audio generation. If the real machine normally genera
If your machine has a 4k monitor and a 96Khz audio output? Then you'll get a 4k rendering of a composite display and, assuming the emulated machine produces source audio at or above 96Khz, 96,000 individual distinct audio samples a second. Interlaced video also works and looks much as it always did on those machines that produce it.
### Samples
| 1:1 Pixel Copying | Composite Decoded |
|---|---|
|![The Electron start screen, with a classic 1:1 pixel emulation](READMEImages/NaiveElectron.png)|![The Electron start screen, decoded from an interlaced composite feed](READMEImages/CompositeElectron.png)|
|![Repton 3 in game, with a classic 1:1 pixel emulation](READMEImages/NaiveRepton3.png)|![Repton 3 in game, decoded from an interlaced composite feed](READMEImages/CompositeRepton3.png)|
|![Stormlord with a classic 1:1 pixel emulation](READMEImages/NaiveStormlord.png)|![Stormlord decoded from an interlaced composite feed](READMEImages/CompositeStormlord.png)|
<img src="READMEImages/ReptonInterlaced.gif" height=600 alt="Repton title screen, interlaced">
## Low Latency
The display produced is an emulated CRT, with phosphor decay. Therefore if you have a 140Hz monitor it can produce 140 distinct frames per second. Latency is dictated by the output hardware, not the emulated machine.

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -69,7 +69,7 @@ void StaticAnalyser::Acorn::AddTargets(
target.probability = 1.0; // TODO: a proper estimation
target.acorn.has_dfs = false;
target.acorn.has_adfs = false;
target.acorn.should_hold_shift = false;
target.acorn.should_shift_restart = false;
// strip out inappropriate cartridges
target.cartridges = AcornCartridgesFrom(cartridges);
@@ -126,20 +126,11 @@ void StaticAnalyser::Acorn::AddTargets(
target.acorn.has_dfs = !!dfs_catalogue;
target.acorn.has_adfs = !!adfs_catalogue;
std::string adfs_command;
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
switch(bootOption)
{
case Catalogue::BootOption::None: adfs_command = "*CAT\n"; break;
case Catalogue::BootOption::LoadBOOT: adfs_command = "*LOAD !BOOT\n"; break;
case Catalogue::BootOption::RunBOOT: adfs_command = "*RUN !BOOT\n"; break;
case Catalogue::BootOption::ExecBOOT: adfs_command = "*EXEC !BOOT\n"; break;
}
// if(target.acorn.has_dfs && bootOption != Catalogue::BootOption::None)
// target.acorn.should_hold_shift = true;
// else
target.loadingCommand = (target.acorn.has_dfs ? "" : "*MOUNT\n") + adfs_command;
if(bootOption != Catalogue::BootOption::None)
target.acorn.should_shift_restart = true;
else
target.loadingCommand = "*CAT\n";
}
}

View File

@@ -47,7 +47,7 @@ struct Target {
struct {
bool has_adfs;
bool has_dfs;
bool should_hold_shift;
bool should_shift_restart;
} acorn;
struct {