Compare commits
	
		
			50 Commits
		
	
	
		
			2017-01-01
			...
			2017-01-27
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | acadfbabec | ||
|  | 9001cc3fc2 | ||
|  | 015b2b49f9 | ||
|  | 92f928ca42 | ||
|  | 6d087ca054 | ||
|  | c2d7e36c8f | ||
|  | 4d6e78e641 | ||
|  | 5761c8267b | ||
|  | a66a8c31b2 | ||
|  | 19e4ee12e1 | ||
|  | 4871572a33 | ||
|  | 2e744a95e4 | ||
|  | ff87f1390d | ||
|  | 76ca30c26d | ||
|  | 7c2685cb34 | ||
|  | 8cf25a2d70 | ||
|  | 8d69dd30f3 | ||
|  | ae8068b86f | ||
|  | baeb0ee89f | ||
|  | c07993bb0a | ||
|  | 7680cbf9c3 | ||
|  | 4920fe6701 | ||
|  | 55fe0176bd | ||
|  | 99fcbb55d1 | ||
|  | 6f78ecd12b | ||
|  | ced644b103 | ||
|  | be1cb2a551 | ||
|  | b4159295f6 | ||
|  | d0a93409e6 | ||
|  | 4c3669f210 | ||
|  | eeb646868b | ||
|  | 3d789732a2 | ||
|  | d2a7d39749 | ||
|  | 9521718120 | ||
|  | 28909e33ca | ||
|  | 79632b1d34 | ||
|  | cf6d03e35c | ||
|  | 4a4b31a15c | ||
|  | f3d9aec8fc | ||
|  | 7ad64ff16b | ||
|  | 6153ada33b | ||
|  | be48c950b4 | ||
|  | 0487b8c178 | ||
|  | 5740015f56 | ||
|  | c84004bfa3 | ||
|  | c746a3711f | ||
|  | aa7774a9a6 | ||
|  | a836120945 | ||
|  | 7d60df9075 | ||
|  | f2b8b26bc4 | 
| @@ -84,7 +84,7 @@ void AY38910::set_clock_rate(double clock_rate) | |||||||
| void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) | void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) | ||||||
| { | { | ||||||
| 	int c = 0; | 	int c = 0; | ||||||
| 	while((master_divider_&15) && c < number_of_samples) | 	while((master_divider_&7) && c < number_of_samples) | ||||||
| 	{ | 	{ | ||||||
| 		target[c] = output_volume_; | 		target[c] = output_volume_; | ||||||
| 		master_divider_++; | 		master_divider_++; | ||||||
| @@ -131,7 +131,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) | |||||||
|  |  | ||||||
| 		evaluate_output_volume(); | 		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_; | 			target[c] = output_volume_; | ||||||
| 			c++; | 			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() | void AY38910::evaluate_output_volume() | ||||||
|   | |||||||
| @@ -71,7 +71,7 @@ void Machine::setup_output(float aspect_ratio) | |||||||
| 			"uint y = c & 14u;" | 			"uint y = c & 14u;" | ||||||
| 			"uint iPhase = (c >> 4);" | 			"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);" | 			"return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);" | ||||||
| 		"}"); | 		"}"); | ||||||
| 	speaker_->set_input_rate((float)(get_clock_rate() / 38.0)); | 	speaker_->set_input_rate((float)(get_clock_rate() / 38.0)); | ||||||
| @@ -777,5 +777,6 @@ void Machine::update_audio() | |||||||
| void Machine::synchronise() | void Machine::synchronise() | ||||||
| { | { | ||||||
| 	update_audio(); | 	update_audio(); | ||||||
|  | 	speaker_->flush(); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -61,6 +61,7 @@ std::shared_ptr<Outputs::Speaker> Machine::get_speaker() | |||||||
| void Machine::clear_all_keys() | void Machine::clear_all_keys() | ||||||
| { | { | ||||||
| 	memset(key_states_, 0, sizeof(key_states_)); | 	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) | 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()); | 		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); | 		shift_restart_counter_ = 1000000; | ||||||
| 		is_holding_shift_ = true; |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -398,6 +398,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin | |||||||
|  |  | ||||||
| 	if(typer_) typer_->update((int)cycles); | 	if(typer_) typer_->update((int)cycles); | ||||||
| 	if(plus3_) plus3_->run_for_cycles(4*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; | 	return cycles; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -144,6 +144,7 @@ class Machine: | |||||||
| 		// Disk | 		// Disk | ||||||
| 		std::unique_ptr<Plus3> plus3_; | 		std::unique_ptr<Plus3> plus3_; | ||||||
| 		bool is_holding_shift_; | 		bool is_holding_shift_; | ||||||
|  | 		int shift_restart_counter_; | ||||||
|  |  | ||||||
| 		// Outputs | 		// Outputs | ||||||
| 		std::unique_ptr<VideoOutput> video_output_; | 		std::unique_ptr<VideoOutput> video_output_; | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ | |||||||
| 		4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; }; | 		4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; }; | ||||||
| 		4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; }; | 		4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; }; | ||||||
| 		4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; }; | 		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 */; }; | 		4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; }; | ||||||
| 		4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; }; | 		4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; }; | ||||||
| 		4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; }; | 		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 */; }; | 		4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; }; | ||||||
| 		4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; | 		4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; | ||||||
| 		4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.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 */; }; | 		4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; | ||||||
| 		4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; | 		4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; | ||||||
| 		4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.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 */; }; | 		4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; | ||||||
| 		4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; }; | 		4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; }; | ||||||
| 		4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.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 */; }; | 		4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; }; | ||||||
| 		4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; }; | 		4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; }; | ||||||
| 		4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* 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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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>"; }; | 		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; | 			path = 6532; | ||||||
| 			sourceTree = "<group>"; | 			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 */ = { | 		4B2409591C45DF85004DA684 /* SignalProcessing */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| @@ -1899,6 +1921,7 @@ | |||||||
| 		4BE5F85A1C3E1C2500C43F01 /* Resources */ = { | 		4BE5F85A1C3E1C2500C43F01 /* Resources */ = { | ||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
|  | 				4B1EDB411E39A0AC009D6819 /* Icons */, | ||||||
| 				4BC9DF441D044FCA00F44158 /* ROMImages */, | 				4BC9DF441D044FCA00F44158 /* ROMImages */, | ||||||
| 			); | 			); | ||||||
| 			path = Resources; | 			path = Resources; | ||||||
| @@ -2052,13 +2075,18 @@ | |||||||
| 			isa = PBXResourcesBuildPhase; | 			isa = PBXResourcesBuildPhase; | ||||||
| 			buildActionMask = 2147483647; | 			buildActionMask = 2147483647; | ||||||
| 			files = ( | 			files = ( | ||||||
|  | 				4B2C45421E3C3896002A2389 /* cartridge.png in Resources */, | ||||||
| 				4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, | 				4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, | ||||||
|  | 				4B79E4451E3AF38600141F11 /* floppy35.png in Resources */, | ||||||
|  | 				4B1EDB451E39A0AC009D6819 /* chip.png in Resources */, | ||||||
| 				4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, | 				4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, | ||||||
| 				4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */, | 				4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */, | ||||||
| 				4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */, | 				4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */, | ||||||
|  | 				4B79E4441E3AF38600141F11 /* cassette.png in Resources */, | ||||||
| 				4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */, | 				4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */, | ||||||
| 				4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, | 				4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, | ||||||
| 				4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */, | 				4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */, | ||||||
|  | 				4B79E4461E3AF38600141F11 /* floppy525.png in Resources */, | ||||||
| 				4BC9DF451D044FCA00F44158 /* ROMImages in Resources */, | 				4BC9DF451D044FCA00F44158 /* ROMImages in Resources */, | ||||||
| 			); | 			); | ||||||
| 			runOnlyForDeploymentPostprocessing = 0; | 			runOnlyForDeploymentPostprocessing = 0; | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ | |||||||
| 				<string>bin</string> | 				<string>bin</string> | ||||||
| 			</array> | 			</array> | ||||||
| 			<key>CFBundleTypeIconFile</key> | 			<key>CFBundleTypeIconFile</key> | ||||||
| 			<string></string> | 			<string>cartridge</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Atari 2600 Cartridge</string> | 			<string>Atari 2600 Cartridge</string> | ||||||
| 			<key>CFBundleTypeOSTypes</key> | 			<key>CFBundleTypeOSTypes</key> | ||||||
| @@ -27,27 +27,13 @@ | |||||||
| 			<key>NSDocumentClass</key> | 			<key>NSDocumentClass</key> | ||||||
| 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | 			<string>$(PRODUCT_MODULE_NAME).MachineDocument</string> | ||||||
| 		</dict> | 		</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> | 		<dict> | ||||||
| 			<key>CFBundleTypeExtensions</key> | 			<key>CFBundleTypeExtensions</key> | ||||||
| 			<array> | 			<array> | ||||||
| 				<string>rom</string> | 				<string>rom</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>chip</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>ROM Image</string> | 			<string>ROM Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| @@ -65,6 +51,8 @@ | |||||||
| 				<string>uef</string> | 				<string>uef</string> | ||||||
| 				<string>uef.gz</string> | 				<string>uef.gz</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>cassette</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Electron/BBC UEF Image</string> | 			<string>Electron/BBC UEF Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| @@ -79,6 +67,8 @@ | |||||||
| 			<array> | 			<array> | ||||||
| 				<string>prg</string> | 				<string>prg</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>floppy525</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore Program</string> | 			<string>Commodore Program</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| @@ -93,6 +83,8 @@ | |||||||
| 			<array> | 			<array> | ||||||
| 				<string>tap</string> | 				<string>tap</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>cassette</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Tape Image</string> | 			<string>Tape Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| @@ -107,6 +99,8 @@ | |||||||
| 			<array> | 			<array> | ||||||
| 				<string>g64</string> | 				<string>g64</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>floppy525</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore Disk</string> | 			<string>Commodore Disk</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| @@ -121,6 +115,8 @@ | |||||||
| 			<array> | 			<array> | ||||||
| 				<string>d64</string> | 				<string>d64</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>floppy525</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Commodore 1540/1 Disk</string> | 			<string>Commodore 1540/1 Disk</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| @@ -139,6 +135,8 @@ | |||||||
| 				<string>adl</string> | 				<string>adl</string> | ||||||
| 				<string>adm</string> | 				<string>adm</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>floppy35</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Electron/BBC Disk Image</string> | 			<string>Electron/BBC Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
| @@ -151,6 +149,8 @@ | |||||||
| 			<array> | 			<array> | ||||||
| 				<string>dsk</string> | 				<string>dsk</string> | ||||||
| 			</array> | 			</array> | ||||||
|  | 			<key>CFBundleTypeIconFile</key> | ||||||
|  | 			<string>floppy35</string> | ||||||
| 			<key>CFBundleTypeName</key> | 			<key>CFBundleTypeName</key> | ||||||
| 			<string>Disk Image</string> | 			<string>Disk Image</string> | ||||||
| 			<key>CFBundleTypeRole</key> | 			<key>CFBundleTypeRole</key> | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock Signal/Resources/Icons/cartridge.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 111 KiB | 
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock Signal/Resources/Icons/cassette.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 194 KiB | 
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock Signal/Resources/Icons/chip.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 105 KiB | 
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock Signal/Resources/Icons/floppy35.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 102 KiB | 
							
								
								
									
										
											BIN
										
									
								
								OSBindings/Mac/Clock Signal/Resources/Icons/floppy525.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 126 KiB | 
| @@ -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." | 																//	for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV." | ||||||
|  |  | ||||||
| 	time_multiplier_ = IntermediateBufferWidth / cycles_per_line; | 	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; | 	phase_numerator_ = 0; | ||||||
| 	colour_cycle_numerator_ = colour_cycle_numerator * time_multiplier_; | 	colour_cycle_numerator_ = colour_cycle_numerator; | ||||||
| 	phase_alternates_ = should_alternate; | 	phase_alternates_ = should_alternate; | ||||||
| 	is_alernate_line_ &= phase_alternates_; | 	is_alernate_line_ &= phase_alternates_; | ||||||
| 	unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_; | 	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_output_position_x2()	(*(uint16_t *)&next_run[SourceVertexOffsetOfEnds + 2]) | ||||||
| #define source_phase()				next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0] | #define source_phase()				next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0] | ||||||
| #define source_amplitude()			next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 2] | #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) | 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_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position(); | ||||||
| 			source_phase() = colour_burst_phase_; | 			source_phase() = colour_burst_phase_; | ||||||
| 			source_amplitude() = colour_burst_amplitude_; | 			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 | 		// 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_++; | 				frames_since_last_delegate_call_++; | ||||||
| 				if(frames_since_last_delegate_call_ == 20) | 				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()); | 					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; | 					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) | 		if(horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) | ||||||
| 		{ | 		{ | ||||||
| 			colour_burst_time_ = (uint16_t)horizontal_flywheel_->get_current_time(); | 			unsigned int position_phase = (horizontal_flywheel_->get_current_time() * colour_cycle_numerator_ * 256) / phase_denominator_; | ||||||
| 			colour_burst_phase_ = scan->phase; | 			colour_burst_phase_ = (position_phase + scan->phase) & 255; | ||||||
| 			colour_burst_amplitude_ = scan->amplitude; | 			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 { | 	Scan scan { | ||||||
| 		.type = Scan::Type::ColourBurst, | 		.type = Scan::Type::ColourBurst, | ||||||
| 		.number_of_cycles = number_of_cycles, | 		.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 | 		.amplitude = 32 | ||||||
| 	}; | 	}; | ||||||
| 	output_scan(&scan); | 	output_scan(&scan); | ||||||
|   | |||||||
| @@ -60,7 +60,6 @@ class CRT { | |||||||
| 		void output_scan(const Scan *scan); | 		void output_scan(const Scan *scan); | ||||||
|  |  | ||||||
| 		uint8_t colour_burst_phase_, colour_burst_amplitude_; | 		uint8_t colour_burst_phase_, colour_burst_amplitude_; | ||||||
| 		uint16_t colour_burst_time_; |  | ||||||
| 		bool is_writing_composite_run_; | 		bool is_writing_composite_run_; | ||||||
|  |  | ||||||
| 		unsigned int phase_denominator_, phase_numerator_, colour_cycle_numerator_; | 		unsigned int phase_denominator_, phase_numerator_, colour_cycle_numerator_; | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ const GLsizei InputBufferBuilderWidth = 2048; | |||||||
| const GLsizei InputBufferBuilderHeight = 512; | const GLsizei InputBufferBuilderHeight = 512; | ||||||
|  |  | ||||||
| // This is the size of the intermediate buffers used during composite to RGB conversion | // 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; | const GLsizei IntermediateBufferHeight = 512; | ||||||
|  |  | ||||||
| // Some internal buffer sizes | // Some internal buffer sizes | ||||||
|   | |||||||
| @@ -6,8 +6,10 @@ | |||||||
| // | // | ||||||
|  |  | ||||||
| #include "CRT.hpp" | #include "CRT.hpp" | ||||||
| #include <stdlib.h> |  | ||||||
| #include <math.h> | #include <cstdlib> | ||||||
|  | #include <cstring> | ||||||
|  | #include <cmath> | ||||||
|  |  | ||||||
| #include "CRTOpenGL.hpp" | #include "CRTOpenGL.hpp" | ||||||
| #include "../../../SignalProcessing/FIRFilter.hpp" | #include "../../../SignalProcessing/FIRFilter.hpp" | ||||||
| @@ -16,12 +18,14 @@ | |||||||
| using namespace Outputs::CRT; | using namespace Outputs::CRT; | ||||||
|  |  | ||||||
| namespace { | namespace { | ||||||
| 	static const GLenum composite_texture_unit			= GL_TEXTURE0; | 	static const GLenum source_data_texture_unit		= GL_TEXTURE0; | ||||||
| 	static const GLenum separated_texture_unit			= GL_TEXTURE1; | 	static const GLenum pixel_accumulation_texture_unit	= GL_TEXTURE1; | ||||||
| 	static const GLenum filtered_y_texture_unit			= GL_TEXTURE2; |  | ||||||
| 	static const GLenum filtered_texture_unit			= GL_TEXTURE3; | 	static const GLenum composite_texture_unit			= GL_TEXTURE2; | ||||||
| 	static const GLenum source_data_texture_unit		= GL_TEXTURE4; | 	static const GLenum separated_texture_unit			= GL_TEXTURE3; | ||||||
| 	static const GLenum pixel_accumulation_texture_unit	= GL_TEXTURE5; | 	static const GLenum filtered_texture_unit			= GL_TEXTURE4; | ||||||
|  |  | ||||||
|  | 	static const GLenum work_texture_unit				= GL_TEXTURE2; | ||||||
| } | } | ||||||
|  |  | ||||||
| OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) : | OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) : | ||||||
| @@ -33,11 +37,7 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) : | |||||||
| 	last_output_height_(0), | 	last_output_height_(0), | ||||||
| 	fence_(nullptr), | 	fence_(nullptr), | ||||||
| 	texture_builder(bytes_per_pixel, source_data_texture_unit), | 	texture_builder(bytes_per_pixel, source_data_texture_unit), | ||||||
| 	array_builder(SourceVertexBufferDataSize, OutputVertexBufferDataSize), | 	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) |  | ||||||
| { | { | ||||||
| 	glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); | 	glBlendFunc(GL_SRC_ALPHA, GL_CONSTANT_COLOR); | ||||||
| 	glBlendColor(0.6f, 0.6f, 0.6f, 1.0f); | 	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 | 	// create the source vertex array | ||||||
| 	glGenVertexArrays(1, &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() | OpenGLOutputBuilder::~OpenGLOutputBuilder() | ||||||
| @@ -57,6 +83,11 @@ OpenGLOutputBuilder::~OpenGLOutputBuilder() | |||||||
| 	free(rgb_shader_); | 	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) | void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) | ||||||
| { | { | ||||||
| 	// lock down any other draw_frames | 	// 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 | 	// make sure there's a target to draw to | ||||||
| 	if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width) | 	if(!framebuffer_ || framebuffer_->get_height() != output_height || framebuffer_->get_width() != output_width) | ||||||
| 	{ | 	{ | ||||||
| 		std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit)); | 		std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(new OpenGL::TextureTarget((GLsizei)output_width, (GLsizei)output_height, pixel_accumulation_texture_unit, GL_LINEAR)); | ||||||
| 		if(framebuffer_) | 		if(framebuffer_) | ||||||
| 		{ | 		{ | ||||||
| 			new_framebuffer->bind_framebuffer(); | 			new_framebuffer->bind_framebuffer(); | ||||||
| @@ -123,30 +154,29 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out | |||||||
| 	output_mutex_.unlock(); | 	output_mutex_.unlock(); | ||||||
|  |  | ||||||
| 	struct RenderStage { | 	struct RenderStage { | ||||||
| 		OpenGL::TextureTarget *const target; |  | ||||||
| 		OpenGL::Shader *const shader; | 		OpenGL::Shader *const shader; | ||||||
|  | 		OpenGL::TextureTarget *const target; | ||||||
| 		float clear_colour[3]; | 		float clear_colour[3]; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// for composite video, go through four steps to get to something that can be painted to the output | 	// for composite video, go through four steps to get to something that can be painted to the output | ||||||
| 	RenderStage composite_render_stages[] = | 	RenderStage composite_render_stages[] = | ||||||
| 	{ | 	{ | ||||||
| 		{&composite_texture_,	composite_input_shader_program_.get(),				{0.0, 0.0, 0.0}}, | 		{composite_input_shader_program_.get(),					composite_texture_.get(),		{0.0, 0.0, 0.0}}, | ||||||
| 		{&separated_texture_,	composite_separation_filter_program_.get(),			{0.0, 0.5, 0.5}}, | 		{composite_separation_filter_program_.get(),			separated_texture_.get(),		{0.0, 0.5, 0.5}}, | ||||||
| 		{&filtered_y_texture_,	composite_y_filter_shader_program_.get(),			{0.0, 0.5, 0.5}}, | 		{composite_chrominance_filter_shader_program_.get(),	filtered_texture_.get(),		{0.0, 0.0, 0.0}}, | ||||||
| 		{&filtered_texture_,	composite_chrominance_filter_shader_program_.get(),	{0.0, 0.0, 0.0}}, |  | ||||||
| 		{nullptr} | 		{nullptr} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// for RGB video, there's only two steps | 	// for RGB video, there's only two steps | ||||||
| 	RenderStage rgb_render_stages[] = | 	RenderStage rgb_render_stages[] = | ||||||
| 	{ | 	{ | ||||||
| 		{&composite_texture_,	rgb_input_shader_program_.get(),	{0.0, 0.0, 0.0}}, | 		{rgb_input_shader_program_.get(),	composite_texture_.get(),	{0.0, 0.0, 0.0}}, | ||||||
| 		{&filtered_texture_,	rgb_filter_shader_program_.get(),	{0.0, 0.0, 0.0}}, | 		{rgb_filter_shader_program_.get(),	filtered_texture_.get(),	{0.0, 0.0, 0.0}}, | ||||||
| 		{nullptr} | 		{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) | 	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_); | 		glBindVertexArray(source_vertex_array_); | ||||||
| 		glDisable(GL_BLEND); | 		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 | 			// switch to the framebuffer and shader associated with this stage | ||||||
| 			active_pipeline->shader->bind(); | 			active_pipeline->shader->bind(); | ||||||
|  |  | ||||||
|  | 			if(!work_texture_) | ||||||
|  | 			{ | ||||||
| 				active_pipeline->target->bind_framebuffer(); | 				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 | 				// 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 | 				// 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); | 					glClearColor(active_pipeline->clear_colour[0], active_pipeline->clear_colour[1], active_pipeline->clear_colour[2], 1.0f); | ||||||
| 					glClear(GL_COLOR_BUFFER_BIT); | 					glClear(GL_COLOR_BUFFER_BIT); | ||||||
|  | //				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// draw | 			// 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++; | 			active_pipeline++; | ||||||
|  | #ifdef GL_NV_texture_barrier | ||||||
|  | 			glTextureBarrierNV(); | ||||||
|  | #endif | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// prepare to transfer to framebuffer | 		// 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); | 		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 | 	// copy framebuffer to the intended place | ||||||
| 	glDisable(GL_BLEND); | 	glDisable(GL_BLEND); | ||||||
| 	glBindFramebuffer(GL_FRAMEBUFFER, 0); | 	glBindFramebuffer(GL_FRAMEBUFFER, 0); | ||||||
| @@ -211,7 +260,6 @@ void OpenGLOutputBuilder::reset_all_OpenGL_state() | |||||||
| { | { | ||||||
| 	composite_input_shader_program_ = nullptr; | 	composite_input_shader_program_ = nullptr; | ||||||
| 	composite_separation_filter_program_ = nullptr; | 	composite_separation_filter_program_ = nullptr; | ||||||
| 	composite_y_filter_shader_program_ = nullptr; |  | ||||||
| 	composite_chrominance_filter_shader_program_ = nullptr; | 	composite_chrominance_filter_shader_program_ = nullptr; | ||||||
| 	rgb_input_shader_program_ = nullptr; | 	rgb_input_shader_program_ = nullptr; | ||||||
| 	rgb_filter_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) | 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); | 	composite_shader_ = strdup(shader); | ||||||
| 	reset_all_OpenGL_state(); | 	reset_all_OpenGL_state(); | ||||||
| 	output_mutex_.unlock(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader) | 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); | 	rgb_shader_ = strdup(shader); | ||||||
| 	reset_all_OpenGL_state(); | 	reset_all_OpenGL_state(); | ||||||
| 	output_mutex_.unlock(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #pragma mark - Program compilation | #pragma mark - Program compilation | ||||||
| @@ -252,16 +298,25 @@ void OpenGLOutputBuilder::prepare_composite_input_shaders() | |||||||
| 	composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); | 	composite_input_shader_program_->set_output_size(IntermediateBufferWidth, IntermediateBufferHeight); | ||||||
|  |  | ||||||
| 	composite_separation_filter_program_ = OpenGL::IntermediateShader::make_chroma_luma_separation_shader(); | 	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_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_ = 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); | 	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() | void OpenGLOutputBuilder::prepare_rgb_input_shaders() | ||||||
| @@ -295,7 +350,9 @@ void OpenGLOutputBuilder::prepare_source_vertex_array() | |||||||
| void OpenGLOutputBuilder::prepare_output_shader() | void OpenGLOutputBuilder::prepare_output_shader() | ||||||
| { | { | ||||||
| 	output_shader_program_ = OpenGL::OutputShader::make_shader("", "texture(texID, srcCoordinatesVarying).rgb", false); | 	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() | void OpenGLOutputBuilder::prepare_output_vertex_array() | ||||||
| @@ -319,6 +376,7 @@ void OpenGLOutputBuilder::set_output_device(OutputDevice output_device) | |||||||
| 		composite_src_output_y_ = 0; | 		composite_src_output_y_ = 0; | ||||||
| 		last_output_width_ = 0; | 		last_output_width_ = 0; | ||||||
| 		last_output_height_ = 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_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); | 	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() | void OpenGLOutputBuilder::set_timing_uniforms() | ||||||
| { | { | ||||||
| 	OpenGL::IntermediateShader *intermediate_shaders[] = { | 	const float colour_subcarrier_frequency = (float)colour_cycle_numerator_ / (float)colour_cycle_denominator_; | ||||||
| 		composite_input_shader_program_.get(), | 	const float output_width = get_composite_output_width(); | ||||||
| 		composite_separation_filter_program_.get(), | 	const float sample_cycles_per_line = cycles_per_line_ / output_width; | ||||||
| 		composite_y_filter_shader_program_.get(), |  | ||||||
| 		composite_chrominance_filter_shader_program_.get() | 	if(composite_separation_filter_program_) | ||||||
| 	}; |  | ||||||
| 	bool extends = false; |  | ||||||
| 	float phaseCyclesPerTick = (float)colour_cycle_numerator_ / (float)(colour_cycle_denominator_ * cycles_per_line_); |  | ||||||
| 	for(int c = 0; c < 3; c++) |  | ||||||
| 	{ | 	{ | ||||||
| 		if(intermediate_shaders[c]) intermediate_shaders[c]->set_phase_cycles_per_sample(phaseCyclesPerTick, extends); | 		composite_separation_filter_program_->set_width_scalers(output_width, output_width); | ||||||
| 		extends = true; | 		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); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -66,13 +66,19 @@ class OpenGLOutputBuilder { | |||||||
| 		GLsizei composite_src_output_y_; | 		GLsizei composite_src_output_y_; | ||||||
|  |  | ||||||
| 		std::unique_ptr<OpenGL::OutputShader> output_shader_program_; | 		std::unique_ptr<OpenGL::OutputShader> output_shader_program_; | ||||||
| 		std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_, composite_separation_filter_program_, composite_y_filter_shader_program_, composite_chrominance_filter_shader_program_; |  | ||||||
| 		std::unique_ptr<OpenGL::IntermediateShader> rgb_input_shader_program_, rgb_filter_shader_program_; |  | ||||||
|  |  | ||||||
| 		OpenGL::TextureTarget composite_texture_;	// receives raw composite levels | 		std::unique_ptr<OpenGL::IntermediateShader> composite_input_shader_program_; | ||||||
| 		OpenGL::TextureTarget separated_texture_;	// receives unfiltered Y in the R channel plus unfiltered but demodulated chrominance in G and B | 		std::unique_ptr<OpenGL::IntermediateShader> composite_separation_filter_program_; | ||||||
| 		OpenGL::TextureTarget filtered_y_texture_;	// receives filtered Y in the R channel plus unfiltered chrominance in G and B | 		std::unique_ptr<OpenGL::IntermediateShader> composite_chrominance_filter_shader_program_; | ||||||
| 		OpenGL::TextureTarget filtered_texture_;	// receives filtered YIQ or YUV |  | ||||||
|  | 		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 | 		std::unique_ptr<OpenGL::TextureTarget> framebuffer_;		// the current pixel output | ||||||
|  |  | ||||||
| @@ -88,6 +94,9 @@ class OpenGLOutputBuilder { | |||||||
| 		void reset_all_OpenGL_state(); | 		void reset_all_OpenGL_state(); | ||||||
|  |  | ||||||
| 		GLsync fence_; | 		GLsync fence_; | ||||||
|  | 		float get_composite_output_width() const; | ||||||
|  | 		void set_output_shader_width(); | ||||||
|  | 		bool get_is_television_output(); | ||||||
|  |  | ||||||
| 	public: | 	public: | ||||||
| 		// These two are protected by output_mutex_. | 		// These two are protected by output_mutex_. | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ | |||||||
| 	#else | 	#else | ||||||
| 		#include <OpenGL/OpenGL.h> | 		#include <OpenGL/OpenGL.h> | ||||||
| 		#include <OpenGL/gl3.h> | 		#include <OpenGL/gl3.h> | ||||||
|  | 		#include <OpenGL/gl3ext.h> | ||||||
| 	#endif | 	#endif | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,11 +40,14 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char * | |||||||
| 		"in vec2 ends;" | 		"in vec2 ends;" | ||||||
| 		"in vec3 phaseTimeAndAmplitude;" | 		"in vec3 phaseTimeAndAmplitude;" | ||||||
|  |  | ||||||
| 		"uniform float phaseCyclesPerTick;" |  | ||||||
| 		"uniform ivec2 outputTextureSize;" | 		"uniform ivec2 outputTextureSize;" | ||||||
| 		"uniform float extension;" | 		"uniform float extension;" | ||||||
| 		"uniform %s texID;" | 		"uniform %s texID;" | ||||||
| 		"uniform float offsets[5];" | 		"uniform float offsets[5];" | ||||||
|  | 		"uniform vec2 widthScalers;" | ||||||
|  | 		"uniform float inputVerticalOffset;" | ||||||
|  | 		"uniform float outputVerticalOffset;" | ||||||
|  | 		"uniform float textureHeightDivisor;" | ||||||
|  |  | ||||||
| 		"out vec2 phaseAndAmplitudeVarying;" | 		"out vec2 phaseAndAmplitudeVarying;" | ||||||
| 		"out vec2 inputPositionsVarying[11];" | 		"out vec2 inputPositionsVarying[11];" | ||||||
| @@ -53,35 +56,51 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_shader(const char * | |||||||
|  |  | ||||||
| 		"void main(void)" | 		"void main(void)" | ||||||
| 		"{" | 		"{" | ||||||
|  | 			// odd vertices are on the left, even on the right | ||||||
| 			"float extent = float(gl_VertexID & 1);" | 			"float extent = float(gl_VertexID & 1);" | ||||||
|  | 			"float longitudinal = float((gl_VertexID & 2) >> 1);" | ||||||
|  |  | ||||||
| 			"vec2 inputPosition = vec2(mix(inputStart.x, ends.x, extent), inputStart.y);" | 			// inputPosition.x is either inputStart.x or ends.x, depending on whether it is on the left or the right; | ||||||
| 			"vec2 outputPosition = vec2(mix(outputStart.x, ends.y, extent), outputStart.y);" | 			// 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);" | 			"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 extendedInputPosition = %s + extensionVector;" | ||||||
| 			"vec2 extendedOutputPosition = outputPosition + extensionVector;" | 			"vec2 extendedOutputPosition = outputPosition + extensionVector;" | ||||||
|  |  | ||||||
|  | 			// keep iInputPositionVarying in whole source pixels, scale mappedInputPosition to the ordinary normalised range | ||||||
| 			"vec2 textureSize = vec2(textureSize(texID, 0));" | 			"vec2 textureSize = vec2(textureSize(texID, 0));" | ||||||
| 			"iInputPositionVarying = extendedInputPosition;" | 			"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);" | 			// setup input positions spaced as per the supplied offsets; these are for filtering where required | ||||||
| 			"inputPositionsVarying[1] = mappedInputPosition - (vec2(offsets[1], 0.0) / textureSize);" | 			"inputPositionsVarying[0] = mappedInputPosition - (vec2(5.0, 0.0) / textureSize);" | ||||||
| 			"inputPositionsVarying[2] = mappedInputPosition - (vec2(offsets[2], 0.0) / textureSize);" | 			"inputPositionsVarying[1] = mappedInputPosition - (vec2(4.0, 0.0) / textureSize);" | ||||||
| 			"inputPositionsVarying[3] = mappedInputPosition - (vec2(offsets[3], 0.0) / textureSize);" | 			"inputPositionsVarying[2] = mappedInputPosition - (vec2(3.0, 0.0) / textureSize);" | ||||||
| 			"inputPositionsVarying[4] = mappedInputPosition - (vec2(offsets[4], 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[5] = mappedInputPosition;" | ||||||
| 			"inputPositionsVarying[6] = mappedInputPosition + (vec2(offsets[4], 0.0) / textureSize);" | 			"inputPositionsVarying[6] = mappedInputPosition + (vec2(1.0, 0.0) / textureSize);" | ||||||
| 			"inputPositionsVarying[7] = mappedInputPosition + (vec2(offsets[3], 0.0) / textureSize);" | 			"inputPositionsVarying[7] = mappedInputPosition + (vec2(2.0, 0.0) / textureSize);" | ||||||
| 			"inputPositionsVarying[8] = mappedInputPosition + (vec2(offsets[2], 0.0) / textureSize);" | 			"inputPositionsVarying[8] = mappedInputPosition + (vec2(3.0, 0.0) / textureSize);" | ||||||
| 			"inputPositionsVarying[9] = mappedInputPosition + (vec2(offsets[1], 0.0) / textureSize);" | 			"inputPositionsVarying[9] = mappedInputPosition + (vec2(4.0, 0.0) / textureSize);" | ||||||
| 			"inputPositionsVarying[10] = mappedInputPosition + (vec2(offsets[0], 0.0) / textureSize);" | 			"inputPositionsVarying[10] = mappedInputPosition + (vec2(5.0, 0.0) / textureSize);" | ||||||
| 			"delayLinePositionVarying = mappedInputPosition - vec2(0.0, 1.0);" | 			"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) | 			"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;" | 			"vec2 eyePosition = 2.0*(extendedOutputPosition / outputTextureSize) - vec2(1.0) + vec2(1.0)/outputTextureSize;" | ||||||
| 			"gl_Position = vec4(eyePosition, 0.0, 1.0);" | 			"gl_Position = vec4(eyePosition, 0.0, 1.0);" | ||||||
| 		"}", sampler_type, input_variable); | 		"}", sampler_type, input_variable); | ||||||
| @@ -172,7 +191,6 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat | |||||||
|  |  | ||||||
| 		"in vec2 phaseAndAmplitudeVarying;" | 		"in vec2 phaseAndAmplitudeVarying;" | ||||||
| 		"in vec2 inputPositionsVarying[11];" | 		"in vec2 inputPositionsVarying[11];" | ||||||
| 		"uniform vec4 weights[3];" |  | ||||||
|  |  | ||||||
| 		"out vec3 fragColour;" | 		"out vec3 fragColour;" | ||||||
|  |  | ||||||
| @@ -180,37 +198,20 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_luma_separat | |||||||
|  |  | ||||||
| 		"void main(void)" | 		"void main(void)" | ||||||
| 		"{" | 		"{" | ||||||
| 			"vec4 samples[3] = vec4[](" | 			"vec4 samples = vec4(" | ||||||
| 				"vec4(" | 				"texture(texID, inputPositionsVarying[3]).r," | ||||||
| 					"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[4]).r," | ||||||
| 				"texture(texID, inputPositionsVarying[5]).r," | 				"texture(texID, inputPositionsVarying[5]).r," | ||||||
| 					"texture(texID, inputPositionsVarying[6]).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" |  | ||||||
| 				")" |  | ||||||
| 			");" | 			");" | ||||||
|  | 			"float luminance = dot(samples, vec4(0.25));" | ||||||
|  |  | ||||||
| 			"float luminance = " | 			// define chroma to be whatever was here, minus luma | ||||||
| 				"dot(vec3(" | 			"float chrominance = 0.5 * (samples.z - luminance) / phaseAndAmplitudeVarying.y;" | ||||||
| 					"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;" |  | ||||||
| 			"luminance /= (1.0 - 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));" | 			"vec2 quadrature = vec2(cos(phaseAndAmplitudeVarying.x), -sin(phaseAndAmplitudeVarying.x));" | ||||||
| 			"fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));" | 			"fragColour = vec3(luminance, vec2(0.5) + (chrominance * quadrature));" | ||||||
| 		"}",false, false); | 		"}",false, false); | ||||||
| @@ -232,41 +233,18 @@ std::unique_ptr<IntermediateShader> IntermediateShader::make_chroma_filter_shade | |||||||
| 		"void main(void)" | 		"void main(void)" | ||||||
| 		"{" | 		"{" | ||||||
| 			"vec3 samples[] = vec3[](" | 			"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[3]).rgb," | ||||||
| 				"texture(texID, inputPositionsVarying[4]).rgb," | 				"texture(texID, inputPositionsVarying[4]).rgb," | ||||||
| 				"texture(texID, inputPositionsVarying[5]).rgb," | 				"texture(texID, inputPositionsVarying[5]).rgb," | ||||||
| 				"texture(texID, inputPositionsVarying[6]).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 chromaChannel1[] = vec4[](" | 			"vec4 chromaChannel1 = vec4(samples[0].g, samples[1].g, samples[2].g, samples[3].g);" | ||||||
| 				"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);" | ||||||
| 				"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)" |  | ||||||
| 			");" |  | ||||||
|  |  | ||||||
| 			"vec3 lumaChromaColour = vec3(samples[5].r," | 			"vec3 lumaChromaColour = vec3(samples[2].r," | ||||||
| 				"dot(vec3(" | 				"dot(chromaChannel1, vec4(0.25))," | ||||||
| 					"dot(chromaChannel1[0], weights[0])," | 				"dot(chromaChannel2, vec4(0.25))" | ||||||
| 					"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 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);" | 			"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); | 		"}", 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() | std::unique_ptr<IntermediateShader> IntermediateShader::make_rgb_filter_shader() | ||||||
| { | { | ||||||
| 	return make_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. | 	// Perform a linear search for the highest number of taps we can use with 11 samples. | ||||||
| 	GLfloat weights[12]; | 	GLfloat weights[12]; | ||||||
| 	GLfloat offsets[5]; | 	GLfloat offsets[5]; | ||||||
| 	unsigned int taps = 21; | 	unsigned int taps = 11; | ||||||
|  | //	unsigned int taps = 21; | ||||||
| 	while(1) | 	while(1) | ||||||
| 	{ | 	{ | ||||||
| 		float coefficients[21]; | 		float coefficients[21]; | ||||||
| 		SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation); | 		SignalProcessing::FIRFilter luminance_filter(taps, sampling_rate, 0.0f, cutoff_frequency, SignalProcessing::FIRFilter::DefaultAttenuation); | ||||||
| 		luminance_filter.get_coefficients(coefficients); | 		luminance_filter.get_coefficients(coefficients); | ||||||
|  |  | ||||||
| 		int sample = 0; | //		int sample = 0; | ||||||
| 		int c = 0; | //		int c = 0; | ||||||
| 		memset(weights, 0, sizeof(float)*12); | 		memset(weights, 0, sizeof(float)*12); | ||||||
| 		memset(offsets, 0, sizeof(float)*5); | 		memset(offsets, 0, sizeof(float)*5); | ||||||
|  |  | ||||||
| 		int halfSize = (taps >> 1); | 		int halfSize = (taps >> 1); | ||||||
| 		while(c < halfSize && sample < 5) | 		for(int c = 0; c < taps; c++) | ||||||
| 		{ | 		{ | ||||||
| 			offsets[sample] = (float)(halfSize - c); | 			if(c < 5) offsets[c] = (halfSize - c); | ||||||
| 			if((coefficients[c] < 0.0f) == (coefficients[c+1] < 0.0f) && c+1 < (taps >> 1)) | 			weights[c] = coefficients[c]; | ||||||
| 			{ |  | ||||||
| 				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; | 		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; | 		taps -= 2; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -454,10 +395,9 @@ void IntermediateShader::set_separation_frequency(float sampling_rate, float col | |||||||
| 	set_filter_coefficients(sampling_rate, colour_burst_frequency); | 	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", extension); | ||||||
| 	set_uniform("extension", extend_runs_to_full_cycle ? ceilf(1.0f / phase_cycles_per_sample) : 0.0f); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void IntermediateShader::set_colour_conversion_matrices(float *fromRGB, float *toRGB) | 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("lumaChromaToRGB", 3, false, toRGB); | ||||||
| 	set_uniform_matrix("rgbToLumaChroma", 3, false, fromRGB); | 	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); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -44,11 +44,6 @@ public: | |||||||
| 	*/ | 	*/ | ||||||
| 	static std::unique_ptr<IntermediateShader> make_chroma_filter_shader(); | 	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. | 		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, | 		geometry should be extended so that a complete colour cycle is included at both the beginning and end, | ||||||
| 		to occur upon the next `bind`. | 		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`. | 		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); | 	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: | private: | ||||||
| 	static std::unique_ptr<IntermediateShader> make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition); | 	static std::unique_ptr<IntermediateShader> make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -38,6 +38,8 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met | |||||||
| 		"uniform vec2 positionConversion;" | 		"uniform vec2 positionConversion;" | ||||||
| 		"uniform vec2 scanNormal;" | 		"uniform vec2 scanNormal;" | ||||||
| 		"uniform %s texID;" | 		"uniform %s texID;" | ||||||
|  | 		"uniform float inputScaler;" | ||||||
|  | 		"uniform int textureHeightDivisor;" | ||||||
|  |  | ||||||
| 		"out float lateralVarying;" | 		"out float lateralVarying;" | ||||||
| 		"out vec2 srcCoordinatesVarying;" | 		"out vec2 srcCoordinatesVarying;" | ||||||
| @@ -52,9 +54,10 @@ std::unique_ptr<OutputShader> OutputShader::make_shader(const char *fragment_met | |||||||
| 			"lateralVarying = lateral - 0.5;" | 			"lateralVarying = lateral - 0.5;" | ||||||
|  |  | ||||||
| 			"vec2 vSrcCoordinates = vec2(x, vertical.y);" | 			"vec2 vSrcCoordinates = vec2(x, vertical.y);" | ||||||
| 			"ivec2 textureSize = textureSize(texID, 0);" | 			"ivec2 textureSize = textureSize(texID, 0) * ivec2(1, textureHeightDivisor);" | ||||||
| 			"iSrcCoordinatesVarying = vSrcCoordinates;" | 			"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 vPosition = vec2(x, vertical.x);" | ||||||
| 			"vec2 floatingPosition = (vPosition / positionConversion) + lateral * scanNormal;" | 			"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("scanNormal", scan_normal[0], scan_normal[1]); | ||||||
| 	set_uniform("positionConversion", (GLfloat)horizontal_scan_period, (GLfloat)vertical_scan_period / (GLfloat)vertical_period_divider); | 	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); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -58,6 +58,16 @@ public: | |||||||
| 		to occur upon the next `bind`. | 		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_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); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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) | void Shader::set_uniform(const std::string &name, GLfloat value1, GLfloat value2) | ||||||
| { | { | ||||||
| 	enqueue_function([name, value1, value2, this] { | 	enqueue_function([name, value1, value2, this] { | ||||||
| 		glUniform2f(location(), value1, value2); | 		GLint location = location(); | ||||||
|  | 		glUniform2f(location, value1, value2); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ | |||||||
|  |  | ||||||
| using namespace OpenGL; | 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), | 	_width(width), | ||||||
| 	_height(height), | 	_height(height), | ||||||
| 	_pixel_shader(nullptr), | 	_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); | 	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); | 	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)_expanded_width, (GLsizei)_expanded_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, blank_buffer); | ||||||
| 	free(blank_buffer); | 	free(blank_buffer); | ||||||
| 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); | ||||||
| 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||||
|  |  | ||||||
| 	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); | 	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ class TextureTarget { | |||||||
| 			@param height The height of target to create. | 			@param height The height of target to create. | ||||||
| 			@param texture_unit A texture unit on which to bind the texture. | 			@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(); | 		~TextureTarget(); | ||||||
|  |  | ||||||
| 		/*! | 		/*! | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -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. | 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 | | ||||||
|  | |---|---| | ||||||
|  | ||| | ||||||
|  | ||| | ||||||
|  | ||| | ||||||
|  |  | ||||||
|  | <img src="READMEImages/ReptonInterlaced.gif" height=600 alt="Repton title screen, interlaced"> | ||||||
|  |  | ||||||
| ## Low Latency | ## 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. | 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. | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								READMEImages/CompositeElectron.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/CompositeRepton3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 136 KiB | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/CompositeStormlord.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 271 KiB | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/NaiveElectron.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 309 B | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/NaiveRepton3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 65 KiB | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/NaiveStormlord.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 82 KiB | 
							
								
								
									
										
											BIN
										
									
								
								READMEImages/ReptonInterlaced.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.3 MiB | 
| @@ -69,7 +69,7 @@ void StaticAnalyser::Acorn::AddTargets( | |||||||
| 	target.probability = 1.0; // TODO: a proper estimation | 	target.probability = 1.0; // TODO: a proper estimation | ||||||
| 	target.acorn.has_dfs = false; | 	target.acorn.has_dfs = false; | ||||||
| 	target.acorn.has_adfs = false; | 	target.acorn.has_adfs = false; | ||||||
| 	target.acorn.should_hold_shift = false; | 	target.acorn.should_shift_restart = false; | ||||||
|  |  | ||||||
| 	// strip out inappropriate cartridges | 	// strip out inappropriate cartridges | ||||||
| 	target.cartridges = AcornCartridgesFrom(cartridges); | 	target.cartridges = AcornCartridgesFrom(cartridges); | ||||||
| @@ -126,20 +126,11 @@ void StaticAnalyser::Acorn::AddTargets( | |||||||
| 			target.acorn.has_dfs = !!dfs_catalogue; | 			target.acorn.has_dfs = !!dfs_catalogue; | ||||||
| 			target.acorn.has_adfs = !!adfs_catalogue; | 			target.acorn.has_adfs = !!adfs_catalogue; | ||||||
|  |  | ||||||
| 			std::string adfs_command; |  | ||||||
| 			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; | 			Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; | ||||||
| 			switch(bootOption) | 			if(bootOption != Catalogue::BootOption::None) | ||||||
| 			{ | 				target.acorn.should_shift_restart = true; | ||||||
| 				case Catalogue::BootOption::None:		adfs_command = "*CAT\n";		break; | 			else | ||||||
| 				case Catalogue::BootOption::LoadBOOT:	adfs_command = "*LOAD !BOOT\n";	break; | 				target.loadingCommand = "*CAT\n"; | ||||||
| 				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; |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ struct Target { | |||||||
| 		struct { | 		struct { | ||||||
| 			bool has_adfs; | 			bool has_adfs; | ||||||
| 			bool has_dfs; | 			bool has_dfs; | ||||||
| 			bool should_hold_shift; | 			bool should_shift_restart; | ||||||
| 		} acorn; | 		} acorn; | ||||||
|  |  | ||||||
| 		struct { | 		struct { | ||||||
|   | |||||||