1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-25 09:27:01 +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();
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(!work_texture_)
{
glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
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].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("
"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"
")"
"vec4 samples = vec4("
"texture(texID, inputPositionsVarying[3]).r,"
"texture(texID, inputPositionsVarying[4]).r,"
"texture(texID, inputPositionsVarying[5]).r,"
"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];
}
break;
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

@@ -24,12 +24,22 @@ The full process of loading a title — even if you've never used the emulated m
## Signal Processing
Consider an ordinary, unmodified Commodore Vic-20. Its only video output is composite. Therefore the emulated machine's only video output is composite. In order to display the video output, your GPU then decodes composite video. Therefore all composite video artefacts are present and exactly correct, not because of a posthoc filter combining all the subjective effects that this author associates with composite video but because the real signal is really being processed.
Consider an ordinary, unmodified Commodore Vic-20. Its only video output is composite. Therefore the emulated machine's only video output is composite. In order to display the video output, your GPU then decodes composite video. Therefore all composite video artefacts are present and exactly correct, not because of a post hoc filter combining all the subjective effects that this author associates with composite video but because the real signal is really being processed.
Similar effort is put into audio generation. If the real machine normally generates audio at 192Khz then the emulator generates a 192Khz source signal and filters it down to whatever the host machine can output.
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 {