diff --git a/Machines/Atari2600/TIA.cpp b/Machines/Atari2600/TIA.cpp index 6a8888cac..7912efae8 100644 --- a/Machines/Atari2600/TIA.cpp +++ b/Machines/Atari2600/TIA.cpp @@ -11,6 +11,7 @@ using namespace Atari2600; namespace { const int cycles_per_line = 228; + const int first_pixel_cycle = 68; const int sync_flag = 0x1; const int blank_flag = 0x2; @@ -20,7 +21,7 @@ namespace { TIA::TIA() : horizontal_counter_(0), - pixel_target_(nullptr), + pixels_start_location_(0), output_mode_(0), background_{0, 0}, background_half_mask_(0) @@ -36,6 +37,65 @@ TIA::TIA() : ((c & 0x10) >> 1) | ((c & 0x20) >> 3) | ((c & 0x40) >> 5) | ((c & 0x80) >> 7) ); } + + for(int c = 0; c < 64; c++) + { + collision_flags_by_buffer_vaules_[c] = 0; // TODO + } + + for(int c = 0; c < 64; c++) + { + bool has_playfield = c & (int)(CollisionType::Playfield); + bool has_ball = c & (int)(CollisionType::Ball); + bool has_player0 = c & (int)(CollisionType::Player0); + bool has_player1 = c & (int)(CollisionType::Player1); + bool has_missile0 = c & (int)(CollisionType::Missile0); + bool has_missile1 = c & (int)(CollisionType::Missile1); + + colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = + colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::Background; + + if(has_playfield || has_ball) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = (uint8_t)ColourIndex::PlayfieldBall; + } + if(has_ball) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayfieldBall; + } + + if(has_player1 || has_missile1) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = + colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile1; + } + if(has_playfield) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayerMissile1; + } + + if(has_player0 || has_missile0) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = + colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile0; + } + if(has_playfield) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = (uint8_t)ColourIndex::PlayerMissile0; + } + + if(has_playfield || has_ball) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayfieldBall; + } + } } void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) @@ -131,7 +191,7 @@ int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) void TIA::set_background_colour(uint8_t colour) { - background_colour_ = colour; + colour_palette_[(int)ColourIndex::Background] = colour; } void TIA::set_playfield(uint16_t offset, uint8_t value) @@ -156,13 +216,24 @@ void TIA::set_playfield(uint16_t offset, uint8_t value) void TIA::set_playfield_control_and_ball_size(uint8_t value) { background_half_mask_ = value & 1; - playfield_is_above_players_ = !!(value & 4); - playfield_is_in_score_mode_ = !playfield_is_above_players_ && (value & 2); + switch(value & 6) + { + case 0: + playfield_priority_ = PlayfieldPriority::Standard; + break; + case 2: + playfield_priority_ = PlayfieldPriority::Score; + break; + case 4: + case 6: + playfield_priority_ = PlayfieldPriority::OnTop; + break; + } } void TIA::set_playfield_ball_colour(uint8_t colour) { - playfield_ball_colour_ = colour; + colour_palette_[(int)ColourIndex::PlayfieldBall] = colour; } void TIA::set_player_number_and_size(int player, uint8_t value) @@ -207,7 +278,7 @@ void TIA::set_player_delay(int player, bool delay) void TIA::set_player_position(int player) { - player_[player].position = ((horizontal_counter_ - 68) + 6)%160; + player_[player].position = ((horizontal_counter_ - first_pixel_cycle) + 6)%160; } void TIA::set_player_motion(int player, uint8_t motion) @@ -217,7 +288,7 @@ void TIA::set_player_motion(int player, uint8_t motion) void TIA::set_player_missile_colour(int player, uint8_t colour) { - player_[player].colour = colour; + colour_palette_[(int)ColourIndex::PlayerMissile0 + player] = colour; } void TIA::set_missile_enable(int missile, bool enabled) @@ -262,11 +333,12 @@ void TIA::clear_motion() uint8_t TIA::get_collision_flags(int offset) { - return 0x00; + return (uint8_t)((collision_flags_ >> (offset << 1)) << 6) & 0xc0; } void TIA::clear_collision_flags() { + collision_flags_ = 0; } void TIA::output_for_cycles(int number_of_cycles) @@ -323,36 +395,76 @@ void TIA::output_for_cycles(int number_of_cycles) if(output_mode_ & blank_flag) { - if(pixel_target_) - { - crt_->output_data((unsigned int)((horizontal_counter_ - pixel_target_origin_) * 2), 2); - pixel_target_ = nullptr; - } + if(pixels_start_location_) output_pixels(pixels_start_location_, output_cursor); int duration = std::min(228, horizontal_counter_) - output_cursor; crt_->output_blank((unsigned int)(duration * 2)); } else { - if(!pixel_target_) + if(!pixels_start_location_) pixels_start_location_ = output_cursor; + + // accumulate an OR'dversion of the output into the collision buffer + draw_playfield(output_cursor, horizontal_counter_); + + // accumulate collision flags + while(output_cursor < horizontal_counter_) { - pixel_target_ = crt_->allocate_write_area((unsigned int)(228 - output_cursor)); - pixel_target_origin_ = output_cursor; + uint8_t buffer_value = collision_buffer_[output_cursor - first_pixel_cycle]; + collision_flags_ |= collision_flags_by_buffer_vaules_[buffer_value]; + output_cursor++; } - if(pixel_target_) - { - draw_background(pixel_target_, output_cursor, horizontal_counter_); - output_cursor = horizontal_counter_; - } else output_cursor = horizontal_counter_; + if(horizontal_counter_ == cycles_per_line) { - crt_->output_data((unsigned int)((horizontal_counter_ - pixel_target_origin_) * 2), 2); - pixel_target_ = nullptr; + output_pixels(pixels_start_location_, cycles_per_line); } } horizontal_counter_ %= cycles_per_line; } +void TIA::output_pixels(int start, int end) +{ + // seek a buffer and convert to pixels + uint8_t *pixel_target = crt_->allocate_write_area((unsigned int)(end - start)); + + if(pixel_target) + { + if(playfield_priority_ == PlayfieldPriority::Score) + { + while(start < end && start < first_pixel_cycle + 80) + { + uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; + *pixel_target = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][buffer_value]]; + start++; + pixel_target++; + } + while(start < end) + { + uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; + *pixel_target = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][buffer_value]]; + start++; + pixel_target++; + } + } + else + { + int table_index = (int)((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop); + while(start < end) + { + uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; + *pixel_target = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]]; + start++; + pixel_target++; + } + } + } + + crt_->output_data((unsigned int)((end - start) * 2), 2); + + pixels_start_location_ = 0; +} + void TIA::output_line() { switch(output_mode_) @@ -378,18 +490,13 @@ void TIA::output_line() #pragma mark - Background and playfield -void TIA::draw_background(uint8_t *target, int start, int length) const +void TIA::draw_playfield(int start, int end) { - if(!target) return; int position = start; - while(length--) + while(position < end) { - int offset = (position - 68) >> 2; - target[position - pixel_target_origin_] = ((background_[(offset/20)&background_half_mask_] >> (offset%20))&1) ? playfield_ball_colour_ : background_colour_; + int offset = (position - first_pixel_cycle) >> 2; + collision_buffer_[position - first_pixel_cycle] = (background_[(offset/20)&background_half_mask_] >> (offset%20))&1; position++; } } - -void TIA::draw_playfield(uint8_t *target, int start, int length) const -{ -} diff --git a/Machines/Atari2600/TIA.hpp b/Machines/Atari2600/TIA.hpp index b9e56379e..366b5e4c1 100644 --- a/Machines/Atari2600/TIA.hpp +++ b/Machines/Atari2600/TIA.hpp @@ -75,8 +75,10 @@ class TIA { inline void output_for_cycles(int number_of_cycles); inline void output_line(); - inline void draw_background(uint8_t *target, int start, int length) const; - inline void draw_playfield(uint8_t *target, int start, int length) const; + inline void draw_playfield(int start, int end); + + int pixels_start_location_; + inline void output_pixels(int start, int end); // the master counter; counts from 0 to 228 with all visible pixels being in the final 160 int horizontal_counter_; @@ -85,24 +87,43 @@ class TIA { int output_mode_; // keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer - uint8_t *pixel_target_; - int pixel_target_origin_; uint8_t collision_buffer_[160]; enum class CollisionType : uint8_t { - Playfield, - Sprite1, - Sprite2, - Missile1, - Missile2, - Ball + Playfield = (1 << 0), + Ball = (1 << 1), + Player0 = (1 << 2), + Player1 = (1 << 3), + Missile0 = (1 << 4), + Missile1 = (1 << 5) }; + int collision_flags_; + int collision_flags_by_buffer_vaules_[64]; + + // colour mapping tables + enum class ColourMode { + Standard = 0, + ScoreLeft, + ScoreRight, + OnTop + }; + uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_pallete_ entry + + enum class ColourIndex { + Background = 0, + PlayfieldBall, + PlayerMissile1, + PlayerMissile0 + }; + uint8_t colour_palette_[4]; + // playfield state - uint8_t playfield_ball_colour_; - uint8_t background_colour_; int background_half_mask_; - bool playfield_is_in_score_mode_; - bool playfield_is_above_players_; + enum class PlayfieldPriority { + Standard, + Score, + OnTop + } playfield_priority_; uint32_t background_[2]; // contains two 20-bit bitfields representing the background state; // at index 0 is the left-hand side of the playfield with bit 0 being // the first bit to display, bit 1 the second, etc. Index 1 contains @@ -117,7 +138,6 @@ class TIA { int size; // 0 = normal, 1 = double, 2 = quad int copy_flags; // a bit field, corresponding to the first few values of NUSIZ uint8_t graphic; // the player graphic - uint8_t colour; // the player colour int reverse_mask; // 7 for a reflected player, 0 for normal uint8_t motion; // low four bits used uint8_t position; // in the range [0, 160) to indicate offset from the left margin, i.e. phase difference