diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index ea96ea83c..50745cb9b 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -12,97 +12,34 @@ using namespace Atari2600; namespace { - static const unsigned int horizontalTimerPeriod = 228; static const double NTSC_clock_rate = 1194720; static const double PAL_clock_rate = 1182298; } 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), + frame_record_pointer_(0), + is_ntsc_(true) { - 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); + tia_->get_crt()->set_delegate(this); } void Machine::close_output() { - crt_ = nullptr; + tia_ = nullptr; + speaker_ = nullptr; } Machine::~Machine() @@ -111,303 +48,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; @@ -417,13 +57,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take // 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; - } + if(operation == CPU6502::BusOperation::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 +113,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: @@ -483,6 +121,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x0a: case 0x0b: // TODO: pot ports + returnValue &= 0; break; case 0x0c: @@ -493,194 +132,55 @@ 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 0x02: - if(horizontal_timer_) set_ready_line(true); - break; - case 0x03: - // Reset is delayed by four cycles. - horizontal_timer_ = horizontalTimerPeriod - 4; + case 0x00: update_video(); tia_->set_sync(*value & 0x02); break; + case 0x01: update_video(); tia_->set_blank(*value & 0x02); break; + case 0x02: set_ready_line(true); break; + 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 - 0x12); 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, (*value)&2); 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; } } } @@ -699,6 +199,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } } + if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) set_ready_line(false); mos6532_.run_for_cycles(cycles_run_for / 3); return cycles_run_for / 3; @@ -764,7 +265,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 +275,59 @@ 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(); } +#pragma mark - CRT delegate + +void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) +{ + frame_records_[frame_record_pointer_].number_of_frames = number_of_frames; + frame_records_[frame_record_pointer_].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs; + const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]); + frame_record_pointer_ = (frame_record_pointer_ + 1) % number_of_frame_records; + + unsigned int total_number_of_frames = 0; + unsigned int total_number_of_unexpected_vertical_syncs = 0; + for(size_t c = 0; c < number_of_frame_records; c++) + { + if(!frame_records_[c].number_of_frames) return; + total_number_of_frames += frame_records_[c].number_of_frames; + total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs; + } + + if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) + { + for(size_t c = 0; c < number_of_frame_records; c++) + { + frame_records_[c].number_of_frames = 0; + frame_records_[c].number_of_unexpected_vertical_syncs = 0; + } + is_ntsc_ ^= true; + + double clock_rate; + if(is_ntsc_) + { + clock_rate = NTSC_clock_rate; + tia_->set_output_mode(TIA::OutputMode::NTSC); + } + else + { + clock_rate = PAL_clock_rate; + tia_->set_output_mode(TIA::OutputMode::PAL); + } + + speaker_->set_input_rate((float)(clock_rate / 38.0)); + set_clock_rate(clock_rate); + } +} diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index b0320a267..f0970348e 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" @@ -27,7 +28,8 @@ const unsigned int number_of_recorded_counters = 7; class Machine: public CPU6502::Processor, public CRTMachine::Machine, - public ConfigurationTarget::Machine { + public ConfigurationTarget::Machine, + public Outputs::CRT::Delegate { public: Machine(); @@ -46,129 +48,45 @@ 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 + + // to satisfy Outputs::CRT::Delegate + virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs); 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_; + // video backlog accumulation counter + unsigned int cycles_since_video_update_; + void update_video(); - // lookup table for collision reporting - uint8_t reported_collisions_[64][8]; - void setup_reported_collisions(); + // output frame rate tracker + struct FrameRecord + { + unsigned int number_of_frames; + unsigned int number_of_unexpected_vertical_syncs; + + FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {} + } frame_records_[4]; + unsigned int frame_record_pointer_; + bool is_ntsc_; }; } diff --git a/Machines/Atari2600/TIA.cpp b/Machines/Atari2600/TIA.cpp new file mode 100644 index 000000000..15fb2e939 --- /dev/null +++ b/Machines/Atari2600/TIA.cpp @@ -0,0 +1,778 @@ +// +// TIA.cpp +// Clock Signal +// +// Created by Thomas Harte on 28/01/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "TIA.hpp" +#include + +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; + + uint8_t reverse_table[256]; +} + +TIA::TIA(bool create_crt) : + horizontal_counter_(0), + pixels_start_location_(0), + output_mode_(0), + pixel_target_(nullptr), + background_{0, 0}, + background_half_mask_(0), + horizontal_blank_extend_(false), + collision_flags_(0) +{ + if(create_crt) + { + crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 + 1, 1, Outputs::CRT::DisplayType::NTSC60, 1)); + crt_->set_output_device(Outputs::CRT::Television); + set_output_mode(OutputMode::NTSC); + } + + for(int c = 0; c < 256; c++) + { + reverse_table[c] = (uint8_t)( + ((c & 0x01) << 7) | ((c & 0x02) << 5) | ((c & 0x04) << 3) | ((c & 0x08) << 1) | + ((c & 0x10) >> 1) | ((c & 0x20) >> 3) | ((c & 0x40) >> 5) | ((c & 0x80) >> 7) + ); + } + + 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); + + uint8_t collision_registers[8]; + collision_registers[0] = ((has_missile0 && has_player1) ? 0x80 : 0x00) | ((has_missile0 && has_player0) ? 0x40 : 0x00); + collision_registers[1] = ((has_missile1 && has_player0) ? 0x80 : 0x00) | ((has_missile1 && has_player1) ? 0x40 : 0x00); + collision_registers[2] = ((has_playfield && has_player0) ? 0x80 : 0x00) | ((has_ball && has_player0) ? 0x40 : 0x00); + collision_registers[3] = ((has_playfield && has_player1) ? 0x80 : 0x00) | ((has_ball && has_player1) ? 0x40 : 0x00); + collision_registers[4] = ((has_playfield && has_missile0) ? 0x80 : 0x00) | ((has_ball && has_missile0) ? 0x40 : 0x00); + collision_registers[5] = ((has_playfield && has_missile1) ? 0x80 : 0x00) | ((has_ball && has_missile1) ? 0x40 : 0x00); + collision_registers[6] = ((has_playfield && has_ball) ? 0x80 : 0x00); + collision_registers[7] = ((has_player0 && has_player1) ? 0x80 : 0x00) | ((has_missile0 && has_missile1) ? 0x40 : 0x00); + collision_flags_by_buffer_vaules_[c] = + (collision_registers[0] >> 6) | + (collision_registers[1] >> 4) | + (collision_registers[2] >> 2) | + (collision_registers[3] >> 0) | + (collision_registers[4] << 2) | + (collision_registers[5] << 4) | + (collision_registers[6] << 6) | + (collision_registers[7] << 8); + + // all priority modes show the background if nothing else is present + 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; + + // test 1 for standard priority: if there is a playfield or ball pixel, plot that colour + if(has_playfield || has_ball) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = (uint8_t)ColourIndex::PlayfieldBall; + } + + // test 1 for score mode: if there is a ball pixel, plot that colour + 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; + } + + // test 1 for on-top mode, test 2 for everbody else: if there is a player 1 or missile 1 pixel, plot that colour + 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; + } + + // in the right-hand side of score mode, the playfield has the same priority as player 1 + if(has_playfield) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayerMissile1; + } + + // next test for everybody: if there is a player 0 or missile 0 pixel, plot that colour instead + 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 this is the left-hand side of score mode, the playfield has the same priority as player 0 + if(has_playfield) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = (uint8_t)ColourIndex::PlayerMissile0; + } + + // a final test for 'on top' priority mode: if the playfield or ball are visible, prefer that colour to all others + if(has_playfield || has_ball) + { + colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayfieldBall; + } + } +} + +TIA::TIA() : TIA(true) {} + +TIA::TIA(std::function line_end_function) : TIA(false) +{ + line_end_function_ = line_end_function; +} + +void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) +{ + Outputs::CRT::DisplayType display_type; + + if(output_mode == OutputMode::NTSC) + { + 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);" + "}"); + display_type = Outputs::CRT::DisplayType::NTSC60; + } + else + { + 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);" + "}"); + display_type = Outputs::CRT::DisplayType::PAL50; + } + crt_->set_new_display_type(cycles_per_line * 2 + 1, display_type); + +/* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/ +} + +void TIA::run_for_cycles(int number_of_cycles) +{ + // if part way through a line, definitely perform a partial, at most up to the end of the line + if(horizontal_counter_) + { + int cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_); + output_for_cycles(cycles); + number_of_cycles -= cycles; + } + + // output full lines for as long as possible + while(number_of_cycles >= cycles_per_line) + { + output_line(); + number_of_cycles -= cycles_per_line; + } + + // partly start a new line if necessary + if(number_of_cycles) + { + output_for_cycles(number_of_cycles); + } +} + +void TIA::set_sync(bool sync) +{ + output_mode_ = (output_mode_ & ~sync_flag) | (sync ? sync_flag : 0); +} + +void TIA::set_blank(bool blank) +{ + output_mode_ = (output_mode_ & ~blank_flag) | (blank ? blank_flag : 0); +} + +void TIA::reset_horizontal_counter() +{ +} + +int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) +{ + return (cycles_per_line - (horizontal_counter_ + (int)from_offset) % cycles_per_line) % cycles_per_line; +} + +void TIA::set_background_colour(uint8_t colour) +{ + colour_palette_[(int)ColourIndex::Background] = colour; +} + +void TIA::set_playfield(uint16_t offset, uint8_t value) +{ + assert(offset >= 0 && offset < 3); + switch(offset) + { + case 0: + background_[1] = (background_[1] & 0x0ffff) | ((uint32_t)reverse_table[value & 0xf0] << 16); + background_[0] = (background_[0] & 0xffff0) | (uint32_t)(value >> 4); + break; + case 1: + background_[1] = (background_[1] & 0xf00ff) | ((uint32_t)value << 8); + background_[0] = (background_[0] & 0xff00f) | ((uint32_t)reverse_table[value] << 4); + break; + case 2: + background_[1] = (background_[1] & 0xfff00) | reverse_table[value]; + background_[0] = (background_[0] & 0x00fff) | ((uint32_t)value << 12); + break; + } +} + +void TIA::set_playfield_control_and_ball_size(uint8_t value) +{ + background_half_mask_ = value & 1; + 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; + } + + ball_.size = 1 << ((value >> 4)&3); +} + +void TIA::set_playfield_ball_colour(uint8_t colour) +{ + colour_palette_[(int)ColourIndex::PlayfieldBall] = colour; +} + +void TIA::set_player_number_and_size(int player, uint8_t value) +{ + assert(player >= 0 && player < 2); + int size = 0; + switch(value & 7) + { + case 0: case 1: case 2: case 3: case 4: + player_[player].copy_flags = value & 7; + break; + case 5: + size = 1; + player_[player].copy_flags = 0; + break; + case 6: + player_[player].copy_flags = 6; + break; + case 7: + size = 2; + player_[player].copy_flags = 0; + break; + } + + missile_[player].size = 1 << ((value >> 4)&3); + missile_[player].copy_flags = player_[player].copy_flags; + player_[player].adder = 4 >> size; +} + +void TIA::set_player_graphic(int player, uint8_t value) +{ + assert(player >= 0 && player < 2); + player_[player].graphic[1] = value; + player_[player^1].graphic[0] = player_[player^1].graphic[1]; + if(player) ball_.enabled[0] = ball_.enabled[1]; +} + +void TIA::set_player_reflected(int player, bool reflected) +{ + assert(player >= 0 && player < 2); + player_[player].reverse_mask = reflected ? 7 : 0; +} + +void TIA::set_player_delay(int player, bool delay) +{ + assert(player >= 0 && player < 2); + player_[player].graphic_index = delay ? 0 : 1; +} + +void TIA::set_player_position(int player) +{ + assert(player >= 0 && player < 2); + // players have an extra clock of delay before output and don't display upon reset; + // both aims are achieved by setting to -1 because: (i) it causes the clock to be + // one behind its real hardware value, creating the extra delay; and (ii) the player + // code is written to start a draw upon wraparound from 159 to 0, so -1 is the + // correct option rather than 159. + player_[player].position = -1; +} + +void TIA::set_player_motion(int player, uint8_t motion) +{ + assert(player >= 0 && player < 2); + player_[player].motion = (motion >> 4)&0xf; +} + +void TIA::set_player_missile_colour(int player, uint8_t colour) +{ + assert(player >= 0 && player < 2); + colour_palette_[(int)ColourIndex::PlayerMissile0 + player] = colour; +} + +void TIA::set_missile_enable(int missile, bool enabled) +{ + assert(missile >= 0 && missile < 2); + missile_[missile].enabled = enabled; +} + +void TIA::set_missile_position(int missile) +{ + assert(missile >= 0 && missile < 2); + missile_[missile].position = 0; +} + +void TIA::set_missile_position_to_player(int missile, bool lock) +{ + assert(missile >= 0 && missile < 2); + missile_[missile].locked_to_player = lock; + player_[missile].latched_pixel4_time = -1; +} + +void TIA::set_missile_motion(int missile, uint8_t motion) +{ + assert(missile >= 0 && missile < 2); + missile_[missile].motion = (motion >> 4)&0xf; +} + +void TIA::set_ball_enable(bool enabled) +{ + ball_.enabled[1] = enabled; +} + +void TIA::set_ball_delay(bool delay) +{ + ball_.enabled_index = delay ? 0 : 1; +} + +void TIA::set_ball_position() +{ + ball_.position = 0; + + // setting the ball position also triggers a draw + ball_.reset_pixels(0); +} + +void TIA::set_ball_motion(uint8_t motion) +{ + ball_.motion = (motion >> 4) & 0xf; +} + +void TIA::move() +{ + horizontal_blank_extend_ = true; + player_[0].is_moving = player_[1].is_moving = missile_[0].is_moving = missile_[1].is_moving = ball_.is_moving = true; + player_[0].motion_step = player_[1].motion_step = missile_[0].motion_step = missile_[1].motion_step = ball_.motion_step = 15; + player_[0].motion_time = player_[1].motion_time = missile_[0].motion_time = missile_[1].motion_time = ball_.motion_time = (horizontal_counter_ + 3) & ~3; +} + +void TIA::clear_motion() +{ + player_[0].motion = player_[1].motion = missile_[0].motion = missile_[1].motion = ball_.motion = 0; +} + +uint8_t TIA::get_collision_flags(int offset) +{ + 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) +{ + /* + Line timing is oriented around 0 being the start of the right-hand side vertical blank; + a wsync synchronises the CPU to horizontal_counter_ = 0. All timing below is in terms of the + NTSC colour clock. + + Therefore, each line is composed of: + + 16 cycles: blank ; -> 16 + 16 cycles: sync ; -> 32 + 16 cycles: colour burst ; -> 48 + 20 cycles: blank ; -> 68 + 8 cycles: blank or pixels, depending on whether the blank extend bit is set + 152 cycles: pixels + */ + int output_cursor = horizontal_counter_; + horizontal_counter_ += number_of_cycles; + + if(!output_cursor) + { + if(line_end_function_) line_end_function_(collision_buffer_); + memset(collision_buffer_, 0, sizeof(collision_buffer_)); + horizontal_blank_extend_ = false; + + ball_.motion_time %= 228; + player_[0].motion_time %= 228; + player_[1].motion_time %= 228; + missile_[0].motion_time %= 228; + missile_[1].motion_time %= 228; + } + + // accumulate an OR'd version of the output into the collision buffer + int latent_start = output_cursor + 4; + int latent_end = horizontal_counter_ + 4; + draw_playfield(latent_start, latent_end); + draw_object(player_[0], (uint8_t)CollisionType::Player0, output_cursor, horizontal_counter_); + draw_object(player_[1], (uint8_t)CollisionType::Player1, output_cursor, horizontal_counter_); + draw_missile(missile_[0], player_[0], (uint8_t)CollisionType::Missile0, output_cursor, horizontal_counter_); + draw_missile(missile_[1], player_[1], (uint8_t)CollisionType::Missile1, output_cursor, horizontal_counter_); + draw_object(ball_, (uint8_t)CollisionType::Ball, output_cursor, horizontal_counter_); + + // convert to television signals + +#define Period(function, target) \ + if(output_cursor < target) \ + { \ + if(horizontal_counter_ <= target) \ + { \ + if(crt_) crt_->function((unsigned int)((horizontal_counter_ - output_cursor) * 2)); \ + horizontal_counter_ %= cycles_per_line; \ + return; \ + } \ + else \ + { \ + if(crt_) crt_->function((unsigned int)((target - output_cursor) * 2)); \ + output_cursor = target; \ + } \ + } + + switch(output_mode_) + { + default: + Period(output_blank, 16) + Period(output_sync, 32) + Period(output_default_colour_burst, 48) + Period(output_blank, 68) + break; + case sync_flag: + case sync_flag | blank_flag: + Period(output_sync, 16) + Period(output_blank, 32) + Period(output_default_colour_burst, 48) + Period(output_sync, 228) + break; + } + +#undef Period + + if(output_mode_ & blank_flag) + { + if(pixel_target_) + { + output_pixels(pixels_start_location_, output_cursor); + if(crt_) crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2); + pixel_target_ = nullptr; + pixels_start_location_ = 0; + } + int duration = std::min(228, horizontal_counter_) - output_cursor; + if(crt_) crt_->output_blank((unsigned int)(duration * 2)); + } + else + { + if(!pixels_start_location_ && crt_) + { + pixels_start_location_ = output_cursor; + pixel_target_ = crt_->allocate_write_area(160); + } + + // convert that into pixels + if(pixel_target_) output_pixels(output_cursor, horizontal_counter_); + + // accumulate collision flags + while(output_cursor < horizontal_counter_) + { + collision_flags_ |= collision_flags_by_buffer_vaules_[collision_buffer_[output_cursor - first_pixel_cycle]]; + output_cursor++; + } + + if(horizontal_counter_ == cycles_per_line && crt_) + { + crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2); + pixel_target_ = nullptr; + pixels_start_location_ = 0; + } + } + + horizontal_counter_ %= cycles_per_line; +} + +void TIA::output_pixels(int start, int end) +{ + start = std::max(start, pixels_start_location_); + int target_position = start - pixels_start_location_; + + if(start < first_pixel_cycle+8 && horizontal_blank_extend_) + { + while(start < end && start < first_pixel_cycle+8) + { + pixel_target_[target_position] = 0; + start++; + target_position++; + } + } + + 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_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][buffer_value]]; + start++; + target_position++; + } + while(start < end) + { + uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle]; + pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][buffer_value]]; + start++; + target_position++; + } + } + 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_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]]; + start++; + target_position++; + } + } +} + +void TIA::output_line() +{ + switch(output_mode_) + { + default: + // TODO: optimise special case + output_for_cycles(cycles_per_line); + break; + case sync_flag: + case sync_flag | blank_flag: + if(crt_) + { + crt_->output_sync(32); + crt_->output_blank(32); + crt_->output_sync(392); + } + horizontal_blank_extend_ = false; + break; + case blank_flag: + if(crt_) + { + crt_->output_blank(32); + crt_->output_sync(32); + crt_->output_default_colour_burst(32); + crt_->output_blank(360); + } + horizontal_blank_extend_ = false; + break; + } +} + +#pragma mark - Playfield output + +void TIA::draw_playfield(int start, int end) +{ + // don't do anything if this window ends too early + if(end < first_pixel_cycle) return; + + // clip to drawable bounds + start = std::max(start, first_pixel_cycle); + end = std::min(end, 228); + + // proceed along four-pixel boundaries, plotting four pixels at a time + int aligned_position = (start + 3)&~3; + while(aligned_position < end) + { + int offset = (aligned_position - first_pixel_cycle) >> 2; + uint32_t value = ((background_[(offset/20)&background_half_mask_] >> (offset%20))&1) * 0x01010101; + *(uint32_t *)&collision_buffer_[aligned_position - first_pixel_cycle] |= value; + aligned_position += 4; + } +} + +#pragma mark - Motion + +template void TIA::perform_motion_step(T &object) +{ + if((object.motion_step ^ (object.motion ^ 8)) == 0xf) + object.is_moving = false; + else + { + if(object.position == 159) object.reset_pixels(0); + else if(object.position == 15 && object.copy_flags&1) object.reset_pixels(1); + else if(object.position == 31 && object.copy_flags&2) object.reset_pixels(2); + else if(object.position == 63 && object.copy_flags&4) object.reset_pixels(3); + else object.skip_pixels(1, object.motion_time); + object.position = (object.position + 1) % 160; + object.motion_step --; + object.motion_time += 4; + } +} + +template void TIA::perform_border_motion(T &object, int start, int end) +{ + while(object.is_moving && object.motion_time < end) + perform_motion_step(object); +} + +template void TIA::draw_object(T &object, const uint8_t collision_identity, int start, int end) +{ + int first_pixel = first_pixel_cycle - 4 + (horizontal_blank_extend_ ? 8 : 0); + + object.dequeue_pixels(collision_buffer_, collision_identity, end - first_pixel_cycle); + + // movement works across the entire screen, so do work that falls outside of the pixel area + if(start < first_pixel) + { + perform_border_motion(object, start, std::max(end, first_pixel)); + } + + // don't continue to do any drawing if this window ends too early + if(end < first_pixel) return; + if(start < first_pixel) start = first_pixel; + if(start >= end) return; + + // perform the visible part of the line, if any + if(start < 224) + { + draw_object_visible(object, collision_identity, start - first_pixel_cycle + 4, std::min(end - first_pixel_cycle + 4, 160), end - first_pixel_cycle); + } + + // move further if required + if(object.is_moving && end >= 224 && object.motion_time < end) + { + perform_motion_step(object); + } +} + +template void TIA::draw_object_visible(T &object, const uint8_t collision_identity, int start, int end, int time_now) +{ + // perform a miniature event loop on (i) triggering draws; (ii) drawing; and (iii) motion + int next_motion_time = object.motion_time - first_pixel_cycle + 4; + while(start < end) + { + int next_event_time = end; + + // is the next event a movement tick? + if(object.is_moving && next_motion_time < next_event_time) + { + next_event_time = next_motion_time; + } + + // is the next event a graphics trigger? + int next_copy = 160; + int next_copy_id = 0; + if(object.copy_flags) + { + if(object.position < 16 && object.copy_flags&1) + { + next_copy = 16; + next_copy_id = 1; + } else if(object.position < 32 && object.copy_flags&2) + { + next_copy = 32; + next_copy_id = 2; + } else if(object.position < 64 && object.copy_flags&4) + { + next_copy = 64; + next_copy_id = 3; + } + } + + int next_copy_time = start + next_copy - object.position; + if(next_copy_time < next_event_time) next_event_time = next_copy_time; + + // the decision is to progress by length + const int length = next_event_time - start; + + // enqueue a future intention to draw pixels if spitting them out now would violate accuracy; + // otherwise draw them now + if(object.enqueues && next_event_time > time_now) + { + if(start < time_now) + { + object.output_pixels(&collision_buffer_[start], time_now - start, collision_identity, start + first_pixel_cycle - 4); + object.enqueue_pixels(time_now, next_event_time, time_now + first_pixel_cycle - 4); + } + else + { + object.enqueue_pixels(start, next_event_time, start + first_pixel_cycle - 4); + } + } + else + { + object.output_pixels(&collision_buffer_[start], length, collision_identity, start + first_pixel_cycle - 4); + } + + // the next interesting event is after next_event_time cycles, so progress + object.position = (object.position + length) % 160; + start = next_event_time; + + // if the event is a motion tick, apply + if(object.is_moving && start == next_motion_time) + { + perform_motion_step(object); + next_motion_time += 4; + } + // if it's a draw trigger, trigger a draw + else if(start == next_copy_time) + { + object.reset_pixels(next_copy_id); + } + } +} + +#pragma mark - Missile drawing + +void TIA::draw_missile(Missile &missile, Player &player, const uint8_t collision_identity, int start, int end) +{ + if(!missile.locked_to_player || player.latched_pixel4_time < 0) + { + draw_object(missile, collision_identity, start, end); + } + else + { + draw_object(missile, collision_identity, start, player.latched_pixel4_time); + missile.position = 0; + draw_object(missile, collision_identity, player.latched_pixel4_time, end); + player.latched_pixel4_time = -1; + } +} diff --git a/Machines/Atari2600/TIA.hpp b/Machines/Atari2600/TIA.hpp new file mode 100644 index 000000000..a462fb54f --- /dev/null +++ b/Machines/Atari2600/TIA.hpp @@ -0,0 +1,344 @@ +// +// TIA.hpp +// Clock Signal +// +// Created by Thomas Harte on 28/01/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef TIA_hpp +#define TIA_hpp + +#include +#include "../CRTMachine.hpp" + +namespace Atari2600 { + +class TIA { + public: + TIA(); + // The supplied hook is for unit testing only; if instantiated with a line_end_function then it will + // be called with the latest collision buffer upon the conclusion of each line. What's a collision + // buffer? It's an implementation detail. If you're not writing a unit test, leave it alone. + TIA(std::function line_end_function); + + enum class OutputMode { + NTSC, PAL + }; + + /*! + Advances the TIA by @c number_of_cycles cycles. Any queued setters take effect in the + first cycle performed. + */ + void run_for_cycles(int number_of_cycles); + void set_output_mode(OutputMode output_mode); + + void set_sync(bool sync); + void set_blank(bool blank); + void reset_horizontal_counter(); // Reset is delayed by four cycles. + + /*! + @returns the number of cycles between (current TIA time) + from_offset to the current or + next horizontal blanking period. Returns numbers in the range [0, 227]. + */ + int get_cycles_until_horizontal_blank(unsigned int from_offset); + + void set_background_colour(uint8_t colour); + + void set_playfield(uint16_t offset, uint8_t value); + void set_playfield_control_and_ball_size(uint8_t value); + void set_playfield_ball_colour(uint8_t colour); + + void set_player_number_and_size(int player, uint8_t value); + void set_player_graphic(int player, uint8_t value); + void set_player_reflected(int player, bool reflected); + void set_player_delay(int player, bool delay); + void set_player_position(int player); + void set_player_motion(int player, uint8_t motion); + void set_player_missile_colour(int player, uint8_t colour); + + void set_missile_enable(int missile, bool enabled); + void set_missile_position(int missile); + void set_missile_position_to_player(int missile, bool lock); + void set_missile_motion(int missile, uint8_t motion); + + void set_ball_enable(bool enabled); + void set_ball_delay(bool delay); + void set_ball_position(); + void set_ball_motion(uint8_t motion); + + void move(); + void clear_motion(); + + uint8_t get_collision_flags(int offset); + void clear_collision_flags(); + + virtual std::shared_ptr get_crt() { return crt_; } + + private: + TIA(bool create_crt); + std::shared_ptr crt_; + std::function line_end_function_; + + // the master counter; counts from 0 to 228 with all visible pixels being in the final 160 + int horizontal_counter_; + + // contains flags to indicate whether sync or blank are currently active + int output_mode_; + + // keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer + alignas(alignof(uint32_t)) uint8_t collision_buffer_[160]; + enum class CollisionType : uint8_t { + 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, + PlayerMissile0, + PlayerMissile1 + }; + uint8_t colour_palette_[4]; + + // playfield state + int background_half_mask_; + 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 + // a mirror image of index 0. If the playfield is being displayed in + // mirroring mode, background_[0] will be output on the left and + // background_[1] on the right; otherwise background_[0] will be + // output twice. + + // objects + template struct Object { + // the two programmer-set values + int position; + int motion; + + // motion_step_ is the current motion counter value; motion_time_ is the next time it will fire + int motion_step; + int motion_time; + + // indicates whether this object is currently undergoing motion + bool is_moving; + + Object() : is_moving(false) {}; + }; + + // player state + struct Player: public Object { + Player() : copy_flags(0), graphic{0, 0}, reverse_mask(false), pixel_position(32), graphic_index(0), adder(4), queue_read_pointer_(0), queue_write_pointer_(0) {} + + int adder; + int copy_flags; // a bit field, corresponding to the first few values of NUSIZ + uint8_t graphic[2]; // the player graphic; 1 = new, 0 = current + int reverse_mask; // 7 for a reflected player, 0 for normal + int graphic_index; + + int pixel_position; + int latched_pixel4_time; + const bool enqueues = true; + + inline void skip_pixels(const int count, int from_horizontal_counter) + { + int old_pixel_position = pixel_position; + pixel_position = std::min(32, pixel_position + count * adder); + if(!copy_index_ && old_pixel_position < 16 && pixel_position >= 16) + { + latched_pixel4_time = from_horizontal_counter + (16 - old_pixel_position) / adder; + } + } + + inline void reset_pixels(int copy) + { + pixel_position = 0; + copy_index_ = copy; + } + + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) + { + output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask); + skip_pixels(count, from_horizontal_counter); + } + + void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) + { + while(queue_read_pointer_ != queue_write_pointer_) + { + uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start]; + if(queue_[queue_read_pointer_].end > time_now) + { + const int length = time_now - queue_[queue_read_pointer_].start; + output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask); + queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder; + queue_[queue_read_pointer_].start = time_now; + return; + } + else + { + output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask); + } + queue_read_pointer_ = (queue_read_pointer_ + 1)&3; + } + } + + void enqueue_pixels(const int start, const int end, int from_horizontal_counter) + { + queue_[queue_write_pointer_].start = start; + queue_[queue_write_pointer_].end = end; + queue_[queue_write_pointer_].pixel_position = pixel_position; + queue_[queue_write_pointer_].adder = adder; + queue_[queue_write_pointer_].reverse_mask = reverse_mask; + queue_write_pointer_ = (queue_write_pointer_ + 1)&3; + skip_pixels(end - start, from_horizontal_counter); + } + + private: + int copy_index_; + struct QueuedPixels + { + int start, end; + int pixel_position; + int adder; + int reverse_mask; + } queue_[4]; + int queue_read_pointer_, queue_write_pointer_; + + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int pixel_position, int adder, int reverse_mask) + { + if(pixel_position == 32 || !graphic[graphic_index]) return; + int output_cursor = 0; + while(pixel_position < 32 && output_cursor < count) + { + int shift = (pixel_position >> 2) ^ reverse_mask; + target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity; + output_cursor++; + pixel_position += adder; + } + } + + } player_[2]; + + // common actor for things that appear as a horizontal run of pixels + struct HorizontalRun: public Object { + int pixel_position; + int size; + const bool enqueues = false; + + inline void skip_pixels(const int count, int from_horizontal_counter) + { + pixel_position = std::max(0, pixel_position - count); + } + + inline void reset_pixels(int copy) + { + pixel_position = size; + } + + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) + { + int output_cursor = 0; + while(pixel_position && output_cursor < count) + { + target[output_cursor] |= collision_identity; + output_cursor++; + pixel_position--; + } + } + + void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {} + void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {} + + HorizontalRun() : pixel_position(0), size(1) {} + }; + + + // missile state + struct Missile: public HorizontalRun { + bool enabled; + bool locked_to_player; + int copy_flags; + + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) + { + if(!pixel_position) return; + if(enabled && !locked_to_player) + { + HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter); + } + else + { + skip_pixels(count, from_horizontal_counter); + } + } + + Missile() : enabled(false), copy_flags(0) {} + } missile_[2]; + + // ball state + struct Ball: public HorizontalRun { + bool enabled[2]; + int enabled_index; + const int copy_flags = 0; + + inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) + { + if(!pixel_position) return; + if(enabled[enabled_index]) + { + HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter); + } + else + { + skip_pixels(count, from_horizontal_counter); + } + } + + Ball() : enabled_index(0), enabled{false, false} {} + } ball_; + + // motion + bool horizontal_blank_extend_; + template void perform_border_motion(T &object, int start, int end); + template void perform_motion_step(T &object); + + // drawing methods and state + void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end); + template void draw_object(T &, const uint8_t collision_identity, int start, int end); + template void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now); + inline void draw_playfield(int start, int end); + + inline void output_for_cycles(int number_of_cycles); + inline void output_line(); + + int pixels_start_location_; + uint8_t *pixel_target_; + inline void output_pixels(int start, int end); +}; + +} + +#endif /* TIA_hpp */ diff --git a/Machines/Electron/Video.cpp b/Machines/Electron/Video.cpp index 29b95b730..2a1f4688c 100644 --- a/Machines/Electron/Video.cpp +++ b/Machines/Electron/Video.cpp @@ -115,7 +115,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) } else { - unsigned int divider = 0; + unsigned int divider = 1; switch(screen_mode_) { case 0: case 3: divider = 2; break; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 18332f71c..e4c35ff0d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; }; 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; }; + 4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2AF8681E513FC20027EE29 /* TIATests.mm */; }; 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; }; 4B2C45421E3C3896002A2389 /* cartridge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B2C45411E3C3896002A2389 /* cartridge.png */; }; @@ -396,6 +397,7 @@ 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; }; 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; }; + 4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; }; 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; }; 4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; }; 4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; }; @@ -473,6 +475,7 @@ 4B2A539C1D117D36003C6002 /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = ""; }; 4B2A539D1D117D36003C6002 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = ""; }; 4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = ""; }; + 4B2AF8681E513FC20027EE29 /* TIATests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TIATests.mm; sourceTree = ""; }; 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = ""; }; 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = ""; }; 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = ""; }; @@ -923,6 +926,8 @@ 4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = ""; }; 4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = ""; }; 4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = ""; }; + 4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = ""; }; + 4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = ""; }; 4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = ""; }; 4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = ""; }; 4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = ""; }; @@ -1094,10 +1099,12 @@ children = ( 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */, 4BEA52641DF3472B007E74F2 /* Speaker.cpp */, + 4BE7C9161E3D397100A5496D /* TIA.cpp */, 4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */, 4B2E2D981C3A06EC00138695 /* Atari2600.hpp */, - 4BEA52651DF3472B007E74F2 /* Speaker.hpp */, 4BEA52671DF34909007E74F2 /* PIA.hpp */, + 4BEA52651DF3472B007E74F2 /* Speaker.hpp */, + 4BE7C9171E3D397100A5496D /* TIA.hpp */, ); path = Atari2600; sourceTree = ""; @@ -1719,6 +1726,7 @@ 4B121F941E05E66800BFDA12 /* PCMPatchedTrackTests.mm */, 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */, 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */, + 4B2AF8681E513FC20027EE29 /* TIATests.mm */, 4B1D08051E0F7A1100763741 /* TimeTests.mm */, 4BB73EB81B587A5100552FC2 /* Info.plist */, 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */, @@ -2434,6 +2442,7 @@ 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, 4B5A12571DD55862007A2231 /* Disassembler6502.cpp in Sources */, + 4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */, 4B1E85751D170228001EF87D /* Typer.cpp in Sources */, 4BF829631D8F536B001BAE39 /* SSD.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, @@ -2488,6 +2497,7 @@ 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */, 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */, 4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */, + 4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */, 4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */, 4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */, 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m index efc3719b0..83280ad8b 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m @@ -12,11 +12,13 @@ #define AudioQueueBufferMaxLength 8192 #define NumberOfStoredAudioQueueBuffer 16 +static NSLock *CSAudioQueueDeallocLock; + @implementation CSAudioQueue { AudioQueueRef _audioQueue; - AudioQueueBufferRef _storedBuffers[NumberOfStoredAudioQueueBuffer]; + NSLock *_storedBuffersLock; } #pragma mark - AudioQueue callbacks @@ -25,18 +27,18 @@ { [self.delegate audioQueueIsRunningDry:self]; - @synchronized(self) + [_storedBuffersLock lock]; + for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++) { - for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++) + if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity) { - if(!_storedBuffers[c] || buffer->mAudioDataBytesCapacity > _storedBuffers[c]->mAudioDataBytesCapacity) - { - if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]); - _storedBuffers[c] = buffer; - return; - } + if(_storedBuffers[c]) AudioQueueFreeBuffer(_audioQueue, _storedBuffers[c]); + _storedBuffers[c] = buffer; + [_storedBuffersLock unlock]; + return; } } + [_storedBuffersLock unlock]; AudioQueueFreeBuffer(_audioQueue, buffer); } @@ -45,7 +47,11 @@ static void audioOutputCallback( AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { - [(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer]; + if([CSAudioQueueDeallocLock tryLock]) + { + [(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer]; + [CSAudioQueueDeallocLock unlock]; + } } #pragma mark - Standard object lifecycle @@ -56,6 +62,12 @@ static void audioOutputCallback( if(self) { + if(!CSAudioQueueDeallocLock) + { + CSAudioQueueDeallocLock = [[NSLock alloc] init]; + } + _storedBuffersLock = [[NSLock alloc] init]; + _samplingRate = samplingRate; // determine preferred buffer sizes @@ -104,7 +116,9 @@ static void audioOutputCallback( - (void)dealloc { - if(_audioQueue) AudioQueueDispose(_audioQueue, NO); + [CSAudioQueueDeallocLock lock]; + if(_audioQueue) AudioQueueDispose(_audioQueue, true); + [CSAudioQueueDeallocLock unlock]; } #pragma mark - Audio enqueuer @@ -113,28 +127,28 @@ static void audioOutputCallback( { size_t bufferBytes = lengthInSamples * sizeof(int16_t); - @synchronized(self) + [_storedBuffersLock lock]; + for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++) { - for(int c = 0; c < NumberOfStoredAudioQueueBuffer; c++) + if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes) { - if(_storedBuffers[c] && _storedBuffers[c]->mAudioDataBytesCapacity >= bufferBytes) - { - memcpy(_storedBuffers[c]->mAudioData, buffer, bufferBytes); - _storedBuffers[c]->mAudioDataByteSize = (UInt32)bufferBytes; + memcpy(_storedBuffers[c]->mAudioData, buffer, bufferBytes); + _storedBuffers[c]->mAudioDataByteSize = (UInt32)bufferBytes; - AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL); - _storedBuffers[c] = NULL; - return; - } + AudioQueueEnqueueBuffer(_audioQueue, _storedBuffers[c], 0, NULL); + _storedBuffers[c] = NULL; + [_storedBuffersLock unlock]; + return; } - - AudioQueueBufferRef newBuffer; - AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer); - memcpy(newBuffer->mAudioData, buffer, bufferBytes); - newBuffer->mAudioDataByteSize = (UInt32)bufferBytes; - - AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL); } + [_storedBuffersLock unlock]; + + AudioQueueBufferRef newBuffer; + AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes * 2, &newBuffer); + memcpy(newBuffer->mAudioData, buffer, bufferBytes); + newBuffer->mAudioDataByteSize = (UInt32)bufferBytes; + + AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL); } #pragma mark - Sampling Rate getters diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAtari2600.mm index bf268669b..08c003fbc 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAtari2600.mm @@ -11,42 +11,8 @@ #include "Atari2600.hpp" #import "CSMachine+Subclassing.h" -@interface CSAtari2600 () -- (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs; -@end - -struct CRTDelegate: public Outputs::CRT::Delegate { - __weak CSAtari2600 *atari2600; - void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) { - [atari2600 crt:crt didEndBatchOfFrames:number_of_frames withUnexpectedVerticalSyncs:number_of_unexpected_vertical_syncs]; - } -}; - @implementation CSAtari2600 { Atari2600::Machine _atari2600; - CRTDelegate _crtDelegate; - - int _frameCount; - int _hitCount; - BOOL _didDecideRegion; - int _batchesReceived; -} - -- (void)crt:(Outputs::CRT::CRT *)crt didEndBatchOfFrames:(unsigned int)numberOfFrames withUnexpectedVerticalSyncs:(unsigned int)numberOfUnexpectedSyncs { - if(!_didDecideRegion) - { - _batchesReceived++; - if(_batchesReceived == 2) - { - _didDecideRegion = YES; - if(numberOfUnexpectedSyncs >= numberOfFrames >> 1) - { - [self.view performWithGLContext:^{ - _atari2600.switch_region(); - }]; - } - } - } } - (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed { @@ -78,8 +44,6 @@ struct CRTDelegate: public Outputs::CRT::Delegate { - (void)setupOutputWithAspectRatio:(float)aspectRatio { @synchronized(self) { [super setupOutputWithAspectRatio:aspectRatio]; - _atari2600.get_crt()->set_delegate(&_crtDelegate); - _crtDelegate.atari2600 = self; } } diff --git a/OSBindings/Mac/Clock SignalTests/TIATests.mm b/OSBindings/Mac/Clock SignalTests/TIATests.mm new file mode 100644 index 000000000..5ebaee5fe --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/TIATests.mm @@ -0,0 +1,119 @@ +// +// TIATests.m +// Clock Signal +// +// Created by Thomas Harte on 12/02/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#import + +#include "TIA.hpp" + +static uint8_t *line; +static void receive_line(uint8_t *next_line) +{ + line = next_line; +} + +@interface TIATests : XCTestCase +@end + +@implementation TIATests { + std::unique_ptr _tia; +} + +- (void)setUp +{ + [super setUp]; + std::function function = receive_line; + _tia.reset(new Atari2600::TIA(function)); + line = nullptr; + + _tia->set_playfield(0, 0x00); + _tia->set_playfield(1, 0x00); + _tia->set_playfield(2, 0x00); + _tia->set_player_graphic(0, 0x00); + _tia->set_player_graphic(1, 0x00); + _tia->set_ball_enable(false); + _tia->set_missile_enable(0, false); + _tia->set_missile_enable(1, false); +} + +- (void)testReflectedPlayfield +{ + // set reflected, bit pattern 1000 + _tia->set_playfield_control_and_ball_size(1); + _tia->set_playfield(0, 0x10); + _tia->set_playfield(1, 0xf0); + _tia->set_playfield(2, 0x0e); + _tia->run_for_cycles(228); + + XCTAssert(line != nullptr, @"228 cycles should have ended the line"); + + uint8_t expected_line[] = { + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 + }; + XCTAssert(!memcmp(expected_line, line, sizeof(expected_line))); +} + +- (void)testRepeatedPlayfield +{ + // set reflected, bit pattern 1000 + _tia->set_playfield_control_and_ball_size(0); + _tia->set_playfield(0, 0x10); + _tia->set_playfield(1, 0xf0); + _tia->set_playfield(2, 0x0e); + + _tia->run_for_cycles(228); + XCTAssert(line != nullptr, @"228 cycles should have ended the line"); + + uint8_t expected_line[] = { + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + XCTAssert(!memcmp(expected_line, line, sizeof(expected_line))); +} + +- (void)testSinglePlayer +{ + // set a player graphic, reset position so that it'll appear from column 1 + _tia->set_player_graphic(0, 0xff); + _tia->set_player_position(0); + + _tia->run_for_cycles(228); + uint8_t first_expected_line[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + XCTAssert(line != nullptr, @"228 cycles should have ended the line"); + XCTAssert(!memcmp(first_expected_line, line, sizeof(first_expected_line))); + line = nullptr; + + _tia->run_for_cycles(228); + uint8_t second_expected_line[] = { + 0, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + XCTAssert(line != nullptr, @"228 cycles should have ended the line"); + XCTAssert(!memcmp(second_expected_line, line, sizeof(second_expected_line))); +} + +@end diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index f1d0c3f88..550a9d624 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -222,6 +222,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo (*(uint16_t *)&input_buffer[position + SourceVertexOffsetOfOutputStart + 2]) = output_y; } }); + colour_burst_amplitude_ = 0; } is_writing_composite_run_ ^= true; } @@ -284,7 +285,7 @@ void CRT::output_scan(const Scan *const scan) // simplified colour burst logic: if it's within the back porch we'll take it if(scan->type == Scan::Type::ColourBurst) { - if(horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) + if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) { unsigned int position_phase = (horizontal_flywheel_->get_current_time() * colour_cycle_numerator_ * 256) / phase_denominator_; colour_burst_phase_ = (position_phase + scan->phase) & 255; diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index af06af273..559c69a3e 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -81,10 +81,19 @@ class CRT { uint16_t x1, y; } output_run_; - // The delegate + // the delegate Delegate *delegate_; unsigned int frames_since_last_delegate_call_; + // queued tasks for the OpenGL queue; performed before the next draw + std::mutex function_mutex_; + std::vector> enqueued_openGL_functions_; + inline void enqueue_openGL_function(const std::function &function) + { + std::lock_guard function_guard(function_mutex_); + enqueued_openGL_functions_.push_back(function); + } + public: /*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency. The requested number of buffers, each with the requested number of bytes per pixel, @@ -204,6 +213,14 @@ class CRT { */ inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { + { + std::lock_guard function_guard(function_mutex_); + for(std::function function : enqueued_openGL_functions_) + { + function(); + } + enqueued_openGL_functions_.clear(); + } openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty); } @@ -216,7 +233,9 @@ class CRT { */ inline void set_openGL_context_will_change(bool should_delete_resources) { - openGL_output_builder_.set_openGL_context_will_change(should_delete_resources); + enqueue_openGL_function([should_delete_resources, this] { + openGL_output_builder_.set_openGL_context_will_change(should_delete_resources); + }); } /*! Sets a function that will map from whatever data the machine provided to a composite signal. @@ -226,9 +245,11 @@ class CRT { that evaluates to the composite signal level as a function of a source buffer, sampling location, colour carrier phase and amplitude. */ - inline void set_composite_sampling_function(const char *shader) + inline void set_composite_sampling_function(const std::string &shader) { - openGL_output_builder_.set_composite_sampling_function(shader); + enqueue_openGL_function([shader, this] { + openGL_output_builder_.set_composite_sampling_function(shader); + }); } /*! Sets a function that will map from whatever data the machine provided to an RGB signal. @@ -244,19 +265,25 @@ class CRT { * `vec2 coordinate` representing the source buffer location to sample from in the range [0, 1); and * `vec2 icoordinate` representing the source buffer location to sample from as a pixel count, for easier multiple-pixels-per-byte unpacking. */ - inline void set_rgb_sampling_function(const char *shader) + inline void set_rgb_sampling_function(const std::string &shader) { - openGL_output_builder_.set_rgb_sampling_function(shader); + enqueue_openGL_function([shader, this] { + openGL_output_builder_.set_rgb_sampling_function(shader); + }); } inline void set_output_device(OutputDevice output_device) { - openGL_output_builder_.set_output_device(output_device); + enqueue_openGL_function([output_device, this] { + openGL_output_builder_.set_output_device(output_device); + }); } inline void set_visible_area(Rect visible_area) { - openGL_output_builder_.set_visible_area(visible_area); + enqueue_openGL_function([visible_area, this] { + openGL_output_builder_.set_visible_area(visible_area); + }); } Rect get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio); diff --git a/Outputs/CRT/Internals/ArrayBuilder.cpp b/Outputs/CRT/Internals/ArrayBuilder.cpp index afda1c9b6..b91246d45 100644 --- a/Outputs/CRT/Internals/ArrayBuilder.cpp +++ b/Outputs/CRT/Internals/ArrayBuilder.cpp @@ -41,7 +41,7 @@ void ArrayBuilder::flush(const std::function -#include #include #include "CRTOpenGL.hpp" @@ -17,7 +16,8 @@ using namespace Outputs::CRT; -namespace { +namespace +{ static const GLenum source_data_texture_unit = GL_TEXTURE0; static const GLenum pixel_accumulation_texture_unit = GL_TEXTURE1; @@ -31,8 +31,6 @@ namespace { OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) : visible_area_(Rect(0, 0, 1, 1)), composite_src_output_y_(0), - composite_shader_(nullptr), - rgb_shader_(nullptr), last_output_width_(0), last_output_height_(0), fence_(nullptr), @@ -78,9 +76,6 @@ OpenGLOutputBuilder::OpenGLOutputBuilder(size_t bytes_per_pixel) : OpenGLOutputBuilder::~OpenGLOutputBuilder() { glDeleteVertexArrays(1, &output_vertex_array_); - - free(composite_shader_); - free(rgb_shader_); } bool OpenGLOutputBuilder::get_is_television_output() @@ -275,17 +270,17 @@ void OpenGLOutputBuilder::set_openGL_context_will_change(bool should_delete_reso output_mutex_.unlock(); } -void OpenGLOutputBuilder::set_composite_sampling_function(const char *shader) +void OpenGLOutputBuilder::set_composite_sampling_function(const std::string &shader) { std::lock_guard lock_guard(output_mutex_); - composite_shader_ = strdup(shader); + composite_shader_ = shader; reset_all_OpenGL_state(); } -void OpenGLOutputBuilder::set_rgb_sampling_function(const char *shader) +void OpenGLOutputBuilder::set_rgb_sampling_function(const std::string &shader) { std::lock_guard lock_guard(output_mutex_); - rgb_shader_ = strdup(shader); + rgb_shader_ = shader; reset_all_OpenGL_state(); } @@ -321,7 +316,7 @@ void OpenGLOutputBuilder::prepare_composite_input_shaders() void OpenGLOutputBuilder::prepare_rgb_input_shaders() { - if(rgb_shader_) + if(rgb_shader_.size()) { rgb_input_shader_program_ = OpenGL::IntermediateShader::make_rgb_source_shader(rgb_shader_); rgb_input_shader_program_->set_source_texture_unit(source_data_texture_unit); diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index b14d49e28..cb980b8fc 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -47,8 +47,8 @@ class OpenGLOutputBuilder { Rect visible_area_; // Other things the caller may have provided. - char *composite_shader_; - char *rgb_shader_; + std::string composite_shader_; + std::string rgb_shader_; // Methods used by the OpenGL code void prepare_output_shader(); @@ -148,8 +148,8 @@ class OpenGLOutputBuilder { void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty); void set_openGL_context_will_change(bool should_delete_resources); - void set_composite_sampling_function(const char *shader); - void set_rgb_sampling_function(const char *shader); + void set_composite_sampling_function(const std::string &shader); + void set_rgb_sampling_function(const std::string &shader); void set_output_device(OutputDevice output_device); void set_timing(unsigned int input_frequency, unsigned int cycles_per_line, unsigned int height_of_display, unsigned int horizontal_scan_period, unsigned int vertical_scan_period, unsigned int vertical_period_divider); }; diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp index 870eef24f..40c8bebbe 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp @@ -26,7 +26,7 @@ namespace { }; } -std::unique_ptr IntermediateShader::make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition) +std::unique_ptr IntermediateShader::make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition) { const char *sampler_type = use_usampler ? "usampler2D" : "sampler2D"; const char *input_variable = input_is_inputPosition ? "inputPosition" : "outputPosition"; @@ -111,12 +111,13 @@ std::unique_ptr IntermediateShader::make_shader(const char * return shader; } -std::unique_ptr IntermediateShader::make_source_conversion_shader(const char *composite_shader, const char *rgb_shader) +std::unique_ptr IntermediateShader::make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader) { - char *composite_sample = (char *)composite_shader; - if(!composite_sample) + char *derived_composite_sample = nullptr; + const char *composite_sample = composite_shader.c_str(); + if(!composite_shader.size()) { - asprintf(&composite_sample, + asprintf(&derived_composite_sample, "%s\n" "uniform mat3 rgbToLumaChroma;" "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" @@ -126,7 +127,8 @@ std::unique_ptr IntermediateShader::make_source_conversion_s "vec2 quadrature = vec2(cos(phase), -sin(phase)) * amplitude;" "return dot(lumaChromaColour, vec3(1.0 - amplitude, quadrature));" "}", - rgb_shader); + rgb_shader.c_str()); + composite_sample = derived_composite_sample; } char *fragment_shader; @@ -148,7 +150,7 @@ std::unique_ptr IntermediateShader::make_source_conversion_s "fragColour = vec4(composite_sample(texID, inputPositionsVarying[5], iInputPositionVarying, phaseAndAmplitudeVarying.x, phaseAndAmplitudeVarying.y));" "}" , composite_sample); - if(!composite_shader) free(composite_sample); + free(derived_composite_sample); std::unique_ptr shader = make_shader(fragment_shader, true, true); free(fragment_shader); @@ -156,7 +158,7 @@ std::unique_ptr IntermediateShader::make_source_conversion_s return shader; } -std::unique_ptr IntermediateShader::make_rgb_source_shader(const char *rgb_shader) +std::unique_ptr IntermediateShader::make_rgb_source_shader(const std::string &rgb_shader) { char *fragment_shader; asprintf(&fragment_shader, @@ -176,7 +178,7 @@ std::unique_ptr IntermediateShader::make_rgb_source_shader(c "{" "fragColour = rgb_sample(texID, inputPositionsVarying[5], iInputPositionVarying);" "}" - , rgb_shader); + , rgb_shader.c_str()); std::unique_ptr shader = make_shader(fragment_shader, true, true); free(fragment_shader); diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp index 13dedc2c4..fc79e5fd7 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.hpp @@ -25,13 +25,13 @@ public: converting them to single-channel composite values using @c composite_shader if supplied or @c rgb_shader and a reference composite conversion if @c composite_shader is @c nullptr. */ - static std::unique_ptr make_source_conversion_shader(const char *composite_shader, const char *rgb_shader); + static std::unique_ptr make_source_conversion_shader(const std::string &composite_shader, const std::string &rgb_shader); /*! Constructs and returns an intermediate shader that will take runs from the inputPositions, converting them to RGB values using @c rgb_shader. */ - static std::unique_ptr make_rgb_source_shader(const char *rgb_shader); + static std::unique_ptr make_rgb_source_shader(const std::string &rgb_shader); /*! Constructs and returns an intermediate shader that will read composite samples from the R channel, @@ -95,7 +95,7 @@ public: void set_is_double_height(bool is_double_height, float input_offset = 0.0f, float output_offset = 0.0f); private: - static std::unique_ptr make_shader(const char *fragment_shader, bool use_usampler, bool input_is_inputPosition); + static std::unique_ptr make_shader(const std::string &fragment_shader, bool use_usampler, bool input_is_inputPosition); }; } diff --git a/Outputs/CRT/Internals/Shaders/Shader.cpp b/Outputs/CRT/Internals/Shaders/Shader.cpp index 15b8bb43c..ddc723c4a 100644 --- a/Outputs/CRT/Internals/Shaders/Shader.cpp +++ b/Outputs/CRT/Internals/Shaders/Shader.cpp @@ -8,22 +8,23 @@ #include "Shader.hpp" -#include #include using namespace OpenGL; -namespace { +namespace +{ Shader *bound_shader = nullptr; } -GLuint Shader::compile_shader(const char *source, GLenum type) +GLuint Shader::compile_shader(const std::string &source, GLenum type) { GLuint shader = glCreateShader(type); - glShaderSource(shader, 1, &source, NULL); + const char *c_str = source.c_str(); + glShaderSource(shader, 1, &c_str, NULL); glCompileShader(shader); -#if defined(DEBUG) +#ifdef DEBUG GLint isCompiled = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled); if(isCompiled == GL_FALSE) @@ -31,10 +32,10 @@ GLuint Shader::compile_shader(const char *source, GLenum type) GLint logLength; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); if(logLength > 0) { - GLchar *log = (GLchar *)malloc((size_t)logLength); + GLchar *log = new GLchar[logLength]; glGetShaderInfoLog(shader, logLength, &logLength, log); printf("Compile log:\n%s\n", log); - free(log); + delete[] log; } throw (type == GL_VERTEX_SHADER) ? VertexShaderCompilationError : FragmentShaderCompilationError; @@ -44,7 +45,7 @@ GLuint Shader::compile_shader(const char *source, GLenum type) return shader; } -Shader::Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings) +Shader::Shader(const std::string &vertex_shader, const std::string &fragment_shader, const AttributeBinding *attribute_bindings) { shader_program_ = glCreateProgram(); GLuint vertex = compile_shader(vertex_shader, GL_VERTEX_SHADER); @@ -64,7 +65,7 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att glLinkProgram(shader_program_); -#if defined(DEBUG) +#ifdef DEBUG GLint didLink = 0; glGetProgramiv(shader_program_, GL_LINK_STATUS, &didLink); if(didLink == GL_FALSE) @@ -72,10 +73,10 @@ Shader::Shader(const char *vertex_shader, const char *fragment_shader, const Att GLint logLength; glGetProgramiv(shader_program_, GL_INFO_LOG_LENGTH, &logLength); if(logLength > 0) { - GLchar *log = (GLchar *)malloc((size_t)logLength); + GLchar *log = new GLchar[logLength]; glGetProgramInfoLog(shader_program_, logLength, &logLength, log); printf("Link log:\n%s\n", log); - free(log); + delete[] log; } throw ProgramLinkageError; } @@ -292,18 +293,16 @@ void Shader::set_uniform_matrix(const std::string &name, GLint size, GLsizei cou void Shader::enqueue_function(std::function function) { - function_mutex_.lock(); + std::lock_guard function_guard(function_mutex_); enqueued_functions_.push_back(function); - function_mutex_.unlock(); } void Shader::flush_functions() { - function_mutex_.lock(); + std::lock_guard function_guard(function_mutex_); for(std::function function : enqueued_functions_) { function(); } enqueued_functions_.clear(); - function_mutex_.unlock(); } diff --git a/Outputs/CRT/Internals/Shaders/Shader.hpp b/Outputs/CRT/Internals/Shaders/Shader.hpp index ba95daf7c..f2afa3ab9 100644 --- a/Outputs/CRT/Internals/Shaders/Shader.hpp +++ b/Outputs/CRT/Internals/Shaders/Shader.hpp @@ -41,7 +41,7 @@ public: @param fragment_shader The fragment shader source code. @param attribute_bindings Either @c nullptr or an array terminated by an entry with a @c nullptr-name of attribute bindings. */ - Shader(const char *vertex_shader, const char *fragment_shader, const AttributeBinding *attribute_bindings); + Shader(const std::string &vertex_shader, const std::string &fragment_shader, const AttributeBinding *attribute_bindings); ~Shader(); /*! @@ -106,7 +106,7 @@ public: void set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values); private: - GLuint compile_shader(const char *source, GLenum type); + GLuint compile_shader(const std::string &source, GLenum type); GLuint shader_program_; void flush_functions(); diff --git a/Outputs/CRT/Internals/TextureBuilder.cpp b/Outputs/CRT/Internals/TextureBuilder.cpp index 89efc616e..a8188b9dc 100644 --- a/Outputs/CRT/Internals/TextureBuilder.cpp +++ b/Outputs/CRT/Internals/TextureBuilder.cpp @@ -196,5 +196,6 @@ void TextureBuilder::flush(const std::function } did_submit_ = false; + has_write_area_ = false; number_of_write_areas_ = 0; } diff --git a/StaticAnalyser/Atari/StaticAnalyser.cpp b/StaticAnalyser/Atari/StaticAnalyser.cpp index 231b40e6b..abce4327d 100644 --- a/StaticAnalyser/Atari/StaticAnalyser.cpp +++ b/StaticAnalyser/Atari/StaticAnalyser.cpp @@ -8,6 +8,8 @@ #include "StaticAnalyser.hpp" +#include "../Disassembler/Disassembler6502.hpp" + using namespace StaticAnalyser::Atari; void StaticAnalyser::Atari::AddTargets( @@ -24,5 +26,31 @@ void StaticAnalyser::Atari::AddTargets( target.disks = disks; target.tapes = tapes; target.cartridges = cartridges; + target.atari.paging_model = Atari2600PagingModel::None; + + // try to figure out the paging scheme +/* if(!cartridges.empty()) + { + const std::list &segments = cartridges.front()->get_segments(); + if(segments.size() == 1) + { + uint16_t entry_address, break_address; + const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); + if(segment.data.size() < 4096) + { + entry_address = (uint16_t)(segment.data[0x7fc] | (segment.data[0x7fd] << 8)); + break_address = (uint16_t)(segment.data[0x7fe] | (segment.data[0x7ff] << 8)); + } + else + { + entry_address = (uint16_t)(segment.data[0xffc] | (segment.data[0xffd] << 8)); + break_address = (uint16_t)(segment.data[0xffe] | (segment.data[0xfff] << 8)); + } + StaticAnalyser::MOS6502::Disassembly disassembly = + StaticAnalyser::MOS6502::Disassemble(segment.data, 0x1000, {entry_address, break_address}, 0x1fff); + printf("%p", &disassembly); + } + }*/ + destination.push_back(target); } diff --git a/StaticAnalyser/Disassembler/Disassembler6502.cpp b/StaticAnalyser/Disassembler/Disassembler6502.cpp index 3b773f10e..bb50ddf33 100644 --- a/StaticAnalyser/Disassembler/Disassembler6502.cpp +++ b/StaticAnalyser/Disassembler/Disassembler6502.cpp @@ -16,9 +16,9 @@ struct PartialDisassembly { std::vector remaining_entry_points; }; -static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector &memory, uint16_t start_address, uint16_t entry_point) +static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector &memory, uint16_t start_address, uint16_t entry_point, uint16_t address_mask) { - uint16_t address = entry_point; + uint16_t address = entry_point & address_mask; while(1) { uint16_t local_address = address - start_address; @@ -26,7 +26,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< struct Instruction instruction; instruction.address = address; - address++; + address = (address + 1) & address_mask; // get operation uint8_t operation = memory[local_address]; @@ -250,7 +250,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< if(operand_address >= memory.size()-1) return; address += 2; - instruction.operand = memory[operand_address] | (uint16_t)(memory[operand_address+1] << 8); + instruction.operand = memory[operand_address] | (uint16_t)(memory[operand_address + 1] << 8); } break; } @@ -259,7 +259,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< disassembly.disassembly.instructions_by_address[instruction.address] = instruction; // TODO: something wider-ranging than this - if(instruction.addressing_mode == Instruction::Absolute && (instruction.operand < start_address || instruction.operand >= start_address + memory.size())) + if((instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) && (instruction.operand < start_address || instruction.operand >= start_address + memory.size())) { if( instruction.operation == Instruction::STY || instruction.operation == Instruction::STX || @@ -276,23 +276,23 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector< if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range if(instruction.operation == Instruction::JSR) { - disassembly.remaining_entry_points.push_back(instruction.operand); + disassembly.remaining_entry_points.push_back(instruction.operand & address_mask); } if(instruction.operation == Instruction::JMP) { if(instruction.addressing_mode == Instruction::Absolute) - disassembly.remaining_entry_points.push_back(instruction.operand); + disassembly.remaining_entry_points.push_back(instruction.operand & address_mask); return; } if(instruction.addressing_mode == Instruction::Relative) { uint16_t destination = (uint16_t)(address + (int8_t)instruction.operand); - disassembly.remaining_entry_points.push_back(destination); + disassembly.remaining_entry_points.push_back(destination & address_mask); } } } -Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector &memory, uint16_t start_address, std::vector entry_points) +Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector &memory, uint16_t start_address, std::vector entry_points, uint16_t address_mask) { PartialDisassembly partialDisassembly; partialDisassembly.remaining_entry_points = entry_points; @@ -300,7 +300,7 @@ Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector &mem while(!partialDisassembly.remaining_entry_points.empty()) { // pull the next entry point from the back of the vector - uint16_t next_entry_point = partialDisassembly.remaining_entry_points.back(); + uint16_t next_entry_point = partialDisassembly.remaining_entry_points.back() & address_mask; partialDisassembly.remaining_entry_points.pop_back(); // if that address has already bene visited, forget about it @@ -310,7 +310,7 @@ Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector &mem if(next_entry_point < start_address || next_entry_point >= start_address + memory.size()) partialDisassembly.disassembly.outward_calls.insert(next_entry_point); else - AddToDisassembly(partialDisassembly, memory, start_address, next_entry_point); + AddToDisassembly(partialDisassembly, memory, start_address, next_entry_point, address_mask); } return std::move(partialDisassembly.disassembly); diff --git a/StaticAnalyser/Disassembler/Disassembler6502.hpp b/StaticAnalyser/Disassembler/Disassembler6502.hpp index 139542777..aff96ba63 100644 --- a/StaticAnalyser/Disassembler/Disassembler6502.hpp +++ b/StaticAnalyser/Disassembler/Disassembler6502.hpp @@ -67,7 +67,7 @@ struct Disassembly { std::set external_stores, external_loads, external_modifies; }; -Disassembly Disassemble(const std::vector &memory, uint16_t start_address, std::vector entry_points); +Disassembly Disassemble(const std::vector &memory, uint16_t start_address, std::vector entry_points, uint16_t address_mask = 0xffff); } } diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index cd26c382c..291187996 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -84,45 +84,49 @@ std::list StaticAnalyser::GetTargets(const char *file_name) TryInsert(list, class, platforms) \ } - Format("a26", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 - Format("adf", disks, Disk::AcornADF, TargetPlatform::Acorn) // ADF - Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN - Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64 - Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD - Format("dsk", disks, Disk::OricMFMDSK, TargetPlatform::Oric) // DSK - Format("g64", disks, Disk::G64, TargetPlatform::Commodore) // G64 - - // PRG - if(!strcmp(lowercase_extension, "prg")) + if(lowercase_extension) { - // try instantiating as a ROM; failing that accept as a tape - try { - Insert(cartridges, Cartridge::PRG, TargetPlatform::Commodore) - } - catch(...) - { - try { - Insert(tapes, Tape::PRG, TargetPlatform::Commodore) - } catch(...) {} - } - } + Format("a26", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 + Format("adf", disks, Disk::AcornADF, TargetPlatform::Acorn) // ADF + Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN + Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64 + Format("dsd", disks, Disk::SSD, TargetPlatform::Acorn) // DSD + Format("dsk", disks, Disk::OricMFMDSK, TargetPlatform::Oric) // DSK + Format("g64", disks, Disk::G64, TargetPlatform::Commodore) // G64 - Format("rom", cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM - Format("ssd", disks, Disk::SSD, TargetPlatform::Acorn) // SSD - Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) - Format("tap", tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) - Format("uef", tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) + // PRG + if(!strcmp(lowercase_extension, "prg")) + { + // try instantiating as a ROM; failing that accept as a tape + try { + Insert(cartridges, Cartridge::PRG, TargetPlatform::Commodore) + } + catch(...) + { + try { + Insert(tapes, Tape::PRG, TargetPlatform::Commodore) + } catch(...) {} + } + } + + Format("rom", cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM + Format("ssd", disks, Disk::SSD, TargetPlatform::Acorn) // SSD + Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) + Format("tap", tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) + Format("uef", tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) #undef Format #undef Insert +#undef TryInsert - // Hand off to platform-specific determination of whether these things are actually compatible and, - // if so, how to load them. (TODO) - if(potential_platforms & (TargetPlatformType)TargetPlatform::Acorn) Acorn::AddTargets(disks, tapes, cartridges, targets); - if(potential_platforms & (TargetPlatformType)TargetPlatform::Atari2600) Atari::AddTargets(disks, tapes, cartridges, targets); - if(potential_platforms & (TargetPlatformType)TargetPlatform::Commodore) Commodore::AddTargets(disks, tapes, cartridges, targets); - if(potential_platforms & (TargetPlatformType)TargetPlatform::Oric) Oric::AddTargets(disks, tapes, cartridges, targets); + // Hand off to platform-specific determination of whether these things are actually compatible and, + // if so, how to load them. (TODO) + if(potential_platforms & (TargetPlatformType)TargetPlatform::Acorn) Acorn::AddTargets(disks, tapes, cartridges, targets); + if(potential_platforms & (TargetPlatformType)TargetPlatform::Atari2600) Atari::AddTargets(disks, tapes, cartridges, targets); + if(potential_platforms & (TargetPlatformType)TargetPlatform::Commodore) Commodore::AddTargets(disks, tapes, cartridges, targets); + if(potential_platforms & (TargetPlatformType)TargetPlatform::Oric) Oric::AddTargets(disks, tapes, cartridges, targets); - free(lowercase_extension); + free(lowercase_extension); + } return targets; } diff --git a/StaticAnalyser/StaticAnalyser.hpp b/StaticAnalyser/StaticAnalyser.hpp index ffef83a23..1aabae662 100644 --- a/StaticAnalyser/StaticAnalyser.hpp +++ b/StaticAnalyser/StaticAnalyser.hpp @@ -25,6 +25,16 @@ enum class Vic20MemoryModel { ThirtyTwoKB }; +enum class Atari2600PagingModel { + None, + Atari8k, + Atari16k, + Atari32k, + ActivisionStack, + ParkerBros, + Tigervision +}; + /*! A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration, and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. @@ -39,21 +49,25 @@ struct Target { float probability; union { - struct { - Vic20MemoryModel memory_model; - bool has_c1540; - } vic20; - struct { bool has_adfs; bool has_dfs; bool should_shift_restart; } acorn; + struct { + Atari2600PagingModel paging_model; + } atari; + struct { bool use_atmos_rom; bool has_microdisc; } oric; + + struct { + Vic20MemoryModel memory_model; + bool has_c1540; + } vic20; }; std::string loadingCommand;