From 41d57e03a67a7537d99b00edb3aab1230ffa99c1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 12 Feb 2023 12:58:46 -0500 Subject: [PATCH] Split out LineBuffer and Storage to make 9918Base more manageable. --- Components/9918/Implementation/9918Base.hpp | 377 +----------------- Components/9918/Implementation/LineBuffer.hpp | 103 +++++ Components/9918/Implementation/Storage.hpp | 311 +++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 4 + 4 files changed, 420 insertions(+), 375 deletions(-) create mode 100644 Components/9918/Implementation/LineBuffer.hpp create mode 100644 Components/9918/Implementation/Storage.hpp diff --git a/Components/9918/Implementation/9918Base.hpp b/Components/9918/Implementation/9918Base.hpp index 9c10aeafd..4a4b34454 100644 --- a/Components/9918/Implementation/9918Base.hpp +++ b/Components/9918/Implementation/9918Base.hpp @@ -16,7 +16,9 @@ #include "../../../Outputs/CRT/CRT.hpp" #include "AccessEnums.hpp" +#include "LineBuffer.hpp" #include "PersonalityTraits.hpp" +#include "Storage.hpp" #include "YamahaCommands.hpp" #include @@ -29,387 +31,12 @@ namespace TI { namespace TMS { -enum class VerticalState { - /// Describes any line on which pixels do not appear and no fetching occurs, including - /// the border, blanking and sync. - Blank, - /// A line on which pixels do not appear but fetching occurs. - Prefetch, - /// A line on which pixels appear and fetching occurs. - Pixels, -}; - -// Temporary buffers collect a representation of each line prior to pixel serialisation. -struct LineBuffer { - LineBuffer() {} - - // The fetch mode describes the proper timing diagram for this line; - // screen mode captures proper output mode. - FetchMode fetch_mode = FetchMode::Text; - ScreenMode screen_mode = ScreenMode::Text; - VerticalState vertical_state = VerticalState::Blank; - - // 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. - union { - // The TMS and Sega VDPs are close enough to always tile-based; - // this struct captures maximal potential detail there. - struct { - uint8_t flags[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]{}; - }; - - // The Yamaha VDP also has a variety of bitmap modes, - // the widest of which is 512px @ 4bpp. - uint8_t bitmap[256]; - }; - - /* - 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 = 94; - int next_border_column = 334; - int pixel_count = 256; - - // 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(); -}; - -struct LineBufferPointer { - int row, column; -}; - constexpr uint8_t StatusInterrupt = 0x80; constexpr uint8_t StatusSpriteOverflow = 0x40; constexpr int StatusSpriteCollisionShift = 5; constexpr uint8_t StatusSpriteCollision = 0x20; -/// A container for personality-specific storage; see specific instances below. -template struct Storage { -}; - -template <> struct Storage { - using AddressT = uint16_t; - - void begin_line(ScreenMode, bool, bool) {} -}; - -// Yamaha-specific storage. -template struct Storage> { - using AddressT = uint32_t; - - int selected_status_ = 0; - - int indirect_register_ = 0; - bool increment_indirect_register_ = false; - - std::array palette_{}; - uint8_t new_colour_ = 0; - uint8_t palette_entry_ = 0; - bool palette_write_phase_ = false; - - uint8_t mode_ = 0; - - uint8_t vertical_offset_ = 0; - - /// Describes an _observable_ memory access event. i.e. anything that it is safe - /// (and convenient) to treat as atomic in between external slots. - struct Event { - /// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when - /// external data must be ready by in order to take part in those slots. - int offset = 1368; - enum class Type { - External, - DataBlock, - SpriteY, - SpriteContents, - } type = Type::External; - - constexpr Event(int offset, Type type) noexcept : - offset(grauw_to_internal(offset)), - type(type) {} - - constexpr Event(int offset) noexcept : - offset(grauw_to_internal(offset)) {} - - constexpr Event() noexcept {} - }; - - // State that tracks fetching position within a line. - const Event *next_event_ = nullptr; - int data_block_ = 0; - int sprite_block_ = 0; - - /// Resets line-ephemeral state for a new line. - void begin_line([[maybe_unused]] ScreenMode mode, bool is_refresh, [[maybe_unused]] bool sprites_enabled) { - // TODO: reinstate upon completion of the Yamaha pipeline. -// assert(mode < ScreenMode::YamahaText80 || next_event_ == nullptr || next_event_->offset == 1368); - - data_block_ = 0; - sprite_block_ = 0; - - if(is_refresh) { - next_event_ = refresh_events; - return; - } - - // TODO: obey sprites_enabled flag, at least. - next_event_ = no_sprites_events; - } - - // Command engine state. - CommandContext command_context_; - std::unique_ptr command_ = nullptr; - - enum class CommandStep { - None, - - ReadSourcePixel, - ReadDestinationPixel, - WritePixel, - - ReadSourceByte, - WriteByte, - }; - CommandStep next_command_step_ = CommandStep::None; - int minimum_command_column_ = 0; - uint8_t command_latch_ = 0; - - void update_command_step(int current_column) { - if(!command_) { - next_command_step_ = CommandStep::None; - return; - } - if(command_->done()) { - command_ = nullptr; - next_command_step_ = CommandStep::None; - return; - } - - minimum_command_column_ = current_column + command_->cycles; - switch(command_->access) { - case Command::AccessType::CopyPoint: - next_command_step_ = CommandStep::ReadSourcePixel; - break; - case Command::AccessType::PlotPoint: - next_command_step_ = CommandStep::ReadDestinationPixel; - break; - - case Command::AccessType::WaitForColourReceipt: - // i.e. nothing to do until a colour is received. - next_command_step_ = CommandStep::None; - break; - - case Command::AccessType::CopyByte: - next_command_step_ = CommandStep::ReadSourceByte; - break; - case Command::AccessType::WriteByte: - next_command_step_ = CommandStep::WriteByte; - break; - } - } - - Storage() noexcept { - // Perform sanity checks on the event lists. -#ifndef NDEBUG - const Event *lists[] = { no_sprites_events, refresh_events, nullptr }; - const Event **list = lists; - while(*list) { - const Event *cursor = *list; - ++list; - - while(cursor[1].offset != 1368) { - assert(cursor[1].offset > cursor[0].offset); - ++cursor; - } - } -#endif - - // Seed to _something_ meaningful. - // - // TODO: this is a workaround [/hack], in effect, for the main TMS' habit of starting - // in a randomised position, which means that start-of-line isn't announced. - // - // Do I really want that behaviour? - next_event_ = refresh_events; - } - - private: - // This emulator treats position 0 as being immediately after the standard pixel area. - // i.e. offset 1282 on Grauw's http://map.grauw.nl/articles/vdp-vram-timing/vdp-timing.png - constexpr static int grauw_to_internal(int offset) { - return (offset + 1368 - 1282) % 1368; - } - - static constexpr Event refresh_events[] = { - Event(1284), Event(1292), Event(1300), Event(1308), Event(1316), Event(1324), - Event(1334), Event(1344), Event(1352), Event(1360), Event(0), Event(8), - Event(16), Event(24), Event(32), Event(40), Event(48), Event(56), - Event(64), Event(72), Event(80), Event(88), Event(96), Event(104), - Event(112), Event(120), - - Event(164), Event(172), Event(180), Event(188), Event(196), Event(204), - Event(212), Event(220), Event(228), Event(236), Event(244), Event(252), - Event(260), Event(268), Event(276), /* Refresh. */ Event(292), Event(300), - Event(308), Event(316), Event(324), Event(332), Event(340), Event(348), - Event(356), Event(364), Event(372), Event(380), Event(388), Event(396), - Event(404), /* Refresh. */ Event(420), Event(428), Event(436), Event(444), - Event(452), Event(460), Event(468), Event(476), Event(484), Event(492), - Event(500), Event(508), Event(516), Event(524), Event(532), /* Refresh. */ - Event(548), Event(556), Event(564), Event(570), Event(580), Event(588), - Event(596), Event(604), Event(612), Event(620), Event(628), Event(636), - Event(644), Event(652), Event(660), /* Refresh. */ Event(676), Event(684), - Event(692), Event(700), Event(708), Event(716), Event(724), Event(732), - Event(740), Event(748), Event(756), Event(764), Event(772), Event(780), - Event(788), /* Refresh. */ Event(804), Event(812), Event(820), Event(828), - Event(836), Event(844), Event(852), Event(860), Event(868), Event(876), - Event(884), Event(892), Event(900), Event(908), Event(916), /* Refresh. */ - Event(932), Event(940), Event(948), Event(956), Event(964), Event(972), - Event(980), Event(988), Event(996), Event(1004), Event(1012), Event(1020), - Event(1028), Event(1036), Event(1044), /* Refresh. */ Event(1060), Event(1068), - Event(1076), Event(1084), Event(1092), Event(1100), Event(1108), Event(1116), - Event(1124), Event(1132), Event(1140), Event(1148), Event(1156), Event(1164), - Event(1172), /* Refresh. */ Event(1188), Event(1196), Event(1204), Event(1212), - Event(1220), Event(1228), - - Event(1268), Event(1276), - - Event() - }; - - static constexpr Event no_sprites_events[] = { - Event(1282), Event(1290), Event(1298), Event(1306), - Event(1314), Event(1322), Event(1332), Event(1342), - Event(1350), Event(1358), Event(1366), - - Event(6), Event(14), Event(22), Event(30), - Event(38), Event(46), Event(54), Event(62), - Event(70), Event(78), Event(86), Event(94), - Event(102), Event(110), Event(118), - - Event(162), Event(170), Event(182), Event(188), - // Omitted: dummy data block. Is not observable. - Event(214), Event(220), - - Event(226, Event::Type::DataBlock), Event(246), Event(252), - Event(258, Event::Type::DataBlock), Event(278), // Omitted: refresh. - Event(290, Event::Type::DataBlock), Event(310), Event(316), - Event(322, Event::Type::DataBlock), Event(342), Event(348), - Event(354, Event::Type::DataBlock), Event(374), Event(380), - Event(386, Event::Type::DataBlock), Event(406), // Omitted: refresh. - Event(418, Event::Type::DataBlock), Event(438), Event(444), - Event(450, Event::Type::DataBlock), Event(470), Event(476), - - Event(482, Event::Type::DataBlock), Event(502), Event(508), - Event(514, Event::Type::DataBlock), Event(534), // Omitted: refresh. - Event(546, Event::Type::DataBlock), Event(566), Event(572), - Event(578, Event::Type::DataBlock), Event(598), Event(604), - Event(610, Event::Type::DataBlock), Event(630), Event(636), - Event(642, Event::Type::DataBlock), Event(662), // Omitted: refresh. - Event(674, Event::Type::DataBlock), Event(694), Event(700), - Event(706, Event::Type::DataBlock), Event(726), Event(732), - - Event(738, Event::Type::DataBlock), Event(758), Event(764), - Event(770, Event::Type::DataBlock), Event(790), // Omitted: refresh. - Event(802, Event::Type::DataBlock), Event(822), Event(828), - Event(834, Event::Type::DataBlock), Event(854), Event(860), - Event(866, Event::Type::DataBlock), Event(886), Event(892), - Event(898, Event::Type::DataBlock), Event(918), // Omitted: refresh. - Event(930, Event::Type::DataBlock), Event(950), Event(956), - Event(962, Event::Type::DataBlock), Event(982), Event(988), - - Event(994, Event::Type::DataBlock), Event(1014), Event(1020), - Event(1026, Event::Type::DataBlock), Event(1046), // Omitted: refresh. - Event(1058, Event::Type::DataBlock), Event(1078), Event(1084), - Event(1090, Event::Type::DataBlock), Event(1110), Event(1116), - Event(1122, Event::Type::DataBlock), Event(1142), Event(1148), - Event(1154, Event::Type::DataBlock), Event(1174), // Omitted: refresh. - Event(1186, Event::Type::DataBlock), Event(1206), Event(1212), - Event(1218, Event::Type::DataBlock), - - Event(1266), - Event(1274), - - Event() - }; -}; - -// Master System-specific storage. -template struct Storage> { - using AddressT = uint16_t; - - // The SMS VDP has a programmer-set colour palette, with a dedicated patch of RAM. But the RAM is only exactly - // fast enough for the pixel clock. So when the programmer writes to it, that causes a one-pixel glitch; there - // isn't the bandwidth for the read both write to occur simultaneously. The following buffer therefore keeps - // track of pending collisions, for visual reproduction. - struct CRAMDot { - LineBufferPointer location; - uint32_t value; - }; - std::vector upcoming_cram_dots_; - - // The Master System's additional colour RAM. - uint32_t colour_ram_[32]; - bool cram_is_selected_ = false; - - // Programmer-set flags. - bool vertical_scroll_lock_ = false; - bool horizontal_scroll_lock_ = false; - bool hide_left_column_ = false; - bool shift_sprites_8px_left_ = false; - bool mode4_enable_ = false; - uint8_t horizontal_scroll_ = 0; - uint8_t vertical_scroll_ = 0; - - // 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; - - // Various resource addresses with VDP-version-specific modifications - // built in. - AddressT pattern_name_address_; - AddressT sprite_attribute_table_address_; - AddressT sprite_generator_table_address_; - - void begin_line(ScreenMode, bool, bool) {} -}; - template struct Base: public Storage { Base(); diff --git a/Components/9918/Implementation/LineBuffer.hpp b/Components/9918/Implementation/LineBuffer.hpp new file mode 100644 index 000000000..a8a2c42a7 --- /dev/null +++ b/Components/9918/Implementation/LineBuffer.hpp @@ -0,0 +1,103 @@ +// +// LineBuffer.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/02/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef LineBuffer_hpp +#define LineBuffer_hpp + +namespace TI { +namespace TMS { + +enum class VerticalState { + /// Describes any line on which pixels do not appear and no fetching occurs, including + /// the border, blanking and sync. + Blank, + /// A line on which pixels do not appear but fetching occurs. + Prefetch, + /// A line on which pixels appear and fetching occurs. + Pixels, +}; + +// Temporary buffers collect a representation of each line prior to pixel serialisation. +struct LineBuffer { + LineBuffer() {} + + // The fetch mode describes the proper timing diagram for this line; + // screen mode captures proper output mode. + FetchMode fetch_mode = FetchMode::Text; + ScreenMode screen_mode = ScreenMode::Text; + VerticalState vertical_state = VerticalState::Blank; + + // 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. + union { + // The TMS and Sega VDPs are close enough to always tile-based; + // this struct captures maximal potential detail there. + struct { + uint8_t flags[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]{}; + }; + + // The Yamaha VDP also has a variety of bitmap modes, + // the widest of which is 512px @ 4bpp. + uint8_t bitmap[256]; + }; + + /* + 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 = 94; + int next_border_column = 334; + int pixel_count = 256; + + // 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(); +}; + +struct LineBufferPointer { + int row, column; +}; + +} +} + +#endif /* LineBuffer_hpp */ diff --git a/Components/9918/Implementation/Storage.hpp b/Components/9918/Implementation/Storage.hpp new file mode 100644 index 000000000..9a5189c9a --- /dev/null +++ b/Components/9918/Implementation/Storage.hpp @@ -0,0 +1,311 @@ +// +// Storage.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/02/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Storage_h +#define Storage_h + +#include "LineBuffer.hpp" +#include "YamahaCommands.hpp" + +namespace TI { +namespace TMS { + +/// A container for personality-specific storage; see specific instances below. +template struct Storage { +}; + +template <> struct Storage { + using AddressT = uint16_t; + + void begin_line(ScreenMode, bool, bool) {} +}; + +// Yamaha-specific storage. +template struct Storage> { + using AddressT = uint32_t; + + int selected_status_ = 0; + + int indirect_register_ = 0; + bool increment_indirect_register_ = false; + + std::array palette_{}; + uint8_t new_colour_ = 0; + uint8_t palette_entry_ = 0; + bool palette_write_phase_ = false; + + uint8_t mode_ = 0; + + uint8_t vertical_offset_ = 0; + + /// Describes an _observable_ memory access event. i.e. anything that it is safe + /// (and convenient) to treat as atomic in between external slots. + struct Event { + /// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when + /// external data must be ready by in order to take part in those slots. + int offset = 1368; + enum class Type { + External, + DataBlock, + SpriteY, + SpriteContents, + } type = Type::External; + + constexpr Event(int offset, Type type) noexcept : + offset(grauw_to_internal(offset)), + type(type) {} + + constexpr Event(int offset) noexcept : + offset(grauw_to_internal(offset)) {} + + constexpr Event() noexcept {} + }; + + // State that tracks fetching position within a line. + const Event *next_event_ = nullptr; + int data_block_ = 0; + int sprite_block_ = 0; + + /// Resets line-ephemeral state for a new line. + void begin_line([[maybe_unused]] ScreenMode mode, bool is_refresh, [[maybe_unused]] bool sprites_enabled) { + // TODO: reinstate upon completion of the Yamaha pipeline. +// assert(mode < ScreenMode::YamahaText80 || next_event_ == nullptr || next_event_->offset == 1368); + + data_block_ = 0; + sprite_block_ = 0; + + if(is_refresh) { + next_event_ = refresh_events; + return; + } + + // TODO: obey sprites_enabled flag, at least. + next_event_ = no_sprites_events; + } + + // Command engine state. + CommandContext command_context_; + std::unique_ptr command_ = nullptr; + + enum class CommandStep { + None, + + ReadSourcePixel, + ReadDestinationPixel, + WritePixel, + + ReadSourceByte, + WriteByte, + }; + CommandStep next_command_step_ = CommandStep::None; + int minimum_command_column_ = 0; + uint8_t command_latch_ = 0; + + void update_command_step(int current_column) { + if(!command_) { + next_command_step_ = CommandStep::None; + return; + } + if(command_->done()) { + command_ = nullptr; + next_command_step_ = CommandStep::None; + return; + } + + minimum_command_column_ = current_column + command_->cycles; + switch(command_->access) { + case Command::AccessType::CopyPoint: + next_command_step_ = CommandStep::ReadSourcePixel; + break; + case Command::AccessType::PlotPoint: + next_command_step_ = CommandStep::ReadDestinationPixel; + break; + + case Command::AccessType::WaitForColourReceipt: + // i.e. nothing to do until a colour is received. + next_command_step_ = CommandStep::None; + break; + + case Command::AccessType::CopyByte: + next_command_step_ = CommandStep::ReadSourceByte; + break; + case Command::AccessType::WriteByte: + next_command_step_ = CommandStep::WriteByte; + break; + } + } + + Storage() noexcept { + // Perform sanity checks on the event lists. +#ifndef NDEBUG + const Event *lists[] = { no_sprites_events, refresh_events, nullptr }; + const Event **list = lists; + while(*list) { + const Event *cursor = *list; + ++list; + + while(cursor[1].offset != 1368) { + assert(cursor[1].offset > cursor[0].offset); + ++cursor; + } + } +#endif + + // Seed to _something_ meaningful. + // + // TODO: this is a workaround [/hack], in effect, for the main TMS' habit of starting + // in a randomised position, which means that start-of-line isn't announced. + // + // Do I really want that behaviour? + next_event_ = refresh_events; + } + + private: + // This emulator treats position 0 as being immediately after the standard pixel area. + // i.e. offset 1282 on Grauw's http://map.grauw.nl/articles/vdp-vram-timing/vdp-timing.png + constexpr static int grauw_to_internal(int offset) { + return (offset + 1368 - 1282) % 1368; + } + + static constexpr Event refresh_events[] = { + Event(1284), Event(1292), Event(1300), Event(1308), Event(1316), Event(1324), + Event(1334), Event(1344), Event(1352), Event(1360), Event(0), Event(8), + Event(16), Event(24), Event(32), Event(40), Event(48), Event(56), + Event(64), Event(72), Event(80), Event(88), Event(96), Event(104), + Event(112), Event(120), + + Event(164), Event(172), Event(180), Event(188), Event(196), Event(204), + Event(212), Event(220), Event(228), Event(236), Event(244), Event(252), + Event(260), Event(268), Event(276), /* Refresh. */ Event(292), Event(300), + Event(308), Event(316), Event(324), Event(332), Event(340), Event(348), + Event(356), Event(364), Event(372), Event(380), Event(388), Event(396), + Event(404), /* Refresh. */ Event(420), Event(428), Event(436), Event(444), + Event(452), Event(460), Event(468), Event(476), Event(484), Event(492), + Event(500), Event(508), Event(516), Event(524), Event(532), /* Refresh. */ + Event(548), Event(556), Event(564), Event(570), Event(580), Event(588), + Event(596), Event(604), Event(612), Event(620), Event(628), Event(636), + Event(644), Event(652), Event(660), /* Refresh. */ Event(676), Event(684), + Event(692), Event(700), Event(708), Event(716), Event(724), Event(732), + Event(740), Event(748), Event(756), Event(764), Event(772), Event(780), + Event(788), /* Refresh. */ Event(804), Event(812), Event(820), Event(828), + Event(836), Event(844), Event(852), Event(860), Event(868), Event(876), + Event(884), Event(892), Event(900), Event(908), Event(916), /* Refresh. */ + Event(932), Event(940), Event(948), Event(956), Event(964), Event(972), + Event(980), Event(988), Event(996), Event(1004), Event(1012), Event(1020), + Event(1028), Event(1036), Event(1044), /* Refresh. */ Event(1060), Event(1068), + Event(1076), Event(1084), Event(1092), Event(1100), Event(1108), Event(1116), + Event(1124), Event(1132), Event(1140), Event(1148), Event(1156), Event(1164), + Event(1172), /* Refresh. */ Event(1188), Event(1196), Event(1204), Event(1212), + Event(1220), Event(1228), + + Event(1268), Event(1276), + + Event() + }; + + static constexpr Event no_sprites_events[] = { + Event(1282), Event(1290), Event(1298), Event(1306), + Event(1314), Event(1322), Event(1332), Event(1342), + Event(1350), Event(1358), Event(1366), + + Event(6), Event(14), Event(22), Event(30), + Event(38), Event(46), Event(54), Event(62), + Event(70), Event(78), Event(86), Event(94), + Event(102), Event(110), Event(118), + + Event(162), Event(170), Event(182), Event(188), + // Omitted: dummy data block. Is not observable. + Event(214), Event(220), + + Event(226, Event::Type::DataBlock), Event(246), Event(252), + Event(258, Event::Type::DataBlock), Event(278), // Omitted: refresh. + Event(290, Event::Type::DataBlock), Event(310), Event(316), + Event(322, Event::Type::DataBlock), Event(342), Event(348), + Event(354, Event::Type::DataBlock), Event(374), Event(380), + Event(386, Event::Type::DataBlock), Event(406), // Omitted: refresh. + Event(418, Event::Type::DataBlock), Event(438), Event(444), + Event(450, Event::Type::DataBlock), Event(470), Event(476), + + Event(482, Event::Type::DataBlock), Event(502), Event(508), + Event(514, Event::Type::DataBlock), Event(534), // Omitted: refresh. + Event(546, Event::Type::DataBlock), Event(566), Event(572), + Event(578, Event::Type::DataBlock), Event(598), Event(604), + Event(610, Event::Type::DataBlock), Event(630), Event(636), + Event(642, Event::Type::DataBlock), Event(662), // Omitted: refresh. + Event(674, Event::Type::DataBlock), Event(694), Event(700), + Event(706, Event::Type::DataBlock), Event(726), Event(732), + + Event(738, Event::Type::DataBlock), Event(758), Event(764), + Event(770, Event::Type::DataBlock), Event(790), // Omitted: refresh. + Event(802, Event::Type::DataBlock), Event(822), Event(828), + Event(834, Event::Type::DataBlock), Event(854), Event(860), + Event(866, Event::Type::DataBlock), Event(886), Event(892), + Event(898, Event::Type::DataBlock), Event(918), // Omitted: refresh. + Event(930, Event::Type::DataBlock), Event(950), Event(956), + Event(962, Event::Type::DataBlock), Event(982), Event(988), + + Event(994, Event::Type::DataBlock), Event(1014), Event(1020), + Event(1026, Event::Type::DataBlock), Event(1046), // Omitted: refresh. + Event(1058, Event::Type::DataBlock), Event(1078), Event(1084), + Event(1090, Event::Type::DataBlock), Event(1110), Event(1116), + Event(1122, Event::Type::DataBlock), Event(1142), Event(1148), + Event(1154, Event::Type::DataBlock), Event(1174), // Omitted: refresh. + Event(1186, Event::Type::DataBlock), Event(1206), Event(1212), + Event(1218, Event::Type::DataBlock), + + Event(1266), + Event(1274), + + Event() + }; +}; + +// Master System-specific storage. +template struct Storage> { + using AddressT = uint16_t; + + // The SMS VDP has a programmer-set colour palette, with a dedicated patch of RAM. But the RAM is only exactly + // fast enough for the pixel clock. So when the programmer writes to it, that causes a one-pixel glitch; there + // isn't the bandwidth for the read both write to occur simultaneously. The following buffer therefore keeps + // track of pending collisions, for visual reproduction. + struct CRAMDot { + LineBufferPointer location; + uint32_t value; + }; + std::vector upcoming_cram_dots_; + + // The Master System's additional colour RAM. + uint32_t colour_ram_[32]; + bool cram_is_selected_ = false; + + // Programmer-set flags. + bool vertical_scroll_lock_ = false; + bool horizontal_scroll_lock_ = false; + bool hide_left_column_ = false; + bool shift_sprites_8px_left_ = false; + bool mode4_enable_ = false; + uint8_t horizontal_scroll_ = 0; + uint8_t vertical_scroll_ = 0; + + // 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; + + // Various resource addresses with VDP-version-specific modifications + // built in. + AddressT pattern_name_address_; + AddressT sprite_attribute_table_address_; + AddressT sprite_generator_table_address_; + + void begin_line(ScreenMode, bool, bool) {} +}; + +} +} + +#endif /* Storage_h */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 261c7967f..a4c0e4ad6 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1252,6 +1252,8 @@ 4B2530F3244E6773007980BF /* fm.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = fm.json; sourceTree = ""; }; 4B262BFF29691F55002EC0F7 /* PersonalityTraits.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PersonalityTraits.hpp; sourceTree = ""; }; 4B2A332C1DB86821002876E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/OricOptions.xib"; sourceTree = SOURCE_ROOT; }; + 4B2A3B5A29993DFA007CE366 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = ""; }; + 4B2A3B5B29995FF6007CE366 /* LineBuffer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineBuffer.hpp; sourceTree = ""; }; 4B2A53901D117D36003C6002 /* CSAudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAudioQueue.h; sourceTree = ""; }; 4B2A53911D117D36003C6002 /* CSAudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSAudioQueue.m; sourceTree = ""; }; 4B2A53951D117D36003C6002 /* CSMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSMachine.h; sourceTree = ""; }; @@ -4757,7 +4759,9 @@ 4B43983C29621024006B0BFC /* ClockConverter.hpp */, 4B43983F2967459B006B0BFC /* Draw.hpp */, 4B43983E29628538006B0BFC /* Fetch.hpp */, + 4B2A3B5B29995FF6007CE366 /* LineBuffer.hpp */, 4B262BFF29691F55002EC0F7 /* PersonalityTraits.hpp */, + 4B2A3B5A29993DFA007CE366 /* Storage.hpp */, 4BF0BC732982E54700CCA2B5 /* YamahaCommands.hpp */, ); path = Implementation;