From e7f4babf4161d6c373443674d4d6344d90b27672 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 17 Sep 2018 22:59:16 -0400 Subject: [PATCH 01/88] Starts taking steps towards SMS/GG and V9938/9958 support. Specifically: routine namespace stuff, plus the intention to move to a table-based operation+cost version of timing. Reordering works fine for the TMS, and probably would also for the SMS/GG, but it'd be problematic with the command engine of the V9938/9958 and maintaining a consistent set of code is easier. --- Components/9918/9918.cpp | 30 +++-- Components/9918/9918.hpp | 10 +- Components/9918/Implementation/9918Base.hpp | 122 +++++++++++++++++++- Machines/ColecoVision/ColecoVision.cpp | 4 +- Machines/MSX/MSX.cpp | 4 +- 5 files changed, 150 insertions(+), 20 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index c64bbac72..d5b1dbdb2 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -11,7 +11,7 @@ #include #include -using namespace TI; +using namespace TI::TMS; namespace { @@ -83,12 +83,28 @@ enum ScreenMode { } -TMS9918Base::TMS9918Base() : +Base::Base(Personality p) : // 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole // line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368. - crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) {} + crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) { -TMS9918::TMS9918(Personality p) { + switch(p) { + case TI::TMS::TMS9918A: + case TI::TMS::SMSVDP: + case TI::TMS::GGVDP: + ram_.resize(16 * 1024); + break; + case TI::TMS::V9938: + ram_.resize(128 * 1024); + break; + case TI::TMS::V9958: + ram_.resize(192 * 1024); + break; + } +} + +TMS9918::TMS9918(Personality p): + Base(p) { // Unimaginatively, this class just passes RGB through to the shader. Investigation is needed // into whether there's a more natural form. crt_->set_rgb_sampling_function( @@ -111,7 +127,7 @@ Outputs::CRT::CRT *TMS9918::get_crt() { return crt_.get(); } -void TMS9918Base::test_sprite(int sprite_number, int screen_row) { +void Base::test_sprite(int sprite_number, int screen_row) { if(!(status_ & StatusFifthSprite)) { status_ = static_cast((status_ & ~31) | sprite_number); } @@ -140,7 +156,7 @@ void TMS9918Base::test_sprite(int sprite_number, int screen_row) { sprite_sets_[active_sprite_set_].active_sprite_slot++; } -void TMS9918Base::get_sprite_contents(int field, int cycles_left, int screen_row) { +void Base::get_sprite_contents(int field, int cycles_left, int screen_row) { int sprite_id = field / 6; field %= 6; @@ -617,7 +633,7 @@ void TMS9918::run_for(const HalfCycles cycles) { } } -void TMS9918Base::output_border(int cycles) { +void Base::output_border(int cycles) { pixel_target_ = reinterpret_cast(crt_->allocate_write_area(1)); if(pixel_target_) *pixel_target_ = palette[background_colour_]; crt_->output_level(static_cast(cycles) * 4); diff --git a/Components/9918/9918.hpp b/Components/9918/9918.hpp index 4f2eff794..cf02cd074 100644 --- a/Components/9918/9918.hpp +++ b/Components/9918/9918.hpp @@ -17,6 +17,7 @@ #include namespace TI { +namespace TMS { /*! Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the @@ -29,12 +30,8 @@ namespace TI { These chips have only one non-on-demand interaction with the outside world: an interrupt line. See get_time_until_interrupt and get_interrupt_line for asynchronous operation options. */ -class TMS9918: public TMS9918Base { +class TMS9918: public Base { public: - enum Personality { - TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired. - }; - /*! Constructs an instance of the drive controller that behaves according to personality @c p. @param p The type of controller to emulate. @@ -81,6 +78,7 @@ class TMS9918: public TMS9918Base { bool get_interrupt_line(); }; -}; +} +} #endif /* TMS9918_hpp */ diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 862c7807c..220587cb6 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -16,14 +16,23 @@ #include namespace TI { +namespace TMS { -class TMS9918Base { +enum Personality { + TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired. + V9938, + V9958, + SMSVDP, + GGVDP, +}; + +class Base { protected: - TMS9918Base(); + Base(Personality p); std::unique_ptr crt_; - uint8_t ram_[16384]; + std::vector ram_; uint16_t ram_pointer_ = 0; uint8_t read_ahead_buffer_ = 0; @@ -94,8 +103,115 @@ class TMS9918Base { inline void test_sprite(int sprite_number, int screen_row); inline void get_sprite_contents(int start, int cycles, int screen_row); + + // Contains tables describing the memory access patterns and, implicitly, + // the timing of video generation. + enum Operation { + HSyncOn, + HSyncOff, + ColourBurstOn, + ColourBurstOff, + + /// A memory access slot that is available for an external read or write. + External, + + /// A refresh cycle; neither used for video fetching nor available for external use. + Refresh, + + /*! + Column N Name Table Read + [= 1 slot] + */ + NameTableRead, + + /*! + Column N Pattern Table Read + [= 1 slot] + */ + PatternTableRead, + + /*! + Y0, X0, N0, C0, Pattern 0 (1), Pattern 0 (2), + Y1, X1, N1, C1, Pattern 1 (1), Pattern 1 (2), + Y2, X2 + [= 14 slots] + */ + TMSSpriteFetch1, + + /*! + N2, C2, Pattern 2 (1), Pattern 2 (2), + Y3, X3, N3, C3, Pattern 3 (1), Pattern 3 (2), + [= 10 slots] + */ + TMSSpriteFetch2, + + /*! + Sprite N fetch, Sprite N+1 fetch [...] + */ + TMSSpriteYFetch, + + /*! + Colour N, Pattern N, + Name N+1, + Sprite N, + + Colour N+1, Pattern N+1, + Name N+2, + Sprite N+1, + + Colour N+2, Pattern N+2, + Name N+3, + Sprite N+2, + + Colour N+3, Pattern N+3, + Name N+4, + [= 15 slots] + */ + TMSBackgroundRenderBlock, + + /*! + Pattern N, + Name N+1 + */ + TMSPatternNameFetch, + + /*! + Sprite N X/Name Read + Sprite N+1 X/Name Read + Sprite N Tile read (1st word) + Sprite N Tile read (2nd word) + Sprite N+1 Tile Read (1st word) + Sprite N+1 Tile Read (2nd word) + [= 6 slots] + */ + SMSSpriteRenderBlock, + + /*! + Column N Tile Read (1st word) + Column N Tile Read (2nd word) + Column N+1 Name Table Read + Sprite (16+N*1.5) Y Read (Reads Y of 2 sprites) + Column N+1 Tile Read (1st word) + Column N+1 Tile Read (2nd word) + Column N+2 Name Table Read + Sprite (16+N*1.5+2) Y Read (Reads Y of 2 sprites) + Column N+2 Tile Read (1st word) + Column N+2 Tile Read (2nd word) + Column N+3 Name Table Read + Sprite (16+N*1.5+4) Y Read (Reads Y of 2 sprites) + Column N+3 Tile Read (1st word) + Column N+3 Tile Read (2nd word) + [= 14 slots] + */ + SMSBackgroundRenderBlock, + }; + struct Period { + Operation operation; + int duration; + }; }; +} } #endif /* TMS9918Base_hpp */ diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index fbcd3ebb6..8452dd124 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -161,7 +161,7 @@ class ConcreteMachine: } void setup_output(float aspect_ratio) override { - vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A)); + vdp_.reset(new TI::TMS::TMS9918(TI::TMS::TMS9918A)); get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); } @@ -358,7 +358,7 @@ class ConcreteMachine: } CPU::Z80::Processor z80_; - std::unique_ptr vdp_; + std::unique_ptr vdp_; Concurrency::DeferringAsyncTaskQueue audio_queue_; TI::SN76489 sn76489_; diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 6c166a08b..f26f7ce9a 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -219,7 +219,7 @@ class ConcreteMachine: } void setup_output(float aspect_ratio) override { - vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A)); + vdp_.reset(new TI::TMS::TMS9918(TI::TMS::TMS9918A)); } void close_output() override { @@ -683,7 +683,7 @@ class ConcreteMachine: }; CPU::Z80::Processor z80_; - std::unique_ptr vdp_; + std::unique_ptr vdp_; Intel::i8255::i8255 i8255_; Concurrency::DeferringAsyncTaskQueue audio_queue_; From 0d01346ad4381b767691027623024e165d6525f6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 20 Sep 2018 22:04:28 -0400 Subject: [PATCH 02/88] Advertises SMS support and goes as far as realising it needs to spawn a Master System. --- Analyser/Machines.hpp | 1 + Analyser/Static/Coleco/StaticAnalyser.hpp | 1 - Analyser/Static/Sega/StaticAnalyser.cpp | 19 +++++++++++ Analyser/Static/Sega/StaticAnalyser.hpp | 26 ++++++++++++++ Analyser/Static/StaticAnalyser.cpp | 3 ++ Machines/MasterSystem/MasterSystem.cpp | 9 +++++ Machines/MasterSystem/MasterSystem.hpp | 14 ++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 34 ++++++++++++++++++- OSBindings/Mac/Clock Signal/Info.plist | 22 ++++++++++++ Storage/TargetPlatforms.hpp | 9 ++--- 10 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 Analyser/Static/Sega/StaticAnalyser.cpp create mode 100644 Analyser/Static/Sega/StaticAnalyser.hpp create mode 100644 Machines/MasterSystem/MasterSystem.cpp create mode 100644 Machines/MasterSystem/MasterSystem.hpp diff --git a/Analyser/Machines.hpp b/Analyser/Machines.hpp index 679e8cb5d..fde24ec46 100644 --- a/Analyser/Machines.hpp +++ b/Analyser/Machines.hpp @@ -17,6 +17,7 @@ enum class Machine { Atari2600, ColecoVision, Electron, + MasterSystem, MSX, Oric, Vic20, diff --git a/Analyser/Static/Coleco/StaticAnalyser.hpp b/Analyser/Static/Coleco/StaticAnalyser.hpp index 77de5abf0..0101c8c71 100644 --- a/Analyser/Static/Coleco/StaticAnalyser.hpp +++ b/Analyser/Static/Coleco/StaticAnalyser.hpp @@ -23,5 +23,4 @@ TargetList GetTargets(const Media &media, const std::string &file_name, TargetPl } } - #endif /* StaticAnalyser_hpp */ diff --git a/Analyser/Static/Sega/StaticAnalyser.cpp b/Analyser/Static/Sega/StaticAnalyser.cpp new file mode 100644 index 000000000..0f8adbf68 --- /dev/null +++ b/Analyser/Static/Sega/StaticAnalyser.cpp @@ -0,0 +1,19 @@ +// +// StaticAnalyser.cpp +// Clock Signal +// +// Created by Thomas Harte on 20/09/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#include "StaticAnalyser.hpp" + +Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { + TargetList targets; + std::unique_ptr target(new Target); + target->machine = Machine::MasterSystem; + target->media.cartridges = media.cartridges; + if(!target->media.empty()) + targets.push_back(std::move(target)); + return targets; +} diff --git a/Analyser/Static/Sega/StaticAnalyser.hpp b/Analyser/Static/Sega/StaticAnalyser.hpp new file mode 100644 index 000000000..127e2cd8d --- /dev/null +++ b/Analyser/Static/Sega/StaticAnalyser.hpp @@ -0,0 +1,26 @@ +// +// StaticAnalyser.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/09/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef StaticAnalyser_Sega_StaticAnalyser_hpp +#define StaticAnalyser_Sega_StaticAnalyser_hpp + +#include "../StaticAnalyser.hpp" +#include "../../../Storage/TargetPlatforms.hpp" +#include + +namespace Analyser { +namespace Static { +namespace Sega { + +TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); + +} +} +} + +#endif /* StaticAnalyser_hpp */ diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index 5351ccecb..3a87db958 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -23,6 +23,7 @@ #include "DiskII/StaticAnalyser.hpp" #include "MSX/StaticAnalyser.hpp" #include "Oric/StaticAnalyser.hpp" +#include "Sega/StaticAnalyser.hpp" #include "ZX8081/StaticAnalyser.hpp" // Cartridges @@ -129,6 +130,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: result.cartridges, Cartridge::BinaryDump, TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM + Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS Format("ssd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // SSD Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) @@ -170,6 +172,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) { if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco); if(potential_platforms & TargetPlatform::Commodore) Append(Commodore); if(potential_platforms & TargetPlatform::DiskII) Append(DiskII); + if(potential_platforms & TargetPlatform::Sega) Append(Sega); if(potential_platforms & TargetPlatform::MSX) Append(MSX); if(potential_platforms & TargetPlatform::Oric) Append(Oric); if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081); diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp new file mode 100644 index 000000000..d9b93a5c3 --- /dev/null +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -0,0 +1,9 @@ +// +// MasterSystem.cpp +// Clock Signal +// +// Created by Thomas Harte on 20/09/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#include "MasterSystem.hpp" diff --git a/Machines/MasterSystem/MasterSystem.hpp b/Machines/MasterSystem/MasterSystem.hpp new file mode 100644 index 000000000..80eecc3f6 --- /dev/null +++ b/Machines/MasterSystem/MasterSystem.hpp @@ -0,0 +1,14 @@ +// +// MasterSystem.hpp +// Clock Signal +// +// Created by Thomas Harte on 20/09/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef MasterSystem_hpp +#define MasterSystem_hpp + +#include + +#endif /* MasterSystem_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index b52f8a493..8562422cf 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -242,6 +242,10 @@ 4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; }; 4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; }; 4B7BC7F61F58F7D200D1B1B4 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; }; + 4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; }; + 4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; }; + 4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; }; + 4B7F1898215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; }; 4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */; }; 4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */; }; 4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334831F5DA0360097E338 /* Z80Storage.cpp */; }; @@ -941,6 +945,10 @@ 4B7A90E42041097C008514A2 /* ColecoVision.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ColecoVision.cpp; sourceTree = ""; }; 4B7A90EB20410A85008514A2 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; + 4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = ""; }; + 4B7F188D2154825D00388727 /* MasterSystem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MasterSystem.hpp; sourceTree = ""; }; + 4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; + 4B7F1896215486A100388727 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BestEffortUpdater.cpp; path = ../../Concurrency/BestEffortUpdater.cpp; sourceTree = ""; }; 4B80ACFF1F85CACA00176895 /* BestEffortUpdater.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BestEffortUpdater.hpp; path = ../../Concurrency/BestEffortUpdater.hpp; sourceTree = ""; }; 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PartialMachineCycle.cpp; sourceTree = ""; }; @@ -2191,6 +2199,24 @@ path = Coleco; sourceTree = ""; }; + 4B7F188B2154825D00388727 /* MasterSystem */ = { + isa = PBXGroup; + children = ( + 4B7F188C2154825D00388727 /* MasterSystem.cpp */, + 4B7F188D2154825D00388727 /* MasterSystem.hpp */, + ); + path = MasterSystem; + sourceTree = ""; + }; + 4B7F1894215486A100388727 /* Sega */ = { + isa = PBXGroup; + children = ( + 4B7F1895215486A100388727 /* StaticAnalyser.hpp */, + 4B7F1896215486A100388727 /* StaticAnalyser.cpp */, + ); + path = Sega; + sourceTree = ""; + }; 4B8334881F5DB8470097E338 /* Implementation */ = { isa = PBXGroup; children = ( @@ -2292,6 +2318,7 @@ 4BD67DC8209BE4D600AB2146 /* DiskII */, 4B89450F201967B4007DE474 /* MSX */, 4B8944F6201967B4007DE474 /* Oric */, + 4B7F1894215486A100388727 /* Sega */, 4B894504201967B4007DE474 /* ZX8081 */, ); path = Static; @@ -2832,11 +2859,11 @@ isa = PBXGroup; children = ( 4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */, - 4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */, 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, 4BBB709C2020109C002FE009 /* DynamicMachine.hpp */, 4B7041271F92C26900735E45 /* JoystickMachine.hpp */, 4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */, + 4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */, 4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */, 4B38F3491F2EC12000D9235D /* AmstradCPC */, 4B15AA082082C799005E6C8D /* AppleII */, @@ -2844,6 +2871,7 @@ 4B7A90E22041097C008514A2 /* ColecoVision */, 4B4DC81D1D2C2425003C5BF8 /* Commodore */, 4B2E2D9E1C3A070900138695 /* Electron */, + 4B7F188B2154825D00388727 /* MasterSystem */, 4B79A4FC1FC8FF9800EEDAD5 /* MSX */, 4BCF1FA51DADC3E10039D2E7 /* Oric */, 4B2B3A461F9B8FA70062DABF /* Utility */, @@ -3628,6 +3656,7 @@ 4B055AD51FAE9B0B0060FFFF /* Video.cpp in Sources */, 4B055AE11FAE9B6F0060FFFF /* ArrayBuilder.cpp in Sources */, 4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */, + 4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */, 4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */, 4B894529201967B4007DE474 /* Disk.cpp in Sources */, 4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */, @@ -3641,6 +3670,7 @@ 4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4BC891AE20F6EAB300EDE5B3 /* Rectangle.cpp in Sources */, 4B894539201967B4007DE474 /* Tape.cpp in Sources */, + 4B7F1898215486A200388727 /* StaticAnalyser.cpp in Sources */, 4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */, 4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */, 4B055AD31FAE9B0B0060FFFF /* Microdisc.cpp in Sources */, @@ -3864,6 +3894,7 @@ 4B12C0ED1FCFA98D005BFD93 /* Keyboard.cpp in Sources */, 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */, 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, + 4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */, 4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* TextureBuilder.cpp in Sources */, 4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */, @@ -3895,6 +3926,7 @@ 4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */, 4B4518871F75E91A00926311 /* DigitalPhaseLockedLoop.cpp in Sources */, 4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */, + 4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */, 4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */, 4B3FCC40201EC24200960631 /* MultiMachine.cpp in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 0e4491399..ad27ed856 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -431,6 +431,28 @@ LSHandlerRank Owner + + CFBundleTypeExtensions + + sms + + CFBundleTypeOSTypes + + ???? + + CFBundleTypeIconFile + cartridge.png + CFBundleTypeName + Master System Cartridge + CFBundleTypeRole + Viewer + LSTypeIsPackage + + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument + LSHandlerRank + Owner + CFBundleTypeExtensions diff --git a/Storage/TargetPlatforms.hpp b/Storage/TargetPlatforms.hpp index e6718dbf4..37017e236 100644 --- a/Storage/TargetPlatforms.hpp +++ b/Storage/TargetPlatforms.hpp @@ -24,10 +24,11 @@ enum Type: IntType { ColecoVision = 1 << 9, Commodore = 1 << 10, DiskII = 1 << 11, - MSX = 1 << 12, - Oric = 1 << 13, - ZX80 = 1 << 14, - ZX81 = 1 << 15, + Sega = 1 << 12, + MSX = 1 << 13, + Oric = 1 << 14, + ZX80 = 1 << 15, + ZX81 = 1 << 16, Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB, ZX8081 = ZX80 | ZX81, From e511261b04b1231e71d90c8b4501cff857d493d0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 21 Sep 2018 22:13:07 -0400 Subject: [PATCH 03/88] Adds a Master System class, so that SMSs can end up somewhere. --- Machines/MasterSystem/MasterSystem.cpp | 109 +++++++++++++++++++++++++ Machines/MasterSystem/MasterSystem.hpp | 15 +++- Machines/Utility/MachineForTarget.cpp | 2 + 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index d9b93a5c3..0a48f4308 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -7,3 +7,112 @@ // #include "MasterSystem.hpp" + +#include "../../Processors/Z80/Z80.hpp" + +#include "../../Components/9918/9918.hpp" +#include "../../Components/SN76489/SN76489.hpp" + +#include "../CRTMachine.hpp" +#include "../JoystickMachine.hpp" + +#include "../../ClockReceiver/ForceInline.hpp" + +#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" + +namespace { +const int sn76489_divider = 2; +} + +namespace Sega { +namespace MasterSystem { + +class ConcreteMachine: + public Machine, + public CPU::Z80::BusHandler, + public CRTMachine::Machine { + + public: + ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + z80_(*this), + sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), + speaker_(sn76489_) { + speaker_.set_input_rate(3579545.0f / static_cast(sn76489_divider)); + set_clock_rate(3579545); + } + + ~ConcreteMachine() { + audio_queue_.flush(); + } + + void setup_output(float aspect_ratio) override { + vdp_.reset(new TI::TMS::TMS9918(TI::TMS::TMS9918A)); + get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); + } + + void close_output() override { + vdp_.reset(); + } + + Outputs::CRT::CRT *get_crt() override { + return vdp_->get_crt(); + } + + Outputs::Speaker::Speaker *get_speaker() override { + return &speaker_; + } + + void run_for(const Cycles cycles) override { + z80_.run_for(cycles); + } + + forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { + time_since_vdp_update_ += cycle.length; + time_since_sn76489_update_ += cycle.length; + + if(time_until_interrupt_ > 0) { + time_until_interrupt_ -= cycle.length; + if(time_until_interrupt_ <= HalfCycles(0)) { + z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_); + } + } + + return HalfCycles(0); + } + + void flush() { + update_video(); + update_audio(); + audio_queue_.perform(); + } + + private: + inline void update_audio() { + speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); + } + inline void update_video() { + vdp_->run_for(time_since_vdp_update_.flush()); + } + + CPU::Z80::Processor z80_; + std::unique_ptr vdp_; + + Concurrency::DeferringAsyncTaskQueue audio_queue_; + TI::SN76489 sn76489_; + Outputs::Speaker::LowpassSpeaker speaker_; + + HalfCycles time_since_vdp_update_; + HalfCycles time_since_sn76489_update_; + HalfCycles time_until_interrupt_; +}; + +} +} + +using namespace Sega::MasterSystem; + +Machine *Machine::MasterSystem(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { + return new ConcreteMachine(*target, rom_fetcher); +} + +Machine::~Machine() {} diff --git a/Machines/MasterSystem/MasterSystem.hpp b/Machines/MasterSystem/MasterSystem.hpp index 80eecc3f6..e6cfae681 100644 --- a/Machines/MasterSystem/MasterSystem.hpp +++ b/Machines/MasterSystem/MasterSystem.hpp @@ -9,6 +9,19 @@ #ifndef MasterSystem_hpp #define MasterSystem_hpp -#include +#include "../../Analyser/Static/StaticAnalyser.hpp" +#include "../ROMMachine.hpp" + +namespace Sega { +namespace MasterSystem { + +class Machine { + public: + virtual ~Machine(); + static Machine *MasterSystem(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); +}; + +} +} #endif /* MasterSystem_hpp */ diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index 4d61e701e..44f0c43db 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -14,6 +14,7 @@ #include "../ColecoVision/ColecoVision.hpp" #include "../Commodore/Vic-20/Vic20.hpp" #include "../Electron/Electron.hpp" +#include "../MasterSystem/MasterSystem.hpp" #include "../MSX/MSX.hpp" #include "../Oric/Oric.hpp" #include "../ZX8081/ZX8081.hpp" @@ -36,6 +37,7 @@ namespace { Bind(Atari2600) BindD(Coleco::Vision, ColecoVision) Bind(Electron) + BindD(Sega::MasterSystem, MasterSystem) Bind(MSX) Bind(Oric) BindD(Commodore::Vic20, Vic20) From d9e65cd75879bd977c89b63fe1fedabefaa2554b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 21 Sep 2018 22:53:35 -0400 Subject: [PATCH 04/88] Ensures neither the ColecoVision nor the MSX processes mid-cycles. --- Machines/ColecoVision/ColecoVision.cpp | 225 ++++++++--------- Machines/MSX/MSX.cpp | 322 +++++++++++++------------ 2 files changed, 276 insertions(+), 271 deletions(-) diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 8452dd124..1b0958817 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -197,131 +197,134 @@ class ConcreteMachine: time_since_vdp_update_ += length; time_since_sn76489_update_ += length; - uint16_t address = cycle.address ? *cycle.address : 0x0000; - switch(cycle.operation) { - case CPU::Z80::PartialMachineCycle::ReadOpcode: - if(!address) pc_zero_accesses_++; - case CPU::Z80::PartialMachineCycle::Read: - if(address < 0x2000) { - if(super_game_module_.replace_bios) { + // Act only if necessary. + if(cycle.is_terminal()) { + uint16_t address = cycle.address ? *cycle.address : 0x0000; + switch(cycle.operation) { + case CPU::Z80::PartialMachineCycle::ReadOpcode: + if(!address) pc_zero_accesses_++; + case CPU::Z80::PartialMachineCycle::Read: + if(address < 0x2000) { + if(super_game_module_.replace_bios) { + *cycle.value = super_game_module_.ram[address]; + } else { + *cycle.value = bios_[address]; + } + } else if(super_game_module_.replace_ram && address < 0x8000) { *cycle.value = super_game_module_.ram[address]; + } else if(address >= 0x6000 && address < 0x8000) { + *cycle.value = ram_[address & 1023]; + } else if(address >= 0x8000 && address <= cartridge_address_limit_) { + if(is_megacart_ && address >= 0xffc0) { + page_megacart(address); + } + *cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff]; } else { - *cycle.value = bios_[address]; + *cycle.value = 0xff; } - } else if(super_game_module_.replace_ram && address < 0x8000) { - *cycle.value = super_game_module_.ram[address]; - } else if(address >= 0x6000 && address < 0x8000) { - *cycle.value = ram_[address & 1023]; - } else if(address >= 0x8000 && address <= cartridge_address_limit_) { - if(is_megacart_ && address >= 0xffc0) { + break; + + case CPU::Z80::PartialMachineCycle::Write: + if(super_game_module_.replace_bios && address < 0x2000) { + super_game_module_.ram[address] = *cycle.value; + } else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) { + super_game_module_.ram[address] = *cycle.value; + } else if(address >= 0x6000 && address < 0x8000) { + ram_[address & 1023] = *cycle.value; + } else if(is_megacart_ && address >= 0xffc0) { page_megacart(address); } - *cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff]; - } else { - *cycle.value = 0xff; - } - break; + break; - case CPU::Z80::PartialMachineCycle::Write: - if(super_game_module_.replace_bios && address < 0x2000) { - super_game_module_.ram[address] = *cycle.value; - } else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) { - super_game_module_.ram[address] = *cycle.value; - } else if(address >= 0x6000 && address < 0x8000) { - ram_[address & 1023] = *cycle.value; - } else if(is_megacart_ && address >= 0xffc0) { - page_megacart(address); - } - break; + case CPU::Z80::PartialMachineCycle::Input: + switch((address >> 5) & 7) { + case 5: + update_video(); + *cycle.value = vdp_->get_register(address); + z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); + break; - case CPU::Z80::PartialMachineCycle::Input: - switch((address >> 5) & 7) { - case 5: - update_video(); - *cycle.value = vdp_->get_register(address); - z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); - break; + case 7: { + const std::size_t joystick_id = (address&2) >> 1; + Joystick *joystick = static_cast(joysticks_[joystick_id].get()); + if(joysticks_in_keypad_mode_) { + *cycle.value = joystick->get_keypad_input(); + } else { + *cycle.value = joystick->get_direction_input(); + } - case 7: { - const std::size_t joystick_id = (address&2) >> 1; - Joystick *joystick = static_cast(joysticks_[joystick_id].get()); - if(joysticks_in_keypad_mode_) { - *cycle.value = joystick->get_keypad_input(); - } else { - *cycle.value = joystick->get_direction_input(); - } + // Hitting exactly the recommended joypad input port is an indicator that + // this really is a ColecoVision game. The BIOS won't do this when just waiting + // to start a game (unlike accessing the VDP and SN). + if((address&0xfc) == 0xfc) confidence_counter_.add_hit(); + } break; - // Hitting exactly the recommended joypad input port is an indicator that - // this really is a ColecoVision game. The BIOS won't do this when just waiting - // to start a game (unlike accessing the VDP and SN). - if((address&0xfc) == 0xfc) confidence_counter_.add_hit(); - } break; + default: + switch(address&0xff) { + default: *cycle.value = 0xff; break; + case 0x52: + // Read AY data. + update_audio(); + ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); + *cycle.value = ay_.get_data_output(); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + break; + } + break; + } + break; - default: - switch(address&0xff) { - default: *cycle.value = 0xff; break; - case 0x52: - // Read AY data. - update_audio(); - ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); - *cycle.value = ay_.get_data_output(); - ay_.set_control_lines(GI::AY38910::ControlLines(0)); - break; - } - break; - } - break; + case CPU::Z80::PartialMachineCycle::Output: { + const int eighth = (address >> 5) & 7; + switch(eighth) { + case 4: case 6: + joysticks_in_keypad_mode_ = eighth == 4; + break; - case CPU::Z80::PartialMachineCycle::Output: { - const int eighth = (address >> 5) & 7; - switch(eighth) { - case 4: case 6: - joysticks_in_keypad_mode_ = eighth == 4; - break; + case 5: + update_video(); + vdp_->set_register(address, *cycle.value); + z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); + break; - case 5: - update_video(); - vdp_->set_register(address, *cycle.value); - z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); - break; + case 7: + update_audio(); + sn76489_.set_register(*cycle.value); + break; - case 7: - update_audio(); - sn76489_.set_register(*cycle.value); - break; + default: + // Catch Super Game Module accesses; it decodes more thoroughly. + switch(address&0xff) { + default: break; + case 0x7f: + super_game_module_.replace_bios = !((*cycle.value)&0x2); + break; + case 0x50: + // Set AY address. + update_audio(); + ay_.set_control_lines(GI::AY38910::BC1); + ay_.set_data_input(*cycle.value); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + break; + case 0x51: + // Set AY data. + update_audio(); + ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); + ay_.set_data_input(*cycle.value); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + break; + case 0x53: + super_game_module_.replace_ram = !!((*cycle.value)&0x1); + break; + } + break; + } + } break; - default: - // Catch Super Game Module accesses; it decodes more thoroughly. - switch(address&0xff) { - default: break; - case 0x7f: - super_game_module_.replace_bios = !((*cycle.value)&0x2); - break; - case 0x50: - // Set AY address. - update_audio(); - ay_.set_control_lines(GI::AY38910::BC1); - ay_.set_data_input(*cycle.value); - ay_.set_control_lines(GI::AY38910::ControlLines(0)); - break; - case 0x51: - // Set AY data. - update_audio(); - ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); - ay_.set_data_input(*cycle.value); - ay_.set_control_lines(GI::AY38910::ControlLines(0)); - break; - case 0x53: - super_game_module_.replace_ram = !!((*cycle.value)&0x1); - break; - } - break; - } - } break; - - default: break; + default: break; + } } if(time_until_interrupt_ > 0) { diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index f26f7ce9a..24fe36c73 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -361,183 +361,185 @@ class ConcreteMachine: memory_slots_[2].cycles_since_update += total_length; memory_slots_[3].cycles_since_update += total_length; - uint16_t address = cycle.address ? *cycle.address : 0x0000; - switch(cycle.operation) { - case CPU::Z80::PartialMachineCycle::ReadOpcode: - if(use_fast_tape_) { - if(address == 0x1a63) { - // TAPION + if(cycle.is_terminal()) { + uint16_t address = cycle.address ? *cycle.address : 0x0000; + switch(cycle.operation) { + case CPU::Z80::PartialMachineCycle::ReadOpcode: + if(use_fast_tape_) { + if(address == 0x1a63) { + // TAPION - // Enable the tape motor. - i8255_.set_register(0xab, 0x8); + // Enable the tape motor. + i8255_.set_register(0xab, 0x8); - // Disable interrupts. - z80_.set_value_of_register(CPU::Z80::Register::IFF1, 0); - z80_.set_value_of_register(CPU::Z80::Register::IFF2, 0); + // Disable interrupts. + z80_.set_value_of_register(CPU::Z80::Register::IFF1, 0); + z80_.set_value_of_register(CPU::Z80::Register::IFF2, 0); - // Use the parser to find a header, and if one is found then populate - // LOWLIM and WINWID, and reset carry. Otherwise set carry. - using Parser = Storage::Tape::MSX::Parser; - std::unique_ptr new_speed = Parser::find_header(tape_player_); - if(new_speed) { - ram_[0xfca4] = new_speed->minimum_start_bit_duration; - ram_[0xfca5] = new_speed->low_high_disrimination_duration; - z80_.set_value_of_register(CPU::Z80::Register::Flags, 0); - } else { - z80_.set_value_of_register(CPU::Z80::Register::Flags, 1); + // Use the parser to find a header, and if one is found then populate + // LOWLIM and WINWID, and reset carry. Otherwise set carry. + using Parser = Storage::Tape::MSX::Parser; + std::unique_ptr new_speed = Parser::find_header(tape_player_); + if(new_speed) { + ram_[0xfca4] = new_speed->minimum_start_bit_duration; + ram_[0xfca5] = new_speed->low_high_disrimination_duration; + z80_.set_value_of_register(CPU::Z80::Register::Flags, 0); + } else { + z80_.set_value_of_register(CPU::Z80::Register::Flags, 1); + } + + // RET. + *cycle.value = 0xc9; + break; } - // RET. - *cycle.value = 0xc9; - break; - } + if(address == 0x1abc) { + // TAPIN - if(address == 0x1abc) { - // TAPIN + // Grab the current values of LOWLIM and WINWID. + using Parser = Storage::Tape::MSX::Parser; + Parser::FileSpeed tape_speed; + tape_speed.minimum_start_bit_duration = ram_[0xfca4]; + tape_speed.low_high_disrimination_duration = ram_[0xfca5]; - // Grab the current values of LOWLIM and WINWID. - using Parser = Storage::Tape::MSX::Parser; - Parser::FileSpeed tape_speed; - tape_speed.minimum_start_bit_duration = ram_[0xfca4]; - tape_speed.low_high_disrimination_duration = ram_[0xfca5]; + // Ask the tape parser to grab a byte. + int next_byte = Parser::get_byte(tape_speed, tape_player_); - // Ask the tape parser to grab a byte. - int next_byte = Parser::get_byte(tape_speed, tape_player_); + // If a byte was found, return it with carry unset. Otherwise set carry to + // indicate error. + if(next_byte >= 0) { + z80_.set_value_of_register(CPU::Z80::Register::A, static_cast(next_byte)); + z80_.set_value_of_register(CPU::Z80::Register::Flags, 0); + } else { + z80_.set_value_of_register(CPU::Z80::Register::Flags, 1); + } - // If a byte was found, return it with carry unset. Otherwise set carry to - // indicate error. - if(next_byte >= 0) { - z80_.set_value_of_register(CPU::Z80::Register::A, static_cast(next_byte)); - z80_.set_value_of_register(CPU::Z80::Register::Flags, 0); - } else { - z80_.set_value_of_register(CPU::Z80::Register::Flags, 1); + // RET. + *cycle.value = 0xc9; + break; } - - // RET. - *cycle.value = 0xc9; - break; } - } - if(!address) { - pc_zero_accesses_++; - } - if(read_pointers_[address >> 13] == unpopulated_) { - performed_unmapped_access_ = true; - } - pc_address_ = address; // This is retained so as to be able to name the source of an access to cartridge handlers. - case CPU::Z80::PartialMachineCycle::Read: - if(read_pointers_[address >> 13]) { - *cycle.value = read_pointers_[address >> 13][address & 8191]; - } else { + if(!address) { + pc_zero_accesses_++; + } + if(read_pointers_[address >> 13] == unpopulated_) { + performed_unmapped_access_ = true; + } + pc_address_ = address; // This is retained so as to be able to name the source of an access to cartridge handlers. + case CPU::Z80::PartialMachineCycle::Read: + if(read_pointers_[address >> 13]) { + *cycle.value = read_pointers_[address >> 13][address & 8191]; + } else { + int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; + memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); + *cycle.value = memory_slots_[slot_hit].handler->read(address); + } + break; + + case CPU::Z80::PartialMachineCycle::Write: { + write_pointers_[address >> 13][address & 8191] = *cycle.value; + int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; - memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); - *cycle.value = memory_slots_[slot_hit].handler->read(address); - } - break; - - case CPU::Z80::PartialMachineCycle::Write: { - write_pointers_[address >> 13][address & 8191] = *cycle.value; - - int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; - if(memory_slots_[slot_hit].handler) { - update_audio(); - memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); - memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]); - } - } break; - - case CPU::Z80::PartialMachineCycle::Input: - switch(address & 0xff) { - case 0x98: case 0x99: - vdp_->run_for(time_since_vdp_update_.flush()); - *cycle.value = vdp_->get_register(address); - z80_.set_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); - break; - - case 0xa2: + if(memory_slots_[slot_hit].handler) { update_audio(); - ay_.set_control_lines(static_cast(GI::AY38910::BC2 | GI::AY38910::BC1)); - *cycle.value = ay_.get_data_output(); - ay_.set_control_lines(static_cast(0)); - break; - - case 0xa8: case 0xa9: - case 0xaa: case 0xab: - *cycle.value = i8255_.get_register(address); - break; - - default: - *cycle.value = 0xff; - break; - } - break; - - case CPU::Z80::PartialMachineCycle::Output: { - const int port = address & 0xff; - switch(port) { - case 0x98: case 0x99: - vdp_->run_for(time_since_vdp_update_.flush()); - vdp_->set_register(address, *cycle.value); - z80_.set_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); - break; - - case 0xa0: case 0xa1: - update_audio(); - ay_.set_control_lines(static_cast(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0))); - ay_.set_data_input(*cycle.value); - ay_.set_control_lines(static_cast(0)); - break; - - case 0xa8: case 0xa9: - case 0xaa: case 0xab: - i8255_.set_register(address, *cycle.value); - break; - - case 0xfc: case 0xfd: case 0xfe: case 0xff: -// printf("RAM banking %02x: %02x\n", port, *cycle.value); - break; - } - } break; - - case CPU::Z80::PartialMachineCycle::Interrupt: - *cycle.value = 0xff; - - // Take this as a convenient moment to jump into the keyboard buffer, if desired. - if(!input_text_.empty()) { - // The following are KEYBUF per the Red Book; its address and its definition as DEFS 40. - const int buffer_start = 0xfbf0; - const int buffer_size = 40; - - // Also from the Red Book: GETPNT is at F3FAH and PUTPNT is at F3F8H. - int read_address = ram_[0xf3fa] | (ram_[0xf3fb] << 8); - int write_address = ram_[0xf3f8] | (ram_[0xf3f9] << 8); - - // Write until either the string is exhausted or the write_pointer is immediately - // behind the read pointer; temporarily map write_address and read_address into - // buffer-relative values. - std::size_t characters_written = 0; - write_address -= buffer_start; - read_address -= buffer_start; - while(characters_written < input_text_.size()) { - const int next_write_address = (write_address + 1) % buffer_size; - if(next_write_address == read_address) break; - ram_[write_address + buffer_start] = static_cast(input_text_[characters_written]); - ++characters_written; - write_address = next_write_address; + memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); + memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]); } - input_text_.erase(input_text_.begin(), input_text_.begin() + static_cast(characters_written)); + } break; - // Map the write address back into absolute terms and write it out again as PUTPNT. - write_address += buffer_start; - ram_[0xf3f8] = static_cast(write_address); - ram_[0xf3f9] = static_cast(write_address >> 8); - } - break; + case CPU::Z80::PartialMachineCycle::Input: + switch(address & 0xff) { + case 0x98: case 0x99: + vdp_->run_for(time_since_vdp_update_.flush()); + *cycle.value = vdp_->get_register(address); + z80_.set_interrupt_line(vdp_->get_interrupt_line()); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); + break; - default: break; + case 0xa2: + update_audio(); + ay_.set_control_lines(static_cast(GI::AY38910::BC2 | GI::AY38910::BC1)); + *cycle.value = ay_.get_data_output(); + ay_.set_control_lines(static_cast(0)); + break; + + case 0xa8: case 0xa9: + case 0xaa: case 0xab: + *cycle.value = i8255_.get_register(address); + break; + + default: + *cycle.value = 0xff; + break; + } + break; + + case CPU::Z80::PartialMachineCycle::Output: { + const int port = address & 0xff; + switch(port) { + case 0x98: case 0x99: + vdp_->run_for(time_since_vdp_update_.flush()); + vdp_->set_register(address, *cycle.value); + z80_.set_interrupt_line(vdp_->get_interrupt_line()); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); + break; + + case 0xa0: case 0xa1: + update_audio(); + ay_.set_control_lines(static_cast(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0))); + ay_.set_data_input(*cycle.value); + ay_.set_control_lines(static_cast(0)); + break; + + case 0xa8: case 0xa9: + case 0xaa: case 0xab: + i8255_.set_register(address, *cycle.value); + break; + + case 0xfc: case 0xfd: case 0xfe: case 0xff: + // printf("RAM banking %02x: %02x\n", port, *cycle.value); + break; + } + } break; + + case CPU::Z80::PartialMachineCycle::Interrupt: + *cycle.value = 0xff; + + // Take this as a convenient moment to jump into the keyboard buffer, if desired. + if(!input_text_.empty()) { + // The following are KEYBUF per the Red Book; its address and its definition as DEFS 40. + const int buffer_start = 0xfbf0; + const int buffer_size = 40; + + // Also from the Red Book: GETPNT is at F3FAH and PUTPNT is at F3F8H. + int read_address = ram_[0xf3fa] | (ram_[0xf3fb] << 8); + int write_address = ram_[0xf3f8] | (ram_[0xf3f9] << 8); + + // Write until either the string is exhausted or the write_pointer is immediately + // behind the read pointer; temporarily map write_address and read_address into + // buffer-relative values. + std::size_t characters_written = 0; + write_address -= buffer_start; + read_address -= buffer_start; + while(characters_written < input_text_.size()) { + const int next_write_address = (write_address + 1) % buffer_size; + if(next_write_address == read_address) break; + ram_[write_address + buffer_start] = static_cast(input_text_[characters_written]); + ++characters_written; + write_address = next_write_address; + } + input_text_.erase(input_text_.begin(), input_text_.begin() + static_cast(characters_written)); + + // Map the write address back into absolute terms and write it out again as PUTPNT. + write_address += buffer_start; + ram_[0xf3f8] = static_cast(write_address); + ram_[0xf3f9] = static_cast(write_address >> 8); + } + break; + + default: break; + } } if(!tape_player_is_sleeping_) From 40c7a63fb5a00d8bf1f264a0089d4e760a9cc0e1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 22 Sep 2018 23:45:29 -0400 Subject: [PATCH 05/88] Makes a first attempt at Master System IO decoding. --- Machines/MasterSystem/MasterSystem.cpp | 78 +++++++++++++++++++++++++- ROMImages/MasterSystem/readme.txt | 5 ++ 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 ROMImages/MasterSystem/readme.txt diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 0a48f4308..3e140e32e 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -35,10 +35,18 @@ class ConcreteMachine: public: ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : z80_(*this), - sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), + sn76489_(TI::SN76489::Personality::SMS, audio_queue_, sn76489_divider), speaker_(sn76489_) { speaker_.set_input_rate(3579545.0f / static_cast(sn76489_divider)); set_clock_rate(3579545); + + const auto roms = rom_fetcher("MasterSystem", {"bios.sms"}); + if(!roms[0]) { + throw ROMMachine::Error::MissingROMs; + } + + roms[0]->resize(8*1024); + memcpy(&bios_, roms[0]->data(), roms[0]->size()); } ~ConcreteMachine() { @@ -46,7 +54,7 @@ class ConcreteMachine: } void setup_output(float aspect_ratio) override { - vdp_.reset(new TI::TMS::TMS9918(TI::TMS::TMS9918A)); + vdp_.reset(new TI::TMS::TMS9918(TI::TMS::SMSVDP)); get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); } @@ -70,6 +78,69 @@ class ConcreteMachine: time_since_vdp_update_ += cycle.length; time_since_sn76489_update_ += cycle.length; + if(cycle.is_terminal()) { + uint16_t address = cycle.address ? *cycle.address : 0x0000; + switch(cycle.operation) { + case CPU::Z80::PartialMachineCycle::ReadOpcode: +// printf("%04x [%02x]\n", address, bios_[address]); + case CPU::Z80::PartialMachineCycle::Read: + if(address < 0x2000) { + *cycle.value = bios_[address]; + } else if(address >= 0xc000) { + *cycle.value = ram_[address & 8191]; + } else { + *cycle.value = 0xff; + } + break; + + case CPU::Z80::PartialMachineCycle::Write: + if(address >= 0xc000) { + ram_[address & 8191] = *cycle.value; +// printf("w %04x\n", address); + } else { +// printf("mw %04x\n", address); + } + break; + + case CPU::Z80::PartialMachineCycle::Input: + printf("Input %04x\n", address); + break; + + case CPU::Z80::PartialMachineCycle::Output: + switch(address & 0xc1) { + case 0x00: + printf("TODO: memory control\n"); + break; + case 0x01: + printf("TODO: I/O port control\n"); + break; + case 0x40: case 0x41: + update_audio(); + sn76489_.set_register(*cycle.value); + break; + case 0x80: case 0x81: + update_video(); + vdp_->set_register(address, *cycle.value); + break; + case 0xc0: + printf("TODO: I/O port A/N\n"); + break; + case 0xc1: + printf("TODO: I/O port B/misc\n"); + break; + + default: break; + } + break; + + case CPU::Z80::PartialMachineCycle::Interrupt: + *cycle.value = 0xff; + break; + + default: break; + } + } + if(time_until_interrupt_ > 0) { time_until_interrupt_ -= cycle.length; if(time_until_interrupt_ <= HalfCycles(0)) { @@ -104,6 +175,9 @@ class ConcreteMachine: HalfCycles time_since_vdp_update_; HalfCycles time_since_sn76489_update_; HalfCycles time_until_interrupt_; + + uint8_t ram_[8*1024]; + uint8_t bios_[8*1024]; }; } diff --git a/ROMImages/MasterSystem/readme.txt b/ROMImages/MasterSystem/readme.txt new file mode 100644 index 000000000..ed5073cc9 --- /dev/null +++ b/ROMImages/MasterSystem/readme.txt @@ -0,0 +1,5 @@ +BIOS files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository. + +Expected files: + +bios.sms — the EU/US Master System BIOS. \ No newline at end of file From 38a1fde3bff2c6b26baf4a5478819800bfa5d939 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 23 Sep 2018 00:07:46 -0400 Subject: [PATCH 06/88] Attempts to permit Master System interrupts. --- Machines/MasterSystem/MasterSystem.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 3e140e32e..352a1649c 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -82,13 +82,14 @@ class ConcreteMachine: uint16_t address = cycle.address ? *cycle.address : 0x0000; switch(cycle.operation) { case CPU::Z80::PartialMachineCycle::ReadOpcode: -// printf("%04x [%02x]\n", address, bios_[address]); +// printf("%04x\n", address); case CPU::Z80::PartialMachineCycle::Read: if(address < 0x2000) { *cycle.value = bios_[address]; } else if(address >= 0xc000) { *cycle.value = ram_[address & 8191]; } else { +// printf("lr %04x\n", address); *cycle.value = 0xff; } break; @@ -121,6 +122,8 @@ class ConcreteMachine: case 0x80: case 0x81: update_video(); vdp_->set_register(address, *cycle.value); + z80_.set_interrupt_line(vdp_->get_interrupt_line()); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 0xc0: printf("TODO: I/O port A/N\n"); @@ -129,7 +132,9 @@ class ConcreteMachine: printf("TODO: I/O port B/misc\n"); break; - default: break; + default: + printf("Clearly some sort of typo\n"); + break; } break; @@ -144,7 +149,7 @@ class ConcreteMachine: if(time_until_interrupt_ > 0) { time_until_interrupt_ -= cycle.length; if(time_until_interrupt_ <= HalfCycles(0)) { - z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_); + z80_.set_interrupt_line(true, time_until_interrupt_); } } From 9683c8f6646de7a110e954dd5cfde7e6fd652e60 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 23 Sep 2018 15:58:23 -0400 Subject: [PATCH 07/88] Advances towards the Master System actually receiving interrupts. --- Analyser/Static/StaticAnalyser.cpp | 1 + Components/9918/9918.cpp | 14 +- Components/9918/Implementation/9918Base.hpp | 215 ++++++++++---------- OSBindings/Mac/Clock Signal/Info.plist | 22 ++ 4 files changed, 149 insertions(+), 103 deletions(-) diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index 3a87db958..a65e9582b 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -130,6 +130,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: result.cartridges, Cartridge::BinaryDump, TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM + Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS Format("ssd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // SSD Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index d5b1dbdb2..dca1649dd 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -84,6 +84,7 @@ enum ScreenMode { } Base::Base(Personality p) : + personality_(p), // 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole // line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368. crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) { @@ -662,8 +663,19 @@ void TMS9918::set_register(int address, uint8_t value) { write_phase_ = false; if(value & 0x80) { + switch(personality_) { + default: + value &= 7; + break; + case TI::TMS::SMSVDP: + value &= 0x7f; + break; + } + +// printf("%02x to %d\n", low_write_, value); + // This is a write to a register. - switch(value & 7) { + switch(value) { case 0: next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1); break; diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 220587cb6..cc9a4d64a 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -30,6 +30,7 @@ class Base { protected: Base(Personality p); + Personality personality_; std::unique_ptr crt_; std::vector ram_; @@ -93,7 +94,7 @@ class Base { uint8_t image[2]; int shift_position = 0; - } active_sprites[4]; + } active_sprites[8]; int active_sprite_slot = 0; } sprite_sets_[2]; int active_sprite_set_ = 0; @@ -104,111 +105,121 @@ class Base { inline void test_sprite(int sprite_number, int screen_row); inline void get_sprite_contents(int start, int cycles, int screen_row); - // Contains tables describing the memory access patterns and, implicitly, - // the timing of video generation. - enum Operation { - HSyncOn, - HSyncOff, - ColourBurstOn, - ColourBurstOff, +/* +#define slot(n) \ + if(use_end && end+1 == n) return;\ + case n - /// A memory access slot that is available for an external read or write. - External, + template void fetch_sms(int start, int end) { +#define sprite_render_block(location, sprite) \ + slot(location): \ + sprite_sets_[active_sprite_set_].info[0] = + switch(start) { + default: +// slot(1): - /// A refresh cycle; neither used for video fetching nor available for external use. - Refresh, + return; + } + } - /*! - Column N Name Table Read - [= 1 slot] - */ - NameTableRead, +#undef slot +*/ - /*! - Column N Pattern Table Read - [= 1 slot] - */ - PatternTableRead, - - /*! - Y0, X0, N0, C0, Pattern 0 (1), Pattern 0 (2), - Y1, X1, N1, C1, Pattern 1 (1), Pattern 1 (2), - Y2, X2 - [= 14 slots] - */ - TMSSpriteFetch1, - - /*! - N2, C2, Pattern 2 (1), Pattern 2 (2), - Y3, X3, N3, C3, Pattern 3 (1), Pattern 3 (2), - [= 10 slots] - */ - TMSSpriteFetch2, - - /*! - Sprite N fetch, Sprite N+1 fetch [...] - */ - TMSSpriteYFetch, - - /*! - Colour N, Pattern N, - Name N+1, - Sprite N, - - Colour N+1, Pattern N+1, - Name N+2, - Sprite N+1, - - Colour N+2, Pattern N+2, - Name N+3, - Sprite N+2, - - Colour N+3, Pattern N+3, - Name N+4, - [= 15 slots] - */ - TMSBackgroundRenderBlock, - - /*! - Pattern N, - Name N+1 - */ - TMSPatternNameFetch, - - /*! - Sprite N X/Name Read - Sprite N+1 X/Name Read - Sprite N Tile read (1st word) - Sprite N Tile read (2nd word) - Sprite N+1 Tile Read (1st word) - Sprite N+1 Tile Read (2nd word) - [= 6 slots] - */ - SMSSpriteRenderBlock, - - /*! - Column N Tile Read (1st word) - Column N Tile Read (2nd word) - Column N+1 Name Table Read - Sprite (16+N*1.5) Y Read (Reads Y of 2 sprites) - Column N+1 Tile Read (1st word) - Column N+1 Tile Read (2nd word) - Column N+2 Name Table Read - Sprite (16+N*1.5+2) Y Read (Reads Y of 2 sprites) - Column N+2 Tile Read (1st word) - Column N+2 Tile Read (2nd word) - Column N+3 Name Table Read - Sprite (16+N*1.5+4) Y Read (Reads Y of 2 sprites) - Column N+3 Tile Read (1st word) - Column N+3 Tile Read (2nd word) - [= 14 slots] - */ - SMSBackgroundRenderBlock, - }; - struct Period { - Operation operation; - int duration; - }; +// // Contains tables describing the memory access patterns and, implicitly, +// // the timing of video generation. +// enum Operation { +// HSyncOn, +// HSyncOff, +// ColourBurstOn, +// ColourBurstOff, +// +// /// A memory access slot that is available for an external read or write. +// External, +// +// /// A refresh cycle; neither used for video fetching nor available for external use. +// Refresh, +// +// /// A slot that reads the next sprite location for +// ReadSpriteY, +// +// /*! +// Column N Name Table Read +// [= 1 slot] +// */ +// NameTableRead, +// +// /*! +// Column N Pattern Table Read +// [= 1 slot] +// */ +// PatternTableRead, +// +// /*! +// Y0, X0, N0, C0, Pattern 0 (1), Pattern 0 (2), +// Y1, X1, N1, C1, Pattern 1 (1), Pattern 1 (2), +// Y2, X2 +// [= 14 slots] +// */ +// TMSSpriteFetch1, +// +// /*! +// N2, C2, Pattern 2 (1), Pattern 2 (2), +// Y3, X3, N3, C3, Pattern 3 (1), Pattern 3 (2), +// [= 10 slots] +// */ +// TMSSpriteFetch2, +// +// /*! +// Sprite N fetch, Sprite N+1 fetch [...] +// */ +// TMSSpriteYFetch, +// +// /*! +// Colour N, Pattern N, +// Name N+1, +// [= 3 slots] +// */ +// TMSBackgroundRenderBlock, +// +// /*! +// Colour N, Pattern N, +// */ +// TMSColourPatternFetch, +// +// /*! +// Sprite N X/Name Read +// Sprite N+1 X/Name Read +// Sprite N Tile read (1st word) +// Sprite N Tile read (2nd word) +// Sprite N+1 Tile Read (1st word) +// Sprite N+1 Tile Read (2nd word) +// [= 6 slots] +// */ +// SMSSpriteRenderBlock, +// +// /*! +// Column N Tile Read (1st word) +// Column N Tile Read (2nd word) +// Column N+1 Name Table Read +// Sprite (16+N*1.5) Y Read (Reads Y of 2 sprites) +// Column N+1 Tile Read (1st word) +// Column N+1 Tile Read (2nd word) +// Column N+2 Name Table Read +// Sprite (16+N*1.5+2) Y Read (Reads Y of 2 sprites) +// Column N+2 Tile Read (1st word) +// Column N+2 Tile Read (2nd word) +// Column N+3 Name Table Read +// Sprite (16+N*1.5+4) Y Read (Reads Y of 2 sprites) +// Column N+3 Tile Read (1st word) +// Column N+3 Tile Read (2nd word) +// [= 14 slots] +// */ +// SMSBackgroundRenderBlock, +// }; +// struct Period { +// Operation operation; +// int duration; +// }; }; } diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index ad27ed856..1fb197ed1 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -453,6 +453,28 @@ LSHandlerRank Owner + + CFBundleTypeExtensions + + sg + + CFBundleTypeOSTypes + + ???? + + CFBundleTypeIconFile + cartridge.png + CFBundleTypeName + SG1000 Cartridge + CFBundleTypeRole + Viewer + LSTypeIsPackage + + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument + LSHandlerRank + Owner + CFBundleTypeExtensions From f59386f523027c2cb7ede4d5368deb2b89b3baf0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 23 Sep 2018 16:05:37 -0400 Subject: [PATCH 08/88] Adds just enough input logic that the Sega sound now plays. --- Machines/MasterSystem/MasterSystem.cpp | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 352a1649c..2ae11bae7 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -105,6 +105,34 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Input: printf("Input %04x\n", address); + *cycle.value = 0xff; + switch(address & 0xc1) { + case 0x00: + printf("TODO: memory control\n"); + break; + case 0x01: + printf("TODO: I/O port control\n"); + break; + case 0x40: case 0x41: + printf("TODO: get current line\n"); + break; + case 0x80: case 0x81: + update_video(); + *cycle.value = vdp_->get_register(address); + z80_.set_interrupt_line(vdp_->get_interrupt_line()); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); + break; + case 0xc0: + printf("TODO: I/O port A/N\n"); + break; + case 0xc1: + printf("TODO: I/O port B/misc\n"); + break; + + default: + printf("Clearly some sort of typo\n"); + break; + } break; case CPU::Z80::PartialMachineCycle::Output: From 00b2db4fb9c34137335615a461f3c5c08c1a8554 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 23 Sep 2018 16:34:47 -0400 Subject: [PATCH 09/88] Ensures the Master System is informed when it should pretend to be an SG1000. --- Analyser/Static/Sega/StaticAnalyser.cpp | 13 +++++++++ Analyser/Static/Sega/Target.hpp | 29 +++++++++++++++++++ Machines/MasterSystem/MasterSystem.cpp | 15 +++++++--- .../Clock Signal.xcodeproj/project.pbxproj | 2 ++ 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 Analyser/Static/Sega/Target.hpp diff --git a/Analyser/Static/Sega/StaticAnalyser.cpp b/Analyser/Static/Sega/StaticAnalyser.cpp index 0f8adbf68..c8d733c27 100644 --- a/Analyser/Static/Sega/StaticAnalyser.cpp +++ b/Analyser/Static/Sega/StaticAnalyser.cpp @@ -8,12 +8,25 @@ #include "StaticAnalyser.hpp" +#include "Target.hpp" + Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { TargetList targets; std::unique_ptr target(new Target); + target->machine = Machine::MasterSystem; + + // Files named .sg are treated as for the SG1000; otherwise assume a Master System. + if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') { + target->model = Target::Model::SG1000; + } else { + target->model = Target::Model::MasterSystem; + } + target->media.cartridges = media.cartridges; + if(!target->media.empty()) targets.push_back(std::move(target)); + return targets; } diff --git a/Analyser/Static/Sega/Target.hpp b/Analyser/Static/Sega/Target.hpp new file mode 100644 index 000000000..391b0e159 --- /dev/null +++ b/Analyser/Static/Sega/Target.hpp @@ -0,0 +1,29 @@ +// +// Target.hpp +// Clock Signal +// +// Created by Thomas Harte on 23/09/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef Analyser_Static_Sega_Target_h +#define Analyser_Static_Sega_Target_h + +namespace Analyser { +namespace Static { +namespace Sega { + +struct Target: public ::Analyser::Static::Target { + enum class Model { + MasterSystem, + SG1000 + }; + + Model model = Model::MasterSystem; +}; + +} +} +} + +#endif /* Analyser_Static_Sega_Target_h */ diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 2ae11bae7..d871b8821 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -20,6 +20,8 @@ #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../Analyser/Static/Sega/Target.hpp" + namespace { const int sn76489_divider = 2; } @@ -33,7 +35,7 @@ class ConcreteMachine: public CRTMachine::Machine { public: - ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : z80_(*this), sn76489_(TI::SN76489::Personality::SMS, audio_queue_, sn76489_divider), speaker_(sn76489_) { @@ -104,17 +106,18 @@ class ConcreteMachine: break; case CPU::Z80::PartialMachineCycle::Input: - printf("Input %04x\n", address); - *cycle.value = 0xff; switch(address & 0xc1) { case 0x00: printf("TODO: memory control\n"); + *cycle.value = 0xff; break; case 0x01: printf("TODO: I/O port control\n"); + *cycle.value = 0xff; break; case 0x40: case 0x41: printf("TODO: get current line\n"); + *cycle.value = 0xff; break; case 0x80: case 0x81: update_video(); @@ -124,9 +127,11 @@ class ConcreteMachine: break; case 0xc0: printf("TODO: I/O port A/N\n"); + *cycle.value = 0; break; case 0xc1: printf("TODO: I/O port B/misc\n"); + *cycle.value = 0; break; default: @@ -219,7 +224,9 @@ class ConcreteMachine: using namespace Sega::MasterSystem; Machine *Machine::MasterSystem(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { - return new ConcreteMachine(*target, rom_fetcher); + using Target = Analyser::Static::Sega::Target; + const Target *const sega_target = dynamic_cast(target); + return new ConcreteMachine(*sega_target, rom_fetcher); } Machine::~Machine() {} diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 8562422cf..6f56f10e9 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1042,6 +1042,7 @@ 4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = ""; }; 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = ""; }; 4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MediaTarget.hpp; sourceTree = ""; }; + 4BAA167B21582B1D008A3276 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = ""; }; 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = ""; }; 4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = ""; }; @@ -2213,6 +2214,7 @@ children = ( 4B7F1895215486A100388727 /* StaticAnalyser.hpp */, 4B7F1896215486A100388727 /* StaticAnalyser.cpp */, + 4BAA167B21582B1D008A3276 /* Target.hpp */, ); path = Sega; sourceTree = ""; From e155dc8d6eaa7216d4e18d15a56c97afee454819 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 23 Sep 2018 17:36:30 -0400 Subject: [PATCH 10/88] Adds fairly standard memory map indirection. --- Machines/MasterSystem/MasterSystem.cpp | 54 ++++++++++++++++---------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index d871b8821..d539e61ed 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -42,13 +42,29 @@ class ConcreteMachine: speaker_.set_input_rate(3579545.0f / static_cast(sn76489_divider)); set_clock_rate(3579545); - const auto roms = rom_fetcher("MasterSystem", {"bios.sms"}); - if(!roms[0]) { - throw ROMMachine::Error::MissingROMs; + for(auto &pointer: read_pointers_) { + pointer = nullptr; + } + for(auto &pointer: write_pointers_) { + pointer = nullptr; } - roms[0]->resize(8*1024); - memcpy(&bios_, roms[0]->data(), roms[0]->size()); + if(target.model == Analyser::Static::Sega::Target::Model::MasterSystem) { + const auto roms = rom_fetcher("MasterSystem", {"bios.sms"}); + if(!roms[0]) { + throw ROMMachine::Error::MissingROMs; + } + + roms[0]->resize(8*1024); + memcpy(&bios_, roms[0]->data(), roms[0]->size()); + map(read_pointers_, bios_, 8*1024, 0); + + map(read_pointers_, ram_, 8*1024, 0xc000, 0x10000); + map(write_pointers_, ram_, 8*1024, 0xc000, 0x10000); + } else { + map(read_pointers_, ram_, 1024, 0xc000, 0x10000); + map(write_pointers_, ram_, 1024, 0xc000, 0x10000); + } } ~ConcreteMachine() { @@ -84,25 +100,12 @@ class ConcreteMachine: uint16_t address = cycle.address ? *cycle.address : 0x0000; switch(cycle.operation) { case CPU::Z80::PartialMachineCycle::ReadOpcode: -// printf("%04x\n", address); case CPU::Z80::PartialMachineCycle::Read: - if(address < 0x2000) { - *cycle.value = bios_[address]; - } else if(address >= 0xc000) { - *cycle.value = ram_[address & 8191]; - } else { -// printf("lr %04x\n", address); - *cycle.value = 0xff; - } + *cycle.value = read_pointers_[address >> 10] ? read_pointers_[address >> 10][address & 1023] : 0xff; break; case CPU::Z80::PartialMachineCycle::Write: - if(address >= 0xc000) { - ram_[address & 8191] = *cycle.value; -// printf("w %04x\n", address); - } else { -// printf("mw %04x\n", address); - } + if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value; break; case CPU::Z80::PartialMachineCycle::Input: @@ -216,6 +219,17 @@ class ConcreteMachine: uint8_t ram_[8*1024]; uint8_t bios_[8*1024]; + std::vector cartridge_; + + // The memory map has a 1kb granularity; this is determined by the SG1000's 1kb of RAM. + const uint8_t *read_pointers_[64]; + uint8_t *write_pointers_[64]; + template void map(T **target, uint8_t *source, int size, int start_address, int end_address = -1) { + if(end_address == -1) end_address = start_address + size; + for(int address = start_address; address < end_address; address += 1024) { + target[address >> 10] = &source[(address - start_address) & (size - 1)]; + } + } }; } From 22533419042c688e3442540a21610c15b7950e47 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 23 Sep 2018 17:42:42 -0400 Subject: [PATCH 11/88] This now goes far enough for the only SG1000 game I'm testing to start up. Which hopefully gives me as much as I need to implement joypads, etc, and definitively get to just the VDP being outstanding. --- Machines/MasterSystem/MasterSystem.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index d539e61ed..6dd3e0070 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -49,6 +49,11 @@ class ConcreteMachine: pointer = nullptr; } + // Take a copy of the cartridge and place it into memory. + cartridge_ = target.media.cartridges[0]->get_segments()[0].data; + map(read_pointers_, cartridge_.data(), static_cast(cartridge_.size()), 0x0000, 0xc000); + + // Establish the BIOS (if relevant) and RAM. if(target.model == Analyser::Static::Sega::Target::Model::MasterSystem) { const auto roms = rom_fetcher("MasterSystem", {"bios.sms"}); if(!roms[0]) { From 5b9e7213dd76d6bdcc92ad0729ee5b7e6a3ddabe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 23 Sep 2018 21:55:07 -0400 Subject: [PATCH 12/88] Adds a couple of joystick inputs. SG1000 titles all seem to work now. --- Machines/MasterSystem/MasterSystem.cpp | 88 ++++++++++++++++++++------ 1 file changed, 70 insertions(+), 18 deletions(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 6dd3e0070..a6385c5a7 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -29,10 +29,50 @@ const int sn76489_divider = 2; namespace Sega { namespace MasterSystem { +class Joystick: public Inputs::ConcreteJoystick { + public: + Joystick() : + ConcreteJoystick({ + Input(Input::Up), + Input(Input::Down), + Input(Input::Left), + Input(Input::Right), + + Input(Input::Fire, 0), + Input(Input::Fire, 1) + }) {} + + void did_set_input(const Input &digital_input, bool is_active) override { + switch(digital_input.type) { + default: return; + + case Input::Up: if(is_active) state_ &= ~0x01; else state_ |= 0x01; break; + case Input::Down: if(is_active) state_ &= ~0x02; else state_ |= 0x02; break; + case Input::Left: if(is_active) state_ &= ~0x04; else state_ |= 0x04; break; + case Input::Right: if(is_active) state_ &= ~0x08; else state_ |= 0x08; break; + case Input::Fire: + switch(digital_input.info.control.index) { + default: break; + case 0: if(is_active) state_ &= ~0x10; else state_ |= 0x10; break; + case 1: if(is_active) state_ &= ~0x20; else state_ |= 0x20; break; + } + break; + } + } + + uint8_t get_state() { + return state_; + } + + private: + uint8_t state_ = 0xff; +}; + class ConcreteMachine: public Machine, public CPU::Z80::BusHandler, - public CRTMachine::Machine { + public CRTMachine::Machine, + public JoystickMachine::Machine { public: ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : @@ -42,6 +82,11 @@ class ConcreteMachine: speaker_.set_input_rate(3579545.0f / static_cast(sn76489_divider)); set_clock_rate(3579545); + // Instantiate the joysticks. + joysticks_.emplace_back(new Joystick); + joysticks_.emplace_back(new Joystick); + + // Clear the memory map. for(auto &pointer: read_pointers_) { pointer = nullptr; } @@ -116,15 +161,15 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Input: switch(address & 0xc1) { case 0x00: - printf("TODO: memory control\n"); + printf("TODO: [input] memory control\n"); *cycle.value = 0xff; break; case 0x01: - printf("TODO: I/O port control\n"); + printf("TODO: [input] I/O port control\n"); *cycle.value = 0xff; break; case 0x40: case 0x41: - printf("TODO: get current line\n"); + printf("TODO: [input] get current line\n"); *cycle.value = 0xff; break; case 0x80: case 0x81: @@ -133,17 +178,18 @@ class ConcreteMachine: z80_.set_interrupt_line(vdp_->get_interrupt_line()); time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; - case 0xc0: - printf("TODO: I/O port A/N\n"); - *cycle.value = 0; - break; - case 0xc1: - printf("TODO: I/O port B/misc\n"); - *cycle.value = 0; - break; + case 0xc0: { + Joystick *const joypad1 = static_cast(joysticks_[0].get()); + Joystick *const joypad2 = static_cast(joysticks_[1].get()); + *cycle.value = static_cast(joypad1->get_state() | (joypad2->get_state() << 6)); + } break; + case 0xc1: { + Joystick *const joypad2 = static_cast(joysticks_[1].get()); + *cycle.value = (joypad2->get_state() >> 2) | 0xf; + } break; default: - printf("Clearly some sort of typo\n"); + printf("[input] Clearly some sort of typo\n"); break; } break; @@ -151,10 +197,10 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Output: switch(address & 0xc1) { case 0x00: - printf("TODO: memory control\n"); + printf("TODO: [output] memory control\n"); break; case 0x01: - printf("TODO: I/O port control\n"); + printf("TODO: [output] I/O port control\n"); break; case 0x40: case 0x41: update_audio(); @@ -167,14 +213,14 @@ class ConcreteMachine: time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 0xc0: - printf("TODO: I/O port A/N\n"); +// printf("TODO: [output] I/O port A/N [%02x]\n", *cycle.value); break; case 0xc1: - printf("TODO: I/O port B/misc\n"); + printf("TODO: [output] I/O port B/misc\n"); break; default: - printf("Clearly some sort of typo\n"); + printf("[output] Clearly some sort of typo\n"); break; } break; @@ -203,6 +249,10 @@ class ConcreteMachine: audio_queue_.perform(); } + std::vector> &get_joysticks() override { + return joysticks_; + } + private: inline void update_audio() { speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); @@ -218,6 +268,8 @@ class ConcreteMachine: TI::SN76489 sn76489_; Outputs::Speaker::LowpassSpeaker speaker_; + std::vector> joysticks_; + HalfCycles time_since_vdp_update_; HalfCycles time_since_sn76489_update_; HalfCycles time_until_interrupt_; From 7515fa8a983f6738a0ced36821abaf7e7108cfe3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 23 Sep 2018 22:24:29 -0400 Subject: [PATCH 13/88] Ensures the SG1000 gets an unadulterated TMS and SN. --- Machines/MasterSystem/MasterSystem.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index a6385c5a7..5c4360f21 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -76,8 +76,12 @@ class ConcreteMachine: public: ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + model_(target.model), z80_(*this), - sn76489_(TI::SN76489::Personality::SMS, audio_queue_, sn76489_divider), + sn76489_( + (target.model == Analyser::Static::Sega::Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS, + audio_queue_, + sn76489_divider), speaker_(sn76489_) { speaker_.set_input_rate(3579545.0f / static_cast(sn76489_divider)); set_clock_rate(3579545); @@ -122,7 +126,7 @@ class ConcreteMachine: } void setup_output(float aspect_ratio) override { - vdp_.reset(new TI::TMS::TMS9918(TI::TMS::SMSVDP)); + vdp_.reset(new TI::TMS::TMS9918(model_ == Analyser::Static::Sega::Target::Model::SG1000 ? TI::TMS::TMS9918A : TI::TMS::SMSVDP)); get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); } @@ -261,6 +265,7 @@ class ConcreteMachine: vdp_->run_for(time_since_vdp_update_.flush()); } + Analyser::Static::Sega::Target::Model model_; CPU::Z80::Processor z80_; std::unique_ptr vdp_; From 2e4c4c3e9175297e7f6db0745667ab5372785d15 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 24 Sep 2018 21:34:42 -0400 Subject: [PATCH 14/88] Makes some attempt to implement paging. This causes several of the 32kb games to be recognised by the BIOS and permitted to start, so it really really may be time to stop deferring work on the VDP. --- Machines/MasterSystem/MasterSystem.cpp | 59 ++++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 5c4360f21..dae6df17d 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -22,6 +22,8 @@ #include "../../Analyser/Static/Sega/Target.hpp" +#include + namespace { const int sn76489_divider = 2; } @@ -91,16 +93,17 @@ class ConcreteMachine: joysticks_.emplace_back(new Joystick); // Clear the memory map. - for(auto &pointer: read_pointers_) { - pointer = nullptr; - } - for(auto &pointer: write_pointers_) { - pointer = nullptr; - } + map(read_pointers_, nullptr, 0x10000, 0); + map(write_pointers_, nullptr, 0x10000, 0); // Take a copy of the cartridge and place it into memory. cartridge_ = target.media.cartridges[0]->get_segments()[0].data; - map(read_pointers_, cartridge_.data(), static_cast(cartridge_.size()), 0x0000, 0xc000); + if(cartridge_.size() < 48*1024) { + std::size_t new_space = 48*1024 - cartridge_.size(); + cartridge_.resize(48*1024); + memset(&cartridge_[48*1024 - new_space], 0xff, new_space); + } + page_cartridge(); // Establish the BIOS (if relevant) and RAM. if(target.model == Analyser::Static::Sega::Target::Model::MasterSystem) { @@ -159,7 +162,11 @@ class ConcreteMachine: break; case CPU::Z80::PartialMachineCycle::Write: - if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value; + if(address >= 0xfffd && cartridge_.size() > 48*1024) { + paging_registers_[address - 0xfffd] = *cycle.value; + page_cartridge(); + } + else if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value; break; case CPU::Z80::PartialMachineCycle::Input: @@ -201,7 +208,21 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Output: switch(address & 0xc1) { case 0x00: - printf("TODO: [output] memory control\n"); + if(model_ == Analyser::Static::Sega::Target::Model::MasterSystem) { + // TODO: Obey the RAM enable. + + // Either install the cartridge or don't. + if(!((*cycle.value) & 0x40)) { + page_cartridge(); + } else { + map(read_pointers_, nullptr, 0xc000, 0x0000); + } + + // Throw the BIOS on top if it isn't disabled. + if(!((*cycle.value) & 0x08)) { + map(read_pointers_, bios_, 8*1024, 0); + } + } break; case 0x01: printf("TODO: [output] I/O port control\n"); @@ -286,10 +307,22 @@ class ConcreteMachine: // The memory map has a 1kb granularity; this is determined by the SG1000's 1kb of RAM. const uint8_t *read_pointers_[64]; uint8_t *write_pointers_[64]; - template void map(T **target, uint8_t *source, int size, int start_address, int end_address = -1) { - if(end_address == -1) end_address = start_address + size; - for(int address = start_address; address < end_address; address += 1024) { - target[address >> 10] = &source[(address - start_address) & (size - 1)]; + template void map(T **target, uint8_t *source, size_t size, size_t start_address, size_t end_address = 0) { + if(!end_address) end_address = start_address + size; + for(auto address = start_address; address < end_address; address += 1024) { + target[address >> 10] = source ? &source[(address - start_address) & (size - 1)] : nullptr; + } + } + + uint8_t paging_registers_[3] = {0, 1, 2}; + void page_cartridge() { + for(size_t c = 0; c < 3; ++c) { + const size_t start_addr = (paging_registers_[c] * 0x4000) % cartridge_.size(); + map( + read_pointers_, + cartridge_.data() + start_addr, + std::min(static_cast(0x4000), cartridge_.size() - start_addr), + c * 0x4000); } } }; From 19482a563f2ab3ca20d31c0efbd23b54e1abbd93 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 27 Sep 2018 21:22:57 -0400 Subject: [PATCH 15/88] Attempts to explicitly make room for the SMS VDP mode. --- Components/9918/9918.cpp | 100 ++++++++++---------- Components/9918/Implementation/9918Base.hpp | 74 +++++++++++++-- 2 files changed, 119 insertions(+), 55 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index dca1649dd..64c33fc04 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -72,15 +72,6 @@ struct ReverseTable { } } reverse_table; -// Bits are reversed in the internal mode value; they're stored -// in the order M1 M2 M3. Hence the definitions below. -enum ScreenMode { - Text = 4, - MultiColour = 2, - ColouredText = 0, - Graphics = 1 -}; - } Base::Base(Personality p) : @@ -135,7 +126,7 @@ void Base::test_sprite(int sprite_number, int screen_row) { if(sprites_stopped_) return; - const int sprite_position = ram_[sprite_attribute_table_address_ + (sprite_number << 2)]; + const int sprite_position = ram_[sprite_attribute_table_address_ + static_cast(sprite_number << 2)]; // A sprite Y of 208 means "don't scan the list any further". if(sprite_position == 208) { sprites_stopped_ = true; @@ -172,15 +163,15 @@ void Base::get_sprite_contents(int field, int cycles_left, int screen_row) { if(field < 4) { std::memcpy( &sprite.info[field], - &ram_[sprite_attribute_table_address_ + (sprite.index << 2) + field], + &ram_[sprite_attribute_table_address_ + static_cast((sprite.index << 2) + field)], static_cast(std::min(4, final_field) - field)); } field = std::min(4, final_field); const int sprite_offset = sprite.info[2] & ~(sprites_16x16_ ? 3 : 0); - const int sprite_address = sprite_generator_table_address_ + (sprite_offset << 3) + sprite.row; // TODO: recalclate sprite.row from screen_row (?) + const size_t sprite_address = sprite_generator_table_address_ + static_cast(sprite_offset << 3) + sprite.row; // TODO: recalclate sprite.row from screen_row (?) while(field < final_field) { - sprite.image[field - 4] = ram_[sprite_address + ((field - 4) << 4)]; + sprite.image[field - 4] = ram_[sprite_address + static_cast(((field - 4) << 4))]; field++; } @@ -262,7 +253,7 @@ void TMS9918::run_for(const HalfCycles cycles) { // ------------------------------ // Perform video memory accesses. // ------------------------------ - if(((row_ < 192) || (row_ == frame_lines_-1)) && !blank_screen_) { + if(((row_ < 192) || (row_ == frame_lines_-1)) && (current_mode_ != ScreenMode::Blank)) { const int sprite_row = (row_ < 192) ? row_ : -1; const int access_slot = column_ >> 1; // There are only 171 available memory accesses per line. switch(line_mode_) { @@ -271,7 +262,7 @@ void TMS9918::run_for(const HalfCycles cycles) { case LineMode::Text: access_pointer_ = std::min(30, access_slot); if(access_pointer_ >= 30 && access_pointer_ < 150) { - const int row_base = pattern_name_address_ + (row_ >> 3) * 40; + const int row_base = pattern_name_address_ + static_cast(row_ >> 3) * 40; const int end = std::min(150, access_slot); // Pattern names are collected every third window starting from window 30. @@ -322,10 +313,10 @@ void TMS9918::run_for(const HalfCycles cycles) { if(access_pointer_ >= 27 && access_pointer_ < 155) { int end = std::min(155, access_slot); - int row_base = pattern_name_address_; - int pattern_base = pattern_generator_table_address_; - int colour_base = colour_table_address_; - if(screen_mode_ == ScreenMode::Graphics) { + size_t row_base = pattern_name_address_; + size_t pattern_base = pattern_generator_table_address_; + size_t colour_base = colour_table_address_; + if(current_mode_ == ScreenMode::Graphics) { // If this is high resolution mode, allow the row number to affect the pattern and colour addresses. pattern_base &= 0x2000 | ((row_ & 0xc0) << 5); colour_base &= 0x2000 | ((row_ & 0xc0) << 5); @@ -340,7 +331,7 @@ void TMS9918::run_for(const HalfCycles cycles) { // Colours are collected every fourth window starting from window 29. const int colours_start = (access_pointer_ - 29 + 3) >> 2; const int colours_end = (end - 29 + 3) >> 2; - if(screen_mode_ != 1) { + if(current_mode_ != ScreenMode::Graphics) { for(int column = colours_start; column < colours_end; ++column) { colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] >> 3)]; } @@ -354,8 +345,8 @@ void TMS9918::run_for(const HalfCycles cycles) { const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2; const int pattern_buffer_end = (end - 30 + 3) >> 2; - // Multicolour mode uss a different function of row to pick bytes - const int row = (screen_mode_ != 2) ? (row_ & 7) : ((row_ >> 2) & 7); + // Multicolour mode uses a different function of row to pick bytes. + const int row = (current_mode_ != ScreenMode::MultiColour) ? (row_ & 7) : ((row_ >> 2) & 7); for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + row]; } @@ -394,7 +385,7 @@ void TMS9918::run_for(const HalfCycles cycles) { // -------------------- // Output video stream. // -------------------- - if(row_ < 192 && !blank_screen_) { + if(row_ < 192 && current_mode_ != ScreenMode::Blank) { // ---------------------- // Output horizontal sync // ---------------------- @@ -431,25 +422,27 @@ void TMS9918::run_for(const HalfCycles cycles) { default: break; case LineMode::Text: { - const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; + if(pixel_target_) { + const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; - const int shift = (output_column_ - first_pixel_column_) % 6; - int byte_column = (output_column_ - first_pixel_column_) / 6; - int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; - int pixels_left = pixels_end - output_column_; - int length = std::min(pixels_left, 6 - shift); - while(true) { - pixels_left -= length; - for(int c = 0; c < length; ++c) { - pixel_target_[c] = colours[pattern&0x01]; - pattern >>= 1; + const int shift = (output_column_ - first_pixel_column_) % 6; + int byte_column = (output_column_ - first_pixel_column_) / 6; + int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; + int pixels_left = pixels_end - output_column_; + int length = std::min(pixels_left, 6 - shift); + while(true) { + pixels_left -= length; + for(int c = 0; c < length; ++c) { + pixel_target_[c] = colours[pattern&0x01]; + pattern >>= 1; + } + pixel_target_ += length; + + if(!pixels_left) break; + length = std::min(6, pixels_left); + byte_column++; + pattern = reverse_table.map[pattern_buffer_[byte_column]]; } - pixel_target_ += length; - - if(!pixels_left) break; - length = std::min(6, pixels_left); - byte_column++; - pattern = reverse_table.map[pattern_buffer_[byte_column]]; } output_column_ = pixels_end; } break; @@ -472,7 +465,7 @@ void TMS9918::run_for(const HalfCycles cycles) { // Paint the background tiles. const int pixels_left = pixels_end - output_column_; - if(screen_mode_ == ScreenMode::MultiColour) { + if(current_mode_ == ScreenMode::MultiColour) { int pixel_location = output_column_ - first_pixel_column_; for(int c = 0; c < pixels_left; ++c) { pixel_target_[c] = palette[ @@ -615,9 +608,11 @@ void TMS9918::run_for(const HalfCycles cycles) { row_ = (row_ + 1) % frame_lines_; if(row_ == 192) status_ |= StatusInterrupt; - screen_mode_ = next_screen_mode_; - blank_screen_ = next_blank_screen_; - switch(screen_mode_) { + // Establish the output mode for the next line. + set_current_mode(); + + // Based on the output mode, pick a line mode. + switch(current_mode_) { case ScreenMode::Text: line_mode_ = LineMode::Text; first_pixel_column_ = 69; @@ -629,7 +624,7 @@ void TMS9918::run_for(const HalfCycles cycles) { first_right_border_column_ = 319; break; } - if(blank_screen_ || (row_ >= 192 && row_ != frame_lines_-1)) line_mode_ = LineMode::Refresh; + if((current_mode_ == ScreenMode::Blank) || (row_ >= 192 && row_ != frame_lines_-1)) line_mode_ = LineMode::Refresh; } } } @@ -677,13 +672,22 @@ void TMS9918::set_register(int address, uint8_t value) { // This is a write to a register. switch(value) { case 0: - next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1); + if(is_sega_vdp(personality_)) { + master_system_.vertical_scroll_lock = !!(low_write_ & 0x80); + master_system_.horizontal_scroll_lock = !!(low_write_ & 0x40); + master_system_.hide_left_column = !!(low_write_ & 0x20); + master_system_.enable_line_interrupts = !!(low_write_ & 0x10); + master_system_.shift_sprites_8px_left = !!(low_write_ & 0x08); + master_system_.mode4_enable = !!(low_write_ & 0x04); + } + mode2_enable_ = !!(low_write_ & 0x02); break; case 1: - next_blank_screen_ = !(low_write_ & 0x40); + blank_display_ = !(low_write_ & 0x40); generate_interrupts_ = !!(low_write_ & 0x20); - next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 2); + mode1_enable_ = !!(low_write_ & 0x10); + mode3_enable_ = !!(low_write_ & 0x08); sprites_16x16_ = !!(low_write_ & 0x02); sprites_magnified_ = !!(low_write_ & 0x01); diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index cc9a4d64a..1c8f9216d 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -26,6 +26,8 @@ enum Personality { GGVDP, }; +#define is_sega_vdp(x) x >= SMSVDP + class Base { protected: Base(Personality p); @@ -47,17 +49,19 @@ class Base { uint8_t low_write_ = 0; // The various register flags. - int next_screen_mode_ = 0, screen_mode_ = 0; - bool next_blank_screen_ = true, blank_screen_ = true; + bool mode1_enable_ = false; + bool mode2_enable_ = false; + bool mode3_enable_ = false; + bool blank_display_ = false; bool sprites_16x16_ = false; bool sprites_magnified_ = false; bool generate_interrupts_ = false; int sprite_height_ = 8; - uint16_t pattern_name_address_ = 0; - uint16_t colour_table_address_ = 0; - uint16_t pattern_generator_table_address_ = 0; - uint16_t sprite_attribute_table_address_ = 0; - uint16_t sprite_generator_table_address_ = 0; + size_t pattern_name_address_ = 0; + size_t colour_table_address_ = 0; + size_t pattern_generator_table_address_ = 0; + size_t sprite_attribute_table_address_ = 0; + size_t sprite_generator_table_address_ = 0; uint8_t text_colour_ = 0; uint8_t background_colour_ = 0; @@ -105,6 +109,62 @@ class Base { inline void test_sprite(int sprite_number, int screen_row); inline void get_sprite_contents(int start, int cycles, int screen_row); + struct { + bool vertical_scroll_lock = false; + bool horizontal_scroll_lock = false; + bool hide_left_column = false; + bool enable_line_interrupts = false; + bool shift_sprites_8px_left = false; + bool mode4_enable = false; + } master_system_; + + enum class ScreenMode { + Blank, + Text, + MultiColour, + ColouredText, + Graphics, + SMSMode4 + } current_mode_; + int height_ = 192; + void set_current_mode() { + if(blank_display_) { + current_mode_ = ScreenMode::Blank; + return; + } + + if(is_sega_vdp(personality_) && master_system_.mode4_enable) { + current_mode_ = ScreenMode::SMSMode4; + height_ = 192; + if(mode2_enable_ && mode1_enable_) height_ = 224; + if(mode2_enable_ && mode3_enable_) height_ = 240; + return; + } + + if(!mode1_enable_ && !mode2_enable_ && !mode3_enable_) { + current_mode_ = ScreenMode::ColouredText; + return; + } + + if(mode1_enable_ && !mode2_enable_ && !mode3_enable_) { + current_mode_ = ScreenMode::Text; + return; + } + + if(!mode1_enable_ && mode2_enable_ && !mode3_enable_) { + current_mode_ = ScreenMode::Graphics; + return; + } + + if(!mode1_enable_ && !mode2_enable_ && mode3_enable_) { + current_mode_ = ScreenMode::MultiColour; + return; + } + + // TODO: undocumented TMS modes. + current_mode_ = ScreenMode::Blank; + } + /* #define slot(n) \ if(use_end && end+1 == n) return;\ From 35c2e74af820c1501b122d24b1751f537bd76375 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 27 Sep 2018 22:33:41 -0400 Subject: [PATCH 16/88] Attempts to establish a coroutine-ish structure for access patterns. The Master System mode, inevitably, is the test case. --- Components/9918/9918.cpp | 12 ++ Components/9918/Implementation/9918Base.hpp | 217 ++++++++++---------- 2 files changed, 126 insertions(+), 103 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 64c33fc04..4b353844d 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -259,6 +259,15 @@ void TMS9918::run_for(const HalfCycles cycles) { switch(line_mode_) { default: break; + case LineMode::SMS: + if(access_slot < 171) { + fetch_sms(access_pointer_ >> 1, access_slot); + } else { + fetch_sms(access_pointer_ >> 1, access_slot); + } + access_pointer_ = column_; + break; + case LineMode::Text: access_pointer_ = std::min(30, access_slot); if(access_pointer_ >= 30 && access_pointer_ < 150) { @@ -618,6 +627,9 @@ void TMS9918::run_for(const HalfCycles cycles) { first_pixel_column_ = 69; first_right_border_column_ = 309; break; + case ScreenMode::SMSMode4: + line_mode_ = LineMode::SMS; + break; default: line_mode_ = LineMode::Character; first_pixel_column_ = 63; diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 1c8f9216d..79ed889d9 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -79,9 +79,10 @@ class Base { // Horizontal selections. enum class LineMode { - Text = 0, - Character = 1, - Refresh = 2 + Text, + Character, + Refresh, + SMS } line_mode_ = LineMode::Text; int first_pixel_column_, first_right_border_column_; @@ -116,6 +117,8 @@ class Base { bool enable_line_interrupts = false; bool shift_sprites_8px_left = false; bool mode4_enable = false; + + uint8_t colour_ram_[32]; } master_system_; enum class ScreenMode { @@ -165,121 +168,129 @@ class Base { current_mode_ = ScreenMode::Blank; } -/* + + void external_slot() { + // TODO: write or read a value if one is queued and ready to read/write. + // (and, later: update the command engine, if this is an MSX2). + } + #define slot(n) \ if(use_end && end+1 == n) return;\ case n +#define external_slot(n) \ + slot(n): external_slot() + template void fetch_sms(int start, int end) { #define sprite_render_block(location, sprite) \ - slot(location): \ - sprite_sets_[active_sprite_set_].info[0] = + slot(location): \ + slot(location+1): \ + slot(location+2): \ + slot(location+3): \ + slot(location+4): \ + slot(location+5): + +/* + TODO: sprite_render_block should fetch: + - sprite n, x position and name + - sprite n+1, x position and name + - sprite n, tile graphic first word + - sprite n, tile graphic second word + - sprite n+1, tile graphic first word + - sprite n+1, tile graphic second word +*/ + +#define sprite_y_read(location) \ + slot(location): + +/* + TODO: sprite_y_read should fetch: + - sprite n and n+1, y position +*/ + +#define background_render_block(location) \ + slot(location): \ + external_slot(location+1); \ + slot(location+2): \ + slot(location+3): \ + slot(location+4): \ + sprite_y_read(location+5); \ + slot(location+6): \ + slot(location+7): \ + slot(location+8): \ + sprite_y_read(location+9); \ + slot(location+10): \ + slot(location+11): \ + slot(location+12): \ + sprite_y_read(location+13); \ + slot(location+14): \ + slot(location+15): + +/* + TODO: background_render_block should fetch: + - column n, name + (external slot) + - column n, tile graphic first word + - column n, tile graphic second word + - column n+1, name + (sprite y fetch) + - column n+1, tile graphic first word + - column n+1, tile graphic second word + - column n+2, name + (sprite y fetch) + - column n+2, tile graphic first word + - column n+2, tile graphic second word + - column n+3, name + (sprite y fetch) + - column n+3, tile graphic first word + - column n+3, tile graphic second word +*/ + switch(start) { default: -// slot(1): + sprite_render_block(0, 0); + sprite_render_block(6, 2); + external_slot(12); + external_slot(13); + external_slot(14); + external_slot(15); + external_slot(16); + sprite_render_block(17, 4); + sprite_render_block(23, 6); + external_slot(29); + external_slot(30); + sprite_y_read(31); + sprite_y_read(32); + sprite_y_read(33); + sprite_y_read(34); + sprite_y_read(35); + sprite_y_read(36); + sprite_y_read(37); + sprite_y_read(38); + background_render_block(39); + background_render_block(55); + background_render_block(71); + background_render_block(87); + background_render_block(103); + background_render_block(119); + background_render_block(135); + background_render_block(151); + external_slot(167); + external_slot(168); + external_slot(169); + external_slot(170); return; } + +#undef background_render_block +#undef sprite_y_read +#undef sprite_render_block } +#undef external_slot #undef slot -*/ -// // Contains tables describing the memory access patterns and, implicitly, -// // the timing of video generation. -// enum Operation { -// HSyncOn, -// HSyncOff, -// ColourBurstOn, -// ColourBurstOff, -// -// /// A memory access slot that is available for an external read or write. -// External, -// -// /// A refresh cycle; neither used for video fetching nor available for external use. -// Refresh, -// -// /// A slot that reads the next sprite location for -// ReadSpriteY, -// -// /*! -// Column N Name Table Read -// [= 1 slot] -// */ -// NameTableRead, -// -// /*! -// Column N Pattern Table Read -// [= 1 slot] -// */ -// PatternTableRead, -// -// /*! -// Y0, X0, N0, C0, Pattern 0 (1), Pattern 0 (2), -// Y1, X1, N1, C1, Pattern 1 (1), Pattern 1 (2), -// Y2, X2 -// [= 14 slots] -// */ -// TMSSpriteFetch1, -// -// /*! -// N2, C2, Pattern 2 (1), Pattern 2 (2), -// Y3, X3, N3, C3, Pattern 3 (1), Pattern 3 (2), -// [= 10 slots] -// */ -// TMSSpriteFetch2, -// -// /*! -// Sprite N fetch, Sprite N+1 fetch [...] -// */ -// TMSSpriteYFetch, -// -// /*! -// Colour N, Pattern N, -// Name N+1, -// [= 3 slots] -// */ -// TMSBackgroundRenderBlock, -// -// /*! -// Colour N, Pattern N, -// */ -// TMSColourPatternFetch, -// -// /*! -// Sprite N X/Name Read -// Sprite N+1 X/Name Read -// Sprite N Tile read (1st word) -// Sprite N Tile read (2nd word) -// Sprite N+1 Tile Read (1st word) -// Sprite N+1 Tile Read (2nd word) -// [= 6 slots] -// */ -// SMSSpriteRenderBlock, -// -// /*! -// Column N Tile Read (1st word) -// Column N Tile Read (2nd word) -// Column N+1 Name Table Read -// Sprite (16+N*1.5) Y Read (Reads Y of 2 sprites) -// Column N+1 Tile Read (1st word) -// Column N+1 Tile Read (2nd word) -// Column N+2 Name Table Read -// Sprite (16+N*1.5+2) Y Read (Reads Y of 2 sprites) -// Column N+2 Tile Read (1st word) -// Column N+2 Tile Read (2nd word) -// Column N+3 Name Table Read -// Sprite (16+N*1.5+4) Y Read (Reads Y of 2 sprites) -// Column N+3 Tile Read (1st word) -// Column N+3 Tile Read (2nd word) -// [= 14 slots] -// */ -// SMSBackgroundRenderBlock, -// }; -// struct Period { -// Operation operation; -// int duration; -// }; }; } From f7e211c2456331fba7a12aa5db232dfd4beeafc4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 28 Sep 2018 20:39:14 -0400 Subject: [PATCH 17/88] Makes first attempt to put something vaguely like the Master System tile map on screen. --- Components/9918/9918.cpp | 30 +++++++++-- Components/9918/Implementation/9918Base.hpp | 58 ++++++++++++++------- 2 files changed, 67 insertions(+), 21 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 4b353844d..157250239 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -211,6 +211,9 @@ void TMS9918::run_for(const HalfCycles cycles) { if(queued_access_ != MemoryAccess::None) { int time_until_access_slot = 0; switch(line_mode_) { + case LineMode::SMS: + time_until_access_slot = 0; // TODO. + break; case LineMode::Refresh: if(column_ < 53 || column_ >= 307) time_until_access_slot = column_&1; else time_until_access_slot = 3 - ((column_ - 53)&3); @@ -271,7 +274,7 @@ void TMS9918::run_for(const HalfCycles cycles) { case LineMode::Text: access_pointer_ = std::min(30, access_slot); if(access_pointer_ >= 30 && access_pointer_ < 150) { - const int row_base = pattern_name_address_ + static_cast(row_ >> 3) * 40; + const size_t row_base = pattern_name_address_ + static_cast(row_ >> 3) * 40; const int end = std::min(150, access_slot); // Pattern names are collected every third window starting from window 30. @@ -430,6 +433,20 @@ void TMS9918::run_for(const HalfCycles cycles) { switch(line_mode_) { default: break; + case LineMode::SMS: { + if(pixel_target_) { + const int pixels_left = pixels_end - output_column_; + int pixel_location = output_column_ - first_pixel_column_; + for(int c = 0; c < pixels_left; ++c) { + pixel_target_[c] = *(uint32_t *)master_system_.tile_graphics[pixel_location >> 8]; + } + pixel_target_ += pixels_left; + } + + output_column_ = pixels_end; + } + break; + case LineMode::Text: { if(pixel_target_) { const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; @@ -629,6 +646,9 @@ void TMS9918::run_for(const HalfCycles cycles) { break; case ScreenMode::SMSMode4: line_mode_ = LineMode::SMS; + master_system_.next_column = 0; + first_pixel_column_ = 63; + first_right_border_column_ = 319; break; default: line_mode_ = LineMode::Character; @@ -672,10 +692,14 @@ void TMS9918::set_register(int address, uint8_t value) { if(value & 0x80) { switch(personality_) { default: - value &= 7; + value &= 0x7; break; case TI::TMS::SMSVDP: - value &= 0x7f; + if(value & 0x40) { + // TODO: CRAM. + return; + } + value &= 0xf; break; } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 79ed889d9..905289ff5 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -118,7 +118,14 @@ class Base { bool shift_sprites_8px_left = false; bool mode4_enable = false; - uint8_t colour_ram_[32]; + uint8_t colour_ram[32]; + + struct { + size_t offset; + uint8_t flags; + } names[32]; + uint8_t tile_graphics[32][4]; + size_t next_column = 0; } master_system_; enum class ScreenMode { @@ -208,23 +215,36 @@ class Base { - sprite n and n+1, y position */ -#define background_render_block(location) \ - slot(location): \ +#define fetch_tile_name(column) {\ + size_t address = pattern_address_base + ((column) << 1); \ + master_system_.names[column].flags = ram_[address+1]; \ + master_system_.names[column].offset = static_cast((master_system_.names[column].flags&1 | ram_[address]) << 5) + sub_row; \ + } + +#define fetch_tile(column) {\ + master_system_.tile_graphics[column][0] = ram_[master_system_.names[column].offset]; \ + master_system_.tile_graphics[column][1] = ram_[master_system_.names[column].offset+1]; \ + master_system_.tile_graphics[column][2] = ram_[master_system_.names[column].offset+2]; \ + master_system_.tile_graphics[column][3] = ram_[master_system_.names[column].offset+3]; \ + } + +#define background_render_block(location, column) \ + slot(location): fetch_tile_name(column) \ external_slot(location+1); \ slot(location+2): \ - slot(location+3): \ - slot(location+4): \ + slot(location+3): fetch_tile(column) \ + slot(location+4): fetch_tile_name(column+1) \ sprite_y_read(location+5); \ slot(location+6): \ - slot(location+7): \ - slot(location+8): \ + slot(location+7): fetch_tile(column+1) \ + slot(location+8): fetch_tile_name(column+2) \ sprite_y_read(location+9); \ slot(location+10): \ - slot(location+11): \ - slot(location+12): \ + slot(location+11): fetch_tile(column+2) \ + slot(location+12): fetch_tile_name(column+3) \ sprite_y_read(location+13); \ slot(location+14): \ - slot(location+15): + slot(location+15): fetch_tile(column+3) /* TODO: background_render_block should fetch: @@ -245,6 +265,8 @@ class Base { - column n+3, tile graphic first word - column n+3, tile graphic second word */ + const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((row_ & ~7) << 6) | 0x3800); + const size_t sub_row = static_cast((row_ & 7) << 2); switch(start) { default: @@ -267,14 +289,14 @@ class Base { sprite_y_read(36); sprite_y_read(37); sprite_y_read(38); - background_render_block(39); - background_render_block(55); - background_render_block(71); - background_render_block(87); - background_render_block(103); - background_render_block(119); - background_render_block(135); - background_render_block(151); + background_render_block(39, 0); + background_render_block(55, 4); + background_render_block(71, 8); + background_render_block(87, 12); + background_render_block(103, 16); + background_render_block(119, 20); + background_render_block(135, 24); + background_render_block(151, 28); external_slot(167); external_slot(168); external_slot(169); From 7b9bb772cacc12fba2b3db2da58f271e7753c63b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 28 Sep 2018 21:03:51 -0400 Subject: [PATCH 18/88] Corrected to give a not-exactly-indexed-correctly approximation of what's on display. --- Components/9918/9918.cpp | 14 ++++++++++++-- Components/9918/Implementation/9918Base.hpp | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 157250239..b69eb3922 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -436,9 +436,19 @@ void TMS9918::run_for(const HalfCycles cycles) { case LineMode::SMS: { if(pixel_target_) { const int pixels_left = pixels_end - output_column_; - int pixel_location = output_column_ - first_pixel_column_; + const int pixel_location = output_column_ - first_pixel_column_; for(int c = 0; c < pixels_left; ++c) { - pixel_target_[c] = *(uint32_t *)master_system_.tile_graphics[pixel_location >> 8]; + const int column = (pixel_location + c) >> 3; + const int shift = 4 + ((pixel_location + c) & 7); + int value = + (( + ((master_system_.tile_graphics[column][0] << shift) & 0x800) | + ((master_system_.tile_graphics[column][1] << (shift - 1)) & 0x400) | + ((master_system_.tile_graphics[column][2] << (shift - 2)) & 0x200) | + (master_system_.tile_graphics[column][3] << (shift - 3)) + ) >> 8) << 4; + + pixel_target_[c] = (value << 24) | (value << 16) | (value << 8) | value; } pixel_target_ += pixels_left; } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 905289ff5..b9acf0a47 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -218,7 +218,7 @@ class Base { #define fetch_tile_name(column) {\ size_t address = pattern_address_base + ((column) << 1); \ master_system_.names[column].flags = ram_[address+1]; \ - master_system_.names[column].offset = static_cast((master_system_.names[column].flags&1 | ram_[address]) << 5) + sub_row; \ + master_system_.names[column].offset = static_cast((((master_system_.names[column].flags&1) << 8) | ram_[address]) << 5) + sub_row; \ } #define fetch_tile(column) {\ @@ -265,7 +265,7 @@ class Base { - column n+3, tile graphic first word - column n+3, tile graphic second word */ - const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((row_ & ~7) << 6) | 0x3800); + const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((row_ & ~7) << 3) | 0x3800); const size_t sub_row = static_cast((row_ & 7) << 2); switch(start) { From 0d8af010b6b56598a7657977e1cde1a9271b535c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 28 Sep 2018 22:37:10 -0400 Subject: [PATCH 19/88] Takes a stab at tile reversal and vertical scrolling. --- Components/9918/9918.cpp | 19 ++++++++++++++++--- Components/9918/Implementation/9918Base.hpp | 11 ++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index b69eb3922..b3123e6a3 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -437,18 +437,19 @@ void TMS9918::run_for(const HalfCycles cycles) { if(pixel_target_) { const int pixels_left = pixels_end - output_column_; const int pixel_location = output_column_ - first_pixel_column_; + const int reverses[2] = {0, 7}; for(int c = 0; c < pixels_left; ++c) { const int column = (pixel_location + c) >> 3; - const int shift = 4 + ((pixel_location + c) & 7); + const int shift = 4 + (((pixel_location + c) & 7) ^ reverses[(master_system_.names[column].flags&2) >> 1]); int value = (( ((master_system_.tile_graphics[column][0] << shift) & 0x800) | ((master_system_.tile_graphics[column][1] << (shift - 1)) & 0x400) | ((master_system_.tile_graphics[column][2] << (shift - 2)) & 0x200) | - (master_system_.tile_graphics[column][3] << (shift - 3)) + ((master_system_.tile_graphics[column][3] << (shift - 3)) & 0x100) ) >> 8) << 4; - pixel_target_[c] = (value << 24) | (value << 16) | (value << 8) | value; + pixel_target_[c] = static_cast((value << 24) | (value << 16) | (value << 8) | value); } pixel_target_ += pixels_left; } @@ -766,6 +767,18 @@ void TMS9918::set_register(int address, uint8_t value) { text_colour_ = low_write_ >> 4; background_colour_ = low_write_ & 0xf; break; + + case 8: + if(is_sega_vdp(personality_)) { + master_system_.horizontal_scroll = low_write_; + } + break; + + case 9: + if(is_sega_vdp(personality_)) { + master_system_.vertical_scroll = low_write_; + } + break; } } else { // This is a write to the RAM pointer. diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index b9acf0a47..7fe0c541d 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -126,6 +126,9 @@ class Base { } names[32]; uint8_t tile_graphics[32][4]; size_t next_column = 0; + + uint8_t horizontal_scroll = 0; + uint8_t vertical_scroll = 0; } master_system_; enum class ScreenMode { @@ -218,7 +221,7 @@ class Base { #define fetch_tile_name(column) {\ size_t address = pattern_address_base + ((column) << 1); \ master_system_.names[column].flags = ram_[address+1]; \ - master_system_.names[column].offset = static_cast((((master_system_.names[column].flags&1) << 8) | ram_[address]) << 5) + sub_row; \ + master_system_.names[column].offset = static_cast((((master_system_.names[column].flags&1) << 8) | ram_[address]) << 5) + sub_row[(master_system_.names[column].flags&4) >> 2]; \ } #define fetch_tile(column) {\ @@ -265,8 +268,10 @@ class Base { - column n+3, tile graphic first word - column n+3, tile graphic second word */ - const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((row_ & ~7) << 3) | 0x3800); - const size_t sub_row = static_cast((row_ & 7) << 2); + + const int scrolled_row = (row_ + master_system_.vertical_scroll) % 224; + const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((scrolled_row & ~7) << 3) | 0x3800); + const size_t sub_row[2] = {static_cast((scrolled_row & 7) << 2), 28 ^ static_cast((scrolled_row & 7) << 2)}; switch(start) { default: From 23191efc054a06c3485811ad7caf1f4800380ae1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 29 Sep 2018 19:50:13 -0400 Subject: [PATCH 20/88] Starts writing and referring to colour RAM for colours. --- Components/9918/9918.cpp | 36 ++++++++++++++------- Components/9918/Implementation/9918Base.hpp | 3 +- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index b3123e6a3..49d3cd450 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -442,14 +442,16 @@ void TMS9918::run_for(const HalfCycles cycles) { const int column = (pixel_location + c) >> 3; const int shift = 4 + (((pixel_location + c) & 7) ^ reverses[(master_system_.names[column].flags&2) >> 1]); int value = - (( - ((master_system_.tile_graphics[column][0] << shift) & 0x800) | - ((master_system_.tile_graphics[column][1] << (shift - 1)) & 0x400) | - ((master_system_.tile_graphics[column][2] << (shift - 2)) & 0x200) | - ((master_system_.tile_graphics[column][3] << (shift - 3)) & 0x100) - ) >> 8) << 4; + ( + ( + ((master_system_.tile_graphics[column][3] << shift) & 0x800) | + ((master_system_.tile_graphics[column][2] << (shift - 1)) & 0x400) | + ((master_system_.tile_graphics[column][1] << (shift - 2)) & 0x200) | + ((master_system_.tile_graphics[column][0] << (shift - 3)) & 0x100) + ) >> 8 + ) | ((master_system_.names[column].flags&0x08) << 1); - pixel_target_[c] = static_cast((value << 24) | (value << 16) | (value << 8) | value); + pixel_target_[c] = master_system_.colour_ram[value]; } pixel_target_ += pixels_left; } @@ -684,9 +686,18 @@ void TMS9918::set_register(int address, uint8_t value) { if(!(address & 1)) { write_phase_ = false; - // Enqueue the write to occur at the next available slot. - read_ahead_buffer_ = value; - queued_access_ = MemoryAccess::Write; + if(master_system_.cram_is_selected) { + master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack( + static_cast((value & 0x03) << 6), + static_cast((value & 0x0c) << 4), + static_cast((value & 0x30) << 2)); + ++ram_pointer_; + // TODO: insert a CRAM dot here. + } else { + // Enqueue the write to occur at the next available slot. + read_ahead_buffer_ = value; + queued_access_ = MemoryAccess::Write; + } return; } @@ -707,7 +718,9 @@ void TMS9918::set_register(int address, uint8_t value) { break; case TI::TMS::SMSVDP: if(value & 0x40) { - // TODO: CRAM. + ram_pointer_ = static_cast(low_write_ | (value << 8)); + master_system_.cram_is_selected = true; + queued_access_ = MemoryAccess::None; return; } value &= 0xf; @@ -787,6 +800,7 @@ void TMS9918::set_register(int address, uint8_t value) { // Officially a 'read' set, so perform lookahead. queued_access_ = MemoryAccess::Read; } + master_system_.cram_is_selected = false; } } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 7fe0c541d..772edb770 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -118,7 +118,7 @@ class Base { bool shift_sprites_8px_left = false; bool mode4_enable = false; - uint8_t colour_ram[32]; + uint32_t colour_ram[32]; struct { size_t offset; @@ -129,6 +129,7 @@ class Base { uint8_t horizontal_scroll = 0; uint8_t vertical_scroll = 0; + bool cram_is_selected = false; } master_system_; enum class ScreenMode { From 48ece623e7b13dc32b297fb1df0fabc9b11c75e1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 30 Sep 2018 20:46:38 -0400 Subject: [PATCH 21/88] Adds the Sega Master System to SConstruct. --- OSBindings/SDL/SConstruct | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index 3b64bd8db..8310ed31a 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -25,6 +25,7 @@ SOURCES += glob.glob('../../Analyser/Static/Disassembler/*.cpp') SOURCES += glob.glob('../../Analyser/Static/DiskII/*.cpp') SOURCES += glob.glob('../../Analyser/Static/MSX/*.cpp') SOURCES += glob.glob('../../Analyser/Static/Oric/*.cpp') +SOURCES += glob.glob('../../Analyser/Static/Sega/*.cpp') SOURCES += glob.glob('../../Analyser/Static/ZX8081/*.cpp') SOURCES += glob.glob('../../Components/1770/*.cpp') @@ -54,6 +55,7 @@ SOURCES += glob.glob('../../Machines/Commodore/*.cpp') SOURCES += glob.glob('../../Machines/Commodore/1540/Implementation/*.cpp') SOURCES += glob.glob('../../Machines/Commodore/Vic-20/*.cpp') SOURCES += glob.glob('../../Machines/Electron/*.cpp') +SOURCES += glob.glob('../../Machines/MasterSytem/*.cpp') SOURCES += glob.glob('../../Machines/MSX/*.cpp') SOURCES += glob.glob('../../Machines/Oric/*.cpp') SOURCES += glob.glob('../../Machines/Utility/*.cpp') From e9328d819e2a60f33058fadd38e90f088029c02f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 30 Sep 2018 20:47:03 -0400 Subject: [PATCH 22/88] Switches to RGB output, at least for development. --- Machines/MasterSystem/MasterSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index dae6df17d..4c67880ec 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -130,7 +130,7 @@ class ConcreteMachine: void setup_output(float aspect_ratio) override { vdp_.reset(new TI::TMS::TMS9918(model_ == Analyser::Static::Sega::Target::Model::SG1000 ? TI::TMS::TMS9918A : TI::TMS::SMSVDP)); - get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); +// get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); } void close_output() override { From 91aa8f929543d61b02d8ad918fc32355184a9196 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 30 Sep 2018 20:47:26 -0400 Subject: [PATCH 23/88] Amps up colour content a little. --- Components/9918/9918.cpp | 7 ++++--- Components/9918/Implementation/9918Base.hpp | 14 +++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 49d3cd450..a257e1eb2 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -688,9 +688,10 @@ void TMS9918::set_register(int address, uint8_t value) { if(master_system_.cram_is_selected) { master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack( - static_cast((value & 0x03) << 6), - static_cast((value & 0x0c) << 4), - static_cast((value & 0x30) << 2)); + static_cast(((value >> 0) & 3) * 255 / 3), + static_cast(((value >> 2) & 3) * 255 / 3), + static_cast(((value >> 4) & 3) * 255 / 3) + ); ++ram_pointer_; // TODO: insert a CRAM dot here. } else { diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 772edb770..4ff6c2513 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -222,7 +222,9 @@ class Base { #define fetch_tile_name(column) {\ size_t address = pattern_address_base + ((column) << 1); \ master_system_.names[column].flags = ram_[address+1]; \ - master_system_.names[column].offset = static_cast((((master_system_.names[column].flags&1) << 8) | ram_[address]) << 5) + sub_row[(master_system_.names[column].flags&4) >> 2]; \ + master_system_.names[column].offset = static_cast( \ + (((master_system_.names[column].flags&1) << 8) | ram_[address]) << 5 \ + ) + sub_row[(master_system_.names[column].flags&4) >> 2]; \ } #define fetch_tile(column) {\ @@ -274,6 +276,13 @@ class Base { const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((scrolled_row & ~7) << 3) | 0x3800); const size_t sub_row[2] = {static_cast((scrolled_row & 7) << 2), 28 ^ static_cast((scrolled_row & 7) << 2)}; + /* + To add, relative to the times below: + + hsync active at cycle 14; + hsync inactive at cycle 27; + */ + switch(start) { default: sprite_render_block(0, 0); @@ -319,6 +328,9 @@ class Base { #undef external_slot #undef slot + void draw_sms(int start, int end) { + } + }; } From cc99b0f532c784114d0b19c6a44346fba0c81eec Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 30 Sep 2018 20:48:55 -0400 Subject: [PATCH 24/88] Fixes typo. --- OSBindings/SDL/SConstruct | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index 8310ed31a..ebcbefbad 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -55,7 +55,7 @@ SOURCES += glob.glob('../../Machines/Commodore/*.cpp') SOURCES += glob.glob('../../Machines/Commodore/1540/Implementation/*.cpp') SOURCES += glob.glob('../../Machines/Commodore/Vic-20/*.cpp') SOURCES += glob.glob('../../Machines/Electron/*.cpp') -SOURCES += glob.glob('../../Machines/MasterSytem/*.cpp') +SOURCES += glob.glob('../../Machines/MasterSystem/*.cpp') SOURCES += glob.glob('../../Machines/MSX/*.cpp') SOURCES += glob.glob('../../Machines/Oric/*.cpp') SOURCES += glob.glob('../../Machines/Utility/*.cpp') From 60bab8fdf1fa1cdfc87ee6c4fa227cb16c3a0cf8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 1 Oct 2018 23:03:17 -0400 Subject: [PATCH 25/88] Starts to reformulate TMS collection as coroutines. For the time being, thereby breaks all video. A static screen of the border colour is all you'll see. --- Components/9918/9918.cpp | 400 +++++--------- Components/9918/Implementation/9918Base.hpp | 554 ++++++++++++++++---- 2 files changed, 587 insertions(+), 367 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index a257e1eb2..d5ae593f4 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -15,38 +15,6 @@ using namespace TI::TMS; namespace { -const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) { - uint32_t result = 0; - uint8_t *const result_ptr = reinterpret_cast(&result); - result_ptr[0] = r; - result_ptr[1] = g; - result_ptr[2] = b; - result_ptr[3] = 0; - return result; -} - -const uint32_t palette[16] = { - palette_pack(0, 0, 0), - palette_pack(0, 0, 0), - palette_pack(33, 200, 66), - palette_pack(94, 220, 120), - - palette_pack(84, 85, 237), - palette_pack(125, 118, 252), - palette_pack(212, 82, 77), - palette_pack(66, 235, 245), - - palette_pack(252, 85, 84), - palette_pack(255, 121, 120), - palette_pack(212, 193, 84), - palette_pack(230, 206, 128), - - palette_pack(33, 176, 59), - palette_pack(201, 91, 186), - palette_pack(204, 204, 204), - palette_pack(255, 255, 255) -}; - const uint8_t StatusInterrupt = 0x80; const uint8_t StatusFifthSprite = 0x40; @@ -120,7 +88,7 @@ Outputs::CRT::CRT *TMS9918::get_crt() { } void Base::test_sprite(int sprite_number, int screen_row) { - if(!(status_ & StatusFifthSprite)) { +/* if(!(status_ & StatusFifthSprite)) { status_ = static_cast((status_ & ~31) | sprite_number); } if(sprites_stopped_) @@ -145,11 +113,11 @@ void Base::test_sprite(int sprite_number, int screen_row) { SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[active_sprite_slot]; sprite.index = sprite_number; sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0); - sprite_sets_[active_sprite_set_].active_sprite_slot++; + sprite_sets_[active_sprite_set_].active_sprite_slot++;*/ } void Base::get_sprite_contents(int field, int cycles_left, int screen_row) { - int sprite_id = field / 6; +/* int sprite_id = field / 6; field %= 6; while(true) { @@ -178,7 +146,7 @@ void Base::get_sprite_contents(int field, int cycles_left, int screen_row) { if(!cycles_left) return; field = 0; sprite_id++; - } + }*/ } void TMS9918::run_for(const HalfCycles cycles) { @@ -189,7 +157,7 @@ void TMS9918::run_for(const HalfCycles cycles) { // Keep a count of cycles separate from internal counts to avoid // potential errors mapping back and forth. - half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(frame_lines_ * 228 * 2); + half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(mode_timing_.total_lines * 228 * 2); // Convert 456 clocked half cycles per line to 342 internal cycles per line; // the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised @@ -201,203 +169,114 @@ void TMS9918::run_for(const HalfCycles cycles) { while(int_cycles) { // Determine how much time has passed in the remainder of this line, and proceed. - int cycles_left = std::min(342 - column_, int_cycles); + const int cycles_left = std::min(342 - column_, int_cycles); + const int end_column = column_ + cycles_left; + // ------------------------ + // Perform memory accesses. + // ------------------------ +#define fetch(function) \ + if(end_column < 171) { \ + function(column_, end_column);\ + } else {\ + function(column_, end_column);\ + } - // ------------------------------------ - // Potentially perform a memory access. - // ------------------------------------ - if(queued_access_ != MemoryAccess::None) { - int time_until_access_slot = 0; - switch(line_mode_) { - case LineMode::SMS: - time_until_access_slot = 0; // TODO. - break; - case LineMode::Refresh: - if(column_ < 53 || column_ >= 307) time_until_access_slot = column_&1; - else time_until_access_slot = 3 - ((column_ - 53)&3); - // i.e. 53 -> 3, 52 -> 2, 51 -> 1, 50 -> 0, etc - break; - case LineMode::Text: - if(column_ < 59 || column_ >= 299) time_until_access_slot = column_&1; - else time_until_access_slot = 5 - ((column_ + 3)%6); - // i.e. 59 -> 3, 60 -> 2, 61 -> 1, etc - break; - case LineMode::Character: - if(column_ < 9) time_until_access_slot = column_&1; - else if(column_ < 30) time_until_access_slot = 30 - column_; - else if(column_ < 37) time_until_access_slot = column_&1; - else if(column_ < 311) time_until_access_slot = 31 - ((column_ + 7)&31); - // i.e. 53 -> 3, 54 -> 2, 55 -> 1, 56 -> 0, 57 -> 31, etc - else if(column_ < 313) time_until_access_slot = column_&1; - else time_until_access_slot = 342 - column_; - break; - } - - if(cycles_left >= time_until_access_slot) { - if(queued_access_ == MemoryAccess::Write) { - ram_[ram_pointer_ & 16383] = read_ahead_buffer_; - } else { - read_ahead_buffer_ = ram_[ram_pointer_ & 16383]; - } - ram_pointer_++; - queued_access_ = MemoryAccess::None; - } + // TODO: column_ and end_column are in 342-per-line cycles; + // adjust them to a count of windows. + switch(line_mode_) { + case LineMode::Text: fetch(fetch_tms_text); break; + case LineMode::Character: fetch(fetch_tms_character); break; + case LineMode::SMS: fetch(fetch_sms); break; + case LineMode::Refresh: fetch(fetch_tms_refresh); break; } - - - column_ += cycles_left; // column_ is now the column that has been reached in this line. - int_cycles -= cycles_left; // Count down duration to run for. - - - - // ------------------------------ - // Perform video memory accesses. - // ------------------------------ - if(((row_ < 192) || (row_ == frame_lines_-1)) && (current_mode_ != ScreenMode::Blank)) { - const int sprite_row = (row_ < 192) ? row_ : -1; - const int access_slot = column_ >> 1; // There are only 171 available memory accesses per line. - switch(line_mode_) { - default: break; - - case LineMode::SMS: - if(access_slot < 171) { - fetch_sms(access_pointer_ >> 1, access_slot); - } else { - fetch_sms(access_pointer_ >> 1, access_slot); - } - access_pointer_ = column_; - break; - - case LineMode::Text: - access_pointer_ = std::min(30, access_slot); - if(access_pointer_ >= 30 && access_pointer_ < 150) { - const size_t row_base = pattern_name_address_ + static_cast(row_ >> 3) * 40; - const int end = std::min(150, access_slot); - - // Pattern names are collected every third window starting from window 30. - const int pattern_names_start = (access_pointer_ - 30 + 2) / 3; - const int pattern_names_end = (end - 30 + 2) / 3; - std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast(pattern_names_end - pattern_names_start)); - - // Patterns are collected every third window starting from window 32. - const int pattern_buffer_start = (access_pointer_ - 32 + 2) / 3; - const int pattern_buffer_end = (end - 32 + 2) / 3; - for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { - pattern_buffer_[column] = ram_[pattern_generator_table_address_ + (pattern_names_[column] << 3) + (row_ & 7)]; - } - } - break; - - case LineMode::Character: - // Four access windows: no collection. - if(access_pointer_ < 5) - access_pointer_ = std::min(5, access_slot); - - // Then ten access windows are filled with collection of sprite 3 and 4 details. - if(access_pointer_ >= 5 && access_pointer_ < 15) { - int end = std::min(15, access_slot); - get_sprite_contents(access_pointer_ - 5 + 14, end - access_pointer_, sprite_row - 1); - access_pointer_ = std::min(15, access_slot); - } - - // Four more access windows: no collection. - if(access_pointer_ >= 15 && access_pointer_ < 19) { - access_pointer_ = std::min(19, access_slot); - - // Start new sprite set if this is location 19. - if(access_pointer_ == 19) { - active_sprite_set_ ^= 1; - sprite_sets_[active_sprite_set_].active_sprite_slot = 0; - sprites_stopped_ = false; - } - } - - // Then eight access windows fetch the y position for the first eight sprites. - while(access_pointer_ < 27 && access_pointer_ < access_slot) { - test_sprite(access_pointer_ - 19, sprite_row); - access_pointer_++; - } - - // The next 128 access slots are video and sprite collection interleaved. - if(access_pointer_ >= 27 && access_pointer_ < 155) { - int end = std::min(155, access_slot); - - size_t row_base = pattern_name_address_; - size_t pattern_base = pattern_generator_table_address_; - size_t colour_base = colour_table_address_; - if(current_mode_ == ScreenMode::Graphics) { - // If this is high resolution mode, allow the row number to affect the pattern and colour addresses. - pattern_base &= 0x2000 | ((row_ & 0xc0) << 5); - colour_base &= 0x2000 | ((row_ & 0xc0) << 5); - } - row_base += (row_ << 2)&~31; - - // Pattern names are collected every fourth window starting from window 27. - const int pattern_names_start = (access_pointer_ - 27 + 3) >> 2; - const int pattern_names_end = (end - 27 + 3) >> 2; - std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast(pattern_names_end - pattern_names_start)); - - // Colours are collected every fourth window starting from window 29. - const int colours_start = (access_pointer_ - 29 + 3) >> 2; - const int colours_end = (end - 29 + 3) >> 2; - if(current_mode_ != ScreenMode::Graphics) { - for(int column = colours_start; column < colours_end; ++column) { - colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] >> 3)]; - } - } else { - for(int column = colours_start; column < colours_end; ++column) { - colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] << 3) + (row_ & 7)]; - } - } - - // Patterns are collected ever fourth window starting from window 30. - const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2; - const int pattern_buffer_end = (end - 30 + 3) >> 2; - - // Multicolour mode uses a different function of row to pick bytes. - const int row = (current_mode_ != ScreenMode::MultiColour) ? (row_ & 7) : ((row_ >> 2) & 7); - for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) { - pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + row]; - } - - // Sprite slots occur in three quarters of ever fourth window starting from window 28. - const int sprite_start = (access_pointer_ - 28 + 3) >> 2; - const int sprite_end = (end - 28 + 3) >> 2; - for(int column = sprite_start; column < sprite_end; ++column) { - if(column&3) { - test_sprite(7 + column - (column >> 2), sprite_row); - } - } - - access_pointer_ = std::min(155, access_slot); - } - - // Two access windows: no collection. - if(access_pointer_ < 157) - access_pointer_ = std::min(157, access_slot); - - // Fourteen access windows: collect initial sprite information. - if(access_pointer_ >= 157 && access_pointer_ < 171) { - int end = std::min(171, access_slot); - get_sprite_contents(access_pointer_ - 157, end - access_pointer_, sprite_row); - access_pointer_ = std::min(171, access_slot); - } - break; - } - } - // -------------------------- - // End video memory accesses. - // -------------------------- - +#undef fetch // -------------------- // Output video stream. // -------------------- - if(row_ < 192 && current_mode_ != ScreenMode::Blank) { + + if(line_mode_ == LineMode::Refresh) { + if(row_ >= mode_timing_.first_vsync_line && row_ < mode_timing_.first_vsync_line+3) { + // Vertical sync. + if(column_ == 342) { + crt_->output_sync(342 * 4); + } + } else { + // Right border. + if(column_ < 15 && end_column >= 15) { + output_border(15); + } + + // Blanking region. + if(column_ < 73 && end_column >= 73) { + crt_->output_blank(8*4); + crt_->output_sync(26*4); + crt_->output_blank(2*4); + crt_->output_default_colour_burst(14*4); + crt_->output_blank(8*4); + } + + // Most of line. + if(end_column == 342) { + output_border(342 - 73); + } + } + } else { + // Right border. + if(column_ < 15 && end_column >= 15) { + output_border(15); + } + + // Blanking region. + if(column_ < 73 && end_column >= 73) { + crt_->output_blank(8*4); + crt_->output_sync(26*4); + crt_->output_blank(2*4); + crt_->output_default_colour_burst(14*4); + crt_->output_blank(8*4); + } + + // Left border. + if(column_ < mode_timing_.first_pixel_output_column && end_column >= mode_timing_.first_pixel_output_column) { + output_border(mode_timing_.first_pixel_output_column - 73); + } + + // In pixel area: + const int pixel_start = std::max(column_, mode_timing_.first_pixel_output_column); + const int pixel_end = std::min(end_column, mode_timing_.next_border_column); + if(pixel_end > pixel_start) { + output_border(pixel_end - pixel_start); +// switch(screen_mode_) { +// case ScreenMode::Text: +// break; +// +// case ScreenMode::ColouredText: +// case ScreenMode::Graphics: +// break; +// +// case ScreenMode::SMSMode4: +// break; +// +// default: break; +// } + } + + // Additional right border, if called for. + if(mode_timing_.next_border_column != 342 && column_ == 342) { + output_border(342 - mode_timing_.next_border_column); + } + } + + + column_ = end_column; // column_ is now the column that has been reached in this line. + int_cycles -= cycles_left; // Count down duration to run for. + + +/* if(row_ < 192 && current_mode_ != ScreenMode::Blank) { // ---------------------- // Output horizontal sync // ---------------------- @@ -616,23 +495,7 @@ void TMS9918::run_for(const HalfCycles cycles) { output_border(column_ - output_column_); output_column_ = column_; } - } else if(row_ >= first_vsync_line_ && row_ < first_vsync_line_+3) { - // Vertical sync. - if(column_ == 342) { - crt_->output_sync(342 * 4); - } - } else { - // Blank. - if(!output_column_ && column_ >= 26) { - crt_->output_sync(13 * 4); - crt_->output_default_colour_burst(13 * 4); - output_column_ = 26; - } - if(output_column_ >= 26) { - output_border(column_ - output_column_); - output_column_ = column_; - } - } + } }*/ // ----------------- // End video stream. // ----------------- @@ -643,40 +506,46 @@ void TMS9918::run_for(const HalfCycles cycles) { // Prepare for next line, potentially. // ----------------------------------- if(column_ == 342) { - access_pointer_ = column_ = output_column_ = 0; - row_ = (row_ + 1) % frame_lines_; - if(row_ == 192) status_ |= StatusInterrupt; + column_ = 0; + row_ = (row_ + 1) % mode_timing_.total_lines; + if(row_ == mode_timing_.pixel_lines) status_ |= StatusInterrupt; // Establish the output mode for the next line. set_current_mode(); // Based on the output mode, pick a line mode. - switch(current_mode_) { + mode_timing_.first_pixel_output_column = 88; + mode_timing_.next_border_column = 344; + mode_timing_.maximum_visible_sprites = 4; + switch(screen_mode_) { case ScreenMode::Text: line_mode_ = LineMode::Text; - first_pixel_column_ = 69; - first_right_border_column_ = 309; + mode_timing_.first_pixel_output_column = 48; + mode_timing_.next_border_column = 168; + mode_timing_.maximum_visible_sprites = 8; break; case ScreenMode::SMSMode4: line_mode_ = LineMode::SMS; - master_system_.next_column = 0; - first_pixel_column_ = 63; - first_right_border_column_ = 319; break; default: line_mode_ = LineMode::Character; - first_pixel_column_ = 63; - first_right_border_column_ = 319; break; } - if((current_mode_ == ScreenMode::Blank) || (row_ >= 192 && row_ != frame_lines_-1)) line_mode_ = LineMode::Refresh; + + if((screen_mode_ == ScreenMode::Blank) || (row_ >= mode_timing_.pixel_lines && row_ != mode_timing_.total_lines-1)) line_mode_ = LineMode::Refresh; } } } void Base::output_border(int cycles) { pixel_target_ = reinterpret_cast(crt_->allocate_write_area(1)); - if(pixel_target_) *pixel_target_ = palette[background_colour_]; + if(pixel_target_) { + if(is_sega_vdp(personality_)) { + *pixel_target_ = master_system_.colour_ram[16 + background_colour_]; + } else { + *pixel_target_ = palette[background_colour_]; + } + } crt_->output_level(static_cast(cycles) * 4); } @@ -686,19 +555,9 @@ void TMS9918::set_register(int address, uint8_t value) { if(!(address & 1)) { write_phase_ = false; - if(master_system_.cram_is_selected) { - master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack( - static_cast(((value >> 0) & 3) * 255 / 3), - static_cast(((value >> 2) & 3) * 255 / 3), - static_cast(((value >> 4) & 3) * 255 / 3) - ); - ++ram_pointer_; - // TODO: insert a CRAM dot here. - } else { - // Enqueue the write to occur at the next available slot. - read_ahead_buffer_ = value; - queued_access_ = MemoryAccess::Write; - } + // Enqueue the write to occur at the next available slot. + read_ahead_buffer_ = value; + queued_access_ = MemoryAccess::Write; return; } @@ -720,16 +579,14 @@ void TMS9918::set_register(int address, uint8_t value) { case TI::TMS::SMSVDP: if(value & 0x40) { ram_pointer_ = static_cast(low_write_ | (value << 8)); + queued_access_ = MemoryAccess::Write; master_system_.cram_is_selected = true; - queued_access_ = MemoryAccess::None; return; } value &= 0xf; break; } -// printf("%02x to %d\n", low_write_, value); - // This is a write to a register. switch(value) { case 0: @@ -823,10 +680,11 @@ uint8_t TMS9918::get_register(int address) { } HalfCycles TMS9918::get_time_until_interrupt() { + // TODO: line interrupts. if(!generate_interrupts_) return HalfCycles(-1); if(get_interrupt_line()) return HalfCycles(0); - const int half_cycles_per_frame = frame_lines_ * 228 * 2; + const int half_cycles_per_frame = mode_timing_.total_lines * 228 * 2; int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame; return HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame); } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 4ff6c2513..1f4fd60ac 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -29,26 +29,64 @@ enum Personality { #define is_sega_vdp(x) x >= SMSVDP class Base { + public: + static const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) { + uint32_t result = 0; + uint8_t *const result_ptr = reinterpret_cast(&result); + result_ptr[0] = r; + result_ptr[1] = g; + result_ptr[2] = b; + result_ptr[3] = 0; + return result; + } + protected: + // The default TMS palette. + const uint32_t palette[16] = { + palette_pack(0, 0, 0), + palette_pack(0, 0, 0), + palette_pack(33, 200, 66), + palette_pack(94, 220, 120), + + palette_pack(84, 85, 237), + palette_pack(125, 118, 252), + palette_pack(212, 82, 77), + palette_pack(66, 235, 245), + + palette_pack(252, 85, 84), + palette_pack(255, 121, 120), + palette_pack(212, 193, 84), + palette_pack(230, 206, 128), + + palette_pack(33, 176, 59), + palette_pack(201, 91, 186), + palette_pack(204, 204, 204), + palette_pack(255, 255, 255) + }; + Base(Personality p); Personality personality_; std::unique_ptr crt_; + // Holds the contents of this VDP's connected DRAM. std::vector ram_; + // Holds the state of the DRAM/CRAM-access mechanism. uint16_t ram_pointer_ = 0; uint8_t read_ahead_buffer_ = 0; enum class MemoryAccess { Read, Write, None } queued_access_ = MemoryAccess::None; + // Holds the main status register. uint8_t status_ = 0; - bool write_phase_ = false; - uint8_t low_write_ = 0; + // Current state of programmer input. + bool write_phase_ = false; // Determines whether the VDP is expecting the low or high byte of a write. + uint8_t low_write_ = 0; // Buffers the low byte of a write. - // The various register flags. + // Various programmable flags. bool mode1_enable_ = false; bool mode2_enable_ = false; bool mode3_enable_ = false; @@ -57,6 +95,7 @@ class Base { bool sprites_magnified_ = false; bool generate_interrupts_ = false; int sprite_height_ = 8; + size_t pattern_name_address_ = 0; size_t colour_table_address_ = 0; size_t pattern_generator_table_address_ = 0; @@ -66,30 +105,102 @@ class Base { uint8_t text_colour_ = 0; uint8_t background_colour_ = 0; + // Internal mechanisms for position tracking. HalfCycles half_cycles_into_frame_; int column_ = 0, row_ = 0, output_column_ = 0; int cycles_error_ = 0; + int access_pointer_ = 0; + + // Internal storage for the pixel part of line serialisation. uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr; + // A helper function to output the current border colour for + // the number of cycles supplied. void output_border(int cycles); - // Vertical timing details. - int frame_lines_ = 262; - int first_vsync_line_ = 227; + // A struct to contain timing information for the current mode. + struct { + /* + Vertical layout: - // Horizontal selections. + Lines 0 to [pixel_lines]: standard data fetch and drawing will occur. + ... to [first_vsync_line]: refresh fetches will occur and border will be output. + .. to [2.5 or 3 lines later]: vertical sync is output. + ... to [total lines - 1]: refresh fetches will occur and border will be output. + ... for one line: standard data fetch will occur, without drawing. + */ + int total_lines = 262; + int pixel_lines = 192; + int first_vsync_line = 227; + + /* + Horizontal layout (on a 342-cycle clock): + + 15 cycles right border + 58 cycles blanking & sync + 13 cycles left border + + ... i.e. to cycle 86, then: + + border up to first_pixel_output_column; + pixels up to next_border_column; + border up to the end. + + e.g. standard 256-pixel modes will want to set + first_pixel_output_column = 86, next_border_column = 342. + */ + int first_pixel_output_column; + int next_border_column; + + // Maximum number of sprite slots to populate; + // if sprites beyond this number should be visible + // then the appropriate status information will be set. + int maximum_visible_sprites = 4; + } mode_timing_; + + // The line mode describes the proper timing diagram for the current line. enum class LineMode { Text, Character, Refresh, SMS } line_mode_ = LineMode::Text; - int first_pixel_column_, first_right_border_column_; + // Temporary buffers collect a representation of this line prior to pixel serialisation. uint8_t pattern_names_[40]; uint8_t pattern_buffer_[40]; uint8_t colour_buffer_[40]; + // Extra information that affects the Master System output mode. + struct { + // Programmer-set flags. + bool vertical_scroll_lock = false; + bool horizontal_scroll_lock = false; + bool hide_left_column = false; + bool enable_line_interrupts = false; + bool shift_sprites_8px_left = false; + bool mode4_enable = false; + uint8_t horizontal_scroll = 0; + uint8_t vertical_scroll = 0; + + // The Master System's additional colour RAM. + uint32_t colour_ram[32]; + bool cram_is_selected = false; + + // Temporary buffers for a line of Master System graphics. + struct { + size_t offset; + uint8_t flags; + } names[32]; + uint8_t tile_graphics[32][4]; + size_t next_column = 0; + } master_system_; + + // Holds results of sprite data fetches that occur on this + // line. Therefore has to contain: up to four or eight sets + // of sprite data for this line, and its horizontal position, + // plus a growing list of which sprites are selected for + // the next line. struct SpriteSet { struct ActiveSprite { int index = 0; @@ -100,38 +211,14 @@ class Base { int shift_position = 0; } active_sprites[8]; - int active_sprite_slot = 0; - } sprite_sets_[2]; - int active_sprite_set_ = 0; - bool sprites_stopped_ = false; - int access_pointer_ = 0; + int active_sprite_slot = 0; + bool sprites_stopped_ = false; + } sprite_set_; inline void test_sprite(int sprite_number, int screen_row); inline void get_sprite_contents(int start, int cycles, int screen_row); - struct { - bool vertical_scroll_lock = false; - bool horizontal_scroll_lock = false; - bool hide_left_column = false; - bool enable_line_interrupts = false; - bool shift_sprites_8px_left = false; - bool mode4_enable = false; - - uint32_t colour_ram[32]; - - struct { - size_t offset; - uint8_t flags; - } names[32]; - uint8_t tile_graphics[32][4]; - size_t next_column = 0; - - uint8_t horizontal_scroll = 0; - uint8_t vertical_scroll = 0; - bool cram_is_selected = false; - } master_system_; - enum class ScreenMode { Blank, Text, @@ -139,50 +226,72 @@ class Base { ColouredText, Graphics, SMSMode4 - } current_mode_; - int height_ = 192; + } screen_mode_; void set_current_mode() { if(blank_display_) { - current_mode_ = ScreenMode::Blank; + screen_mode_ = ScreenMode::Blank; return; } if(is_sega_vdp(personality_) && master_system_.mode4_enable) { - current_mode_ = ScreenMode::SMSMode4; - height_ = 192; - if(mode2_enable_ && mode1_enable_) height_ = 224; - if(mode2_enable_ && mode3_enable_) height_ = 240; + screen_mode_ = ScreenMode::SMSMode4; + mode_timing_.pixel_lines = 192; + if(mode2_enable_ && mode1_enable_) mode_timing_.pixel_lines = 224; + if(mode2_enable_ && mode3_enable_) mode_timing_.pixel_lines = 240; + mode_timing_.maximum_visible_sprites = 8; return; } + mode_timing_.maximum_visible_sprites = 8; if(!mode1_enable_ && !mode2_enable_ && !mode3_enable_) { - current_mode_ = ScreenMode::ColouredText; + screen_mode_ = ScreenMode::ColouredText; return; } if(mode1_enable_ && !mode2_enable_ && !mode3_enable_) { - current_mode_ = ScreenMode::Text; + screen_mode_ = ScreenMode::Text; return; } if(!mode1_enable_ && mode2_enable_ && !mode3_enable_) { - current_mode_ = ScreenMode::Graphics; + screen_mode_ = ScreenMode::Graphics; return; } if(!mode1_enable_ && !mode2_enable_ && mode3_enable_) { - current_mode_ = ScreenMode::MultiColour; + screen_mode_ = ScreenMode::MultiColour; return; } // TODO: undocumented TMS modes. - current_mode_ = ScreenMode::Blank; + screen_mode_ = ScreenMode::Blank; } void external_slot() { // TODO: write or read a value if one is queued and ready to read/write. // (and, later: update the command engine, if this is an MSX2). + + switch(queued_access_) { + default: return; + + case MemoryAccess::Write: + if(master_system_.cram_is_selected) { + master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack( + static_cast(((read_ahead_buffer_ >> 0) & 3) * 255 / 3), + static_cast(((read_ahead_buffer_ >> 2) & 3) * 255 / 3), + static_cast(((read_ahead_buffer_ >> 4) & 3) * 255 / 3) + ); + } else { + ram_[ram_pointer_ & 16383] = read_ahead_buffer_; + } + break; + case MemoryAccess::Read: + read_ahead_buffer_ = ram_[ram_pointer_ & 16383]; + break; + } + ++ram_pointer_; + queued_access_ = MemoryAccess::None; } #define slot(n) \ @@ -190,10 +299,282 @@ class Base { case n #define external_slot(n) \ - slot(n): external_slot() + slot(n): external_slot(); + +#define external_slots_2(n) \ + external_slot(n); \ + external_slot(n+1); + +#define external_slots_4(n) \ + external_slots_2(n); \ + external_slots_2(n+2); + +#define external_slots_8(n) \ + external_slots_4(n); \ + external_slots_4(n+4); + +#define external_slots_16(n) \ + external_slots_8(n); \ + external_slots_8(n+8); + +#define external_slots_32(n) \ + external_slots_16(n); \ + external_slots_16(n+16); + +/* + Fetching routines follow below; they obey the following rules: + + 1) input is a start position and an end position; they should perform the proper + operations for the period: start <= time < end. + 2) times are measured relative to a 172-cycles-per-line clock (so: they directly + count access windows on the TMS and Master System). + 3) time 0 is the beginning of the access window immediately after the last pattern/data + block fetch that would contribute to this line, in a normal 32-column mode. So: + + * it's cycle 309 on Mattias' TMS diagram; + * it's cycle 1238 on his V9938 diagram; + * it's after the last background render block in Mask of Destiny's Master System timing diagram. + + That division point was selected, albeit arbitrarily, because it puts all the tile + fetches for a single line into the same [0, 171] period. + + 4) all of these functions are templated with a `use_end` parameter. That will be true if + end is < 172, false otherwise. So functions can use it to eliminate should-exit-not checks, + for the more usual path of execution. + + Provided for the benefit of the methods below: + + * the function external_slot(), which will perform any pending VRAM read/write. + * the macros slot(n) and external_slot(n) which can be used to schedule those things inside a + switch(start)-based implementation. + + All functions should just spool data to intermediary storage. This is because for most VDPs there is + a decoupling between fetch pattern and output pattern, and it's neater to keep the same division + for the exceptions. +*/ + + +/*********************************************** + TMS9918 Fetching Code +************************************************/ + + template void fetch_tms_refresh(int start, int end) { +#define refresh(location) external_slot(location+1) + +#define refreshes_2(location) \ + refresh(location); \ + refresh(location+2); + +#define refreshes_4(location) \ + refreshes_2(location); \ + refreshes_2(location+4); + +#define refreshes_8(location) \ + refreshes_4(location); \ + refreshes_4(location+8); + + switch(start) { + default: + + /* 44 external slots (= 44 windows) */ + external_slots_32(0) + external_slots_8(32) + external_slots_4(40) + + /* 64 refresh/external slot pairs (= 128 windows) */ + refreshes_8(44); + refreshes_8(60); + refreshes_8(76); + refreshes_8(92); + refreshes_8(108); + refreshes_8(124); + refreshes_8(140); + refreshes_8(156); + + return; + } + +#undef refreshes_8 +#undef refreshes_4 +#undef refreshes_2 +#undef refresh + } + + template void fetch_tms_text(int start, int end) { +#define fetch_tile_name(location, column) slot(location): pattern_names_[column] = ram_[row_base + column]; +#define fetch_tile_pattern(location, column) slot(location): pattern_buffer_[column] = ram_[row_offset + static_cast(pattern_names_[column] << 3)]; + +#define fetch_column(location, column) \ + fetch_tile_name(location, column); \ + external_slot(location+1); \ + fetch_tile_pattern(location+2, column); + +#define fetch_columns_2(location, column) \ + fetch_column(location, column); \ + fetch_column(location+3, column+1); + +#define fetch_columns_4(location, column) \ + fetch_columns_2(location, column); \ + fetch_columns_2(location+6, column+2); + +#define fetch_columns_8(location, column) \ + fetch_columns_4(location, column); \ + fetch_columns_4(location+12, column+4); + + const size_t row_base = pattern_name_address_ + static_cast(row_ >> 3) * 40; + const size_t row_offset = pattern_generator_table_address_ + (row_ & 7); + + switch(start) { + default: + + /* 47 external slots (= 47 windows) */ + external_slots_32(0) + external_slots_8(32) + external_slots_4(40) + external_slots_2(44) + external_slot(46) + + /* 40 column fetches (= 120 windows) */ + fetch_columns_8(47, 0); + fetch_columns_8(71, 8); + fetch_columns_8(95, 16); + fetch_columns_8(119, 24); + fetch_columns_8(143, 32); + + /* 4 more external slots */ + external_slots_4(167); + + return; + } + +#undef fetch_columns_8 +#undef fetch_columns_4 +#undef fetch_columns_2 +#undef fetch_column +#undef fetch_tile_name + } + + template void fetch_tms_character(int start, int end) { +#define sprite_fetch_coordinates(location, sprite) \ + slot(location): \ + slot(location+1): \ + +#define sprite_fetch_graphics(location, sprite) \ + slot(location): \ + slot(location+1): \ + slot(location+2): \ + slot(location+3): \ + +#define sprite_fetch_block(location, sprite) \ + slot(location): \ + slot(location+1): \ + slot(location+2): \ + slot(location+3): \ + slot(location+4): \ + slot(location+5): + +#define sprite_y_read(location, sprite) \ + slot(location): + +#define fetch_tile_name(column) pattern_names_[column] = ram_[row_base + column]; + +#define fetch_tile(column) {\ + colour_buffer_[column] = ram_[colour_base + static_cast((pattern_names_[column] << 3) >> colour_name_shift)]; \ + pattern_buffer_[column] = ram_[pattern_base + static_cast(pattern_names_[column] << 3)]; \ + } + +#define background_fetch_block(location, column) \ + slot(location): fetch_tile_name(column) \ + external_slot(location+1); \ + slot(location+2): \ + slot(location+3): fetch_tile(column) \ + slot(location+4): fetch_tile_name(column+1) \ + sprite_y_read(location+5, column+8); \ + slot(location+6): \ + slot(location+7): fetch_tile(column+1) \ + slot(location+8): fetch_tile_name(column+2) \ + sprite_y_read(location+9, column+9); \ + slot(location+10): \ + slot(location+11): fetch_tile(column+2) \ + slot(location+12): fetch_tile_name(column+3) \ + sprite_y_read(location+13, column+10); \ + slot(location+14): \ + slot(location+15): fetch_tile(column+3) + + const size_t row_base = pattern_name_address_ + static_cast((row_ << 2)&~31); + + size_t pattern_base = pattern_generator_table_address_; + size_t colour_base = colour_table_address_; + int colour_name_shift = 6; + + if(screen_mode_ == ScreenMode::Graphics) { + // If this is high resolution mode, allow the row number to affect the pattern and colour addresses. + pattern_base &= static_cast(0x2000 | ((row_ & 0xc0) << 5)); + colour_base &= static_cast(0x2000 | ((row_ & 0xc0) << 5)); + + colour_base += static_cast(row_ & 7); + colour_name_shift = 0; + } + + if(screen_mode_ == ScreenMode::MultiColour) { + pattern_base += static_cast((row_ >> 2) & 7); + } else { + pattern_base += static_cast(row_ & 7); + } + + switch(start) { + default: + external_slots_2(0); + + sprite_fetch_block(2, 0); + sprite_fetch_block(8, 1); + sprite_fetch_coordinates(14, 2); + + external_slots_4(16); + external_slot(20); + + sprite_fetch_graphics(21, 2); + sprite_fetch_block(25, 3); + + external_slots_4(31); + + sprite_y_read(35, 0); + sprite_y_read(36, 1); + sprite_y_read(37, 2); + sprite_y_read(38, 3); + sprite_y_read(39, 4); + sprite_y_read(40, 5); + sprite_y_read(41, 6); + sprite_y_read(42, 7); + + background_fetch_block(43, 0); + background_fetch_block(59, 4); + background_fetch_block(75, 8); + background_fetch_block(91, 12); + background_fetch_block(107, 16); + background_fetch_block(123, 20); + background_fetch_block(139, 24); + background_fetch_block(155, 28); + + return; + } + +#undef background_fetch_block +#undef fetch_tile +#undef fetch_tile_name +#undef sprite_y_read +#undef sprite_fetch_block +#undef sprite_fetch_graphics +#undef sprite_fetch_coordinates + } + + +/*********************************************** + Master System Fetching Code +************************************************/ template void fetch_sms(int start, int end) { -#define sprite_render_block(location, sprite) \ +#define sprite_fetch_block(location, sprite) \ slot(location): \ slot(location+1): \ slot(location+2): \ @@ -220,7 +601,8 @@ class Base { */ #define fetch_tile_name(column) {\ - size_t address = pattern_address_base + ((column) << 1); \ + const size_t scrolled_column = (column - (master_system_.horizontal_scroll >> 3)) & 0x1f;\ + const size_t address = pattern_address_base + (scrolled_column << 1); \ master_system_.names[column].flags = ram_[address+1]; \ master_system_.names[column].offset = static_cast( \ (((master_system_.names[column].flags&1) << 8) | ram_[address]) << 5 \ @@ -252,25 +634,6 @@ class Base { slot(location+14): \ slot(location+15): fetch_tile(column+3) -/* - TODO: background_render_block should fetch: - - column n, name - (external slot) - - column n, tile graphic first word - - column n, tile graphic second word - - column n+1, name - (sprite y fetch) - - column n+1, tile graphic first word - - column n+1, tile graphic second word - - column n+2, name - (sprite y fetch) - - column n+2, tile graphic first word - - column n+2, tile graphic second word - - column n+3, name - (sprite y fetch) - - column n+3, tile graphic first word - - column n+3, tile graphic second word -*/ const int scrolled_row = (row_ + master_system_.vertical_scroll) % 224; const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((scrolled_row & ~7) << 3) | 0x3800); @@ -285,37 +648,36 @@ class Base { switch(start) { default: - sprite_render_block(0, 0); - sprite_render_block(6, 2); - external_slot(12); - external_slot(13); - external_slot(14); - external_slot(15); - external_slot(16); - sprite_render_block(17, 4); - sprite_render_block(23, 6); - external_slot(29); - external_slot(30); - sprite_y_read(31); - sprite_y_read(32); - sprite_y_read(33); - sprite_y_read(34); + external_slots_4(0); + + sprite_fetch_block(4, 0); + sprite_fetch_block(10, 2); + + external_slots_4(16); + external_slot(20); + + sprite_fetch_block(21, 4); + sprite_fetch_block(27, 6); + + external_slots_2(33); + sprite_y_read(35); sprite_y_read(36); sprite_y_read(37); sprite_y_read(38); - background_render_block(39, 0); - background_render_block(55, 4); - background_render_block(71, 8); - background_render_block(87, 12); - background_render_block(103, 16); - background_render_block(119, 20); - background_render_block(135, 24); - background_render_block(151, 28); - external_slot(167); - external_slot(168); - external_slot(169); - external_slot(170); + sprite_y_read(39); + sprite_y_read(40); + sprite_y_read(41); + sprite_y_read(42); + + background_render_block(43, 0); + background_render_block(59, 4); + background_render_block(75, 8); + background_render_block(91, 12); + background_render_block(107, 16); + background_render_block(123, 20); + background_render_block(139, 24); + background_render_block(156, 28); return; } From 536112035346225d85414f5f724e1e4e58081ba6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 2 Oct 2018 21:05:30 -0400 Subject: [PATCH 26/88] Restores a stable frame. --- Components/9918/9918.cpp | 102 +++++++++++++------- Components/9918/Implementation/9918Base.hpp | 8 +- 2 files changed, 71 insertions(+), 39 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index d5ae593f4..14bbf14aa 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -178,18 +178,23 @@ void TMS9918::run_for(const HalfCycles cycles) { // ------------------------ #define fetch(function) \ if(end_column < 171) { \ - function(column_, end_column);\ + function(first_window, final_window);\ } else {\ - function(column_, end_column);\ + function(first_window, final_window);\ } // TODO: column_ and end_column are in 342-per-line cycles; // adjust them to a count of windows. - switch(line_mode_) { - case LineMode::Text: fetch(fetch_tms_text); break; - case LineMode::Character: fetch(fetch_tms_character); break; - case LineMode::SMS: fetch(fetch_sms); break; - case LineMode::Refresh: fetch(fetch_tms_refresh); break; + int first_window = column_ >> 1; + int final_window = end_column >> 1; + + if(first_window != final_window) { + switch(line_mode_) { + case LineMode::Text: fetch(fetch_tms_text); break; + case LineMode::Character: fetch(fetch_tms_character); break; + case LineMode::SMS: fetch(fetch_sms); break; + case LineMode::Refresh: fetch(fetch_tms_refresh); break; + } } #undef fetch @@ -200,9 +205,9 @@ void TMS9918::run_for(const HalfCycles cycles) { // -------------------- if(line_mode_ == LineMode::Refresh) { - if(row_ >= mode_timing_.first_vsync_line && row_ < mode_timing_.first_vsync_line+3) { + if(row_ >= mode_timing_.first_vsync_line && row_ < mode_timing_.first_vsync_line+4) { // Vertical sync. - if(column_ == 342) { + if(end_column == 342) { crt_->output_sync(342 * 4); } } else { @@ -248,27 +253,32 @@ void TMS9918::run_for(const HalfCycles cycles) { // In pixel area: const int pixel_start = std::max(column_, mode_timing_.first_pixel_output_column); const int pixel_end = std::min(end_column, mode_timing_.next_border_column); - if(pixel_end > pixel_start) { - output_border(pixel_end - pixel_start); -// switch(screen_mode_) { -// case ScreenMode::Text: -// break; +// if(end_column == 342) { + if(pixel_end > pixel_start) { + crt_->output_blank((pixel_end - pixel_start)*4); +// switch(screen_mode_) { +// case ScreenMode::Text: +// draw_tms_text(); +// break; // -// case ScreenMode::ColouredText: -// case ScreenMode::Graphics: -// break; +// case ScreenMode::ColouredText: +// case ScreenMode::Graphics: +// draw_tms_graphics(); +// break; // -// case ScreenMode::SMSMode4: -// break; +// case ScreenMode::SMSMode4: +// draw_sms(); +// break; // -// default: break; -// } - } +// default: break; +// } + } - // Additional right border, if called for. - if(mode_timing_.next_border_column != 342 && column_ == 342) { - output_border(342 - mode_timing_.next_border_column); - } + // Additional right border, if called for. + if(mode_timing_.next_border_column != 342 && end_column == 342) { + output_border(342 - mode_timing_.next_border_column); + } +// } } @@ -514,14 +524,14 @@ void TMS9918::run_for(const HalfCycles cycles) { set_current_mode(); // Based on the output mode, pick a line mode. - mode_timing_.first_pixel_output_column = 88; - mode_timing_.next_border_column = 344; + mode_timing_.first_pixel_output_column = 86; + mode_timing_.next_border_column = 342; mode_timing_.maximum_visible_sprites = 4; switch(screen_mode_) { case ScreenMode::Text: line_mode_ = LineMode::Text; - mode_timing_.first_pixel_output_column = 48; - mode_timing_.next_border_column = 168; + mode_timing_.first_pixel_output_column = 94; + mode_timing_.next_border_column = 334; mode_timing_.maximum_visible_sprites = 8; break; case ScreenMode::SMSMode4: @@ -538,12 +548,12 @@ void TMS9918::run_for(const HalfCycles cycles) { } void Base::output_border(int cycles) { - pixel_target_ = reinterpret_cast(crt_->allocate_write_area(1)); - if(pixel_target_) { + uint32_t *const pixel_target = reinterpret_cast(crt_->allocate_write_area(1)); + if(pixel_target) { if(is_sega_vdp(personality_)) { - *pixel_target_ = master_system_.colour_ram[16 + background_colour_]; + *pixel_target = master_system_.colour_ram[16 + background_colour_]; } else { - *pixel_target_ = palette[background_colour_]; + *pixel_target = palette[background_colour_]; } } crt_->output_level(static_cast(cycles) * 4); @@ -692,3 +702,27 @@ uint8_t TMS9918::get_register(int address) { bool TMS9918::get_interrupt_line() { return (status_ & StatusInterrupt) && generate_interrupts_; } + +// MARK: - + +void Base::draw_tms_graphics() { + +} + +void Base::draw_tms_text() { + +} + +void Base::draw_sms() { + uint32_t *const pixel_target_ = reinterpret_cast(crt_->allocate_write_area(256)); + + if(pixel_target_) { + for(int c = 0; c < 256; ++c) + pixel_target_[c] = static_cast(c * 0x01010101); + } + + crt_->output_data(256 * 4, 256); +} + +// output_border(pixel_end - pixel_start); + diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 1f4fd60ac..decba5da9 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -111,9 +111,6 @@ class Base { int cycles_error_ = 0; int access_pointer_ = 0; - // Internal storage for the pixel part of line serialisation. - uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr; - // A helper function to output the current border colour for // the number of cycles supplied. void output_border(int cycles); @@ -690,8 +687,9 @@ class Base { #undef external_slot #undef slot - void draw_sms(int start, int end) { - } + void draw_tms_graphics(); + void draw_tms_text(); + void draw_sms(); }; From 1dd5272190b12006a57ad3c3319b9043af4cf275 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 2 Oct 2018 21:18:28 -0400 Subject: [PATCH 27/88] Ensures real-time output of all areas, to ensure proper palette response. --- Components/9918/9918.cpp | 49 +++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 14bbf14aa..65ee1dd18 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -204,6 +204,15 @@ void TMS9918::run_for(const HalfCycles cycles) { // Output video stream. // -------------------- +#define intersect(left, right, code) \ + { \ + const int start = std::max(column_, left); \ + const int end = std::min(end_column, right); \ + if(end > start) {\ + code;\ + }\ + } + if(line_mode_ == LineMode::Refresh) { if(row_ >= mode_timing_.first_vsync_line && row_ < mode_timing_.first_vsync_line+4) { // Vertical sync. @@ -212,9 +221,7 @@ void TMS9918::run_for(const HalfCycles cycles) { } } else { // Right border. - if(column_ < 15 && end_column >= 15) { - output_border(15); - } + intersect(0, 15, output_border(end - start)); // Blanking region. if(column_ < 73 && end_column >= 73) { @@ -226,15 +233,11 @@ void TMS9918::run_for(const HalfCycles cycles) { } // Most of line. - if(end_column == 342) { - output_border(342 - 73); - } + intersect(73, 342, output_border(end - start)); } } else { // Right border. - if(column_ < 15 && end_column >= 15) { - output_border(15); - } + intersect(0, 15, output_border(end - start)); // Blanking region. if(column_ < 73 && end_column >= 73) { @@ -246,16 +249,16 @@ void TMS9918::run_for(const HalfCycles cycles) { } // Left border. - if(column_ < mode_timing_.first_pixel_output_column && end_column >= mode_timing_.first_pixel_output_column) { - output_border(mode_timing_.first_pixel_output_column - 73); - } + intersect(73, mode_timing_.first_pixel_output_column, output_border(end - start)); // In pixel area: - const int pixel_start = std::max(column_, mode_timing_.first_pixel_output_column); - const int pixel_end = std::min(end_column, mode_timing_.next_border_column); -// if(end_column == 342) { - if(pixel_end > pixel_start) { - crt_->output_blank((pixel_end - pixel_start)*4); + intersect(mode_timing_.first_pixel_output_column, mode_timing_.next_border_column, + crt_->output_blank((end - start)*4); + ); +// const int pixel_start = std::max(column_, mode_timing_.first_pixel_output_column); +// const int pixel_end = std::min(end_column, mode_timing_.next_border_column); +// if(pixel_end > pixel_start) { +// crt_->output_blank((pixel_end - pixel_start)*4); // switch(screen_mode_) { // case ScreenMode::Text: // draw_tms_text(); @@ -272,15 +275,15 @@ void TMS9918::run_for(const HalfCycles cycles) { // // default: break; // } - } +// } - // Additional right border, if called for. - if(mode_timing_.next_border_column != 342 && end_column == 342) { - output_border(342 - mode_timing_.next_border_column); - } -// } + // Additional right border, if called for. + if(mode_timing_.next_border_column != 342) { + intersect(mode_timing_.next_border_column, 342, output_border(end - start)); + } } +#undef intersect column_ = end_column; // column_ is now the column that has been reached in this line. int_cycles -= cycles_left; // Count down duration to run for. From 499fc62187e439f9627fc00efd2da181930ac369 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 2 Oct 2018 21:58:09 -0400 Subject: [PATCH 28/88] Sets things up for implementation of the inner mode-specific logic. --- Components/9918/9918.cpp | 109 +++++++------------- Components/9918/Implementation/9918Base.hpp | 7 +- 2 files changed, 39 insertions(+), 77 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 65ee1dd18..699d62699 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -183,11 +183,10 @@ void TMS9918::run_for(const HalfCycles cycles) { function(first_window, final_window);\ } - // TODO: column_ and end_column are in 342-per-line cycles; + // column_ and end_column are in 342-per-line cycles; // adjust them to a count of windows. - int first_window = column_ >> 1; - int final_window = end_column >> 1; - + const int first_window = column_ >> 1; + const int final_window = end_column >> 1; if(first_window != final_window) { switch(line_mode_) { case LineMode::Text: fetch(fetch_tms_text); break; @@ -252,30 +251,33 @@ void TMS9918::run_for(const HalfCycles cycles) { intersect(73, mode_timing_.first_pixel_output_column, output_border(end - start)); // In pixel area: - intersect(mode_timing_.first_pixel_output_column, mode_timing_.next_border_column, - crt_->output_blank((end - start)*4); + intersect( + mode_timing_.first_pixel_output_column, + mode_timing_.next_border_column, + if(start == mode_timing_.first_pixel_output_column) { + pixel_target_ = reinterpret_cast( + crt_->allocate_write_area(static_cast(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column)) + ); + } + + if(pixel_target_) { + const int relative_start = start - mode_timing_.first_pixel_output_column; + const int relative_end = end - mode_timing_.first_pixel_output_column; + switch(line_mode_) { + case LineMode::SMS: draw_sms(relative_start, relative_end); break; + case LineMode::Character: draw_tms_character(relative_start, relative_end); break; + case LineMode::Text: draw_tms_text(relative_start, relative_end); break; + + case LineMode::Refresh: break; /* Dealt with elsewhere. */ + } + } + + if(end == mode_timing_.next_border_column) { + const unsigned int length = static_cast(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column); + crt_->output_data(length * 4, length); + pixel_target_ = nullptr; + } ); -// const int pixel_start = std::max(column_, mode_timing_.first_pixel_output_column); -// const int pixel_end = std::min(end_column, mode_timing_.next_border_column); -// if(pixel_end > pixel_start) { -// crt_->output_blank((pixel_end - pixel_start)*4); -// switch(screen_mode_) { -// case ScreenMode::Text: -// draw_tms_text(); -// break; -// -// case ScreenMode::ColouredText: -// case ScreenMode::Graphics: -// draw_tms_graphics(); -// break; -// -// case ScreenMode::SMSMode4: -// draw_sms(); -// break; -// -// default: break; -// } -// } // Additional right border, if called for. if(mode_timing_.next_border_column != 342) { @@ -290,30 +292,6 @@ void TMS9918::run_for(const HalfCycles cycles) { /* if(row_ < 192 && current_mode_ != ScreenMode::Blank) { - // ---------------------- - // Output horizontal sync - // ---------------------- - if(!output_column_ && column_ >= 26) { - crt_->output_sync(13 * 4); - crt_->output_default_colour_burst(13 * 4); - output_column_ = 26; - } - - // ------------------- - // Output left border. - // ------------------- - if(output_column_ >= 26) { - int pixels_end = std::min(first_pixel_column_, column_); - if(output_column_ < pixels_end) { - output_border(pixels_end - output_column_); - output_column_ = pixels_end; - - // Grab a pointer for drawing pixels to, if the moment has arrived. - if(pixels_end == first_pixel_column_) { - pixel_base_ = pixel_target_ = reinterpret_cast(crt_->allocate_write_area(static_cast(first_right_border_column_ - first_pixel_column_))); - } - } - } // -------------- // Output pixels. @@ -501,14 +479,7 @@ void TMS9918::run_for(const HalfCycles cycles) { } } - // -------------------- - // Output right border. - // -------------------- - if(output_column_ >= first_right_border_column_) { - output_border(column_ - output_column_); - output_column_ = column_; - } - } }*/ + }*/ // ----------------- // End video stream. // ----------------- @@ -708,24 +679,14 @@ bool TMS9918::get_interrupt_line() { // MARK: - -void Base::draw_tms_graphics() { - +void Base::draw_tms_character(int start, int end) { } -void Base::draw_tms_text() { - +void Base::draw_tms_text(int start, int end) { } -void Base::draw_sms() { - uint32_t *const pixel_target_ = reinterpret_cast(crt_->allocate_write_area(256)); - - if(pixel_target_) { - for(int c = 0; c < 256; ++c) - pixel_target_[c] = static_cast(c * 0x01010101); +void Base::draw_sms(int start, int end) { + for(int c = start; c < end; ++c) { + pixel_target_[c] = static_cast(c * 0x01010101); } - - crt_->output_data(256 * 4, 256); } - -// output_border(pixel_end - pixel_start); - diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index decba5da9..9dfc4aeb6 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -687,9 +687,10 @@ class Base { #undef external_slot #undef slot - void draw_tms_graphics(); - void draw_tms_text(); - void draw_sms(); + uint32_t *pixel_target_ = nullptr; + void draw_tms_character(int start, int end); + void draw_tms_text(int start, int end); + void draw_sms(int start, int end); }; From 23c3fa69935b69c9f3569833b5c68cf781c0b5be Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 2 Oct 2018 22:01:43 -0400 Subject: [PATCH 29/88] Fixed: it's the SMS that has 8 sprites, not text mode (which has none). --- Components/9918/9918.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 699d62699..8ffab2c7a 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -506,10 +506,10 @@ void TMS9918::run_for(const HalfCycles cycles) { line_mode_ = LineMode::Text; mode_timing_.first_pixel_output_column = 94; mode_timing_.next_border_column = 334; - mode_timing_.maximum_visible_sprites = 8; break; case ScreenMode::SMSMode4: line_mode_ = LineMode::SMS; + mode_timing_.maximum_visible_sprites = 8; break; default: line_mode_ = LineMode::Character; @@ -680,9 +680,15 @@ bool TMS9918::get_interrupt_line() { // MARK: - void Base::draw_tms_character(int start, int end) { + for(int c = start; c < end; ++c) { + pixel_target_[c] = static_cast(c * 0x01010101); + } } void Base::draw_tms_text(int start, int end) { + for(int c = start; c < end; ++c) { + pixel_target_[c] = static_cast(c * 0x01010101); + } } void Base::draw_sms(int start, int end) { From be52b31b5cd7e49512a2a644916ec50182cef100 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 2 Oct 2018 22:05:58 -0400 Subject: [PATCH 30/88] Attempts fully to revive text mode. --- Components/9918/9918.cpp | 47 ++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 8ffab2c7a..f1196f446 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -330,32 +330,6 @@ void TMS9918::run_for(const HalfCycles cycles) { } break; - case LineMode::Text: { - if(pixel_target_) { - const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; - - const int shift = (output_column_ - first_pixel_column_) % 6; - int byte_column = (output_column_ - first_pixel_column_) / 6; - int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; - int pixels_left = pixels_end - output_column_; - int length = std::min(pixels_left, 6 - shift); - while(true) { - pixels_left -= length; - for(int c = 0; c < length; ++c) { - pixel_target_[c] = colours[pattern&0x01]; - pattern >>= 1; - } - pixel_target_ += length; - - if(!pixels_left) break; - length = std::min(6, pixels_left); - byte_column++; - pattern = reverse_table.map[pattern_buffer_[byte_column]]; - } - } - output_column_ = pixels_end; - } break; - case LineMode::Character: { // If this is the start of the visible area, seed sprite shifter positions. SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1]; @@ -686,8 +660,25 @@ void Base::draw_tms_character(int start, int end) { } void Base::draw_tms_text(int start, int end) { - for(int c = start; c < end; ++c) { - pixel_target_[c] = static_cast(c * 0x01010101); + const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; + + const int shift = start % 6; + int byte_column = start / 6; + int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; + int pixels_left = end - start; + int length = std::min(pixels_left, 6 - shift); + while(true) { + pixels_left -= length; + for(int c = 0; c < length; ++c) { + pixel_target_[c] = colours[pattern&0x01]; + pattern >>= 1; + } + pixel_target_ += length; + + if(!pixels_left) break; + length = std::min(6, pixels_left); + byte_column++; + pattern = reverse_table.map[pattern_buffer_[byte_column]]; } } From 252f47a425a8cb82a9db1944910e68d41f77c171 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 2 Oct 2018 22:59:20 -0400 Subject: [PATCH 31/88] Ensures no pixel output on line one before end, and adds a temporary debugging test. --- Components/9918/9918.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index f1196f446..e5a972bd2 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -212,7 +212,7 @@ void TMS9918::run_for(const HalfCycles cycles) { }\ } - if(line_mode_ == LineMode::Refresh) { + if(line_mode_ == LineMode::Refresh || row_ > mode_timing_.pixel_lines) { if(row_ >= mode_timing_.first_vsync_line && row_ < mode_timing_.first_vsync_line+4) { // Vertical sync. if(end_column == 342) { @@ -276,6 +276,12 @@ void TMS9918::run_for(const HalfCycles cycles) { const unsigned int length = static_cast(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column); crt_->output_data(length * 4, length); pixel_target_ = nullptr; + +#ifdef DEBUG + memset(pattern_names_, sizeof(pattern_names_), 0xff); + memset(pattern_buffer_, sizeof(pattern_buffer_), 0xff); + memset(colour_buffer_, sizeof(colour_buffer_), 0xff); +#endif } ); From 05248ab990bd32bc2c4e85277c975581f6fad228 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 3 Oct 2018 23:13:21 -0400 Subject: [PATCH 32/88] Starts to reimplement Master System output. --- Components/9918/9918.cpp | 138 ++++++++++++-------- Components/9918/Implementation/9918Base.hpp | 9 +- 2 files changed, 86 insertions(+), 61 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index e5a972bd2..baa98a251 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -256,7 +256,7 @@ void TMS9918::run_for(const HalfCycles cycles) { mode_timing_.next_border_column, if(start == mode_timing_.first_pixel_output_column) { pixel_target_ = reinterpret_cast( - crt_->allocate_write_area(static_cast(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column)) + crt_->allocate_write_area(static_cast(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column) + 8) // TODO: the +8 is really for the SMS only; make it optional. ); } @@ -276,12 +276,6 @@ void TMS9918::run_for(const HalfCycles cycles) { const unsigned int length = static_cast(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column); crt_->output_data(length * 4, length); pixel_target_ = nullptr; - -#ifdef DEBUG - memset(pattern_names_, sizeof(pattern_names_), 0xff); - memset(pattern_buffer_, sizeof(pattern_buffer_), 0xff); - memset(colour_buffer_, sizeof(colour_buffer_), 0xff); -#endif } ); @@ -293,48 +287,20 @@ void TMS9918::run_for(const HalfCycles cycles) { #undef intersect + // ----------------- + // End video stream. + // ----------------- + + column_ = end_column; // column_ is now the column that has been reached in this line. int_cycles -= cycles_left; // Count down duration to run for. - -/* if(row_ < 192 && current_mode_ != ScreenMode::Blank) { +/* // -------------- // Output pixels. // -------------- - if(output_column_ >= first_pixel_column_) { - int pixels_end = std::min(first_right_border_column_, column_); - if(output_column_ < pixels_end) { - switch(line_mode_) { - default: break; - - case LineMode::SMS: { - if(pixel_target_) { - const int pixels_left = pixels_end - output_column_; - const int pixel_location = output_column_ - first_pixel_column_; - const int reverses[2] = {0, 7}; - for(int c = 0; c < pixels_left; ++c) { - const int column = (pixel_location + c) >> 3; - const int shift = 4 + (((pixel_location + c) & 7) ^ reverses[(master_system_.names[column].flags&2) >> 1]); - int value = - ( - ( - ((master_system_.tile_graphics[column][3] << shift) & 0x800) | - ((master_system_.tile_graphics[column][2] << (shift - 1)) & 0x400) | - ((master_system_.tile_graphics[column][1] << (shift - 2)) & 0x200) | - ((master_system_.tile_graphics[column][0] << (shift - 3)) & 0x100) - ) >> 8 - ) | ((master_system_.names[column].flags&0x08) << 1); - - pixel_target_[c] = master_system_.colour_ram[value]; - } - pixel_target_ += pixels_left; - } - - output_column_ = pixels_end; - } - break; case LineMode::Character: { // If this is the start of the visible area, seed sprite shifter positions. @@ -451,18 +417,8 @@ void TMS9918::run_for(const HalfCycles cycles) { } break; } - if(output_column_ == first_right_border_column_) { - const unsigned int data_length = static_cast(first_right_border_column_ - first_pixel_column_); - crt_->output_data(data_length * 4, data_length); - pixel_target_ = nullptr; - } - } - } }*/ - // ----------------- - // End video stream. - // ----------------- @@ -660,9 +616,11 @@ bool TMS9918::get_interrupt_line() { // MARK: - void Base::draw_tms_character(int start, int end) { - for(int c = start; c < end; ++c) { - pixel_target_[c] = static_cast(c * 0x01010101); - } +// if(!start) printf("\n"); +// printf("%d to %d | ", start, end); +// for(int c = start; c < end; ++c) { +// pixel_target_[c] = static_cast(c * 0x01010101); +// } } void Base::draw_tms_text(int start, int end) { @@ -689,7 +647,75 @@ void Base::draw_tms_text(int start, int end) { } void Base::draw_sms(int start, int end) { - for(int c = start; c < end; ++c) { - pixel_target_[c] = static_cast(c * 0x01010101); + if(!start) { + pixel_target_ += master_system_.horizontal_scroll & 7; } + + const int shift = start & 7; + int byte_column = start >> 3; + int pixels_left = end - start; + int length = std::min(pixels_left, 8 - shift); + uint32_t pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); + + if(master_system_.names[byte_column].flags&2) + pattern >>= shift; + else + pattern <<= shift; + + while(true) { + pixels_left -= length; + const uint32_t palette_offset = static_cast((master_system_.names[byte_column].flags&0x08) << 1); + if(master_system_.names[byte_column].flags&2) { + for(int c = 0; c < length; ++c) { + const uint32_t value = + ((pattern & 0x00000001) >> 0) | + ((pattern & 0x00000100) >> 6) | + ((pattern & 0x00010000) >> 15) | + ((pattern & 0x01000000) >> 24) | + palette_offset; + pixel_target_[c] = master_system_.colour_ram[value]; + pattern >>= 1; + } + } else { + for(int c = 0; c < length; ++c) { + const uint32_t value = + ((pattern & 0x00000080) >> 7) | + ((pattern & 0x00008000) >> 14) | + ((pattern & 0x00800000) >> 21) | + ((pattern & 0x80000000) >> 28) | + palette_offset; + pixel_target_[c] = master_system_.colour_ram[value]; + pattern <<= 1; + } + } + pixel_target_ += length; + + if(!pixels_left) break; + length = std::min(8, pixels_left); + byte_column++; + pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); + } + +// const int pixels_left = pixels_end - output_column_; +// const int pixel_location = output_column_ - first_pixel_column_; +// const int reverses[2] = {0, 7}; +// for(int c = 0; c < pixels_left; ++c) { +// const int column = (pixel_location + c) >> 3; +// const int shift = 4 + (((pixel_location + c) & 7) ^ reverses[(master_system_.names[column].flags&2) >> 1]); +// int value = +// ( +// ( +// ((master_system_.tile_graphics[column][3] << shift) & 0x800) | +// ((master_system_.tile_graphics[column][2] << (shift - 1)) & 0x400) | +// ((master_system_.tile_graphics[column][1] << (shift - 2)) & 0x200) | +// ((master_system_.tile_graphics[column][0] << (shift - 3)) & 0x100) +// ) >> 8 +// ) | ((master_system_.names[column].flags&0x08) << 1); +// +// pixel_target_[c] = master_system_.colour_ram[value]; +// } + +// for(int c = start; c < end; ++c) { +// pixel_target_[c] = static_cast(c * 0x01010101); +// } } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 9dfc4aeb6..13177ecf3 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -239,7 +239,7 @@ class Base { return; } - mode_timing_.maximum_visible_sprites = 8; + mode_timing_.maximum_visible_sprites = 4; if(!mode1_enable_ && !mode2_enable_ && !mode3_enable_) { screen_mode_ = ScreenMode::ColouredText; return; @@ -268,7 +268,6 @@ class Base { void external_slot() { // TODO: write or read a value if one is queued and ready to read/write. // (and, later: update the command engine, if this is an MSX2). - switch(queued_access_) { default: return; @@ -473,11 +472,11 @@ class Base { #define sprite_y_read(location, sprite) \ slot(location): -#define fetch_tile_name(column) pattern_names_[column] = ram_[row_base + column]; +#define fetch_tile_name(column) pattern_names_[column] = ram_[(row_base + column) & 0x3fff]; #define fetch_tile(column) {\ - colour_buffer_[column] = ram_[colour_base + static_cast((pattern_names_[column] << 3) >> colour_name_shift)]; \ - pattern_buffer_[column] = ram_[pattern_base + static_cast(pattern_names_[column] << 3)]; \ + colour_buffer_[column] = ram_[(colour_base + static_cast((pattern_names_[column] << 3) >> colour_name_shift)) & 0x3fff]; \ + pattern_buffer_[column] = ram_[(pattern_base + static_cast(pattern_names_[column] << 3)) & 0x3fff]; \ } #define background_fetch_block(location, column) \ From de4e5c40aa85e0aae85e6e6eff3820946cc25c52 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 3 Oct 2018 23:28:33 -0400 Subject: [PATCH 33/88] Implements horizontal scrolling lock. --- Components/9918/9918.cpp | 26 +++++++++++---------- Components/9918/Implementation/9918Base.hpp | 3 ++- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index baa98a251..b4815fd29 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -647,7 +647,7 @@ void Base::draw_tms_text(int start, int end) { } void Base::draw_sms(int start, int end) { - if(!start) { + if(!start && (row_ >= 16 || !master_system_.horizontal_scroll_lock)) { pixel_target_ += master_system_.horizontal_scroll & 7; } @@ -655,7 +655,9 @@ void Base::draw_sms(int start, int end) { int byte_column = start >> 3; int pixels_left = end - start; int length = std::min(pixels_left, 8 - shift); + uint32_t pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); + uint8_t *const pattern_index = reinterpret_cast(&pattern); if(master_system_.names[byte_column].flags&2) pattern >>= shift; @@ -664,25 +666,25 @@ void Base::draw_sms(int start, int end) { while(true) { pixels_left -= length; - const uint32_t palette_offset = static_cast((master_system_.names[byte_column].flags&0x08) << 1); + const int palette_offset = (master_system_.names[byte_column].flags&0x08) << 1; if(master_system_.names[byte_column].flags&2) { for(int c = 0; c < length; ++c) { - const uint32_t value = - ((pattern & 0x00000001) >> 0) | - ((pattern & 0x00000100) >> 6) | - ((pattern & 0x00010000) >> 15) | - ((pattern & 0x01000000) >> 24) | + const int value = + ((pattern_index[3] & 0x01) << 3) | + ((pattern_index[2] & 0x01) << 2) | + ((pattern_index[1] & 0x01) << 1) | + ((pattern_index[0] & 0x01) << 0) | palette_offset; pixel_target_[c] = master_system_.colour_ram[value]; pattern >>= 1; } } else { for(int c = 0; c < length; ++c) { - const uint32_t value = - ((pattern & 0x00000080) >> 7) | - ((pattern & 0x00008000) >> 14) | - ((pattern & 0x00800000) >> 21) | - ((pattern & 0x80000000) >> 28) | + const int value = + ((pattern_index[3] & 0x80) >> 4) | + ((pattern_index[2] & 0x80) >> 5) | + ((pattern_index[1] & 0x80) >> 6) | + ((pattern_index[0] & 0x80) >> 7) | palette_offset; pixel_target_[c] = master_system_.colour_ram[value]; pattern <<= 1; diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 13177ecf3..de34e51b8 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -597,7 +597,7 @@ class Base { */ #define fetch_tile_name(column) {\ - const size_t scrolled_column = (column - (master_system_.horizontal_scroll >> 3)) & 0x1f;\ + const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\ const size_t address = pattern_address_base + (scrolled_column << 1); \ master_system_.names[column].flags = ram_[address+1]; \ master_system_.names[column].offset = static_cast( \ @@ -634,6 +634,7 @@ class Base { const int scrolled_row = (row_ + master_system_.vertical_scroll) % 224; const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((scrolled_row & ~7) << 3) | 0x3800); const size_t sub_row[2] = {static_cast((scrolled_row & 7) << 2), 28 ^ static_cast((scrolled_row & 7) << 2)}; + const int horizontal_offset = (row_ >= 16 || !master_system_.horizontal_scroll_lock) ? (master_system_.horizontal_scroll >> 3) : 0; /* To add, relative to the times below: From 89db1d6a6acb36ba8bcd38b6fbcf91fb81fc546c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 4 Oct 2018 18:44:49 -0400 Subject: [PATCH 34/88] Switches to a more accurate means of left-padding. --- Components/9918/9918.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index b4815fd29..f39fd605b 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -647,8 +647,17 @@ void Base::draw_tms_text(int start, int end) { } void Base::draw_sms(int start, int end) { - if(!start && (row_ >= 16 || !master_system_.horizontal_scroll_lock)) { - pixel_target_ += master_system_.horizontal_scroll & 7; + // Shift the output window by the fine scroll amount, and fill in + // any border pixels that leaves on the left-hand side. + start -= master_system_.horizontal_scroll & 7; + end -= master_system_.horizontal_scroll & 7; + if(start < 0) { + while(start < end && start < 0) { + *pixel_target_ = master_system_.colour_ram[16 + background_colour_]; + ++pixel_target_; + ++start; + } + if(start == end) return; } const int shift = start & 7; From 858721a7a507438bebb2b0ad03a13a8bd6993448 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 4 Oct 2018 18:52:23 -0400 Subject: [PATCH 35/88] Added left border hiding. --- Components/9918/9918.cpp | 11 +++++++++-- Components/9918/Implementation/9918Base.hpp | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index f39fd605b..98e899524 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -255,7 +255,7 @@ void TMS9918::run_for(const HalfCycles cycles) { mode_timing_.first_pixel_output_column, mode_timing_.next_border_column, if(start == mode_timing_.first_pixel_output_column) { - pixel_target_ = reinterpret_cast( + pixel_origin_ = pixel_target_ = reinterpret_cast( crt_->allocate_write_area(static_cast(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column) + 8) // TODO: the +8 is really for the SMS only; make it optional. ); } @@ -275,7 +275,7 @@ void TMS9918::run_for(const HalfCycles cycles) { if(end == mode_timing_.next_border_column) { const unsigned int length = static_cast(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column); crt_->output_data(length * 4, length); - pixel_target_ = nullptr; + pixel_origin_ = pixel_target_ = nullptr; } ); @@ -647,6 +647,8 @@ void Base::draw_tms_text(int start, int end) { } void Base::draw_sms(int start, int end) { + const bool is_end = end == 256; + // Shift the output window by the fine scroll amount, and fill in // any border pixels that leaves on the left-hand side. start -= master_system_.horizontal_scroll & 7; @@ -707,6 +709,11 @@ void Base::draw_sms(int start, int end) { pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); } + if(is_end && master_system_.hide_left_column) { + pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] = + pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] = + master_system_.colour_ram[16 + background_colour_]; + } // const int pixels_left = pixels_end - output_column_; // const int pixel_location = output_column_ - first_pixel_column_; // const int reverses[2] = {0, 7}; diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index de34e51b8..8410d50a5 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -687,7 +687,7 @@ class Base { #undef external_slot #undef slot - uint32_t *pixel_target_ = nullptr; + uint32_t *pixel_target_ = nullptr, *pixel_origin_ = nullptr; void draw_tms_character(int start, int end); void draw_tms_text(int start, int end); void draw_sms(int start, int end); From ca26dfcd61335c7500f91b21068d777e044190f9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 4 Oct 2018 19:12:31 -0400 Subject: [PATCH 36/88] Correct Master System palette writes. --- Components/9918/9918.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 98e899524..5e257adf3 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -499,7 +499,6 @@ void TMS9918::set_register(int address, uint8_t value) { case TI::TMS::SMSVDP: if(value & 0x40) { ram_pointer_ = static_cast(low_write_ | (value << 8)); - queued_access_ = MemoryAccess::Write; master_system_.cram_is_selected = true; return; } @@ -709,6 +708,8 @@ void Base::draw_sms(int start, int end) { pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); } + // If the VDP is set to hide the left column and this is the final call that'll come + // this line, hide it. if(is_end && master_system_.hide_left_column) { pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] = pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] = @@ -732,8 +733,4 @@ void Base::draw_sms(int start, int end) { // // pixel_target_[c] = master_system_.colour_ram[value]; // } - -// for(int c = start; c < end; ++c) { -// pixel_target_[c] = static_cast(c * 0x01010101); -// } } From aac97a89832fb71872b42f4bf37936d0a5e16c33 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 4 Oct 2018 19:18:15 -0400 Subject: [PATCH 37/88] Re-revokes fine scroll on the top two lines when requested. --- Components/9918/9918.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 5e257adf3..74656d473 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -650,15 +650,17 @@ void Base::draw_sms(int start, int end) { // Shift the output window by the fine scroll amount, and fill in // any border pixels that leaves on the left-hand side. - start -= master_system_.horizontal_scroll & 7; - end -= master_system_.horizontal_scroll & 7; - if(start < 0) { - while(start < end && start < 0) { - *pixel_target_ = master_system_.colour_ram[16 + background_colour_]; - ++pixel_target_; - ++start; + if(row_ >= 16 || !master_system_.horizontal_scroll_lock) { + start -= master_system_.horizontal_scroll & 7; + end -= master_system_.horizontal_scroll & 7; + if(start < 0) { + while(start < end && start < 0) { + *pixel_target_ = master_system_.colour_ram[16 + background_colour_]; + ++pixel_target_; + ++start; + } + if(start == end) return; } - if(start == end) return; } const int shift = start & 7; From 7830cda912bfe793c5761b1da23211831691f322 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 4 Oct 2018 22:50:35 -0400 Subject: [PATCH 38/88] Implements line querying and most of line interrupts. --- Components/9918/9918.cpp | 76 ++++++++++++++++++--- Components/9918/9918.hpp | 3 + Components/9918/Implementation/9918Base.hpp | 12 +++- Machines/MasterSystem/MasterSystem.cpp | 10 +-- 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 74656d473..4b26a10f8 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -61,6 +61,10 @@ Base::Base(Personality p) : ram_.resize(192 * 1024); break; } + + if(is_sega_vdp(personality_)) { + mode_timing_.line_interrupt_position = 15; + } } TMS9918::TMS9918(Personality p): @@ -292,9 +296,6 @@ void TMS9918::run_for(const HalfCycles cycles) { // ----------------- - column_ = end_column; // column_ is now the column that has been reached in this line. - int_cycles -= cycles_left; // Count down duration to run for. - /* // -------------- @@ -421,6 +422,26 @@ void TMS9918::run_for(const HalfCycles cycles) { }*/ + if(column_ < mode_timing_.line_interrupt_position && end_column >= mode_timing_.line_interrupt_position) { + if(row_ <= mode_timing_.pixel_lines) { + --line_interrupt_counter; + if(line_interrupt_counter == 0xff) { +// line_interrupt_pending_ = true; + line_interrupt_counter = line_interrupt_target; + } + } else { + line_interrupt_counter = line_interrupt_target; + } + } + + + // ------------- + // Advance time. + // ------------- + column_ = end_column; // column_ is now the column that has been reached in this line. + int_cycles -= cycles_left; // Count down duration to run for. + + // ----------------------------------- // Prepare for next line, potentially. @@ -513,7 +534,7 @@ void TMS9918::set_register(int address, uint8_t value) { master_system_.vertical_scroll_lock = !!(low_write_ & 0x80); master_system_.horizontal_scroll_lock = !!(low_write_ & 0x40); master_system_.hide_left_column = !!(low_write_ & 0x20); - master_system_.enable_line_interrupts = !!(low_write_ & 0x10); + enable_line_interrupts_ = !!(low_write_ & 0x10); master_system_.shift_sprites_8px_left = !!(low_write_ & 0x08); master_system_.mode4_enable = !!(low_write_ & 0x04); } @@ -569,6 +590,16 @@ void TMS9918::set_register(int address, uint8_t value) { master_system_.vertical_scroll = low_write_; } break; + + case 10: + if(is_sega_vdp(personality_)) { + line_interrupt_target = value; + } + break; + + default: + printf("%d to %d\n", low_write_, value); + break; } } else { // This is a write to the RAM pointer. @@ -581,6 +612,10 @@ void TMS9918::set_register(int address, uint8_t value) { } } +uint8_t TMS9918::get_current_line() { + return static_cast(row_); +} + uint8_t TMS9918::get_register(int address) { write_phase_ = false; @@ -595,21 +630,44 @@ uint8_t TMS9918::get_register(int address) { // Reads from address 1 get the status register. uint8_t result = status_; status_ &= ~(StatusInterrupt | StatusFifthSprite | StatusSpriteCollision); + line_interrupt_pending_ = false; return result; } HalfCycles TMS9918::get_time_until_interrupt() { - // TODO: line interrupts. - if(!generate_interrupts_) return HalfCycles(-1); + if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles(-1); if(get_interrupt_line()) return HalfCycles(0); + // Calculate the amount of time until the next end-of-frame interrupt. const int half_cycles_per_frame = mode_timing_.total_lines * 228 * 2; - int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame; - return HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame); + const int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame; + const auto time_until_frame_interrupt = HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame); + + // Calculate the number of times the line interrupt position will be decremented this frame. +// return HalfCycles(20); +/* auto time_until_line_count = mode_timing_.line_interrupt_position - row_; + auto decrements_left_this_frame = mode_timing_.pixel_lines - row_; + if(time_until_line_count > 0) { + ++decrements_left_this_frame; + } + + // If that's enough to underflow the line counter, there's the next interupt. + HalfCycles time_until_line_interrupt; + if(decrements_left_this_frame >= line_interrupt_counter+1) { + time_until_line_interrupt = HalfCycles + } + + if(!enable_line_interrupts_) { + return time_until_frame_interrupt; + } else if(!generate_interrupts_) { + + }*/ + + return time_until_frame_interrupt; } bool TMS9918::get_interrupt_line() { - return (status_ & StatusInterrupt) && generate_interrupts_; + return ((status_ & StatusInterrupt) && generate_interrupts_) || (enable_line_interrupts_ && line_interrupt_pending_); } // MARK: - diff --git a/Components/9918/9918.hpp b/Components/9918/9918.hpp index cf02cd074..2ffc010df 100644 --- a/Components/9918/9918.hpp +++ b/Components/9918/9918.hpp @@ -63,6 +63,9 @@ class TMS9918: public Base { /*! Gets a register value. */ uint8_t get_register(int address); + /*! Gets the current scan line; provided by the Master System only. */ + uint8_t get_current_line(); + /*! Returns the amount of time until get_interrupt_line would next return true if there are no interceding calls to set_register or get_register. diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 8410d50a5..8f1f90cf7 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -153,8 +153,17 @@ class Base { // if sprites beyond this number should be visible // then the appropriate status information will be set. int maximum_visible_sprites = 4; + + // + int end_of_frame_interrupt_position = 342; + int line_interrupt_position = -1; } mode_timing_; + uint8_t line_interrupt_target = 0; + uint8_t line_interrupt_counter = 0; + bool enable_line_interrupts_ = false; + bool line_interrupt_pending_ = false; + // The line mode describes the proper timing diagram for the current line. enum class LineMode { Text, @@ -174,7 +183,6 @@ class Base { bool vertical_scroll_lock = false; bool horizontal_scroll_lock = false; bool hide_left_column = false; - bool enable_line_interrupts = false; bool shift_sprites_8px_left = false; bool mode4_enable = false; uint8_t horizontal_scroll = 0; @@ -673,7 +681,7 @@ class Base { background_render_block(91, 12); background_render_block(107, 16); background_render_block(123, 20); - background_render_block(139, 24); + background_render_block(139, 24); // TODO: this and the next one should ignore master_system_.vertical_scroll. background_render_block(156, 28); return; diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 4c67880ec..d8bb20581 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -163,8 +163,10 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Write: if(address >= 0xfffd && cartridge_.size() > 48*1024) { - paging_registers_[address - 0xfffd] = *cycle.value; - page_cartridge(); + if(paging_registers_[address - 0xfffd] != *cycle.value) { + paging_registers_[address - 0xfffd] = *cycle.value; + page_cartridge(); + } } else if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value; break; @@ -180,8 +182,8 @@ class ConcreteMachine: *cycle.value = 0xff; break; case 0x40: case 0x41: - printf("TODO: [input] get current line\n"); - *cycle.value = 0xff; + update_video(); + *cycle.value = vdp_->get_current_line(); break; case 0x80: case 0x81: update_video(); From 76f3b9f6badfb0fd346df27197918b70e8789716 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 6 Oct 2018 14:26:00 -0400 Subject: [PATCH 39/88] Fixed: paging writes don't obstruct RAM. --- Machines/MasterSystem/MasterSystem.cpp | 45 ++++++++++++++------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index d8bb20581..60a81fa2b 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -168,7 +168,8 @@ class ConcreteMachine: page_cartridge(); } } - else if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value; + + if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value; break; case CPU::Z80::PartialMachineCycle::Input: @@ -212,18 +213,8 @@ class ConcreteMachine: case 0x00: if(model_ == Analyser::Static::Sega::Target::Model::MasterSystem) { // TODO: Obey the RAM enable. - - // Either install the cartridge or don't. - if(!((*cycle.value) & 0x40)) { - page_cartridge(); - } else { - map(read_pointers_, nullptr, 0xc000, 0x0000); - } - - // Throw the BIOS on top if it isn't disabled. - if(!((*cycle.value) & 0x08)) { - map(read_pointers_, bios_, 8*1024, 0); - } + memory_control_ = *cycle.value; + page_cartridge(); } break; case 0x01: @@ -317,14 +308,28 @@ class ConcreteMachine: } uint8_t paging_registers_[3] = {0, 1, 2}; + uint8_t memory_control_ = 0; void page_cartridge() { - for(size_t c = 0; c < 3; ++c) { - const size_t start_addr = (paging_registers_[c] * 0x4000) % cartridge_.size(); - map( - read_pointers_, - cartridge_.data() + start_addr, - std::min(static_cast(0x4000), cartridge_.size() - start_addr), - c * 0x4000); + // Either install the cartridge or don't. + if(!(memory_control_ & 0x40)) { + for(size_t c = 0; c < 3; ++c) { + const size_t start_addr = (paging_registers_[c] * 0x4000) % cartridge_.size(); + map( + read_pointers_, + cartridge_.data() + start_addr, + std::min(static_cast(0x4000), cartridge_.size() - start_addr), + c * 0x4000); + } + + // The first 1kb doesn't page though. + map(read_pointers_, cartridge_.data(), 0x400, 0x0000); + } else { + map(read_pointers_, nullptr, 0xc000, 0x0000); + } + + // Throw the BIOS on top if it isn't disabled. + if(!(memory_control_ & 0x08)) { + map(read_pointers_, bios_, 8*1024, 0); } } }; From e6510dc87b46a9c2ce70bb384b6da4b01ad9fe5a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 6 Oct 2018 19:27:19 -0400 Subject: [PATCH 40/88] Attempts to get at least as far as picking visible sprite indices. --- Components/9918/9918.cpp | 33 +++++----- Components/9918/Implementation/9918Base.hpp | 68 +++++++++++---------- 2 files changed, 55 insertions(+), 46 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 4b26a10f8..ccd85936b 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -16,7 +16,7 @@ using namespace TI::TMS; namespace { const uint8_t StatusInterrupt = 0x80; -const uint8_t StatusFifthSprite = 0x40; +const uint8_t StatusSpriteOverflow = 0x40; const int StatusSpriteCollisionShift = 5; const uint8_t StatusSpriteCollision = 0x20; @@ -91,33 +91,38 @@ Outputs::CRT::CRT *TMS9918::get_crt() { return crt_.get(); } -void Base::test_sprite(int sprite_number, int screen_row) { -/* if(!(status_ & StatusFifthSprite)) { +void Base::reset_sprite_collection() { + sprite_set_.sprites_stopped = false; + sprite_set_.fetched_sprite_slot = sprite_set_.active_sprite_slot; + sprite_set_.active_sprite_slot = 0; +} + +void Base::posit_sprite(int sprite_number, int sprite_position, int screen_row) { + if(!(status_ & StatusSpriteOverflow)) { status_ = static_cast((status_ & ~31) | sprite_number); } - if(sprites_stopped_) + if(sprite_set_.sprites_stopped) return; - const int sprite_position = ram_[sprite_attribute_table_address_ + static_cast(sprite_number << 2)]; +// const int sprite_position = ram_[sprite_attribute_table_address_ + static_cast(sprite_number << 2)]; // A sprite Y of 208 means "don't scan the list any further". - if(sprite_position == 208) { - sprites_stopped_ = true; + if(mode_timing_.allow_sprite_terminator && sprite_position == 208) { + sprite_set_.sprites_stopped = true; return; } const int sprite_row = (screen_row - sprite_position)&255; if(sprite_row < 0 || sprite_row >= sprite_height_) return; - const int active_sprite_slot = sprite_sets_[active_sprite_set_].active_sprite_slot; - if(active_sprite_slot == 4) { - status_ |= StatusFifthSprite; + if(sprite_set_.active_sprite_slot == mode_timing_.maximum_visible_sprites) { + status_ |= StatusSpriteOverflow; return; } - SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[active_sprite_slot]; + SpriteSet::ActiveSprite &sprite = sprite_set_.active_sprites[sprite_set_.active_sprite_slot]; sprite.index = sprite_number; sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0); - sprite_sets_[active_sprite_set_].active_sprite_slot++;*/ + ++sprite_set_.active_sprite_slot; } void Base::get_sprite_contents(int field, int cycles_left, int screen_row) { @@ -254,7 +259,7 @@ void TMS9918::run_for(const HalfCycles cycles) { // Left border. intersect(73, mode_timing_.first_pixel_output_column, output_border(end - start)); - // In pixel area: + // Pixel region. intersect( mode_timing_.first_pixel_output_column, mode_timing_.next_border_column, @@ -629,7 +634,7 @@ uint8_t TMS9918::get_register(int address) { // Reads from address 1 get the status register. uint8_t result = status_; - status_ &= ~(StatusInterrupt | StatusFifthSprite | StatusSpriteCollision); + status_ &= ~(StatusInterrupt | StatusSpriteOverflow | StatusSpriteCollision); line_interrupt_pending_ = false; return result; } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 8f1f90cf7..09086f29d 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -157,6 +157,8 @@ class Base { // int end_of_frame_interrupt_position = 342; int line_interrupt_position = -1; + + bool allow_sprite_terminator = true; } mode_timing_; uint8_t line_interrupt_target = 0; @@ -218,10 +220,12 @@ class Base { } active_sprites[8]; int active_sprite_slot = 0; - bool sprites_stopped_ = false; + int fetched_sprite_slot = 0; + bool sprites_stopped = false; } sprite_set_; - inline void test_sprite(int sprite_number, int screen_row); + inline void reset_sprite_collection(); + inline void posit_sprite(int sprite_number, int sprite_y, int screen_row); inline void get_sprite_contents(int start, int cycles, int screen_row); enum class ScreenMode { @@ -273,7 +277,7 @@ class Base { } - void external_slot() { + void do_external_slot() { // TODO: write or read a value if one is queued and ready to read/write. // (and, later: update the command engine, if this is an MSX2). switch(queued_access_) { @@ -303,7 +307,7 @@ class Base { case n #define external_slot(n) \ - slot(n): external_slot(); + slot(n): do_external_slot(); #define external_slots_2(n) \ external_slot(n); \ @@ -596,13 +600,10 @@ class Base { - sprite n+1, tile graphic second word */ -#define sprite_y_read(location) \ - slot(location): - -/* - TODO: sprite_y_read should fetch: - - sprite n and n+1, y position -*/ +#define sprite_y_read(location, sprite) \ + slot(location): \ + posit_sprite(sprite, ram_[sprite_attribute_table_address_ + sprite], row_); \ + posit_sprite(sprite, ram_[sprite_attribute_table_address_ + sprite + 1], row_); \ #define fetch_tile_name(column) {\ const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\ @@ -620,21 +621,21 @@ class Base { master_system_.tile_graphics[column][3] = ram_[master_system_.names[column].offset+3]; \ } -#define background_render_block(location, column) \ +#define background_render_block(location, column, sprite) \ slot(location): fetch_tile_name(column) \ external_slot(location+1); \ slot(location+2): \ slot(location+3): fetch_tile(column) \ slot(location+4): fetch_tile_name(column+1) \ - sprite_y_read(location+5); \ + sprite_y_read(location+5, sprite); \ slot(location+6): \ slot(location+7): fetch_tile(column+1) \ slot(location+8): fetch_tile_name(column+2) \ - sprite_y_read(location+9); \ + sprite_y_read(location+9, sprite+2); \ slot(location+10): \ slot(location+11): fetch_tile(column+2) \ slot(location+12): fetch_tile_name(column+3) \ - sprite_y_read(location+13); \ + sprite_y_read(location+13, sprite+4); \ slot(location+14): \ slot(location+15): fetch_tile(column+3) @@ -664,25 +665,28 @@ class Base { sprite_fetch_block(21, 4); sprite_fetch_block(27, 6); - external_slots_2(33); + slot(33): + reset_sprite_collection(); + do_external_slot(); + external_slot(34); - sprite_y_read(35); - sprite_y_read(36); - sprite_y_read(37); - sprite_y_read(38); - sprite_y_read(39); - sprite_y_read(40); - sprite_y_read(41); - sprite_y_read(42); + sprite_y_read(35, 0); + sprite_y_read(36, 2); + sprite_y_read(37, 4); + sprite_y_read(38, 6); + sprite_y_read(39, 8); + sprite_y_read(40, 10); + sprite_y_read(41, 12); + sprite_y_read(42, 14); - background_render_block(43, 0); - background_render_block(59, 4); - background_render_block(75, 8); - background_render_block(91, 12); - background_render_block(107, 16); - background_render_block(123, 20); - background_render_block(139, 24); // TODO: this and the next one should ignore master_system_.vertical_scroll. - background_render_block(156, 28); + background_render_block(43, 0, 16); + background_render_block(59, 4, 22); + background_render_block(75, 8, 28); + background_render_block(91, 12, 34); + background_render_block(107, 16, 40); + background_render_block(123, 20, 46); + background_render_block(139, 24, 52); // TODO: this and the next one should ignore master_system_.vertical_scroll. + background_render_block(156, 28, 58); return; } From 338aec293029666692d7e4a01b90359f40b6df9c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 6 Oct 2018 22:07:04 -0400 Subject: [PATCH 41/88] Groups background fetches and experimentally seeks to daub sprites as white. --- Components/9918/9918.cpp | 8 +++ Components/9918/Implementation/9918Base.hpp | 56 +++++++++++++-------- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index ccd85936b..be2a927d8 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -779,6 +779,14 @@ void Base::draw_sms(int start, int end) { pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] = pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] = master_system_.colour_ram[16 + background_colour_]; + + // EXPERIMENTAL: chuck sprite outlines on as a post-fix. + for(int c = 0; c < sprite_set_.fetched_sprite_slot; ++c) { + int x = -sprite_set_.active_sprites[c].shift_position; + for(int ox = x; ox < x+8; ox++) { + if(ox >= 0 && ox < 256) pixel_origin_[ox] = 0xffffffff; + } + } } // const int pixels_left = pixels_end - output_column_; // const int pixel_location = output_column_ - first_pixel_column_; diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 09086f29d..60cd95541 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -213,9 +213,7 @@ class Base { int index = 0; int row = 0; - uint8_t info[4]; - uint8_t image[2]; - + uint8_t image[4]; int shift_position = 0; } active_sprites[8]; @@ -582,13 +580,25 @@ class Base { ************************************************/ template void fetch_sms(int start, int end) { +#define sprite_fetch(sprite) {\ + sprite_set_.active_sprites[sprite].shift_position = -ram_[sprite_attribute_table_address_ + 128 + (sprite_set_.active_sprites[sprite].index << 1)] + (master_system_.shift_sprites_8px_left ? 8 : 0); \ + sprite_set_.active_sprites[sprite].image[0] = \ + sprite_set_.active_sprites[sprite].image[1] = \ + sprite_set_.active_sprites[sprite].image[2] = \ + sprite_set_.active_sprites[sprite].image[3] = 0xff; \ + } + +// size_t graphic_location = sprite_generator_table_address_; \ + #define sprite_fetch_block(location, sprite) \ slot(location): \ slot(location+1): \ slot(location+2): \ slot(location+3): \ slot(location+4): \ - slot(location+5): + slot(location+5): \ + sprite_fetch(sprite);\ + sprite_fetch(sprite+1); /* TODO: sprite_render_block should fetch: @@ -614,32 +624,36 @@ class Base { ) + sub_row[(master_system_.names[column].flags&4) >> 2]; \ } -#define fetch_tile(column) {\ - master_system_.tile_graphics[column][0] = ram_[master_system_.names[column].offset]; \ - master_system_.tile_graphics[column][1] = ram_[master_system_.names[column].offset+1]; \ - master_system_.tile_graphics[column][2] = ram_[master_system_.names[column].offset+2]; \ - master_system_.tile_graphics[column][3] = ram_[master_system_.names[column].offset+3]; \ - } +#define fetch_tile(column) \ + master_system_.tile_graphics[column][0] = ram_[master_system_.names[column].offset]; \ + master_system_.tile_graphics[column][1] = ram_[master_system_.names[column].offset+1]; \ + master_system_.tile_graphics[column][2] = ram_[master_system_.names[column].offset+2]; \ + master_system_.tile_graphics[column][3] = ram_[master_system_.names[column].offset+3]; #define background_render_block(location, column, sprite) \ slot(location): fetch_tile_name(column) \ - external_slot(location+1); \ + external_slot(location+1); \ slot(location+2): \ - slot(location+3): fetch_tile(column) \ - slot(location+4): fetch_tile_name(column+1) \ - sprite_y_read(location+5, sprite); \ + slot(location+3): \ + slot(location+4): \ + fetch_tile(column) \ + fetch_tile_name(column+1) \ + sprite_y_read(location+5, sprite); \ slot(location+6): \ - slot(location+7): fetch_tile(column+1) \ - slot(location+8): fetch_tile_name(column+2) \ - sprite_y_read(location+9, sprite+2); \ + slot(location+7): \ + slot(location+8): \ + fetch_tile(column+1) \ + fetch_tile_name(column+2) \ + sprite_y_read(location+9, sprite+2); \ slot(location+10): \ - slot(location+11): fetch_tile(column+2) \ - slot(location+12): fetch_tile_name(column+3) \ - sprite_y_read(location+13, sprite+4); \ + slot(location+11): \ + slot(location+12): \ + fetch_tile(column+2) \ + fetch_tile_name(column+3) \ + sprite_y_read(location+13, sprite+4); \ slot(location+14): \ slot(location+15): fetch_tile(column+3) - const int scrolled_row = (row_ + master_system_.vertical_scroll) % 224; const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((scrolled_row & ~7) << 3) | 0x3800); const size_t sub_row[2] = {static_cast((scrolled_row & 7) << 2), 28 ^ static_cast((scrolled_row & 7) << 2)}; From bfb9d8ccb67de3e8cc178f4515d3c3154dadf11d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Oct 2018 14:32:20 -0400 Subject: [PATCH 42/88] At least attempts to use proper addressing for sprite info fetches. --- Components/9918/Implementation/9918Base.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 60cd95541..873e9b05b 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -581,7 +581,10 @@ class Base { template void fetch_sms(int start, int end) { #define sprite_fetch(sprite) {\ - sprite_set_.active_sprites[sprite].shift_position = -ram_[sprite_attribute_table_address_ + 128 + (sprite_set_.active_sprites[sprite].index << 1)] + (master_system_.shift_sprites_8px_left ? 8 : 0); \ + sprite_set_.active_sprites[sprite].shift_position = \ + -ram_[\ + (sprite_attribute_table_address_ | 0x7f) & (0x3f80 | (sprite_set_.active_sprites[sprite].index << 1))\ + ] + (master_system_.shift_sprites_8px_left ? 8 : 0); \ sprite_set_.active_sprites[sprite].image[0] = \ sprite_set_.active_sprites[sprite].image[1] = \ sprite_set_.active_sprites[sprite].image[2] = \ @@ -612,8 +615,8 @@ class Base { #define sprite_y_read(location, sprite) \ slot(location): \ - posit_sprite(sprite, ram_[sprite_attribute_table_address_ + sprite], row_); \ - posit_sprite(sprite, ram_[sprite_attribute_table_address_ + sprite + 1], row_); \ + posit_sprite(sprite, ram_[(sprite_attribute_table_address_ | 0x7f) & (sprite | 0x3f00)], row_); \ + posit_sprite(sprite, ram_[(sprite_attribute_table_address_ | 0x7f) & ((sprite + 1) | 0x3f00)], row_); \ #define fetch_tile_name(column) {\ const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\ From df411b4ede4740336464681e08cd4539d0a1719a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Oct 2018 16:40:32 -0400 Subject: [PATCH 43/88] Corrects storage of visible sprites. --- Components/9918/Implementation/9918Base.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 873e9b05b..c9b1e1243 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -616,7 +616,7 @@ class Base { #define sprite_y_read(location, sprite) \ slot(location): \ posit_sprite(sprite, ram_[(sprite_attribute_table_address_ | 0x7f) & (sprite | 0x3f00)], row_); \ - posit_sprite(sprite, ram_[(sprite_attribute_table_address_ | 0x7f) & ((sprite + 1) | 0x3f00)], row_); \ + posit_sprite(sprite+1, ram_[(sprite_attribute_table_address_ | 0x7f) & ((sprite + 1) | 0x3f00)], row_); \ #define fetch_tile_name(column) {\ const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\ From ee20e423728cdd50983ad508fbc7d1ab3d0dac91 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Oct 2018 16:53:25 -0400 Subject: [PATCH 44/88] Makes initial attempt at collecting sprite contents. With test plotting, indicating some sort of issue. --- Components/9918/9918.cpp | 31 ++++++++------------- Components/9918/Implementation/9918Base.hpp | 10 +++---- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index be2a927d8..da4c5958e 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -783,27 +783,20 @@ void Base::draw_sms(int start, int end) { // EXPERIMENTAL: chuck sprite outlines on as a post-fix. for(int c = 0; c < sprite_set_.fetched_sprite_slot; ++c) { int x = -sprite_set_.active_sprites[c].shift_position; + pattern = *reinterpret_cast(sprite_set_.active_sprites[c].image); for(int ox = x; ox < x+8; ox++) { - if(ox >= 0 && ox < 256) pixel_origin_[ox] = 0xffffffff; + if(ox >= 0 && ox < 256) { + pixel_origin_[ox] = + master_system_.colour_ram[ + ((pattern_index[3] & 0x80) >> 4) | + ((pattern_index[2] & 0x80) >> 5) | + ((pattern_index[1] & 0x80) >> 6) | + ((pattern_index[0] & 0x80) >> 7) | + 0x10 + ]; + } + pattern <<= 1; } } } -// const int pixels_left = pixels_end - output_column_; -// const int pixel_location = output_column_ - first_pixel_column_; -// const int reverses[2] = {0, 7}; -// for(int c = 0; c < pixels_left; ++c) { -// const int column = (pixel_location + c) >> 3; -// const int shift = 4 + (((pixel_location + c) & 7) ^ reverses[(master_system_.names[column].flags&2) >> 1]); -// int value = -// ( -// ( -// ((master_system_.tile_graphics[column][3] << shift) & 0x800) | -// ((master_system_.tile_graphics[column][2] << (shift - 1)) & 0x400) | -// ((master_system_.tile_graphics[column][1] << (shift - 2)) & 0x200) | -// ((master_system_.tile_graphics[column][0] << (shift - 3)) & 0x100) -// ) >> 8 -// ) | ((master_system_.names[column].flags&0x08) << 1); -// -// pixel_target_[c] = master_system_.colour_ram[value]; -// } } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index c9b1e1243..a893e17d9 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -585,13 +585,13 @@ class Base { -ram_[\ (sprite_attribute_table_address_ | 0x7f) & (0x3f80 | (sprite_set_.active_sprites[sprite].index << 1))\ ] + (master_system_.shift_sprites_8px_left ? 8 : 0); \ - sprite_set_.active_sprites[sprite].image[0] = \ - sprite_set_.active_sprites[sprite].image[1] = \ - sprite_set_.active_sprites[sprite].image[2] = \ - sprite_set_.active_sprites[sprite].image[3] = 0xff; \ + size_t graphic_location = (sprite_generator_table_address_ | 0xfff) & (0x2000 | (sprite_set_.active_sprites[sprite].index << 5) | (sprite_set_.active_sprites[sprite].row << 2)); \ + sprite_set_.active_sprites[sprite].image[0] = ram_[graphic_location]; \ + sprite_set_.active_sprites[sprite].image[1] = ram_[graphic_location+1]; \ + sprite_set_.active_sprites[sprite].image[2] = ram_[graphic_location+2]; \ + sprite_set_.active_sprites[sprite].image[3] = ram_[graphic_location+3]; \ } -// size_t graphic_location = sprite_generator_table_address_; \ #define sprite_fetch_block(location, sprite) \ slot(location): \ From 3bead070438ca68f328fc367d91ce4f9809d472a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Oct 2018 17:15:42 -0400 Subject: [PATCH 45/88] Introduces proper indirection for sprite patterns. This seems to work, so the onus is now back on the rendering loop. --- Components/9918/9918.cpp | 2 + Components/9918/Implementation/9918Base.hpp | 57 +++++++++------------ 2 files changed, 25 insertions(+), 34 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index da4c5958e..51a0c6c06 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -573,10 +573,12 @@ void TMS9918::set_register(int address, uint8_t value) { case 5: sprite_attribute_table_address_ = static_cast((low_write_ & 0x7f) << 7); + if(is_sega_vdp(personality_)) sprite_attribute_table_address_ |= 0x7f; break; case 6: sprite_generator_table_address_ = static_cast((low_write_ & 0x07) << 11); + if(is_sega_vdp(personality_)) sprite_generator_table_address_ |= 0x7ff; break; case 7: diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index a893e17d9..6cf2ecefb 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -583,16 +583,18 @@ class Base { #define sprite_fetch(sprite) {\ sprite_set_.active_sprites[sprite].shift_position = \ -ram_[\ - (sprite_attribute_table_address_ | 0x7f) & (0x3f80 | (sprite_set_.active_sprites[sprite].index << 1))\ - ] + (master_system_.shift_sprites_8px_left ? 8 : 0); \ - size_t graphic_location = (sprite_generator_table_address_ | 0xfff) & (0x2000 | (sprite_set_.active_sprites[sprite].index << 5) | (sprite_set_.active_sprites[sprite].row << 2)); \ + sprite_attribute_table_address_ & size_t(0x3f80 | (sprite_set_.active_sprites[sprite].index << 1))\ + ] + (master_system_.shift_sprites_8px_left ? size_t(8) : size_t(0)); \ + const uint8_t name = ram_[\ + sprite_attribute_table_address_ & size_t(0x3f81 | (sprite_set_.active_sprites[sprite].index << 1))\ + ];\ + const size_t graphic_location = sprite_generator_table_address_ & size_t(0x2000 | (name << 5) | (sprite_set_.active_sprites[sprite].row << 2)); \ sprite_set_.active_sprites[sprite].image[0] = ram_[graphic_location]; \ sprite_set_.active_sprites[sprite].image[1] = ram_[graphic_location+1]; \ sprite_set_.active_sprites[sprite].image[2] = ram_[graphic_location+2]; \ sprite_set_.active_sprites[sprite].image[3] = ram_[graphic_location+3]; \ } - #define sprite_fetch_block(location, sprite) \ slot(location): \ slot(location+1): \ @@ -603,20 +605,10 @@ class Base { sprite_fetch(sprite);\ sprite_fetch(sprite+1); -/* - TODO: sprite_render_block should fetch: - - sprite n, x position and name - - sprite n+1, x position and name - - sprite n, tile graphic first word - - sprite n, tile graphic second word - - sprite n+1, tile graphic first word - - sprite n+1, tile graphic second word -*/ - #define sprite_y_read(location, sprite) \ slot(location): \ - posit_sprite(sprite, ram_[(sprite_attribute_table_address_ | 0x7f) & (sprite | 0x3f00)], row_); \ - posit_sprite(sprite+1, ram_[(sprite_attribute_table_address_ | 0x7f) & ((sprite + 1) | 0x3f00)], row_); \ + posit_sprite(sprite, ram_[sprite_attribute_table_address_ & (sprite | 0x3f00)], row_); \ + posit_sprite(sprite+1, ram_[sprite_attribute_table_address_ & ((sprite + 1) | 0x3f00)], row_); \ #define fetch_tile_name(column) {\ const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\ @@ -633,7 +625,7 @@ class Base { master_system_.tile_graphics[column][2] = ram_[master_system_.names[column].offset+2]; \ master_system_.tile_graphics[column][3] = ram_[master_system_.names[column].offset+3]; -#define background_render_block(location, column, sprite) \ +#define background_fetch_block(location, column, sprite) \ slot(location): fetch_tile_name(column) \ external_slot(location+1); \ slot(location+2): \ @@ -658,17 +650,11 @@ class Base { slot(location+15): fetch_tile(column+3) const int scrolled_row = (row_ + master_system_.vertical_scroll) % 224; + const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((scrolled_row & ~7) << 3) | 0x3800); const size_t sub_row[2] = {static_cast((scrolled_row & 7) << 2), 28 ^ static_cast((scrolled_row & 7) << 2)}; const int horizontal_offset = (row_ >= 16 || !master_system_.horizontal_scroll_lock) ? (master_system_.horizontal_scroll >> 3) : 0; - /* - To add, relative to the times below: - - hsync active at cycle 14; - hsync inactive at cycle 27; - */ - switch(start) { default: external_slots_4(0); @@ -696,21 +682,24 @@ class Base { sprite_y_read(41, 12); sprite_y_read(42, 14); - background_render_block(43, 0, 16); - background_render_block(59, 4, 22); - background_render_block(75, 8, 28); - background_render_block(91, 12, 34); - background_render_block(107, 16, 40); - background_render_block(123, 20, 46); - background_render_block(139, 24, 52); // TODO: this and the next one should ignore master_system_.vertical_scroll. - background_render_block(156, 28, 58); + background_fetch_block(43, 0, 16); + background_fetch_block(59, 4, 22); + background_fetch_block(75, 8, 28); + background_fetch_block(91, 12, 34); + background_fetch_block(107, 16, 40); + background_fetch_block(123, 20, 46); + background_fetch_block(139, 24, 52); // TODO: this and the next one should ignore master_system_.vertical_scroll. + background_fetch_block(156, 28, 58); return; } -#undef background_render_block +#undef background_fetch_block +#undef fetch_tile +#undef fetch_tile_name #undef sprite_y_read -#undef sprite_render_block +#undef sprite_fetch_block +#undef sprite_fetch } #undef external_slot From 24644f1dd120088c4239cb48673bd6e619a93610 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Oct 2018 18:39:03 -0400 Subject: [PATCH 46/88] Adds a low-pass filter, picked entirely by ear, and switches to composite output, at least for now. --- Machines/MasterSystem/MasterSystem.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 60a81fa2b..54b4ffbdf 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -122,6 +122,8 @@ class ConcreteMachine: map(read_pointers_, ram_, 1024, 0xc000, 0x10000); map(write_pointers_, ram_, 1024, 0xc000, 0x10000); } + + speaker_.set_high_frequency_cutoff(8000); } ~ConcreteMachine() { @@ -130,7 +132,7 @@ class ConcreteMachine: void setup_output(float aspect_ratio) override { vdp_.reset(new TI::TMS::TMS9918(model_ == Analyser::Static::Sega::Target::Model::SG1000 ? TI::TMS::TMS9918A : TI::TMS::SMSVDP)); -// get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); + get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite); } void close_output() override { From 6975ed22c0ed1ca5fc54b331ebcc757f6ac9f80e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Oct 2018 18:55:35 -0400 Subject: [PATCH 47/88] Doubles down on address-storage format, and implements the vertical scrolling lock. --- Components/9918/9918.cpp | 36 +++++++----- Components/9918/Implementation/9918Base.hpp | 62 +++++++++++++-------- 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 51a0c6c06..f45ae1306 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -560,25 +560,23 @@ void TMS9918::set_register(int address, uint8_t value) { break; case 2: - pattern_name_address_ = static_cast((low_write_ & 0xf) << 10); + pattern_name_address_ = size_t((low_write_ & 0xf) << 10) | 0x3ff; break; case 3: - colour_table_address_ = static_cast(low_write_ << 6); + colour_table_address_ = size_t(low_write_ << 6) | 0x1f; break; case 4: - pattern_generator_table_address_ = static_cast((low_write_ & 0x07) << 11); + pattern_generator_table_address_ = size_t((low_write_ & 0x07) << 11) | 0x7ff; break; case 5: - sprite_attribute_table_address_ = static_cast((low_write_ & 0x7f) << 7); - if(is_sega_vdp(personality_)) sprite_attribute_table_address_ |= 0x7f; + sprite_attribute_table_address_ = size_t((low_write_ & 0x7f) << 7) | 0x7f; break; case 6: - sprite_generator_table_address_ = static_cast((low_write_ & 0x07) << 11); - if(is_sega_vdp(personality_)) sprite_generator_table_address_ |= 0x7ff; + sprite_generator_table_address_ = size_t((low_write_ & 0x07) << 11) | 0x7ff; break; case 7: @@ -605,14 +603,15 @@ void TMS9918::set_register(int address, uint8_t value) { break; default: - printf("%d to %d\n", low_write_, value); + printf("Unknown TMS write: %d to %d\n", low_write_, value); break; } } else { - // This is a write to the RAM pointer. + // This is an access via the RAM pointer. ram_pointer_ = static_cast(low_write_ | (value << 8)); if(!(value & 0x40)) { - // Officially a 'read' set, so perform lookahead. + // A read request is enqueued upon setting the address; conversely a write + // won't be enqueued unless and until some actual data is supplied. queued_access_ = MemoryAccess::Read; } master_system_.cram_is_selected = false; @@ -788,14 +787,21 @@ void Base::draw_sms(int start, int end) { pattern = *reinterpret_cast(sprite_set_.active_sprites[c].image); for(int ox = x; ox < x+8; ox++) { if(ox >= 0 && ox < 256) { - pixel_origin_[ox] = - master_system_.colour_ram[ + if( ((pattern_index[3] & 0x80) >> 4) | ((pattern_index[2] & 0x80) >> 5) | ((pattern_index[1] & 0x80) >> 6) | - ((pattern_index[0] & 0x80) >> 7) | - 0x10 - ]; + ((pattern_index[0] & 0x80) >> 7) + ) { + pixel_origin_[ox] = + master_system_.colour_ram[ + ((pattern_index[3] & 0x80) >> 4) | + ((pattern_index[2] & 0x80) >> 5) | + ((pattern_index[1] & 0x80) >> 6) | + ((pattern_index[0] & 0x80) >> 7) | + 0x10 + ]; + } } pattern <<= 1; } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 6cf2ecefb..992ae5026 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -276,8 +276,6 @@ class Base { void do_external_slot() { - // TODO: write or read a value if one is queued and ready to read/write. - // (and, later: update the command engine, if this is an MSX2). switch(queued_access_) { default: return; @@ -427,8 +425,8 @@ class Base { fetch_columns_4(location, column); \ fetch_columns_4(location+12, column+4); - const size_t row_base = pattern_name_address_ + static_cast(row_ >> 3) * 40; - const size_t row_offset = pattern_generator_table_address_ + (row_ & 7); + const size_t row_base = pattern_name_address_ & static_cast(row_ >> 3) * 40; + const size_t row_offset = pattern_generator_table_address_ & (row_ & 7); switch(start) { default: @@ -610,13 +608,13 @@ class Base { posit_sprite(sprite, ram_[sprite_attribute_table_address_ & (sprite | 0x3f00)], row_); \ posit_sprite(sprite+1, ram_[sprite_attribute_table_address_ & ((sprite + 1) | 0x3f00)], row_); \ -#define fetch_tile_name(column) {\ +#define fetch_tile_name(column, row_info) {\ const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\ - const size_t address = pattern_address_base + (scrolled_column << 1); \ + const size_t address = row_info.pattern_address_base + (scrolled_column << 1); \ master_system_.names[column].flags = ram_[address+1]; \ master_system_.names[column].offset = static_cast( \ (((master_system_.names[column].flags&1) << 8) | ram_[address]) << 5 \ - ) + sub_row[(master_system_.names[column].flags&4) >> 2]; \ + ) + row_info.sub_row[(master_system_.names[column].flags&4) >> 2]; \ } #define fetch_tile(column) \ @@ -625,36 +623,52 @@ class Base { master_system_.tile_graphics[column][2] = ram_[master_system_.names[column].offset+2]; \ master_system_.tile_graphics[column][3] = ram_[master_system_.names[column].offset+3]; -#define background_fetch_block(location, column, sprite) \ - slot(location): fetch_tile_name(column) \ +#define background_fetch_block(location, column, sprite, row_info) \ + slot(location): fetch_tile_name(column, row_info) \ external_slot(location+1); \ slot(location+2): \ slot(location+3): \ slot(location+4): \ fetch_tile(column) \ - fetch_tile_name(column+1) \ + fetch_tile_name(column+1, row_info) \ sprite_y_read(location+5, sprite); \ slot(location+6): \ slot(location+7): \ slot(location+8): \ fetch_tile(column+1) \ - fetch_tile_name(column+2) \ + fetch_tile_name(column+2, row_info) \ sprite_y_read(location+9, sprite+2); \ slot(location+10): \ slot(location+11): \ slot(location+12): \ fetch_tile(column+2) \ - fetch_tile_name(column+3) \ + fetch_tile_name(column+3, row_info) \ sprite_y_read(location+13, sprite+4); \ slot(location+14): \ slot(location+15): fetch_tile(column+3) - const int scrolled_row = (row_ + master_system_.vertical_scroll) % 224; - - const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast(((scrolled_row & ~7) << 3) | 0x3800); - const size_t sub_row[2] = {static_cast((scrolled_row & 7) << 2), 28 ^ static_cast((scrolled_row & 7) << 2)}; + // Determine the coarse horizontal scrolling offset; this isn't applied on the first two lines if the programmer has requested it. const int horizontal_offset = (row_ >= 16 || !master_system_.horizontal_scroll_lock) ? (master_system_.horizontal_scroll >> 3) : 0; + // Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't. + // The programmer can opt out of applying vertical scrolling to the right-hand portion of the display. + const int scrolled_row = (row_ + master_system_.vertical_scroll) % 224; + struct RowInfo { + size_t pattern_address_base; + size_t sub_row[2]; + }; + const RowInfo scrolled_row_info = { + pattern_name_address_ & static_cast(((scrolled_row & ~7) << 3) | 0x3800), + {static_cast((scrolled_row & 7) << 2), 28 ^ static_cast((scrolled_row & 7) << 2)} + }; + RowInfo row_info; + if(master_system_.vertical_scroll_lock) { + row_info.pattern_address_base = pattern_name_address_ & static_cast(((row_ & ~7) << 3) | 0x3800); + row_info.sub_row[0] = size_t((row_ & 7) << 2); + row_info.sub_row[1] = 28 ^ size_t((row_ & 7) << 2); + } else row_info = scrolled_row_info; + + // ... and do the actual fetching, which follows this routine: switch(start) { default: external_slots_4(0); @@ -682,14 +696,14 @@ class Base { sprite_y_read(41, 12); sprite_y_read(42, 14); - background_fetch_block(43, 0, 16); - background_fetch_block(59, 4, 22); - background_fetch_block(75, 8, 28); - background_fetch_block(91, 12, 34); - background_fetch_block(107, 16, 40); - background_fetch_block(123, 20, 46); - background_fetch_block(139, 24, 52); // TODO: this and the next one should ignore master_system_.vertical_scroll. - background_fetch_block(156, 28, 58); + background_fetch_block(43, 0, 16, scrolled_row_info); + background_fetch_block(59, 4, 22, scrolled_row_info); + background_fetch_block(75, 8, 28, scrolled_row_info); + background_fetch_block(91, 12, 34, scrolled_row_info); + background_fetch_block(107, 16, 40, scrolled_row_info); + background_fetch_block(123, 20, 46, scrolled_row_info); + background_fetch_block(139, 24, 52, row_info); + background_fetch_block(156, 28, 58, row_info); return; } From 5f789092be31338f0b043f5b8dd25274e8c63c50 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Oct 2018 19:16:35 -0400 Subject: [PATCH 48/88] Flips sprite priority in the temporary renderer. The better to test other issues in the interim. --- Components/9918/9918.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index f45ae1306..2fe0fb186 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -782,7 +782,7 @@ void Base::draw_sms(int start, int end) { master_system_.colour_ram[16 + background_colour_]; // EXPERIMENTAL: chuck sprite outlines on as a post-fix. - for(int c = 0; c < sprite_set_.fetched_sprite_slot; ++c) { + for(int c = sprite_set_.fetched_sprite_slot - 1; c >= 0; --c) { int x = -sprite_set_.active_sprites[c].shift_position; pattern = *reinterpret_cast(sprite_set_.active_sprites[c].image); for(int ox = x; ox < x+8; ox++) { From bca2161a0586feee5629f10163c73088a3bb64c1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 7 Oct 2018 21:09:01 -0400 Subject: [PATCH 49/88] Fixes TMS text mode for the new addressing order. --- Components/9918/9918.cpp | 7 +++++++ Components/9918/Implementation/9918Base.hpp | 12 ++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 2fe0fb186..410870019 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -712,6 +712,13 @@ void Base::draw_tms_text(int start, int end) { void Base::draw_sms(int start, int end) { const bool is_end = end == 256; + // If this is the very start of the line, clear the background + // priority mask — it will be a bitfield in which 1s indicate locations + // where the background should take priority over the sprites. + if(!start) { + memset(master_system_.background_priority_mask, 0, sizeof(master_system_.background_priority_mask)); + } + // Shift the output window by the fine scroll amount, and fill in // any border pixels that leaves on the left-hand side. if(row_ >= 16 || !master_system_.horizontal_scroll_lock) { diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 992ae5026..b56933d08 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -154,10 +154,13 @@ class Base { // then the appropriate status information will be set. int maximum_visible_sprites = 4; - // + // Set the position, in cycles, of the two interrupts, + // within a line. int end_of_frame_interrupt_position = 342; int line_interrupt_position = -1; + // Enables or disabled the recognition of 0xd0 as a sprite + // list terminator. bool allow_sprite_terminator = true; } mode_timing_; @@ -201,6 +204,7 @@ class Base { } names[32]; uint8_t tile_graphics[32][4]; size_t next_column = 0; + uint32_t background_priority_mask[9]; } master_system_; // Holds results of sprite data fetches that occur on this @@ -406,7 +410,7 @@ class Base { template void fetch_tms_text(int start, int end) { #define fetch_tile_name(location, column) slot(location): pattern_names_[column] = ram_[row_base + column]; -#define fetch_tile_pattern(location, column) slot(location): pattern_buffer_[column] = ram_[row_offset + static_cast(pattern_names_[column] << 3)]; +#define fetch_tile_pattern(location, column) slot(location): pattern_buffer_[column] = ram_[row_offset + size_t(pattern_names_[column] << 3)]; #define fetch_column(location, column) \ fetch_tile_name(location, column); \ @@ -425,8 +429,8 @@ class Base { fetch_columns_4(location, column); \ fetch_columns_4(location+12, column+4); - const size_t row_base = pattern_name_address_ & static_cast(row_ >> 3) * 40; - const size_t row_offset = pattern_generator_table_address_ & (row_ & 7); + const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast(row_ >> 3) * 40); + const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (row_ & 7)); switch(start) { default: From e410302237809827cb6988e5767aa000060210b9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 8 Oct 2018 22:43:10 -0400 Subject: [PATCH 50/88] Switches to real SMS line output composition. Including setting the sprite collision bit. --- Components/9918/9918.cpp | 199 +++++++++++--------- Components/9918/Implementation/9918Base.hpp | 9 +- 2 files changed, 120 insertions(+), 88 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 410870019..5f981a6aa 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -95,6 +95,10 @@ void Base::reset_sprite_collection() { sprite_set_.sprites_stopped = false; sprite_set_.fetched_sprite_slot = sprite_set_.active_sprite_slot; sprite_set_.active_sprite_slot = 0; + + for(int c = 0; c < sprite_set_.fetched_sprite_slot; ++c) { + sprite_set_.active_sprites[c].shift_position = 0; + } } void Base::posit_sprite(int sprite_number, int sprite_position, int screen_row) { @@ -710,108 +714,135 @@ void Base::draw_tms_text(int start, int end) { } void Base::draw_sms(int start, int end) { + int colour_buffer[256]; const bool is_end = end == 256; - // If this is the very start of the line, clear the background - // priority mask — it will be a bitfield in which 1s indicate locations - // where the background should take priority over the sprites. - if(!start) { - memset(master_system_.background_priority_mask, 0, sizeof(master_system_.background_priority_mask)); - } - - // Shift the output window by the fine scroll amount, and fill in - // any border pixels that leaves on the left-hand side. + /* + Add extra border for any pixels that fall before the fine scroll. + */ + int tile_start = start, tile_end = end; if(row_ >= 16 || !master_system_.horizontal_scroll_lock) { - start -= master_system_.horizontal_scroll & 7; - end -= master_system_.horizontal_scroll & 7; - if(start < 0) { - while(start < end && start < 0) { - *pixel_target_ = master_system_.colour_ram[16 + background_colour_]; - ++pixel_target_; - ++start; - } - if(start == end) return; + for(int c = start; c < (master_system_.horizontal_scroll & 7); ++c) { + colour_buffer[c] = 16 + background_colour_; } + tile_start -= master_system_.horizontal_scroll & 7; + tile_end -= master_system_.horizontal_scroll & 7; } - const int shift = start & 7; - int byte_column = start >> 3; - int pixels_left = end - start; - int length = std::min(pixels_left, 8 - shift); - uint32_t pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); + uint32_t pattern; uint8_t *const pattern_index = reinterpret_cast(&pattern); - if(master_system_.names[byte_column].flags&2) - pattern >>= shift; - else - pattern <<= shift; + /* + Add background tiles; these will fill the colour_buffer with values in which + the low five bits are a palette index, and bit six is set if this tile has + priority over sprites. + */ + if(tile_start < end) { + int offset = (master_system_.horizontal_scroll & 7) + tile_start; + const int shift = tile_start & 7; + int byte_column = tile_start >> 3; + int pixels_left = tile_end - tile_start; + int length = std::min(pixels_left, 8 - shift); - while(true) { - pixels_left -= length; - const int palette_offset = (master_system_.names[byte_column].flags&0x08) << 1; - if(master_system_.names[byte_column].flags&2) { - for(int c = 0; c < length; ++c) { - const int value = - ((pattern_index[3] & 0x01) << 3) | - ((pattern_index[2] & 0x01) << 2) | - ((pattern_index[1] & 0x01) << 1) | - ((pattern_index[0] & 0x01) << 0) | - palette_offset; - pixel_target_[c] = master_system_.colour_ram[value]; - pattern >>= 1; + pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); + if(master_system_.names[byte_column].flags&2) + pattern >>= shift; + else + pattern <<= shift; + + while(true) { + pixels_left -= length; + const int palette_offset = (master_system_.names[byte_column].flags&0x18) << 1; + if(master_system_.names[byte_column].flags&2) { + for(int c = 0; c < length; ++c) { + colour_buffer[offset] = + ((pattern_index[3] & 0x01) << 3) | + ((pattern_index[2] & 0x01) << 2) | + ((pattern_index[1] & 0x01) << 1) | + ((pattern_index[0] & 0x01) << 0) | + palette_offset; + ++offset; + pattern >>= 1; + } + } else { + for(int c = 0; c < length; ++c) { + colour_buffer[offset] = + ((pattern_index[3] & 0x80) >> 4) | + ((pattern_index[2] & 0x80) >> 5) | + ((pattern_index[1] & 0x80) >> 6) | + ((pattern_index[0] & 0x80) >> 7) | + palette_offset; + ++offset; + pattern <<= 1; + } } - } else { - for(int c = 0; c < length; ++c) { - const int value = - ((pattern_index[3] & 0x80) >> 4) | - ((pattern_index[2] & 0x80) >> 5) | - ((pattern_index[1] & 0x80) >> 6) | - ((pattern_index[0] & 0x80) >> 7) | - palette_offset; - pixel_target_[c] = master_system_.colour_ram[value]; - pattern <<= 1; + + if(!pixels_left) break; + length = std::min(8, pixels_left); + byte_column++; + pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); + } + } + + /* + Apply sprites (if any). + */ + if(sprite_set_.fetched_sprite_slot) { + int sprite_buffer[256]; + int sprite_collision = 0; + memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(int)); + + // Draw all sprites into the sprite buffer. + for(int index = sprite_set_.fetched_sprite_slot - 1; index >= 0; --index) { + SpriteSet::ActiveSprite &sprite = sprite_set_.active_sprites[index]; + if(sprite.shift_position < 16) { + int pixel_start = std::max(start, sprite.x); + + // TODO: it feels like the work below should be simplifiable; + // the double shift in particular, and hopefully the variable shift. + for(int c = pixel_start; c < end && sprite.shift_position < 16; ++c) { + const int shift = (sprite.shift_position >> 1); + const int sprite_colour = + (((sprite.image[3] << shift) & 0x80) >> 4) | + (((sprite.image[2] << shift) & 0x80) >> 5) | + (((sprite.image[1] << shift) & 0x80) >> 6) | + (((sprite.image[0] << shift) & 0x80) >> 7); + + if(sprite_colour) { + sprite_collision |= sprite_buffer[c]; + sprite_buffer[c] = sprite_colour | 0x10; + } + + sprite.shift_position += sprites_magnified_ ? 1 : 2; + } } } - pixel_target_ += length; - if(!pixels_left) break; - length = std::min(8, pixels_left); - byte_column++; - pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); + // Draw the sprite buffer onto the colour buffer, wherever the tile map doesn't have + // priority (or is transparent). + for(int c = start; c < end; ++c) { + if( + sprite_buffer[c] && + (!(colour_buffer[c]&0x20) || !(colour_buffer[c]&0xf)) + ) colour_buffer[c] = sprite_buffer[c]; + } + + if(sprite_collision) status_ |= StatusSpriteCollision; + } + + // Map from the 32-colour buffer to real output pixels. + for(int c = start; c < end; ++c) { + pixel_target_[c] = master_system_.colour_ram[colour_buffer[c] & 0x1f]; } // If the VDP is set to hide the left column and this is the final call that'll come // this line, hide it. - if(is_end && master_system_.hide_left_column) { - pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] = - pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] = - master_system_.colour_ram[16 + background_colour_]; - - // EXPERIMENTAL: chuck sprite outlines on as a post-fix. - for(int c = sprite_set_.fetched_sprite_slot - 1; c >= 0; --c) { - int x = -sprite_set_.active_sprites[c].shift_position; - pattern = *reinterpret_cast(sprite_set_.active_sprites[c].image); - for(int ox = x; ox < x+8; ox++) { - if(ox >= 0 && ox < 256) { - if( - ((pattern_index[3] & 0x80) >> 4) | - ((pattern_index[2] & 0x80) >> 5) | - ((pattern_index[1] & 0x80) >> 6) | - ((pattern_index[0] & 0x80) >> 7) - ) { - pixel_origin_[ox] = - master_system_.colour_ram[ - ((pattern_index[3] & 0x80) >> 4) | - ((pattern_index[2] & 0x80) >> 5) | - ((pattern_index[1] & 0x80) >> 6) | - ((pattern_index[0] & 0x80) >> 7) | - 0x10 - ]; - } - } - pattern <<= 1; - } + if(is_end) { + if(master_system_.hide_left_column) { + pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] = + pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] = + master_system_.colour_ram[16 + background_colour_]; } } } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index b56933d08..0116a3c06 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -204,7 +204,6 @@ class Base { } names[32]; uint8_t tile_graphics[32][4]; size_t next_column = 0; - uint32_t background_priority_mask[9]; } master_system_; // Holds results of sprite data fetches that occur on this @@ -218,6 +217,7 @@ class Base { int row = 0; uint8_t image[4]; + int x = 0; int shift_position = 0; } active_sprites[8]; @@ -459,6 +459,7 @@ class Base { #undef fetch_columns_4 #undef fetch_columns_2 #undef fetch_column +#undef fetch_tile_pattern #undef fetch_tile_name } @@ -583,10 +584,10 @@ class Base { template void fetch_sms(int start, int end) { #define sprite_fetch(sprite) {\ - sprite_set_.active_sprites[sprite].shift_position = \ - -ram_[\ + sprite_set_.active_sprites[sprite].x = \ + ram_[\ sprite_attribute_table_address_ & size_t(0x3f80 | (sprite_set_.active_sprites[sprite].index << 1))\ - ] + (master_system_.shift_sprites_8px_left ? size_t(8) : size_t(0)); \ + ] - (master_system_.shift_sprites_8px_left ? size_t(8) : size_t(0)); \ const uint8_t name = ram_[\ sprite_attribute_table_address_ & size_t(0x3f81 | (sprite_set_.active_sprites[sprite].index << 1))\ ];\ From 7a74fe2ff7756ff98a6b8621934c74ebce4fb810 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 8 Oct 2018 22:56:31 -0400 Subject: [PATCH 51/88] Corrects tile plotting window and eliminates a redundant local. --- Components/9918/9918.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 5f981a6aa..190d3cb27 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -715,7 +715,6 @@ void Base::draw_tms_text(int start, int end) { void Base::draw_sms(int start, int end) { int colour_buffer[256]; - const bool is_end = end == 256; /* Add extra border for any pixels that fall before the fine scroll. @@ -725,8 +724,10 @@ void Base::draw_sms(int start, int end) { for(int c = start; c < (master_system_.horizontal_scroll & 7); ++c) { colour_buffer[c] = 16 + background_colour_; } - tile_start -= master_system_.horizontal_scroll & 7; - tile_end -= master_system_.horizontal_scroll & 7; + + // Remove the border area from that to which tiles will be drawn. + tile_start = std::max(start - (master_system_.horizontal_scroll & 7), 0); + tile_end = std::max(end - (master_system_.horizontal_scroll & 7), 0); } @@ -838,7 +839,7 @@ void Base::draw_sms(int start, int end) { // If the VDP is set to hide the left column and this is the final call that'll come // this line, hide it. - if(is_end) { + if(end == 256) { if(master_system_.hide_left_column) { pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] = pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] = From 748366c70ea0f7061d2db8ca5c9143ac5cf6d2a1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 8 Oct 2018 23:06:22 -0400 Subject: [PATCH 52/88] Corrects buffer overrun when the horizontal scroll lock is on. --- Components/9918/9918.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 190d3cb27..70a9e4476 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -720,6 +720,7 @@ void Base::draw_sms(int start, int end) { Add extra border for any pixels that fall before the fine scroll. */ int tile_start = start, tile_end = end; + int tile_offset = start; if(row_ >= 16 || !master_system_.horizontal_scroll_lock) { for(int c = start; c < (master_system_.horizontal_scroll & 7); ++c) { colour_buffer[c] = 16 + background_colour_; @@ -728,6 +729,7 @@ void Base::draw_sms(int start, int end) { // Remove the border area from that to which tiles will be drawn. tile_start = std::max(start - (master_system_.horizontal_scroll & 7), 0); tile_end = std::max(end - (master_system_.horizontal_scroll & 7), 0); + tile_offset += master_system_.horizontal_scroll & 7; } @@ -740,7 +742,6 @@ void Base::draw_sms(int start, int end) { priority over sprites. */ if(tile_start < end) { - int offset = (master_system_.horizontal_scroll & 7) + tile_start; const int shift = tile_start & 7; int byte_column = tile_start >> 3; int pixels_left = tile_end - tile_start; @@ -757,24 +758,24 @@ void Base::draw_sms(int start, int end) { const int palette_offset = (master_system_.names[byte_column].flags&0x18) << 1; if(master_system_.names[byte_column].flags&2) { for(int c = 0; c < length; ++c) { - colour_buffer[offset] = + colour_buffer[tile_offset] = ((pattern_index[3] & 0x01) << 3) | ((pattern_index[2] & 0x01) << 2) | ((pattern_index[1] & 0x01) << 1) | ((pattern_index[0] & 0x01) << 0) | palette_offset; - ++offset; + ++tile_offset; pattern >>= 1; } } else { for(int c = 0; c < length; ++c) { - colour_buffer[offset] = + colour_buffer[tile_offset] = ((pattern_index[3] & 0x80) >> 4) | ((pattern_index[2] & 0x80) >> 5) | ((pattern_index[1] & 0x80) >> 6) | ((pattern_index[0] & 0x80) >> 7) | palette_offset; - ++offset; + ++tile_offset; pattern <<= 1; } } From 2d8ab72e22062b618916811bd7e8d648a36c2d4d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 8 Oct 2018 23:13:37 -0400 Subject: [PATCH 53/88] Fixed proper starting position for (interrupted) tile drawing. --- Components/9918/9918.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 70a9e4476..b7d2e83ca 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -724,12 +724,12 @@ void Base::draw_sms(int start, int end) { if(row_ >= 16 || !master_system_.horizontal_scroll_lock) { for(int c = start; c < (master_system_.horizontal_scroll & 7); ++c) { colour_buffer[c] = 16 + background_colour_; + ++tile_offset; } // Remove the border area from that to which tiles will be drawn. tile_start = std::max(start - (master_system_.horizontal_scroll & 7), 0); tile_end = std::max(end - (master_system_.horizontal_scroll & 7), 0); - tile_offset += master_system_.horizontal_scroll & 7; } @@ -754,7 +754,6 @@ void Base::draw_sms(int start, int end) { pattern <<= shift; while(true) { - pixels_left -= length; const int palette_offset = (master_system_.names[byte_column].flags&0x18) << 1; if(master_system_.names[byte_column].flags&2) { for(int c = 0; c < length; ++c) { @@ -780,7 +779,9 @@ void Base::draw_sms(int start, int end) { } } + pixels_left -= length; if(!pixels_left) break; + length = std::min(8, pixels_left); byte_column++; pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); From dccf17e770bc0702669b545030ded5270a31e7d3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 9 Oct 2018 20:51:09 -0400 Subject: [PATCH 54/88] Makes a first serious attempt at Master System line interrupts. --- Components/9918/9918.cpp | 307 +++++++++++++++++++++------------------ 1 file changed, 162 insertions(+), 145 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index b7d2e83ca..2e417fa5e 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -305,142 +305,26 @@ void TMS9918::run_for(const HalfCycles cycles) { // ----------------- -/* - - // -------------- - // Output pixels. - // -------------- - - - case LineMode::Character: { - // If this is the start of the visible area, seed sprite shifter positions. - SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1]; - if(output_column_ == first_pixel_column_) { - int c = sprite_set.active_sprite_slot; - while(c--) { - SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; - sprite.shift_position = -sprite.info[1]; - if(sprite.info[3] & 0x80) { - sprite.shift_position += 32; - if(sprite.shift_position > 0 && !sprites_magnified_) - sprite.shift_position *= 2; - } - } - } - - // Paint the background tiles. - const int pixels_left = pixels_end - output_column_; - if(current_mode_ == ScreenMode::MultiColour) { - int pixel_location = output_column_ - first_pixel_column_; - for(int c = 0; c < pixels_left; ++c) { - pixel_target_[c] = palette[ - (pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15 - ]; - } - pixel_target_ += pixels_left; - } else { - const int shift = (output_column_ - first_pixel_column_) & 7; - int byte_column = (output_column_ - first_pixel_column_) >> 3; - - int length = std::min(pixels_left, 8 - shift); - - int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; - uint8_t colour = colour_buffer_[byte_column]; - uint32_t colours[2] = { - palette[(colour & 15) ? (colour & 15) : background_colour_], - palette[(colour >> 4) ? (colour >> 4) : background_colour_] - }; - - int background_pixels_left = pixels_left; - while(true) { - background_pixels_left -= length; - for(int c = 0; c < length; ++c) { - pixel_target_[c] = colours[pattern&0x01]; - pattern >>= 1; - } - pixel_target_ += length; - - if(!background_pixels_left) break; - length = std::min(8, background_pixels_left); - byte_column++; - - pattern = reverse_table.map[pattern_buffer_[byte_column]]; - colour = colour_buffer_[byte_column]; - colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; - colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; - } - } - - // Paint sprites and check for collisions, but only if at least one sprite is active - // on this line. - if(sprite_set.active_sprite_slot) { - int sprite_pixels_left = pixels_left; - const int shift_advance = sprites_magnified_ ? 1 : 2; - - static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; - static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - - while(sprite_pixels_left--) { - // sprite_colour is the colour that's going to reach the display after sprite logic has been - // applied; by default assume that nothing is going to be drawn. - uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; - - // The sprite_mask is used to keep track of whether two sprites have both sought to output - // a pixel at the same location, and to feed that into the status register's sprite - // collision bit. - int sprite_mask = 0; - - int c = sprite_set.active_sprite_slot; - while(c--) { - SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; - - if(sprite.shift_position < 0) { - sprite.shift_position++; - continue; - } else if(sprite.shift_position < 32) { - int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); - mask = (mask >> 7) & 1; - - // Ignore the right half of whatever was collected if sprites are not in 16x16 mode. - if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) { - // If any previous sprite has been painted in this column and this sprite - // has this pixel set, set the sprite collision status bit. - status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; - sprite_mask |= mask; - - // Check that the sprite colour is not transparent - mask &= colour_masks[sprite.info[3]&15]; - sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); - } - - sprite.shift_position += shift_advance; - } - } - - // Output whichever sprite colour was on top. - pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; - output_column_++; - } - } - - output_column_ = pixels_end; - } break; - } - - - }*/ if(column_ < mode_timing_.line_interrupt_position && end_column >= mode_timing_.line_interrupt_position) { - if(row_ <= mode_timing_.pixel_lines) { - --line_interrupt_counter; - if(line_interrupt_counter == 0xff) { -// line_interrupt_pending_ = true; + // The Sega VDP offers a decrementing counter for triggering line interrupts; + // it is reloaded either when it overflows or upon every non-pixel line after the first. + // It is otherwise decremented. + if(is_sega_vdp(personality_)) { + if(row_ <= mode_timing_.pixel_lines) { + --line_interrupt_counter; + if(line_interrupt_counter == 0xff) { + line_interrupt_pending_ = true; + line_interrupt_counter = line_interrupt_target; + } + } else { line_interrupt_counter = line_interrupt_target; } - } else { - line_interrupt_counter = line_interrupt_target; } + + // TODO: the V9938 provides line interrupts from direct specification of the target line. + // So life is easy. } @@ -652,28 +536,37 @@ uint8_t TMS9918::get_register(int address) { const int half_cycles_per_frame = mode_timing_.total_lines * 228 * 2; const int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame; const auto time_until_frame_interrupt = HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame); + if(!enable_line_interrupts_) return time_until_frame_interrupt; - // Calculate the number of times the line interrupt position will be decremented this frame. -// return HalfCycles(20); -/* auto time_until_line_count = mode_timing_.line_interrupt_position - row_; - auto decrements_left_this_frame = mode_timing_.pixel_lines - row_; - if(time_until_line_count > 0) { - ++decrements_left_this_frame; + // Calculate the row upon which the next line interrupt will occur; + int next_line_interrupt_row = -1; + + if(is_sega_vdp(personality_)) { + // If there is still time for a line interrupt this frame, that'll be it; + // otherwise it'll be on the next frame, supposing there's ever time for + // it at all. + if(row_+line_interrupt_counter <= mode_timing_.pixel_lines) { + next_line_interrupt_row = row_+line_interrupt_counter; + } else { + if(line_interrupt_target <= mode_timing_.pixel_lines) + next_line_interrupt_row = mode_timing_.total_lines + line_interrupt_target; + } } - // If that's enough to underflow the line counter, there's the next interupt. - HalfCycles time_until_line_interrupt; - if(decrements_left_this_frame >= line_interrupt_counter+1) { - time_until_line_interrupt = HalfCycles + // If there's actually no interrupt upcoming, despite being enabled, either return + // the frame end interrupt or no interrupt pending as appropriate. + if(next_line_interrupt_row == -1) { + return generate_interrupts_ ? time_until_frame_interrupt : HalfCycles(-1); } - if(!enable_line_interrupts_) { - return time_until_frame_interrupt; - } else if(!generate_interrupts_) { + // Figure out the number of internal cycles until the next line interrupt. + const int local_cycles_until_line_interrupt = ((mode_timing_.line_interrupt_position - column_ + 342) % 342) + (next_line_interrupt_row - row_) * 342; - }*/ + // Map that to input cycles by multiplying by 2/3 (and incrementing on any carry). + auto time_until_line_interrupt = HalfCycles( (local_cycles_until_line_interrupt * 6 + 7) / 4); - return time_until_frame_interrupt; + // Return whichever interrupt is closer. + return std::min(time_until_frame_interrupt, time_until_line_interrupt); } bool TMS9918::get_interrupt_line() { @@ -688,6 +581,130 @@ void Base::draw_tms_character(int start, int end) { // for(int c = start; c < end; ++c) { // pixel_target_[c] = static_cast(c * 0x01010101); // } +/* + + // -------------- + // Output pixels. + // -------------- + + + case LineMode::Character: { + // If this is the start of the visible area, seed sprite shifter positions. + SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1]; + if(output_column_ == first_pixel_column_) { + int c = sprite_set.active_sprite_slot; + while(c--) { + SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; + sprite.shift_position = -sprite.info[1]; + if(sprite.info[3] & 0x80) { + sprite.shift_position += 32; + if(sprite.shift_position > 0 && !sprites_magnified_) + sprite.shift_position *= 2; + } + } + } + + // Paint the background tiles. + const int pixels_left = pixels_end - output_column_; + if(current_mode_ == ScreenMode::MultiColour) { + int pixel_location = output_column_ - first_pixel_column_; + for(int c = 0; c < pixels_left; ++c) { + pixel_target_[c] = palette[ + (pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15 + ]; + } + pixel_target_ += pixels_left; + } else { + const int shift = (output_column_ - first_pixel_column_) & 7; + int byte_column = (output_column_ - first_pixel_column_) >> 3; + + int length = std::min(pixels_left, 8 - shift); + + int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; + uint8_t colour = colour_buffer_[byte_column]; + uint32_t colours[2] = { + palette[(colour & 15) ? (colour & 15) : background_colour_], + palette[(colour >> 4) ? (colour >> 4) : background_colour_] + }; + + int background_pixels_left = pixels_left; + while(true) { + background_pixels_left -= length; + for(int c = 0; c < length; ++c) { + pixel_target_[c] = colours[pattern&0x01]; + pattern >>= 1; + } + pixel_target_ += length; + + if(!background_pixels_left) break; + length = std::min(8, background_pixels_left); + byte_column++; + + pattern = reverse_table.map[pattern_buffer_[byte_column]]; + colour = colour_buffer_[byte_column]; + colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; + colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; + } + } + + // Paint sprites and check for collisions, but only if at least one sprite is active + // on this line. + if(sprite_set.active_sprite_slot) { + int sprite_pixels_left = pixels_left; + const int shift_advance = sprites_magnified_ ? 1 : 2; + + static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; + static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + + while(sprite_pixels_left--) { + // sprite_colour is the colour that's going to reach the display after sprite logic has been + // applied; by default assume that nothing is going to be drawn. + uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; + + // The sprite_mask is used to keep track of whether two sprites have both sought to output + // a pixel at the same location, and to feed that into the status register's sprite + // collision bit. + int sprite_mask = 0; + + int c = sprite_set.active_sprite_slot; + while(c--) { + SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; + + if(sprite.shift_position < 0) { + sprite.shift_position++; + continue; + } else if(sprite.shift_position < 32) { + int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); + mask = (mask >> 7) & 1; + + // Ignore the right half of whatever was collected if sprites are not in 16x16 mode. + if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) { + // If any previous sprite has been painted in this column and this sprite + // has this pixel set, set the sprite collision status bit. + status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; + sprite_mask |= mask; + + // Check that the sprite colour is not transparent + mask &= colour_masks[sprite.info[3]&15]; + sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); + } + + sprite.shift_position += shift_advance; + } + } + + // Output whichever sprite colour was on top. + pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; + output_column_++; + } + } + + output_column_ = pixels_end; + } break; + } + + + }*/ } void Base::draw_tms_text(int start, int end) { From c128ddb5499307c1b99f3b319208889bec14dd57 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 9 Oct 2018 21:49:21 -0400 Subject: [PATCH 55/88] Introduces a first unit test for line interrupts and corrects backup behaviour. --- Components/9918/9918.cpp | 10 ++- .../Clock Signal.xcodeproj/project.pbxproj | 6 +- .../Clock SignalTests/MasterSystemVDPTests.mm | 69 +++++++++++++++++++ 3 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 2e417fa5e..7343aebf9 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -560,10 +560,14 @@ uint8_t TMS9918::get_register(int address) { } // Figure out the number of internal cycles until the next line interrupt. - const int local_cycles_until_line_interrupt = ((mode_timing_.line_interrupt_position - column_ + 342) % 342) + (next_line_interrupt_row - row_) * 342; + int local_cycles_until_line_interrupt = ((mode_timing_.line_interrupt_position - column_ + 342) % 342) + (next_line_interrupt_row - row_) * 342; + local_cycles_until_line_interrupt <<= 2; + local_cycles_until_line_interrupt -= cycles_error_; - // Map that to input cycles by multiplying by 2/3 (and incrementing on any carry). - auto time_until_line_interrupt = HalfCycles( (local_cycles_until_line_interrupt * 6 + 7) / 4); + // Map that to input cycles by multiplying by 2/3 (and incrementing on any carry, TODO: allowing for current error). + auto time_until_line_interrupt = HalfCycles(local_cycles_until_line_interrupt / 3); + + if(!generate_interrupts_) return time_until_line_interrupt; // Return whichever interrupt is closer. return std::min(time_until_frame_interrupt, time_until_line_interrupt); diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 6f56f10e9..95080054f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -315,6 +315,7 @@ 4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; }; 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; }; 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; }; + 4BA91E1D216D85BA00F79557 /* MasterSystemVDPTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */; }; 4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; 4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */; }; 4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */; }; @@ -1041,6 +1042,7 @@ 4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = ""; }; 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = ""; }; + 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MasterSystemVDPTests.mm; sourceTree = ""; }; 4BA9C3CF1D8164A9002DDB61 /* MediaTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MediaTarget.hpp; sourceTree = ""; }; 4BAA167B21582B1D008A3276 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = ""; }; @@ -2818,10 +2820,11 @@ 4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = { isa = PBXGroup; children = ( - 4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */, 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */, 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */, 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */, + 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */, + 4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */, 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */, 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */, 4B2AF8681E513FC20027EE29 /* TIATests.mm */, @@ -4000,6 +4003,7 @@ 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */, 4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */, 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */, + 4BA91E1D216D85BA00F79557 /* MasterSystemVDPTests.mm in Sources */, 4B98A0611FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm in Sources */, 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */, 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */, diff --git a/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm b/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm new file mode 100644 index 000000000..0047ac20b --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm @@ -0,0 +1,69 @@ +// +// MasterSystemVDPTests.m +// Clock SignalTests +// +// Created by Thomas Harte on 09/10/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#import +#import + +#include "9918.hpp" + +@interface MasterSystemVDPTests : XCTestCase +@end + +@implementation MasterSystemVDPTests { + NSOpenGLContext *_openGLContext; +} + +- (void)setUp { + [super setUp]; + + // Create a valid OpenGL context, so that a VDP can be constructed. + NSOpenGLPixelFormatAttribute attributes[] = + { + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + 0 + }; + + NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; + _openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil]; + [_openGLContext makeCurrentContext]; +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + _openGLContext = nil; + + [super tearDown]; +} + +- (void)testLineInterrupt { + TI::TMS::TMS9918 vdp(TI::TMS::Personality::SMSVDP); + + // Disable end-of-frame interrupts, enable line interrupts. + vdp.set_register(1, 0x00); + vdp.set_register(1, 0x81); + + vdp.set_register(1, 0x10); + vdp.set_register(1, 0x80); + + // Set a line interrupt to occur in five lines. + vdp.set_register(1, 5); + vdp.set_register(1, 0x8a); + + // Get time until interrupt. + int time_until_interrupt = vdp.get_time_until_interrupt().as_int() - 1; + + // Check interrupt flag isn't set prior to the reported time. + vdp.run_for(HalfCycles(time_until_interrupt)); + NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early"); + + // Check interrupt flag is set at the reported time. + vdp.run_for(HalfCycles(1)); + NSAssert(vdp.get_interrupt_line(), @"Interrupt line wasn't set when promised"); +} + +@end From acdc84e08c429e5a480ae99b7050edd7130a8041 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 9 Oct 2018 22:14:35 -0400 Subject: [PATCH 56/88] Improves test slightly, and fixes line interrupt reload value setting. --- Components/9918/9918.cpp | 10 +++++++--- .../Clock SignalTests/MasterSystemVDPTests.mm | 17 +++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 7343aebf9..ec90b91c3 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -486,7 +486,7 @@ void TMS9918::set_register(int address, uint8_t value) { case 10: if(is_sega_vdp(personality_)) { - line_interrupt_target = value; + line_interrupt_target = low_write_; } break; @@ -559,8 +559,12 @@ uint8_t TMS9918::get_register(int address) { return generate_interrupts_ ? time_until_frame_interrupt : HalfCycles(-1); } - // Figure out the number of internal cycles until the next line interrupt. - int local_cycles_until_line_interrupt = ((mode_timing_.line_interrupt_position - column_ + 342) % 342) + (next_line_interrupt_row - row_) * 342; + // Figure out the number of internal cycles until the next line interrupt, which is the amount + // of time to the next tick over and then next_line_interrupt_row - row_ lines further. + int local_cycles_until_next_tick = (mode_timing_.line_interrupt_position - column_ + 342) % 342; + if(!local_cycles_until_next_tick) local_cycles_until_next_tick += 342; + + int local_cycles_until_line_interrupt = local_cycles_until_next_tick + (next_line_interrupt_row - row_) * 342; local_cycles_until_line_interrupt <<= 2; local_cycles_until_line_interrupt -= cycles_error_; diff --git a/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm b/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm index 0047ac20b..ec5c36587 100644 --- a/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm +++ b/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm @@ -59,11 +59,24 @@ // Check interrupt flag isn't set prior to the reported time. vdp.run_for(HalfCycles(time_until_interrupt)); - NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early"); + NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [1]"); // Check interrupt flag is set at the reported time. vdp.run_for(HalfCycles(1)); - NSAssert(vdp.get_interrupt_line(), @"Interrupt line wasn't set when promised"); + NSAssert(vdp.get_interrupt_line(), @"Interrupt line wasn't set when promised [1]"); + + // Read the status register to clear interrupt status. + vdp.get_register(1); + NSAssert(!vdp.get_interrupt_line(), @"Interrupt wasn't reset by status read"); + + // Check interrupt flag isn't set prior to the reported time. + time_until_interrupt = vdp.get_time_until_interrupt().as_int() - 1; + vdp.run_for(HalfCycles(time_until_interrupt)); + NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [2]"); + + // Check interrupt flag is set at the reported time. + vdp.run_for(HalfCycles(1)); + NSAssert(vdp.get_interrupt_line(), @"Interrupt line wasn't set when promised [2]"); } @end From 50e23f4a2ef0c53693ea4b7535f5a2f3c3915952 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 10 Oct 2018 20:34:00 -0400 Subject: [PATCH 57/88] Fixes 16px-high sprites. --- Components/9918/9918.cpp | 6 ++++-- Components/9918/Implementation/9918Base.hpp | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index ec90b91c3..14b334a22 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -63,7 +63,7 @@ Base::Base(Personality p) : } if(is_sega_vdp(personality_)) { - mode_timing_.line_interrupt_position = 15; + mode_timing_.line_interrupt_position = 4; } } @@ -507,7 +507,9 @@ void TMS9918::set_register(int address, uint8_t value) { } uint8_t TMS9918::get_current_line() { - return static_cast(row_); + const int source_row = (column_ < mode_timing_.line_interrupt_position) ? (row_ + mode_timing_.pixel_lines - 1)%mode_timing_.pixel_lines : row_; + + return static_cast(source_row); } uint8_t TMS9918::get_register(int address) { diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 0116a3c06..ee0a94ff8 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -164,7 +164,7 @@ class Base { bool allow_sprite_terminator = true; } mode_timing_; - uint8_t line_interrupt_target = 0; + uint8_t line_interrupt_target = 0xff; uint8_t line_interrupt_counter = 0; bool enable_line_interrupts_ = false; bool line_interrupt_pending_ = false; @@ -590,7 +590,7 @@ class Base { ] - (master_system_.shift_sprites_8px_left ? size_t(8) : size_t(0)); \ const uint8_t name = ram_[\ sprite_attribute_table_address_ & size_t(0x3f81 | (sprite_set_.active_sprites[sprite].index << 1))\ - ];\ + ] & (sprites_16x16_ ? ~1 : ~0);\ const size_t graphic_location = sprite_generator_table_address_ & size_t(0x2000 | (name << 5) | (sprite_set_.active_sprites[sprite].row << 2)); \ sprite_set_.active_sprites[sprite].image[0] = ram_[graphic_location]; \ sprite_set_.active_sprites[sprite].image[1] = ram_[graphic_location+1]; \ From f00f6c8c236b1fd45390c65c9eb00a214e6bac0b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 10 Oct 2018 21:07:39 -0400 Subject: [PATCH 58/88] Allows the frame interrupt to be placed anywhere in the frame. --- Components/9918/9918.cpp | 53 +++++++++++++-------- Components/9918/Implementation/9918Base.hpp | 10 ++-- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 14b334a22..3995d44e3 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -168,10 +168,6 @@ void TMS9918::run_for(const HalfCycles cycles) { // PAL output is 313 lines total. NTSC output is 262 lines total. // Interrupt is signalled upon entering the lower border. - // Keep a count of cycles separate from internal counts to avoid - // potential errors mapping back and forth. - half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(mode_timing_.total_lines * 228 * 2); - // Convert 456 clocked half cycles per line to 342 internal cycles per line; // the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised // for this part. So multiply by three quarters. @@ -306,6 +302,9 @@ void TMS9918::run_for(const HalfCycles cycles) { + // ------------------------------- + // Check for interrupt conditions. + // ------------------------------- if(column_ < mode_timing_.line_interrupt_position && end_column >= mode_timing_.line_interrupt_position) { // The Sega VDP offers a decrementing counter for triggering line interrupts; @@ -327,10 +326,20 @@ void TMS9918::run_for(const HalfCycles cycles) { // So life is easy. } + if( + row_ == mode_timing_.end_of_frame_interrupt_position.row && + column_ < mode_timing_.end_of_frame_interrupt_position.column && + end_column >= mode_timing_.end_of_frame_interrupt_position.column + ) { + status_ |= StatusInterrupt; + } + + // ------------- // Advance time. // ------------- + column_ = end_column; // column_ is now the column that has been reached in this line. int_cycles -= cycles_left; // Count down duration to run for. @@ -342,7 +351,6 @@ void TMS9918::run_for(const HalfCycles cycles) { if(column_ == 342) { column_ = 0; row_ = (row_ + 1) % mode_timing_.total_lines; - if(row_ == mode_timing_.pixel_lines) status_ |= StatusInterrupt; // Establish the output mode for the next line. set_current_mode(); @@ -530,15 +538,24 @@ uint8_t TMS9918::get_register(int address) { return result; } - HalfCycles TMS9918::get_time_until_interrupt() { +HalfCycles Base::half_cycles_before_internal_cycles(int internal_cycles) { + return HalfCycles(((internal_cycles << 2) - cycles_error_) / 3); +} + + +HalfCycles TMS9918::get_time_until_interrupt() { if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles(-1); if(get_interrupt_line()) return HalfCycles(0); // Calculate the amount of time until the next end-of-frame interrupt. - const int half_cycles_per_frame = mode_timing_.total_lines * 228 * 2; - const int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame; - const auto time_until_frame_interrupt = HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame); - if(!enable_line_interrupts_) return time_until_frame_interrupt; + const int frame_length = 342 * mode_timing_.pixel_lines; + const int time_until_frame_interrupt = + ( + ((mode_timing_.end_of_frame_interrupt_position.row * 342) + mode_timing_.end_of_frame_interrupt_position.column + frame_length) - + ((row_ * 342) + column_) + ) % frame_length; + + if(!enable_line_interrupts_) return half_cycles_before_internal_cycles(time_until_frame_interrupt); // Calculate the row upon which the next line interrupt will occur; int next_line_interrupt_row = -1; @@ -558,25 +575,21 @@ uint8_t TMS9918::get_register(int address) { // If there's actually no interrupt upcoming, despite being enabled, either return // the frame end interrupt or no interrupt pending as appropriate. if(next_line_interrupt_row == -1) { - return generate_interrupts_ ? time_until_frame_interrupt : HalfCycles(-1); + return generate_interrupts_ ? + half_cycles_before_internal_cycles(time_until_frame_interrupt) : + HalfCycles(-1); } // Figure out the number of internal cycles until the next line interrupt, which is the amount // of time to the next tick over and then next_line_interrupt_row - row_ lines further. int local_cycles_until_next_tick = (mode_timing_.line_interrupt_position - column_ + 342) % 342; if(!local_cycles_until_next_tick) local_cycles_until_next_tick += 342; + const int local_cycles_until_line_interrupt = local_cycles_until_next_tick + (next_line_interrupt_row - row_) * 342; - int local_cycles_until_line_interrupt = local_cycles_until_next_tick + (next_line_interrupt_row - row_) * 342; - local_cycles_until_line_interrupt <<= 2; - local_cycles_until_line_interrupt -= cycles_error_; - - // Map that to input cycles by multiplying by 2/3 (and incrementing on any carry, TODO: allowing for current error). - auto time_until_line_interrupt = HalfCycles(local_cycles_until_line_interrupt / 3); - - if(!generate_interrupts_) return time_until_line_interrupt; + if(!generate_interrupts_) return half_cycles_before_internal_cycles(time_until_frame_interrupt); // Return whichever interrupt is closer. - return std::min(time_until_frame_interrupt, time_until_line_interrupt); + return half_cycles_before_internal_cycles(std::min(local_cycles_until_line_interrupt, time_until_frame_interrupt)); } bool TMS9918::get_interrupt_line() { diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index ee0a94ff8..479da6495 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -106,10 +106,9 @@ class Base { uint8_t background_colour_ = 0; // Internal mechanisms for position tracking. - HalfCycles half_cycles_into_frame_; - int column_ = 0, row_ = 0, output_column_ = 0; + int column_ = 0, row_ = 0; int cycles_error_ = 0; - int access_pointer_ = 0; + HalfCycles half_cycles_before_internal_cycles(int internal_cycles); // A helper function to output the current border colour for // the number of cycles supplied. @@ -156,7 +155,10 @@ class Base { // Set the position, in cycles, of the two interrupts, // within a line. - int end_of_frame_interrupt_position = 342; + struct { + int column = 342; + int row = 191; + } end_of_frame_interrupt_position; int line_interrupt_position = -1; // Enables or disabled the recognition of 0xd0 as a sprite From 2e379b083483b0b215e7fac3341319a24eceaf04 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 10 Oct 2018 21:28:18 -0400 Subject: [PATCH 59/88] Adds latching of scroll values. --- Components/9918/9918.cpp | 11 +++++++---- Components/9918/Implementation/9918Base.hpp | 19 +++++++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 3995d44e3..7c75ed74c 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -64,6 +64,9 @@ Base::Base(Personality p) : if(is_sega_vdp(personality_)) { mode_timing_.line_interrupt_position = 4; + + mode_timing_.end_of_frame_interrupt_position.column = 296; + mode_timing_.end_of_frame_interrupt_position.row = 191; } } @@ -548,7 +551,7 @@ HalfCycles TMS9918::get_time_until_interrupt() { if(get_interrupt_line()) return HalfCycles(0); // Calculate the amount of time until the next end-of-frame interrupt. - const int frame_length = 342 * mode_timing_.pixel_lines; + const int frame_length = 342 * mode_timing_.total_lines; const int time_until_frame_interrupt = ( ((mode_timing_.end_of_frame_interrupt_position.row * 342) + mode_timing_.end_of_frame_interrupt_position.column + frame_length) - @@ -762,14 +765,14 @@ void Base::draw_sms(int start, int end) { int tile_start = start, tile_end = end; int tile_offset = start; if(row_ >= 16 || !master_system_.horizontal_scroll_lock) { - for(int c = start; c < (master_system_.horizontal_scroll & 7); ++c) { + for(int c = start; c < (master_system_.latched_horizontal_scroll & 7); ++c) { colour_buffer[c] = 16 + background_colour_; ++tile_offset; } // Remove the border area from that to which tiles will be drawn. - tile_start = std::max(start - (master_system_.horizontal_scroll & 7), 0); - tile_end = std::max(end - (master_system_.horizontal_scroll & 7), 0); + tile_start = std::max(start - (master_system_.latched_horizontal_scroll & 7), 0); + tile_end = std::max(end - (master_system_.latched_horizontal_scroll & 7), 0); } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 479da6495..8318813b4 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -199,13 +199,15 @@ class Base { uint32_t colour_ram[32]; bool cram_is_selected = false; - // Temporary buffers for a line of Master System graphics. + // Temporary buffers for a line of Master System graphics, + // and latched scrolling offsets. struct { size_t offset; uint8_t flags; } names[32]; uint8_t tile_graphics[32][4]; - size_t next_column = 0; + uint8_t latched_vertical_scroll = 0; + uint8_t latched_horizontal_scroll = 0; } master_system_; // Holds results of sprite data fetches that occur on this @@ -654,12 +656,21 @@ class Base { slot(location+14): \ slot(location+15): fetch_tile(column+3) + // Latch scrolling position at slot 33 for now; unless or until + // a more accurate number becomes apparent. + if(start < 33 && end >= 33) { + if(!row_) { + master_system_.latched_vertical_scroll = master_system_.vertical_scroll; + } + master_system_.latched_horizontal_scroll = master_system_.horizontal_scroll; + } + // Determine the coarse horizontal scrolling offset; this isn't applied on the first two lines if the programmer has requested it. - const int horizontal_offset = (row_ >= 16 || !master_system_.horizontal_scroll_lock) ? (master_system_.horizontal_scroll >> 3) : 0; + const int horizontal_offset = (row_ >= 16 || !master_system_.horizontal_scroll_lock) ? (master_system_.latched_horizontal_scroll >> 3) : 0; // Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't. // The programmer can opt out of applying vertical scrolling to the right-hand portion of the display. - const int scrolled_row = (row_ + master_system_.vertical_scroll) % 224; + const int scrolled_row = (row_ + master_system_.latched_vertical_scroll) % 224; struct RowInfo { size_t pattern_address_base; size_t sub_row[2]; From 63fb3f03d1be0981cb6b4768995d27332628e8bc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 10 Oct 2018 21:47:48 -0400 Subject: [PATCH 60/88] Corrects address loading upon accesses of registers other than 0. --- Components/9918/9918.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 7c75ed74c..99e05d402 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -415,6 +415,10 @@ void TMS9918::set_register(int address, uint8_t value) { return; } + // The RAM pointer is always set on a second write, regardless of + // whether the caller is intending to enqueue a VDP operation. + ram_pointer_ = static_cast(low_write_ | (value << 8)); + write_phase_ = false; if(value & 0x80) { switch(personality_) { @@ -423,7 +427,6 @@ void TMS9918::set_register(int address, uint8_t value) { break; case TI::TMS::SMSVDP: if(value & 0x40) { - ram_pointer_ = static_cast(low_write_ | (value << 8)); master_system_.cram_is_selected = true; return; } @@ -507,7 +510,6 @@ void TMS9918::set_register(int address, uint8_t value) { } } else { // This is an access via the RAM pointer. - ram_pointer_ = static_cast(low_write_ | (value << 8)); if(!(value & 0x40)) { // A read request is enqueued upon setting the address; conversely a write // won't be enqueued unless and until some actual data is supplied. @@ -518,8 +520,9 @@ void TMS9918::set_register(int address, uint8_t value) { } uint8_t TMS9918::get_current_line() { - const int source_row = (column_ < mode_timing_.line_interrupt_position) ? (row_ + mode_timing_.pixel_lines - 1)%mode_timing_.pixel_lines : row_; - + int source_row = (column_ < mode_timing_.line_interrupt_position) ? (row_ + mode_timing_.pixel_lines - 1)%mode_timing_.pixel_lines : row_; + // This assumes NTSC 192-line. TODO: other modes. + if(source_row >= 0xdb) source_row -= 5; return static_cast(source_row); } From 9f69dbf31a7bb05f84f77088ddaa22a2221a9442 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 10 Oct 2018 21:59:08 -0400 Subject: [PATCH 61/88] Adds half-updating of RAM pointer. This emulator now passes the first screen of the SMS VDP test. --- Components/9918/9918.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 99e05d402..445b1ff49 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -412,12 +412,15 @@ void TMS9918::set_register(int address, uint8_t value) { if(!write_phase_) { low_write_ = value; write_phase_ = true; + + // The initial write should half update the access pointer. + ram_pointer_ = (ram_pointer_ & 0xff00) | low_write_; return; } // The RAM pointer is always set on a second write, regardless of // whether the caller is intending to enqueue a VDP operation. - ram_pointer_ = static_cast(low_write_ | (value << 8)); + ram_pointer_ = (ram_pointer_ & 0x00ff) | static_cast(value << 8); write_phase_ = false; if(value & 0x80) { From 062b2ae8d3ef626d807d3babae8255687c8f0aa0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 10 Oct 2018 22:15:38 -0400 Subject: [PATCH 62/88] Corrects calculation of [NTSC, 192 line] current row. --- Components/9918/9918.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 445b1ff49..77d37d3a1 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -523,9 +523,9 @@ void TMS9918::set_register(int address, uint8_t value) { } uint8_t TMS9918::get_current_line() { - int source_row = (column_ < mode_timing_.line_interrupt_position) ? (row_ + mode_timing_.pixel_lines - 1)%mode_timing_.pixel_lines : row_; + int source_row = (column_ < mode_timing_.line_interrupt_position) ? (row_ + mode_timing_.total_lines - 1)%mode_timing_.total_lines : row_; // This assumes NTSC 192-line. TODO: other modes. - if(source_row >= 0xdb) source_row -= 5; + if(source_row >= 0xdb) source_row -= 6; return static_cast(source_row); } From 9a933993f52f4c8a961f0e6565c590e4759d74fe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 10 Oct 2018 22:17:17 -0400 Subject: [PATCH 63/88] Added TODO. --- Components/9918/9918.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 77d37d3a1..fdba59e2a 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -529,6 +529,17 @@ uint8_t TMS9918::get_current_line() { return static_cast(source_row); } +/* + TODO: Full proper sequence of current lines: + + NTSC 256x192 00-DA, D5-FF + NTSC 256x224 00-EA, E5-FF + NTSC 256x240 00-FF, 00-06 + PAL 256x192 00-F2, BA-FF + PAL 256x224 00-FF, 00-02, CA-FF + PAL 256x240 00-FF, 00-0A, D2-FF +*/ + uint8_t TMS9918::get_register(int address) { write_phase_ = false; From 52e02db5c8d9b1dba52ec8a151312e8065e7ec74 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 11 Oct 2018 19:56:32 -0400 Subject: [PATCH 64/88] Introduces horizontal counter latching and reading. Then makes a new guess at frame IRQ position. But gets it wrong. Hmmm. --- Components/9918/9918.cpp | 18 ++++++++- Components/9918/9918.hpp | 4 ++ Components/9918/Implementation/9918Base.hpp | 6 +-- Machines/MasterSystem/MasterSystem.cpp | 43 ++++++++++++++++++--- 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index fdba59e2a..59379d38a 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -526,8 +526,9 @@ uint8_t TMS9918::get_current_line() { int source_row = (column_ < mode_timing_.line_interrupt_position) ? (row_ + mode_timing_.total_lines - 1)%mode_timing_.total_lines : row_; // This assumes NTSC 192-line. TODO: other modes. if(source_row >= 0xdb) source_row -= 6; + +// printf("Current row: %d -> %d\n", row_, source_row); return static_cast(source_row); -} /* TODO: Full proper sequence of current lines: @@ -539,6 +540,21 @@ uint8_t TMS9918::get_current_line() { PAL 256x224 00-FF, 00-02, CA-FF PAL 256x240 00-FF, 00-0A, D2-FF */ +} + +uint8_t TMS9918::get_latched_horizontal_counter() { + // Translate from internal numbering, which puts pixel output + // in the final 256 pixels of 342, to the public numbering, + // which makes the 256 pixels the first 256 spots, but starts + // counting at -48, and returns only the top 8 bits of the number. + int public_counter = latched_column_ - 86; + if(public_counter < -48) public_counter += 342; + return uint8_t(public_counter >> 1); +} + +void TMS9918::latch_horizontal_counter() { + latched_column_ = column_; +} uint8_t TMS9918::get_register(int address) { write_phase_ = false; diff --git a/Components/9918/9918.hpp b/Components/9918/9918.hpp index 2ffc010df..5dc552e67 100644 --- a/Components/9918/9918.hpp +++ b/Components/9918/9918.hpp @@ -66,6 +66,10 @@ class TMS9918: public Base { /*! Gets the current scan line; provided by the Master System only. */ uint8_t get_current_line(); + uint8_t get_latched_horizontal_counter(); + + void latch_horizontal_counter(); + /*! Returns the amount of time until get_interrupt_line would next return true if there are no interceding calls to set_register or get_register. diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 8318813b4..b18572a97 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -106,7 +106,7 @@ class Base { uint8_t background_colour_ = 0; // Internal mechanisms for position tracking. - int column_ = 0, row_ = 0; + int column_ = 0, row_ = 0, latched_column_ = 0; int cycles_error_ = 0; HalfCycles half_cycles_before_internal_cycles(int internal_cycles); @@ -156,8 +156,8 @@ class Base { // Set the position, in cycles, of the two interrupts, // within a line. struct { - int column = 342; - int row = 191; + int column = 4; + int row = 193; } end_of_frame_interrupt_position; int line_interrupt_position = -1; diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 54b4ffbdf..63c179126 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -184,10 +184,13 @@ class ConcreteMachine: printf("TODO: [input] I/O port control\n"); *cycle.value = 0xff; break; - case 0x40: case 0x41: + case 0x40: update_video(); *cycle.value = vdp_->get_current_line(); break; + case 0x41: + *cycle.value = vdp_->get_latched_horizontal_counter(); + break; case 0x80: case 0x81: update_video(); *cycle.value = vdp_->get_register(address); @@ -201,7 +204,11 @@ class ConcreteMachine: } break; case 0xc1: { Joystick *const joypad2 = static_cast(joysticks_[1].get()); - *cycle.value = (joypad2->get_state() >> 2) | 0xf; + + *cycle.value = + (joypad2->get_state() >> 2) | + 0x30 | + get_th_values(); } break; default: @@ -219,9 +226,20 @@ class ConcreteMachine: page_cartridge(); } break; - case 0x01: - printf("TODO: [output] I/O port control\n"); - break; + case 0x01: { + // A programmer can force the TH lines to 0 here, + // causing a phoney lightgun latch, so check for any + // discontinuity in TH inputs. + const auto previous_ths = get_th_values(); + io_port_control_ = *cycle.value; + const auto new_ths = get_th_values(); + + // Latch if either TH has newly gone to 1. + if((new_ths^previous_ths)&new_ths) { + update_video(); + vdp_->latch_horizontal_counter(); + } + } break; case 0x40: case 0x41: update_audio(); sn76489_.set_register(*cycle.value); @@ -233,7 +251,7 @@ class ConcreteMachine: time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 0xc0: -// printf("TODO: [output] I/O port A/N [%02x]\n", *cycle.value); + printf("TODO: [output] I/O port A/N [%02x]\n", *cycle.value); break; case 0xc1: printf("TODO: [output] I/O port B/misc\n"); @@ -274,6 +292,17 @@ class ConcreteMachine: } private: + inline uint8_t get_th_values() { + // Quick not on TH inputs here: if either is setup as an output, then the + // currently output level is returned. Otherwise they're fixed at 1. + return + static_cast( + ((io_port_control_ & 0x02) << 5) | ((io_port_control_&0x20) << 1) | + ((io_port_control_ & 0x08) << 4) | (io_port_control_&0x80) + ); + + } + inline void update_audio() { speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); } @@ -299,6 +328,8 @@ class ConcreteMachine: uint8_t bios_[8*1024]; std::vector cartridge_; + uint8_t io_port_control_ = 0x0f; + // The memory map has a 1kb granularity; this is determined by the SG1000's 1kb of RAM. const uint8_t *read_pointers_[64]; uint8_t *write_pointers_[64]; From 82b79445998df11891578c75ee87725ca1ae0131 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 11 Oct 2018 20:37:29 -0400 Subject: [PATCH 65/88] Fixes horizontal counter wrapping. --- Components/9918/9918.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 59379d38a..fae7d0046 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -548,7 +548,7 @@ uint8_t TMS9918::get_latched_horizontal_counter() { // which makes the 256 pixels the first 256 spots, but starts // counting at -48, and returns only the top 8 bits of the number. int public_counter = latched_column_ - 86; - if(public_counter < -48) public_counter += 342; + if(public_counter < -46) public_counter += 342; return uint8_t(public_counter >> 1); } From 6a94dda60dd588f1788e9b871bba0fab4435a56d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 11 Oct 2018 21:42:09 -0400 Subject: [PATCH 66/88] Selects potentially-correct interrupt times. --- Components/9918/9918.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index fae7d0046..98f9efa9c 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -63,10 +63,9 @@ Base::Base(Personality p) : } if(is_sega_vdp(personality_)) { - mode_timing_.line_interrupt_position = 4; - - mode_timing_.end_of_frame_interrupt_position.column = 296; - mode_timing_.end_of_frame_interrupt_position.row = 191; + mode_timing_.line_interrupt_position = 65; + mode_timing_.end_of_frame_interrupt_position.column = 63; + mode_timing_.end_of_frame_interrupt_position.row = 193; } } @@ -314,7 +313,7 @@ void TMS9918::run_for(const HalfCycles cycles) { // it is reloaded either when it overflows or upon every non-pixel line after the first. // It is otherwise decremented. if(is_sega_vdp(personality_)) { - if(row_ <= mode_timing_.pixel_lines) { + if(row_ > 0 && row_ <= mode_timing_.pixel_lines+1) { --line_interrupt_counter; if(line_interrupt_counter == 0xff) { line_interrupt_pending_ = true; @@ -508,7 +507,7 @@ void TMS9918::set_register(int address, uint8_t value) { break; default: - printf("Unknown TMS write: %d to %d\n", low_write_, value); +// printf("Unknown TMS write: %d to %d\n", low_write_, value); break; } } else { @@ -523,11 +522,14 @@ void TMS9918::set_register(int address, uint8_t value) { } uint8_t TMS9918::get_current_line() { - int source_row = (column_ < mode_timing_.line_interrupt_position) ? (row_ + mode_timing_.total_lines - 1)%mode_timing_.total_lines : row_; + // Determine the row to return. + static const int row_change_position = 62; // This is the proper Master System value; substitute if any other VDPs turn out to have this functionality. + int source_row = (column_ < row_change_position) ? (row_ + mode_timing_.total_lines - 1)%mode_timing_.total_lines : row_; + // This assumes NTSC 192-line. TODO: other modes. if(source_row >= 0xdb) source_row -= 6; - // printf("Current row: %d -> %d\n", row_, source_row); + return static_cast(source_row); /* From f6af6778abab8ccc1fdbf33e34dd2f39742de06c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 11 Oct 2018 22:36:27 -0400 Subject: [PATCH 67/88] Moves scrolling latch to proper position and implements 4-window fetching offset. --- Components/9918/9918.cpp | 14 ++++- Components/9918/Implementation/9918Base.hpp | 64 +++++++++------------ 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 98f9efa9c..567704a25 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -184,6 +184,17 @@ void TMS9918::run_for(const HalfCycles cycles) { const int end_column = column_ + cycles_left; + // --------------------------------------- + // Latch scrolling position, if necessary. + // --------------------------------------- + if(column_ < 61 && end_column >= 61) { + if(!row_) { + master_system_.latched_vertical_scroll = master_system_.vertical_scroll; + } + master_system_.latched_horizontal_scroll = master_system_.horizontal_scroll; + } + + // ------------------------ // Perform memory accesses. // ------------------------ @@ -313,7 +324,7 @@ void TMS9918::run_for(const HalfCycles cycles) { // it is reloaded either when it overflows or upon every non-pixel line after the first. // It is otherwise decremented. if(is_sega_vdp(personality_)) { - if(row_ > 0 && row_ <= mode_timing_.pixel_lines+1) { + if(row_ >= mode_timing_.total_lines-1 && row_ <= mode_timing_.pixel_lines) { --line_interrupt_counter; if(line_interrupt_counter == 0xff) { line_interrupt_pending_ = true; @@ -491,6 +502,7 @@ void TMS9918::set_register(int address, uint8_t value) { case 8: if(is_sega_vdp(personality_)) { master_system_.horizontal_scroll = low_write_; +// printf("Set to %d at %d, %d\n", low_write_, row_, column_); } break; diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index b18572a97..e3536f5d0 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -334,6 +334,8 @@ class Base { external_slots_16(n+16); /* + TODO: explain offset by four windows in data gathering. + Fetching routines follow below; they obey the following rules: 1) input is a start position and an end position; they should perform the proper @@ -656,15 +658,6 @@ class Base { slot(location+14): \ slot(location+15): fetch_tile(column+3) - // Latch scrolling position at slot 33 for now; unless or until - // a more accurate number becomes apparent. - if(start < 33 && end >= 33) { - if(!row_) { - master_system_.latched_vertical_scroll = master_system_.vertical_scroll; - } - master_system_.latched_horizontal_scroll = master_system_.horizontal_scroll; - } - // Determine the coarse horizontal scrolling offset; this isn't applied on the first two lines if the programmer has requested it. const int horizontal_offset = (row_ >= 16 || !master_system_.horizontal_scroll_lock) ? (master_system_.latched_horizontal_scroll >> 3) : 0; @@ -689,39 +682,39 @@ class Base { // ... and do the actual fetching, which follows this routine: switch(start) { default: - external_slots_4(0); + sprite_fetch_block(0, 0); + sprite_fetch_block(6, 2); - sprite_fetch_block(4, 0); - sprite_fetch_block(10, 2); + external_slots_4(12); + external_slot(16); - external_slots_4(16); - external_slot(20); + sprite_fetch_block(17, 4); + sprite_fetch_block(23, 6); - sprite_fetch_block(21, 4); - sprite_fetch_block(27, 6); - - slot(33): + slot(29): reset_sprite_collection(); do_external_slot(); - external_slot(34); + external_slot(30); - sprite_y_read(35, 0); - sprite_y_read(36, 2); - sprite_y_read(37, 4); - sprite_y_read(38, 6); - sprite_y_read(39, 8); - sprite_y_read(40, 10); - sprite_y_read(41, 12); - sprite_y_read(42, 14); + sprite_y_read(31, 0); + sprite_y_read(32, 2); + sprite_y_read(33, 4); + sprite_y_read(34, 6); + sprite_y_read(35, 8); + sprite_y_read(36, 10); + sprite_y_read(37, 12); + sprite_y_read(38, 14); - background_fetch_block(43, 0, 16, scrolled_row_info); - background_fetch_block(59, 4, 22, scrolled_row_info); - background_fetch_block(75, 8, 28, scrolled_row_info); - background_fetch_block(91, 12, 34, scrolled_row_info); - background_fetch_block(107, 16, 40, scrolled_row_info); - background_fetch_block(123, 20, 46, scrolled_row_info); - background_fetch_block(139, 24, 52, row_info); - background_fetch_block(156, 28, 58, row_info); + background_fetch_block(39, 0, 16, scrolled_row_info); + background_fetch_block(55, 4, 22, scrolled_row_info); + background_fetch_block(71, 8, 28, scrolled_row_info); + background_fetch_block(87, 12, 34, scrolled_row_info); + background_fetch_block(103, 16, 40, scrolled_row_info); + background_fetch_block(119, 20, 46, scrolled_row_info); + background_fetch_block(135, 24, 52, row_info); + background_fetch_block(152, 28, 58, row_info); + + external_slots_4(168); return; } @@ -741,7 +734,6 @@ class Base { void draw_tms_character(int start, int end); void draw_tms_text(int start, int end); void draw_sms(int start, int end); - }; } From 9ab0c54426553af38f277de836dcbeeeed217997 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 12 Oct 2018 18:57:07 -0400 Subject: [PATCH 68/88] Eliminates faulty attempt to satisfy SMSVDP vertical counter test. --- Components/9918/9918.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 567704a25..e083eb071 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -64,6 +64,7 @@ Base::Base(Personality p) : if(is_sega_vdp(personality_)) { mode_timing_.line_interrupt_position = 65; + mode_timing_.end_of_frame_interrupt_position.column = 63; mode_timing_.end_of_frame_interrupt_position.row = 193; } @@ -324,7 +325,7 @@ void TMS9918::run_for(const HalfCycles cycles) { // it is reloaded either when it overflows or upon every non-pixel line after the first. // It is otherwise decremented. if(is_sega_vdp(personality_)) { - if(row_ >= mode_timing_.total_lines-1 && row_ <= mode_timing_.pixel_lines) { + if(row_ >= 0 && row_ <= mode_timing_.pixel_lines) { --line_interrupt_counter; if(line_interrupt_counter == 0xff) { line_interrupt_pending_ = true; From 9e52ead09a302336b72b9d87092fb1df98fe643e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 12 Oct 2018 19:50:48 -0400 Subject: [PATCH 69/88] Ensures sprite scanning doesn't improperly set collision flag; that slot 151 is filled. --- Components/9918/9918.cpp | 7 ++++--- Components/9918/Implementation/9918Base.hpp | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index e083eb071..8c94d72d3 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -106,7 +106,7 @@ void Base::reset_sprite_collection() { void Base::posit_sprite(int sprite_number, int sprite_position, int screen_row) { if(!(status_ & StatusSpriteOverflow)) { - status_ = static_cast((status_ & ~31) | sprite_number); + status_ = static_cast((status_ & ~0x1f) | (sprite_number & 0x1f)); } if(sprite_set_.sprites_stopped) return; @@ -891,7 +891,7 @@ void Base::draw_sms(int start, int end) { for(int index = sprite_set_.fetched_sprite_slot - 1; index >= 0; --index) { SpriteSet::ActiveSprite &sprite = sprite_set_.active_sprites[index]; if(sprite.shift_position < 16) { - int pixel_start = std::max(start, sprite.x); + const int pixel_start = std::max(start, sprite.x); // TODO: it feels like the work below should be simplifiable; // the double shift in particular, and hopefully the variable shift. @@ -922,7 +922,8 @@ void Base::draw_sms(int start, int end) { ) colour_buffer[c] = sprite_buffer[c]; } - if(sprite_collision) status_ |= StatusSpriteCollision; + if(sprite_collision) + status_ |= StatusSpriteCollision; } // Map from the 32-colour buffer to real output pixels. diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index e3536f5d0..af2a2b1ee 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -12,6 +12,7 @@ #include "../../../Outputs/CRT/CRT.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" +#include #include #include @@ -593,7 +594,7 @@ class Base { sprite_set_.active_sprites[sprite].x = \ ram_[\ sprite_attribute_table_address_ & size_t(0x3f80 | (sprite_set_.active_sprites[sprite].index << 1))\ - ] - (master_system_.shift_sprites_8px_left ? size_t(8) : size_t(0)); \ + ] - (master_system_.shift_sprites_8px_left ? 8 : 0); \ const uint8_t name = ram_[\ sprite_attribute_table_address_ & size_t(0x3f81 | (sprite_set_.active_sprites[sprite].index << 1))\ ] & (sprites_16x16_ ? ~1 : ~0);\ @@ -682,6 +683,8 @@ class Base { // ... and do the actual fetching, which follows this routine: switch(start) { default: + assert(false); + sprite_fetch_block(0, 0); sprite_fetch_block(6, 2); @@ -712,9 +715,9 @@ class Base { background_fetch_block(103, 16, 40, scrolled_row_info); background_fetch_block(119, 20, 46, scrolled_row_info); background_fetch_block(135, 24, 52, row_info); - background_fetch_block(152, 28, 58, row_info); + background_fetch_block(151, 28, 58, row_info); - external_slots_4(168); + external_slots_4(167); return; } From 6c09abc6cb06a3df696593637473e3adac8cd165 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 14 Oct 2018 16:23:45 -0400 Subject: [PATCH 70/88] Makes a flawed attempt to reformulate this exactly as two separate processes on a common clock with an interchange buffer. Specifically because closer inspection of the TMS modes shows it isn't quite valid to model output of one line as having fully completed prior to fetching of the next. So some sort of extra buffer is required. At which point it is most natural to continue with the logic that each fetch routine is oriented around the fetching process for a single line, and each output routine has the same view, suggesting separate read/write addresses. Something is wrong though, as video data is being output too rapidly (I think) and with occasional sync issues (again: subject to investigation). --- Components/9918/9918.cpp | 428 +++++++++++--------- Components/9918/Implementation/9918Base.hpp | 323 ++++++++------- 2 files changed, 407 insertions(+), 344 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 8c94d72d3..c79f44053 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -68,6 +68,14 @@ Base::Base(Personality p) : mode_timing_.end_of_frame_interrupt_position.column = 63; mode_timing_.end_of_frame_interrupt_position.row = 193; } + + // Establish that output is 10 cycles after values have been read. + // This is definitely correct for the TMS; more research may be + // necessary for the other implemented VDPs. + read_pointer_.row = 0; + read_pointer_.column = 0; + write_pointer_.row = 0; + write_pointer_.column = 10; // i.e. 10 cycles ahead of the read pointer. } TMS9918::TMS9918(Personality p): @@ -94,45 +102,44 @@ Outputs::CRT::CRT *TMS9918::get_crt() { return crt_.get(); } -void Base::reset_sprite_collection() { - sprite_set_.sprites_stopped = false; - sprite_set_.fetched_sprite_slot = sprite_set_.active_sprite_slot; - sprite_set_.active_sprite_slot = 0; +void Base::LineBuffer::reset_sprite_collection() { + sprites_stopped = false; + active_sprite_slot = 0; - for(int c = 0; c < sprite_set_.fetched_sprite_slot; ++c) { - sprite_set_.active_sprites[c].shift_position = 0; + for(int c = 0; c < 8; ++c) { + active_sprites[c].shift_position = 0; } } -void Base::posit_sprite(int sprite_number, int sprite_position, int screen_row) { +void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_position, int screen_row) { if(!(status_ & StatusSpriteOverflow)) { status_ = static_cast((status_ & ~0x1f) | (sprite_number & 0x1f)); } - if(sprite_set_.sprites_stopped) + if(buffer.sprites_stopped) return; // const int sprite_position = ram_[sprite_attribute_table_address_ + static_cast(sprite_number << 2)]; // A sprite Y of 208 means "don't scan the list any further". if(mode_timing_.allow_sprite_terminator && sprite_position == 208) { - sprite_set_.sprites_stopped = true; + buffer.sprites_stopped = true; return; } const int sprite_row = (screen_row - sprite_position)&255; if(sprite_row < 0 || sprite_row >= sprite_height_) return; - if(sprite_set_.active_sprite_slot == mode_timing_.maximum_visible_sprites) { + if(buffer.active_sprite_slot == mode_timing_.maximum_visible_sprites) { status_ |= StatusSpriteOverflow; return; } - SpriteSet::ActiveSprite &sprite = sprite_set_.active_sprites[sprite_set_.active_sprite_slot]; + LineBuffer::ActiveSprite &sprite = buffer.active_sprites[buffer.active_sprite_slot]; sprite.index = sprite_number; sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0); - ++sprite_set_.active_sprite_slot; + ++buffer.active_sprite_slot; } -void Base::get_sprite_contents(int field, int cycles_left, int screen_row) { +//void Base::get_sprite_contents(int field, int cycles_left, int screen_row) { /* int sprite_id = field / 6; field %= 6; @@ -163,7 +170,7 @@ void Base::get_sprite_contents(int field, int cycles_left, int screen_row) { field = 0; sprite_id++; }*/ -} +//} void TMS9918::run_for(const HalfCycles cycles) { // As specific as I've been able to get: @@ -179,74 +186,185 @@ void TMS9918::run_for(const HalfCycles cycles) { int_cycles >>= 2; if(!int_cycles) return; - while(int_cycles) { - // Determine how much time has passed in the remainder of this line, and proceed. - const int cycles_left = std::min(342 - column_, int_cycles); - const int end_column = column_ + cycles_left; + // There are two intertwined processes here, 'writing' (which means writing to the + // line buffers, i.e. it's everything to do with collecting a line) and 'reading' + // (which means reading from the line buffers and generating video). + int write_cycles_pool = int_cycles; + int read_cycles_pool = int_cycles; + + while(write_cycles_pool || read_cycles_pool) { + if(write_cycles_pool) { + // Determine how much writing to do. + const int write_cycles = std::min(342 - write_pointer_.column, write_cycles_pool); + const int end_column = write_pointer_.column + write_cycles; + LineBuffer &line_buffer = line_buffers_[0];//write_pointer_.row & 1]; - // --------------------------------------- - // Latch scrolling position, if necessary. - // --------------------------------------- - if(column_ < 61 && end_column >= 61) { - if(!row_) { - master_system_.latched_vertical_scroll = master_system_.vertical_scroll; + + // --------------------------------------- + // Latch scrolling position, if necessary. + // --------------------------------------- + if(write_pointer_.column < 61 && end_column >= 61) { + if(!write_pointer_.row) { + master_system_.latched_vertical_scroll = master_system_.vertical_scroll; + } + line_buffer.latched_horizontal_scroll = master_system_.horizontal_scroll; } - master_system_.latched_horizontal_scroll = master_system_.horizontal_scroll; - } - // ------------------------ - // Perform memory accesses. - // ------------------------ + + // ------------------------ + // Perform memory accesses. + // ------------------------ #define fetch(function) \ - if(end_column < 171) { \ + if(final_window < 171) { \ function(first_window, final_window);\ } else {\ function(first_window, final_window);\ } - // column_ and end_column are in 342-per-line cycles; - // adjust them to a count of windows. - const int first_window = column_ >> 1; - const int final_window = end_column >> 1; - if(first_window != final_window) { - switch(line_mode_) { - case LineMode::Text: fetch(fetch_tms_text); break; - case LineMode::Character: fetch(fetch_tms_character); break; - case LineMode::SMS: fetch(fetch_sms); break; - case LineMode::Refresh: fetch(fetch_tms_refresh); break; + // column_ and end_column are in 342-per-line cycles; + // adjust them to a count of windows. + const int first_window = write_pointer_.column >> 1; + const int final_window = end_column >> 1; + if(first_window != final_window) { + switch(line_buffer.line_mode) { + case LineMode::Text: fetch(fetch_tms_text); break; + case LineMode::Character: fetch(fetch_tms_character); break; + case LineMode::SMS: fetch(fetch_sms); break; + case LineMode::Refresh: fetch(fetch_tms_refresh); break; + } } - } #undef fetch - // -------------------- - // Output video stream. - // -------------------- + + // ------------------------------- + // Check for interrupt conditions. + // ------------------------------- + if(write_pointer_.column < mode_timing_.line_interrupt_position && end_column >= mode_timing_.line_interrupt_position) { + // The Sega VDP offers a decrementing counter for triggering line interrupts; + // it is reloaded either when it overflows or upon every non-pixel line after the first. + // It is otherwise decremented. + if(is_sega_vdp(personality_)) { + if(write_pointer_.row >= 0 && write_pointer_.row <= mode_timing_.pixel_lines) { + --line_interrupt_counter; + if(line_interrupt_counter == 0xff) { + line_interrupt_pending_ = true; + line_interrupt_counter = line_interrupt_target; + } + } else { + line_interrupt_counter = line_interrupt_target; + } + } + + // TODO: the V9938 provides line interrupts from direct specification of the target line. + // So life is easy. + } + + if( + write_pointer_.row == mode_timing_.end_of_frame_interrupt_position.row && + write_pointer_.column < mode_timing_.end_of_frame_interrupt_position.column && + end_column >= mode_timing_.end_of_frame_interrupt_position.column + ) { + status_ |= StatusInterrupt; + } + + + + // ------------- + // Advance time. + // ------------- + write_pointer_.column = end_column; + write_cycles_pool -= write_cycles; + + if(write_pointer_.column == 342) { + write_pointer_.column = 0; + write_pointer_.row = (write_pointer_.row + 1) % mode_timing_.total_lines; + line_buffer = line_buffers_[0];//write_pointer_.row & 1]; + + // Establish the output mode for the next line. + set_current_screen_mode(); + + // Based on the output mode, pick a line mode. + line_buffer.first_pixel_output_column = 86; + line_buffer.next_border_column = 342; + mode_timing_.maximum_visible_sprites = 4; + switch(screen_mode_) { + case ScreenMode::Text: + line_buffer.line_mode = LineMode::Text; + line_buffer.first_pixel_output_column = 94; + line_buffer.next_border_column = 334; + break; + case ScreenMode::SMSMode4: + line_buffer.line_mode = LineMode::SMS; + mode_timing_.maximum_visible_sprites = 8; + break; + default: + line_buffer.line_mode = LineMode::Character; + break; + } + + if( + (screen_mode_ == ScreenMode::Blank) || + (write_pointer_.row >= mode_timing_.pixel_lines && write_pointer_.row != mode_timing_.total_lines-1)) + line_buffer.line_mode = LineMode::Refresh; + } + } + + + if(read_cycles_pool) { + // Determine how much time has passed in the remainder of this line, and proceed. + const int read_cycles = std::min(342 - read_pointer_.column, read_cycles_pool); + const int end_column = read_pointer_.column + read_cycles; + LineBuffer &line_buffer = line_buffers_[0];//read_pointer_.row & 1]; + + + + // -------------------- + // Output video stream. + // -------------------- #define intersect(left, right, code) \ { \ - const int start = std::max(column_, left); \ + const int start = std::max(read_pointer_.column, left); \ const int end = std::min(end_column, right); \ if(end > start) {\ code;\ }\ } - if(line_mode_ == LineMode::Refresh || row_ > mode_timing_.pixel_lines) { - if(row_ >= mode_timing_.first_vsync_line && row_ < mode_timing_.first_vsync_line+4) { - // Vertical sync. - if(end_column == 342) { - crt_->output_sync(342 * 4); + if(line_buffer.line_mode == LineMode::Refresh) { + if(read_pointer_.row >= mode_timing_.first_vsync_line && read_pointer_.row < mode_timing_.first_vsync_line+4) { + // Vertical sync. + if(end_column == 342) { + crt_->output_sync(342 * 4); + } + } else { + // Right border. + intersect(0, 15, output_border(end - start)); + + // Blanking region; total length is 58 cycles, + // and 58+15 = 73. So output the lot when the + // cursor passes 73. + if(read_pointer_.column < 73 && end_column >= 73) { + crt_->output_blank(8*4); + crt_->output_sync(26*4); + crt_->output_blank(2*4); + crt_->output_default_colour_burst(14*4); + crt_->output_blank(8*4); + } + + // Border colour for the rest of the line. + intersect(73, 342, output_border(end - start)); } } else { // Right border. intersect(0, 15, output_border(end - start)); // Blanking region. - if(column_ < 73 && end_column >= 73) { + if(read_pointer_.column < 73 && end_column >= 73) { crt_->output_blank(8*4); crt_->output_sync(26*4); crt_->output_blank(2*4); @@ -254,141 +372,58 @@ void TMS9918::run_for(const HalfCycles cycles) { crt_->output_blank(8*4); } - // Most of line. - intersect(73, 342, output_border(end - start)); - } - } else { - // Right border. - intersect(0, 15, output_border(end - start)); + // Left border. + intersect(73, line_buffer.first_pixel_output_column, output_border(end - start)); - // Blanking region. - if(column_ < 73 && end_column >= 73) { - crt_->output_blank(8*4); - crt_->output_sync(26*4); - crt_->output_blank(2*4); - crt_->output_default_colour_burst(14*4); - crt_->output_blank(8*4); - } - - // Left border. - intersect(73, mode_timing_.first_pixel_output_column, output_border(end - start)); - - // Pixel region. - intersect( - mode_timing_.first_pixel_output_column, - mode_timing_.next_border_column, - if(start == mode_timing_.first_pixel_output_column) { - pixel_origin_ = pixel_target_ = reinterpret_cast( - crt_->allocate_write_area(static_cast(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column) + 8) // TODO: the +8 is really for the SMS only; make it optional. - ); - } - - if(pixel_target_) { - const int relative_start = start - mode_timing_.first_pixel_output_column; - const int relative_end = end - mode_timing_.first_pixel_output_column; - switch(line_mode_) { - case LineMode::SMS: draw_sms(relative_start, relative_end); break; - case LineMode::Character: draw_tms_character(relative_start, relative_end); break; - case LineMode::Text: draw_tms_text(relative_start, relative_end); break; - - case LineMode::Refresh: break; /* Dealt with elsewhere. */ + // Pixel region. + intersect( + line_buffer.first_pixel_output_column, + line_buffer.next_border_column, + if(start == line_buffer.first_pixel_output_column) { + pixel_origin_ = pixel_target_ = reinterpret_cast( + crt_->allocate_write_area(static_cast(line_buffer.next_border_column - line_buffer.first_pixel_output_column)) + ); } - } - if(end == mode_timing_.next_border_column) { - const unsigned int length = static_cast(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column); - crt_->output_data(length * 4, length); - pixel_origin_ = pixel_target_ = nullptr; - } - ); + if(pixel_target_) { + const int relative_start = start - line_buffer.first_pixel_output_column; + const int relative_end = end - line_buffer.first_pixel_output_column; + switch(line_buffer.line_mode) { + case LineMode::SMS: draw_sms(relative_start, relative_end); break; + case LineMode::Character: draw_tms_character(relative_start, relative_end); break; + case LineMode::Text: draw_tms_text(relative_start, relative_end); break; - // Additional right border, if called for. - if(mode_timing_.next_border_column != 342) { - intersect(mode_timing_.next_border_column, 342, output_border(end - start)); - } - } - -#undef intersect - - // ----------------- - // End video stream. - // ----------------- - - - - // ------------------------------- - // Check for interrupt conditions. - // ------------------------------- - - if(column_ < mode_timing_.line_interrupt_position && end_column >= mode_timing_.line_interrupt_position) { - // The Sega VDP offers a decrementing counter for triggering line interrupts; - // it is reloaded either when it overflows or upon every non-pixel line after the first. - // It is otherwise decremented. - if(is_sega_vdp(personality_)) { - if(row_ >= 0 && row_ <= mode_timing_.pixel_lines) { - --line_interrupt_counter; - if(line_interrupt_counter == 0xff) { - line_interrupt_pending_ = true; - line_interrupt_counter = line_interrupt_target; + case LineMode::Refresh: break; /* Dealt with elsewhere. */ + } } - } else { - line_interrupt_counter = line_interrupt_target; + + if(end == line_buffer.next_border_column) { + const unsigned int length = static_cast(line_buffer.next_border_column - line_buffer.first_pixel_output_column); + crt_->output_data(length * 4, length); + pixel_origin_ = pixel_target_ = nullptr; + } + ); + + // Additional right border, if called for. + if(line_buffer.next_border_column != 342) { + intersect(line_buffer.next_border_column, 342, output_border(end - start)); } } - // TODO: the V9938 provides line interrupts from direct specification of the target line. - // So life is easy. - } - - if( - row_ == mode_timing_.end_of_frame_interrupt_position.row && - column_ < mode_timing_.end_of_frame_interrupt_position.column && - end_column >= mode_timing_.end_of_frame_interrupt_position.column - ) { - status_ |= StatusInterrupt; - } + #undef intersect - // ------------- - // Advance time. - // ------------- + // ------------- + // Advance time. + // ------------- + read_pointer_.column = end_column; + read_cycles_pool -= read_cycles; - column_ = end_column; // column_ is now the column that has been reached in this line. - int_cycles -= cycles_left; // Count down duration to run for. - - - - // ----------------------------------- - // Prepare for next line, potentially. - // ----------------------------------- - if(column_ == 342) { - column_ = 0; - row_ = (row_ + 1) % mode_timing_.total_lines; - - // Establish the output mode for the next line. - set_current_mode(); - - // Based on the output mode, pick a line mode. - mode_timing_.first_pixel_output_column = 86; - mode_timing_.next_border_column = 342; - mode_timing_.maximum_visible_sprites = 4; - switch(screen_mode_) { - case ScreenMode::Text: - line_mode_ = LineMode::Text; - mode_timing_.first_pixel_output_column = 94; - mode_timing_.next_border_column = 334; - break; - case ScreenMode::SMSMode4: - line_mode_ = LineMode::SMS; - mode_timing_.maximum_visible_sprites = 8; - break; - default: - line_mode_ = LineMode::Character; - break; + if(read_pointer_.column == 342) { + read_pointer_.column = 0; + read_pointer_.row = (read_pointer_.row + 1) % mode_timing_.total_lines; } - - if((screen_mode_ == ScreenMode::Blank) || (row_ >= mode_timing_.pixel_lines && row_ != mode_timing_.total_lines-1)) line_mode_ = LineMode::Refresh; } } } @@ -537,7 +572,10 @@ void TMS9918::set_register(int address, uint8_t value) { uint8_t TMS9918::get_current_line() { // Determine the row to return. static const int row_change_position = 62; // This is the proper Master System value; substitute if any other VDPs turn out to have this functionality. - int source_row = (column_ < row_change_position) ? (row_ + mode_timing_.total_lines - 1)%mode_timing_.total_lines : row_; + int source_row = + (write_pointer_.column < row_change_position) + ? (write_pointer_.row + mode_timing_.total_lines - 1)%mode_timing_.total_lines + : write_pointer_.row; // This assumes NTSC 192-line. TODO: other modes. if(source_row >= 0xdb) source_row -= 6; @@ -568,7 +606,7 @@ uint8_t TMS9918::get_latched_horizontal_counter() { } void TMS9918::latch_horizontal_counter() { - latched_column_ = column_; + latched_column_ = write_pointer_.column; } uint8_t TMS9918::get_register(int address) { @@ -603,7 +641,7 @@ HalfCycles TMS9918::get_time_until_interrupt() { const int time_until_frame_interrupt = ( ((mode_timing_.end_of_frame_interrupt_position.row * 342) + mode_timing_.end_of_frame_interrupt_position.column + frame_length) - - ((row_ * 342) + column_) + ((write_pointer_.row * 342) + write_pointer_.column) ) % frame_length; if(!enable_line_interrupts_) return half_cycles_before_internal_cycles(time_until_frame_interrupt); @@ -615,8 +653,8 @@ HalfCycles TMS9918::get_time_until_interrupt() { // If there is still time for a line interrupt this frame, that'll be it; // otherwise it'll be on the next frame, supposing there's ever time for // it at all. - if(row_+line_interrupt_counter <= mode_timing_.pixel_lines) { - next_line_interrupt_row = row_+line_interrupt_counter; + if(write_pointer_.row+line_interrupt_counter <= mode_timing_.pixel_lines) { + next_line_interrupt_row = write_pointer_.row+line_interrupt_counter; } else { if(line_interrupt_target <= mode_timing_.pixel_lines) next_line_interrupt_row = mode_timing_.total_lines + line_interrupt_target; @@ -633,9 +671,9 @@ HalfCycles TMS9918::get_time_until_interrupt() { // Figure out the number of internal cycles until the next line interrupt, which is the amount // of time to the next tick over and then next_line_interrupt_row - row_ lines further. - int local_cycles_until_next_tick = (mode_timing_.line_interrupt_position - column_ + 342) % 342; + int local_cycles_until_next_tick = (mode_timing_.line_interrupt_position - write_pointer_.column + 342) % 342; if(!local_cycles_until_next_tick) local_cycles_until_next_tick += 342; - const int local_cycles_until_line_interrupt = local_cycles_until_next_tick + (next_line_interrupt_row - row_) * 342; + const int local_cycles_until_line_interrupt = local_cycles_until_next_tick + (next_line_interrupt_row - write_pointer_.row) * 342; if(!generate_interrupts_) return half_cycles_before_internal_cycles(time_until_frame_interrupt); @@ -782,11 +820,12 @@ void Base::draw_tms_character(int start, int end) { } void Base::draw_tms_text(int start, int end) { + LineBuffer &line_buffer = line_buffers_[0];//read_pointer_.row & 1]; const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; const int shift = start % 6; int byte_column = start / 6; - int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; + int pattern = reverse_table.map[line_buffer.patterns[byte_column][0]] >> shift; int pixels_left = end - start; int length = std::min(pixels_left, 6 - shift); while(true) { @@ -800,11 +839,12 @@ void Base::draw_tms_text(int start, int end) { if(!pixels_left) break; length = std::min(6, pixels_left); byte_column++; - pattern = reverse_table.map[pattern_buffer_[byte_column]]; + pattern = reverse_table.map[line_buffer.patterns[byte_column][0]]; } } void Base::draw_sms(int start, int end) { + LineBuffer &line_buffer = line_buffers_[0];//read_pointer_.row & 1]; int colour_buffer[256]; /* @@ -812,15 +852,15 @@ void Base::draw_sms(int start, int end) { */ int tile_start = start, tile_end = end; int tile_offset = start; - if(row_ >= 16 || !master_system_.horizontal_scroll_lock) { - for(int c = start; c < (master_system_.latched_horizontal_scroll & 7); ++c) { + if(read_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) { + for(int c = start; c < (line_buffer.latched_horizontal_scroll & 7); ++c) { colour_buffer[c] = 16 + background_colour_; ++tile_offset; } // Remove the border area from that to which tiles will be drawn. - tile_start = std::max(start - (master_system_.latched_horizontal_scroll & 7), 0); - tile_end = std::max(end - (master_system_.latched_horizontal_scroll & 7), 0); + tile_start = std::max(start - (line_buffer.latched_horizontal_scroll & 7), 0); + tile_end = std::max(end - (line_buffer.latched_horizontal_scroll & 7), 0); } @@ -838,15 +878,15 @@ void Base::draw_sms(int start, int end) { int pixels_left = tile_end - tile_start; int length = std::min(pixels_left, 8 - shift); - pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); - if(master_system_.names[byte_column].flags&2) + pattern = *reinterpret_cast(line_buffer.patterns[byte_column]); + if(line_buffer.names[byte_column].flags&2) pattern >>= shift; else pattern <<= shift; while(true) { - const int palette_offset = (master_system_.names[byte_column].flags&0x18) << 1; - if(master_system_.names[byte_column].flags&2) { + const int palette_offset = (line_buffer.names[byte_column].flags&0x18) << 1; + if(line_buffer.names[byte_column].flags&2) { for(int c = 0; c < length; ++c) { colour_buffer[tile_offset] = ((pattern_index[3] & 0x01) << 3) | @@ -875,21 +915,21 @@ void Base::draw_sms(int start, int end) { length = std::min(8, pixels_left); byte_column++; - pattern = *reinterpret_cast(master_system_.tile_graphics[byte_column]); + pattern = *reinterpret_cast(line_buffer.patterns[byte_column]); } } /* Apply sprites (if any). */ - if(sprite_set_.fetched_sprite_slot) { + if(line_buffer.active_sprite_slot) { int sprite_buffer[256]; int sprite_collision = 0; memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(int)); // Draw all sprites into the sprite buffer. - for(int index = sprite_set_.fetched_sprite_slot - 1; index >= 0; --index) { - SpriteSet::ActiveSprite &sprite = sprite_set_.active_sprites[index]; + for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { + LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; if(sprite.shift_position < 16) { const int pixel_start = std::max(start, sprite.x); diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index af2a2b1ee..fb9da9745 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -97,20 +97,24 @@ class Base { bool generate_interrupts_ = false; int sprite_height_ = 8; - size_t pattern_name_address_ = 0; - size_t colour_table_address_ = 0; - size_t pattern_generator_table_address_ = 0; - size_t sprite_attribute_table_address_ = 0; - size_t sprite_generator_table_address_ = 0; + size_t pattern_name_address_ = 0; // i.e. address of the tile map. + size_t colour_table_address_ = 0; // address of the colour map (if applicable). + size_t pattern_generator_table_address_ = 0; // address of the tile contents. + size_t sprite_attribute_table_address_ = 0; // address of the sprite list. + size_t sprite_generator_table_address_ = 0; // address of the sprite contents. uint8_t text_colour_ = 0; uint8_t background_colour_ = 0; - // Internal mechanisms for position tracking. - int column_ = 0, row_ = 0, latched_column_ = 0; + // This implementation of this chip officially accepts a 3.58Mhz clock, but runs + // internally at 5.37Mhz. The following two help to maintain a lossless conversion + // from the one to the other. int cycles_error_ = 0; HalfCycles half_cycles_before_internal_cycles(int internal_cycles); + // Internal mechanisms for position tracking. + int latched_column_ = 0; + // A helper function to output the current border colour for // the number of cycles supplied. void output_border(int cycles); @@ -130,25 +134,6 @@ class Base { int pixel_lines = 192; int first_vsync_line = 227; - /* - Horizontal layout (on a 342-cycle clock): - - 15 cycles right border - 58 cycles blanking & sync - 13 cycles left border - - ... i.e. to cycle 86, then: - - border up to first_pixel_output_column; - pixels up to next_border_column; - border up to the end. - - e.g. standard 256-pixel modes will want to set - first_pixel_output_column = 86, next_border_column = 342. - */ - int first_pixel_output_column; - int next_border_column; - // Maximum number of sprite slots to populate; // if sprites beyond this number should be visible // then the appropriate status information will be set. @@ -172,18 +157,91 @@ class Base { bool enable_line_interrupts_ = false; bool line_interrupt_pending_ = false; - // The line mode describes the proper timing diagram for the current line. + // The screen mode is a necessary predecessor to picking the line mode, + // which is the thing latched per line. + enum class ScreenMode { + Blank, + Text, + MultiColour, + ColouredText, + Graphics, + SMSMode4 + } screen_mode_; + enum class LineMode { Text, Character, Refresh, SMS - } line_mode_ = LineMode::Text; + }; // Temporary buffers collect a representation of this line prior to pixel serialisation. - uint8_t pattern_names_[40]; - uint8_t pattern_buffer_[40]; - uint8_t colour_buffer_[40]; + struct LineBuffer { + // The line mode describes the proper timing diagram for this line. + LineMode line_mode = LineMode::Text; + + // Holds the horizontal scroll position to apply to this line; + // of those VDPs currently implemented, affects the Master System only. + uint8_t latched_horizontal_scroll = 0; + + // The names array holds pattern names, as an offset into memory, and + // potentially flags also. + struct { + size_t offset; + uint8_t flags; + } names[40]; + + // The patterns array holds tile patterns, corresponding 1:1 with names. + // Four bytes per pattern is the maximum required by any + // currently-implemented VDP. + uint8_t patterns[40][4]; + + /* + Horizontal layout (on a 342-cycle clock): + + 15 cycles right border + 58 cycles blanking & sync + 13 cycles left border + + ... i.e. to cycle 86, then: + + border up to first_pixel_output_column; + pixels up to next_border_column; + border up to the end. + + e.g. standard 256-pixel modes will want to set + first_pixel_output_column = 86, next_border_column = 342. + */ + int first_pixel_output_column; + int next_border_column; + + // An active sprite is one that has been selected for composition onto + // this line. + struct ActiveSprite { + int index = 0; // The original in-table index of this sprite. + int row = 0; // The row of the sprite that should be drawn. + int x = 0; // The sprite's x position on screen. + + uint8_t image[4]; // Up to four bytes of image information. + int shift_position = 0; // An offset representing how much of the image information has already been drawn. + } active_sprites[8]; + + int active_sprite_slot = 0; // A pointer to the slot into which a new active sprite will be deposited, if required. + bool sprites_stopped = false; // A special TMS feature is that a sentinel value can be used to prevent any further sprites + // being evaluated for display. This flag determines whether the sentinel has yet been reached. + + void reset_sprite_collection(); + } line_buffers_[2]; + void posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_y, int screen_row); + + // There is a delay between reading into the line buffer and outputting from there to the screen. That delay + // is observeable because reading time affects availability of memory accesses and therefore time in which + // to update sprites and tiles, but writing time affects when the palette is used and when the collision flag + // may end up being set. So the two processes are slightly decoupled. The end of reading one line may overlap + // with the beginning of writing the next, hence the two separate line buffers. + struct LineBufferPointer { + int row, column; + } read_pointer_, write_pointer_; // Extra information that affects the Master System output mode. struct { @@ -200,50 +258,12 @@ class Base { uint32_t colour_ram[32]; bool cram_is_selected = false; - // Temporary buffers for a line of Master System graphics, - // and latched scrolling offsets. - struct { - size_t offset; - uint8_t flags; - } names[32]; - uint8_t tile_graphics[32][4]; + // Holds the vertical scroll position for this frame; this is latched + // once and cannot dynamically be changed until the next frame. uint8_t latched_vertical_scroll = 0; - uint8_t latched_horizontal_scroll = 0; } master_system_; - // Holds results of sprite data fetches that occur on this - // line. Therefore has to contain: up to four or eight sets - // of sprite data for this line, and its horizontal position, - // plus a growing list of which sprites are selected for - // the next line. - struct SpriteSet { - struct ActiveSprite { - int index = 0; - int row = 0; - - uint8_t image[4]; - int x = 0; - int shift_position = 0; - } active_sprites[8]; - - int active_sprite_slot = 0; - int fetched_sprite_slot = 0; - bool sprites_stopped = false; - } sprite_set_; - - inline void reset_sprite_collection(); - inline void posit_sprite(int sprite_number, int sprite_y, int screen_row); - inline void get_sprite_contents(int start, int cycles, int screen_row); - - enum class ScreenMode { - Blank, - Text, - MultiColour, - ColouredText, - Graphics, - SMSMode4 - } screen_mode_; - void set_current_mode() { + void set_current_screen_mode() { if(blank_display_) { screen_mode_ = ScreenMode::Blank; return; @@ -283,7 +303,6 @@ class Base { screen_mode_ = ScreenMode::Blank; } - void do_external_slot() { switch(queued_access_) { default: return; @@ -307,36 +326,7 @@ class Base { queued_access_ = MemoryAccess::None; } -#define slot(n) \ - if(use_end && end+1 == n) return;\ - case n - -#define external_slot(n) \ - slot(n): do_external_slot(); - -#define external_slots_2(n) \ - external_slot(n); \ - external_slot(n+1); - -#define external_slots_4(n) \ - external_slots_2(n); \ - external_slots_2(n+2); - -#define external_slots_8(n) \ - external_slots_4(n); \ - external_slots_4(n+4); - -#define external_slots_16(n) \ - external_slots_8(n); \ - external_slots_8(n+8); - -#define external_slots_32(n) \ - external_slots_16(n); \ - external_slots_16(n+16); - /* - TODO: explain offset by four windows in data gathering. - Fetching routines follow below; they obey the following rules: 1) input is a start position and an end position; they should perform the proper @@ -368,30 +358,59 @@ class Base { for the exceptions. */ +#define slot(n) \ + if(use_end && end+1 == n) return;\ + case n + +#define external_slot(n) \ + slot(n): do_external_slot(); + +#define external_slots_2(n) \ + external_slot(n); \ + external_slot(n+1); + +#define external_slots_4(n) \ + external_slots_2(n); \ + external_slots_2(n+2); + +#define external_slots_8(n) \ + external_slots_4(n); \ + external_slots_4(n+4); + +#define external_slots_16(n) \ + external_slots_8(n); \ + external_slots_8(n+8); + +#define external_slots_32(n) \ + external_slots_16(n); \ + external_slots_16(n+16); + /*********************************************** TMS9918 Fetching Code ************************************************/ template void fetch_tms_refresh(int start, int end) { -#define refresh(location) external_slot(location+1) +#define refresh(location) \ + slot(location): \ + external_slot(location+1); #define refreshes_2(location) \ - refresh(location); \ + refresh(location); \ refresh(location+2); #define refreshes_4(location) \ - refreshes_2(location); \ + refreshes_2(location); \ refreshes_2(location+4); #define refreshes_8(location) \ - refreshes_4(location); \ + refreshes_4(location); \ refreshes_4(location+8); switch(start) { - default: + default: assert(false); - /* 44 external slots (= 44 windows) */ + /* 44 external slots */ external_slots_32(0) external_slots_8(32) external_slots_4(40) @@ -416,8 +435,8 @@ class Base { } template void fetch_tms_text(int start, int end) { -#define fetch_tile_name(location, column) slot(location): pattern_names_[column] = ram_[row_base + column]; -#define fetch_tile_pattern(location, column) slot(location): pattern_buffer_[column] = ram_[row_offset + size_t(pattern_names_[column] << 3)]; +#define fetch_tile_name(location, column) slot(location): line_buffer.names[column].offset = ram_[row_base + column]; +#define fetch_tile_pattern(location, column) slot(location): line_buffer.patterns[column][0] = ram_[row_offset + size_t(line_buffer.names[column].offset << 3)]; #define fetch_column(location, column) \ fetch_tile_name(location, column); \ @@ -436,11 +455,12 @@ class Base { fetch_columns_4(location, column); \ fetch_columns_4(location+12, column+4); - const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast(row_ >> 3) * 40); - const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (row_ & 7)); + LineBuffer &line_buffer = line_buffers_[0];//write_pointer_.row & 1]; + const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast(write_pointer_.row >> 3) * 40); + const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7)); switch(start) { - default: + default: assert(false); /* 47 external slots (= 47 windows) */ external_slots_32(0) @@ -456,8 +476,9 @@ class Base { fetch_columns_8(119, 24); fetch_columns_8(143, 32); - /* 4 more external slots */ + /* 5 more external slots */ external_slots_4(167); + external_slot(171); return; } @@ -492,11 +513,11 @@ class Base { #define sprite_y_read(location, sprite) \ slot(location): -#define fetch_tile_name(column) pattern_names_[column] = ram_[(row_base + column) & 0x3fff]; +#define fetch_tile_name(column) line_buffer.names[column].offset = ram_[(row_base + column) & 0x3fff]; #define fetch_tile(column) {\ - colour_buffer_[column] = ram_[(colour_base + static_cast((pattern_names_[column] << 3) >> colour_name_shift)) & 0x3fff]; \ - pattern_buffer_[column] = ram_[(pattern_base + static_cast(pattern_names_[column] << 3)) & 0x3fff]; \ + line_buffer.patterns[column][0] = ram_[(colour_base + static_cast((line_buffer.names[column].offset << 3) >> colour_name_shift)) & 0x3fff]; \ + line_buffer.patterns[column][1] = ram_[(pattern_base + static_cast(line_buffer.names[column].offset << 3)) & 0x3fff]; \ } #define background_fetch_block(location, column) \ @@ -517,7 +538,8 @@ class Base { slot(location+14): \ slot(location+15): fetch_tile(column+3) - const size_t row_base = pattern_name_address_ + static_cast((row_ << 2)&~31); + LineBuffer &line_buffer = line_buffers_[0];//write_pointer_.row & 1]; + const size_t row_base = pattern_name_address_ + static_cast((write_pointer_.row << 2)&~31); size_t pattern_base = pattern_generator_table_address_; size_t colour_base = colour_table_address_; @@ -525,21 +547,22 @@ class Base { if(screen_mode_ == ScreenMode::Graphics) { // If this is high resolution mode, allow the row number to affect the pattern and colour addresses. - pattern_base &= static_cast(0x2000 | ((row_ & 0xc0) << 5)); - colour_base &= static_cast(0x2000 | ((row_ & 0xc0) << 5)); + pattern_base &= static_cast(0x2000 | ((write_pointer_.row & 0xc0) << 5)); + colour_base &= static_cast(0x2000 | ((write_pointer_.row & 0xc0) << 5)); - colour_base += static_cast(row_ & 7); + colour_base += static_cast(write_pointer_.row & 7); colour_name_shift = 0; } if(screen_mode_ == ScreenMode::MultiColour) { - pattern_base += static_cast((row_ >> 2) & 7); + pattern_base += static_cast((write_pointer_.row >> 2) & 7); } else { - pattern_base += static_cast(row_ & 7); + pattern_base += static_cast(write_pointer_.row & 7); } switch(start) { - default: + default: assert(false); + external_slots_2(0); sprite_fetch_block(2, 0); @@ -591,18 +614,18 @@ class Base { template void fetch_sms(int start, int end) { #define sprite_fetch(sprite) {\ - sprite_set_.active_sprites[sprite].x = \ + line_buffer.active_sprites[sprite].x = \ ram_[\ - sprite_attribute_table_address_ & size_t(0x3f80 | (sprite_set_.active_sprites[sprite].index << 1))\ + sprite_attribute_table_address_ & size_t(0x3f80 | (line_buffer.active_sprites[sprite].index << 1))\ ] - (master_system_.shift_sprites_8px_left ? 8 : 0); \ const uint8_t name = ram_[\ - sprite_attribute_table_address_ & size_t(0x3f81 | (sprite_set_.active_sprites[sprite].index << 1))\ + sprite_attribute_table_address_ & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 1))\ ] & (sprites_16x16_ ? ~1 : ~0);\ - const size_t graphic_location = sprite_generator_table_address_ & size_t(0x2000 | (name << 5) | (sprite_set_.active_sprites[sprite].row << 2)); \ - sprite_set_.active_sprites[sprite].image[0] = ram_[graphic_location]; \ - sprite_set_.active_sprites[sprite].image[1] = ram_[graphic_location+1]; \ - sprite_set_.active_sprites[sprite].image[2] = ram_[graphic_location+2]; \ - sprite_set_.active_sprites[sprite].image[3] = ram_[graphic_location+3]; \ + const size_t graphic_location = sprite_generator_table_address_ & size_t(0x2000 | (name << 5) | (line_buffer.active_sprites[sprite].row << 2)); \ + line_buffer.active_sprites[sprite].image[0] = ram_[graphic_location]; \ + line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+1]; \ + line_buffer.active_sprites[sprite].image[2] = ram_[graphic_location+2]; \ + line_buffer.active_sprites[sprite].image[3] = ram_[graphic_location+3]; \ } #define sprite_fetch_block(location, sprite) \ @@ -617,23 +640,23 @@ class Base { #define sprite_y_read(location, sprite) \ slot(location): \ - posit_sprite(sprite, ram_[sprite_attribute_table_address_ & (sprite | 0x3f00)], row_); \ - posit_sprite(sprite+1, ram_[sprite_attribute_table_address_ & ((sprite + 1) | 0x3f00)], row_); \ + posit_sprite(line_buffer, sprite, ram_[sprite_attribute_table_address_ & (sprite | 0x3f00)], write_pointer_.row); \ + posit_sprite(line_buffer, sprite+1, ram_[sprite_attribute_table_address_ & ((sprite + 1) | 0x3f00)], write_pointer_.row); \ #define fetch_tile_name(column, row_info) {\ const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\ const size_t address = row_info.pattern_address_base + (scrolled_column << 1); \ - master_system_.names[column].flags = ram_[address+1]; \ - master_system_.names[column].offset = static_cast( \ - (((master_system_.names[column].flags&1) << 8) | ram_[address]) << 5 \ - ) + row_info.sub_row[(master_system_.names[column].flags&4) >> 2]; \ + line_buffer.names[column].flags = ram_[address+1]; \ + line_buffer.names[column].offset = static_cast( \ + (((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5 \ + ) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2]; \ } #define fetch_tile(column) \ - master_system_.tile_graphics[column][0] = ram_[master_system_.names[column].offset]; \ - master_system_.tile_graphics[column][1] = ram_[master_system_.names[column].offset+1]; \ - master_system_.tile_graphics[column][2] = ram_[master_system_.names[column].offset+2]; \ - master_system_.tile_graphics[column][3] = ram_[master_system_.names[column].offset+3]; + line_buffer.patterns[column][0] = ram_[line_buffer.names[column].offset]; \ + line_buffer.patterns[column][1] = ram_[line_buffer.names[column].offset+1]; \ + line_buffer.patterns[column][2] = ram_[line_buffer.names[column].offset+2]; \ + line_buffer.patterns[column][3] = ram_[line_buffer.names[column].offset+3]; #define background_fetch_block(location, column, sprite, row_info) \ slot(location): fetch_tile_name(column, row_info) \ @@ -660,11 +683,12 @@ class Base { slot(location+15): fetch_tile(column+3) // Determine the coarse horizontal scrolling offset; this isn't applied on the first two lines if the programmer has requested it. - const int horizontal_offset = (row_ >= 16 || !master_system_.horizontal_scroll_lock) ? (master_system_.latched_horizontal_scroll >> 3) : 0; + LineBuffer &line_buffer = line_buffers_[0];//write_pointer_ .row & 1]; + const int horizontal_offset = (write_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) ? (line_buffer.latched_horizontal_scroll >> 3) : 0; // Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't. // The programmer can opt out of applying vertical scrolling to the right-hand portion of the display. - const int scrolled_row = (row_ + master_system_.latched_vertical_scroll) % 224; + const int scrolled_row = (write_pointer_.row + master_system_.latched_vertical_scroll) % 224; struct RowInfo { size_t pattern_address_base; size_t sub_row[2]; @@ -675,15 +699,14 @@ class Base { }; RowInfo row_info; if(master_system_.vertical_scroll_lock) { - row_info.pattern_address_base = pattern_name_address_ & static_cast(((row_ & ~7) << 3) | 0x3800); - row_info.sub_row[0] = size_t((row_ & 7) << 2); - row_info.sub_row[1] = 28 ^ size_t((row_ & 7) << 2); + row_info.pattern_address_base = pattern_name_address_ & static_cast(((write_pointer_.row & ~7) << 3) | 0x3800); + row_info.sub_row[0] = size_t((write_pointer_.row & 7) << 2); + row_info.sub_row[1] = 28 ^ size_t((write_pointer_.row & 7) << 2); } else row_info = scrolled_row_info; // ... and do the actual fetching, which follows this routine: switch(start) { - default: - assert(false); + default: assert(false); sprite_fetch_block(0, 0); sprite_fetch_block(6, 2); @@ -695,7 +718,7 @@ class Base { sprite_fetch_block(23, 6); slot(29): - reset_sprite_collection(); + line_buffer.reset_sprite_collection(); do_external_slot(); external_slot(30); From d66979c68f17b1b940b6853fbda2cce1d8b4bc61 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 14 Oct 2018 18:19:11 -0400 Subject: [PATCH 71/88] Switched to a very large number of buffers, and resolved stupid attempt to reassign a reference. --- Components/9918/9918.cpp | 38 +++++++++++++-------- Components/9918/Implementation/9918Base.hpp | 12 +++---- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index c79f44053..f294fc4db 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -193,11 +193,13 @@ void TMS9918::run_for(const HalfCycles cycles) { int read_cycles_pool = int_cycles; while(write_cycles_pool || read_cycles_pool) { + LineBufferPointer backup = read_pointer_; + if(write_cycles_pool) { // Determine how much writing to do. const int write_cycles = std::min(342 - write_pointer_.column, write_cycles_pool); const int end_column = write_pointer_.column + write_cycles; - LineBuffer &line_buffer = line_buffers_[0];//write_pointer_.row & 1]; + LineBuffer &line_buffer = line_buffers_[write_pointer_.row]; @@ -217,7 +219,7 @@ void TMS9918::run_for(const HalfCycles cycles) { // Perform memory accesses. // ------------------------ #define fetch(function) \ - if(final_window < 171) { \ + if(final_window != 171) { \ function(first_window, final_window);\ } else {\ function(first_window, final_window);\ @@ -282,43 +284,47 @@ void TMS9918::run_for(const HalfCycles cycles) { if(write_pointer_.column == 342) { write_pointer_.column = 0; write_pointer_.row = (write_pointer_.row + 1) % mode_timing_.total_lines; - line_buffer = line_buffers_[0];//write_pointer_.row & 1]; + LineBuffer &next_line_buffer = line_buffers_[write_pointer_.row]; // Establish the output mode for the next line. set_current_screen_mode(); // Based on the output mode, pick a line mode. - line_buffer.first_pixel_output_column = 86; - line_buffer.next_border_column = 342; + next_line_buffer.first_pixel_output_column = 86; + next_line_buffer.next_border_column = 342; mode_timing_.maximum_visible_sprites = 4; switch(screen_mode_) { case ScreenMode::Text: - line_buffer.line_mode = LineMode::Text; - line_buffer.first_pixel_output_column = 94; - line_buffer.next_border_column = 334; + next_line_buffer.line_mode = LineMode::Text; + next_line_buffer.first_pixel_output_column = 94; + next_line_buffer.next_border_column = 334; break; case ScreenMode::SMSMode4: - line_buffer.line_mode = LineMode::SMS; + next_line_buffer.line_mode = LineMode::SMS; mode_timing_.maximum_visible_sprites = 8; break; default: - line_buffer.line_mode = LineMode::Character; + next_line_buffer.line_mode = LineMode::Character; break; } if( (screen_mode_ == ScreenMode::Blank) || (write_pointer_.row >= mode_timing_.pixel_lines && write_pointer_.row != mode_timing_.total_lines-1)) - line_buffer.line_mode = LineMode::Refresh; + next_line_buffer.line_mode = LineMode::Refresh; } } + assert(backup.row == read_pointer_.row && backup.column == read_pointer_.column); + backup = write_pointer_; + + if(read_cycles_pool) { // Determine how much time has passed in the remainder of this line, and proceed. const int read_cycles = std::min(342 - read_pointer_.column, read_cycles_pool); const int end_column = read_pointer_.column + read_cycles; - LineBuffer &line_buffer = line_buffers_[0];//read_pointer_.row & 1]; + LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; @@ -425,6 +431,8 @@ void TMS9918::run_for(const HalfCycles cycles) { read_pointer_.row = (read_pointer_.row + 1) % mode_timing_.total_lines; } } + + assert(backup.row == write_pointer_.row && backup.column == write_pointer_.column); } } @@ -820,7 +828,7 @@ void Base::draw_tms_character(int start, int end) { } void Base::draw_tms_text(int start, int end) { - LineBuffer &line_buffer = line_buffers_[0];//read_pointer_.row & 1]; + LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] }; const int shift = start % 6; @@ -844,7 +852,7 @@ void Base::draw_tms_text(int start, int end) { } void Base::draw_sms(int start, int end) { - LineBuffer &line_buffer = line_buffers_[0];//read_pointer_.row & 1]; + LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; int colour_buffer[256]; /* @@ -925,7 +933,7 @@ void Base::draw_sms(int start, int end) { if(line_buffer.active_sprite_slot) { int sprite_buffer[256]; int sprite_collision = 0; - memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(int)); + memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0])); // Draw all sprites into the sprite buffer. for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index fb9da9745..72b8b7ab3 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -212,8 +212,8 @@ class Base { e.g. standard 256-pixel modes will want to set first_pixel_output_column = 86, next_border_column = 342. */ - int first_pixel_output_column; - int next_border_column; + int first_pixel_output_column = 94; + int next_border_column = 334; // An active sprite is one that has been selected for composition onto // this line. @@ -231,7 +231,7 @@ class Base { // being evaluated for display. This flag determines whether the sentinel has yet been reached. void reset_sprite_collection(); - } line_buffers_[2]; + } line_buffers_[313]; void posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_y, int screen_row); // There is a delay between reading into the line buffer and outputting from there to the screen. That delay @@ -455,7 +455,7 @@ class Base { fetch_columns_4(location, column); \ fetch_columns_4(location+12, column+4); - LineBuffer &line_buffer = line_buffers_[0];//write_pointer_.row & 1]; + LineBuffer &line_buffer = line_buffers_[write_pointer_.row]; const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast(write_pointer_.row >> 3) * 40); const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7)); @@ -538,7 +538,7 @@ class Base { slot(location+14): \ slot(location+15): fetch_tile(column+3) - LineBuffer &line_buffer = line_buffers_[0];//write_pointer_.row & 1]; + LineBuffer &line_buffer = line_buffers_[write_pointer_.row]; const size_t row_base = pattern_name_address_ + static_cast((write_pointer_.row << 2)&~31); size_t pattern_base = pattern_generator_table_address_; @@ -683,7 +683,7 @@ class Base { slot(location+15): fetch_tile(column+3) // Determine the coarse horizontal scrolling offset; this isn't applied on the first two lines if the programmer has requested it. - LineBuffer &line_buffer = line_buffers_[0];//write_pointer_ .row & 1]; + LineBuffer &line_buffer = line_buffers_[write_pointer_.row]; const int horizontal_offset = (write_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) ? (line_buffer.latched_horizontal_scroll >> 3) : 0; // Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't. From c1e6406fc9941684ef12487f0688478f9afc8032 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 14 Oct 2018 19:56:09 -0400 Subject: [PATCH 72/88] Corrects sprite accumulation. --- Components/9918/Implementation/9918Base.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 72b8b7ab3..613bc0a0a 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -640,8 +640,8 @@ class Base { #define sprite_y_read(location, sprite) \ slot(location): \ - posit_sprite(line_buffer, sprite, ram_[sprite_attribute_table_address_ & (sprite | 0x3f00)], write_pointer_.row); \ - posit_sprite(line_buffer, sprite+1, ram_[sprite_attribute_table_address_ & ((sprite + 1) | 0x3f00)], write_pointer_.row); \ + posit_sprite(sprite_selection_buffer, sprite, ram_[sprite_attribute_table_address_ & (sprite | 0x3f00)], write_pointer_.row); \ + posit_sprite(sprite_selection_buffer, sprite+1, ram_[sprite_attribute_table_address_ & ((sprite + 1) | 0x3f00)], write_pointer_.row); \ #define fetch_tile_name(column, row_info) {\ const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\ @@ -684,6 +684,7 @@ class Base { // Determine the coarse horizontal scrolling offset; this isn't applied on the first two lines if the programmer has requested it. LineBuffer &line_buffer = line_buffers_[write_pointer_.row]; + LineBuffer &sprite_selection_buffer = line_buffers_[(write_pointer_.row + 1) % mode_timing_.total_lines]; const int horizontal_offset = (write_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) ? (line_buffer.latched_horizontal_scroll >> 3) : 0; // Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't. @@ -718,7 +719,7 @@ class Base { sprite_fetch_block(23, 6); slot(29): - line_buffer.reset_sprite_collection(); + sprite_selection_buffer.reset_sprite_collection(); do_external_slot(); external_slot(30); From 1c5f939aeafd8f083235b580ec377c9b93592e2c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 14 Oct 2018 21:52:13 -0400 Subject: [PATCH 73/88] Reintroduces tiles and some element of sprites in regular TMS mode. --- Components/9918/9918.cpp | 203 +++++++++----------- Components/9918/Implementation/9918Base.hpp | 29 +-- 2 files changed, 105 insertions(+), 127 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index f294fc4db..9b73c3dab 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -695,136 +695,107 @@ bool TMS9918::get_interrupt_line() { // MARK: - +// if(sprite.shift_position > 0 && !sprites_magnified_) +// sprite.shift_position *= 2; + void Base::draw_tms_character(int start, int end) { -// if(!start) printf("\n"); -// printf("%d to %d | ", start, end); -// for(int c = start; c < end; ++c) { -// pixel_target_[c] = static_cast(c * 0x01010101); -// } -/* + LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; - // -------------- - // Output pixels. - // -------------- + // Paint the background tiles. + const int pixels_left = end - start; + if(screen_mode_ == ScreenMode::MultiColour) { + for(int c = start; c < end; ++c) { + pixel_target_[c] = palette[ + (line_buffer.patterns[c >> 3][0] >> (((c & 4)^4))) & 15 + ]; + } + pixel_target_ += pixels_left; + } else { + const int shift = start & 7; + int byte_column = start >> 3; + int length = std::min(pixels_left, 8 - shift); - case LineMode::Character: { - // If this is the start of the visible area, seed sprite shifter positions. - SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1]; - if(output_column_ == first_pixel_column_) { - int c = sprite_set.active_sprite_slot; - while(c--) { - SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; - sprite.shift_position = -sprite.info[1]; - if(sprite.info[3] & 0x80) { - sprite.shift_position += 32; - if(sprite.shift_position > 0 && !sprites_magnified_) - sprite.shift_position *= 2; - } - } - } + int pattern = reverse_table.map[line_buffer.patterns[byte_column][0]] >> shift; + uint8_t colour = line_buffer.patterns[byte_column][1]; + uint32_t colours[2] = { + palette[(colour & 15) ? (colour & 15) : background_colour_], + palette[(colour >> 4) ? (colour >> 4) : background_colour_] + }; - // Paint the background tiles. - const int pixels_left = pixels_end - output_column_; - if(current_mode_ == ScreenMode::MultiColour) { - int pixel_location = output_column_ - first_pixel_column_; - for(int c = 0; c < pixels_left; ++c) { - pixel_target_[c] = palette[ - (pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15 - ]; - } - pixel_target_ += pixels_left; - } else { - const int shift = (output_column_ - first_pixel_column_) & 7; - int byte_column = (output_column_ - first_pixel_column_) >> 3; + int background_pixels_left = pixels_left; + while(true) { + background_pixels_left -= length; + for(int c = 0; c < length; ++c) { + pixel_target_[c] = colours[pattern&0x01]; + pattern >>= 1; + } + pixel_target_ += length; - int length = std::min(pixels_left, 8 - shift); + if(!background_pixels_left) break; + length = std::min(8, background_pixels_left); + byte_column++; - int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift; - uint8_t colour = colour_buffer_[byte_column]; - uint32_t colours[2] = { - palette[(colour & 15) ? (colour & 15) : background_colour_], - palette[(colour >> 4) ? (colour >> 4) : background_colour_] - }; + pattern = reverse_table.map[line_buffer.patterns[byte_column][0]]; + colour = line_buffer.patterns[byte_column][1]; + colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; + colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; + } + } - int background_pixels_left = pixels_left; - while(true) { - background_pixels_left -= length; - for(int c = 0; c < length; ++c) { - pixel_target_[c] = colours[pattern&0x01]; - pattern >>= 1; - } - pixel_target_ += length; + // Paint sprites and check for collisions, but only if at least one sprite is active + // on this line. + if(line_buffer.active_sprite_slot) { + int sprite_buffer[256]; + int sprite_collision = 0; - if(!background_pixels_left) break; - length = std::min(8, background_pixels_left); - byte_column++; + const int shift_advance = sprites_magnified_ ? 1 : 2; - pattern = reverse_table.map[pattern_buffer_[byte_column]]; - colour = colour_buffer_[byte_column]; - colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_]; - colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_]; - } - } + static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; + static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - // Paint sprites and check for collisions, but only if at least one sprite is active - // on this line. - if(sprite_set.active_sprite_slot) { - int sprite_pixels_left = pixels_left; - const int shift_advance = sprites_magnified_ ? 1 : 2; + // Draw all sprites into the sprite buffer. + for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { + LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; + if(sprite.shift_position < 16) { + const int pixel_start = std::max(start, sprite.x); + const int shifter_target = sprites_16x16_ ? 32 : 16; + for(int c = pixel_start; c < end && sprite.shift_position < shifter_target; ++c) { + pixel_origin_[c] = 0xffffffff; +// const int shift = (sprite.shift_position >> 1); +// const int sprite_colour = ((sprite.image[0] << shift) & 0x80) >> 7) & 1; +// +// if(sprite_colour) { +// sprite_collision |= sprite_buffer[c]; +// sprite_buffer[c] = sprite_colour | 0x10; +// } - static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; - static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + sprite.shift_position += shift_advance; + } + } + } - while(sprite_pixels_left--) { - // sprite_colour is the colour that's going to reach the display after sprite logic has been - // applied; by default assume that nothing is going to be drawn. - uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_]; + status_ |= sprite_collision << StatusSpriteCollisionShift; - // The sprite_mask is used to keep track of whether two sprites have both sought to output - // a pixel at the same location, and to feed that into the status register's sprite - // collision bit. - int sprite_mask = 0; - - int c = sprite_set.active_sprite_slot; - while(c--) { - SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c]; - - if(sprite.shift_position < 0) { - sprite.shift_position++; - continue; - } else if(sprite.shift_position < 32) { - int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); - mask = (mask >> 7) & 1; - - // Ignore the right half of whatever was collected if sprites are not in 16x16 mode. - if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) { - // If any previous sprite has been painted in this column and this sprite - // has this pixel set, set the sprite collision status bit. - status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; - sprite_mask |= mask; - - // Check that the sprite colour is not transparent - mask &= colour_masks[sprite.info[3]&15]; - sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); - } - - sprite.shift_position += shift_advance; - } - } - - // Output whichever sprite colour was on top. - pixel_base_[output_column_ - first_pixel_column_] = sprite_colour; - output_column_++; - } - } - - output_column_ = pixels_end; - } break; - } - - - }*/ +// int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); +// mask = (mask >> 7) & 1; +// +// // Ignore the right half of whatever was collected if sprites are not in 16x16 mode. +// if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) { +// // If any previous sprite has been painted in this column and this sprite +// // has this pixel set, set the sprite collision status bit. +// status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; +// sprite_mask |= mask; +// +// // Check that the sprite colour is not transparent +// mask &= colour_masks[sprite.info[3]&15]; +// sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); +// } +// +// sprite.shift_position += shift_advance; +// } +// } + } } void Base::draw_tms_text(int start, int end) { diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 613bc0a0a..ea56329c8 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -495,6 +495,12 @@ class Base { #define sprite_fetch_coordinates(location, sprite) \ slot(location): \ slot(location+1): \ + line_buffer.active_sprites[sprite].x = \ + ram_[\ + sprite_attribute_table_address_ & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 2))\ + ]; + +//- ( ? 8 : 0) #define sprite_fetch_graphics(location, sprite) \ slot(location): \ @@ -503,21 +509,17 @@ class Base { slot(location+3): \ #define sprite_fetch_block(location, sprite) \ - slot(location): \ - slot(location+1): \ - slot(location+2): \ - slot(location+3): \ - slot(location+4): \ - slot(location+5): + sprite_fetch_coordinates(location, sprite) \ + sprite_fetch_graphics(location+2, sprite) #define sprite_y_read(location, sprite) \ - slot(location): + slot(location): posit_sprite(sprite_selection_buffer, sprite, ram_[sprite_attribute_table_address_ & (((sprite) << 2) | 0x3f80)], write_pointer_.row); #define fetch_tile_name(column) line_buffer.names[column].offset = ram_[(row_base + column) & 0x3fff]; #define fetch_tile(column) {\ - line_buffer.patterns[column][0] = ram_[(colour_base + static_cast((line_buffer.names[column].offset << 3) >> colour_name_shift)) & 0x3fff]; \ - line_buffer.patterns[column][1] = ram_[(pattern_base + static_cast(line_buffer.names[column].offset << 3)) & 0x3fff]; \ + line_buffer.patterns[column][1] = ram_[(colour_base + static_cast((line_buffer.names[column].offset << 3) >> colour_name_shift)) & 0x3fff]; \ + line_buffer.patterns[column][0] = ram_[(pattern_base + static_cast(line_buffer.names[column].offset << 3)) & 0x3fff]; \ } #define background_fetch_block(location, column) \ @@ -539,7 +541,8 @@ class Base { slot(location+15): fetch_tile(column+3) LineBuffer &line_buffer = line_buffers_[write_pointer_.row]; - const size_t row_base = pattern_name_address_ + static_cast((write_pointer_.row << 2)&~31); + LineBuffer &sprite_selection_buffer = line_buffers_[(write_pointer_.row + 1) % mode_timing_.total_lines]; + const size_t row_base = pattern_name_address_ & (static_cast((write_pointer_.row << 2)&~31) | 0x3c00); size_t pattern_base = pattern_generator_table_address_; size_t colour_base = colour_table_address_; @@ -575,7 +578,11 @@ class Base { sprite_fetch_graphics(21, 2); sprite_fetch_block(25, 3); - external_slots_4(31); + slot(31): + sprite_selection_buffer.reset_sprite_collection(); + do_external_slot(); + external_slots_2(32); + external_slot(34); sprite_y_read(35, 0); sprite_y_read(36, 1); From 231009b9010acdaec287d90db55dc954f90ea720 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Oct 2018 20:00:06 -0400 Subject: [PATCH 74/88] Makes faulty attempt to reintroduce TMS-mode sprites. --- Components/9918/9918.cpp | 40 ++++++--------------- Components/9918/Implementation/9918Base.hpp | 16 +++++++-- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 9b73c3dab..2f8c4c7df 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -748,27 +748,28 @@ void Base::draw_tms_character(int start, int end) { if(line_buffer.active_sprite_slot) { int sprite_buffer[256]; int sprite_collision = 0; - - const int shift_advance = sprites_magnified_ ? 1 : 2; + memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0])); static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff}; static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; // Draw all sprites into the sprite buffer. + const int shift_advance = sprites_magnified_ ? 1 : 2; for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; if(sprite.shift_position < 16) { const int pixel_start = std::max(start, sprite.x); const int shifter_target = sprites_16x16_ ? 32 : 16; for(int c = pixel_start; c < end && sprite.shift_position < shifter_target; ++c) { - pixel_origin_[c] = 0xffffffff; -// const int shift = (sprite.shift_position >> 1); -// const int sprite_colour = ((sprite.image[0] << shift) & 0x80) >> 7) & 1; -// -// if(sprite_colour) { -// sprite_collision |= sprite_buffer[c]; -// sprite_buffer[c] = sprite_colour | 0x10; -// } + const int shift = (sprite.shift_position >> 1) ^ 7; + int sprite_colour = (sprite.image[shift >> 3] >> (shift & 7)) & 1; + + sprite_collision |= sprite_buffer[c] & sprite_colour; + sprite_buffer[c] |= sprite_colour; + + sprite_colour &= colour_masks[sprite.image[3]&15]; + pixel_origin_[c] = + (pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) | (palette[sprite.image[3]&15] & sprite_colour_selection_masks[sprite_colour]); sprite.shift_position += shift_advance; } @@ -776,25 +777,6 @@ void Base::draw_tms_character(int start, int end) { } status_ |= sprite_collision << StatusSpriteCollisionShift; - -// int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1); -// mask = (mask >> 7) & 1; -// -// // Ignore the right half of whatever was collected if sprites are not in 16x16 mode. -// if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) { -// // If any previous sprite has been painted in this column and this sprite -// // has this pixel set, set the sprite collision status bit. -// status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift; -// sprite_mask |= mask; -// -// // Check that the sprite colour is not transparent -// mask &= colour_masks[sprite.info[3]&15]; -// sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]); -// } -// -// sprite.shift_position += shift_advance; -// } -// } } } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index ea56329c8..c99c757f6 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -500,13 +500,25 @@ class Base { sprite_attribute_table_address_ & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 2))\ ]; -//- ( ? 8 : 0) + // This implementation doesn't refetch Y; it's unclear to me + // whether it's refetched. #define sprite_fetch_graphics(location, sprite) \ slot(location): \ slot(location+1): \ slot(location+2): \ - slot(location+3): \ + slot(location+3): {\ + const uint8_t name = ram_[\ + sprite_attribute_table_address_ & size_t(0x3f82 | (line_buffer.active_sprites[sprite].index << 2))\ + ] & (sprites_16x16_ ? ~1 : ~0);\ + line_buffer.active_sprites[sprite].image[2] = ram_[\ + sprite_attribute_table_address_ & size_t(0x3f83 | (line_buffer.active_sprites[sprite].index << 2))\ + ];\ + line_buffer.active_sprites[sprite].x -= (line_buffer.active_sprites[sprite].image[2] & 0x80) >> 2;\ + const size_t graphic_location = sprite_generator_table_address_ & size_t(0x2000 | (name << 3) | (line_buffer.active_sprites[sprite].row << 1)); \ + line_buffer.active_sprites[sprite].image[0] = ram_[graphic_location];\ + line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+1];\ + } #define sprite_fetch_block(location, sprite) \ sprite_fetch_coordinates(location, sprite) \ From 5dfe7d8596a81baf6a5b123a62050b3876e70bbb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Oct 2018 20:49:04 -0400 Subject: [PATCH 75/88] Corrects most of TMS sprite drawing. --- Components/9918/9918.cpp | 38 ++------------------- Components/9918/Implementation/9918Base.hpp | 6 ++-- 2 files changed, 5 insertions(+), 39 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 2f8c4c7df..d92709762 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -118,7 +118,6 @@ void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_positi if(buffer.sprites_stopped) return; -// const int sprite_position = ram_[sprite_attribute_table_address_ + static_cast(sprite_number << 2)]; // A sprite Y of 208 means "don't scan the list any further". if(mode_timing_.allow_sprite_terminator && sprite_position == 208) { buffer.sprites_stopped = true; @@ -139,39 +138,6 @@ void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_positi ++buffer.active_sprite_slot; } -//void Base::get_sprite_contents(int field, int cycles_left, int screen_row) { -/* int sprite_id = field / 6; - field %= 6; - - while(true) { - const int cycles_in_sprite = std::min(cycles_left, 6 - field); - cycles_left -= cycles_in_sprite; - const int final_field = cycles_in_sprite + field; - - assert(sprite_id < 4); - SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[sprite_id]; - - if(field < 4) { - std::memcpy( - &sprite.info[field], - &ram_[sprite_attribute_table_address_ + static_cast((sprite.index << 2) + field)], - static_cast(std::min(4, final_field) - field)); - } - - field = std::min(4, final_field); - const int sprite_offset = sprite.info[2] & ~(sprites_16x16_ ? 3 : 0); - const size_t sprite_address = sprite_generator_table_address_ + static_cast(sprite_offset << 3) + sprite.row; // TODO: recalclate sprite.row from screen_row (?) - while(field < final_field) { - sprite.image[field - 4] = ram_[sprite_address + static_cast(((field - 4) << 4))]; - field++; - } - - if(!cycles_left) return; - field = 0; - sprite_id++; - }*/ -//} - void TMS9918::run_for(const HalfCycles cycles) { // As specific as I've been able to get: // Scanline time is always 228 cycles. @@ -767,9 +733,9 @@ void Base::draw_tms_character(int start, int end) { sprite_collision |= sprite_buffer[c] & sprite_colour; sprite_buffer[c] |= sprite_colour; - sprite_colour &= colour_masks[sprite.image[3]&15]; + sprite_colour &= colour_masks[sprite.image[2]&15]; pixel_origin_[c] = - (pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) | (palette[sprite.image[3]&15] & sprite_colour_selection_masks[sprite_colour]); + (pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) | (palette[sprite.image[2]&15] & sprite_colour_selection_masks[sprite_colour]); sprite.shift_position += shift_advance; } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index c99c757f6..57e05ca60 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -510,14 +510,14 @@ class Base { slot(location+3): {\ const uint8_t name = ram_[\ sprite_attribute_table_address_ & size_t(0x3f82 | (line_buffer.active_sprites[sprite].index << 2))\ - ] & (sprites_16x16_ ? ~1 : ~0);\ + ] & (sprites_16x16_ ? ~3 : ~0);\ line_buffer.active_sprites[sprite].image[2] = ram_[\ sprite_attribute_table_address_ & size_t(0x3f83 | (line_buffer.active_sprites[sprite].index << 2))\ ];\ line_buffer.active_sprites[sprite].x -= (line_buffer.active_sprites[sprite].image[2] & 0x80) >> 2;\ - const size_t graphic_location = sprite_generator_table_address_ & size_t(0x2000 | (name << 3) | (line_buffer.active_sprites[sprite].row << 1)); \ + const size_t graphic_location = sprite_generator_table_address_ & size_t(0x3800 | (name << 3) | line_buffer.active_sprites[sprite].row); \ line_buffer.active_sprites[sprite].image[0] = ram_[graphic_location];\ - line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+1];\ + line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+16];\ } #define sprite_fetch_block(location, sprite) \ From 0f7bf6d6c634ff99cb637296044f8e47c9483aaf Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Oct 2018 21:02:31 -0400 Subject: [PATCH 76/88] Resolves attempt to output graphics on the line one before the display. --- Components/9918/9918.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index d92709762..07280ac28 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -307,7 +307,7 @@ void TMS9918::run_for(const HalfCycles cycles) { }\ } - if(line_buffer.line_mode == LineMode::Refresh) { + if(line_buffer.line_mode == LineMode::Refresh || read_pointer_.row > mode_timing_.pixel_lines) { if(read_pointer_.row >= mode_timing_.first_vsync_line && read_pointer_.row < mode_timing_.first_vsync_line+4) { // Vertical sync. if(end_column == 342) { From b4f871a2ef44951aa21eab92902151c015330e39 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Oct 2018 21:16:29 -0400 Subject: [PATCH 77/88] Corrects first line sprite row selection. --- Components/9918/9918.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 07280ac28..c6c9a61e2 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -124,7 +124,7 @@ void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_positi return; } - const int sprite_row = (screen_row - sprite_position)&255; + const int sprite_row = ((screen_row + 1) % mode_timing_.total_lines) - ((sprite_position + 1) & 255); if(sprite_row < 0 || sprite_row >= sprite_height_) return; if(buffer.active_sprite_slot == mode_timing_.maximum_visible_sprites) { From 58ca74c68a8fa885596c4ffcf591ae43b73666c2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Oct 2018 21:25:08 -0400 Subject: [PATCH 78/88] Resolves right-side TMS sprite droppages. --- Components/9918/9918.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index c6c9a61e2..b62d91e93 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -723,9 +723,9 @@ void Base::draw_tms_character(int start, int end) { const int shift_advance = sprites_magnified_ ? 1 : 2; for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; - if(sprite.shift_position < 16) { + const int shifter_target = sprites_16x16_ ? 32 : 16; + if(sprite.shift_position < shifter_target) { const int pixel_start = std::max(start, sprite.x); - const int shifter_target = sprites_16x16_ ? 32 : 16; for(int c = pixel_start; c < end && sprite.shift_position < shifter_target; ++c) { const int shift = (sprite.shift_position >> 1) ^ 7; int sprite_colour = (sprite.image[shift >> 3] >> (shift & 7)) & 1; From 1fc88c4effae7ae7f302e33369c54595b42981f7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 16 Oct 2018 21:36:31 -0400 Subject: [PATCH 79/88] Corrects off-by-one error in line fetching coroutines. --- Components/9918/9918.cpp | 7 ++++-- Components/9918/Implementation/9918Base.hpp | 26 ++++++++++----------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index b62d91e93..d4cff3c07 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -721,21 +721,24 @@ void Base::draw_tms_character(int start, int end) { // Draw all sprites into the sprite buffer. const int shift_advance = sprites_magnified_ ? 1 : 2; + const int shifter_target = sprites_16x16_ ? 32 : 16; for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; - const int shifter_target = sprites_16x16_ ? 32 : 16; if(sprite.shift_position < shifter_target) { const int pixel_start = std::max(start, sprite.x); for(int c = pixel_start; c < end && sprite.shift_position < shifter_target; ++c) { const int shift = (sprite.shift_position >> 1) ^ 7; int sprite_colour = (sprite.image[shift >> 3] >> (shift & 7)) & 1; + // A colision is detected regardless of sprite colour ... sprite_collision |= sprite_buffer[c] & sprite_colour; sprite_buffer[c] |= sprite_colour; + // ... but a sprite with the transparent colour won't actually be visible. sprite_colour &= colour_masks[sprite.image[2]&15]; pixel_origin_[c] = - (pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) | (palette[sprite.image[2]&15] & sprite_colour_selection_masks[sprite_colour]); + (pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) | + (palette[sprite.image[2]&15] & sprite_colour_selection_masks[sprite_colour]); sprite.shift_position += shift_advance; } diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 57e05ca60..c3a1573aa 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -359,7 +359,7 @@ class Base { */ #define slot(n) \ - if(use_end && end+1 == n) return;\ + if(use_end && end == n) return;\ case n #define external_slot(n) \ @@ -534,21 +534,21 @@ class Base { line_buffer.patterns[column][0] = ram_[(pattern_base + static_cast(line_buffer.names[column].offset << 3)) & 0x3fff]; \ } -#define background_fetch_block(location, column) \ +#define background_fetch_block(location, column, sprite) \ slot(location): fetch_tile_name(column) \ external_slot(location+1); \ slot(location+2): \ slot(location+3): fetch_tile(column) \ slot(location+4): fetch_tile_name(column+1) \ - sprite_y_read(location+5, column+8); \ + sprite_y_read(location+5, sprite); \ slot(location+6): \ slot(location+7): fetch_tile(column+1) \ slot(location+8): fetch_tile_name(column+2) \ - sprite_y_read(location+9, column+9); \ + sprite_y_read(location+9, sprite+1); \ slot(location+10): \ slot(location+11): fetch_tile(column+2) \ slot(location+12): fetch_tile_name(column+3) \ - sprite_y_read(location+13, column+10); \ + sprite_y_read(location+13, sprite+2); \ slot(location+14): \ slot(location+15): fetch_tile(column+3) @@ -605,14 +605,14 @@ class Base { sprite_y_read(41, 6); sprite_y_read(42, 7); - background_fetch_block(43, 0); - background_fetch_block(59, 4); - background_fetch_block(75, 8); - background_fetch_block(91, 12); - background_fetch_block(107, 16); - background_fetch_block(123, 20); - background_fetch_block(139, 24); - background_fetch_block(155, 28); + background_fetch_block(43, 0, 8); + background_fetch_block(59, 4, 11); + background_fetch_block(75, 8, 14); + background_fetch_block(91, 12, 17); + background_fetch_block(107, 16, 20); + background_fetch_block(123, 20, 23); + background_fetch_block(139, 24, 26); + background_fetch_block(155, 28, 29); return; } From 0278d5b61ce170a84533fda47ceea8c1c1c8134a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Oct 2018 19:13:15 -0400 Subject: [PATCH 80/88] Restores SG1000 compatibility. --- Machines/MasterSystem/MasterSystem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 63c179126..e3108043a 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -360,8 +360,8 @@ class ConcreteMachine: map(read_pointers_, nullptr, 0xc000, 0x0000); } - // Throw the BIOS on top if it isn't disabled. - if(!(memory_control_ & 0x08)) { + // Throw the BIOS on top if this machine has one and it isn't disabled. + if(model_ == Analyser::Static::Sega::Target::Model::MasterSystem && !(memory_control_ & 0x08)) { map(read_pointers_, bios_, 8*1024, 0); } } From 8ff265c3a199244f488f7f1c0275ee9aec56decb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Oct 2018 20:25:42 -0400 Subject: [PATCH 81/88] Corrects multicolour text mode. --- Components/9918/9918.cpp | 1 - Components/9918/Implementation/9918Base.hpp | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index d4cff3c07..7d41b5af6 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -605,7 +605,6 @@ HalfCycles Base::half_cycles_before_internal_cycles(int internal_cycles) { return HalfCycles(((internal_cycles << 2) - cycles_error_) / 3); } - HalfCycles TMS9918::get_time_until_interrupt() { if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles(-1); if(get_interrupt_line()) return HalfCycles(0); diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index c3a1573aa..c0c310bac 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -554,7 +554,7 @@ class Base { LineBuffer &line_buffer = line_buffers_[write_pointer_.row]; LineBuffer &sprite_selection_buffer = line_buffers_[(write_pointer_.row + 1) % mode_timing_.total_lines]; - const size_t row_base = pattern_name_address_ & (static_cast((write_pointer_.row << 2)&~31) | 0x3c00); + const size_t row_base = pattern_name_address_ & (size_t((write_pointer_.row << 2)&~31) | 0x3c00); size_t pattern_base = pattern_generator_table_address_; size_t colour_base = colour_table_address_; @@ -562,17 +562,20 @@ class Base { if(screen_mode_ == ScreenMode::Graphics) { // If this is high resolution mode, allow the row number to affect the pattern and colour addresses. - pattern_base &= static_cast(0x2000 | ((write_pointer_.row & 0xc0) << 5)); - colour_base &= static_cast(0x2000 | ((write_pointer_.row & 0xc0) << 5)); + pattern_base &= size_t(0x2000 | ((write_pointer_.row & 0xc0) << 5)); + colour_base &= size_t(0x2000 | ((write_pointer_.row & 0xc0) << 5)); - colour_base += static_cast(write_pointer_.row & 7); + colour_base += size_t(write_pointer_.row & 7); colour_name_shift = 0; + } else { + colour_base &= size_t(0xffc0 | (write_pointer_.row & 7)); + pattern_base &= size_t(0x3800); } if(screen_mode_ == ScreenMode::MultiColour) { - pattern_base += static_cast((write_pointer_.row >> 2) & 7); + pattern_base += size_t((write_pointer_.row >> 2) & 7); } else { - pattern_base += static_cast(write_pointer_.row & 7); + pattern_base += size_t(write_pointer_.row & 7); } switch(start) { From da00c832f5c61647b716dd92df3f68ec67ae3583 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Oct 2018 20:38:00 -0400 Subject: [PATCH 82/88] Corrects colour fetching for multicolour text mode. --- Components/9918/9918.cpp | 2 +- Components/9918/Implementation/9918Base.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 7d41b5af6..9a5e20643 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -489,7 +489,7 @@ void TMS9918::set_register(int address, uint8_t value) { break; case 3: - colour_table_address_ = size_t(low_write_ << 6) | 0x1f; + colour_table_address_ = size_t(low_write_ << 6) | 0x3f; break; case 4: diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index c0c310bac..77add1839 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -530,8 +530,8 @@ class Base { #define fetch_tile_name(column) line_buffer.names[column].offset = ram_[(row_base + column) & 0x3fff]; #define fetch_tile(column) {\ - line_buffer.patterns[column][1] = ram_[(colour_base + static_cast((line_buffer.names[column].offset << 3) >> colour_name_shift)) & 0x3fff]; \ - line_buffer.patterns[column][0] = ram_[(pattern_base + static_cast(line_buffer.names[column].offset << 3)) & 0x3fff]; \ + line_buffer.patterns[column][1] = ram_[(colour_base + size_t((line_buffer.names[column].offset << 3) >> colour_name_shift)) & 0x3fff]; \ + line_buffer.patterns[column][0] = ram_[(pattern_base + size_t(line_buffer.names[column].offset << 3)) & 0x3fff]; \ } #define background_fetch_block(location, column, sprite) \ @@ -568,7 +568,7 @@ class Base { colour_base += size_t(write_pointer_.row & 7); colour_name_shift = 0; } else { - colour_base &= size_t(0xffc0 | (write_pointer_.row & 7)); + colour_base &= size_t(0xffc0); pattern_base &= size_t(0x3800); } From 34e13d0d4dc13624f5326158e9e05697627352f5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Oct 2018 21:05:58 -0400 Subject: [PATCH 83/88] Clears top bit when reading the keypad and ensures no undefined behaviour reading the cartridge. --- Machines/ColecoVision/ColecoVision.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 1b0958817..a690bf3d4 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -100,7 +100,7 @@ class Joystick: public Inputs::ConcreteJoystick { private: uint8_t direction_ = 0xff; - uint8_t keypad_ = 0xff; + uint8_t keypad_ = 0x7f; }; class ConcreteMachine: @@ -145,6 +145,7 @@ class ConcreteMachine: cartridge_pages_[1] = cartridge_.data(); is_megacart_ = true; } else { + cartridge_.resize(32768); cartridge_pages_[0] = cartridge_.data(); cartridge_pages_[1] = cartridge_.data() + 16384; is_megacart_ = false; From 6a2db52adbe6e687880d59c3065a999362681aae Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Oct 2018 21:09:05 -0400 Subject: [PATCH 84/88] Ensures safe Megacart cartridge sizes too. --- Machines/ColecoVision/ColecoVision.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index a690bf3d4..5f071307e 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -141,11 +141,19 @@ class ConcreteMachine: cartridge_address_limit_ = static_cast(0x8000 + cartridge_.size() - 1); if(cartridge_.size() > 32768) { + // Ensure the cartrige is a multiple of 16kb in size, as that won't + // be checked when paging. + const size_t extension = (16384 - cartridge_.size() & 16383) % 16384; + cartridge_.resize(cartridge_.size() + extension); + cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384]; cartridge_pages_[1] = cartridge_.data(); is_megacart_ = true; } else { + // Ensure at least 32kb is allocated to the cartrige so that + // reads are never out of bounds. cartridge_.resize(32768); + cartridge_pages_[0] = cartridge_.data(); cartridge_pages_[1] = cartridge_.data() + 16384; is_megacart_ = false; From 512f085891838bbab8d142062e2388dd0d24cc68 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Oct 2018 21:14:16 -0400 Subject: [PATCH 85/88] Ensures proper left clipping of sprites. --- Components/9918/9918.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 9a5e20643..554d39416 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -711,6 +711,15 @@ void Base::draw_tms_character(int start, int end) { // Paint sprites and check for collisions, but only if at least one sprite is active // on this line. if(line_buffer.active_sprite_slot) { + const int shift_advance = sprites_magnified_ ? 1 : 2; + // If this is the start of the line clip any part of any sprites that is off to the left. + if(!start) { + for(int index = 0; index < line_buffer.active_sprite_slot; ++index) { + LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; + if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x; + } + } + int sprite_buffer[256]; int sprite_collision = 0; memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0])); @@ -719,7 +728,6 @@ void Base::draw_tms_character(int start, int end) { static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; // Draw all sprites into the sprite buffer. - const int shift_advance = sprites_magnified_ ? 1 : 2; const int shifter_target = sprites_16x16_ ? 32 : 16; for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) { LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; @@ -852,6 +860,16 @@ void Base::draw_sms(int start, int end) { Apply sprites (if any). */ if(line_buffer.active_sprite_slot) { + const int shift_advance = sprites_magnified_ ? 1 : 2; + + // If this is the start of the line clip any part of any sprites that is off to the left. + if(!start) { + for(int index = 0; index < line_buffer.active_sprite_slot; ++index) { + LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index]; + if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x; + } + } + int sprite_buffer[256]; int sprite_collision = 0; memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0])); @@ -877,7 +895,7 @@ void Base::draw_sms(int start, int end) { sprite_buffer[c] = sprite_colour | 0x10; } - sprite.shift_position += sprites_magnified_ ? 1 : 2; + sprite.shift_position += shift_advance; } } } From 57f03e660c3ec6ac36a41608a9787d4817b3e117 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Oct 2018 21:16:56 -0400 Subject: [PATCH 86/88] Ensures console output only in debug builds. --- Machines/MasterSystem/MasterSystem.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index e3108043a..bc3306686 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -19,6 +19,7 @@ #include "../../ClockReceiver/ForceInline.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../Outputs/Log.hpp" #include "../../Analyser/Static/Sega/Target.hpp" @@ -177,11 +178,11 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Input: switch(address & 0xc1) { case 0x00: - printf("TODO: [input] memory control\n"); + LOG("TODO: [input] memory control"); *cycle.value = 0xff; break; case 0x01: - printf("TODO: [input] I/O port control\n"); + LOG("TODO: [input] I/O port control"); *cycle.value = 0xff; break; case 0x40: @@ -212,7 +213,7 @@ class ConcreteMachine: } break; default: - printf("[input] Clearly some sort of typo\n"); + ERROR("[input] Clearly some sort of typo"); break; } break; @@ -251,14 +252,14 @@ class ConcreteMachine: time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 0xc0: - printf("TODO: [output] I/O port A/N [%02x]\n", *cycle.value); + LOG("TODO: [output] I/O port A/N; " << *cycle.value); break; case 0xc1: - printf("TODO: [output] I/O port B/misc\n"); + LOG("TODO: [output] I/O port B/misc"); break; default: - printf("[output] Clearly some sort of typo\n"); + ERROR("[output] Clearly some sort of typo"); break; } break; From 10d9cbdeb1a0eb6e86e26ba8f52d19ac4edb6d13 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Oct 2018 21:48:37 -0400 Subject: [PATCH 87/88] Adds an extra LOG to track the memory map as a potential cause of emulation failure. --- Machines/MasterSystem/MasterSystem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index bc3306686..e2cb42463 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -173,6 +173,7 @@ class ConcreteMachine: } if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value; + else LOG("Ignored write to ROM"); break; case CPU::Z80::PartialMachineCycle::Input: From 9bc09046c0a15012c042ccb20ab742ef97ed4358 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Oct 2018 21:48:57 -0400 Subject: [PATCH 88/88] Attempts to ensure that sprites can go off the top of the screen. --- Components/9918/9918.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 554d39416..e4ae897a5 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -124,7 +124,7 @@ void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_positi return; } - const int sprite_row = ((screen_row + 1) % mode_timing_.total_lines) - ((sprite_position + 1) & 255); + const int sprite_row = (((screen_row + 1) % mode_timing_.total_lines) - ((sprite_position + 1) & 255)) & 255; if(sprite_row < 0 || sprite_row >= sprite_height_) return; if(buffer.active_sprite_slot == mode_timing_.maximum_visible_sprites) {