// // Fetch.hpp // Clock Signal // // Created by Thomas Harte on 01/01/2023. // Copyright © 2023 Thomas Harte. All rights reserved. // #ifndef Fetch_hpp #define Fetch_hpp /* 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. */ // MARK: - Address mask helpers. /// @returns An instance of @c AddressT with all top bits set down to and including /// bit @c end and all others clear. /// /// So e.g. if @c AddressT is @c uint16_t and this VDP has a 15-bit address space then /// @c top_bits<10> will be the address with bits 15 to 10 (inclusive) set and the rest clear. template constexpr AddressT top_bits() { return AddressT(~0) - AddressT((1 << end) - 1); } /// Modifies and returns @c source so that all bits above position @c n are set; the others are unmodified. template constexpr AddressT bits(AddressT source = 0) { return AddressT(source | top_bits()); } // MARK: - 171-window Dispatcher. template template void Base::dispatch(SequencerT &fetcher, int start, int end) { #define index(n) \ if(use_end && end == n) return; \ [[fallthrough]]; \ case n: fetcher.template fetch(); switch(start) { default: assert(false); index(0); index(1); index(2); index(3); index(4); index(5); index(6); index(7); index(8); index(9); index(10); index(11); index(12); index(13); index(14); index(15); index(16); index(17); index(18); index(19); index(20); index(21); index(22); index(23); index(24); index(25); index(26); index(27); index(28); index(29); index(30); index(31); index(32); index(33); index(34); index(35); index(36); index(37); index(38); index(39); index(40); index(41); index(42); index(43); index(44); index(45); index(46); index(47); index(48); index(49); index(50); index(51); index(52); index(53); index(54); index(55); index(56); index(57); index(58); index(59); index(60); index(61); index(62); index(63); index(64); index(65); index(66); index(67); index(68); index(69); index(70); index(71); index(72); index(73); index(74); index(75); index(76); index(77); index(78); index(79); index(80); index(81); index(82); index(83); index(84); index(85); index(86); index(87); index(88); index(89); index(90); index(91); index(92); index(93); index(94); index(95); index(96); index(97); index(98); index(99); index(100); index(101); index(102); index(103); index(104); index(105); index(106); index(107); index(108); index(109); index(110); index(111); index(112); index(113); index(114); index(115); index(116); index(117); index(118); index(119); index(120); index(121); index(122); index(123); index(124); index(125); index(126); index(127); index(128); index(129); index(130); index(131); index(132); index(133); index(134); index(135); index(136); index(137); index(138); index(139); index(140); index(141); index(142); index(143); index(144); index(145); index(146); index(147); index(148); index(149); index(150); index(151); index(152); index(153); index(154); index(155); index(156); index(157); index(158); index(159); index(160); index(161); index(162); index(163); index(164); index(165); index(166); index(167); index(168); index(169); index(170); } #undef index } // MARK: - Fetchers. template struct TextFetcher { using AddressT = typename Base::AddressT; TextFetcher(Base *base, LineBuffer &buffer, int y) : base(base), line_buffer(buffer), row_base(base->pattern_name_address_ & bits<10>(AddressT((y >> 3) * 40))), row_offset(base->pattern_generator_table_address_ & bits<11>(AddressT(y & 7))) {} void fetch_name(AddressT column, int slot = 0) { base->name_[slot] = base->ram_[row_base + column]; } void fetch_pattern(AddressT column, int slot = 0) { line_buffer.characters.shapes[column] = base->ram_[row_offset + size_t(base->name_[slot] << 3)]; } Base *const base; LineBuffer &line_buffer; const AddressT row_base; const AddressT row_offset; }; template struct CharacterFetcher { using AddressT = typename Base::AddressT; CharacterFetcher(Base *base, LineBuffer &buffer, LineBuffer &sprite_selection_buffer, int y) : base(base), tile_buffer(buffer), sprite_selection_buffer(sprite_selection_buffer), y(y), row_base(base->pattern_name_address_ & bits<10>(AddressT((y << 2)&~31))) { pattern_base = base->pattern_generator_table_address_; colour_base = base->colour_table_address_; colour_name_shift = 6; if(buffer.screen_mode == ScreenMode::Graphics || buffer.screen_mode == ScreenMode::YamahaGraphics3) { // If this is high resolution mode, allow the row number to affect the pattern and colour addresses. pattern_base &= bits<13>(AddressT(((y & 0xc0) << 5))); colour_base &= bits<13>(AddressT(((y & 0xc0) << 5))); colour_base += AddressT(y & 7); colour_name_shift = 0; } else { colour_base &= bits<6, AddressT>(); pattern_base &= bits<11, AddressT>(); } if(buffer.screen_mode == ScreenMode::MultiColour) { pattern_base += AddressT((y >> 2) & 7); } else { pattern_base += AddressT(y & 7); } } void fetch_name(int column) { base->tile_offset_ = base->ram_[row_base + AddressT(column)]; } void fetch_pattern(int column) { tile_buffer.tiles.patterns[column][0] = base->ram_[pattern_base + AddressT(base->tile_offset_ << 3)]; } void fetch_colour(int column) { tile_buffer.tiles.patterns[column][1] = base->ram_[colour_base + AddressT((base->tile_offset_ << 3) >> colour_name_shift)]; } Base *const base; LineBuffer &tile_buffer; LineBuffer &sprite_selection_buffer; const int y; const AddressT row_base; AddressT pattern_base; AddressT colour_base; int colour_name_shift; }; enum class SpriteMode { Mode1, Mode2, // MasterSystem, }; constexpr SpriteMode sprite_mode(ScreenMode screen_mode) { switch(screen_mode) { default: return SpriteMode::Mode2; case ScreenMode::MultiColour: case ScreenMode::ColouredText: case ScreenMode::Graphics: return SpriteMode::Mode1; // case ScreenMode::SMSMode4: // return SpriteMode::MasterSystem; } } template class SpriteFetcher { public: using AddressT = typename Base::AddressT; SpriteFetcher(Base *base, LineBuffer &buffer, LineBuffer &sprite_selection_buffer, int y) : base(base), tile_buffer(buffer), sprite_selection_buffer(sprite_selection_buffer), y(y) {} void fetch_location(int slot) { fetch_xy(slot); if constexpr (mode == SpriteMode::Mode2) { fetch_xy(slot + 1); base->name_[0] = name(slot); base->name_[1] = name(slot + 1); } } void fetch_pattern(int slot) { switch(mode) { case SpriteMode::Mode1: fetch_image(slot, name(slot)); break; case SpriteMode::Mode2: fetch_image(slot, base->name_[0]); fetch_image(slot + 1, base->name_[1]); break; } } void fetch_y(int sprite) { base->posit_sprite(sprite_selection_buffer, sprite, base->ram_[base->sprite_attribute_table_address_ & bits<7>(AddressT(sprite << 2))], y); } private: void fetch_xy(int slot) { tile_buffer.active_sprites[slot].x = base->ram_[ base->sprite_attribute_table_address_ & bits<7>(AddressT((tile_buffer.active_sprites[slot].index << 2) | 1)) ]; } uint8_t name(int slot) { return base->ram_[ base->sprite_attribute_table_address_ & bits<7>(AddressT((tile_buffer.active_sprites[slot].index << 2) | 2)) ] & (base->sprites_16x16_ ? ~3 : ~0); } void fetch_image(int slot, uint8_t name) { const AddressT graphic_offset = bits<11>(AddressT((name << 3) | tile_buffer.active_sprites[slot].row)); uint8_t colour = 0; switch(mode) { case SpriteMode::Mode1: // Fetch colour from the attribute table, per this sprite's slot. colour = base->ram_[ base->sprite_attribute_table_address_ & bits<7>(AddressT((tile_buffer.active_sprites[slot].index << 2) | 3)) ]; break; case SpriteMode::Mode2: { // Fetch colour from the colour table, per this sprite's name and row. colour = base->ram_[ base->sprite_attribute_table_address_ & AddressT(~512) & graphic_offset ]; } break; } tile_buffer.active_sprites[slot].image[2] = colour; tile_buffer.active_sprites[slot].x -= (colour & 0x80) >> 2; const AddressT graphic_location = base->sprite_generator_table_address_ & graphic_offset; tile_buffer.active_sprites[slot].image[0] = base->ram_[graphic_location]; tile_buffer.active_sprites[slot].image[1] = base->ram_[graphic_location+16]; } Base *const base; LineBuffer &tile_buffer; LineBuffer &sprite_selection_buffer; const int y; }; template struct SMSFetcher { using AddressT = typename Base::AddressT; struct RowInfo { AddressT pattern_address_base; AddressT sub_row[2]; }; SMSFetcher(Base *base, LineBuffer &buffer, LineBuffer &sprite_selection_buffer, int y) : base(base), storage(static_cast *>(base)), tile_buffer(buffer), sprite_selection_buffer(sprite_selection_buffer), y(y), horizontal_offset((y >= 16 || !storage->horizontal_scroll_lock_) ? (tile_buffer.latched_horizontal_scroll >> 3) : 0) { // Limit address bits in use if this is a SMS2 mode. const bool is_tall_mode = base->mode_timing_.pixel_lines != 192; const AddressT pattern_name_address = storage->pattern_name_address_ | (is_tall_mode ? 0x800 : 0); const AddressT pattern_name_offset = is_tall_mode ? 0x100 : 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 = (y + storage->latched_vertical_scroll_) % (is_tall_mode ? 256 : 224); scrolled_row_info.pattern_address_base = pattern_name_address & bits<11>(AddressT((scrolled_row & ~7) << 3)) - pattern_name_offset; scrolled_row_info.sub_row[0] = AddressT((scrolled_row & 7) << 2); scrolled_row_info.sub_row[1] = AddressT(28 ^ ((scrolled_row & 7) << 2)); if(storage->vertical_scroll_lock_) { static_row_info.pattern_address_base = bits<11>(AddressT(pattern_name_address & ((y & ~7) << 3))) - pattern_name_offset; static_row_info.sub_row[0] = AddressT((y & 7) << 2); static_row_info.sub_row[1] = 28 ^ AddressT((y & 7) << 2); } else static_row_info = scrolled_row_info; } void fetch_sprite(int sprite) { tile_buffer.active_sprites[sprite].x = base->ram_[ storage->sprite_attribute_table_address_ & bits<7>((tile_buffer.active_sprites[sprite].index << 1) | 0) ] - (storage->shift_sprites_8px_left_ ? 8 : 0); const uint8_t name = base->ram_[ storage->sprite_attribute_table_address_ & bits<7>((tile_buffer.active_sprites[sprite].index << 1) | 1) ] & (base->sprites_16x16_ ? ~1 : ~0); const AddressT graphic_location = storage->sprite_generator_table_address_ & bits<13>(AddressT((name << 5) | (tile_buffer.active_sprites[sprite].row << 2))); tile_buffer.active_sprites[sprite].image[0] = base->ram_[graphic_location]; tile_buffer.active_sprites[sprite].image[1] = base->ram_[graphic_location+1]; tile_buffer.active_sprites[sprite].image[2] = base->ram_[graphic_location+2]; tile_buffer.active_sprites[sprite].image[3] = base->ram_[graphic_location+3]; } void fetch_tile_name(int column) { const RowInfo &row_info = column < 24 ? scrolled_row_info : static_row_info; const size_t scrolled_column = (column - horizontal_offset) & 0x1f; const size_t address = row_info.pattern_address_base + (scrolled_column << 1); tile_buffer.tiles.flags[column] = base->ram_[address+1]; base->tile_offset_ = AddressT( (((tile_buffer.tiles.flags[column]&1) << 8) | base->ram_[address]) << 5 ) + row_info.sub_row[(tile_buffer.tiles.flags[column]&4) >> 2]; } void fetch_tile_pattern(int column) { tile_buffer.tiles.patterns[column][0] = base->ram_[base->tile_offset_]; tile_buffer.tiles.patterns[column][1] = base->ram_[base->tile_offset_+1]; tile_buffer.tiles.patterns[column][2] = base->ram_[base->tile_offset_+2]; tile_buffer.tiles.patterns[column][3] = base->ram_[base->tile_offset_+3]; } void posit_sprite(int sprite) { base->posit_sprite(sprite_selection_buffer, sprite, base->ram_[storage->sprite_attribute_table_address_ & bits<8>(AddressT(sprite))], y); } Base *const base; const Storage *const storage; LineBuffer &tile_buffer; LineBuffer &sprite_selection_buffer; const int y; const int horizontal_offset; RowInfo scrolled_row_info, static_row_info; }; // MARK: - TMS Sequencers. template struct RefreshSequencer { RefreshSequencer(Base *base) : base(base) {} template void fetch() { if(cycle < 44 || (cycle&1)) { base->do_external_slot(to_internal(cycle)); } } Base *const base; }; template struct TextSequencer { template TextSequencer(Args&&... args) : fetcher(std::forward(args)...) {} template void fetch() { // The first 47 and the final 4 slots are external. if constexpr (cycle < 47 || cycle >= 167) { fetcher.base->do_external_slot(to_internal(cycle)); return; } else { // For the 120 slots in between follow a three-step pattern of: constexpr int offset = cycle - 47; constexpr auto column = AddressT(offset / 3); switch(offset % 3) { case 0: fetcher.fetch_name(column); break; // (1) fetch tile name. case 1: fetcher.base->do_external_slot(to_internal(cycle)); break; // (2) external slot. case 2: fetcher.fetch_pattern(column); break; // (3) fetch tile pattern. } } } using AddressT = typename Base::AddressT; TextFetcher fetcher; }; template struct CharacterSequencer { template CharacterSequencer(Args&&... args) : character_fetcher(std::forward(args)...), sprite_fetcher(std::forward(args)...) {} template void fetch() { if(cycle < 2) { character_fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 2) { // Fetch: y0, x0, n0, c0, pat0a, pat0b, y1, x1, n1, c1, pat1a, pat1b, y2, x2. sprite_fetcher.fetch_location(0); sprite_fetcher.fetch_pattern(0); sprite_fetcher.fetch_location(1); sprite_fetcher.fetch_pattern(1); sprite_fetcher.fetch_location(2); } if(cycle > 16 && cycle < 21) { character_fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 21) { // Fetch: n1, c2, pat2a, pat2b, y3, x3, n3, c3, pat3a, pat3b. sprite_fetcher.fetch_pattern(2); sprite_fetcher.fetch_location(3); sprite_fetcher.fetch_pattern(3); } if(cycle >= 31 && cycle < 35) { character_fetcher.base->do_external_slot(to_internal(cycle)); } // Cycles 35 to 43: fetch 8 new sprite Y coordinates, to begin selecting sprites for next line. if(cycle == 35) { sprite_fetcher.fetch_y(0); sprite_fetcher.fetch_y(1); sprite_fetcher.fetch_y(2); sprite_fetcher.fetch_y(3); sprite_fetcher.fetch_y(4); sprite_fetcher.fetch_y(5); sprite_fetcher.fetch_y(6); sprite_fetcher.fetch_y(7); } // Rest of line: tiles themselves, plus some additional potential sprites. if(cycle >= 43) { constexpr int offset = cycle - 43; constexpr int block = offset >> 2; constexpr int sub_block = offset & 3; switch(sub_block) { case 0: character_fetcher.fetch_name(block); break; case 1: if(!(block & 3)) { character_fetcher.base->do_external_slot(to_internal(cycle)); } else { constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1); sprite_fetcher.fetch_y(sprite); } break; case 3: character_fetcher.fetch_pattern(block); character_fetcher.fetch_pattern(block); break; default: break; } } } using AddressT = typename Base::AddressT; CharacterFetcher character_fetcher; SpriteFetcher sprite_fetcher; }; // MARK: - TMS fetch routines. template template void Base::fetch_tms_refresh(LineBuffer &, LineBuffer &, int, int start, int end) { RefreshSequencer sequencer(this); dispatch(sequencer, start, end); } template template void Base::fetch_tms_text(LineBuffer &line_buffer, LineBuffer &, int y, int start, int end) { TextSequencer sequencer(this, line_buffer, y); dispatch(sequencer, start, end); } template template void Base::fetch_tms_character(LineBuffer &line_buffer, LineBuffer &next_line_buffer, int y, int start, int end) { CharacterSequencer sequencer(this, line_buffer, next_line_buffer, y); dispatch(sequencer, start, end); } // MARK: - Master System template struct SMSSequencer { template SMSSequencer(Args&&... args) : fetcher(std::forward(args)...) {} template void fetch() { if(!cycle) { fetcher.fetch_sprite(0); fetcher.fetch_sprite(1); fetcher.fetch_sprite(2); fetcher.fetch_sprite(3); } if(cycle >= 12 && cycle < 17) { fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 17) { fetcher.fetch_sprite(4); fetcher.fetch_sprite(5); fetcher.fetch_sprite(6); fetcher.fetch_sprite(7); } if(cycle == 29 || cycle == 30) { fetcher.base->do_external_slot(to_internal(cycle)); } if(cycle == 31) { fetcher.posit_sprite(0); fetcher.posit_sprite(1); fetcher.posit_sprite(2); fetcher.posit_sprite(3); fetcher.posit_sprite(4); fetcher.posit_sprite(5); fetcher.posit_sprite(6); fetcher.posit_sprite(7); fetcher.posit_sprite(8); fetcher.posit_sprite(9); fetcher.posit_sprite(10); fetcher.posit_sprite(11); fetcher.posit_sprite(12); fetcher.posit_sprite(13); fetcher.posit_sprite(14); fetcher.posit_sprite(15); } if(cycle >= 39 && cycle < 167) { constexpr int offset = cycle - 39; constexpr int block = offset >> 2; constexpr int sub_block = offset & 3; switch(sub_block) { default: break; case 0: fetcher.fetch_tile_name(block); break; case 1: if(!(block & 3)) { fetcher.base->do_external_slot(to_internal(cycle)); } else { constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1; fetcher.posit_sprite(sprite); fetcher.posit_sprite(sprite+1); } break; case 2: fetcher.fetch_tile_pattern(block); break; } } if(cycle >= 167) { fetcher.base->do_external_slot(to_internal(cycle)); } } using AddressT = typename Base::AddressT; SMSFetcher fetcher; }; template template void Base::fetch_sms(LineBuffer &line_buffer, LineBuffer &next_line_buffer, int y, int start, int end) { if constexpr (is_sega_vdp(personality)) { SMSSequencer sequencer(this, line_buffer, next_line_buffer, y); dispatch(sequencer, start, end); } } // MARK: - Yamaha template template void Base::fetch_yamaha(LineBuffer &line_buffer, LineBuffer &next_line_buffer, int y, int end) { const AddressT rotated_name_ = pattern_name_address_ >> 1; const uint8_t *const ram2 = &ram_[65536]; CharacterFetcher character_fetcher(this, line_buffer, next_line_buffer, y); TextFetcher text_fetcher(this, line_buffer, y); SpriteFetcher sprite_fetcher(this, line_buffer, next_line_buffer, y); using Type = typename Storage::Event::Type; while(Storage::next_event_->offset < end) { switch(Storage::next_event_->type) { case Type::External: do_external_slot(Storage::next_event_->offset); break; case Type::Name: switch(mode) { case ScreenMode::Text: { const auto column = AddressT(Storage::next_event_->id << 1); text_fetcher.fetch_name(column, 0); text_fetcher.fetch_name(column + 1, 1); } break; case ScreenMode::YamahaText80: { const auto column = AddressT(Storage::next_event_->id << 2); const auto start = pattern_name_address_ & bits<12>(AddressT((y >> 3) * 80)); name_[0] = ram_[start + column + 0]; name_[1] = ram_[start + column + 1]; name_[2] = ram_[start + column + 2]; name_[3] = ram_[start + column + 3]; } break; case ScreenMode::Graphics: case ScreenMode::MultiColour: case ScreenMode::ColouredText: character_fetcher.fetch_name(Storage::next_event_->id); break; default: break; } break; case Type::Colour: switch(mode) { case ScreenMode::YamahaText80: { const auto column = AddressT(Storage::next_event_->id); const auto address = colour_table_address_ & bits<9>(AddressT((y >> 3) * 10)); line_buffer.characters.flags[column] = ram_[address + column]; } break; case ScreenMode::Graphics: case ScreenMode::MultiColour: case ScreenMode::ColouredText: character_fetcher.fetch_colour(Storage::next_event_->id); break; default: break; } break; case Type::Pattern: switch(mode) { case ScreenMode::Text: { const auto column = AddressT(Storage::next_event_->id << 1); text_fetcher.fetch_pattern(column, 0); text_fetcher.fetch_pattern(column + 1, 1); } break; case ScreenMode::YamahaText80: { const auto column = Storage::next_event_->id << 2; const auto start = pattern_generator_table_address_ & bits<11>(AddressT(y & 7)); line_buffer.characters.shapes[column + 0] = ram_[start + AddressT(name_[0] << 3)]; line_buffer.characters.shapes[column + 1] = ram_[start + AddressT(name_[1] << 3)]; line_buffer.characters.shapes[column + 2] = ram_[start + AddressT(name_[2] << 3)]; line_buffer.characters.shapes[column + 3] = ram_[start + AddressT(name_[3] << 3)]; } break; case ScreenMode::Graphics: case ScreenMode::MultiColour: case ScreenMode::ColouredText: character_fetcher.fetch_pattern(Storage::next_event_->id); break; case ScreenMode::YamahaGraphics3: // As per comment elsewhere; my _guess_ is that G3 is slotted as if it were // a bitmap mode, with the three bytes that describe each column fitting into // the relevant windows. character_fetcher.fetch_name(Storage::next_event_->id); character_fetcher.fetch_colour(Storage::next_event_->id); character_fetcher.fetch_pattern(Storage::next_event_->id); break; case ScreenMode::YamahaGraphics4: case ScreenMode::YamahaGraphics5: { const int column = Storage::next_event_->id << 2; const auto start = bits<15>((y << 7) | column); line_buffer.bitmap[column + 0] = ram_[pattern_name_address_ & AddressT(start + 0)]; line_buffer.bitmap[column + 1] = ram_[pattern_name_address_ & AddressT(start + 1)]; line_buffer.bitmap[column + 2] = ram_[pattern_name_address_ & AddressT(start + 2)]; line_buffer.bitmap[column + 3] = ram_[pattern_name_address_ & AddressT(start + 3)]; } break; case ScreenMode::YamahaGraphics6: case ScreenMode::YamahaGraphics7: { const int column = Storage::next_event_->id << 3; const auto start = bits<15>((y << 7) | column); // Fetch from alternate banks. line_buffer.bitmap[column + 0] = ram_[rotated_name_ & AddressT(start + 0)]; line_buffer.bitmap[column + 1] = ram2[rotated_name_ & AddressT(start + 0)]; line_buffer.bitmap[column + 2] = ram_[rotated_name_ & AddressT(start + 1)]; line_buffer.bitmap[column + 3] = ram2[rotated_name_ & AddressT(start + 1)]; line_buffer.bitmap[column + 4] = ram_[rotated_name_ & AddressT(start + 2)]; line_buffer.bitmap[column + 5] = ram2[rotated_name_ & AddressT(start + 2)]; line_buffer.bitmap[column + 6] = ram_[rotated_name_ & AddressT(start + 3)]; line_buffer.bitmap[column + 7] = ram2[rotated_name_ & AddressT(start + 3)]; } break; default: break; } break; case Type::SpriteY: switch(mode) { case ScreenMode::Blank: case ScreenMode::Text: case ScreenMode::YamahaText80: // Ensure the compiler can discard character_fetcher in these modes. break; default: sprite_fetcher.fetch_y(Storage::next_event_->id); break; } break; case Type::SpriteLocation: switch(mode) { case ScreenMode::Blank: case ScreenMode::Text: case ScreenMode::YamahaText80: // Ensure the compiler can discard character_fetcher in these modes. break; default: sprite_fetcher.fetch_location(Storage::next_event_->id); break; } break; case Type::SpritePattern: switch(mode) { case ScreenMode::Blank: case ScreenMode::Text: case ScreenMode::YamahaText80: // Ensure the compiler can discard character_fetcher in these modes. break; default: sprite_fetcher.fetch_pattern(Storage::next_event_->id); break; } break; default: break; } ++Storage::next_event_; } } template template void Base::fetch_yamaha(LineBuffer &line_buffer, LineBuffer &next_line_buffer, int y, int, int end) { if constexpr (is_yamaha_vdp(personality)) { // Dispatch according to [supported] screen mode. #define Dispatch(mode) case mode: fetch_yamaha(line_buffer, next_line_buffer, y, end); break; switch(line_buffer.screen_mode) { default: break; Dispatch(ScreenMode::Blank); Dispatch(ScreenMode::Text); Dispatch(ScreenMode::MultiColour); Dispatch(ScreenMode::ColouredText); Dispatch(ScreenMode::Graphics); Dispatch(ScreenMode::YamahaText80); Dispatch(ScreenMode::YamahaGraphics3); Dispatch(ScreenMode::YamahaGraphics4); Dispatch(ScreenMode::YamahaGraphics5); Dispatch(ScreenMode::YamahaGraphics6); Dispatch(ScreenMode::YamahaGraphics7); } #undef Dispatch } } // MARK: - Mega Drive // TODO. #endif /* Fetch_hpp */