From a246530953ca0e0315ac387030d6a00c6888ff11 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 28 Jan 2017 21:46:40 -0500 Subject: [PATCH] Supposing the TIA were implemented, this is (more or less) what the Atari 2600 would now look like. --- Machines/Atari2600/Atari2600.cpp | 600 +++---------------------------- Machines/Atari2600/Atari2600.hpp | 111 +----- Machines/Atari2600/TIA.cpp | 49 +++ Machines/Atari2600/TIA.hpp | 14 +- 4 files changed, 121 insertions(+), 653 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index ea96ea83c..75db7269f 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -18,91 +18,25 @@ namespace { } Machine::Machine() : - horizontal_timer_(0), - last_output_state_duration_(0), - last_output_state_(OutputState::Sync), rom_(nullptr), + rom_pages_{nullptr, nullptr, nullptr, nullptr}, tia_input_value_{0xff, 0xff}, - upcoming_events_pointer_(0), - object_counter_pointer_(0), - state_by_time_(state_by_extend_time_[0]), cycles_since_speaker_update_(0), - is_pal_region_(false) + cycles_since_video_update_(0) { - memset(collisions_, 0xff, sizeof(collisions_)); - setup_reported_collisions(); - - for(int vbextend = 0; vbextend < 2; vbextend++) - { - for(int c = 0; c < 57; c++) - { - OutputState state; - - // determine which output state will be active in four cycles from now - switch(c) - { - case 0: case 1: case 2: case 3: state = OutputState::Blank; break; - case 4: case 5: case 6: case 7: state = OutputState::Sync; break; - case 8: case 9: case 10: case 11: state = OutputState::ColourBurst; break; - case 12: case 13: case 14: - case 15: case 16: state = OutputState::Blank; break; - - case 17: case 18: state = vbextend ? OutputState::Blank : OutputState::Pixel; break; - default: state = OutputState::Pixel; break; - } - - state_by_extend_time_[vbextend][c] = state; - } - } set_clock_rate(NTSC_clock_rate); } void Machine::setup_output(float aspect_ratio) { + tia_.reset(new TIA); speaker_.reset(new Speaker); - crt_.reset(new Outputs::CRT::CRT(228, 1, 263, Outputs::CRT::ColourSpace::YIQ, 228, 1, false, 1)); - crt_->set_output_device(Outputs::CRT::Television); - - // this is the NTSC phase offset function; see below for PAL - crt_->set_composite_sampling_function( - "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" - "{" - "uint c = texture(texID, coordinate).r;" - "uint y = c & 14u;" - "uint iPhase = (c >> 4);" - - "float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;" - "return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);" - "}"); - speaker_->set_input_rate((float)(get_clock_rate() / 38.0)); -} - -void Machine::switch_region() -{ - // the PAL function - crt_->set_composite_sampling_function( - "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" - "{" - "uint c = texture(texID, coordinate).r;" - "uint y = c & 14u;" - "uint iPhase = (c >> 4);" - - "uint direction = iPhase & 1u;" - "float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);" - "phaseOffset *= 6.283185308 / 12.0;" - "return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);" - "}"); - - crt_->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1, true); - - is_pal_region_ = true; - speaker_->set_input_rate((float)(get_clock_rate() / 38.0)); - set_clock_rate(PAL_clock_rate); } void Machine::close_output() { - crt_ = nullptr; + tia_ = nullptr; + speaker_ = nullptr; } Machine::~Machine() @@ -111,303 +45,6 @@ Machine::~Machine() close_output(); } -void Machine::update_timers(int mask) -{ - unsigned int upcoming_pointer_plus_4 = (upcoming_events_pointer_ + 4)%number_of_upcoming_events; - - object_counter_pointer_ = (object_counter_pointer_ + 1)%number_of_recorded_counters; - ObjectCounter *oneClockAgo = object_counter_[(object_counter_pointer_ - 1 + number_of_recorded_counters)%number_of_recorded_counters]; - ObjectCounter *twoClocksAgo = object_counter_[(object_counter_pointer_ - 2 + number_of_recorded_counters)%number_of_recorded_counters]; - ObjectCounter *now = object_counter_[object_counter_pointer_]; - - // grab the background now, for application in four clocks - if(mask & (1 << 5) && !(horizontal_timer_&3)) - { - unsigned int offset = 4 + horizontal_timer_ - (horizontalTimerPeriod - 160); - upcoming_events_[upcoming_pointer_plus_4].updates |= Event::Action::Playfield; - upcoming_events_[upcoming_pointer_plus_4].playfield_pixel = playfield_[(offset >> 2)%40]; - } - - if(mask & (1 << 4)) - { - // the ball becomes visible whenever it hits zero, regardless of whether its status - // is the result of a counter rollover or a programmatic reset, and there's a four - // clock delay on that triggering the start signal - now[4].count = (oneClockAgo[4].count + 1)%160; - now[4].pixel = oneClockAgo[4].pixel + 1; - if(!now[4].count) now[4].pixel = 0; - } - else - { - now[4] = oneClockAgo[4]; - } - - // check for player and missle triggers - for(int c = 0; c < 4; c++) - { - if(mask & (1 << c)) - { - // update the count - now[c].count = (oneClockAgo[c].count + 1)%160; - - uint8_t repeatMask = player_and_missile_size_[c&1] & 7; - ObjectCounter *rollover; - ObjectCounter *equality; - - if(c < 2) - { - // update the pixel - now[c].broad_pixel = oneClockAgo[c].broad_pixel + 1; - switch(repeatMask) - { - default: now[c].pixel = oneClockAgo[c].pixel + 1; break; - case 5: now[c].pixel = oneClockAgo[c].pixel + (now[c].broad_pixel&1); break; - case 7: now[c].pixel = oneClockAgo[c].pixel + (((now[c].broad_pixel | (now[c].broad_pixel >> 1))^1)&1); break; - } - - // check for a rollover six clocks ago or equality five clocks ago - rollover = twoClocksAgo; - equality = oneClockAgo; - } - else - { - // update the pixel - now[c].pixel = oneClockAgo[c].pixel + 1; - - // check for a rollover five clocks ago or equality four clocks ago - rollover = oneClockAgo; - equality = now; - } - - if( - (rollover[c].count == 159) || - (has_second_copy_[c&1] && equality[c].count == 16) || - (has_third_copy_[c&1] && equality[c].count == 32) || - (has_fourth_copy_[c&1] && equality[c].count == 64) - ) - { - now[c].pixel = 0; - now[c].broad_pixel = 0; - } - } - else - { - now[c] = oneClockAgo[c]; - } - } -} - -uint8_t Machine::get_output_pixel() -{ - ObjectCounter *now = object_counter_[object_counter_pointer_]; - - // get the playfield pixel - unsigned int offset = horizontal_timer_ - (horizontalTimerPeriod - 160); - uint8_t playfieldColour = ((playfield_control_&6) == 2) ? player_colour_[offset / 80] : playfield_colour_; - - // ball pixel - uint8_t ballPixel = 0; - if(now[4].pixel < ball_size_) { - ballPixel = ball_graphics_enable_[ball_graphics_selector_]; - } - - // determine the player and missile pixels - uint8_t playerPixels[2] = { 0, 0 }; - uint8_t missilePixels[2] = { 0, 0 }; - for(int c = 0; c < 2; c++) - { - if(player_graphics_[c] && now[c].pixel < 8) { - playerPixels[c] = (player_graphics_[player_graphics_selector_[c]][c] >> (now[c].pixel ^ player_reflection_mask_[c])) & 1; - } - - if(!missile_graphics_reset_[c] && now[c+2].pixel < missile_size_[c]) { - missilePixels[c] = missile_graphics_enable_[c]; - } - } - - // accumulate collisions - int pixel_mask = playerPixels[0] | (playerPixels[1] << 1) | (missilePixels[0] << 2) | (missilePixels[1] << 3) | (ballPixel << 4) | (playfield_output_ << 5); - collisions_[0] |= reported_collisions_[pixel_mask][0]; - collisions_[1] |= reported_collisions_[pixel_mask][1]; - collisions_[2] |= reported_collisions_[pixel_mask][2]; - collisions_[3] |= reported_collisions_[pixel_mask][3]; - collisions_[4] |= reported_collisions_[pixel_mask][4]; - collisions_[5] |= reported_collisions_[pixel_mask][5]; - collisions_[6] |= reported_collisions_[pixel_mask][6]; - collisions_[7] |= reported_collisions_[pixel_mask][7]; - - // apply appropriate priority to pick a colour - uint8_t playfield_pixel = playfield_output_ | ballPixel; - uint8_t outputColour = playfield_pixel ? playfieldColour : background_colour_; - - if(!(playfield_control_&0x04) || !playfield_pixel) { - if(playerPixels[1] || missilePixels[1]) outputColour = player_colour_[1]; - if(playerPixels[0] || missilePixels[0]) outputColour = player_colour_[0]; - } - - // return colour - return outputColour; -} - -void Machine::setup_reported_collisions() -{ - for(int c = 0; c < 64; c++) - { - memset(reported_collisions_[c], 0, 8); - - int playerPixels[2] = { c&1, (c >> 1)&1 }; - int missilePixels[2] = { (c >> 2)&1, (c >> 3)&1 }; - int ballPixel = (c >> 4)&1; - int playfield_pixel = (c >> 5)&1; - - if(playerPixels[0] | playerPixels[1]) { - reported_collisions_[c][0] |= ((missilePixels[0] & playerPixels[1]) << 7) | ((missilePixels[0] & playerPixels[0]) << 6); - reported_collisions_[c][1] |= ((missilePixels[1] & playerPixels[0]) << 7) | ((missilePixels[1] & playerPixels[1]) << 6); - - reported_collisions_[c][2] |= ((playfield_pixel & playerPixels[0]) << 7) | ((ballPixel & playerPixels[0]) << 6); - reported_collisions_[c][3] |= ((playfield_pixel & playerPixels[1]) << 7) | ((ballPixel & playerPixels[1]) << 6); - - reported_collisions_[c][7] |= ((playerPixels[0] & playerPixels[1]) << 7); - } - - if(playfield_pixel | ballPixel) { - reported_collisions_[c][4] |= ((playfield_pixel & missilePixels[0]) << 7) | ((ballPixel & missilePixels[0]) << 6); - reported_collisions_[c][5] |= ((playfield_pixel & missilePixels[1]) << 7) | ((ballPixel & missilePixels[1]) << 6); - - reported_collisions_[c][6] |= ((playfield_pixel & ballPixel) << 7); - } - - if(missilePixels[0] & missilePixels[1]) - reported_collisions_[c][7] |= (1 << 6); - } -} - -void Machine::output_pixels(unsigned int count) -{ - while(count--) - { - if(upcoming_events_[upcoming_events_pointer_].updates) - { - // apply any queued changes and flush the record - if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveSetup) - { - // schedule an extended left border - state_by_time_ = state_by_extend_time_[1]; - - // clear any ongoing moves - if(hmove_flags_) - { - for(int c = 0; c < number_of_upcoming_events; c++) - { - upcoming_events_[c].updates &= ~(Event::Action::HMoveCompare | Event::Action::HMoveDecrement); - } - } - - // schedule new moves - hmove_flags_ = 0x1f; - hmove_counter_ = 15; - - // follow-through into a compare immediately - upcoming_events_[upcoming_events_pointer_].updates |= Event::Action::HMoveCompare; - } - - if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveCompare) - { - for(int c = 0; c < 5; c++) - { - if(((object_motions_[c] >> 4)^hmove_counter_) == 7) - { - hmove_flags_ &= ~(1 << c); - } - } - if(hmove_flags_) - { - if(hmove_counter_) hmove_counter_--; - upcoming_events_[(upcoming_events_pointer_+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; - upcoming_events_[(upcoming_events_pointer_+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; - } - } - - if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::HMoveDecrement) - { - update_timers(hmove_flags_); - } - - if(upcoming_events_[upcoming_events_pointer_].updates & Event::Action::ResetCounter) - { - object_counter_[object_counter_pointer_][upcoming_events_[upcoming_events_pointer_].counter].count = 0; - } - - // zero out current update event - upcoming_events_[upcoming_events_pointer_].updates = 0; - } - - // progress to next event - upcoming_events_pointer_ = (upcoming_events_pointer_ + 1)%number_of_upcoming_events; - - // determine which output state is currently active - OutputState primary_state = state_by_time_[horizontal_timer_ >> 2]; - OutputState effective_state = primary_state; - - // update pixel timers - if(primary_state == OutputState::Pixel) update_timers(~0); - - // update the background chain - if(horizontal_timer_ >= 64 && horizontal_timer_ <= 160+64 && !(horizontal_timer_&3)) - { - playfield_output_ = next_playfield_output_; - next_playfield_output_ = playfield_[(horizontal_timer_ - 64) >> 2]; - } - - // if vsync is enabled, output the opposite of the automatic hsync output; - // also honour the vertical blank flag - if(vsync_enabled_) { - effective_state = (effective_state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; - } else if(vblank_enabled_ && effective_state == OutputState::Pixel) { - effective_state = OutputState::Blank; - } - - // decide what that means needs to be communicated to the CRT - last_output_state_duration_++; - if(effective_state != last_output_state_) { - switch(last_output_state_) { - case OutputState::Blank: crt_->output_blank(last_output_state_duration_); break; - case OutputState::Sync: crt_->output_sync(last_output_state_duration_); break; - case OutputState::ColourBurst: crt_->output_colour_burst(last_output_state_duration_, 96, 0); break; - case OutputState::Pixel: crt_->output_data(last_output_state_duration_, 1); break; - } - last_output_state_duration_ = 0; - last_output_state_ = effective_state; - - if(effective_state == OutputState::Pixel) { - output_buffer_ = crt_->allocate_write_area(160); - } else { - output_buffer_ = nullptr; - } - } - - // decide on a pixel colour if that's what's happening - if(effective_state == OutputState::Pixel) - { - uint8_t colour = get_output_pixel(); - if(output_buffer_) - { - *output_buffer_ = colour; - output_buffer_++; - } - } - - // advance horizontal timer, perform reset actions if desired - horizontal_timer_ = (horizontal_timer_ + 1) % horizontalTimerPeriod; - if(!horizontal_timer_) - { - // switch back to a normal length left border - state_by_time_ = state_by_extend_time_[0]; - set_ready_line(false); - } - } -} - unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { uint8_t returnValue = 0xff; @@ -418,12 +55,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // effect until the next read; therefore it isn't safe to assume that signalling ready immediately // skips to the end of the line. if(operation == CPU6502::BusOperation::Ready) { - unsigned int distance_to_end_of_ready = horizontalTimerPeriod - horizontal_timer_; - cycles_run_for = distance_to_end_of_ready; + cycles_run_for = (unsigned int)tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_); } - output_pixels(cycles_run_for); cycles_since_speaker_update_ += cycles_run_for; + cycles_since_video_update_ += cycles_run_for; if(operation != CPU6502::BusOperation::Ready) { @@ -475,7 +111,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x05: // missile 1 / playfield / ball collisions case 0x06: // ball / playfield collisions case 0x07: // player / player, missile / missile collisions - returnValue &= collisions_[decodedAddress]; + returnValue &= tia_->get_collision_flags(decodedAddress); break; case 0x08: @@ -493,194 +129,58 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } else { const uint16_t decodedAddress = address & 0x3f; switch(decodedAddress) { - case 0x00: - vsync_enabled_ = !!(*value & 0x02); - break; - case 0x01: vblank_enabled_ = !!(*value & 0x02); break; + case 0x00: update_video(); tia_->set_vsync(!!(*value & 0x02)); break; + case 0x01: update_video(); tia_->set_vblank(!!(*value & 0x02)); break; case 0x02: - if(horizontal_timer_) set_ready_line(true); + set_ready_line(true); + // TODO: if(horizontal_timer_) break; - case 0x03: - // Reset is delayed by four cycles. - horizontal_timer_ = horizontalTimerPeriod - 4; - + case 0x03: update_video(); tia_->reset_horizontal_counter(); break; // TODO: audio will now be out of synchronisation — fix - break; case 0x04: - case 0x05: { - int entry = decodedAddress - 0x04; - player_and_missile_size_[entry] = *value; - missile_size_[entry] = 1 << ((*value >> 4)&3); - - uint8_t repeatMask = (*value)&7; - has_second_copy_[entry] = (repeatMask == 1) || (repeatMask == 3); - has_third_copy_[entry] = (repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6); - has_fourth_copy_[entry] = (repeatMask == 4) || (repeatMask == 6); - } break; - + case 0x05: update_video(); tia_->set_player_number_and_size(decodedAddress - 0x04, *value); break; case 0x06: - case 0x07: player_colour_[decodedAddress - 0x06] = *value; break; - case 0x08: playfield_colour_ = *value; break; - case 0x09: background_colour_ = *value; break; - - case 0x0a: { - uint8_t old_playfield_control = playfield_control_; - playfield_control_ = *value; - ball_size_ = 1 << ((playfield_control_ >> 4)&3); - - // did the mirroring bit change? - if((playfield_control_^old_playfield_control)&1) { - if(playfield_control_&1) { - for(int c = 0; c < 20; c++) playfield_[c+20] = playfield_[19-c]; - } else { - memcpy(&playfield_[20], playfield_, 20); - } - } - } break; + case 0x07: update_video(); tia_->set_player_missile_colour(decodedAddress - 0x06, *value); break; + case 0x08: update_video(); tia_->set_playfield_ball_colour(*value); break; + case 0x09: update_video(); tia_->set_background_colour(*value); break; + case 0x0a: update_video(); tia_->set_playfield_control_and_ball_size(*value); break; case 0x0b: - case 0x0c: player_reflection_mask_[decodedAddress - 0x0b] = (*value)&8 ? 0 : 7; break; - + case 0x0c: update_video(); tia_->set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break; case 0x0d: - playfield_[0] = ((*value) >> 4)&1; - playfield_[1] = ((*value) >> 5)&1; - playfield_[2] = ((*value) >> 6)&1; - playfield_[3] = (*value) >> 7; - - if(playfield_control_&1) { - for(int c = 0; c < 4; c++) playfield_[39-c] = playfield_[c]; - } else { - memcpy(&playfield_[20], playfield_, 4); - } - break; case 0x0e: - playfield_[4] = (*value) >> 7; - playfield_[5] = ((*value) >> 6)&1; - playfield_[6] = ((*value) >> 5)&1; - playfield_[7] = ((*value) >> 4)&1; - playfield_[8] = ((*value) >> 3)&1; - playfield_[9] = ((*value) >> 2)&1; - playfield_[10] = ((*value) >> 1)&1; - playfield_[11] = (*value)&1; - - if(playfield_control_&1) { - for(int c = 0; c < 8; c++) playfield_[35-c] = playfield_[c+4]; - } else { - memcpy(&playfield_[24], &playfield_[4], 8); - } - break; - case 0x0f: - playfield_[19] = (*value) >> 7; - playfield_[18] = ((*value) >> 6)&1; - playfield_[17] = ((*value) >> 5)&1; - playfield_[16] = ((*value) >> 4)&1; - playfield_[15] = ((*value) >> 3)&1; - playfield_[14] = ((*value) >> 2)&1; - playfield_[13] = ((*value) >> 1)&1; - playfield_[12] = (*value)&1; - - if(playfield_control_&1) { - for(int c = 0; c < 8; c++) playfield_[27-c] = playfield_[c+12]; - } else { - memcpy(&playfield_[32], &playfield_[12], 8); - } - break; - - case 0x10: case 0x11: case 0x12: case 0x13: - case 0x14: - upcoming_events_[(upcoming_events_pointer_ + 4)%number_of_upcoming_events].updates |= Event::Action::ResetCounter; - upcoming_events_[(upcoming_events_pointer_ + 4)%number_of_upcoming_events].counter = decodedAddress - 0x10; - break; - - case 0x15: case 0x16: - update_audio(); - speaker_->set_control(decodedAddress - 0x15, *value); - break; - - case 0x17: case 0x18: - update_audio(); - speaker_->set_divider(decodedAddress - 0x17, *value); - break; - - case 0x19: case 0x1a: - update_audio(); - speaker_->set_volume(decodedAddress - 0x19, *value); - break; - - case 0x1c: - ball_graphics_enable_[1] = ball_graphics_enable_[0]; - case 0x1b: { - int index = decodedAddress - 0x1b; - player_graphics_[0][index] = *value; - player_graphics_[1][index^1] = player_graphics_[0][index^1]; - } break; + case 0x0f: update_video(); tia_->set_playfield(decodedAddress - 0x0d, *value); break; + case 0x10: + case 0x11: update_video(); tia_->set_player_position(decodedAddress - 0x10); break; + case 0x12: + case 0x13: update_video(); tia_->set_missile_position(decodedAddress - 0x13); break; + case 0x14: update_video(); tia_->set_ball_position(); break; + case 0x1b: + case 0x1c: update_video(); tia_->set_player_graphic(decodedAddress - 0x1b, *value); break; case 0x1d: - case 0x1e: - missile_graphics_enable_[decodedAddress - 0x1d] = ((*value) >> 1)&1; -// printf("e:%02x <- %c\n", decodedAddress - 0x1d, ((*value)&1) ? 'E' : '-'); - break; - case 0x1f: - ball_graphics_enable_[0] = ((*value) >> 1)&1; - break; - + case 0x1e: update_video(); tia_->set_missile_enable(decodedAddress - 0x1d, !!((*value)&2)); break; + case 0x1f: update_video(); tia_->set_ball_enable(!!((*value)&2)); break; case 0x20: - case 0x21: + case 0x21: update_video(); tia_->set_player_motion(decodedAddress - 0x20, *value); break; case 0x22: - case 0x23: - case 0x24: - object_motions_[decodedAddress - 0x20] = *value; - break; - - case 0x25: player_graphics_selector_[0] = (*value)&1; break; - case 0x26: player_graphics_selector_[1] = (*value)&1; break; - case 0x27: ball_graphics_selector_ = (*value)&1; break; - + case 0x23: update_video(); tia_->set_missile_motion(decodedAddress - 0x22, *value); break; + case 0x24: update_video(); tia_->set_ball_motion(*value); break; + case 0x25: + case 0x26: tia_->set_player_delay(decodedAddress - 0x25, !!((*value)&1)); break; + case 0x27: tia_->set_ball_delay(!!((*value)&1)); break; case 0x28: - case 0x29: - { - // TODO: this should properly mean setting a flag and propagating later, I think? - int index = decodedAddress - 0x28; - if(!(*value&0x02) && missile_graphics_reset_[index]) - { - object_counter_[object_counter_pointer_][index + 2].count = object_counter_[object_counter_pointer_][index].count; + case 0x29: update_video(); tia_->set_missile_position_to_player(decodedAddress - 0x28); break; + case 0x2a: update_video(); tia_->move(); break; + case 0x2b: update_video(); tia_->clear_motion(); break; + case 0x2c: update_video(); tia_->clear_collision_flags(); break; - uint8_t repeatMask = player_and_missile_size_[index] & 7; - int extra_offset; - switch(repeatMask) - { - default: extra_offset = 3; break; - case 5: extra_offset = 6; break; - case 7: extra_offset = 10; break; - } - - object_counter_[object_counter_pointer_][index + 2].count = (object_counter_[object_counter_pointer_][index + 2].count + extra_offset)%160; - } - missile_graphics_reset_[index] = !!((*value) & 0x02); -// printf("r:%02x <- %c\n", decodedAddress - 0x28, ((*value)&2) ? 'R' : '-'); - } - break; - - case 0x2a: { - // justification for +5: "we need to wait at least 71 [clocks] before the HMOVE operation is complete"; - // which will take 16*4 + 2 = 66 cycles from the first compare, implying the first compare must be - // in five cycles from now -// int start_pause = ((horizontal_timer_ + 3)&3) + 4; - upcoming_events_[(upcoming_events_pointer_ + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup; - } break; - case 0x2b: - object_motions_[0] = - object_motions_[1] = - object_motions_[2] = - object_motions_[3] = - object_motions_[4] = 0; - break; - case 0x2c: - collisions_[0] = collisions_[1] = collisions_[2] = - collisions_[3] = collisions_[4] = collisions_[5] = 0x3f; - collisions_[6] = 0x7f; - collisions_[7] = 0x3f; - break; + case 0x15: + case 0x16: update_audio(); speaker_->set_control(decodedAddress - 0x15, *value); break; + case 0x17: + case 0x18: update_audio(); speaker_->set_divider(decodedAddress - 0x17, *value); break; + case 0x19: + case 0x1a: update_audio(); speaker_->set_volume(decodedAddress - 0x19, *value); break; } } } @@ -764,7 +264,7 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) rom_pages_[3] = &rom_[3072 & romMask]; } -#pragma mark - Audio +#pragma mark - Audio and Video void Machine::update_audio() { @@ -774,9 +274,15 @@ void Machine::update_audio() cycles_since_speaker_update_ %= 114; } +void Machine::update_video() +{ + tia_->run_for_cycles((int)cycles_since_video_update_); + cycles_since_video_update_ = 0; +} + void Machine::synchronise() { update_audio(); + update_video(); speaker_->flush(); } - diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index b0320a267..d78bae584 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -15,6 +15,7 @@ #include "../CRTMachine.hpp" #include "PIA.hpp" #include "Speaker.hpp" +#include "TIA.hpp" #include "../ConfigurationTarget.hpp" #include "Atari2600Inputs.h" @@ -46,129 +47,31 @@ class Machine: // to satisfy CRTMachine::Machine virtual void setup_output(float aspect_ratio); virtual void close_output(); - virtual std::shared_ptr get_crt() { return crt_; } + virtual std::shared_ptr get_crt() { return tia_->get_crt(); } virtual std::shared_ptr get_speaker() { return speaker_; } virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } - // TODO: different rate for PAL private: uint8_t *rom_, *rom_pages_[4]; size_t rom_size_; - // the RIOT + // the RIOT and TIA PIA mos6532_; - - // playfield registers - uint8_t playfield_control_; - uint8_t playfield_colour_; - uint8_t background_colour_; - uint8_t playfield_[41]; - - // ... and derivatives - int ball_size_, missile_size_[2]; - - // delayed clock events - enum OutputState { - Sync, - Blank, - ColourBurst, - Pixel - }; - - struct Event { - enum Action { - Playfield = 1 << 0, - ResetCounter = 1 << 1, - - HMoveSetup = 1 << 2, - HMoveCompare = 1 << 3, - HMoveDecrement = 1 << 4, - }; - int updates; - - OutputState state; - uint8_t playfield_pixel; - int counter; - - Event() : updates(0), playfield_pixel(0) {} - } upcoming_events_[number_of_upcoming_events]; - unsigned int upcoming_events_pointer_; - - // object counters - struct ObjectCounter { - int count; // the counter value, multiplied by four, counting phase - int pixel; // for non-sprite objects, a count of cycles since the last counter reset; for sprite objects a count of pixels so far elapsed - int broad_pixel; // for sprite objects, a count of cycles since the last counter reset; otherwise unused - - ObjectCounter() : count(0), pixel(0), broad_pixel(0) {} - } object_counter_[number_of_recorded_counters][5]; - unsigned int object_counter_pointer_; - - // the latched playfield output - uint8_t playfield_output_, next_playfield_output_; - - // player registers - uint8_t player_colour_[2]; - uint8_t player_reflection_mask_[2]; - uint8_t player_graphics_[2][2]; - uint8_t player_graphics_selector_[2]; - - // object flags - bool has_second_copy_[2]; - bool has_third_copy_[2]; - bool has_fourth_copy_[2]; - uint8_t object_motions_[5]; // the value stored to this counter's motion register - - // player + missile registers - uint8_t player_and_missile_size_[2]; - - // missile registers - uint8_t missile_graphics_enable_[2]; - bool missile_graphics_reset_[2]; - - // ball registers - uint8_t ball_graphics_enable_[2]; - uint8_t ball_graphics_selector_; - - // graphics output - unsigned int horizontal_timer_; - bool vsync_enabled_, vblank_enabled_; - - // horizontal motion control - uint8_t hmove_counter_; - uint8_t hmove_flags_; + std::unique_ptr tia_; // joystick state uint8_t tia_input_value_[2]; - // collisions - uint8_t collisions_[8]; - - void output_pixels(unsigned int count); - uint8_t get_output_pixel(); - void update_timers(int mask); - // outputs - std::shared_ptr crt_; std::shared_ptr speaker_; - // current mode - bool is_pal_region_; - // speaker backlog accumlation counter unsigned int cycles_since_speaker_update_; void update_audio(); - // latched output state - unsigned int last_output_state_duration_; - OutputState state_by_extend_time_[2][57]; - OutputState *state_by_time_; - OutputState last_output_state_; - uint8_t *output_buffer_; - - // lookup table for collision reporting - uint8_t reported_collisions_[64][8]; - void setup_reported_collisions(); + // video backlog accumulation counter + unsigned int cycles_since_video_update_; + void update_video(); }; } diff --git a/Machines/Atari2600/TIA.cpp b/Machines/Atari2600/TIA.cpp index bac602803..f600468a7 100644 --- a/Machines/Atari2600/TIA.cpp +++ b/Machines/Atari2600/TIA.cpp @@ -7,3 +7,52 @@ // #include "TIA.hpp" + +using namespace Atari2600; + +TIA::TIA() +{ +/* + crt_.reset(new Outputs::CRT::CRT(228, 1, 263, Outputs::CRT::ColourSpace::YIQ, 228, 1, false, 1)); + crt_->set_output_device(Outputs::CRT::Television); + + // this is the NTSC phase offset function; see below for PAL + crt_->set_composite_sampling_function( + "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" + "{" + "uint c = texture(texID, coordinate).r;" + "uint y = c & 14u;" + "uint iPhase = (c >> 4);" + + "float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;" + "return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);" + "}"); + speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/ +} + +/*void Machine::switch_region() +{ + // the PAL function + crt_->set_composite_sampling_function( + "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" + "{" + "uint c = texture(texID, coordinate).r;" + "uint y = c & 14u;" + "uint iPhase = (c >> 4);" + + "uint direction = iPhase & 1u;" + "float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);" + "phaseOffset *= 6.283185308 / 12.0;" + "return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);" + "}"); + + crt_->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1, true); + + is_pal_region_ = true; + speaker_->set_input_rate((float)(get_clock_rate() / 38.0)); + set_clock_rate(PAL_clock_rate); +}*/ + + // justification for +5: "we need to wait at least 71 [clocks] before the HMOVE operation is complete"; + // which will take 16*4 + 2 = 66 cycles from the first compare, implying the first compare must be + // in five cycles from now diff --git a/Machines/Atari2600/TIA.hpp b/Machines/Atari2600/TIA.hpp index 24d3376ad..334c87875 100644 --- a/Machines/Atari2600/TIA.hpp +++ b/Machines/Atari2600/TIA.hpp @@ -10,17 +10,22 @@ #define TIA_hpp #include +#include "../CRTMachine.hpp" namespace Atari2600 { class TIA { public: + TIA(); + ~TIA(); + void run_for_cycles(int number_of_cycles); void set_vsync(bool vsync); void set_vblank(bool vblank); - void reset_horizontal_counter(); - int get_cycles_until_horizontal_blank(); + void reset_horizontal_counter(); // Reset is delayed by four cycles. + + int get_cycles_until_horizontal_blank(unsigned int from_offset); void set_background_colour(uint8_t colour); @@ -51,6 +56,11 @@ class TIA { uint8_t get_collision_flags(int offset); void clear_collision_flags(); + + virtual std::shared_ptr get_crt() { return crt_; } + + private: + std::shared_ptr crt_; }; }