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; }