From 9d92ad659f8e06ab2ca2a6c5a4c44dddb5f38ae5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 May 2016 08:01:29 -0400 Subject: [PATCH 01/53] Established the basic timing loop, albeit without clocking delays yet. --- Machines/Atari2600/Atari2600.cpp | 108 ++++++++++-------- Machines/Atari2600/Atari2600.hpp | 2 +- .../Internals/Shaders/IntermediateShader.cpp | 2 +- 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 835352a92..8389468a1 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -11,7 +11,9 @@ #include using namespace Atari2600; -static const int horizontalTimerReload = 227; +namespace { + static const unsigned int horizontalTimerPeriod = 228; +} Machine::Machine() : _horizontalTimer(0), @@ -185,58 +187,64 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset) void Machine::output_pixels(unsigned int count) { - const int32_t start_of_sync = 214; +/* const int32_t start_of_sync = 214; const int32_t end_of_sync = 198; - const int32_t end_of_colour_burst = 188; + const int32_t end_of_colour_burst = 188;*/ while(count--) { OutputState state; - // update hmove - if(!(_horizontalTimer&3)) { - - if(_hMoveFlags) { - const uint8_t counterValue = _hMoveCounter ^ 0x7; - for(int c = 0; c < 5; c++) { - if(counterValue == (_objectMotion[c] >> 4)) _hMoveFlags &= ~(1 << c); - if(_hMoveFlags&(1 << c)) increment_object_counter(c); - } - } - - if(_hMoveIsCounting) { - _hMoveIsCounting = !!_hMoveCounter; - _hMoveCounter = (_hMoveCounter-1)&0xf; - } - } - - - // blank is decoded as 68 counts; sync and colour burst as 16 counts - - // 4 blank - // 4 sync - // 9 'blank'; colour burst after 4 - // 40 pixels - - // it'll be about 43 cycles from start of hsync to start of visible frame, so... - // guesses, until I can find information: 26 cycles blank, 16 sync, 40 blank, 160 pixels - if(_horizontalTimer < (_vBlankExtend ? 152 : 160)) { - if(_vBlankEnabled) { + switch(_horizontalTimer >> 2) + { + case 0: case 1: case 2: case 3: state = OutputState::Blank; - } else { + 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 = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; + break; + + default: state = OutputState::Pixel; - } + break; } - else if(_horizontalTimer < end_of_colour_burst) state = OutputState::Blank; - else if(_horizontalTimer < end_of_sync) state = OutputState::ColourBurst; - else if(_horizontalTimer < start_of_sync) state = OutputState::Sync; - else state = OutputState::Blank; // logic: if vsync is enabled, output the opposite of the automatic hsync output if(_vSyncEnabled) { state = (state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; } + // update hmove +// if(!(_horizontalTimer&3)) { +// +// if(_hMoveFlags) { +// const uint8_t counterValue = _hMoveCounter ^ 0x7; +// for(int c = 0; c < 5; c++) { +// if(counterValue == (_objectMotion[c] >> 4)) _hMoveFlags &= ~(1 << c); +// if(_hMoveFlags&(1 << c)) increment_object_counter(c); +// } +// } +// +// if(_hMoveIsCounting) { +// _hMoveIsCounting = !!_hMoveCounter; +// _hMoveCounter = (_hMoveCounter-1)&0xf; +// } +// } + + _lastOutputStateDuration++; if(state != _lastOutputState) { switch(_lastOutputState) { @@ -250,12 +258,13 @@ void Machine::output_pixels(unsigned int count) if(state == OutputState::Pixel) { _outputBuffer = _crt->allocate_write_area(160); + if(_outputBuffer) for(int c = 0; c < 160; c++) _outputBuffer[c] = (uint8_t)rand(); } else { _outputBuffer = nullptr; } } - if(_horizontalTimer < (_vBlankExtend ? 152 : 160)) { +/* if(_horizontalTimer < (_vBlankExtend ? 152 : 160)) { uint8_t throwaway_pixel; get_output_pixel(_outputBuffer ? &_outputBuffer[_lastOutputStateDuration] : &throwaway_pixel, 159 - _horizontalTimer); @@ -274,7 +283,9 @@ void Machine::output_pixels(unsigned int count) _horizontalTimer = (_horizontalTimer&~sign_extension) | (sign_extension&horizontalTimerReload); if(!_horizontalTimer) - _vBlankExtend = false; + _vBlankExtend = false;*/ + + _horizontalTimer = (_horizontalTimer + 1) % horizontalTimerPeriod; } } @@ -284,24 +295,23 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin uint8_t returnValue = 0xff; unsigned int cycles_run_for = 1; - const int32_t ready_line_disable_time = 227;//horizontalTimerReload; if(operation == CPU6502::BusOperation::Ready) { - unsigned int distance_to_end_of_ready = (_horizontalTimer - ready_line_disable_time + horizontalTimerReload + 1)%(horizontalTimerReload + 1); + unsigned int distance_to_end_of_ready = horizontalTimerPeriod - _horizontalTimer; cycles_run_for = distance_to_end_of_ready / 3; output_pixels(distance_to_end_of_ready); } else { output_pixels(3); } - if(_hMoveWillCount) { - _hMoveCounter = 0x0f; - _hMoveFlags = 0x1f; - _hMoveIsCounting = true; - _hMoveWillCount = false; - } +// if(_hMoveWillCount) { +// _hMoveCounter = 0x0f; +// _hMoveFlags = 0x1f; +// _hMoveIsCounting = true; +// _hMoveWillCount = false; +// } - if(_horizontalTimer == ready_line_disable_time) + if(!_horizontalTimer) set_ready_line(false); if(operation != CPU6502::BusOperation::Ready) { diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 12b0c4d9e..5c628c062 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -66,7 +66,7 @@ class Machine: public CPU6502::Processor { uint8_t _ballGraphicsEnableDelay; // graphics output - int32_t _horizontalTimer; + unsigned int _horizontalTimer; bool _vSyncEnabled, _vBlankEnabled; bool _vBlankExtend; uint8_t _hMoveCounter; diff --git a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp index ed82601ea..8d15a1291 100644 --- a/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp +++ b/Outputs/CRT/Internals/Shaders/IntermediateShader.cpp @@ -269,7 +269,7 @@ std::unique_ptr IntermediateShader::make_chroma_filter_shade "), vec3(1.0))" ");" - "vec3 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 4.0, 4.0);" + "vec3 lumaChromaColourInRange = (lumaChromaColour - vec3(0.0, 0.5, 0.5)) * vec3(1.0, 2.0, 2.0);" "fragColour = lumaChromaToRGB * lumaChromaColourInRange;" "}", false, false); } From f19ed2e8f8f323e44b60ba45a4deef4e76469958 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 May 2016 19:04:13 -0400 Subject: [PATCH 02/53] Started merely attempting to reintroduce the background layer. --- Machines/Atari2600/Atari2600.cpp | 75 ++++++++++++-------------------- Machines/Atari2600/Atari2600.hpp | 2 +- 2 files changed, 29 insertions(+), 48 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 8389468a1..c009415f6 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -77,14 +77,15 @@ Machine::~Machine() close_output(); } -void Machine::get_output_pixel(uint8_t *pixel, int offset) +uint8_t Machine::get_output_pixel() { // get the playfield pixel and hence a proposed colour + int offset = _horizontalTimer - (horizontalTimerPeriod - 160); uint8_t playfieldPixel = _playfield[offset >> 2]; uint8_t playfieldColour = ((_playfieldControl&6) == 2) ? _playerColour[offset / 80] : _playfieldColour; // get player and missile proposed pixels - uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; +/* uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; for(int c = 0; c < 2; c++) { const uint8_t repeatMask = _playerAndMissileSize[c]&7; @@ -172,13 +173,10 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset) if(!(_playfieldControl&0x04) || !playfieldPixel) { if(playerPixels[1] || missilePixels[1]) outputColour = _playerColour[1]; if(playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; - } + }*/ - // store colour -// static int lc; -// if(_vSyncEnabled) lc = 0; else lc += (offset == 159) ? 1 : 0; -// *pixel = (uint8_t)(((offset / 10) << 4) | (((lc >> 4)&7) << 1)); - *pixel = outputColour; + // return colour + return playfieldPixel ? playfieldColour : _backgroundColour; } // in imputing the knowledge that all we're dealing with is the rollover from 159 to 0, @@ -187,42 +185,21 @@ void Machine::get_output_pixel(uint8_t *pixel, int offset) void Machine::output_pixels(unsigned int count) { -/* const int32_t start_of_sync = 214; - const int32_t end_of_sync = 198; - const int32_t end_of_colour_burst = 188;*/ - while(count--) { OutputState state; switch(_horizontalTimer >> 2) { - 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 = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; - break; - - default: - state = OutputState::Pixel; - break; + 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 = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; + default: state = OutputState::Pixel; break; } - // logic: if vsync is enabled, output the opposite of the automatic hsync output + // if vsync is enabled, output the opposite of the automatic hsync output if(_vSyncEnabled) { state = (state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; } @@ -258,12 +235,21 @@ void Machine::output_pixels(unsigned int count) if(state == OutputState::Pixel) { _outputBuffer = _crt->allocate_write_area(160); - if(_outputBuffer) for(int c = 0; c < 160; c++) _outputBuffer[c] = (uint8_t)rand(); } else { _outputBuffer = nullptr; } } + if(state == OutputState::Pixel) + { + uint8_t colour = get_output_pixel(); + if(_outputBuffer) + { + *_outputBuffer = colour; + _outputBuffer++; + } + } + /* if(_horizontalTimer < (_vBlankExtend ? 152 : 160)) { uint8_t throwaway_pixel; get_output_pixel(_outputBuffer ? &_outputBuffer[_lastOutputStateDuration] : &throwaway_pixel, 159 - _horizontalTimer); @@ -274,18 +260,13 @@ void Machine::output_pixels(unsigned int count) increment_object_counter(2); increment_object_counter(3); increment_object_counter(4); - } - - // assumption here: signed shifts right; otherwise it's just - // an attempt to avoid both the % operator and a conditional - _horizontalTimer--; - const int32_t sign_extension = _horizontalTimer >> 31; - _horizontalTimer = (_horizontalTimer&~sign_extension) | (sign_extension&horizontalTimerReload); - - if(!_horizontalTimer) - _vBlankExtend = false;*/ + }*/ _horizontalTimer = (_horizontalTimer + 1) % horizontalTimerPeriod; + if(!_horizontalTimer) + { + _vBlankExtend = false; + } } } diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 5c628c062..2d9e6426c 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -90,7 +90,7 @@ class Machine: public CPU6502::Processor { }; void output_pixels(unsigned int count); - void get_output_pixel(uint8_t *pixel, int offset); + uint8_t get_output_pixel(); Outputs::CRT::CRT *_crt; // latched output state From 4ad074fc78149b67b9a575d85636b9bc0263dbb4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 May 2016 19:55:56 -0400 Subject: [PATCH 03/53] Introduced a four-cycle delay between playfield fetch and display, curing Yar's Revenge. Also disabled barrel roll scanling colouring again. I really need to make my mind up on that. --- Machines/Atari2600/Atari2600.cpp | 34 ++++++++++++++----- Machines/Atari2600/Atari2600.hpp | 4 +++ .../CRT/Internals/Shaders/OutputShader.cpp | 2 +- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index c009415f6..65508c4d4 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -80,8 +80,12 @@ Machine::~Machine() uint8_t Machine::get_output_pixel() { // get the playfield pixel and hence a proposed colour - int offset = _horizontalTimer - (horizontalTimerPeriod - 160); - uint8_t playfieldPixel = _playfield[offset >> 2]; + unsigned int offset = _horizontalTimer - (horizontalTimerPeriod - 160); + if(!(offset&3)) + { + _playfieldPixel = _nextPlayfieldPixel; + _nextPlayfieldPixel = _playfield[(1 + (offset >> 2))%40]; + } uint8_t playfieldColour = ((_playfieldControl&6) == 2) ? _playerColour[offset / 80] : _playfieldColour; // get player and missile proposed pixels @@ -176,7 +180,7 @@ uint8_t Machine::get_output_pixel() }*/ // return colour - return playfieldPixel ? playfieldColour : _backgroundColour; + return _playfieldPixel ? playfieldColour : _backgroundColour; } // in imputing the knowledge that all we're dealing with is the rollover from 159 to 0, @@ -249,6 +253,15 @@ void Machine::output_pixels(unsigned int count) _outputBuffer++; } } + else + { + // fetch this for the entire blank period just to ensure it's in place when needed + if(!(_horizontalTimer&3)) + { + unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); + _nextPlayfieldPixel = _playfield[(offset >> 2)%40]; + } + } /* if(_horizontalTimer < (_vBlankExtend ? 152 : 160)) { uint8_t throwaway_pixel; @@ -266,6 +279,7 @@ void Machine::output_pixels(unsigned int count) if(!_horizontalTimer) { _vBlankExtend = false; + set_ready_line(false); } } } @@ -277,14 +291,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin uint8_t returnValue = 0xff; unsigned int cycles_run_for = 1; + // this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for + // 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 - _horizontalTimer; cycles_run_for = distance_to_end_of_ready / 3; - output_pixels(distance_to_end_of_ready); - } else { - output_pixels(3); } + output_pixels(cycles_run_for * 3); + // if(_hMoveWillCount) { // _hMoveCounter = 0x0f; // _hMoveFlags = 0x1f; @@ -292,9 +309,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // _hMoveWillCount = false; // } - if(!_horizontalTimer) - set_ready_line(false); - if(operation != CPU6502::BusOperation::Ready) { // check for a paging access @@ -558,6 +572,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _piaTimerStatus |= 0xc0; } +// output_pixels(cycles_run_for * 3); + return cycles_run_for; } diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 2d9e6426c..f7dd223e4 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -48,6 +48,10 @@ class Machine: public CPU6502::Processor { uint8_t _backgroundColour; uint8_t _playfield[40]; + // playfield outputs + uint8_t _playfieldPixel; // the pixel currently being output + uint8_t _nextPlayfieldPixel; // the next pixel to be output; latched ahead of time + // player registers uint8_t _playerColour[2]; uint8_t _playerReflection[2]; diff --git a/Outputs/CRT/Internals/Shaders/OutputShader.cpp b/Outputs/CRT/Internals/Shaders/OutputShader.cpp index 682e62304..f543853a7 100644 --- a/Outputs/CRT/Internals/Shaders/OutputShader.cpp +++ b/Outputs/CRT/Internals/Shaders/OutputShader.cpp @@ -78,7 +78,7 @@ std::unique_ptr OutputShader::make_shader(const char *fragment_met "void main(void)" "{" - "fragColour = vec4(%s, 0.5*cos(lateralVarying));" + "fragColour = vec4(%s, 0.5);"//*cos(lateralVarying) "}", sampler_type, fragment_methods, colour_expression); From 3d003070b35acb541187fdf7c96674f3c4b4c712 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 May 2016 21:54:27 -0400 Subject: [PATCH 04/53] Made an attempt better to generalise the idea of things with 4 CLK delays. --- Machines/Atari2600/Atari2600.cpp | 157 ++++++++++++++++++++----------- Machines/Atari2600/Atari2600.hpp | 31 ++++-- 2 files changed, 121 insertions(+), 67 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 65508c4d4..7c4949220 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -23,7 +23,8 @@ Machine::Machine() : _rom(nullptr), _hMoveWillCount(false), _piaDataValue{0xff, 0xff}, - _tiaInputValue{0xff, 0xff} + _tiaInputValue{0xff, 0xff}, + _upcomingEventsPointer(0) { memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); @@ -77,17 +78,33 @@ Machine::~Machine() close_output(); } -uint8_t Machine::get_output_pixel() +void Machine::update_upcoming_event() { - // get the playfield pixel and hence a proposed colour - unsigned int offset = _horizontalTimer - (horizontalTimerPeriod - 160); + _upcomingEvents[_upcomingEventsPointer].updates = 0; + + unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); if(!(offset&3)) { - _playfieldPixel = _nextPlayfieldPixel; - _nextPlayfieldPixel = _playfield[(1 + (offset >> 2))%40]; + _upcomingEvents[_upcomingEventsPointer].updates |= Event::Action::Playfield; + _upcomingEvents[_upcomingEventsPointer].playfieldOutput = _playfield[(offset >> 2)%40]; } +} + +uint8_t Machine::get_output_pixel() +{ + unsigned int offset = _horizontalTimer - (horizontalTimerPeriod - 160); + + // get the playfield pixel and hence a proposed colour uint8_t playfieldColour = ((_playfieldControl&6) == 2) ? _playerColour[offset / 80] : _playfieldColour; + // get the ball proposed colour +// uint8_t ballPixel = 0; +// if(_ballGraphicsEnable&2) { +// int ballIndex = _objectCounter[4]; +// int ballSize = 1 << ((_playfieldControl >> 4)&3); +// ballPixel = (ballIndex >= 0 && ballIndex < ballSize) ? 1 : 0; +// } + // get player and missile proposed pixels /* uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; for(int c = 0; c < 2; c++) @@ -141,14 +158,6 @@ uint8_t Machine::get_output_pixel() } } - // get the ball proposed colour - uint8_t ballPixel = 0; - if(_ballGraphicsEnable&2) { - int ballIndex = _objectCounter[4] - 4; - int ballSize = 1 << ((_playfieldControl >> 4)&3); - ballPixel = (ballIndex >= 0 && ballIndex < ballSize) ? 1 : 0; - } - // accumulate collisions if(playerPixels[0] | playerPixels[1]) { _collisions[0] |= ((missilePixels[0] & playerPixels[1]) << 7) | ((missilePixels[0] & playerPixels[0]) << 6); @@ -180,12 +189,12 @@ uint8_t Machine::get_output_pixel() }*/ // return colour - return _playfieldPixel ? playfieldColour : _backgroundColour; + return _playfieldOutput ? playfieldColour : _backgroundColour; } // in imputing the knowledge that all we're dealing with is the rollover from 159 to 0, // this is faster than the straightforward +1)%160 per profiling -#define increment_object_counter(c) _objectCounter[c] = (_objectCounter[c]+1)&~((158-_objectCounter[c]) >> 8) +//#define increment_object_counter(c) _objectCounter[c] = (_objectCounter[c]+1)&~((158-_objectCounter[c]) >> 8) void Machine::output_pixels(unsigned int count) { @@ -193,13 +202,14 @@ void Machine::output_pixels(unsigned int count) { OutputState state; + // determine which output will start this cycle; all outputs are delayed by 4 CLKs momentarily... switch(_horizontalTimer >> 2) { - 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 = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; + case 227: case 0: case 1: case 2: state = OutputState::Blank; break; + case 3: case 4: case 5: case 6: state = OutputState::Sync; break; + case 7: case 8: case 9: case 10: state = OutputState::ColourBurst; break; + case 11: case 12: case 13: case 14: case 15: state = OutputState::Blank; break; + case 16: case 17: state = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; default: state = OutputState::Pixel; break; } @@ -208,6 +218,57 @@ void Machine::output_pixels(unsigned int count) state = (state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; } + // write that state as the one that will become effective in four clocks + _upcomingEvents[_upcomingEventsPointer].state = state; + + // grab pixel state if desired + if(state == OutputState::Pixel) + { + update_upcoming_event(); + } + + // advance, hitting the state that will become active now + _upcomingEventsPointer = (_upcomingEventsPointer + 1)&3; + + // apply any queued changes + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) + { + _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldOutput; + } + + // read that state + state = _upcomingEvents[_upcomingEventsPointer].state; + + // decide what that means needs to be communicated to the CRT + _lastOutputStateDuration++; + if(state != _lastOutputState) { + switch(_lastOutputState) { + case OutputState::Blank: _crt->output_blank(_lastOutputStateDuration); break; + case OutputState::Sync: _crt->output_sync(_lastOutputStateDuration); break; + case OutputState::ColourBurst: _crt->output_colour_burst(_lastOutputStateDuration, 96, 0); break; + case OutputState::Pixel: _crt->output_data(_lastOutputStateDuration, 1); break; + } + _lastOutputStateDuration = 0; + _lastOutputState = state; + + if(state == OutputState::Pixel) { + _outputBuffer = _crt->allocate_write_area(160); + } else { + _outputBuffer = nullptr; + } + } + + // decide on a pixel colour if that's what's happening + if(state == OutputState::Pixel) + { + uint8_t colour = get_output_pixel(); + if(_outputBuffer) + { + *_outputBuffer = colour; + _outputBuffer++; + } + } + // update hmove // if(!(_horizontalTimer&3)) { // @@ -225,43 +286,24 @@ void Machine::output_pixels(unsigned int count) // } // } - - _lastOutputStateDuration++; - if(state != _lastOutputState) { - switch(_lastOutputState) { - case OutputState::Blank: _crt->output_blank(_lastOutputStateDuration); break; - case OutputState::Sync: _crt->output_sync(_lastOutputStateDuration); break; - case OutputState::ColourBurst: _crt->output_colour_burst(_lastOutputStateDuration, 96, 0); break; - case OutputState::Pixel: _crt->output_data(_lastOutputStateDuration, 1); break; - } - _lastOutputStateDuration = 0; - _lastOutputState = state; - - if(state == OutputState::Pixel) { - _outputBuffer = _crt->allocate_write_area(160); - } else { - _outputBuffer = nullptr; - } - } - - if(state == OutputState::Pixel) - { - uint8_t colour = get_output_pixel(); - if(_outputBuffer) - { - *_outputBuffer = colour; - _outputBuffer++; - } - } - else - { +// if(state == OutputState::Pixel) +// { +// uint8_t colour = get_output_pixel(); +// if(_outputBuffer) +// { +// *_outputBuffer = colour; +// _outputBuffer++; +// } +// } +// else +// { // fetch this for the entire blank period just to ensure it's in place when needed - if(!(_horizontalTimer&3)) - { - unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); - _nextPlayfieldPixel = _playfield[(offset >> 2)%40]; - } - } +// if(!(_horizonta lTimer&3)) +// { +// unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); +// _nextPlayfieldPixel = _playfield[(offset >> 2)%40]; +// } +// } /* if(_horizontalTimer < (_vBlankExtend ? 152 : 160)) { uint8_t throwaway_pixel; @@ -275,6 +317,7 @@ void Machine::output_pixels(unsigned int count) increment_object_counter(4); }*/ + // advance horizontal timer, perform reset actions if requested _horizontalTimer = (_horizontalTimer + 1) % horizontalTimerPeriod; if(!_horizontalTimer) { diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index f7dd223e4..c38e1be92 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -48,9 +48,26 @@ class Machine: public CPU6502::Processor { uint8_t _backgroundColour; uint8_t _playfield[40]; - // playfield outputs - uint8_t _playfieldPixel; // the pixel currently being output - uint8_t _nextPlayfieldPixel; // the next pixel to be output; latched ahead of time + // delayed clock events + enum OutputState { + Sync, + Blank, + ColourBurst, + Pixel + }; + + struct Event { + enum Action { + OutputSate = 1 << 0, + Playfield = 1 << 1, + }; + unsigned int updates; + uint8_t playfieldOutput; + OutputState state; + } _upcomingEvents[4]; + unsigned int _upcomingEventsPointer; + + uint8_t _playfieldOutput; // player registers uint8_t _playerColour[2]; @@ -86,15 +103,9 @@ class Machine: public CPU6502::Processor { // collisions uint8_t _collisions[8]; - enum OutputState { - Sync, - Blank, - ColourBurst, - Pixel - }; - void output_pixels(unsigned int count); uint8_t get_output_pixel(); + void update_upcoming_event(); Outputs::CRT::CRT *_crt; // latched output state From 7d3cf765765c7f036685b37aaa288fbce1729ff2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 17 May 2016 07:09:18 -0400 Subject: [PATCH 05/53] Generalised slightly, to allow events to be queued up to eight cycles in advance; most importantly each event gets to pick its own delay. --- Machines/Atari2600/Atari2600.cpp | 28 ++++++++++++++++++---------- Machines/Atari2600/Atari2600.hpp | 4 +++- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 7c4949220..6be7d481e 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -80,14 +80,21 @@ Machine::~Machine() void Machine::update_upcoming_event() { - _upcomingEvents[_upcomingEventsPointer].updates = 0; - - unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); - if(!(offset&3)) + // grab the background now, for display in four clocks + if(!(_horizontalTimer&3)) { - _upcomingEvents[_upcomingEventsPointer].updates |= Event::Action::Playfield; - _upcomingEvents[_upcomingEventsPointer].playfieldOutput = _playfield[(offset >> 2)%40]; + unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); + _upcomingEvents[(_upcomingEventsPointer + 4)%number_of_upcoming_events].updates |= Event::Action::Playfield; + _upcomingEvents[(_upcomingEventsPointer + 4)%number_of_upcoming_events].playfieldOutput = _playfield[(offset >> 2)%40]; } + + // the ball becomes visible whenever it hits zero, regardless of whether its status + // is the result of a counter rollover or a programmatic reset + if(!_objectCounter[4] && _ballGraphicsEnable&2) + { + } + + // the players and missles become visible only upon overflow to zero } uint8_t Machine::get_output_pixel() @@ -227,14 +234,12 @@ void Machine::output_pixels(unsigned int count) update_upcoming_event(); } - // advance, hitting the state that will become active now - _upcomingEventsPointer = (_upcomingEventsPointer + 1)&3; - - // apply any queued changes + // apply any queued changes and flush the record if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) { _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldOutput; } + _upcomingEvents[_upcomingEventsPointer].updates = 0; // read that state state = _upcomingEvents[_upcomingEventsPointer].state; @@ -269,6 +274,9 @@ void Machine::output_pixels(unsigned int count) } } + // advance + _upcomingEventsPointer = (_upcomingEventsPointer + 1)%number_of_upcoming_events; + // update hmove // if(!(_horizontalTimer&3)) { // diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index c38e1be92..4528e9f75 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -16,6 +16,8 @@ namespace Atari2600 { +const unsigned int number_of_upcoming_events = 8; + class Machine: public CPU6502::Processor { public: @@ -64,7 +66,7 @@ class Machine: public CPU6502::Processor { unsigned int updates; uint8_t playfieldOutput; OutputState state; - } _upcomingEvents[4]; + } _upcomingEvents[number_of_upcoming_events]; unsigned int _upcomingEventsPointer; uint8_t _playfieldOutput; From 6a961c4d28e1ece51b770f16e5a895403919592f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 17 May 2016 07:18:14 -0400 Subject: [PATCH 06/53] Added something of the ball counter, but without yet a working HMOVE it's prone to error. --- Machines/Atari2600/Atari2600.cpp | 31 ++++++++++++++++++------------- Machines/Atari2600/Atari2600.hpp | 7 +++++-- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 6be7d481e..420da42df 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -80,21 +80,26 @@ Machine::~Machine() void Machine::update_upcoming_event() { + unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; + // grab the background now, for display in four clocks if(!(_horizontalTimer&3)) { unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); - _upcomingEvents[(_upcomingEventsPointer + 4)%number_of_upcoming_events].updates |= Event::Action::Playfield; - _upcomingEvents[(_upcomingEventsPointer + 4)%number_of_upcoming_events].playfieldOutput = _playfield[(offset >> 2)%40]; + _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Playfield; + _upcomingEvents[upcomingEventsPointerPlus4].playfieldOutput = _playfield[(offset >> 2)%40]; } // the ball becomes visible whenever it hits zero, regardless of whether its status // is the result of a counter rollover or a programmatic reset - if(!_objectCounter[4] && _ballGraphicsEnable&2) + if(!_objectCounter[4]) { + _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Ball; } + _objectCounter[4] = (_objectCounter[4] + 1)%160; - // the players and missles become visible only upon overflow to zero + // the players and missles become visible only upon overflow to zero, so schedule for + // 5/6 clocks ahead from 159 } uint8_t Machine::get_output_pixel() @@ -105,12 +110,12 @@ uint8_t Machine::get_output_pixel() uint8_t playfieldColour = ((_playfieldControl&6) == 2) ? _playerColour[offset / 80] : _playfieldColour; // get the ball proposed colour -// uint8_t ballPixel = 0; -// if(_ballGraphicsEnable&2) { -// int ballIndex = _objectCounter[4]; -// int ballSize = 1 << ((_playfieldControl >> 4)&3); -// ballPixel = (ballIndex >= 0 && ballIndex < ballSize) ? 1 : 0; -// } + uint8_t ballPixel = 0; + if(_ballGraphicsEnable&2) { + int ballSize = 1 << ((_playfieldControl >> 4)&3); + ballPixel = (_pixelCounters.ball < ballSize) ? 1 : 0; + } + _pixelCounters.ball ++; // get player and missile proposed pixels /* uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; @@ -196,7 +201,7 @@ uint8_t Machine::get_output_pixel() }*/ // return colour - return _playfieldOutput ? playfieldColour : _backgroundColour; + return (_playfieldOutput | ballPixel) ? playfieldColour : _backgroundColour; } // in imputing the knowledge that all we're dealing with is the rollover from 159 to 0, @@ -236,9 +241,9 @@ void Machine::output_pixels(unsigned int count) // apply any queued changes and flush the record if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) - { _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldOutput; - } + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Ball) + _pixelCounters.ball = 0; _upcomingEvents[_upcomingEventsPointer].updates = 0; // read that state diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 4528e9f75..bc69e5799 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -60,8 +60,8 @@ class Machine: public CPU6502::Processor { struct Event { enum Action { - OutputSate = 1 << 0, - Playfield = 1 << 1, + Playfield = 1 << 0, + Ball = 1 << 1 }; unsigned int updates; uint8_t playfieldOutput; @@ -70,6 +70,9 @@ class Machine: public CPU6502::Processor { unsigned int _upcomingEventsPointer; uint8_t _playfieldOutput; + struct { + uint8_t ball; + } _pixelCounters; // player registers uint8_t _playerColour[2]; From d170bb14e636c3c1061350b241710c5ae95337aa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 17 May 2016 18:21:49 -0400 Subject: [PATCH 07/53] Extended scheduling plans, inserted initial events for hmove, albeit not yet implemented. --- Machines/Atari2600/Atari2600.cpp | 17 +++++++++++++++-- Machines/Atari2600/Atari2600.hpp | 17 ++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 420da42df..4480bff03 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -21,7 +21,6 @@ Machine::Machine() : _lastOutputState(OutputState::Sync), _piaTimerStatus(0xff), _rom(nullptr), - _hMoveWillCount(false), _piaDataValue{0xff, 0xff}, _tiaInputValue{0xff, 0xff}, _upcomingEventsPointer(0) @@ -555,7 +554,21 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x2a: _vBlankExtend = true; - _hMoveWillCount = true; + + // clear any ongoing moves + if(_hMoveFlags) + { + for(int c = 0; c < number_of_upcoming_events; c++) + { + _upcomingEvents[c].updates &= ~(Event::Action::HMoveCompare | Event::Action::HMoveDecrement); + } + } + + // schedule new moves + _hMoveFlags = 0x1f; + _hMoveCounter = 15; + _upcomingEvents[(_upcomingEventsPointer + 15)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; + _upcomingEvents[(_upcomingEventsPointer + 17)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; break; case 0x2b: _objectMotion[0] = diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index bc69e5799..a553fb70b 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -16,7 +16,7 @@ namespace Atari2600 { -const unsigned int number_of_upcoming_events = 8; +const unsigned int number_of_upcoming_events = 18; class Machine: public CPU6502::Processor { @@ -61,11 +61,14 @@ class Machine: public CPU6502::Processor { struct Event { enum Action { Playfield = 1 << 0, - Ball = 1 << 1 + Ball = 1 << 1, + HMoveCompare = 1 << 2, + HMoveDecrement = 1 << 3 }; - unsigned int updates; + int updates; uint8_t playfieldOutput; OutputState state; + Event() : updates(0) {} } _upcomingEvents[number_of_upcoming_events]; unsigned int _upcomingEventsPointer; @@ -95,10 +98,14 @@ class Machine: public CPU6502::Processor { unsigned int _horizontalTimer; bool _vSyncEnabled, _vBlankEnabled; bool _vBlankExtend; + + // horizontal motion control uint8_t _hMoveCounter; - bool _hMoveIsCounting, _hMoveWillCount; - uint8_t _objectCounter[5], _objectMotion[5]; uint8_t _hMoveFlags; + uint8_t _objectMotion[5]; + + // object counters + uint8_t _objectCounter[5]; // joystick state uint8_t _piaDataDirection[2]; From c96674b341296dbd8095bfa9df94cfb8a839ef7d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 17 May 2016 18:35:40 -0400 Subject: [PATCH 08/53] Made genuine first approximate attempt at hmove. --- Machines/Atari2600/Atari2600.cpp | 34 ++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 4480bff03..976c96991 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -241,8 +241,39 @@ void Machine::output_pixels(unsigned int count) // apply any queued changes and flush the record if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldOutput; + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Ball) _pixelCounters.ball = 0; + + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveCompare) + { + for(int c = 0; c < 5; c++) + { + if((_objectMotion[c]^8^_hMoveCounter) == 0xf) + { + _hMoveFlags &= ~(1 << c); + } + } + if(_hMoveFlags) + { + if(_hMoveCounter) _hMoveCounter--; + _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; + _upcomingEvents[(_upcomingEventsPointer+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; + } else printf("\n"); + } + + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveDecrement) + { + for(int c = 0; c < 5; c++) + { + if(_hMoveFlags & (1 << c)) + { + _objectCounter[c] = (_objectCounter[c] + 1)%160; + } + } + printf("[%d] %d ", _objectMotion[4], _objectCounter[4]); + } + _upcomingEvents[_upcomingEventsPointer].updates = 0; // read that state @@ -538,7 +569,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x22: case 0x23: case 0x24: - _objectMotion[decodedAddress - 0x20] = *value; + _objectMotion[decodedAddress - 0x20] = (*value) & 0xf; break; case 0x25: _playerGraphicsLatchEnable[0] = *value; break; @@ -568,7 +599,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _hMoveFlags = 0x1f; _hMoveCounter = 15; _upcomingEvents[(_upcomingEventsPointer + 15)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; - _upcomingEvents[(_upcomingEventsPointer + 17)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; break; case 0x2b: _objectMotion[0] = From ad6f405483c71332fded16b0c586dae06cd1998f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 17 May 2016 19:02:32 -0400 Subject: [PATCH 09/53] Fixed: was off by one on pixels, allowing four extra pixel output cycles per line. --- Machines/Atari2600/Atari2600.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 976c96991..654a1ef44 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -96,6 +96,7 @@ void Machine::update_upcoming_event() _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Ball; } _objectCounter[4] = (_objectCounter[4] + 1)%160; +// printf("-%d- ", _objectCounter[4]); // the players and missles become visible only upon overflow to zero, so schedule for // 5/6 clocks ahead from 159 @@ -219,8 +220,9 @@ void Machine::output_pixels(unsigned int count) case 227: case 0: case 1: case 2: state = OutputState::Blank; break; case 3: case 4: case 5: case 6: state = OutputState::Sync; break; case 7: case 8: case 9: case 10: state = OutputState::ColourBurst; break; - case 11: case 12: case 13: case 14: case 15: state = OutputState::Blank; break; - case 16: case 17: state = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; + case 11: case 12: case 13: + case 14: case 15: case 16: state = OutputState::Blank; break; + case 17: case 18: state = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; default: state = OutputState::Pixel; break; } @@ -235,6 +237,7 @@ void Machine::output_pixels(unsigned int count) // grab pixel state if desired if(state == OutputState::Pixel) { + update_upcoming_event(); } @@ -259,7 +262,7 @@ void Machine::output_pixels(unsigned int count) if(_hMoveCounter) _hMoveCounter--; _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; _upcomingEvents[(_upcomingEventsPointer+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; - } else printf("\n"); + } //else printf("\n"); } if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveDecrement) @@ -269,11 +272,16 @@ void Machine::output_pixels(unsigned int count) if(_hMoveFlags & (1 << c)) { _objectCounter[c] = (_objectCounter[c] + 1)%160; + if(c == 4) + { +// printf("[%d] ", _objectCounter[4]); + _pixelCounters.ball++; // TODO: generalise this, obviously. + } } } - printf("[%d] %d ", _objectMotion[4], _objectCounter[4]); +// if(_hMoveFlags & (1 << 4)) +// printf("%d ", _objectCounter[4]); } - _upcomingEvents[_upcomingEventsPointer].updates = 0; // read that state @@ -366,6 +374,7 @@ void Machine::output_pixels(unsigned int count) { _vBlankExtend = false; set_ready_line(false); +// printf("\n"); } } } @@ -599,6 +608,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _hMoveFlags = 0x1f; _hMoveCounter = 15; _upcomingEvents[(_upcomingEventsPointer + 15)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; +// _objectCounter[4] = (_objectCounter[4] + 8)%160; + printf("%d: ", _objectMotion[4]); break; case 0x2b: _objectMotion[0] = From a1e63e8320a1d9396ef7e69599841a04eff53d20 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 17 May 2016 21:41:32 -0400 Subject: [PATCH 10/53] Attempted to generalise on pixel counter storage, at least. Further adjusted background timing but I'm still not sure. --- Machines/Atari2600/Atari2600.cpp | 93 ++++++++------------------------ Machines/Atari2600/Atari2600.hpp | 17 +++--- 2 files changed, 32 insertions(+), 78 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 654a1ef44..d88da666b 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -93,7 +93,10 @@ void Machine::update_upcoming_event() // is the result of a counter rollover or a programmatic reset if(!_objectCounter[4]) { - _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Ball; + _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::ResetPixelCounter; + _upcomingEvents[upcomingEventsPointerPlus4].pixelCounterMask |= (1 << 4); +// _upcomingEvents[_upcomingEventsPointer].updates |= Event::Action::ResetPixelCounter; +// _upcomingEvents[_upcomingEventsPointer].pixelCounterMask |= (1 << 4); } _objectCounter[4] = (_objectCounter[4] + 1)%160; // printf("-%d- ", _objectCounter[4]); @@ -113,9 +116,9 @@ uint8_t Machine::get_output_pixel() uint8_t ballPixel = 0; if(_ballGraphicsEnable&2) { int ballSize = 1 << ((_playfieldControl >> 4)&3); - ballPixel = (_pixelCounters.ball < ballSize) ? 1 : 0; + ballPixel = (_pixelCounter[4] < ballSize) ? 1 : 0; } - _pixelCounters.ball ++; + _pixelCounter[4] ++; // get player and missile proposed pixels /* uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; @@ -214,15 +217,16 @@ void Machine::output_pixels(unsigned int count) { OutputState state; - // determine which output will start this cycle; all outputs are delayed by 4 CLKs momentarily... + // determine which output will start in four cycles switch(_horizontalTimer >> 2) { - case 227: case 0: case 1: case 2: state = OutputState::Blank; break; + case 56: case 0: case 1: case 2: state = OutputState::Blank; break; case 3: case 4: case 5: case 6: state = OutputState::Sync; break; case 7: case 8: case 9: case 10: state = OutputState::ColourBurst; break; case 11: case 12: case 13: - case 14: case 15: case 16: state = OutputState::Blank; break; - case 17: case 18: state = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; + case 14: case 15: state = OutputState::Blank; break; + + case 16: case 17: state = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; default: state = OutputState::Pixel; break; } @@ -232,12 +236,11 @@ void Machine::output_pixels(unsigned int count) } // write that state as the one that will become effective in four clocks - _upcomingEvents[_upcomingEventsPointer].state = state; + _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].state = state; // grab pixel state if desired if(state == OutputState::Pixel) { - update_upcoming_event(); } @@ -245,8 +248,15 @@ void Machine::output_pixels(unsigned int count) if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldOutput; - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Ball) - _pixelCounters.ball = 0; + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ResetPixelCounter) + { + for(int c = 0; c < 5; c++) + { + if(_upcomingEvents[_upcomingEventsPointer].pixelCounterMask & (1 << c)) + _pixelCounter[c] = 0; + } + _upcomingEvents[_upcomingEventsPointer].pixelCounterMask = 0; + } if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveCompare) { @@ -262,7 +272,7 @@ void Machine::output_pixels(unsigned int count) if(_hMoveCounter) _hMoveCounter--; _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; _upcomingEvents[(_upcomingEventsPointer+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; - } //else printf("\n"); + } } if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveDecrement) @@ -272,15 +282,9 @@ void Machine::output_pixels(unsigned int count) if(_hMoveFlags & (1 << c)) { _objectCounter[c] = (_objectCounter[c] + 1)%160; - if(c == 4) - { -// printf("[%d] ", _objectCounter[4]); - _pixelCounters.ball++; // TODO: generalise this, obviously. - } + _pixelCounter[c] ++; } } -// if(_hMoveFlags & (1 << 4)) -// printf("%d ", _objectCounter[4]); } _upcomingEvents[_upcomingEventsPointer].updates = 0; @@ -320,61 +324,12 @@ void Machine::output_pixels(unsigned int count) // advance _upcomingEventsPointer = (_upcomingEventsPointer + 1)%number_of_upcoming_events; - // update hmove -// if(!(_horizontalTimer&3)) { -// -// if(_hMoveFlags) { -// const uint8_t counterValue = _hMoveCounter ^ 0x7; -// for(int c = 0; c < 5; c++) { -// if(counterValue == (_objectMotion[c] >> 4)) _hMoveFlags &= ~(1 << c); -// if(_hMoveFlags&(1 << c)) increment_object_counter(c); -// } -// } -// -// if(_hMoveIsCounting) { -// _hMoveIsCounting = !!_hMoveCounter; -// _hMoveCounter = (_hMoveCounter-1)&0xf; -// } -// } - -// if(state == OutputState::Pixel) -// { -// uint8_t colour = get_output_pixel(); -// if(_outputBuffer) -// { -// *_outputBuffer = colour; -// _outputBuffer++; -// } -// } -// else -// { - // fetch this for the entire blank period just to ensure it's in place when needed -// if(!(_horizonta lTimer&3)) -// { -// unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); -// _nextPlayfieldPixel = _playfield[(offset >> 2)%40]; -// } -// } - -/* if(_horizontalTimer < (_vBlankExtend ? 152 : 160)) { - uint8_t throwaway_pixel; - get_output_pixel(_outputBuffer ? &_outputBuffer[_lastOutputStateDuration] : &throwaway_pixel, 159 - _horizontalTimer); - - // increment all graphics counters - increment_object_counter(0); - increment_object_counter(1); - increment_object_counter(2); - increment_object_counter(3); - increment_object_counter(4); - }*/ - // advance horizontal timer, perform reset actions if requested _horizontalTimer = (_horizontalTimer + 1) % horizontalTimerPeriod; if(!_horizontalTimer) { _vBlankExtend = false; set_ready_line(false); -// printf("\n"); } } } @@ -608,8 +563,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _hMoveFlags = 0x1f; _hMoveCounter = 15; _upcomingEvents[(_upcomingEventsPointer + 15)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; -// _objectCounter[4] = (_objectCounter[4] + 8)%160; - printf("%d: ", _objectMotion[4]); break; case 0x2b: _objectMotion[0] = diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index a553fb70b..25f48fc45 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -60,22 +60,22 @@ class Machine: public CPU6502::Processor { struct Event { enum Action { - Playfield = 1 << 0, - Ball = 1 << 1, - HMoveCompare = 1 << 2, - HMoveDecrement = 1 << 3 + Playfield = 1 << 0, + ResetPixelCounter = 1 << 1, + HMoveCompare = 1 << 2, + HMoveDecrement = 1 << 3, }; int updates; + + int pixelCounterMask; uint8_t playfieldOutput; OutputState state; - Event() : updates(0) {} + + Event() : updates(0), pixelCounterMask(0) {} } _upcomingEvents[number_of_upcoming_events]; unsigned int _upcomingEventsPointer; uint8_t _playfieldOutput; - struct { - uint8_t ball; - } _pixelCounters; // player registers uint8_t _playerColour[2]; @@ -106,6 +106,7 @@ class Machine: public CPU6502::Processor { // object counters uint8_t _objectCounter[5]; + uint8_t _pixelCounter[5]; // joystick state uint8_t _piaDataDirection[2]; From b8708b805c742327d0525ee7d6887c846569411b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 17 May 2016 22:02:57 -0400 Subject: [PATCH 11/53] The vertical blank flag now works again. --- Machines/Atari2600/Atari2600.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index d88da666b..3ea6d2703 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -217,7 +217,7 @@ void Machine::output_pixels(unsigned int count) { OutputState state; - // determine which output will start in four cycles + // determine which output state will be active in four cycles from now switch(_horizontalTimer >> 2) { case 56: case 0: case 1: case 2: state = OutputState::Blank; break; @@ -235,6 +235,11 @@ void Machine::output_pixels(unsigned int count) state = (state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; } + // honour the vertical blank flag + if(_vBlankEnabled && state == OutputState::Pixel) { + state = OutputState::Blank; + } + // write that state as the one that will become effective in four clocks _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].state = state; @@ -352,13 +357,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin output_pixels(cycles_run_for * 3); -// if(_hMoveWillCount) { -// _hMoveCounter = 0x0f; -// _hMoveFlags = 0x1f; -// _hMoveIsCounting = true; -// _hMoveWillCount = false; -// } - if(operation != CPU6502::BusOperation::Ready) { // check for a paging access From 0b99649b0fad69ae5c3e97150783da49236c439e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 May 2016 07:31:05 -0400 Subject: [PATCH 12/53] Had a first shot at triggering player and missile resets. --- Machines/Atari2600/Atari2600.cpp | 41 +++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 3ea6d2703..8a6a53ecf 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -95,14 +95,38 @@ void Machine::update_upcoming_event() { _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::ResetPixelCounter; _upcomingEvents[upcomingEventsPointerPlus4].pixelCounterMask |= (1 << 4); -// _upcomingEvents[_upcomingEventsPointer].updates |= Event::Action::ResetPixelCounter; -// _upcomingEvents[_upcomingEventsPointer].pixelCounterMask |= (1 << 4); } _objectCounter[4] = (_objectCounter[4] + 1)%160; -// printf("-%d- ", _objectCounter[4]); - // the players and missles become visible only upon overflow to zero, so schedule for - // 5/6 clocks ahead from 159 + unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; + + // check for player and missle triggers + for(int c = 0; c < 4; c++) + { + // the players and missles become visible only upon overflow to zero, so schedule for + // 5/6 clocks ahead from 159 + if(_objectCounter[c] == 159) + { + unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus6 : upcomingEventsPointerPlus5; + _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; + _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); + } + else + { + uint8_t repeatMask = _playerAndMissileSize[c] & 7; + if( + ( _objectCounter[c] == 12 && ((repeatMask == 1) || (repeatMask == 3)) ) || + ( _objectCounter[c] == 28 && ((repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6)) ) || + ( _objectCounter[c] == 60 && ((repeatMask == 4) || (repeatMask == 6)) ) + ) + { + unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus5 : upcomingEventsPointerPlus4; + _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; + _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); + } + } + } } uint8_t Machine::get_output_pixel() @@ -120,6 +144,9 @@ uint8_t Machine::get_output_pixel() } _pixelCounter[4] ++; + // deal with the sprites + + // get player and missile proposed pixels /* uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; for(int c = 0; c < 2; c++) @@ -507,7 +534,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin break; case 0x10: case 0x11: case 0x12: case 0x13: - case 0x14: _objectCounter[decodedAddress - 0x10] = 0; break; + case 0x14: + _objectCounter[decodedAddress - 0x10] = 0; + break; case 0x1c: _ballGraphicsEnable = _ballGraphicsEnableLatch; From 354143a78c4caed2326e882858967a0cdeb5962c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 May 2016 07:51:25 -0400 Subject: [PATCH 13/53] Reintroduced an attempt to plot sprites. --- Machines/Atari2600/Atari2600.cpp | 48 ++++++++++++++++++++++++-------- Machines/Atari2600/Atari2600.hpp | 2 +- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 8a6a53ecf..87dba7af3 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -77,7 +77,7 @@ Machine::~Machine() close_output(); } -void Machine::update_upcoming_event() +void Machine::update_upcoming_events() { unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; @@ -98,10 +98,9 @@ void Machine::update_upcoming_event() } _objectCounter[4] = (_objectCounter[4] + 1)%160; + // check for player and missle triggers unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; - - // check for player and missle triggers for(int c = 0; c < 4; c++) { // the players and missles become visible only upon overflow to zero, so schedule for @@ -114,6 +113,8 @@ void Machine::update_upcoming_event() } else { + // otherwise visibility is determined by an appropriate repeat mask and hitting any of 12, 28 or 60, + // in which case the counter reset (and hence the start of drawing) will occur in 4/5 cycles uint8_t repeatMask = _playerAndMissileSize[c] & 7; if( ( _objectCounter[c] == 12 && ((repeatMask == 1) || (repeatMask == 3)) ) || @@ -126,6 +127,8 @@ void Machine::update_upcoming_event() _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); } } + + _objectCounter[c] = (_objectCounter[c] + 1)%160; } } @@ -136,7 +139,7 @@ uint8_t Machine::get_output_pixel() // get the playfield pixel and hence a proposed colour uint8_t playfieldColour = ((_playfieldControl&6) == 2) ? _playerColour[offset / 80] : _playfieldColour; - // get the ball proposed colour + // get the ball proposed state uint8_t ballPixel = 0; if(_ballGraphicsEnable&2) { int ballSize = 1 << ((_playfieldControl >> 4)&3); @@ -145,6 +148,29 @@ uint8_t Machine::get_output_pixel() _pixelCounter[4] ++; // deal with the sprites + uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; + for(int c = 0; c < 2; c++) + { + if(_playerGraphics[c]) { + // figure out player colour + int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; + if(_pixelCounter[c] < 8) + playerPixels[c] = (_playerGraphics[c] >> (_pixelCounter[c] ^ flipMask)) &1; + } + uint8_t repeatMask = _playerAndMissileSize[c] & 7; + switch(repeatMask) + { + default: + _pixelCounter[c]++; + break; + case 5: + _pixelCounter[c] += ((_objectCounter[c] >> 1)&1); + break; + case 7: + _pixelCounter[c] += ((_objectCounter[c] >> 2)&1); + break; + } + } // get player and missile proposed pixels @@ -219,19 +245,19 @@ uint8_t Machine::get_output_pixel() } if(missilePixels[0] & missilePixels[1]) - _collisions[7] |= (1 << 6); + _collisions[7] |= (1 << 6);*/ // apply appropriate priority to pick a colour - playfieldPixel |= ballPixel; + uint8_t playfieldPixel = _playfieldOutput | ballPixel; uint8_t outputColour = playfieldPixel ? playfieldColour : _backgroundColour; if(!(_playfieldControl&0x04) || !playfieldPixel) { if(playerPixels[1] || missilePixels[1]) outputColour = _playerColour[1]; if(playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; - }*/ + } // return colour - return (_playfieldOutput | ballPixel) ? playfieldColour : _backgroundColour; + return outputColour; } // in imputing the knowledge that all we're dealing with is the rollover from 159 to 0, @@ -270,11 +296,9 @@ void Machine::output_pixels(unsigned int count) // write that state as the one that will become effective in four clocks _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].state = state; - // grab pixel state if desired + // grab background colour and schedule pixel counter resets if(state == OutputState::Pixel) - { - update_upcoming_event(); - } + update_upcoming_events(); // apply any queued changes and flush the record if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 25f48fc45..96c780530 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -118,7 +118,7 @@ class Machine: public CPU6502::Processor { void output_pixels(unsigned int count); uint8_t get_output_pixel(); - void update_upcoming_event(); + void update_upcoming_events(); Outputs::CRT::CRT *_crt; // latched output state From 877c55b5c58f41b9c628f74f0c3714f365494b90 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 May 2016 07:54:44 -0400 Subject: [PATCH 14/53] Threw in missiles. To 'complete' graphics output. Or, rather, to move me on to debugging. --- Machines/Atari2600/Atari2600.cpp | 65 +++++--------------------------- 1 file changed, 10 insertions(+), 55 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 87dba7af3..0f149b59e 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -157,77 +157,32 @@ uint8_t Machine::get_output_pixel() if(_pixelCounter[c] < 8) playerPixels[c] = (_playerGraphics[c] >> (_pixelCounter[c] ^ flipMask)) &1; } + + if((_missileGraphicsEnable[c]&2) && !(_missileGraphicsReset[c]&2)) { + int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); + missilePixels[c] = (_pixelCounter[c+2] < missileSize) ? 1 : 0; + } + uint8_t repeatMask = _playerAndMissileSize[c] & 7; switch(repeatMask) { default: _pixelCounter[c]++; + _pixelCounter[c+2]++; break; case 5: _pixelCounter[c] += ((_objectCounter[c] >> 1)&1); + _pixelCounter[c+2] += ((_objectCounter[c+2] >> 1)&1); break; case 7: _pixelCounter[c] += ((_objectCounter[c] >> 2)&1); + _pixelCounter[c+2] += ((_objectCounter[c+2] >> 2)&1); break; } } - - // get player and missile proposed pixels -/* uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; - for(int c = 0; c < 2; c++) - { - const uint8_t repeatMask = _playerAndMissileSize[c]&7; - if(_playerGraphics[c]) { - // figure out player colour - int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; - - int relativeTimer = _objectCounter[c] - 5; - switch (repeatMask) - { - case 0: break; - default: - if(repeatMask&4 && relativeTimer >= 64) relativeTimer -= 64; - else if(repeatMask&2 && relativeTimer >= 32) relativeTimer -= 32; - else if(repeatMask&1 && relativeTimer >= 16) relativeTimer -= 16; - break; - case 5: - relativeTimer >>= 1; - break; - case 7: - relativeTimer >>= 2; - break; - } - - if(relativeTimer >= 0 && relativeTimer < 8) - playerPixels[c] = (_playerGraphics[c] >> (relativeTimer ^ flipMask)) &1; - } - - // figure out missile colour - if((_missileGraphicsEnable[c]&2) && !(_missileGraphicsReset[c]&2)) { - int missileIndex = _objectCounter[2+c] - 4; - switch (repeatMask) - { - case 0: break; - default: - if(repeatMask&4 && missileIndex >= 64) missileIndex -= 64; - else if(repeatMask&2 && missileIndex >= 32) missileIndex -= 32; - else if(repeatMask&1 && missileIndex >= 16) missileIndex -= 16; - break; - case 5: - missileIndex >>= 1; - break; - case 7: - missileIndex >>= 2; - break; - } - int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); - missilePixels[c] = (missileIndex >= 0 && missileIndex < missileSize) ? 1 : 0; - } - } - // accumulate collisions - if(playerPixels[0] | playerPixels[1]) { +/* if(playerPixels[0] | playerPixels[1]) { _collisions[0] |= ((missilePixels[0] & playerPixels[1]) << 7) | ((missilePixels[0] & playerPixels[0]) << 6); _collisions[1] |= ((missilePixels[1] & playerPixels[0]) << 7) | ((missilePixels[1] & playerPixels[1]) << 6); From 83ed6a82cff072b2a30e8235ada300a2b6d9e775 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 May 2016 18:49:40 -0400 Subject: [PATCH 15/53] Fixed: asserting vertical blank doesn't affect underlying timing. --- Machines/Atari2600/Atari2600.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 0f149b59e..46fd0d8de 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -129,6 +129,7 @@ void Machine::update_upcoming_events() } _objectCounter[c] = (_objectCounter[c] + 1)%160; +// if(c == 0) printf("."); } } @@ -243,11 +244,6 @@ void Machine::output_pixels(unsigned int count) state = (state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; } - // honour the vertical blank flag - if(_vBlankEnabled && state == OutputState::Pixel) { - state = OutputState::Blank; - } - // write that state as the one that will become effective in four clocks _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].state = state; @@ -293,7 +289,8 @@ void Machine::output_pixels(unsigned int count) if(_hMoveFlags & (1 << c)) { _objectCounter[c] = (_objectCounter[c] + 1)%160; - _pixelCounter[c] ++; + _pixelCounter[c] ++; // TODO: this isn't always a straight increment +// if(c == 0) printf("+"); } } } @@ -301,10 +298,16 @@ void Machine::output_pixels(unsigned int count) // read that state state = _upcomingEvents[_upcomingEventsPointer].state; + OutputState actingState = state; + + // honour the vertical blank flag + if(_vBlankEnabled && state == OutputState::Pixel) { + actingState = OutputState::Blank; + } // decide what that means needs to be communicated to the CRT _lastOutputStateDuration++; - if(state != _lastOutputState) { + if(actingState != _lastOutputState) { switch(_lastOutputState) { case OutputState::Blank: _crt->output_blank(_lastOutputStateDuration); break; case OutputState::Sync: _crt->output_sync(_lastOutputStateDuration); break; @@ -312,9 +315,9 @@ void Machine::output_pixels(unsigned int count) case OutputState::Pixel: _crt->output_data(_lastOutputStateDuration, 1); break; } _lastOutputStateDuration = 0; - _lastOutputState = state; + _lastOutputState = actingState; - if(state == OutputState::Pixel) { + if(actingState == OutputState::Pixel) { _outputBuffer = _crt->allocate_write_area(160); } else { _outputBuffer = nullptr; @@ -339,6 +342,7 @@ void Machine::output_pixels(unsigned int count) _horizontalTimer = (_horizontalTimer + 1) % horizontalTimerPeriod; if(!_horizontalTimer) { +// printf("\n"); _vBlankExtend = false; set_ready_line(false); } From d013d46337d4a2062e5bd46520bc3a6516d03180 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 May 2016 21:36:28 -0400 Subject: [PATCH 16/53] Fixed object motion decoding and checking of the repeat mask for missile graphics. --- Machines/Atari2600/Atari2600.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 46fd0d8de..63686e915 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -115,7 +115,7 @@ void Machine::update_upcoming_events() { // otherwise visibility is determined by an appropriate repeat mask and hitting any of 12, 28 or 60, // in which case the counter reset (and hence the start of drawing) will occur in 4/5 cycles - uint8_t repeatMask = _playerAndMissileSize[c] & 7; + uint8_t repeatMask = _playerAndMissileSize[c&3] & 7; if( ( _objectCounter[c] == 12 && ((repeatMask == 1) || (repeatMask == 3)) ) || ( _objectCounter[c] == 28 && ((repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6)) ) || @@ -129,7 +129,6 @@ void Machine::update_upcoming_events() } _objectCounter[c] = (_objectCounter[c] + 1)%160; -// if(c == 0) printf("."); } } @@ -290,7 +289,6 @@ void Machine::output_pixels(unsigned int count) { _objectCounter[c] = (_objectCounter[c] + 1)%160; _pixelCounter[c] ++; // TODO: this isn't always a straight increment -// if(c == 0) printf("+"); } } } @@ -342,7 +340,6 @@ void Machine::output_pixels(unsigned int count) _horizontalTimer = (_horizontalTimer + 1) % horizontalTimerPeriod; if(!_horizontalTimer) { -// printf("\n"); _vBlankExtend = false; set_ready_line(false); } @@ -543,7 +540,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x22: case 0x23: case 0x24: - _objectMotion[decodedAddress - 0x20] = (*value) & 0xf; + _objectMotion[decodedAddress - 0x20] = (*value) >> 4; break; case 0x25: _playerGraphicsLatchEnable[0] = *value; break; From 8c7ce1ec3fcfa3c3938c089063d551810672ebcd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 May 2016 21:41:25 -0400 Subject: [PATCH 17/53] Attempted to fix sprite sizing and, again, missile repetition. --- Machines/Atari2600/Atari2600.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 63686e915..67c6d2c12 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -115,7 +115,7 @@ void Machine::update_upcoming_events() { // otherwise visibility is determined by an appropriate repeat mask and hitting any of 12, 28 or 60, // in which case the counter reset (and hence the start of drawing) will occur in 4/5 cycles - uint8_t repeatMask = _playerAndMissileSize[c&3] & 7; + uint8_t repeatMask = _playerAndMissileSize[c&1] & 7; if( ( _objectCounter[c] == 12 && ((repeatMask == 1) || (repeatMask == 3)) ) || ( _objectCounter[c] == 28 && ((repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6)) ) || @@ -154,29 +154,29 @@ uint8_t Machine::get_output_pixel() if(_playerGraphics[c]) { // figure out player colour int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; - if(_pixelCounter[c] < 8) - playerPixels[c] = (_playerGraphics[c] >> (_pixelCounter[c] ^ flipMask)) &1; + if(_pixelCounter[c] < 32) + playerPixels[c] = (_playerGraphics[c] >> ((_pixelCounter[c] >> 2) ^ flipMask)) &1; } if((_missileGraphicsEnable[c]&2) && !(_missileGraphicsReset[c]&2)) { int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); - missilePixels[c] = (_pixelCounter[c+2] < missileSize) ? 1 : 0; + missilePixels[c] = ((_pixelCounter[c+2] >> 2) < missileSize) ? 1 : 0; } uint8_t repeatMask = _playerAndMissileSize[c] & 7; switch(repeatMask) { default: - _pixelCounter[c]++; - _pixelCounter[c+2]++; + _pixelCounter[c]+=4; + _pixelCounter[c+2]+=4; break; case 5: - _pixelCounter[c] += ((_objectCounter[c] >> 1)&1); - _pixelCounter[c+2] += ((_objectCounter[c+2] >> 1)&1); + _pixelCounter[c] += 2; + _pixelCounter[c+2] += 2; break; case 7: - _pixelCounter[c] += ((_objectCounter[c] >> 2)&1); - _pixelCounter[c+2] += ((_objectCounter[c+2] >> 2)&1); + _pixelCounter[c] += 1; + _pixelCounter[c+2] += 1; break; } } From 3765c5fbb5a8fa6a30774bfec79f072c82e624c4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 May 2016 21:45:35 -0400 Subject: [PATCH 18/53] Fixed error that would prevent an interrupting frame from ever being drawn. --- OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index ea66b245c..087c00e97 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -69,7 +69,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt // Draw the display only if a previous draw is not still ongoing. -drawViewOnlyIfDirty: is guaranteed // to be safe to call concurrently with -openGLView:updateToTime: so there's no need to worry about // the above interrupting the below or vice versa. - if(!OSAtomicTestAndSet(drawingMask, &_updateIsOngoing) && _hasSkipped) + if(_hasSkipped && !OSAtomicTestAndSet(drawingMask, &_updateIsOngoing)) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self drawViewOnlyIfDirty:YES]; From e61392d3fb4fc0c0ff71080aabb6eba8be5bcb92 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 May 2016 21:50:28 -0400 Subject: [PATCH 19/53] Fixed incorrect duplication. --- Machines/Atari2600/Atari2600.cpp | 8 ++++---- Machines/Atari2600/Atari2600.hpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 67c6d2c12..dab8b512f 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -167,8 +167,8 @@ uint8_t Machine::get_output_pixel() switch(repeatMask) { default: - _pixelCounter[c]+=4; - _pixelCounter[c+2]+=4; + _pixelCounter[c] += 4; + _pixelCounter[c+2] += 4; break; case 5: _pixelCounter[c] += 2; @@ -268,7 +268,7 @@ void Machine::output_pixels(unsigned int count) { for(int c = 0; c < 5; c++) { - if((_objectMotion[c]^8^_hMoveCounter) == 0xf) + if(((_objectMotion[c] >> 4)^8^_hMoveCounter) == 0xf) { _hMoveFlags &= ~(1 << c); } @@ -540,7 +540,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x22: case 0x23: case 0x24: - _objectMotion[decodedAddress - 0x20] = (*value) >> 4; + _objectMotion[decodedAddress - 0x20] = *value; break; case 0x25: _playerGraphicsLatchEnable[0] = *value; break; diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 96c780530..c15077fdc 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -106,7 +106,7 @@ class Machine: public CPU6502::Processor { // object counters uint8_t _objectCounter[5]; - uint8_t _pixelCounter[5]; + int _pixelCounter[5]; // joystick state uint8_t _piaDataDirection[2]; From 40600b4bcbf2bb08baf0c9c01dc85a6470ea7084 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 18 May 2016 22:07:24 -0400 Subject: [PATCH 20/53] Made sprite placement consistent at least. Not necessarily yet correct. --- Machines/Atari2600/Atari2600.cpp | 6 +++--- OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index dab8b512f..8a7544bc2 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -117,9 +117,9 @@ void Machine::update_upcoming_events() // in which case the counter reset (and hence the start of drawing) will occur in 4/5 cycles uint8_t repeatMask = _playerAndMissileSize[c&1] & 7; if( - ( _objectCounter[c] == 12 && ((repeatMask == 1) || (repeatMask == 3)) ) || - ( _objectCounter[c] == 28 && ((repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6)) ) || - ( _objectCounter[c] == 60 && ((repeatMask == 4) || (repeatMask == 6)) ) + ( _objectCounter[c] == 16 && ((repeatMask == 1) || (repeatMask == 3)) ) || + ( _objectCounter[c] == 32 && ((repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6)) ) || + ( _objectCounter[c] == 64 && ((repeatMask == 4) || (repeatMask == 6)) ) ) { unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus5 : upcomingEventsPointerPlus4; diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 087c00e97..29d399e15 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -51,7 +51,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawAtTime:(const CVTimeStamp *)now frequency:(double)frequency { const uint32_t processingMask = 0x01; - const uint32_t drawingMask = 0x02; +// const uint32_t drawingMask = 0x02; // Always post a -openGLView:didUpdateToTime:. This is the hook upon which the substantial processing occurs. if(!OSAtomicTestAndSet(processingMask, &_updateIsOngoing)) @@ -69,13 +69,13 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt // Draw the display only if a previous draw is not still ongoing. -drawViewOnlyIfDirty: is guaranteed // to be safe to call concurrently with -openGLView:updateToTime: so there's no need to worry about // the above interrupting the below or vice versa. - if(_hasSkipped && !OSAtomicTestAndSet(drawingMask, &_updateIsOngoing)) - { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - [self drawViewOnlyIfDirty:YES]; - OSAtomicTestAndClear(drawingMask, &_updateIsOngoing); - }); - } +// if(_hasSkipped && !OSAtomicTestAndSet(drawingMask, &_updateIsOngoing)) +// { +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ +// [self drawViewOnlyIfDirty:YES]; +// OSAtomicTestAndClear(drawingMask, &_updateIsOngoing); +// }); +// } } - (void)invalidate From cf5b8ab92efea9d03f8aa03ef9bb07eb74816519 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 19 May 2016 08:30:10 -0400 Subject: [PATCH 21/53] This is now approximately back to where it was on Cosmic Ark. --- Machines/Atari2600/Atari2600.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 8a7544bc2..2a5a95297 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -268,7 +268,7 @@ void Machine::output_pixels(unsigned int count) { for(int c = 0; c < 5; c++) { - if(((_objectMotion[c] >> 4)^8^_hMoveCounter) == 0xf) + if(((_objectMotion[c] >> 4)^_hMoveCounter) == 7) { _hMoveFlags &= ~(1 << c); } @@ -569,7 +569,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // schedule new moves _hMoveFlags = 0x1f; _hMoveCounter = 15; - _upcomingEvents[(_upcomingEventsPointer + 15)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; + + // 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 + _upcomingEvents[(_upcomingEventsPointer + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; break; case 0x2b: _objectMotion[0] = From a952813036f7a1e2544e8a13dd75ae9c4a794e2b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 19 May 2016 18:30:17 -0400 Subject: [PATCH 22/53] Fixed missile sizes, played about with ball placement. --- Machines/Atari2600/Atari2600.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 2a5a95297..ee4b174cd 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -80,6 +80,10 @@ Machine::~Machine() void Machine::update_upcoming_events() { unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus7 = (_upcomingEventsPointer + 7)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus8 = (_upcomingEventsPointer + 8)%number_of_upcoming_events; // grab the background now, for display in four clocks if(!(_horizontalTimer&3)) @@ -93,14 +97,12 @@ void Machine::update_upcoming_events() // is the result of a counter rollover or a programmatic reset if(!_objectCounter[4]) { - _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::ResetPixelCounter; - _upcomingEvents[upcomingEventsPointerPlus4].pixelCounterMask |= (1 << 4); + _upcomingEvents[upcomingEventsPointerPlus6].updates |= Event::Action::ResetPixelCounter; + _upcomingEvents[upcomingEventsPointerPlus6].pixelCounterMask |= (1 << 4); } _objectCounter[4] = (_objectCounter[4] + 1)%160; // check for player and missle triggers - unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; for(int c = 0; c < 4; c++) { // the players and missles become visible only upon overflow to zero, so schedule for @@ -160,7 +162,7 @@ uint8_t Machine::get_output_pixel() if((_missileGraphicsEnable[c]&2) && !(_missileGraphicsReset[c]&2)) { int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); - missilePixels[c] = ((_pixelCounter[c+2] >> 2) < missileSize) ? 1 : 0; + missilePixels[c] = (_pixelCounter[c+2] < missileSize) ? 1 : 0; } uint8_t repeatMask = _playerAndMissileSize[c] & 7; @@ -168,17 +170,15 @@ uint8_t Machine::get_output_pixel() { default: _pixelCounter[c] += 4; - _pixelCounter[c+2] += 4; break; case 5: _pixelCounter[c] += 2; - _pixelCounter[c+2] += 2; break; case 7: _pixelCounter[c] += 1; - _pixelCounter[c+2] += 1; break; } + _pixelCounter[c+2] ++; } // accumulate collisions From 36d19cb6cf080ad3c980a3ec42d969bdf5dacf1e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 21 May 2016 10:18:15 -0400 Subject: [PATCH 23/53] Made an attempt at properly pumping timers during hmove. --- Machines/Atari2600/Atari2600.cpp | 125 +++++++++++++++++-------------- Machines/Atari2600/Atari2600.hpp | 2 +- 2 files changed, 70 insertions(+), 57 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index ee4b174cd..b44b7d1e8 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -77,60 +77,95 @@ Machine::~Machine() close_output(); } -void Machine::update_upcoming_events() +void Machine::update_timers(int mask) { unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus7 = (_upcomingEventsPointer + 7)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus8 = (_upcomingEventsPointer + 8)%number_of_upcoming_events; +// unsigned int upcomingEventsPointerPlus7 = (_upcomingEventsPointer + 7)%number_of_upcoming_events; +// unsigned int upcomingEventsPointerPlus8 = (_upcomingEventsPointer + 8)%number_of_upcoming_events; // grab the background now, for display in four clocks - if(!(_horizontalTimer&3)) + if(mask & (1 << 5)) { - unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); - _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Playfield; - _upcomingEvents[upcomingEventsPointerPlus4].playfieldOutput = _playfield[(offset >> 2)%40]; + if(!(_horizontalTimer&3)) + { + unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); + _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Playfield; + _upcomingEvents[upcomingEventsPointerPlus4].playfieldOutput = _playfield[(offset >> 2)%40]; + } } // the ball becomes visible whenever it hits zero, regardless of whether its status // is the result of a counter rollover or a programmatic reset - if(!_objectCounter[4]) + if(mask & (1 << 4)) { - _upcomingEvents[upcomingEventsPointerPlus6].updates |= Event::Action::ResetPixelCounter; - _upcomingEvents[upcomingEventsPointerPlus6].pixelCounterMask |= (1 << 4); + if(!_objectCounter[4]) + { + _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::ResetPixelCounter; + _upcomingEvents[upcomingEventsPointerPlus4].pixelCounterMask |= (1 << 4); + } + _objectCounter[4] = (_objectCounter[4] + 1)%160; + _pixelCounter[4] ++; } - _objectCounter[4] = (_objectCounter[4] + 1)%160; // check for player and missle triggers for(int c = 0; c < 4; c++) { - // the players and missles become visible only upon overflow to zero, so schedule for - // 5/6 clocks ahead from 159 - if(_objectCounter[c] == 159) + if(mask & (1 << c)) { - unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus6 : upcomingEventsPointerPlus5; - _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; - _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); - } - else - { - // otherwise visibility is determined by an appropriate repeat mask and hitting any of 12, 28 or 60, - // in which case the counter reset (and hence the start of drawing) will occur in 4/5 cycles - uint8_t repeatMask = _playerAndMissileSize[c&1] & 7; - if( - ( _objectCounter[c] == 16 && ((repeatMask == 1) || (repeatMask == 3)) ) || - ( _objectCounter[c] == 32 && ((repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6)) ) || - ( _objectCounter[c] == 64 && ((repeatMask == 4) || (repeatMask == 6)) ) - ) + // the players and missles become visible only upon overflow to zero, so schedule for + // 5/6 clocks ahead from 159 + if(_objectCounter[c] == 159) { - unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus5 : upcomingEventsPointerPlus4; + unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus6 : upcomingEventsPointerPlus5; _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); } + else + { + // otherwise visibility is determined by an appropriate repeat mask and hitting any of 12, 28 or 60, + // in which case the counter reset (and hence the start of drawing) will occur in 4/5 cycles + uint8_t repeatMask = _playerAndMissileSize[c&1] & 7; + if( + ( _objectCounter[c] == 16 && ((repeatMask == 1) || (repeatMask == 3)) ) || + ( _objectCounter[c] == 32 && ((repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6)) ) || + ( _objectCounter[c] == 64 && ((repeatMask == 4) || (repeatMask == 6)) ) + ) + { + unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus5 : upcomingEventsPointerPlus4; + _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; + _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); + } + } + } + } + + for(int c = 0; c < 2; c++) + { + if(mask & (1 << c)) + { + uint8_t repeatMask = _playerAndMissileSize[c] & 7; + switch(repeatMask) + { + default: + _pixelCounter[c] += 4; + break; + case 5: + _pixelCounter[c] += 2; + break; + case 7: + _pixelCounter[c] += 1; + break; + } + _objectCounter[c] = (_objectCounter[c] + 1)%160; } - _objectCounter[c] = (_objectCounter[c] + 1)%160; + if(mask & (1 << (c + 2))) + { + _objectCounter[c+2] = (_objectCounter[c+2] + 1)%160; + _pixelCounter[c+2] ++; + } } } @@ -147,7 +182,6 @@ uint8_t Machine::get_output_pixel() int ballSize = 1 << ((_playfieldControl >> 4)&3); ballPixel = (_pixelCounter[4] < ballSize) ? 1 : 0; } - _pixelCounter[4] ++; // deal with the sprites uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; @@ -164,21 +198,6 @@ uint8_t Machine::get_output_pixel() int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); missilePixels[c] = (_pixelCounter[c+2] < missileSize) ? 1 : 0; } - - uint8_t repeatMask = _playerAndMissileSize[c] & 7; - switch(repeatMask) - { - default: - _pixelCounter[c] += 4; - break; - case 5: - _pixelCounter[c] += 2; - break; - case 7: - _pixelCounter[c] += 1; - break; - } - _pixelCounter[c+2] ++; } // accumulate collisions @@ -248,7 +267,7 @@ void Machine::output_pixels(unsigned int count) // grab background colour and schedule pixel counter resets if(state == OutputState::Pixel) - update_upcoming_events(); + update_timers(~0); // apply any queued changes and flush the record if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) @@ -283,14 +302,7 @@ void Machine::output_pixels(unsigned int count) if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveDecrement) { - for(int c = 0; c < 5; c++) - { - if(_hMoveFlags & (1 << c)) - { - _objectCounter[c] = (_objectCounter[c] + 1)%160; - _pixelCounter[c] ++; // TODO: this isn't always a straight increment - } - } + update_timers(_hMoveFlags); } _upcomingEvents[_upcomingEventsPointer].updates = 0; @@ -438,7 +450,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x01: _vBlankEnabled = !!(*value & 0x02); break; case 0x02: - set_ready_line(true); +// printf("%d\n", _horizontalTimer); + if(_horizontalTimer) set_ready_line(true); break; case 0x03: _horizontalTimer = 0; diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index c15077fdc..9cde69691 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -118,7 +118,7 @@ class Machine: public CPU6502::Processor { void output_pixels(unsigned int count); uint8_t get_output_pixel(); - void update_upcoming_events(); + void update_timers(int mask); Outputs::CRT::CRT *_crt; // latched output state From 470c90428ac82d7ca6245301d11a0798d5953696 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 21 May 2016 10:26:27 -0400 Subject: [PATCH 24/53] Switched to two-register implementation of VDELP, etc. --- Machines/Atari2600/Atari2600.cpp | 12 +++++------- Machines/Atari2600/Atari2600.hpp | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index b44b7d1e8..3f3cc223b 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -191,7 +191,7 @@ uint8_t Machine::get_output_pixel() // figure out player colour int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; if(_pixelCounter[c] < 32) - playerPixels[c] = (_playerGraphics[c] >> ((_pixelCounter[c] >> 2) ^ flipMask)) &1; + playerPixels[c] = (_playerGraphics[_playerGraphicsSelector[c]][c] >> ((_pixelCounter[c] >> 2) ^ flipMask)) &1; } if((_missileGraphicsEnable[c]&2) && !(_missileGraphicsReset[c]&2)) { @@ -535,10 +535,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _ballGraphicsEnable = _ballGraphicsEnableLatch; case 0x1b: { int index = decodedAddress - 0x1b; - _playerGraphicsLatch[index] = *value; - if(!(_playerGraphicsLatchEnable[index]&1)) - _playerGraphics[index] = _playerGraphicsLatch[index]; - _playerGraphics[index^1] = _playerGraphicsLatch[index^1]; + _playerGraphics[0][index] = *value; + _playerGraphics[1][index^1] = _playerGraphics[0][index^1]; } break; case 0x1d: _missileGraphicsEnable[0] = *value; break; case 0x1e: _missileGraphicsEnable[1] = *value; break; @@ -556,8 +554,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _objectMotion[decodedAddress - 0x20] = *value; break; - case 0x25: _playerGraphicsLatchEnable[0] = *value; break; - case 0x26: _playerGraphicsLatchEnable[1] = *value; break; + case 0x25: _playerGraphicsSelector[0] = (*value)&1; break; + case 0x26: _playerGraphicsSelector[1] = (*value)&1; break; case 0x27: _ballGraphicsEnableDelay = *value; break; case 0x28: diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 9cde69691..e65cb3218 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -80,8 +80,8 @@ class Machine: public CPU6502::Processor { // player registers uint8_t _playerColour[2]; uint8_t _playerReflection[2]; - uint8_t _playerGraphicsLatch[2], _playerGraphics[2]; - uint8_t _playerGraphicsLatchEnable[2]; + uint8_t _playerGraphics[2][2]; + uint8_t _playerGraphicsSelector[2]; bool _playerStart[2]; // player + missile registers From b1c84f5402d5daf8cc454b80790fd10e9e42bfda Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 21 May 2016 12:54:39 -0400 Subject: [PATCH 25/53] Counters run every scan line regardless of blank and sync. Also played further with timing. --- Machines/Atari2600/Atari2600.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 3f3cc223b..ff2aeb1b8 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -79,6 +79,9 @@ Machine::~Machine() void Machine::update_timers(int mask) { + unsigned int upcomingEventsPointerPlus1 = (_upcomingEventsPointer + 1)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus2 = (_upcomingEventsPointer + 2)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus3 = (_upcomingEventsPointer + 3)%number_of_upcoming_events; unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; @@ -91,8 +94,8 @@ void Machine::update_timers(int mask) if(!(_horizontalTimer&3)) { unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); - _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Playfield; - _upcomingEvents[upcomingEventsPointerPlus4].playfieldOutput = _playfield[(offset >> 2)%40]; + _upcomingEvents[upcomingEventsPointerPlus2].updates |= Event::Action::Playfield; + _upcomingEvents[upcomingEventsPointerPlus2].playfieldOutput = _playfield[(offset >> 2)%40]; } } @@ -102,8 +105,8 @@ void Machine::update_timers(int mask) { if(!_objectCounter[4]) { - _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::ResetPixelCounter; - _upcomingEvents[upcomingEventsPointerPlus4].pixelCounterMask |= (1 << 4); + _upcomingEvents[upcomingEventsPointerPlus2].updates |= Event::Action::ResetPixelCounter; + _upcomingEvents[upcomingEventsPointerPlus2].pixelCounterMask |= (1 << 4); } _objectCounter[4] = (_objectCounter[4] + 1)%160; _pixelCounter[4] ++; @@ -118,7 +121,8 @@ void Machine::update_timers(int mask) // 5/6 clocks ahead from 159 if(_objectCounter[c] == 159) { - unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus6 : upcomingEventsPointerPlus5; + unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus4 : upcomingEventsPointerPlus3; +// unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus6 : upcomingEventsPointerPlus5; _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); } @@ -133,7 +137,8 @@ void Machine::update_timers(int mask) ( _objectCounter[c] == 64 && ((repeatMask == 4) || (repeatMask == 6)) ) ) { - unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus5 : upcomingEventsPointerPlus4; + unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus3 : upcomingEventsPointerPlus2; +// unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus5 : upcomingEventsPointerPlus4; _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); } @@ -257,6 +262,10 @@ void Machine::output_pixels(unsigned int count) default: state = OutputState::Pixel; break; } + // grab background colour and schedule pixel counter resets + if(state == OutputState::Pixel) + update_timers(~0); + // if vsync is enabled, output the opposite of the automatic hsync output if(_vSyncEnabled) { state = (state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; @@ -265,10 +274,6 @@ void Machine::output_pixels(unsigned int count) // write that state as the one that will become effective in four clocks _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].state = state; - // grab background colour and schedule pixel counter resets - if(state == OutputState::Pixel) - update_timers(~0); - // apply any queued changes and flush the record if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldOutput; From 4eded9b9d0674e0f37f9a169174292fb57311d83 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 21 May 2016 13:05:36 -0400 Subject: [PATCH 26/53] Re-enabled collisions. --- Machines/Atari2600/Atari2600.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index ff2aeb1b8..7701e51a0 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -206,25 +206,25 @@ uint8_t Machine::get_output_pixel() } // accumulate collisions -/* if(playerPixels[0] | playerPixels[1]) { + if(playerPixels[0] | playerPixels[1]) { _collisions[0] |= ((missilePixels[0] & playerPixels[1]) << 7) | ((missilePixels[0] & playerPixels[0]) << 6); _collisions[1] |= ((missilePixels[1] & playerPixels[0]) << 7) | ((missilePixels[1] & playerPixels[1]) << 6); - _collisions[2] |= ((playfieldPixel & playerPixels[0]) << 7) | ((ballPixel & playerPixels[0]) << 6); - _collisions[3] |= ((playfieldPixel & playerPixels[1]) << 7) | ((ballPixel & playerPixels[1]) << 6); + _collisions[2] |= ((_playfieldOutput & playerPixels[0]) << 7) | ((ballPixel & playerPixels[0]) << 6); + _collisions[3] |= ((_playfieldOutput & playerPixels[1]) << 7) | ((ballPixel & playerPixels[1]) << 6); _collisions[7] |= ((playerPixels[0] & playerPixels[1]) << 7); } - if(playfieldPixel | ballPixel) { - _collisions[4] |= ((playfieldPixel & missilePixels[0]) << 7) | ((ballPixel & missilePixels[0]) << 6); - _collisions[5] |= ((playfieldPixel & missilePixels[1]) << 7) | ((ballPixel & missilePixels[1]) << 6); + if(_playfieldOutput | ballPixel) { + _collisions[4] |= ((_playfieldOutput & missilePixels[0]) << 7) | ((ballPixel & missilePixels[0]) << 6); + _collisions[5] |= ((_playfieldOutput & missilePixels[1]) << 7) | ((ballPixel & missilePixels[1]) << 6); - _collisions[6] |= ((playfieldPixel & ballPixel) << 7); + _collisions[6] |= ((_playfieldOutput & ballPixel) << 7); } if(missilePixels[0] & missilePixels[1]) - _collisions[7] |= (1 << 6);*/ + _collisions[7] |= (1 << 6); // apply appropriate priority to pick a colour uint8_t playfieldPixel = _playfieldOutput | ballPixel; From 5e220562e452f5ce6f055d64706ab49a1bd95f05 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 21 May 2016 21:44:54 -0400 Subject: [PATCH 27/53] Actually, the four-clock delay, with palette taking effect immediately, appears to be correct. So back to the drawing board on that. --- Machines/Atari2600/Atari2600.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 7701e51a0..5767be297 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -79,12 +79,12 @@ Machine::~Machine() void Machine::update_timers(int mask) { - unsigned int upcomingEventsPointerPlus1 = (_upcomingEventsPointer + 1)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus2 = (_upcomingEventsPointer + 2)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus3 = (_upcomingEventsPointer + 3)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; +// unsigned int upcomingEventsPointerPlus1 = (_upcomingEventsPointer + 1)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus2 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus3 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; +// unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; +// unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; // unsigned int upcomingEventsPointerPlus7 = (_upcomingEventsPointer + 7)%number_of_upcoming_events; // unsigned int upcomingEventsPointerPlus8 = (_upcomingEventsPointer + 8)%number_of_upcoming_events; @@ -227,12 +227,14 @@ uint8_t Machine::get_output_pixel() _collisions[7] |= (1 << 6); // apply appropriate priority to pick a colour - uint8_t playfieldPixel = _playfieldOutput | ballPixel; + uint8_t playfieldPixel = _playfieldOutput;// | ballPixel; uint8_t outputColour = playfieldPixel ? playfieldColour : _backgroundColour; if(!(_playfieldControl&0x04) || !playfieldPixel) { - if(playerPixels[1] || missilePixels[1]) outputColour = _playerColour[1]; - if(playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; + if(missilePixels[1]) outputColour = _playerColour[1]; + if(missilePixels[0]) outputColour = _playerColour[0]; +// if(playerPixels[1] || missilePixels[1]) outputColour = _playerColour[1]; +// if(playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; } // return colour From 0242924fb4701f55893bf81d631543f11e944b46 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 22 May 2016 14:26:02 -0400 Subject: [PATCH 28/53] Various bits of caveman debugging appearing and disappearing, switched to latching ball behaviour. --- Machines/Atari2600/Atari2600.cpp | 81 +++++++++++++++++++++++--------- Machines/Atari2600/Atari2600.hpp | 14 +++--- Processors/6502/CPU6502.hpp | 1 + 3 files changed, 68 insertions(+), 28 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 5767be297..62ea63a34 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -79,6 +79,8 @@ Machine::~Machine() void Machine::update_timers(int mask) { +// if(_vBlankExtend) printf("."); + // unsigned int upcomingEventsPointerPlus1 = (_upcomingEventsPointer + 1)%number_of_upcoming_events; unsigned int upcomingEventsPointerPlus2 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; unsigned int upcomingEventsPointerPlus3 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; @@ -153,15 +155,9 @@ void Machine::update_timers(int mask) uint8_t repeatMask = _playerAndMissileSize[c] & 7; switch(repeatMask) { - default: - _pixelCounter[c] += 4; - break; - case 5: - _pixelCounter[c] += 2; - break; - case 7: - _pixelCounter[c] += 1; - break; + default: _pixelCounter[c] += 4; break; + case 5: _pixelCounter[c] += 2; break; + case 7: _pixelCounter[c] += 1; break; } _objectCounter[c] = (_objectCounter[c] + 1)%160; } @@ -183,7 +179,7 @@ uint8_t Machine::get_output_pixel() // get the ball proposed state uint8_t ballPixel = 0; - if(_ballGraphicsEnable&2) { + if(_ballGraphicsEnable[_ballGraphicsSelector]&2) { int ballSize = 1 << ((_playfieldControl >> 4)&3); ballPixel = (_pixelCounter[4] < ballSize) ? 1 : 0; } @@ -231,10 +227,10 @@ uint8_t Machine::get_output_pixel() uint8_t outputColour = playfieldPixel ? playfieldColour : _backgroundColour; if(!(_playfieldControl&0x04) || !playfieldPixel) { - if(missilePixels[1]) outputColour = _playerColour[1]; - if(missilePixels[0]) outputColour = _playerColour[0]; -// if(playerPixels[1] || missilePixels[1]) outputColour = _playerColour[1]; -// if(playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; +// if(missilePixels[1]) outputColour = _playerColour[1]; +// if(missilePixels[0]) outputColour = _playerColour[0]; + if(playerPixels[1] || missilePixels[1]) outputColour = _playerColour[1]; + if(playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; } // return colour @@ -263,6 +259,10 @@ void Machine::output_pixels(unsigned int count) case 16: case 17: state = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; default: state = OutputState::Pixel; break; } +// if(!(_horizontalTimer&3) && _vBlankExtend) +// { +// printf("%c", 'a' + state); +// } // grab background colour and schedule pixel counter resets if(state == OutputState::Pixel) @@ -305,11 +305,20 @@ void Machine::output_pixels(unsigned int count) _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; _upcomingEvents[(_upcomingEventsPointer+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; } +// else +// { +// _vBlankExtend = false; +// } } if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveDecrement) { update_timers(_hMoveFlags); +// for(int c = 0; c < 5; c++) +// { +// printf("%c", _hMoveFlags & (1 << c) ? 'X' : '-'); +// } +// printf(" "); } _upcomingEvents[_upcomingEventsPointer].updates = 0; @@ -383,6 +392,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin output_pixels(cycles_run_for * 3); +// printf("/%d/", _horizontalTimer); + if(operation != CPU6502::BusOperation::Ready) { // check for a paging access @@ -458,10 +469,12 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x02: // printf("%d\n", _horizontalTimer); +// printf("W"); if(_horizontalTimer) set_ready_line(true); break; case 0x03: - _horizontalTimer = 0; + _horizontalTimer = 0; // TODO: this should be delayed by four cycles, affecting phase; + // need to fix wait logic. break; case 0x04: @@ -539,7 +552,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin break; case 0x1c: - _ballGraphicsEnable = _ballGraphicsEnableLatch; + _ballGraphicsEnable[1] = _ballGraphicsEnable[0]; case 0x1b: { int index = decodedAddress - 0x1b; _playerGraphics[0][index] = *value; @@ -548,9 +561,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x1d: _missileGraphicsEnable[0] = *value; break; case 0x1e: _missileGraphicsEnable[1] = *value; break; case 0x1f: - _ballGraphicsEnableLatch = *value; - if(!(_ballGraphicsEnableDelay&1)) - _ballGraphicsEnable = _ballGraphicsEnableLatch; + _ballGraphicsEnable[0] = *value; break; case 0x20: @@ -561,9 +572,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _objectMotion[decodedAddress - 0x20] = *value; break; - case 0x25: _playerGraphicsSelector[0] = (*value)&1; break; - case 0x26: _playerGraphicsSelector[1] = (*value)&1; break; - case 0x27: _ballGraphicsEnableDelay = *value; break; + case 0x25: _playerGraphicsSelector[0] = (*value)&1; break; + case 0x26: _playerGraphicsSelector[1] = (*value)&1; break; + case 0x27: _ballGraphicsSelector = (*value)&1; break; case 0x28: case 0x29: @@ -588,6 +599,31 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _hMoveFlags = 0x1f; _hMoveCounter = 15; +// static int lc = 0; +// lc++; +// if(lc == 2813) +// printf("{%d}", lc); + +// printf("/%d/", _horizontalTimer); +// printf("%d [", _horizontalTimer); +// for(int c = 0; c < 5; c++) +// { +// printf("%d ", _objectMotion[c] >> 4); +// } +// printf("]"); +// printf("["); +// for(int c = 0; c < 5; c++) +// { +// printf("%d ", _objectCounter[c]); +// } +// printf("]\n"); + +// for(int c = 0; c < 5; c++) +// { +// if(_objectMotion[c] >> 4) +// printf("!"); +// } + // 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 @@ -622,6 +658,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x01: case 0x03: // TODO: port DDR + printf("!!!DDR!!!"); break; case 0x04: returnValue &= _piaTimerValue >> _piaTimerShift; diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index e65cb3218..5854f78b2 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -61,14 +61,16 @@ class Machine: public CPU6502::Processor { struct Event { enum Action { Playfield = 1 << 0, - ResetPixelCounter = 1 << 1, - HMoveCompare = 1 << 2, - HMoveDecrement = 1 << 3, + + ResetPixelCounter = 1 << 3, + + HMoveCompare = 1 << 4, + HMoveDecrement = 1 << 5, }; int updates; int pixelCounterMask; - uint8_t playfieldOutput; + uint8_t playfieldOutput, player0Output, player1Output; OutputState state; Event() : updates(0), pixelCounterMask(0) {} @@ -91,8 +93,8 @@ class Machine: public CPU6502::Processor { uint8_t _missileGraphicsEnable[2], _missileGraphicsReset[2]; // ball registers - uint8_t _ballGraphicsEnable, _ballGraphicsEnableLatch; - uint8_t _ballGraphicsEnableDelay; + uint8_t _ballGraphicsEnable[2]; + uint8_t _ballGraphicsSelector; // graphics output unsigned int _horizontalTimer; diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index f5d731daa..fc3fc5633 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -597,6 +597,7 @@ template class Processor { case CycleFetchOperation: { _lastOperationPC = _pc; +// printf("%04x\n", _pc.full); _pc.full++; read_op(_operation, _lastOperationPC.full); From df93d7849da8cbd6c9bb0870a7692bcdaad90509 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 22 May 2016 14:38:14 -0400 Subject: [PATCH 29/53] Re-enabled the ball. --- Machines/Atari2600/Atari2600.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 62ea63a34..19b7b585c 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -223,12 +223,10 @@ uint8_t Machine::get_output_pixel() _collisions[7] |= (1 << 6); // apply appropriate priority to pick a colour - uint8_t playfieldPixel = _playfieldOutput;// | ballPixel; + uint8_t playfieldPixel = _playfieldOutput | ballPixel; uint8_t outputColour = playfieldPixel ? playfieldColour : _backgroundColour; if(!(_playfieldControl&0x04) || !playfieldPixel) { -// if(missilePixels[1]) outputColour = _playerColour[1]; -// if(missilePixels[0]) outputColour = _playerColour[0]; if(playerPixels[1] || missilePixels[1]) outputColour = _playerColour[1]; if(playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; } From 4c6d4d899d23d671bb9ec4a3f0121160336db380 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 22 May 2016 16:29:53 -0400 Subject: [PATCH 30/53] Ensured that resetting the horizontal timer affects phase. --- Machines/Atari2600/Atari2600.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 19b7b585c..84b8f57ec 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -378,6 +378,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin uint8_t returnValue = 0xff; unsigned int cycles_run_for = 1; + unsigned int additional_pixels = 0; // this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for // leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take @@ -386,9 +387,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if(operation == CPU6502::BusOperation::Ready) { unsigned int distance_to_end_of_ready = horizontalTimerPeriod - _horizontalTimer; cycles_run_for = distance_to_end_of_ready / 3; + additional_pixels = distance_to_end_of_ready % 3; } - output_pixels(cycles_run_for * 3); + output_pixels(additional_pixels + cycles_run_for * 3); // printf("/%d/", _horizontalTimer); @@ -471,8 +473,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if(_horizontalTimer) set_ready_line(true); break; case 0x03: - _horizontalTimer = 0; // TODO: this should be delayed by four cycles, affecting phase; - // need to fix wait logic. + // Reset is delayed by four cycles. + _horizontalTimer = horizontalTimerPeriod - 4; break; case 0x04: @@ -699,7 +701,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _piaTimerStatus |= 0xc0; } -// output_pixels(cycles_run_for * 3); +// output_pixels(additional_pixels + cycles_run_for * 3); return cycles_run_for; } From c3e719c4abbe95b057c7dc67a27466b0bb3484fd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 22 May 2016 17:01:56 -0400 Subject: [PATCH 31/53] Added missile-to-player offsetting. Completing the list of graphics-related TODOs. --- Machines/Atari2600/Atari2600.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 84b8f57ec..2224147b2 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -195,7 +195,7 @@ uint8_t Machine::get_output_pixel() playerPixels[c] = (_playerGraphics[_playerGraphicsSelector[c]][c] >> ((_pixelCounter[c] >> 2) ^ flipMask)) &1; } - if((_missileGraphicsEnable[c]&2) && !(_missileGraphicsReset[c]&2)) { + if((_missileGraphicsEnable[c]&2) && !_missileGraphicsReset[c]) { int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); missilePixels[c] = (_pixelCounter[c+2] < missileSize) ? 1 : 0; } @@ -578,9 +578,25 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x28: case 0x29: - if(!(*value&0x02) && _missileGraphicsReset[decodedAddress - 0x28]&0x02) - _objectCounter[decodedAddress - 0x26] = _objectCounter[decodedAddress - 0x28]; // TODO: +3 for normal, +6 for double, +10 for quad - _missileGraphicsReset[decodedAddress - 0x28] = *value; + { + int index = decodedAddress - 0x28; + if(!(*value&0x02) && _missileGraphicsReset[index]) + { + _objectCounter[index + 2] = _objectCounter[index]; + + uint8_t repeatMask = _playerAndMissileSize[index] & 7; + int extra_offset; + switch(repeatMask) + { + default: extra_offset = 3; break; + case 5: extra_offset = 6; break; + case 7: extra_offset = 10; break; + } + + _objectCounter[index + 2] = (_objectCounter[index + 2] + extra_offset)%160; + } + _missileGraphicsReset[index] = (*value) & 0x02; + } break; case 0x2a: From 5c1e5949373cf6537f599e53e30f26c60226eeb1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 22 May 2016 21:45:40 -0400 Subject: [PATCH 32/53] Realised there's a clocking delay on starting horizontal move. Which fixes Coke Zero. --- Machines/Atari2600/Atari2600.cpp | 110 +++++++++++++------------------ Machines/Atari2600/Atari2600.hpp | 9 ++- 2 files changed, 53 insertions(+), 66 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 2224147b2..b9b88eeda 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -79,16 +79,9 @@ Machine::~Machine() void Machine::update_timers(int mask) { -// if(_vBlankExtend) printf("."); - -// unsigned int upcomingEventsPointerPlus1 = (_upcomingEventsPointer + 1)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus2 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus3 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; -// unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; -// unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; -// unsigned int upcomingEventsPointerPlus7 = (_upcomingEventsPointer + 7)%number_of_upcoming_events; -// unsigned int upcomingEventsPointerPlus8 = (_upcomingEventsPointer + 8)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; // grab the background now, for display in four clocks if(mask & (1 << 5)) @@ -96,8 +89,8 @@ void Machine::update_timers(int mask) if(!(_horizontalTimer&3)) { unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); - _upcomingEvents[upcomingEventsPointerPlus2].updates |= Event::Action::Playfield; - _upcomingEvents[upcomingEventsPointerPlus2].playfieldOutput = _playfield[(offset >> 2)%40]; + _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Playfield; + _upcomingEvents[upcomingEventsPointerPlus4].playfieldOutput = _playfield[(offset >> 2)%40]; } } @@ -107,8 +100,8 @@ void Machine::update_timers(int mask) { if(!_objectCounter[4]) { - _upcomingEvents[upcomingEventsPointerPlus2].updates |= Event::Action::ResetPixelCounter; - _upcomingEvents[upcomingEventsPointerPlus2].pixelCounterMask |= (1 << 4); + _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::ResetPixelCounter; + _upcomingEvents[upcomingEventsPointerPlus4].pixelCounterMask |= (1 << 4); } _objectCounter[4] = (_objectCounter[4] + 1)%160; _pixelCounter[4] ++; @@ -123,8 +116,7 @@ void Machine::update_timers(int mask) // 5/6 clocks ahead from 159 if(_objectCounter[c] == 159) { - unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus4 : upcomingEventsPointerPlus3; -// unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus6 : upcomingEventsPointerPlus5; + unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus6 : upcomingEventsPointerPlus5; _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); } @@ -139,8 +131,7 @@ void Machine::update_timers(int mask) ( _objectCounter[c] == 64 && ((repeatMask == 4) || (repeatMask == 6)) ) ) { - unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus3 : upcomingEventsPointerPlus2; -// unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus5 : upcomingEventsPointerPlus4; + unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus5 : upcomingEventsPointerPlus4; _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); } @@ -148,6 +139,7 @@ void Machine::update_timers(int mask) } } + // update the pixel counters for(int c = 0; c < 2; c++) { if(mask & (1 << c)) @@ -168,6 +160,23 @@ void Machine::update_timers(int mask) _pixelCounter[c+2] ++; } } + + // determine the pixel masks +// uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; +// for(int c = 0; c < 2; c++) +// { +// if(_playerGraphics[c]) { +// // figure out player colour +// int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; +// if(_pixelCounter[c] < 32) +// playerPixels[c] = (_playerGraphics[_playerGraphicsSelector[c]][c] >> ((_pixelCounter[c] >> 2) ^ flipMask)) &1; +// } +// +// if((_missileGraphicsEnable[c]&2) && !_missileGraphicsReset[c]) { +// int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); +// missilePixels[c] = (_pixelCounter[c+2] < missileSize) ? 1 : 0; +// } +// } } uint8_t Machine::get_output_pixel() @@ -288,6 +297,25 @@ void Machine::output_pixels(unsigned int count) _upcomingEvents[_upcomingEventsPointer].pixelCounterMask = 0; } + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveSetup) + { + _upcomingEvents[_upcomingEventsPointer].updates |= Event::Action::HMoveCompare; + _vBlankExtend = true; + + // clear any ongoing moves + if(_hMoveFlags) + { + for(int c = 0; c < number_of_upcoming_events; c++) + { + _upcomingEvents[c].updates &= ~(Event::Action::HMoveCompare | Event::Action::HMoveDecrement); + } + } + + // schedule new moves + _hMoveFlags = 0x1f; + _hMoveCounter = 15; + } + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveCompare) { for(int c = 0; c < 5; c++) @@ -303,10 +331,6 @@ void Machine::output_pixels(unsigned int count) _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; _upcomingEvents[(_upcomingEventsPointer+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; } -// else -// { -// _vBlankExtend = false; -// } } if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveDecrement) @@ -600,50 +624,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin break; case 0x2a: - _vBlankExtend = true; - - // clear any ongoing moves - if(_hMoveFlags) - { - for(int c = 0; c < number_of_upcoming_events; c++) - { - _upcomingEvents[c].updates &= ~(Event::Action::HMoveCompare | Event::Action::HMoveDecrement); - } - } - - // schedule new moves - _hMoveFlags = 0x1f; - _hMoveCounter = 15; - -// static int lc = 0; -// lc++; -// if(lc == 2813) -// printf("{%d}", lc); - -// printf("/%d/", _horizontalTimer); -// printf("%d [", _horizontalTimer); -// for(int c = 0; c < 5; c++) -// { -// printf("%d ", _objectMotion[c] >> 4); -// } -// printf("]"); -// printf("["); -// for(int c = 0; c < 5; c++) -// { -// printf("%d ", _objectCounter[c]); -// } -// printf("]\n"); - -// for(int c = 0; c < 5; c++) -// { -// if(_objectMotion[c] >> 4) -// printf("!"); -// } - // 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 - _upcomingEvents[(_upcomingEventsPointer + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; + _upcomingEvents[(_upcomingEventsPointer + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup; break; case 0x2b: _objectMotion[0] = diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 5854f78b2..ab4c6b587 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -64,15 +64,18 @@ class Machine: public CPU6502::Processor { ResetPixelCounter = 1 << 3, - HMoveCompare = 1 << 4, - HMoveDecrement = 1 << 5, + HMoveSetup = 1 << 4, + HMoveCompare = 1 << 5, + HMoveDecrement = 1 << 6, }; int updates; int pixelCounterMask; - uint8_t playfieldOutput, player0Output, player1Output; OutputState state; + uint8_t playfieldOutput, playerOutput[2]; + uint8_t ballPixel; + Event() : updates(0), pixelCounterMask(0) {} } _upcomingEvents[number_of_upcoming_events]; unsigned int _upcomingEventsPointer; From 0b081831a985bd0eb958c9b63cf4f1fbc2f61f5d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 24 May 2016 07:58:26 -0400 Subject: [PATCH 33/53] Set up a pipeline for all pixels that attempts to allow for appropriate delays. --- Machines/Atari2600/Atari2600.cpp | 111 +++++++++++++++---------------- Machines/Atari2600/Atari2600.hpp | 8 +-- 2 files changed, 55 insertions(+), 64 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index b9b88eeda..b067d0f93 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -80,8 +80,8 @@ Machine::~Machine() void Machine::update_timers(int mask) { unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus1 = (_upcomingEventsPointer + 1)%number_of_upcoming_events; + unsigned int upcomingEventsPointerPlus2 = (_upcomingEventsPointer + 2)%number_of_upcoming_events; // grab the background now, for display in four clocks if(mask & (1 << 5)) @@ -90,19 +90,21 @@ void Machine::update_timers(int mask) { unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Playfield; - _upcomingEvents[upcomingEventsPointerPlus4].playfieldOutput = _playfield[(offset >> 2)%40]; + _upcomingEvents[upcomingEventsPointerPlus4].pixels |= _playfield[(offset >> 2)%40] << 5; } } - // the ball becomes visible whenever it hits zero, regardless of whether its status - // is the result of a counter rollover or a programmatic reset if(mask & (1 << 4)) { - if(!_objectCounter[4]) - { - _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::ResetPixelCounter; - _upcomingEvents[upcomingEventsPointerPlus4].pixelCounterMask |= (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 + if(!_objectCounter[4]) _pixelCounter[4] = 0; + + if(_pixelCounter[4] < 8 && _ballGraphicsEnable[_ballGraphicsSelector]&2) { + int ballSize = 1 << ((_playfieldControl >> 4)&3); + _upcomingEvents[upcomingEventsPointerPlus4].pixels |= (_pixelCounter[4] < ballSize) ? (1 << 4) : 0; } + _objectCounter[4] = (_objectCounter[4] + 1)%160; _pixelCounter[4] ++; } @@ -113,10 +115,10 @@ void Machine::update_timers(int mask) if(mask & (1 << c)) { // the players and missles become visible only upon overflow to zero, so schedule for - // 5/6 clocks ahead from 159 + // 1/2 clocks ahead from 159 if(_objectCounter[c] == 159) { - unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus6 : upcomingEventsPointerPlus5; + unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus2 : upcomingEventsPointerPlus1; _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); } @@ -131,7 +133,7 @@ void Machine::update_timers(int mask) ( _objectCounter[c] == 64 && ((repeatMask == 4) || (repeatMask == 6)) ) ) { - unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus5 : upcomingEventsPointerPlus4; + unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus1 : _upcomingEventsPointer; _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); } @@ -161,22 +163,32 @@ void Machine::update_timers(int mask) } } + // apply any resets + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ResetPixelCounter) + { + for(int c = 0; c < 5; c++) + { + if(_upcomingEvents[_upcomingEventsPointer].pixelCounterMask & (1 << c)) + _pixelCounter[c] = 0; + } + _upcomingEvents[_upcomingEventsPointer].pixelCounterMask = 0; + } + // determine the pixel masks -// uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; -// for(int c = 0; c < 2; c++) -// { -// if(_playerGraphics[c]) { -// // figure out player colour -// int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; -// if(_pixelCounter[c] < 32) -// playerPixels[c] = (_playerGraphics[_playerGraphicsSelector[c]][c] >> ((_pixelCounter[c] >> 2) ^ flipMask)) &1; -// } -// -// if((_missileGraphicsEnable[c]&2) && !_missileGraphicsReset[c]) { -// int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); -// missilePixels[c] = (_pixelCounter[c+2] < missileSize) ? 1 : 0; -// } -// } + for(int c = 0; c < 2; c++) + { + if(_playerGraphics[c]) { + // figure out player colour + int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; + if(_pixelCounter[c] < 32) + _upcomingEvents[upcomingEventsPointerPlus4].pixels |= ((_playerGraphics[_playerGraphicsSelector[c]][c] >> ((_pixelCounter[c] >> 2) ^ flipMask)) & 1) << c; + } + + if(_pixelCounter[c+2] < 8 && (_missileGraphicsEnable[c]&2) && !_missileGraphicsReset[c]) { + int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); + _upcomingEvents[upcomingEventsPointerPlus4].pixels |= ((_pixelCounter[c+2] < missileSize) ? 1 : 0) << (c + 2); + } + } } uint8_t Machine::get_output_pixel() @@ -187,28 +199,19 @@ uint8_t Machine::get_output_pixel() uint8_t playfieldColour = ((_playfieldControl&6) == 2) ? _playerColour[offset / 80] : _playfieldColour; // get the ball proposed state - uint8_t ballPixel = 0; - if(_ballGraphicsEnable[_ballGraphicsSelector]&2) { - int ballSize = 1 << ((_playfieldControl >> 4)&3); - ballPixel = (_pixelCounter[4] < ballSize) ? 1 : 0; - } + uint8_t ballPixel = (_upcomingEvents[_upcomingEventsPointer].pixels >> 4) & 1; // deal with the sprites - uint8_t playerPixels[2] = {0, 0}, missilePixels[2] = {0, 0}; - for(int c = 0; c < 2; c++) + uint8_t playerPixels[2] = { - if(_playerGraphics[c]) { - // figure out player colour - int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; - if(_pixelCounter[c] < 32) - playerPixels[c] = (_playerGraphics[_playerGraphicsSelector[c]][c] >> ((_pixelCounter[c] >> 2) ^ flipMask)) &1; - } - - if((_missileGraphicsEnable[c]&2) && !_missileGraphicsReset[c]) { - int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); - missilePixels[c] = (_pixelCounter[c+2] < missileSize) ? 1 : 0; - } - } + static_cast((_upcomingEvents[_upcomingEventsPointer].pixels >> 0) & 1), + static_cast((_upcomingEvents[_upcomingEventsPointer].pixels >> 1) & 1) + }; + uint8_t missilePixels[2] = + { + static_cast((_upcomingEvents[_upcomingEventsPointer].pixels >> 2) & 1), + static_cast((_upcomingEvents[_upcomingEventsPointer].pixels >> 3) & 1), + }; // accumulate collisions if(playerPixels[0] | playerPixels[1]) { @@ -285,17 +288,7 @@ void Machine::output_pixels(unsigned int count) // apply any queued changes and flush the record if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) - _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldOutput; - - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ResetPixelCounter) - { - for(int c = 0; c < 5; c++) - { - if(_upcomingEvents[_upcomingEventsPointer].pixelCounterMask & (1 << c)) - _pixelCounter[c] = 0; - } - _upcomingEvents[_upcomingEventsPointer].pixelCounterMask = 0; - } + _playfieldOutput = (_upcomingEvents[_upcomingEventsPointer].pixels >> 5)&1; if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveSetup) { @@ -342,8 +335,6 @@ void Machine::output_pixels(unsigned int count) // } // printf(" "); } - _upcomingEvents[_upcomingEventsPointer].updates = 0; - // read that state state = _upcomingEvents[_upcomingEventsPointer].state; OutputState actingState = state; @@ -384,6 +375,8 @@ void Machine::output_pixels(unsigned int count) } // advance + _upcomingEvents[_upcomingEventsPointer].updates = 0; + _upcomingEvents[_upcomingEventsPointer].pixels = 0; _upcomingEventsPointer = (_upcomingEventsPointer + 1)%number_of_upcoming_events; // advance horizontal timer, perform reset actions if requested diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index ab4c6b587..081a1407f 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -16,7 +16,7 @@ namespace Atari2600 { -const unsigned int number_of_upcoming_events = 18; +const unsigned int number_of_upcoming_events = 6; class Machine: public CPU6502::Processor { @@ -72,11 +72,9 @@ class Machine: public CPU6502::Processor { int pixelCounterMask; OutputState state; + uint8_t pixels; - uint8_t playfieldOutput, playerOutput[2]; - uint8_t ballPixel; - - Event() : updates(0), pixelCounterMask(0) {} + Event() : updates(0), pixelCounterMask(0), pixels(0) {} } _upcomingEvents[number_of_upcoming_events]; unsigned int _upcomingEventsPointer; From 758806f9243c3584a8a080e1120816968503e119 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 24 May 2016 21:39:57 -0400 Subject: [PATCH 34/53] Introduced a separate queue for pixels, which may or may not be correct. --- Machines/Atari2600/Atari2600.cpp | 63 ++++++++++++++++---------------- Machines/Atari2600/Atari2600.hpp | 9 +++-- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index b067d0f93..1705fd0c9 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -23,7 +23,8 @@ Machine::Machine() : _rom(nullptr), _piaDataValue{0xff, 0xff}, _tiaInputValue{0xff, 0xff}, - _upcomingEventsPointer(0) + _upcomingEventsPointer(0), + _upcomingPixelsPointer(0) { memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); @@ -83,6 +84,8 @@ void Machine::update_timers(int mask) unsigned int upcomingEventsPointerPlus1 = (_upcomingEventsPointer + 1)%number_of_upcoming_events; unsigned int upcomingEventsPointerPlus2 = (_upcomingEventsPointer + 2)%number_of_upcoming_events; + unsigned int upcomingPixelsPointerPlus4 = (_upcomingPixelsPointer + 4)%number_of_upcoming_events; + // grab the background now, for display in four clocks if(mask & (1 << 5)) { @@ -90,7 +93,7 @@ void Machine::update_timers(int mask) { unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Playfield; - _upcomingEvents[upcomingEventsPointerPlus4].pixels |= _playfield[(offset >> 2)%40] << 5; + _upcomingEvents[upcomingEventsPointerPlus4].playfieldPixel = _playfield[(offset >> 2)%40]; } } @@ -102,7 +105,7 @@ void Machine::update_timers(int mask) if(_pixelCounter[4] < 8 && _ballGraphicsEnable[_ballGraphicsSelector]&2) { int ballSize = 1 << ((_playfieldControl >> 4)&3); - _upcomingEvents[upcomingEventsPointerPlus4].pixels |= (_pixelCounter[4] < ballSize) ? (1 << 4) : 0; + _upcomingPixels[upcomingPixelsPointerPlus4] |= (_pixelCounter[4] < ballSize) ? (1 << 4) : 0; } _objectCounter[4] = (_objectCounter[4] + 1)%160; @@ -146,12 +149,15 @@ void Machine::update_timers(int mask) { if(mask & (1 << c)) { + int lastSpriteCounter = _spriteCounter[c]; + _spriteCounter[c]++; + uint8_t repeatMask = _playerAndMissileSize[c] & 7; switch(repeatMask) { - default: _pixelCounter[c] += 4; break; - case 5: _pixelCounter[c] += 2; break; - case 7: _pixelCounter[c] += 1; break; + default: _pixelCounter[c] ++; break; + case 5: _pixelCounter[c] += ((lastSpriteCounter ^ _spriteCounter[c]) >> 1)&1; break; + case 7: _pixelCounter[c] += ((lastSpriteCounter ^ _spriteCounter[c]) >> 2)&1; break; } _objectCounter[c] = (_objectCounter[c] + 1)%160; } @@ -174,19 +180,23 @@ void Machine::update_timers(int mask) _upcomingEvents[_upcomingEventsPointer].pixelCounterMask = 0; } + // reload the playfield pixel if appropriate + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) + { + _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldPixel; + } + // determine the pixel masks for(int c = 0; c < 2; c++) { - if(_playerGraphics[c]) { - // figure out player colour + if(_playerGraphics[c] && _pixelCounter[c] < 32) { int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; - if(_pixelCounter[c] < 32) - _upcomingEvents[upcomingEventsPointerPlus4].pixels |= ((_playerGraphics[_playerGraphicsSelector[c]][c] >> ((_pixelCounter[c] >> 2) ^ flipMask)) & 1) << c; + _upcomingPixels[upcomingPixelsPointerPlus4] |= ((_playerGraphics[_playerGraphicsSelector[c]][c] >> (_pixelCounter[c] ^ flipMask)) & 1) << c; } if(_pixelCounter[c+2] < 8 && (_missileGraphicsEnable[c]&2) && !_missileGraphicsReset[c]) { int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); - _upcomingEvents[upcomingEventsPointerPlus4].pixels |= ((_pixelCounter[c+2] < missileSize) ? 1 : 0) << (c + 2); + _upcomingPixels[upcomingPixelsPointerPlus4] |= ((_pixelCounter[c+2] < missileSize) ? 1 : 0) << (c + 2); } } } @@ -199,18 +209,18 @@ uint8_t Machine::get_output_pixel() uint8_t playfieldColour = ((_playfieldControl&6) == 2) ? _playerColour[offset / 80] : _playfieldColour; // get the ball proposed state - uint8_t ballPixel = (_upcomingEvents[_upcomingEventsPointer].pixels >> 4) & 1; + uint8_t ballPixel = (_upcomingPixels[_upcomingPixelsPointer] >> 4) & 1; // deal with the sprites uint8_t playerPixels[2] = { - static_cast((_upcomingEvents[_upcomingEventsPointer].pixels >> 0) & 1), - static_cast((_upcomingEvents[_upcomingEventsPointer].pixels >> 1) & 1) + static_cast((_upcomingPixels[_upcomingPixelsPointer] >> 0) & 1), + static_cast((_upcomingPixels[_upcomingPixelsPointer] >> 1) & 1) }; uint8_t missilePixels[2] = { - static_cast((_upcomingEvents[_upcomingEventsPointer].pixels >> 2) & 1), - static_cast((_upcomingEvents[_upcomingEventsPointer].pixels >> 3) & 1), + static_cast((_upcomingPixels[_upcomingPixelsPointer] >> 2) & 1), + static_cast((_upcomingPixels[_upcomingPixelsPointer] >> 3) & 1), }; // accumulate collisions @@ -243,6 +253,10 @@ uint8_t Machine::get_output_pixel() if(playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; } + // update pixel chain + _upcomingPixels[_upcomingPixelsPointer] = 0; + _upcomingPixelsPointer = (_upcomingPixelsPointer + 1)%number_of_upcoming_events; + // return colour return outputColour; } @@ -269,12 +283,7 @@ void Machine::output_pixels(unsigned int count) case 16: case 17: state = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; default: state = OutputState::Pixel; break; } -// if(!(_horizontalTimer&3) && _vBlankExtend) -// { -// printf("%c", 'a' + state); -// } - - // grab background colour and schedule pixel counter resets + // update pixel timers if(state == OutputState::Pixel) update_timers(~0); @@ -287,9 +296,6 @@ void Machine::output_pixels(unsigned int count) _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].state = state; // apply any queued changes and flush the record - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) - _playfieldOutput = (_upcomingEvents[_upcomingEventsPointer].pixels >> 5)&1; - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveSetup) { _upcomingEvents[_upcomingEventsPointer].updates |= Event::Action::HMoveCompare; @@ -329,12 +335,8 @@ void Machine::output_pixels(unsigned int count) if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveDecrement) { update_timers(_hMoveFlags); -// for(int c = 0; c < 5; c++) -// { -// printf("%c", _hMoveFlags & (1 << c) ? 'X' : '-'); -// } -// printf(" "); } + // read that state state = _upcomingEvents[_upcomingEventsPointer].state; OutputState actingState = state; @@ -376,7 +378,6 @@ void Machine::output_pixels(unsigned int count) // advance _upcomingEvents[_upcomingEventsPointer].updates = 0; - _upcomingEvents[_upcomingEventsPointer].pixels = 0; _upcomingEventsPointer = (_upcomingEventsPointer + 1)%number_of_upcoming_events; // advance horizontal timer, perform reset actions if requested diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 081a1407f..a38c6e5d2 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -72,12 +72,15 @@ class Machine: public CPU6502::Processor { int pixelCounterMask; OutputState state; - uint8_t pixels; + uint8_t playfieldPixel; - Event() : updates(0), pixelCounterMask(0), pixels(0) {} + Event() : updates(0), pixelCounterMask(0), playfieldPixel(0) {} } _upcomingEvents[number_of_upcoming_events]; unsigned int _upcomingEventsPointer; + uint8_t _upcomingPixels[number_of_upcoming_events]; + unsigned int _upcomingPixelsPointer; + uint8_t _playfieldOutput; // player registers @@ -109,7 +112,7 @@ class Machine: public CPU6502::Processor { // object counters uint8_t _objectCounter[5]; - int _pixelCounter[5]; + int _pixelCounter[5], _spriteCounter[2]; // joystick state uint8_t _piaDataDirection[2]; From b01b474e360ddf16e17c425339ca732c51006a4b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 May 2016 07:32:25 -0400 Subject: [PATCH 35/53] I'm back to thinking that resets are deferred but pixel counts are live. --- Machines/Atari2600/Atari2600.cpp | 163 +++++++++++++------------------ Machines/Atari2600/Atari2600.hpp | 25 ++--- 2 files changed, 80 insertions(+), 108 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 1705fd0c9..39dcf4b28 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -23,8 +23,7 @@ Machine::Machine() : _rom(nullptr), _piaDataValue{0xff, 0xff}, _tiaInputValue{0xff, 0xff}, - _upcomingEventsPointer(0), - _upcomingPixelsPointer(0) + _upcomingEventsPointer(0) { memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); @@ -80,11 +79,9 @@ Machine::~Machine() void Machine::update_timers(int mask) { - unsigned int upcomingEventsPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus1 = (_upcomingEventsPointer + 1)%number_of_upcoming_events; - unsigned int upcomingEventsPointerPlus2 = (_upcomingEventsPointer + 2)%number_of_upcoming_events; - - unsigned int upcomingPixelsPointerPlus4 = (_upcomingPixelsPointer + 4)%number_of_upcoming_events; + unsigned int upcomingPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; + unsigned int upcomingPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; + unsigned int upcomingPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; // grab the background now, for display in four clocks if(mask & (1 << 5)) @@ -92,8 +89,8 @@ void Machine::update_timers(int mask) if(!(_horizontalTimer&3)) { unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); - _upcomingEvents[upcomingEventsPointerPlus4].updates |= Event::Action::Playfield; - _upcomingEvents[upcomingEventsPointerPlus4].playfieldPixel = _playfield[(offset >> 2)%40]; + _upcomingEvents[upcomingPointerPlus4].updates |= Event::Action::Playfield; + _upcomingEvents[upcomingPointerPlus4].playfieldPixel = _playfield[(offset >> 2)%40]; } } @@ -101,15 +98,9 @@ void Machine::update_timers(int mask) { // the ball becomes visible whenever it hits zero, regardless of whether its status // is the result of a counter rollover or a programmatic reset - if(!_objectCounter[4]) _pixelCounter[4] = 0; - - if(_pixelCounter[4] < 8 && _ballGraphicsEnable[_ballGraphicsSelector]&2) { - int ballSize = 1 << ((_playfieldControl >> 4)&3); - _upcomingPixels[upcomingPixelsPointerPlus4] |= (_pixelCounter[4] < ballSize) ? (1 << 4) : 0; - } - - _objectCounter[4] = (_objectCounter[4] + 1)%160; - _pixelCounter[4] ++; + if(!_objectCounter[4].count) _upcomingEvents[upcomingPointerPlus4].pixelCounterResetMask &= ~(1 << 4); + _objectCounter[4].count = (_objectCounter[4].count + 1)%160; + _objectCounter[4].pixel ++; } // check for player and missle triggers @@ -119,11 +110,10 @@ void Machine::update_timers(int mask) { // the players and missles become visible only upon overflow to zero, so schedule for // 1/2 clocks ahead from 159 - if(_objectCounter[c] == 159) + if(_objectCounter[c].count == 159) { - unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus2 : upcomingEventsPointerPlus1; - _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; - _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); + unsigned int actionSlot = (c < 2) ? upcomingPointerPlus6 : upcomingPointerPlus5; + _upcomingEvents[actionSlot].pixelCounterResetMask &= ~(1 << c); } else { @@ -131,14 +121,13 @@ void Machine::update_timers(int mask) // in which case the counter reset (and hence the start of drawing) will occur in 4/5 cycles uint8_t repeatMask = _playerAndMissileSize[c&1] & 7; if( - ( _objectCounter[c] == 16 && ((repeatMask == 1) || (repeatMask == 3)) ) || - ( _objectCounter[c] == 32 && ((repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6)) ) || - ( _objectCounter[c] == 64 && ((repeatMask == 4) || (repeatMask == 6)) ) + ( _objectCounter[c].count == 16 && ((repeatMask == 1) || (repeatMask == 3)) ) || + ( _objectCounter[c].count == 32 && ((repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6)) ) || + ( _objectCounter[c].count == 64 && ((repeatMask == 4) || (repeatMask == 6)) ) ) { - unsigned int actionSlot = (c < 2) ? upcomingEventsPointerPlus1 : _upcomingEventsPointer; - _upcomingEvents[actionSlot].updates |= Event::Action::ResetPixelCounter; - _upcomingEvents[actionSlot].pixelCounterMask |= (1 << c); + unsigned int actionSlot = (c < 2) ? upcomingPointerPlus5 : upcomingPointerPlus4; + _upcomingEvents[actionSlot].pixelCounterResetMask &= ~(1 << c); } } } @@ -149,54 +138,23 @@ void Machine::update_timers(int mask) { if(mask & (1 << c)) { - int lastSpriteCounter = _spriteCounter[c]; - _spriteCounter[c]++; + int last_broad_pixel = _objectCounter[c].broad_pixel; + _objectCounter[c].broad_pixel++; uint8_t repeatMask = _playerAndMissileSize[c] & 7; switch(repeatMask) { - default: _pixelCounter[c] ++; break; - case 5: _pixelCounter[c] += ((lastSpriteCounter ^ _spriteCounter[c]) >> 1)&1; break; - case 7: _pixelCounter[c] += ((lastSpriteCounter ^ _spriteCounter[c]) >> 2)&1; break; + default: _objectCounter[c].pixel ++; break; + case 5: _objectCounter[c].pixel += ((last_broad_pixel ^ _objectCounter[c].broad_pixel) >> 1)&1; break; + case 7: _objectCounter[c].pixel += ((last_broad_pixel ^ _objectCounter[c].broad_pixel) >> 2)&1; break; } - _objectCounter[c] = (_objectCounter[c] + 1)%160; + _objectCounter[c].count = (_objectCounter[c].count + 1)%160; } if(mask & (1 << (c + 2))) { - _objectCounter[c+2] = (_objectCounter[c+2] + 1)%160; - _pixelCounter[c+2] ++; - } - } - - // apply any resets - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ResetPixelCounter) - { - for(int c = 0; c < 5; c++) - { - if(_upcomingEvents[_upcomingEventsPointer].pixelCounterMask & (1 << c)) - _pixelCounter[c] = 0; - } - _upcomingEvents[_upcomingEventsPointer].pixelCounterMask = 0; - } - - // reload the playfield pixel if appropriate - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) - { - _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldPixel; - } - - // determine the pixel masks - for(int c = 0; c < 2; c++) - { - if(_playerGraphics[c] && _pixelCounter[c] < 32) { - int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; - _upcomingPixels[upcomingPixelsPointerPlus4] |= ((_playerGraphics[_playerGraphicsSelector[c]][c] >> (_pixelCounter[c] ^ flipMask)) & 1) << c; - } - - if(_pixelCounter[c+2] < 8 && (_missileGraphicsEnable[c]&2) && !_missileGraphicsReset[c]) { - int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); - _upcomingPixels[upcomingPixelsPointerPlus4] |= ((_pixelCounter[c+2] < missileSize) ? 1 : 0) << (c + 2); + _objectCounter[c+2].count = (_objectCounter[c+2].count + 1)%160; + _objectCounter[c+2].pixel ++; } } } @@ -208,20 +166,27 @@ uint8_t Machine::get_output_pixel() // get the playfield pixel and hence a proposed colour uint8_t playfieldColour = ((_playfieldControl&6) == 2) ? _playerColour[offset / 80] : _playfieldColour; - // get the ball proposed state - uint8_t ballPixel = (_upcomingPixels[_upcomingPixelsPointer] >> 4) & 1; + uint8_t ballPixel = 0; + if(_objectCounter[4].pixel < 8 && _ballGraphicsEnable[_ballGraphicsSelector]&2) { + int ballSize = 1 << ((_playfieldControl >> 4)&3); + ballPixel = (_objectCounter[4].pixel < ballSize) ? 1 : 0; + } - // deal with the sprites - uint8_t playerPixels[2] = + // determine the pixel masks + uint8_t playerPixels[2] = { 0, 0 }; + uint8_t missilePixels[2] = { 0, 0 }; + for(int c = 0; c < 2; c++) { - static_cast((_upcomingPixels[_upcomingPixelsPointer] >> 0) & 1), - static_cast((_upcomingPixels[_upcomingPixelsPointer] >> 1) & 1) - }; - uint8_t missilePixels[2] = - { - static_cast((_upcomingPixels[_upcomingPixelsPointer] >> 2) & 1), - static_cast((_upcomingPixels[_upcomingPixelsPointer] >> 3) & 1), - }; + if(_playerGraphics[c] && _objectCounter[c].pixel < 8) { + int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; + playerPixels[c] = (_playerGraphics[_playerGraphicsSelector[c]][c] >> (_objectCounter[c].pixel ^ flipMask)) & 1; + } + + if(_objectCounter[c+2].pixel < 8 && (_missileGraphicsEnable[c]&2) && !_missileGraphicsReset[c]) { + int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); + missilePixels[c] = (_objectCounter[c+2].pixel < missileSize) ? 1 : 0; + } + } // accumulate collisions if(playerPixels[0] | playerPixels[1]) { @@ -253,10 +218,6 @@ uint8_t Machine::get_output_pixel() if(playerPixels[0] || missilePixels[0]) outputColour = _playerColour[0]; } - // update pixel chain - _upcomingPixels[_upcomingPixelsPointer] = 0; - _upcomingPixelsPointer = (_upcomingPixelsPointer + 1)%number_of_upcoming_events; - // return colour return outputColour; } @@ -319,7 +280,7 @@ void Machine::output_pixels(unsigned int count) { for(int c = 0; c < 5; c++) { - if(((_objectMotion[c] >> 4)^_hMoveCounter) == 7) + if(((_objectCounter[c].motion >> 4)^_hMoveCounter) == 7) { _hMoveFlags &= ~(1 << c); } @@ -337,6 +298,21 @@ void Machine::output_pixels(unsigned int count) update_timers(_hMoveFlags); } + // apply any resets + _objectCounter[0].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 0) & 1; + _objectCounter[0].broad_pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 0) & 1; + _objectCounter[1].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 1) & 1; + _objectCounter[1].broad_pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 1) & 1; + _objectCounter[2].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 2) & 1; + _objectCounter[3].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 3) & 1; + _objectCounter[4].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 4) & 1; + + // reload the playfield pixel if appropriate + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) + { + _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldPixel; + } + // read that state state = _upcomingEvents[_upcomingEventsPointer].state; OutputState actingState = state; @@ -378,6 +354,7 @@ void Machine::output_pixels(unsigned int count) // advance _upcomingEvents[_upcomingEventsPointer].updates = 0; + _upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask = ~0; _upcomingEventsPointer = (_upcomingEventsPointer + 1)%number_of_upcoming_events; // advance horizontal timer, perform reset actions if requested @@ -566,7 +543,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: - _objectCounter[decodedAddress - 0x10] = 0; + _objectCounter[decodedAddress - 0x10].count = 0; break; case 0x1c: @@ -587,7 +564,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x22: case 0x23: case 0x24: - _objectMotion[decodedAddress - 0x20] = *value; + _objectCounter[decodedAddress - 0x20].motion = *value; break; case 0x25: _playerGraphicsSelector[0] = (*value)&1; break; @@ -600,7 +577,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin int index = decodedAddress - 0x28; if(!(*value&0x02) && _missileGraphicsReset[index]) { - _objectCounter[index + 2] = _objectCounter[index]; + _objectCounter[index + 2].count = _objectCounter[index].count; uint8_t repeatMask = _playerAndMissileSize[index] & 7; int extra_offset; @@ -611,7 +588,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 7: extra_offset = 10; break; } - _objectCounter[index + 2] = (_objectCounter[index + 2] + extra_offset)%160; + _objectCounter[index + 2].count = (_objectCounter[index + 2].count + extra_offset)%160; } _missileGraphicsReset[index] = (*value) & 0x02; } @@ -624,11 +601,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _upcomingEvents[(_upcomingEventsPointer + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup; break; case 0x2b: - _objectMotion[0] = - _objectMotion[1] = - _objectMotion[2] = - _objectMotion[3] = - _objectMotion[4] = 0; + _objectCounter[0].motion = + _objectCounter[1].motion = + _objectCounter[2].motion = + _objectCounter[3].motion = + _objectCounter[4].motion = 0; break; case 0x2c: _collisions[0] = _collisions[1] = _collisions[2] = diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index a38c6e5d2..e027bfcb0 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -16,7 +16,7 @@ namespace Atari2600 { -const unsigned int number_of_upcoming_events = 6; +const unsigned int number_of_upcoming_events = 7; class Machine: public CPU6502::Processor { @@ -61,26 +61,20 @@ class Machine: public CPU6502::Processor { struct Event { enum Action { Playfield = 1 << 0, - - ResetPixelCounter = 1 << 3, - - HMoveSetup = 1 << 4, - HMoveCompare = 1 << 5, - HMoveDecrement = 1 << 6, + HMoveSetup = 1 << 1, + HMoveCompare = 1 << 2, + HMoveDecrement = 1 << 3, }; int updates; - int pixelCounterMask; OutputState state; + int pixelCounterResetMask; uint8_t playfieldPixel; - Event() : updates(0), pixelCounterMask(0), playfieldPixel(0) {} + Event() : updates(0), pixelCounterResetMask(~0), playfieldPixel(0) {} } _upcomingEvents[number_of_upcoming_events]; unsigned int _upcomingEventsPointer; - uint8_t _upcomingPixels[number_of_upcoming_events]; - unsigned int _upcomingPixelsPointer; - uint8_t _playfieldOutput; // player registers @@ -108,11 +102,12 @@ class Machine: public CPU6502::Processor { // horizontal motion control uint8_t _hMoveCounter; uint8_t _hMoveFlags; - uint8_t _objectMotion[5]; // object counters - uint8_t _objectCounter[5]; - int _pixelCounter[5], _spriteCounter[2]; + struct { + int count, pixel, broad_pixel; + uint8_t motion; + } _objectCounter[5]; // joystick state uint8_t _piaDataDirection[2]; From 40c2c0bd2db0673eebd859105bcec340902dd00f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 May 2016 21:12:25 -0400 Subject: [PATCH 36/53] Minor simplifications and improvements. Still trying to figure out what's causing the non-linear edge addressing. --- Machines/Atari2600/Atari2600.cpp | 41 +++++++++++++++++--------------- Machines/Atari2600/Atari2600.hpp | 16 +++++++++---- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 39dcf4b28..710a907da 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -83,15 +83,12 @@ void Machine::update_timers(int mask) unsigned int upcomingPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; unsigned int upcomingPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; - // grab the background now, for display in four clocks - if(mask & (1 << 5)) + // grab the background now, for application in four clocks + if(mask & (1 << 5) && !(_horizontalTimer&3)) { - if(!(_horizontalTimer&3)) - { - unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); - _upcomingEvents[upcomingPointerPlus4].updates |= Event::Action::Playfield; - _upcomingEvents[upcomingPointerPlus4].playfieldPixel = _playfield[(offset >> 2)%40]; - } + unsigned int offset = 4 + _horizontalTimer - (horizontalTimerPeriod - 160); + _upcomingEvents[upcomingPointerPlus4].updates |= Event::Action::Playfield; + _upcomingEvents[upcomingPointerPlus4].playfieldPixel = _playfield[(offset >> 2)%40]; } if(mask & (1 << 4)) @@ -99,8 +96,6 @@ void Machine::update_timers(int mask) // the ball becomes visible whenever it hits zero, regardless of whether its status // is the result of a counter rollover or a programmatic reset if(!_objectCounter[4].count) _upcomingEvents[upcomingPointerPlus4].pixelCounterResetMask &= ~(1 << 4); - _objectCounter[4].count = (_objectCounter[4].count + 1)%160; - _objectCounter[4].pixel ++; } // check for player and missle triggers @@ -136,25 +131,27 @@ void Machine::update_timers(int mask) // update the pixel counters for(int c = 0; c < 2; c++) { - if(mask & (1 << c)) + if(mask&(1 << c)) { - int last_broad_pixel = _objectCounter[c].broad_pixel; _objectCounter[c].broad_pixel++; uint8_t repeatMask = _playerAndMissileSize[c] & 7; switch(repeatMask) { default: _objectCounter[c].pixel ++; break; - case 5: _objectCounter[c].pixel += ((last_broad_pixel ^ _objectCounter[c].broad_pixel) >> 1)&1; break; - case 7: _objectCounter[c].pixel += ((last_broad_pixel ^ _objectCounter[c].broad_pixel) >> 2)&1; break; + case 5: _objectCounter[c].pixel += _objectCounter[c].broad_pixel&1; break; + case 7: _objectCounter[c].pixel += ((_objectCounter[c].broad_pixel | (_objectCounter[c].broad_pixel >> 1))^1)&1; break; } _objectCounter[c].count = (_objectCounter[c].count + 1)%160; } + } - if(mask & (1 << (c + 2))) + for(int c = 2; c < 5; c++) + { + if(mask&(1 << c)) { - _objectCounter[c+2].count = (_objectCounter[c+2].count + 1)%160; - _objectCounter[c+2].pixel ++; + _objectCounter[c].count = (_objectCounter[c].count + 1)%160; + _objectCounter[c].pixel ++; } } } @@ -244,9 +241,13 @@ void Machine::output_pixels(unsigned int count) case 16: case 17: state = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; default: state = OutputState::Pixel; break; } + // update pixel timers if(state == OutputState::Pixel) + { update_timers(~0); + _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].updates |= Event::Action::ClockPixels; + } // if vsync is enabled, output the opposite of the automatic hsync output if(_vSyncEnabled) { @@ -298,6 +299,10 @@ void Machine::output_pixels(unsigned int count) update_timers(_hMoveFlags); } + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ClockPixels) + { + } + // apply any resets _objectCounter[0].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 0) & 1; _objectCounter[0].broad_pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 0) & 1; @@ -387,8 +392,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin output_pixels(additional_pixels + cycles_run_for * 3); -// printf("/%d/", _horizontalTimer); - if(operation != CPU6502::BusOperation::Ready) { // check for a paging access diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index e027bfcb0..35e70817e 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -61,9 +61,11 @@ class Machine: public CPU6502::Processor { struct Event { enum Action { Playfield = 1 << 0, - HMoveSetup = 1 << 1, - HMoveCompare = 1 << 2, - HMoveDecrement = 1 << 3, + ClockPixels = 1 << 1, + + HMoveSetup = 1 << 2, + HMoveCompare = 1 << 3, + HMoveDecrement = 1 << 4, }; int updates; @@ -71,6 +73,8 @@ class Machine: public CPU6502::Processor { int pixelCounterResetMask; uint8_t playfieldPixel; + int pixelCounters[5]; + Event() : updates(0), pixelCounterResetMask(~0), playfieldPixel(0) {} } _upcomingEvents[number_of_upcoming_events]; unsigned int _upcomingEventsPointer; @@ -105,8 +109,10 @@ class Machine: public CPU6502::Processor { // object counters struct { - int count, pixel, broad_pixel; - uint8_t motion; + 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 + uint8_t motion; // the value stored to this counter's motion register } _objectCounter[5]; // joystick state From 106ddae907dfbffd9ea2752ac3f4e39bca4c9456 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 25 May 2016 21:43:19 -0400 Subject: [PATCH 37/53] Sprites are still a pixel off but better; made some attempt to move things outside of the loop. --- Machines/Atari2600/Atari2600.cpp | 121 ++++++++++++++++++++----------- Machines/Atari2600/Atari2600.hpp | 14 +++- 2 files changed, 88 insertions(+), 47 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 710a907da..2f5a9a208 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -27,6 +27,7 @@ Machine::Machine() : { memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); + setup_reported_collisions(); } void Machine::setup_output(float aspect_ratio) @@ -79,9 +80,9 @@ Machine::~Machine() void Machine::update_timers(int mask) { + unsigned int upcomingPointerPlus1 = (_upcomingEventsPointer + 1)%number_of_upcoming_events; + unsigned int upcomingPointerPlus2 = (_upcomingEventsPointer + 2)%number_of_upcoming_events; unsigned int upcomingPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; - unsigned int upcomingPointerPlus5 = (_upcomingEventsPointer + 5)%number_of_upcoming_events; - unsigned int upcomingPointerPlus6 = (_upcomingEventsPointer + 6)%number_of_upcoming_events; // grab the background now, for application in four clocks if(mask & (1 << 5) && !(_horizontalTimer&3)) @@ -95,7 +96,7 @@ void Machine::update_timers(int mask) { // the ball becomes visible whenever it hits zero, regardless of whether its status // is the result of a counter rollover or a programmatic reset - if(!_objectCounter[4].count) _upcomingEvents[upcomingPointerPlus4].pixelCounterResetMask &= ~(1 << 4); + if(!_objectCounter[4].count) _objectCounter[4].pixel = 0; } // check for player and missle triggers @@ -107,7 +108,7 @@ void Machine::update_timers(int mask) // 1/2 clocks ahead from 159 if(_objectCounter[c].count == 159) { - unsigned int actionSlot = (c < 2) ? upcomingPointerPlus6 : upcomingPointerPlus5; + unsigned int actionSlot = (c < 2) ? upcomingPointerPlus2 : upcomingPointerPlus1; _upcomingEvents[actionSlot].pixelCounterResetMask &= ~(1 << c); } else @@ -121,8 +122,14 @@ void Machine::update_timers(int mask) ( _objectCounter[c].count == 64 && ((repeatMask == 4) || (repeatMask == 6)) ) ) { - unsigned int actionSlot = (c < 2) ? upcomingPointerPlus5 : upcomingPointerPlus4; - _upcomingEvents[actionSlot].pixelCounterResetMask &= ~(1 << c); + if(c < 2) + { + _upcomingEvents[upcomingPointerPlus1].pixelCounterResetMask &= ~(1 << c); + } + else + { + _objectCounter[c].pixel = 0; + } } } } @@ -131,6 +138,7 @@ void Machine::update_timers(int mask) // update the pixel counters for(int c = 0; c < 2; c++) { + _upcomingEvents[upcomingPointerPlus4].pixelCounters[c] = _objectCounter[c].pixel; if(mask&(1 << c)) { _objectCounter[c].broad_pixel++; @@ -148,6 +156,7 @@ void Machine::update_timers(int mask) for(int c = 2; c < 5; c++) { + _upcomingEvents[upcomingPointerPlus4].pixelCounters[c] = _objectCounter[c].pixel; if(mask&(1 << c)) { _objectCounter[c].count = (_objectCounter[c].count + 1)%160; @@ -164,9 +173,8 @@ uint8_t Machine::get_output_pixel() uint8_t playfieldColour = ((_playfieldControl&6) == 2) ? _playerColour[offset / 80] : _playfieldColour; uint8_t ballPixel = 0; - if(_objectCounter[4].pixel < 8 && _ballGraphicsEnable[_ballGraphicsSelector]&2) { - int ballSize = 1 << ((_playfieldControl >> 4)&3); - ballPixel = (_objectCounter[4].pixel < ballSize) ? 1 : 0; + if(_upcomingEvents[_upcomingEventsPointer].pixelCounters[4] < _ballSize) { + ballPixel = _ballGraphicsEnable[_ballGraphicsSelector]; } // determine the pixel masks @@ -174,37 +182,25 @@ uint8_t Machine::get_output_pixel() uint8_t missilePixels[2] = { 0, 0 }; for(int c = 0; c < 2; c++) { - if(_playerGraphics[c] && _objectCounter[c].pixel < 8) { - int flipMask = (_playerReflection[c]&0x8) ? 0 : 7; - playerPixels[c] = (_playerGraphics[_playerGraphicsSelector[c]][c] >> (_objectCounter[c].pixel ^ flipMask)) & 1; + if(_playerGraphics[c] && _upcomingEvents[_upcomingEventsPointer].pixelCounters[c] < 8) { + playerPixels[c] = (_playerGraphics[_playerGraphicsSelector[c]][c] >> (_upcomingEvents[_upcomingEventsPointer].pixelCounters[c] ^ _playerReflectionMask[c])) & 1; } - if(_objectCounter[c+2].pixel < 8 && (_missileGraphicsEnable[c]&2) && !_missileGraphicsReset[c]) { - int missileSize = 1 << ((_playerAndMissileSize[c] >> 4)&3); - missilePixels[c] = (_objectCounter[c+2].pixel < missileSize) ? 1 : 0; + if(_upcomingEvents[_upcomingEventsPointer].pixelCounters[c+2] < _missileSize[c] && !_missileGraphicsReset[c]) { + missilePixels[c] = _missileGraphicsEnable[c]; } } // accumulate collisions - if(playerPixels[0] | playerPixels[1]) { - _collisions[0] |= ((missilePixels[0] & playerPixels[1]) << 7) | ((missilePixels[0] & playerPixels[0]) << 6); - _collisions[1] |= ((missilePixels[1] & playerPixels[0]) << 7) | ((missilePixels[1] & playerPixels[1]) << 6); - - _collisions[2] |= ((_playfieldOutput & playerPixels[0]) << 7) | ((ballPixel & playerPixels[0]) << 6); - _collisions[3] |= ((_playfieldOutput & playerPixels[1]) << 7) | ((ballPixel & playerPixels[1]) << 6); - - _collisions[7] |= ((playerPixels[0] & playerPixels[1]) << 7); - } - - if(_playfieldOutput | ballPixel) { - _collisions[4] |= ((_playfieldOutput & missilePixels[0]) << 7) | ((ballPixel & missilePixels[0]) << 6); - _collisions[5] |= ((_playfieldOutput & missilePixels[1]) << 7) | ((ballPixel & missilePixels[1]) << 6); - - _collisions[6] |= ((_playfieldOutput & ballPixel) << 7); - } - - if(missilePixels[0] & missilePixels[1]) - _collisions[7] |= (1 << 6); + int pixel_mask = playerPixels[0] | (playerPixels[1] << 1) | (missilePixels[0] << 2) | (missilePixels[1] << 3) | (ballPixel << 4) | (_playfieldOutput << 5); + _collisions[0] |= _reportedCollisions[pixel_mask][0]; + _collisions[1] |= _reportedCollisions[pixel_mask][1]; + _collisions[2] |= _reportedCollisions[pixel_mask][2]; + _collisions[3] |= _reportedCollisions[pixel_mask][3]; + _collisions[4] |= _reportedCollisions[pixel_mask][4]; + _collisions[5] |= _reportedCollisions[pixel_mask][5]; + _collisions[6] |= _reportedCollisions[pixel_mask][6]; + _collisions[7] |= _reportedCollisions[pixel_mask][7]; // apply appropriate priority to pick a colour uint8_t playfieldPixel = _playfieldOutput | ballPixel; @@ -219,6 +215,40 @@ uint8_t Machine::get_output_pixel() return outputColour; } +void Machine::setup_reported_collisions() +{ + for(int c = 0; c < 32; c++) + { + memset(_reportedCollisions[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 playfieldPixel = (c >> 5)&1; + + if(playerPixels[0] | playerPixels[1]) { + _reportedCollisions[c][0] |= ((missilePixels[0] & playerPixels[1]) << 7) | ((missilePixels[0] & playerPixels[0]) << 6); + _reportedCollisions[c][1] |= ((missilePixels[1] & playerPixels[0]) << 7) | ((missilePixels[1] & playerPixels[1]) << 6); + + _reportedCollisions[c][2] |= ((playfieldPixel & playerPixels[0]) << 7) | ((ballPixel & playerPixels[0]) << 6); + _reportedCollisions[c][3] |= ((playfieldPixel & playerPixels[1]) << 7) | ((ballPixel & playerPixels[1]) << 6); + + _reportedCollisions[c][7] |= ((playerPixels[0] & playerPixels[1]) << 7); + } + + if(_playfieldOutput | ballPixel) { + _reportedCollisions[c][4] |= ((playfieldPixel & missilePixels[0]) << 7) | ((ballPixel & missilePixels[0]) << 6); + _reportedCollisions[c][5] |= ((playfieldPixel & missilePixels[1]) << 7) | ((ballPixel & missilePixels[1]) << 6); + + _reportedCollisions[c][6] |= ((playfieldPixel & ballPixel) << 7); + } + + if(missilePixels[0] & missilePixels[1]) + _reportedCollisions[c][7] |= (1 << 6); + } +} + + // in imputing the knowledge that all we're dealing with is the rollover from 159 to 0, // this is faster than the straightforward +1)%160 per profiling //#define increment_object_counter(c) _objectCounter[c] = (_objectCounter[c]+1)&~((158-_objectCounter[c]) >> 8) @@ -246,7 +276,6 @@ void Machine::output_pixels(unsigned int count) if(state == OutputState::Pixel) { update_timers(~0); - _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].updates |= Event::Action::ClockPixels; } // if vsync is enabled, output the opposite of the automatic hsync output @@ -299,9 +328,9 @@ void Machine::output_pixels(unsigned int count) update_timers(_hMoveFlags); } - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ClockPixels) - { - } +// if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ClockPixels) +// { +// } // apply any resets _objectCounter[0].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 0) & 1; @@ -476,7 +505,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin break; case 0x04: - case 0x05: _playerAndMissileSize[decodedAddress - 0x04] = *value; break; + case 0x05: + _playerAndMissileSize[decodedAddress - 0x04] = *value; + _missileSize[decodedAddress - 0x04] = 1 << ((*value >> 4)&3); + break; case 0x06: case 0x07: _playerColour[decodedAddress - 0x06] = *value; break; @@ -486,6 +518,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x0a: { uint8_t old_playfield_control = _playfieldControl; _playfieldControl = *value; + _ballSize = 1 << ((_playfieldControl >> 4)&3); // did the mirroring bit change? if((_playfieldControl^old_playfield_control)&1) { @@ -497,7 +530,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } } break; case 0x0b: - case 0x0c: _playerReflection[decodedAddress - 0x0b] = *value; break; + case 0x0c: _playerReflectionMask[decodedAddress - 0x0b] = (*value)&8 ? 0 : 7; break; case 0x0d: _playfield[0] = ((*value) >> 4)&1; @@ -556,10 +589,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _playerGraphics[0][index] = *value; _playerGraphics[1][index^1] = _playerGraphics[0][index^1]; } break; - case 0x1d: _missileGraphicsEnable[0] = *value; break; - case 0x1e: _missileGraphicsEnable[1] = *value; break; + case 0x1d: + case 0x1e: _missileGraphicsEnable[decodedAddress - 0x1d] = ((*value) >> 1)&1; break; case 0x1f: - _ballGraphicsEnable[0] = *value; + _ballGraphicsEnable[0] = ((*value) >> 1)&1; break; case 0x20: @@ -593,7 +626,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _objectCounter[index + 2].count = (_objectCounter[index + 2].count + extra_offset)%160; } - _missileGraphicsReset[index] = (*value) & 0x02; + _missileGraphicsReset[index] = !!((*value) & 0x02); } break; diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 35e70817e..4370d1fc4 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -16,7 +16,7 @@ namespace Atari2600 { -const unsigned int number_of_upcoming_events = 7; +const unsigned int number_of_upcoming_events = 6; class Machine: public CPU6502::Processor { @@ -50,6 +50,9 @@ class Machine: public CPU6502::Processor { uint8_t _backgroundColour; uint8_t _playfield[40]; + // ... and derivatives + int _ballSize, _missileSize[2]; + // delayed clock events enum OutputState { Sync, @@ -83,7 +86,7 @@ class Machine: public CPU6502::Processor { // player registers uint8_t _playerColour[2]; - uint8_t _playerReflection[2]; + uint8_t _playerReflectionMask[2]; uint8_t _playerGraphics[2][2]; uint8_t _playerGraphicsSelector[2]; bool _playerStart[2]; @@ -92,7 +95,8 @@ class Machine: public CPU6502::Processor { uint8_t _playerAndMissileSize[2]; // missile registers - uint8_t _missileGraphicsEnable[2], _missileGraphicsReset[2]; + uint8_t _missileGraphicsEnable[2]; + bool _missileGraphicsReset[2]; // ball registers uint8_t _ballGraphicsEnable[2]; @@ -132,6 +136,10 @@ class Machine: public CPU6502::Processor { unsigned int _lastOutputStateDuration; OutputState _lastOutputState; uint8_t *_outputBuffer; + + // lookup table for collision reporting + uint8_t _reportedCollisions[32][8]; + void setup_reported_collisions(); }; } From 91d3453cc145eef1310e2c015cffe87bb3c7e2fc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 27 May 2016 14:33:08 -0400 Subject: [PATCH 38/53] Switched to looking backwards rather than forwards. --- Machines/Atari2600/Atari2600.cpp | 177 +++++++++++++++---------------- Machines/Atari2600/Atari2600.hpp | 40 ++++--- 2 files changed, 107 insertions(+), 110 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 2f5a9a208..952998cd9 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -23,7 +23,8 @@ Machine::Machine() : _rom(nullptr), _piaDataValue{0xff, 0xff}, _tiaInputValue{0xff, 0xff}, - _upcomingEventsPointer(0) + _upcomingEventsPointer(0), + _objectCounterPointer(0) { memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); @@ -80,10 +81,15 @@ Machine::~Machine() void Machine::update_timers(int mask) { - unsigned int upcomingPointerPlus1 = (_upcomingEventsPointer + 1)%number_of_upcoming_events; - unsigned int upcomingPointerPlus2 = (_upcomingEventsPointer + 2)%number_of_upcoming_events; unsigned int upcomingPointerPlus4 = (_upcomingEventsPointer + 4)%number_of_upcoming_events; + _objectCounterPointer = (_objectCounterPointer + 1)%number_of_recorded_counters; + ObjectCounter *oneClockAgo = _objectCounter[(_objectCounterPointer - 1 + number_of_recorded_counters)%number_of_recorded_counters]; + ObjectCounter *fourClocksAgo = _objectCounter[(_objectCounterPointer - 4 + number_of_recorded_counters)%number_of_recorded_counters]; + ObjectCounter *fiveClocksAgo = _objectCounter[(_objectCounterPointer - 5 + number_of_recorded_counters)%number_of_recorded_counters]; + ObjectCounter *sixClocksAgo = _objectCounter[(_objectCounterPointer - 6 + number_of_recorded_counters)%number_of_recorded_counters]; + ObjectCounter *now = _objectCounter[_objectCounterPointer]; + // grab the background now, for application in four clocks if(mask & (1 << 5) && !(_horizontalTimer&3)) { @@ -95,8 +101,15 @@ void Machine::update_timers(int mask) 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 - if(!_objectCounter[4].count) _objectCounter[4].pixel = 0; + // 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(!fourClocksAgo[4].count) now[4].pixel = 0; + } + else + { + now[4] = oneClockAgo[4]; } // check for player and missle triggers @@ -104,89 +117,80 @@ void Machine::update_timers(int mask) { if(mask & (1 << c)) { - // the players and missles become visible only upon overflow to zero, so schedule for - // 1/2 clocks ahead from 159 - if(_objectCounter[c].count == 159) + // update the count + now[c].count = (oneClockAgo[c].count + 1)%160; + + uint8_t repeatMask = _playerAndMissileSize[c&1] & 7; + ObjectCounter *rollover; + ObjectCounter *equality; + + if(c < 2) { - unsigned int actionSlot = (c < 2) ? upcomingPointerPlus2 : upcomingPointerPlus1; - _upcomingEvents[actionSlot].pixelCounterResetMask &= ~(1 << c); + // 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 = sixClocksAgo; + equality = fiveClocksAgo; } else { - // otherwise visibility is determined by an appropriate repeat mask and hitting any of 12, 28 or 60, - // in which case the counter reset (and hence the start of drawing) will occur in 4/5 cycles - uint8_t repeatMask = _playerAndMissileSize[c&1] & 7; - if( - ( _objectCounter[c].count == 16 && ((repeatMask == 1) || (repeatMask == 3)) ) || - ( _objectCounter[c].count == 32 && ((repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6)) ) || - ( _objectCounter[c].count == 64 && ((repeatMask == 4) || (repeatMask == 6)) ) - ) - { - if(c < 2) - { - _upcomingEvents[upcomingPointerPlus1].pixelCounterResetMask &= ~(1 << c); - } - else - { - _objectCounter[c].pixel = 0; - } - } + // update the pixel + now[c].pixel = oneClockAgo[c].pixel + 1; + + // check for a rollover five clocks ago or equality four clocks ago + rollover = fiveClocksAgo; + equality = fourClocksAgo; } - } - } - // update the pixel counters - for(int c = 0; c < 2; c++) - { - _upcomingEvents[upcomingPointerPlus4].pixelCounters[c] = _objectCounter[c].pixel; - if(mask&(1 << c)) - { - _objectCounter[c].broad_pixel++; - - uint8_t repeatMask = _playerAndMissileSize[c] & 7; - switch(repeatMask) + if( + (rollover[c].count == 159) || + (_hasSecondCopy[c&1] && equality[c].count == 16) || + (_hasThirdCopy[c&1] && equality[c].count == 32) || + (_hasFourthCopy[c&1] && equality[c].count == 64) + ) { - default: _objectCounter[c].pixel ++; break; - case 5: _objectCounter[c].pixel += _objectCounter[c].broad_pixel&1; break; - case 7: _objectCounter[c].pixel += ((_objectCounter[c].broad_pixel | (_objectCounter[c].broad_pixel >> 1))^1)&1; break; + now[c].pixel = 0; + now[c].broad_pixel = 0; } - _objectCounter[c].count = (_objectCounter[c].count + 1)%160; } - } - - for(int c = 2; c < 5; c++) - { - _upcomingEvents[upcomingPointerPlus4].pixelCounters[c] = _objectCounter[c].pixel; - if(mask&(1 << c)) + else { - _objectCounter[c].count = (_objectCounter[c].count + 1)%160; - _objectCounter[c].pixel ++; + now[c] = oneClockAgo[c]; } } } uint8_t Machine::get_output_pixel() { - unsigned int offset = _horizontalTimer - (horizontalTimerPeriod - 160); + ObjectCounter *now = _objectCounter[_objectCounterPointer]; - // get the playfield pixel and hence a proposed colour + // get the playfield pixel + unsigned int offset = _horizontalTimer - (horizontalTimerPeriod - 160); uint8_t playfieldColour = ((_playfieldControl&6) == 2) ? _playerColour[offset / 80] : _playfieldColour; + // ball pixel uint8_t ballPixel = 0; - if(_upcomingEvents[_upcomingEventsPointer].pixelCounters[4] < _ballSize) { + if(now[4].pixel < _ballSize) { ballPixel = _ballGraphicsEnable[_ballGraphicsSelector]; } - // determine the pixel masks + // 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(_playerGraphics[c] && _upcomingEvents[_upcomingEventsPointer].pixelCounters[c] < 8) { - playerPixels[c] = (_playerGraphics[_playerGraphicsSelector[c]][c] >> (_upcomingEvents[_upcomingEventsPointer].pixelCounters[c] ^ _playerReflectionMask[c])) & 1; + if(_playerGraphics[c] && now[c].pixel < 8) { + playerPixels[c] = (_playerGraphics[_playerGraphicsSelector[c]][c] >> (now[c].pixel ^ _playerReflectionMask[c])) & 1; } - if(_upcomingEvents[_upcomingEventsPointer].pixelCounters[c+2] < _missileSize[c] && !_missileGraphicsReset[c]) { + if(!_missileGraphicsReset[c] && now[c+2].pixel < _missileSize[c]) { missilePixels[c] = _missileGraphicsEnable[c]; } } @@ -217,7 +221,7 @@ uint8_t Machine::get_output_pixel() void Machine::setup_reported_collisions() { - for(int c = 0; c < 32; c++) + for(int c = 0; c < 64; c++) { memset(_reportedCollisions[c], 0, 8); @@ -248,11 +252,6 @@ void Machine::setup_reported_collisions() } } - -// in imputing the knowledge that all we're dealing with is the rollover from 159 to 0, -// this is faster than the straightforward +1)%160 per profiling -//#define increment_object_counter(c) _objectCounter[c] = (_objectCounter[c]+1)&~((158-_objectCounter[c]) >> 8) - void Machine::output_pixels(unsigned int count) { while(count--) @@ -310,7 +309,7 @@ void Machine::output_pixels(unsigned int count) { for(int c = 0; c < 5; c++) { - if(((_objectCounter[c].motion >> 4)^_hMoveCounter) == 7) + if(((_objectMotion[c] >> 4)^_hMoveCounter) == 7) { _hMoveFlags &= ~(1 << c); } @@ -328,19 +327,6 @@ void Machine::output_pixels(unsigned int count) update_timers(_hMoveFlags); } -// if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ClockPixels) -// { -// } - - // apply any resets - _objectCounter[0].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 0) & 1; - _objectCounter[0].broad_pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 0) & 1; - _objectCounter[1].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 1) & 1; - _objectCounter[1].broad_pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 1) & 1; - _objectCounter[2].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 2) & 1; - _objectCounter[3].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 3) & 1; - _objectCounter[4].pixel *= (_upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask >> 4) & 1; - // reload the playfield pixel if appropriate if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) { @@ -388,7 +374,6 @@ void Machine::output_pixels(unsigned int count) // advance _upcomingEvents[_upcomingEventsPointer].updates = 0; - _upcomingEvents[_upcomingEventsPointer].pixelCounterResetMask = ~0; _upcomingEventsPointer = (_upcomingEventsPointer + 1)%number_of_upcoming_events; // advance horizontal timer, perform reset actions if requested @@ -505,10 +490,16 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin break; case 0x04: - case 0x05: - _playerAndMissileSize[decodedAddress - 0x04] = *value; - _missileSize[decodedAddress - 0x04] = 1 << ((*value >> 4)&3); - break; + case 0x05: { + int entry = decodedAddress - 0x04; + _playerAndMissileSize[entry] = *value; + _missileSize[entry] = 1 << ((*value >> 4)&3); + + uint8_t repeatMask = (*value)&7; + _hasSecondCopy[entry] = (repeatMask == 1) || (repeatMask == 3); + _hasThirdCopy[entry] = (repeatMask == 2) || (repeatMask == 3) || (repeatMask == 6); + _hasFourthCopy[entry] = (repeatMask == 4) || (repeatMask == 6); + } break; case 0x06: case 0x07: _playerColour[decodedAddress - 0x06] = *value; break; @@ -579,7 +570,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: - _objectCounter[decodedAddress - 0x10].count = 0; + _objectCounter[_objectCounterPointer][decodedAddress - 0x10].count = 0; break; case 0x1c: @@ -600,7 +591,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x22: case 0x23: case 0x24: - _objectCounter[decodedAddress - 0x20].motion = *value; + _objectMotion[decodedAddress - 0x20] = *value; break; case 0x25: _playerGraphicsSelector[0] = (*value)&1; break; @@ -613,7 +604,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin int index = decodedAddress - 0x28; if(!(*value&0x02) && _missileGraphicsReset[index]) { - _objectCounter[index + 2].count = _objectCounter[index].count; + _objectCounter[_objectCounterPointer][index + 2].count = _objectCounter[_objectCounterPointer][index].count; uint8_t repeatMask = _playerAndMissileSize[index] & 7; int extra_offset; @@ -624,7 +615,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 7: extra_offset = 10; break; } - _objectCounter[index + 2].count = (_objectCounter[index + 2].count + extra_offset)%160; + _objectCounter[_objectCounterPointer][index + 2].count = (_objectCounter[_objectCounterPointer][index + 2].count + extra_offset)%160; } _missileGraphicsReset[index] = !!((*value) & 0x02); } @@ -637,11 +628,11 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _upcomingEvents[(_upcomingEventsPointer + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup; break; case 0x2b: - _objectCounter[0].motion = - _objectCounter[1].motion = - _objectCounter[2].motion = - _objectCounter[3].motion = - _objectCounter[4].motion = 0; + _objectMotion[0] = + _objectMotion[1] = + _objectMotion[2] = + _objectMotion[3] = + _objectMotion[4] = 0; break; case 0x2c: _collisions[0] = _collisions[1] = _collisions[2] = diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 4370d1fc4..bd0a34bfd 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -17,6 +17,7 @@ namespace Atari2600 { const unsigned int number_of_upcoming_events = 6; +const unsigned int number_of_recorded_counters = 7; class Machine: public CPU6502::Processor { @@ -64,24 +65,31 @@ class Machine: public CPU6502::Processor { struct Event { enum Action { Playfield = 1 << 0, - ClockPixels = 1 << 1, - HMoveSetup = 1 << 2, - HMoveCompare = 1 << 3, - HMoveDecrement = 1 << 4, + HMoveSetup = 1 << 1, + HMoveCompare = 1 << 2, + HMoveDecrement = 1 << 3, }; int updates; OutputState state; - int pixelCounterResetMask; uint8_t playfieldPixel; - int pixelCounters[5]; - - Event() : updates(0), pixelCounterResetMask(~0), playfieldPixel(0) {} + Event() : updates(0), playfieldPixel(0) {} } _upcomingEvents[number_of_upcoming_events]; unsigned int _upcomingEventsPointer; + // 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) {} + } _objectCounter[number_of_recorded_counters][5]; + unsigned int _objectCounterPointer; + + // the latched playfield output uint8_t _playfieldOutput; // player registers @@ -91,6 +99,12 @@ class Machine: public CPU6502::Processor { uint8_t _playerGraphicsSelector[2]; bool _playerStart[2]; + // object flags + bool _hasSecondCopy[2]; + bool _hasThirdCopy[2]; + bool _hasFourthCopy[2]; + uint8_t _objectMotion[5]; // the value stored to this counter's motion register + // player + missile registers uint8_t _playerAndMissileSize[2]; @@ -111,14 +125,6 @@ class Machine: public CPU6502::Processor { uint8_t _hMoveCounter; uint8_t _hMoveFlags; - // object counters - struct { - 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 - uint8_t motion; // the value stored to this counter's motion register - } _objectCounter[5]; - // joystick state uint8_t _piaDataDirection[2]; uint8_t _piaDataValue[2]; @@ -138,7 +144,7 @@ class Machine: public CPU6502::Processor { uint8_t *_outputBuffer; // lookup table for collision reporting - uint8_t _reportedCollisions[32][8]; + uint8_t _reportedCollisions[64][8]; void setup_reported_collisions(); }; From 8b342f77a9a3951c8c28668514e96c1fcdd8d0d2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 27 May 2016 21:51:27 -0400 Subject: [PATCH 39/53] Made an attempt further to rationalise timing. --- Machines/Atari2600/Atari2600.cpp | 144 ++++++++++++++++--------------- Machines/Atari2600/Atari2600.hpp | 7 +- 2 files changed, 79 insertions(+), 72 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 952998cd9..c5e1acd9e 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -24,11 +24,35 @@ Machine::Machine() : _piaDataValue{0xff, 0xff}, _tiaInputValue{0xff, 0xff}, _upcomingEventsPointer(0), - _objectCounterPointer(0) + _objectCounterPointer(0), + _stateByTime(_stateByExtendTime[0]) { memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); 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; + } + + _stateByExtendTime[vbextend][c] = state; + } + } } void Machine::setup_output(float aspect_ratio) @@ -85,9 +109,11 @@ void Machine::update_timers(int mask) _objectCounterPointer = (_objectCounterPointer + 1)%number_of_recorded_counters; ObjectCounter *oneClockAgo = _objectCounter[(_objectCounterPointer - 1 + number_of_recorded_counters)%number_of_recorded_counters]; - ObjectCounter *fourClocksAgo = _objectCounter[(_objectCounterPointer - 4 + number_of_recorded_counters)%number_of_recorded_counters]; - ObjectCounter *fiveClocksAgo = _objectCounter[(_objectCounterPointer - 5 + number_of_recorded_counters)%number_of_recorded_counters]; - ObjectCounter *sixClocksAgo = _objectCounter[(_objectCounterPointer - 6 + number_of_recorded_counters)%number_of_recorded_counters]; + ObjectCounter *twoClocksAgo = _objectCounter[(_objectCounterPointer - 2 + number_of_recorded_counters)%number_of_recorded_counters]; +// ObjectCounter *threeClocksAgo = _objectCounter[(_objectCounterPointer - 3 + number_of_recorded_counters)%number_of_recorded_counters]; +// ObjectCounter *fourClocksAgo = _objectCounter[(_objectCounterPointer - 4 + number_of_recorded_counters)%number_of_recorded_counters]; +// ObjectCounter *fiveClocksAgo = _objectCounter[(_objectCounterPointer - 5 + number_of_recorded_counters)%number_of_recorded_counters]; +// ObjectCounter *sixClocksAgo = _objectCounter[(_objectCounterPointer - 6 + number_of_recorded_counters)%number_of_recorded_counters]; ObjectCounter *now = _objectCounter[_objectCounterPointer]; // grab the background now, for application in four clocks @@ -105,7 +131,7 @@ void Machine::update_timers(int mask) // clock delay on that triggering the start signal now[4].count = (oneClockAgo[4].count + 1)%160; now[4].pixel = oneClockAgo[4].pixel + 1; - if(!fourClocksAgo[4].count) now[4].pixel = 0; + if(!now[4].count) now[4].pixel = 0; } else { @@ -136,8 +162,8 @@ void Machine::update_timers(int mask) } // check for a rollover six clocks ago or equality five clocks ago - rollover = sixClocksAgo; - equality = fiveClocksAgo; + rollover = twoClocksAgo; + equality = oneClockAgo; } else { @@ -145,8 +171,8 @@ void Machine::update_timers(int mask) now[c].pixel = oneClockAgo[c].pixel + 1; // check for a rollover five clocks ago or equality four clocks ago - rollover = fiveClocksAgo; - equality = fourClocksAgo; + rollover = oneClockAgo; + equality = now; } if( @@ -256,40 +282,10 @@ void Machine::output_pixels(unsigned int count) { while(count--) { - OutputState state; - - // determine which output state will be active in four cycles from now - switch(_horizontalTimer >> 2) - { - case 56: case 0: case 1: case 2: state = OutputState::Blank; break; - case 3: case 4: case 5: case 6: state = OutputState::Sync; break; - case 7: case 8: case 9: case 10: state = OutputState::ColourBurst; break; - case 11: case 12: case 13: - case 14: case 15: state = OutputState::Blank; break; - - case 16: case 17: state = _vBlankExtend ? OutputState::Blank : OutputState::Pixel; break; - default: state = OutputState::Pixel; break; - } - - // update pixel timers - if(state == OutputState::Pixel) - { - update_timers(~0); - } - - // if vsync is enabled, output the opposite of the automatic hsync output - if(_vSyncEnabled) { - state = (state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; - } - - // write that state as the one that will become effective in four clocks - _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].state = state; - // apply any queued changes and flush the record if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveSetup) { - _upcomingEvents[_upcomingEventsPointer].updates |= Event::Action::HMoveCompare; - _vBlankExtend = true; + _stateByTime = _stateByExtendTime[1]; // clear any ongoing moves if(_hMoveFlags) @@ -303,6 +299,9 @@ void Machine::output_pixels(unsigned int count) // schedule new moves _hMoveFlags = 0x1f; _hMoveCounter = 15; + + // follow-through into a compare immediately + _upcomingEvents[_upcomingEventsPointer].updates |= Event::Action::HMoveCompare; } if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveCompare) @@ -326,25 +325,36 @@ void Machine::output_pixels(unsigned int count) { update_timers(_hMoveFlags); } + _upcomingEvents[_upcomingEventsPointer].updates = 0; + _upcomingEventsPointer = (_upcomingEventsPointer + 1)%number_of_upcoming_events; - // reload the playfield pixel if appropriate - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::Playfield) + // determine which output state is currently active + OutputState primary_state = _stateByTime[_horizontalTimer >> 2]; + OutputState effective_state = primary_state; + + // update pixel timers + if(primary_state == OutputState::Pixel) update_timers(~0); + + // update the background chain + if(_horizontalTimer >= 64 && _horizontalTimer <= 160+64 && !(_horizontalTimer&3)) { - _playfieldOutput = _upcomingEvents[_upcomingEventsPointer].playfieldPixel; + _playfieldOutput = _nextPlayfieldOutput; + _nextPlayfieldOutput = _playfield[(_horizontalTimer - 64) >> 2]; } - // read that state - state = _upcomingEvents[_upcomingEventsPointer].state; - OutputState actingState = state; + // if vsync is enabled, output the opposite of the automatic hsync output + if(_vSyncEnabled) { + effective_state = (effective_state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; + } // honour the vertical blank flag - if(_vBlankEnabled && state == OutputState::Pixel) { - actingState = OutputState::Blank; + if(_vBlankEnabled && effective_state == OutputState::Pixel) { + effective_state = OutputState::Blank; } // decide what that means needs to be communicated to the CRT _lastOutputStateDuration++; - if(actingState != _lastOutputState) { + if(effective_state != _lastOutputState) { switch(_lastOutputState) { case OutputState::Blank: _crt->output_blank(_lastOutputStateDuration); break; case OutputState::Sync: _crt->output_sync(_lastOutputStateDuration); break; @@ -352,9 +362,9 @@ void Machine::output_pixels(unsigned int count) case OutputState::Pixel: _crt->output_data(_lastOutputStateDuration, 1); break; } _lastOutputStateDuration = 0; - _lastOutputState = actingState; + _lastOutputState = effective_state; - if(actingState == OutputState::Pixel) { + if(effective_state == OutputState::Pixel) { _outputBuffer = _crt->allocate_write_area(160); } else { _outputBuffer = nullptr; @@ -362,7 +372,7 @@ void Machine::output_pixels(unsigned int count) } // decide on a pixel colour if that's what's happening - if(state == OutputState::Pixel) + if(effective_state == OutputState::Pixel) { uint8_t colour = get_output_pixel(); if(_outputBuffer) @@ -372,16 +382,13 @@ void Machine::output_pixels(unsigned int count) } } - // advance - _upcomingEvents[_upcomingEventsPointer].updates = 0; - _upcomingEventsPointer = (_upcomingEventsPointer + 1)%number_of_upcoming_events; - - // advance horizontal timer, perform reset actions if requested + // advance horizontal timer, perform reset actions if desired _horizontalTimer = (_horizontalTimer + 1) % horizontalTimerPeriod; if(!_horizontalTimer) { - _vBlankExtend = false; + _stateByTime = _stateByExtendTime[0]; set_ready_line(false); +// printf("\n"); } } } @@ -391,8 +398,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin set_reset_line(false); uint8_t returnValue = 0xff; - unsigned int cycles_run_for = 1; - unsigned int additional_pixels = 0; + unsigned int cycles_run_for = 3; // this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for // leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take @@ -400,11 +406,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin // skips to the end of the line. if(operation == CPU6502::BusOperation::Ready) { unsigned int distance_to_end_of_ready = horizontalTimerPeriod - _horizontalTimer; - cycles_run_for = distance_to_end_of_ready / 3; - additional_pixels = distance_to_end_of_ready % 3; + cycles_run_for = distance_to_end_of_ready; } - output_pixels(additional_pixels + cycles_run_for * 3); + output_pixels(2); if(operation != CPU6502::BusOperation::Ready) { @@ -482,7 +487,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x02: // printf("%d\n", _horizontalTimer); // printf("W"); - if(_horizontalTimer) set_ready_line(true); +// if(_horizontalTimer) + set_ready_line(true); break; case 0x03: // Reset is delayed by four cycles. @@ -691,17 +697,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } } - if(_piaTimerValue >= cycles_run_for) { - _piaTimerValue -= cycles_run_for; + if(_piaTimerValue >= cycles_run_for / 3) { + _piaTimerValue -= cycles_run_for / 3; } else { - _piaTimerValue += 0xff - cycles_run_for; + _piaTimerValue += 0xff - cycles_run_for / 3; _piaTimerShift = 0; _piaTimerStatus |= 0xc0; } -// output_pixels(additional_pixels + cycles_run_for * 3); + output_pixels(cycles_run_for - 2); - return cycles_run_for; + return cycles_run_for / 3; } void Machine::set_digital_input(Atari2600DigitalInput input, bool state) diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index bd0a34bfd..78a061642 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -49,7 +49,7 @@ class Machine: public CPU6502::Processor { uint8_t _playfieldControl; uint8_t _playfieldColour; uint8_t _backgroundColour; - uint8_t _playfield[40]; + uint8_t _playfield[41]; // ... and derivatives int _ballSize, _missileSize[2]; @@ -90,7 +90,7 @@ class Machine: public CPU6502::Processor { unsigned int _objectCounterPointer; // the latched playfield output - uint8_t _playfieldOutput; + uint8_t _playfieldOutput, _nextPlayfieldOutput; // player registers uint8_t _playerColour[2]; @@ -119,7 +119,6 @@ class Machine: public CPU6502::Processor { // graphics output unsigned int _horizontalTimer; bool _vSyncEnabled, _vBlankEnabled; - bool _vBlankExtend; // horizontal motion control uint8_t _hMoveCounter; @@ -140,6 +139,8 @@ class Machine: public CPU6502::Processor { // latched output state unsigned int _lastOutputStateDuration; + OutputState _stateByExtendTime[2][57]; + OutputState *_stateByTime; OutputState _lastOutputState; uint8_t *_outputBuffer; From 3fc80ea01f2d1287049da876474c63bb8ed24043 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 30 May 2016 19:56:36 -0400 Subject: [PATCH 40/53] Rethought, hopefully perfecting, sprite timing. --- Machines/Atari2600/Atari2600.cpp | 33 +++++++++++++++----------------- Machines/Atari2600/Atari2600.hpp | 8 +++++--- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index c5e1acd9e..176951f5c 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -110,10 +110,6 @@ void Machine::update_timers(int mask) _objectCounterPointer = (_objectCounterPointer + 1)%number_of_recorded_counters; ObjectCounter *oneClockAgo = _objectCounter[(_objectCounterPointer - 1 + number_of_recorded_counters)%number_of_recorded_counters]; ObjectCounter *twoClocksAgo = _objectCounter[(_objectCounterPointer - 2 + number_of_recorded_counters)%number_of_recorded_counters]; -// ObjectCounter *threeClocksAgo = _objectCounter[(_objectCounterPointer - 3 + number_of_recorded_counters)%number_of_recorded_counters]; -// ObjectCounter *fourClocksAgo = _objectCounter[(_objectCounterPointer - 4 + number_of_recorded_counters)%number_of_recorded_counters]; -// ObjectCounter *fiveClocksAgo = _objectCounter[(_objectCounterPointer - 5 + number_of_recorded_counters)%number_of_recorded_counters]; -// ObjectCounter *sixClocksAgo = _objectCounter[(_objectCounterPointer - 6 + number_of_recorded_counters)%number_of_recorded_counters]; ObjectCounter *now = _objectCounter[_objectCounterPointer]; // grab the background now, for application in four clocks @@ -325,6 +321,13 @@ void Machine::output_pixels(unsigned int count) { update_timers(_hMoveFlags); } + + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ResetCounter) + { + _objectCounter[_objectCounterPointer][_upcomingEvents[_upcomingEventsPointer].counter].count = 0; + } + + // zero out current update event, progress to next _upcomingEvents[_upcomingEventsPointer].updates = 0; _upcomingEventsPointer = (_upcomingEventsPointer + 1)%number_of_upcoming_events; @@ -342,13 +345,11 @@ void Machine::output_pixels(unsigned int count) _nextPlayfieldOutput = _playfield[(_horizontalTimer - 64) >> 2]; } - // if vsync is enabled, output the opposite of the automatic hsync output + // if vsync is enabled, output the opposite of the automatic hsync output; + // also honour the vertical blank flag if(_vSyncEnabled) { effective_state = (effective_state = OutputState::Sync) ? OutputState::Blank : OutputState::Sync; - } - - // honour the vertical blank flag - if(_vBlankEnabled && effective_state == OutputState::Pixel) { + } else if(_vBlankEnabled && effective_state == OutputState::Pixel) { effective_state = OutputState::Blank; } @@ -388,7 +389,6 @@ void Machine::output_pixels(unsigned int count) { _stateByTime = _stateByExtendTime[0]; set_ready_line(false); -// printf("\n"); } } } @@ -409,7 +409,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin cycles_run_for = distance_to_end_of_ready; } - output_pixels(2); + output_pixels(cycles_run_for); if(operation != CPU6502::BusOperation::Ready) { @@ -485,10 +485,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x01: _vBlankEnabled = !!(*value & 0x02); break; case 0x02: -// printf("%d\n", _horizontalTimer); -// printf("W"); -// if(_horizontalTimer) - set_ready_line(true); + if(_horizontalTimer) set_ready_line(true); break; case 0x03: // Reset is delayed by four cycles. @@ -576,7 +573,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: - _objectCounter[_objectCounterPointer][decodedAddress - 0x10].count = 0; + _upcomingEvents[(_upcomingEventsPointer + 4)%number_of_upcoming_events].updates |= Event::Action::ResetCounter; + _upcomingEvents[(_upcomingEventsPointer + 4)%number_of_upcoming_events].counter = decodedAddress - 0x10; break; case 0x1c: @@ -607,6 +605,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x28: case 0x29: { + // TODO: this should properly mean setting a flag and propagating later, I think? int index = decodedAddress - 0x28; if(!(*value&0x02) && _missileGraphicsReset[index]) { @@ -705,8 +704,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _piaTimerStatus |= 0xc0; } - output_pixels(cycles_run_for - 2); - return cycles_run_for / 3; } diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 78a061642..a64f32556 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -65,15 +65,17 @@ class Machine: public CPU6502::Processor { struct Event { enum Action { Playfield = 1 << 0, + ResetCounter = 1 << 1, - HMoveSetup = 1 << 1, - HMoveCompare = 1 << 2, - HMoveDecrement = 1 << 3, + HMoveSetup = 1 << 2, + HMoveCompare = 1 << 3, + HMoveDecrement = 1 << 4, }; int updates; OutputState state; uint8_t playfieldPixel; + int counter; Event() : updates(0), playfieldPixel(0) {} } _upcomingEvents[number_of_upcoming_events]; From 5c4f35e13ff5f67acafa42b4432e12bf287a77a6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 May 2016 21:23:44 -0400 Subject: [PATCH 41/53] Finally started on generalising the C++ stuff so as to be able to be able to get a working audio binding on the OS-specific side without further repetition by factoring an appropriate protocol out from the Electron and sketching out the correct speaker class for the Atari. Added a method to ask it what a good output frequency would be. --- Machines/Atari2600/Atari2600.cpp | 19 +++++++-- Machines/Atari2600/Atari2600.hpp | 40 +++++++++++++++---- Machines/CRTMachine.hpp | 30 ++++++++++++++ Machines/Electron/Electron.cpp | 2 +- Machines/Electron/Electron.hpp | 33 ++++++++------- .../Clock Signal.xcodeproj/project.pbxproj | 2 + .../Mac/Clock Signal/Wrappers/CSElectron.mm | 1 - Outputs/Speaker.hpp | 12 ++++++ Processors/6502/CPU6502.hpp | 29 ++++++++------ 9 files changed, 128 insertions(+), 40 deletions(-) create mode 100644 Machines/CRTMachine.hpp diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 176951f5c..7cf96d27e 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -71,6 +71,8 @@ void Machine::setup_output(float aspect_ratio) "return (float(y) / 14.0) * (1.0 - amplitude) + step(1, iPhase) * amplitude * cos(phase + phaseOffset);" "}"); _crt->set_output_device(Outputs::CRT::Television); + + _speaker.set_input_rate(2 * 263 * 60); } void Machine::switch_region() @@ -89,6 +91,8 @@ void Machine::switch_region() "return (float(y) / 14.0) * (1.0 - amplitude) + step(4, (iPhase + 2u) & 15u) * amplitude * cos(phase + phaseOffset);" "}"); _crt->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1); + + _speaker.set_input_rate(2 * 312 * 50); } void Machine::close_output() @@ -445,6 +449,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin returnValue &= _ram[address&0x7f]; } else { _ram[address&0x7f] = *value; +// if((address&0x7f) == (0x9a&0x7f)) +// { +// printf("[9a] <- %02x\n", *value); +// } } } @@ -585,7 +593,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _playerGraphics[1][index^1] = _playerGraphics[0][index^1]; } break; case 0x1d: - case 0x1e: _missileGraphicsEnable[decodedAddress - 0x1d] = ((*value) >> 1)&1; break; + case 0x1e: + _missileGraphicsEnable[decodedAddress - 0x1d] = ((*value) >> 1)&1; +// printf("e:%02x <- %c\n", decodedAddress - 0x1d, ((*value)&1) ? 'E' : '-'); + break; case 0x1f: _ballGraphicsEnable[0] = ((*value) >> 1)&1; break; @@ -623,15 +634,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _objectCounter[_objectCounterPointer][index + 2].count = (_objectCounter[_objectCounterPointer][index + 2].count + extra_offset)%160; } _missileGraphicsReset[index] = !!((*value) & 0x02); +// printf("r:%02x <- %c\n", decodedAddress - 0x28, ((*value)&2) ? 'R' : '-'); } break; - case 0x2a: + 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 = ((_horizontalTimer + 3)&3) + 4; _upcomingEvents[(_upcomingEventsPointer + 5)%number_of_upcoming_events].updates |= Event::Action::HMoveSetup; - break; + } break; case 0x2b: _objectMotion[0] = _objectMotion[1] = diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index a64f32556..1596692c4 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -10,31 +10,52 @@ #define Atari2600_cpp #include "../../Processors/6502/CPU6502.hpp" -#include "../../Outputs/CRT/CRT.hpp" +#include "../CRTMachine.hpp" #include #include "Atari2600Inputs.h" namespace Atari2600 { -const unsigned int number_of_upcoming_events = 6; +const unsigned int number_of_upcoming_events = 16; const unsigned int number_of_recorded_counters = 7; -class Machine: public CPU6502::Processor { +class Speaker: public ::Outputs::Filter { + public: + void set_volume(int channel, uint8_t volume); + void set_divider(int channel, uint8_t divider); + void set_control(int channel, uint8_t control); + + void get_samples(unsigned int number_of_samples, int16_t *target); + void skip_samples(unsigned int number_of_samples); + + private: + uint8_t _volume[2]; + uint8_t _divider[2]; + uint8_t _control[2]; + int _shift_counters[2]; +}; + +class Machine: public CPU6502::Processor, CRTMachine::Machine { public: Machine(); ~Machine(); - unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); - void set_rom(size_t length, const uint8_t *data); void switch_region(); void set_digital_input(Atari2600DigitalInput input, bool state); - Outputs::CRT::CRT *get_crt() { return _crt; } - void setup_output(float aspect_ratio); - void close_output(); + // to satisfy CPU6502::Processor + unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); + void synchronise() {} + + // to satisfy CRTMachine::Machine + virtual void setup_output(float aspect_ratio); + virtual void close_output(); + virtual Outputs::CRT::CRT *get_crt() { return _crt; } + virtual Outputs::Speaker *get_speaker() { return &_speaker; } + virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } private: uint8_t *_rom, *_romPages[4], _ram[128]; @@ -137,7 +158,10 @@ class Machine: public CPU6502::Processor { void output_pixels(unsigned int count); uint8_t get_output_pixel(); void update_timers(int mask); + + // Outputs Outputs::CRT::CRT *_crt; + Speaker _speaker; // latched output state unsigned int _lastOutputStateDuration; diff --git a/Machines/CRTMachine.hpp b/Machines/CRTMachine.hpp new file mode 100644 index 000000000..235e1e10b --- /dev/null +++ b/Machines/CRTMachine.hpp @@ -0,0 +1,30 @@ +// +// CRTMachine.hpp +// Clock Signal +// +// Created by Thomas Harte on 31/05/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef CRTMachine_hpp +#define CRTMachine_hpp + +#include "../Outputs/CRT/CRT.hpp" +#include "../Outputs/Speaker.hpp" + +namespace CRTMachine { + +class Machine { + public: + virtual void setup_output(float aspect_ratio) = 0; + virtual void close_output() = 0; + + virtual Outputs::CRT::CRT *get_crt() = 0; + virtual Outputs::Speaker *get_speaker() = 0; + + virtual void run_for_cycles(int number_of_cycles) = 0; +}; + +} + +#endif /* CRTMachine_hpp */ diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ff1b5ab93..af60bb8de 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -450,7 +450,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin return cycles; } -void Machine::update_output() +void Machine::synchronise() { update_display(); update_audio(); diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 151cdbe54..373d555a6 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -10,9 +10,8 @@ #define Electron_hpp #include "../../Processors/6502/CPU6502.hpp" -#include "../../Outputs/CRT/CRT.hpp" -#include "../../Outputs/Speaker.hpp" #include "../../Storage/Tape/Tape.hpp" +#include "../CRTMachine.hpp" #include namespace Electron { @@ -142,30 +141,34 @@ class Speaker: public ::Outputs::Filter { @discussion An instance of Electron::Machine represents the current state of an Acorn Electron. */ -class Machine: public CPU6502::Processor, Tape::Delegate { +class Machine: public CPU6502::Processor, Tape::Delegate, CRTMachine::Machine { public: Machine(); - unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); - void set_rom(ROMSlot slot, size_t length, const uint8_t *data); void set_tape(std::shared_ptr tape); void set_key_state(Key key, bool isPressed); void clear_all_keys(); - void setup_output(float aspect_ratio); - void close_output(); - Outputs::CRT::CRT *get_crt() { return _crt.get(); } - Outputs::Speaker *get_speaker() { return &_speaker; } - - virtual void tape_did_change_interrupt_status(Tape *tape); - - void update_output(); inline void set_use_fast_tape_hack(bool activate) { _use_fast_tape_hack = activate; } + // to satisfy CPU6502::Processor + unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); + void synchronise(); + + // to satisfy CRTMachine::Machine + virtual void setup_output(float aspect_ratio); + virtual void close_output(); + virtual Outputs::CRT::CRT *get_crt() { return _crt.get(); } + virtual Outputs::Speaker *get_speaker() { return &_speaker; } + virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } + + // to satisfy Tape::Delegate + virtual void tape_did_change_interrupt_status(Tape *tape); + private: inline void update_display(); @@ -215,12 +218,12 @@ class Machine: public CPU6502::Processor, Tape::Delegate { uint8_t *_current_output_target, *_initial_output_target; unsigned int _current_output_divider; - // Tape. + // Tape Tape _tape; bool _use_fast_tape_hack; bool _fast_load_is_in_data; - // Outputs. + // Outputs std::unique_ptr _crt; Speaker _speaker; }; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 5b2fa6348..f9dac9acd 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -334,6 +334,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = ""; }; 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = ""; }; 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = ""; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = ""; }; @@ -1192,6 +1193,7 @@ children = ( 4B2E2D961C3A06EC00138695 /* Atari2600 */, 4B2E2D9E1C3A070900138695 /* Electron */, + 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, ); name = Machines; path = ../../Machines; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index bb8ec8c12..08a039cec 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -19,7 +19,6 @@ - (void)runForNumberOfCycles:(int)numberOfCycles { @synchronized(self) { _electron.run_for_cycles(numberOfCycles); - _electron.update_output(); } } diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 759d04472..efd1fe772 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -24,6 +24,18 @@ class Speaker { virtual void speaker_did_complete_samples(Speaker *speaker, const int16_t *buffer, int buffer_size) = 0; }; + int get_ideal_clock_rate_in_range(int minimum, int maximum) + { + // return exactly the input rate if possible + if(_input_cycles_per_second >= minimum && _input_cycles_per_second <= maximum) return _input_cycles_per_second; + + // if the input rate is lower, return the minimum + if(_input_cycles_per_second < minimum) return minimum; + + // otherwise, return the maximum + return maximum; + } + void set_output_rate(int cycles_per_second, int buffer_size) { _output_cycles_per_second = cycles_per_second; diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index fc3fc5633..e4e884a06 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -67,9 +67,12 @@ extern const uint8_t JamOpcode; @abstact An abstract base class for emulation of a 6502 processor via the curiously recurring template pattern/f-bounded polymorphism. @discussion Subclasses should implement @c perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) in - order to provde the bus on which the 6502 operates. Additional functionality can be provided by the host machine by providing - a jam handler and inserting jam opcodes where appropriate; that will cause call outs when the program counter reaches those - addresses. @c return_from_subroutine can be used to exit from a jammed state. + order to provide the bus on which the 6502 operates and @c synchronise(), which is called upon completion of a continuous run + of cycles to allow a subclass to bring any on-demand activities up to date. + + Additional functionality can be provided by the host machine by providing a jam handler and inserting jam opcodes where appropriate; + that will cause call outs when the program counter reaches those addresses. @c return_from_subroutine can be used to exit from a + jammed state. */ template class Processor { public: @@ -597,7 +600,7 @@ template class Processor { case CycleFetchOperation: { _lastOperationPC = _pc; -// printf("%04x\n", _pc.full); +// printf("%04x x:%02x\n", _pc.full, _x); _pc.full++; read_op(_operation, _lastOperationPC.full); @@ -1041,15 +1044,17 @@ template class Processor { _ready_is_active = true; } } - - _cycles_left_to_run = number_of_cycles; - _scheduleProgramsReadPointer = scheduleProgramsReadPointer; - _scheduleProgramProgramCounter = scheduleProgramProgramCounter; - _nextAddress = nextAddress; - _nextBusOperation = nextBusOperation; - _busAddress = busAddress; - _busValue = busValue; } + + _cycles_left_to_run = number_of_cycles; + _scheduleProgramsReadPointer = scheduleProgramsReadPointer; + _scheduleProgramProgramCounter = scheduleProgramProgramCounter; + _nextAddress = nextAddress; + _nextBusOperation = nextBusOperation; + _busAddress = busAddress; + _busValue = busValue; + + static_cast(this)->synchronise(); } /*! From 8dc66167bedd7363f8608786d4de541a11367bc4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 May 2016 22:16:20 -0400 Subject: [PATCH 42/53] Made an attempt to consolidate the Objective-C++ side of things based on the incoming `CRTMachine::Machine`. --- Machines/Atari2600/Atari2600.hpp | 2 +- Machines/Electron/Electron.hpp | 2 +- .../Mac/Clock Signal/Wrappers/CSAtari2600.h | 2 - .../Mac/Clock Signal/Wrappers/CSAtari2600.mm | 18 ++----- .../Mac/Clock Signal/Wrappers/CSElectron.h | 2 - .../Mac/Clock Signal/Wrappers/CSElectron.mm | 48 ++++--------------- .../Wrappers/CSMachine+Subclassing.h | 9 ++-- .../Mac/Clock Signal/Wrappers/CSMachine.h | 2 + .../Mac/Clock Signal/Wrappers/CSMachine.mm | 38 ++++++++++++--- 9 files changed, 50 insertions(+), 73 deletions(-) diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 1596692c4..d29cb78bc 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -35,7 +35,7 @@ class Speaker: public ::Outputs::Filter { int _shift_counters[2]; }; -class Machine: public CPU6502::Processor, CRTMachine::Machine { +class Machine: public CPU6502::Processor, public CRTMachine::Machine { public: Machine(); diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 373d555a6..d249bac0d 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -141,7 +141,7 @@ class Speaker: public ::Outputs::Filter { @discussion An instance of Electron::Machine represents the current state of an Acorn Electron. */ -class Machine: public CPU6502::Processor, Tape::Delegate, CRTMachine::Machine { +class Machine: public CPU6502::Processor, public CRTMachine::Machine, Tape::Delegate { public: diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h index c0e097c52..05a0b70c3 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.h @@ -15,6 +15,4 @@ - (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput; - (void)setResetLineEnabled:(BOOL)enabled; -- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; - @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm index 4dab3724c..cec8e85bf 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSAtari2600.mm @@ -49,16 +49,6 @@ struct CRTDelegate: public Outputs::CRT::Delegate { } } -- (void)runForNumberOfCycles:(int)numberOfCycles { - @synchronized(self) { - _atari2600.run_for_cycles(numberOfCycles); - } -} - -- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { - _atari2600.get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); -} - - (void)setROM:(NSData *)rom { @synchronized(self) { _atari2600.set_rom(rom.length, (const uint8_t *)rom.bytes); @@ -79,16 +69,14 @@ struct CRTDelegate: public Outputs::CRT::Delegate { - (void)setupOutputWithAspectRatio:(float)aspectRatio { @synchronized(self) { - _atari2600.setup_output(aspectRatio); + [super setupOutputWithAspectRatio:aspectRatio]; _atari2600.get_crt()->set_delegate(&_crtDelegate); _crtDelegate.atari2600 = self; } } -- (void)closeOutput { - @synchronized(self) { - _atari2600.close_output(); - } +- (CRTMachine::Machine * const)machine { + return &_atari2600; } @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index 3b4d45ff3..04d8e51ac 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -19,8 +19,6 @@ - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; - (void)clearAllKeys; -- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; - @property (nonatomic, assign) BOOL useFastLoadingHack; @property (nonatomic, assign) BOOL useTelevisionOutput; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index 08a039cec..c7b82425e 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -16,10 +16,8 @@ Electron::Machine _electron; } -- (void)runForNumberOfCycles:(int)numberOfCycles { - @synchronized(self) { - _electron.run_for_cycles(numberOfCycles); - } +- (CRTMachine::Machine * const)machine { + return &_electron; } - (void)setOSROM:(nonnull NSData *)rom { @@ -40,10 +38,6 @@ } } -- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { - _electron.get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); -} - - (BOOL)openUEFAtURL:(NSURL *)URL { @synchronized(self) { try { @@ -56,24 +50,13 @@ } } -- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { +- (void)clearAllKeys { @synchronized(self) { - _electron.get_speaker()->set_output_rate(sampleRate, 256); - _electron.get_speaker()->set_delegate(delegate); - return YES; + _electron.clear_all_keys(); } } -- (void)clearAllKeys { -// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ - @synchronized(self) { - _electron.clear_all_keys(); - } -// }); -} - - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { -// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ @synchronized(self) { switch(key) { @@ -151,16 +134,13 @@ break; } } -// }); } - (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { -// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ - @synchronized(self) { - _useFastLoadingHack = useFastLoadingHack; - _electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false); - } -// }); + @synchronized(self) { + _useFastLoadingHack = useFastLoadingHack; + _electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false); + } } - (void)setUseTelevisionOutput:(BOOL)useTelevisionOutput { @@ -170,16 +150,4 @@ } } -- (void)setupOutputWithAspectRatio:(float)aspectRatio { - @synchronized(self) { - _electron.setup_output(aspectRatio); - } -} - -- (void)closeOutput { - @synchronized(self) { - _electron.close_output(); - } -} - @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h index 96d090697..59f724bb2 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine+Subclassing.h @@ -7,16 +7,15 @@ // #import "CSMachine.h" -#include "CRT.hpp" -#include "Speaker.hpp" +#include "CRTMachine.hpp" @interface CSMachine (Subclassing) -- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate; -- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; - (void)performAsync:(dispatch_block_t)action; - (void)performSync:(dispatch_block_t)action; + +- (CRTMachine::Machine * const)machine; + - (void)setupOutputWithAspectRatio:(float)aspectRatio; -- (void)closeOutput; @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h index 89467de11..1791b3780 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -13,7 +13,9 @@ @interface CSMachine : NSObject - (void)runForNumberOfCycles:(int)numberOfCycles; + - (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio; +- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; @property (nonatomic, weak) AudioQueue *audioQueue; @property (nonatomic, readonly) CSOpenGLView *view; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index 7c5507218..10b5b67d0 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -9,6 +9,10 @@ #import "CSMachine.h" #import "CSMachine+Subclassing.h" +@interface CSMachine() +- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; +@end + struct SpeakerDelegate: public Outputs::Speaker::Delegate { __weak CSMachine *machine; void speaker_did_complete_samples(Outputs::Speaker *speaker, const int16_t *buffer, int buffer_size) { @@ -39,15 +43,30 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { - (void)dealloc { [_view performWithGLContext:^{ - [self closeOutput]; + @synchronized(self) { + self.machine->close_output(); + } }]; } - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { - return NO; + @synchronized(self) { + Outputs::Speaker *speaker = self.machine->get_speaker(); + if(speaker) + { + speaker->set_output_rate(sampleRate, 256); + speaker->set_delegate(delegate); + return YES; + } + return NO; + } } -- (void)runForNumberOfCycles:(int)numberOfCycles {} +- (void)runForNumberOfCycles:(int)numberOfCycles { + @synchronized(self) { + self.machine->run_for_cycles(numberOfCycles); + } +} - (void)performSync:(dispatch_block_t)action { dispatch_sync(_serialDispatchQueue, action); @@ -57,10 +76,6 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { dispatch_async(_serialDispatchQueue, action); } -- (void)setupOutputWithAspectRatio:(float)aspectRatio {} - -- (void)closeOutput {} - - (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio { _view = view; [view performWithGLContext:^{ @@ -68,4 +83,13 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { }]; } +- (void)setupOutputWithAspectRatio:(float)aspectRatio { + self.machine->setup_output(aspectRatio); +} + +- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { + self.machine->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); +} + + @end From c773c3a66caca5685fb3061873035dd4f2078d4c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 May 2016 22:32:38 -0400 Subject: [PATCH 43/53] Started trying to clean up and consolidate on the Swift side of things but time is up for the day. --- .../Documents/Atari2600Document.swift | 21 +++------ .../Documents/ElectronDocument.swift | 45 +++++-------------- .../Documents/MachineDocument.swift | 33 +++++++++++++- 3 files changed, 46 insertions(+), 53 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index 6c586c360..172a9a1f2 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -10,6 +10,11 @@ import Cocoa class Atari2600Document: MachineDocument { + private var atari2600 = CSAtari2600() + override func machine() -> CSMachine? { + return atari2600 + } + // MARK: NSDocument overrides override init() { super.init() @@ -31,7 +36,6 @@ class Atari2600Document: MachineDocument { return "Atari2600Document" } - private var atari2600 = CSAtari2600() override func dataOfType(typeName: String) throws -> NSData { // Insert code here to write your document to data of the specified type. If outError != nil, ensure that you create and set an appropriate error when returning nil. // You can also choose to override fileWrapperOfType:error:, writeToURL:ofType:error:, or writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead. @@ -42,21 +46,6 @@ class Atari2600Document: MachineDocument { atari2600.setROM(data) } - override func close() { - super.close() - openGLView.invalidate() - } - - // MARK: MachineDocument overrides - - override func runForNumberOfCycles(numberOfCycles: Int32) { - atari2600.runForNumberOfCycles(numberOfCycles) - } - - override func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) { - atari2600.drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty) - } - // MARK: CSOpenGLViewResponderDelegate private func inputForKey(event: NSEvent) -> Atari2600DigitalInput? { diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 4d08e603a..f60b93aee 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -12,21 +12,24 @@ import AudioToolbox class ElectronDocument: MachineDocument { private lazy var electron = CSElectron() + override func machine() -> CSMachine! { + return electron + } override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) self.intendedCyclesPerSecond = 2000000 aController.window?.contentAspectRatio = NSSize(width: 11.0, height: 10.0) + if let osPath = NSBundle.mainBundle().pathForResource("os", ofType: "rom") { + self.electron.setOSROM(NSData(contentsOfFile: osPath)!) + } + if let basicPath = NSBundle.mainBundle().pathForResource("basic", ofType: "rom") { + self.electron.setBASICROM(NSData(contentsOfFile: basicPath)!) + } openGLView.performWithGLContext({ - if let osPath = NSBundle.mainBundle().pathForResource("os", ofType: "rom") { - self.electron.setOSROM(NSData(contentsOfFile: osPath)!) - } - if let basicPath = NSBundle.mainBundle().pathForResource("basic", ofType: "rom") { - self.electron.setBASICROM(NSData(contentsOfFile: basicPath)!) - } self.electron.setView(self.openGLView, aspectRatio: 11.0 / 10.0) - self.electron.audioQueue = self.audioQueue }) + self.electron.audioQueue = self.audioQueue establishStoredOptions() } @@ -58,19 +61,6 @@ class ElectronDocument: MachineDocument { electron.setROM(data, slot: 15) } - lazy var actionLock = NSLock() - lazy var drawLock = NSLock() - override func close() { - actionLock.lock() - drawLock.lock() - openGLView.invalidate() - openGLView.openGLContext!.makeCurrentContext() - actionLock.unlock() - drawLock.unlock() - - super.close() - } - // MARK: IBActions @IBOutlet var displayTypeButton: NSPopUpButton! @IBAction func setDisplayType(sender: NSPopUpButton!) { @@ -102,21 +92,6 @@ class ElectronDocument: MachineDocument { self.displayTypeButton.selectItemAtIndex(displayType) } - // MARK: CSOpenGLViewDelegate - override func runForNumberOfCycles(numberOfCycles: Int32) { - if actionLock.tryLock() { - electron.runForNumberOfCycles(numberOfCycles) - actionLock.unlock() - } - } - - override func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) { - if drawLock.tryLock() { - electron.drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty) - drawLock.unlock() - } - } - // MARK: NSWindowDelegate func windowDidResignKey(notification: NSNotification) { electron.clearAllKeys() diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 473e81aad..4d93ccfea 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -11,6 +11,12 @@ import AudioToolbox class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate, NSWindowDelegate { + lazy var actionLock = NSLock() + lazy var drawLock = NSLock() + func machine() -> CSMachine! { + return nil + } + @IBOutlet weak var openGLView: CSOpenGLView! { didSet { openGLView.delegate = self @@ -32,6 +38,17 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe aController.window?.contentAspectRatio = NSSize(width: 4.0, height: 3.0) } + override func close() { + actionLock.lock() + drawLock.lock() + openGLView.invalidate() + openGLView.openGLContext!.makeCurrentContext() + actionLock.unlock() + drawLock.unlock() + + super.close() + } + var intendedCyclesPerSecond: Int64 = 0 private var cycleCountError: Int64 = 0 private var lastTime: CVTimeStamp? @@ -62,8 +79,20 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe lastTime = time } - func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) {} - func runForNumberOfCycles(numberOfCycles: Int32) {} + // MARK: CSOpenGLViewDelegate + func runForNumberOfCycles(numberOfCycles: Int32) { + if actionLock.tryLock() { + self.machine().runForNumberOfCycles(numberOfCycles) + actionLock.unlock() + } + } + + func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) { + if drawLock.tryLock() { + self.machine().drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty) + drawLock.unlock() + } + } // MARK: CSOpenGLViewResponderDelegate func keyDown(event: NSEvent) {} From 50543e9676c34490f8fdaf1b8ef8f22617b22393 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 31 May 2016 22:36:53 -0400 Subject: [PATCH 44/53] Minor factoring up: the audio queue is now pushed universally by `MachineDocument`. --- .../Mac/Clock Signal/Documents/ElectronDocument.swift | 7 +++++-- .../Mac/Clock Signal/Documents/MachineDocument.swift | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index f60b93aee..1af7427ca 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -18,18 +18,21 @@ class ElectronDocument: MachineDocument { override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) - self.intendedCyclesPerSecond = 2000000 aController.window?.contentAspectRatio = NSSize(width: 11.0, height: 10.0) + + self.intendedCyclesPerSecond = 2000000 + if let osPath = NSBundle.mainBundle().pathForResource("os", ofType: "rom") { self.electron.setOSROM(NSData(contentsOfFile: osPath)!) } if let basicPath = NSBundle.mainBundle().pathForResource("basic", ofType: "rom") { self.electron.setBASICROM(NSData(contentsOfFile: basicPath)!) } + openGLView.performWithGLContext({ self.electron.setView(self.openGLView, aspectRatio: 11.0 / 10.0) }) - self.electron.audioQueue = self.audioQueue + establishStoredOptions() } diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 4d93ccfea..40da4bdd1 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -34,8 +34,11 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) - // bind the content aspect ratio to remain 4:3 from now on + // bind the content aspect ratio to remain 4:3 from now on as a default aController.window?.contentAspectRatio = NSSize(width: 4.0, height: 3.0) + + // provide the audio queue + self.machine().audioQueue = self.audioQueue } override func close() { From 8623dc28337b00e13a92a8b79e460b14a8b6af93 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 1 Jun 2016 19:04:07 -0400 Subject: [PATCH 45/53] Consolidated a little more within the common code, adding sampling rate selection based on querying the machine. --- .../Documents/Atari2600Document.swift | 5 -- .../Documents/ElectronDocument.swift | 9 ++-- .../Documents/MachineDocument.swift | 20 ++++++-- .../Mac/Clock Signal/Wrappers/AudioQueue.h | 5 ++ .../Mac/Clock Signal/Wrappers/AudioQueue.m | 48 +++++++++++++++---- .../Mac/Clock Signal/Wrappers/CSMachine.h | 3 ++ .../Mac/Clock Signal/Wrappers/CSMachine.mm | 20 +++++++- 7 files changed, 85 insertions(+), 25 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index 172a9a1f2..e13fff8fa 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -21,11 +21,6 @@ class Atari2600Document: MachineDocument { self.intendedCyclesPerSecond = 1194720 } - override func windowControllerDidLoadNib(aController: NSWindowController) { - super.windowControllerDidLoadNib(aController) - atari2600.setView(openGLView, aspectRatio: 4.0 / 3.0) - } - override class func autosavesInPlace() -> Bool { return true } diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 1af7427ca..9f73af395 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -16,9 +16,12 @@ class ElectronDocument: MachineDocument { return electron } + override func aspectRatio() -> NSSize { + return NSSize(width: 11.0, height: 10.0) + } + override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) - aController.window?.contentAspectRatio = NSSize(width: 11.0, height: 10.0) self.intendedCyclesPerSecond = 2000000 @@ -29,10 +32,6 @@ class ElectronDocument: MachineDocument { self.electron.setBASICROM(NSData(contentsOfFile: basicPath)!) } - openGLView.performWithGLContext({ - self.electron.setView(self.openGLView, aspectRatio: 11.0 / 10.0) - }) - establishStoredOptions() } diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 40da4bdd1..491941257 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -17,6 +17,10 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe return nil } + func aspectRatio() -> NSSize { + return NSSize(width: 4.0, height: 3.0) + } + @IBOutlet weak var openGLView: CSOpenGLView! { didSet { openGLView.delegate = self @@ -29,16 +33,24 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe optionsPanel?.setIsVisible(true) } - lazy var audioQueue = AudioQueue() + var audioQueue : AudioQueue! = nil override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) - // bind the content aspect ratio to remain 4:3 from now on as a default - aController.window?.contentAspectRatio = NSSize(width: 4.0, height: 3.0) + // establish the output aspect ratio and audio + let displayAspectRatio = self.aspectRatio() + aController.window?.contentAspectRatio = displayAspectRatio + openGLView.performWithGLContext({ + self.machine().setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height)) + }) - // provide the audio queue + // establish and provide the audio queue, taking advice as to an appropriate sampling rate + let maximumSamplingRate = AudioQueue.preferredSamplingRate() + let selectedSamplingRate = self.machine().idealSamplingRateFromRange(NSRange(location: 0, length: NSInteger(maximumSamplingRate))) + audioQueue = AudioQueue(samplingRate: Float64(selectedSamplingRate)) self.machine().audioQueue = self.audioQueue + self.machine().setAudioSamplingRate(selectedSamplingRate) } override func close() { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h index cf96e1166..de14ea68b 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.h @@ -10,6 +10,11 @@ @interface AudioQueue : NSObject +- (instancetype)initWithSamplingRate:(Float64)samplingRate; - (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples; +@property (nonatomic, readonly) Float64 samplingRate; + ++ (Float64)preferredSamplingRate; + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m index 80f14d7e0..9763cd1c6 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -85,20 +85,21 @@ static void audioOutputCallback( [(__bridge AudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer]; } -- (instancetype)init +- (instancetype)initWithSamplingRate:(Float64)samplingRate { self = [super init]; if(self) { _writeLock = [[NSConditionLock alloc] initWithCondition:AudioQueueCanProceed]; + _samplingRate = samplingRate; /* Describe a mono, 16bit, 44.1Khz audio format */ AudioStreamBasicDescription outputDescription; - outputDescription.mSampleRate = 44100; + outputDescription.mSampleRate = samplingRate; outputDescription.mFormatID = kAudioFormatLinearPCM; outputDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; @@ -113,13 +114,13 @@ static void audioOutputCallback( // create an audio output queue along those lines if(!AudioQueueNewOutput( - &outputDescription, - audioOutputCallback, - (__bridge void *)(self), - NULL, - kCFRunLoopCommonModes, - 0, - &_audioQueue)) + &outputDescription, + audioOutputCallback, + (__bridge void *)(self), + NULL, + kCFRunLoopCommonModes, + 0, + &_audioQueue)) { UInt32 bufferBytes = AudioQueueBufferLength * sizeof(int16_t); @@ -139,6 +140,11 @@ static void audioOutputCallback( return self; } +- (instancetype)init +{ + return [self initWithSamplingRate:[[self class] preferredSamplingRate]]; +} + - (void)dealloc { [_writeLock lock]; @@ -190,4 +196,28 @@ static void audioOutputCallback( return ((_audioStreamWritePosition - _audioStreamReadPosition) < (AudioQueueStreamLength - AudioQueueBufferLength)) ? AudioQueueCanProceed : AudioQueueWait; } ++ (AudioDeviceID)defaultOutputDevice +{ + AudioObjectPropertyAddress address; + address.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + address.mScope = kAudioObjectPropertyScopeGlobal; + address.mElement = kAudioObjectPropertyElementMaster; + + AudioDeviceID deviceID; + UInt32 size = sizeof(AudioDeviceID); + return AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &size, &deviceID) ? 0 : deviceID; +} + ++ (Float64)preferredSamplingRate +{ + AudioObjectPropertyAddress address; + address.mSelector = kAudioDevicePropertyNominalSampleRate; + address.mScope = kAudioObjectPropertyScopeGlobal; + address.mElement = kAudioObjectPropertyElementMaster; + + Float64 samplingRate; + UInt32 size = sizeof(Float64); + return AudioHardwareServiceGetPropertyData([self defaultOutputDevice], &address, 0, NULL, &size, &samplingRate) ? 0.0 : samplingRate; +} + @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h index 1791b3780..c91487b7f 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -14,6 +14,9 @@ - (void)runForNumberOfCycles:(int)numberOfCycles; +- (int)idealSamplingRateFromRange:(NSRange)range; +- (void)setAudioSamplingRate:(int)samplingRate; + - (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio; - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index 10b5b67d0..f17c7e31a 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -34,8 +34,6 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { if(self) { _serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL); - _speakerDelegate.machine = self; - [self setSpeakerDelegate:&_speakerDelegate sampleRate:44100]; } return self; @@ -49,6 +47,24 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { }]; } +- (int)idealSamplingRateFromRange:(NSRange)range { + @synchronized(self) { + Outputs::Speaker *speaker = self.machine->get_speaker(); + if(speaker) + { + return speaker->get_ideal_clock_rate_in_range((int)range.location, (int)(range.location + range.length)); + } + return (int)range.location; + } +} + +- (void)setAudioSamplingRate:(int)samplingRate { + @synchronized(self) { + _speakerDelegate.machine = self; + [self setSpeakerDelegate:&_speakerDelegate sampleRate:samplingRate]; + } +} + - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { @synchronized(self) { Outputs::Speaker *speaker = self.machine->get_speaker(); From 40c4544fb795628c410a0f7432ab6ce2a95e9adb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 1 Jun 2016 19:27:04 -0400 Subject: [PATCH 46/53] Got the basic sound pipeline pumping, just enough for static-level audio seemingly to work. Berzerk VE says "intruder alert, intruder alert", anyway. --- Machines/Atari2600/Atari2600.cpp | 75 +++++++++++++++++++++++++++++--- Machines/Atari2600/Atari2600.hpp | 8 +++- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 7cf96d27e..f9229f97c 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -25,7 +25,8 @@ Machine::Machine() : _tiaInputValue{0xff, 0xff}, _upcomingEventsPointer(0), _objectCounterPointer(0), - _stateByTime(_stateByExtendTime[0]) + _stateByTime(_stateByExtendTime[0]), + _cycles_since_speaker_update(0) { memset(_collisions, 0xff, sizeof(_collisions)); set_reset_line(true); @@ -414,6 +415,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } output_pixels(cycles_run_for); + _cycles_since_speaker_update += cycles_run_for; if(operation != CPU6502::BusOperation::Ready) { @@ -449,10 +451,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin returnValue &= _ram[address&0x7f]; } else { _ram[address&0x7f] = *value; -// if((address&0x7f) == (0x9a&0x7f)) -// { -// printf("[9a] <- %02x\n", *value); -// } } } @@ -498,6 +496,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x03: // Reset is delayed by four cycles. _horizontalTimer = horizontalTimerPeriod - 4; + + // TODO: audio will now be out of synchronisation — fix break; case 0x04: @@ -585,6 +585,21 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _upcomingEvents[(_upcomingEventsPointer + 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: _ballGraphicsEnable[1] = _ballGraphicsEnable[0]; case 0x1b: { @@ -765,3 +780,53 @@ void Machine::set_rom(size_t length, const uint8_t *data) _romPages[2] = &_rom[2048 & romMask]; _romPages[3] = &_rom[3072 & romMask]; } + +#pragma mark - Audio + +void Machine::update_audio() +{ + _speaker.run_for_cycles(_cycles_since_speaker_update / 114); + _cycles_since_speaker_update %= 114; +} + +void Machine::synchronise() +{ + update_audio(); +} + +void Atari2600::Speaker::set_volume(int channel, uint8_t volume) +{ + _volume[channel] = volume & 0xf; +} + +void Atari2600::Speaker::set_divider(int channel, uint8_t divider) +{ + _divider[channel] = divider & 0x1f; +} + +void Atari2600::Speaker::set_control(int channel, uint8_t control) +{ + _control[channel] = control & 0xf; +} + +void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) +{ + for(unsigned int c = 0; c < number_of_samples; c++) + { + target[c] = 0; + for(int channel = 0; channel < 2; channel++) + { + if(!_control[channel]) + { + target[c] += _volume[channel] * 1024; + } + else + { + } + } + } +} + +void Atari2600::Speaker::skip_samples(unsigned int number_of_samples) +{ +} diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index d29cb78bc..2965a26c4 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -48,7 +48,7 @@ class Machine: public CPU6502::Processor, public CRTMachine::Machine { // to satisfy CPU6502::Processor unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); - void synchronise() {} + void synchronise(); // to satisfy CRTMachine::Machine virtual void setup_output(float aspect_ratio); @@ -159,10 +159,14 @@ class Machine: public CPU6502::Processor, public CRTMachine::Machine { uint8_t get_output_pixel(); void update_timers(int mask); - // Outputs + // outputs Outputs::CRT::CRT *_crt; Speaker _speaker; + // speaker backlog accumlation counter + unsigned int _cycles_since_speaker_update; + void update_audio(); + // latched output state unsigned int _lastOutputStateDuration; OutputState _stateByExtendTime[2][57]; From 1e0fcbbee8c2f49c812dffd6c8e370c44d70ef81 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 1 Jun 2016 19:53:16 -0400 Subject: [PATCH 47/53] Made a very basic stab at a couple of the tone generators, added straight-through path for the speaker when input rate exactly equals output rate. --- Machines/Atari2600/Atari2600.cpp | 20 ++++++-- Machines/Atari2600/Atari2600.hpp | 4 +- Outputs/Speaker.hpp | 84 ++++++++++++++++++++++---------- 3 files changed, 76 insertions(+), 32 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index f9229f97c..9ce5deba7 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -802,6 +802,7 @@ void Atari2600::Speaker::set_volume(int channel, uint8_t volume) void Atari2600::Speaker::set_divider(int channel, uint8_t divider) { _divider[channel] = divider & 0x1f; + _divider_counter[channel] = 0; } void Atari2600::Speaker::set_control(int channel, uint8_t control) @@ -816,12 +817,21 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta target[c] = 0; for(int channel = 0; channel < 2; channel++) { - if(!_control[channel]) - { - target[c] += _volume[channel] * 1024; - } - else + switch(_control[channel]) { + case 0x0: case 0xb: + target[c] += _volume[channel] * 1024; + break; + + case 0x4: case 0x5: + _divider_counter[channel] ++; + target[c] += _volume[channel] * 1024 * ((_divider_counter[channel] / (_divider[channel]+1))&1); + break; + + case 0xc: case 0xd: + _divider_counter[channel] ++; + target[c] += _volume[channel] * 1024 * ((_divider_counter[channel] / ((_divider[channel]+1)*3))&1); + break; } } } diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index 2965a26c4..b6c84995b 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -32,7 +32,9 @@ class Speaker: public ::Outputs::Filter { uint8_t _volume[2]; uint8_t _divider[2]; uint8_t _control[2]; - int _shift_counters[2]; + int _shift_counter[2]; + int _divider_counter[2]; + int _output_state[2]; }; class Machine: public CPU6502::Processor, public CRTMachine::Machine { diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index efd1fe772..9742e86df 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -88,20 +88,16 @@ template class Filter: public Speaker { { if(_coefficients_are_dirty) update_filter_coefficients(); - // TODO: what if output rate is greater than input rate? - - // fill up as much of the input buffer as possible - while(input_cycles) + // if input and output rates exactly match, just accumulate results and pass on + if(_input_cycles_per_second == _output_cycles_per_second) { - unsigned int cycles_to_read = (unsigned int)std::min((int)input_cycles, _number_of_taps - _input_buffer_depth); - static_cast(this)->get_samples(cycles_to_read, &_input_buffer.get()[_input_buffer_depth]); - input_cycles -= cycles_to_read; - _input_buffer_depth += cycles_to_read; - - if(_input_buffer_depth == _number_of_taps) + while(input_cycles) { - _buffer_in_progress.get()[_buffer_in_progress_pointer] = _filter->apply(_input_buffer.get()); - _buffer_in_progress_pointer++; + unsigned int cycles_to_read = (unsigned int)(_buffer_size - _buffer_in_progress_pointer); + if(cycles_to_read > input_cycles) cycles_to_read = input_cycles; + + static_cast(this)->get_samples(cycles_to_read, &_buffer_in_progress.get()[_buffer_in_progress_pointer]); + _buffer_in_progress_pointer += cycles_to_read; // announce to delegate if full if(_buffer_in_progress_pointer == _buffer_size) @@ -113,24 +109,60 @@ template class Filter: public Speaker { } } - // If the next loop around is going to reuse some of the samples just collected, use a memmove to - // preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip - // anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse. - uint64_t steps = _stepper->step(); - if(steps < _number_of_taps) + input_cycles -= cycles_to_read; + } + + return; + } + + // if the output rate is less than the input rate, use the filter + if(_input_cycles_per_second > _output_cycles_per_second) + { + while(input_cycles) + { + unsigned int cycles_to_read = (unsigned int)std::min((int)input_cycles, _number_of_taps - _input_buffer_depth); + static_cast(this)->get_samples(cycles_to_read, &_input_buffer.get()[_input_buffer_depth]); + input_cycles -= cycles_to_read; + _input_buffer_depth += cycles_to_read; + + if(_input_buffer_depth == _number_of_taps) { - int16_t *input_buffer = _input_buffer.get(); - memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)_number_of_taps - (size_t)steps)); - _input_buffer_depth -= steps; - } - else - { - if(steps > _number_of_taps) - static_cast(this)->skip_samples((unsigned int)steps - (unsigned int)_number_of_taps); - _input_buffer_depth = 0; + _buffer_in_progress.get()[_buffer_in_progress_pointer] = _filter->apply(_input_buffer.get()); + _buffer_in_progress_pointer++; + + // announce to delegate if full + if(_buffer_in_progress_pointer == _buffer_size) + { + _buffer_in_progress_pointer = 0; + if(_delegate) + { + _delegate->speaker_did_complete_samples(this, _buffer_in_progress.get(), _buffer_size); + } + } + + // If the next loop around is going to reuse some of the samples just collected, use a memmove to + // preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip + // anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse. + uint64_t steps = _stepper->step(); + if(steps < _number_of_taps) + { + int16_t *input_buffer = _input_buffer.get(); + memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * ((size_t)_number_of_taps - (size_t)steps)); + _input_buffer_depth -= steps; + } + else + { + if(steps > _number_of_taps) + static_cast(this)->skip_samples((unsigned int)steps - (unsigned int)_number_of_taps); + _input_buffer_depth = 0; + } } } + + return; } + + // TODO: input rate is less than output rate } private: From 253e5a42030ed30234f3f7fc30a58906124fcd05 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 2 Jun 2016 19:50:16 -0400 Subject: [PATCH 48/53] With no regard to performance whatsoever, endeavoured to bring sound to the 2600. It's very scratchy, so something is wrong. --- Machines/Atari2600/Atari2600.cpp | 117 +++++++++++++++--- Machines/Atari2600/Atari2600.hpp | 14 ++- .../Mac/Clock Signal/Wrappers/AudioQueue.m | 2 +- .../Mac/Clock Signal/Wrappers/CSMachine.mm | 2 +- 4 files changed, 116 insertions(+), 19 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 9ce5deba7..676208be3 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -73,7 +73,7 @@ void Machine::setup_output(float aspect_ratio) "}"); _crt->set_output_device(Outputs::CRT::Television); - _speaker.set_input_rate(2 * 263 * 60); + _speaker.set_input_rate(1194720 / 38); } void Machine::switch_region() @@ -93,7 +93,7 @@ void Machine::switch_region() "}"); _crt->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1); - _speaker.set_input_rate(2 * 312 * 50); +// _speaker.set_input_rate(2 * 312 * 50); } void Machine::close_output() @@ -692,6 +692,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin printf("!!!DDR!!!"); break; case 0x04: + case 0x06: returnValue &= _piaTimerValue >> _piaTimerShift; if(_writtenPiaTimerShift != _piaTimerShift) { @@ -700,8 +701,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } break; case 0x05: + case 0x07: returnValue &= _piaTimerStatus; - _piaTimerStatus &= ~0x40; + _piaTimerStatus &= ~0x80; break; } } else { @@ -711,9 +713,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin case 0x05: case 0x06: case 0x07: - _writtenPiaTimerShift = _piaTimerShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); - _piaTimerValue = (unsigned int)(*value << _piaTimerShift); - _piaTimerStatus &= ~0xc0; + _writtenPiaTimerShift = _piaTimerShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10 + _piaTimerValue = (unsigned int)(*value) << _piaTimerShift; + _piaTimerStatus &= ~0x40; break; } } @@ -727,7 +729,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin if(_piaTimerValue >= cycles_run_for / 3) { _piaTimerValue -= cycles_run_for / 3; } else { - _piaTimerValue += 0xff - cycles_run_for / 3; + _piaTimerValue = 0x100 + ((_piaTimerValue - (cycles_run_for / 3)) >> _piaTimerShift); _piaTimerShift = 0; _piaTimerStatus |= 0xc0; } @@ -794,6 +796,17 @@ void Machine::synchronise() update_audio(); } +Atari2600::Speaker::Speaker() +{ + _poly4_counter[0] = _poly4_counter[1] = + _poly5_counter[0] = _poly5_counter[1] = + _poly9_counter[0] = _poly9_counter[1] = ~0; +} + +Atari2600::Speaker::~Speaker() +{ +} + void Atari2600::Speaker::set_volume(int channel, uint8_t volume) { _volume[channel] = volume & 0xf; @@ -808,8 +821,15 @@ void Atari2600::Speaker::set_divider(int channel, uint8_t divider) void Atari2600::Speaker::set_control(int channel, uint8_t control) { _control[channel] = control & 0xf; +// _shift_counter[channel] = ~0; +// printf("%d\n", _control[channel]); } +#define advance_poly4(c) _poly4_counter[channel] = (_poly4_counter[channel] >> 1) | (((_poly4_counter[channel] << 3) ^ (_poly4_counter[channel] << 2))&0x008) +#define advance_poly5(c) _poly5_counter[channel] = (_poly5_counter[channel] >> 1) | (((_poly5_counter[channel] << 4) ^ (_poly5_counter[channel] << 2))&0x010) +#define advance_poly9(c) _poly9_counter[channel] = (_poly9_counter[channel] >> 1) | (((_poly9_counter[channel] << 4) ^ (_poly9_counter[channel] << 8))&0x200) + + void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) { for(unsigned int c = 0; c < number_of_samples; c++) @@ -817,22 +837,89 @@ void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *ta target[c] = 0; for(int channel = 0; channel < 2; channel++) { + _divider_counter[channel] ++; + int level = 0; switch(_control[channel]) { - case 0x0: case 0xb: - target[c] += _volume[channel] * 1024; + case 0x0: case 0xb: // constant 1 + level = 1; break; - case 0x4: case 0x5: - _divider_counter[channel] ++; - target[c] += _volume[channel] * 1024 * ((_divider_counter[channel] / (_divider[channel]+1))&1); + case 0x4: case 0x5: // div2 tone + level = (_divider_counter[channel] / (_divider[channel]+1))&1; break; - case 0xc: case 0xd: - _divider_counter[channel] ++; - target[c] += _volume[channel] * 1024 * ((_divider_counter[channel] / ((_divider[channel]+1)*3))&1); + case 0xc: case 0xd: // div6 tone + level = (_divider_counter[channel] / ((_divider[channel]+1)*3))&1; + break; + + case 0x6: case 0xa: // div31 tone + level = (_divider_counter[channel] / (_divider[channel]+1))%30 <= 18; + break; + + case 0xe: // div93 tone + level = (_divider_counter[channel] / ((_divider[channel]+1)*3))%30 <= 18; + break; + + case 0x1: // 4-bit poly + level = _poly4_counter[channel]&1; + if(_divider_counter[channel] == _divider[channel]+1) + { + _divider_counter[channel] = 0; + advance_poly4(channel); + } + break; + + case 0x2: // 4-bit poly div31 + level = _poly4_counter[channel]&1; + if(_divider_counter[channel]%(30*(_divider[channel]+1)) == 18) + { + advance_poly4(channel); + } + break; + + case 0x3: // 5/4-bit poly + level = _output_state[channel]; + if(_divider_counter[channel] == _divider[channel]+1) + { + if(_poly5_counter[channel]&1) + { + _output_state[channel] = _poly4_counter[channel]&1; + advance_poly4(channel); + } + advance_poly5(channel); + } + break; + + case 0x7: case 0x9: // 5-bit poly + level = _poly5_counter[channel]&1; + if(_divider_counter[channel] == _divider[channel]+1) + { + _divider_counter[channel] = 0; + advance_poly5(channel); + } + break; + + case 0xf: // 5-bit poly div6 + level = _poly5_counter[channel]&1; + if(_divider_counter[channel] == (_divider[channel]+1)*3) + { + _divider_counter[channel] = 0; + advance_poly5(channel); + } + break; + + case 0x8: // 9-bit poly + level = _poly9_counter[channel]&1; + if(_divider_counter[channel] == _divider[channel]+1) + { + _divider_counter[channel] = 0; + advance_poly9(channel); + } break; } + + target[c] += _volume[channel] * 1024 * level; } } } diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index b6c84995b..c6c8e99a4 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -21,6 +21,9 @@ const unsigned int number_of_recorded_counters = 7; class Speaker: public ::Outputs::Filter { public: + Speaker(); + ~Speaker(); + void set_volume(int channel, uint8_t volume); void set_divider(int channel, uint8_t divider); void set_control(int channel, uint8_t control); @@ -32,9 +35,16 @@ class Speaker: public ::Outputs::Filter { uint8_t _volume[2]; uint8_t _divider[2]; uint8_t _control[2]; - int _shift_counter[2]; - int _divider_counter[2]; + + int _poly4_counter[2]; + int _poly5_counter[2]; + int _poly9_counter[2]; int _output_state[2]; + + int _divider_counter[2]; + + int _pattern_periods[16]; + int _patterns[16][512]; }; class Machine: public CPU6502::Processor, public CRTMachine::Machine { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m index 9763cd1c6..9a409ea60 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -11,7 +11,7 @@ #define AudioQueueNumAudioBuffers 4 #define AudioQueueStreamLength 1024 -#define AudioQueueBufferLength 256 +#define AudioQueueBufferLength 512 enum { AudioQueueCanProceed, diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index f17c7e31a..a52d68bec 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -70,7 +70,7 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { Outputs::Speaker *speaker = self.machine->get_speaker(); if(speaker) { - speaker->set_output_rate(sampleRate, 256); + speaker->set_output_rate(sampleRate, 512); speaker->set_delegate(delegate); return YES; } From 7aac306a122674ca77f33f3630edd268a41d61fc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 2 Jun 2016 19:56:02 -0400 Subject: [PATCH 49/53] Quick fix to the polynomials. --- Machines/Atari2600/Atari2600.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 676208be3..277a1cae3 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -798,9 +798,9 @@ void Machine::synchronise() Atari2600::Speaker::Speaker() { - _poly4_counter[0] = _poly4_counter[1] = - _poly5_counter[0] = _poly5_counter[1] = - _poly9_counter[0] = _poly9_counter[1] = ~0; + _poly4_counter[0] = _poly4_counter[1] = 0x00f; + _poly5_counter[0] = _poly5_counter[1] = 0x01f; + _poly9_counter[0] = _poly9_counter[1] = 0x1ff; } Atari2600::Speaker::~Speaker() @@ -821,13 +821,11 @@ void Atari2600::Speaker::set_divider(int channel, uint8_t divider) void Atari2600::Speaker::set_control(int channel, uint8_t control) { _control[channel] = control & 0xf; -// _shift_counter[channel] = ~0; -// printf("%d\n", _control[channel]); } #define advance_poly4(c) _poly4_counter[channel] = (_poly4_counter[channel] >> 1) | (((_poly4_counter[channel] << 3) ^ (_poly4_counter[channel] << 2))&0x008) #define advance_poly5(c) _poly5_counter[channel] = (_poly5_counter[channel] >> 1) | (((_poly5_counter[channel] << 4) ^ (_poly5_counter[channel] << 2))&0x010) -#define advance_poly9(c) _poly9_counter[channel] = (_poly9_counter[channel] >> 1) | (((_poly9_counter[channel] << 4) ^ (_poly9_counter[channel] << 8))&0x200) +#define advance_poly9(c) _poly9_counter[channel] = (_poly9_counter[channel] >> 1) | (((_poly9_counter[channel] << 4) ^ (_poly9_counter[channel] << 8))&0x100) void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) From 11073daee17077c1dc4caa3ef91a67849e9601bb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 2 Jun 2016 20:15:48 -0400 Subject: [PATCH 50/53] Issues appear to be around timing generally. Working on it. --- Machines/Atari2600/Atari2600.cpp | 26 ++++++++++++++++- .../Mac/Clock Signal/Views/CSOpenGLView.m | 29 ++++++++++++------- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 277a1cae3..118d451c2 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -734,6 +734,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _piaTimerStatus |= 0xc0; } +// static unsigned int total_cycles = 0; +// total_cycles += cycles_run_for / 3; +// static time_t logged_time = 0; +// time_t time_now = time(nullptr); +// if(time_now - logged_time > 0) +// { +// printf("[c] %ld : %d\n", time_now - logged_time, total_cycles); +// total_cycles = 0; +// logged_time = time_now; +// } + return cycles_run_for / 3; } @@ -787,7 +798,20 @@ void Machine::set_rom(size_t length, const uint8_t *data) void Machine::update_audio() { - _speaker.run_for_cycles(_cycles_since_speaker_update / 114); + unsigned int audio_cycles = _cycles_since_speaker_update / 114; + +// static unsigned int total_cycles = 0; +// total_cycles += audio_cycles; +// static time_t logged_time = 0; +// time_t time_now = time(nullptr); +// if(time_now - logged_time > 0) +// { +// printf("[s] %ld : %d\n", time_now - logged_time, total_cycles); +// total_cycles = 0; +// logged_time = time_now; +// } + + _speaker.run_for_cycles(audio_cycles); _cycles_since_speaker_update %= 114; } diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 29d399e15..08188405d 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -14,6 +14,7 @@ CVDisplayLinkRef _displayLink; uint32_t _updateIsOngoing; BOOL _hasSkipped; + dispatch_queue_t _serialDispatchQueue; } - (void)prepareOpenGL @@ -33,6 +34,8 @@ CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat); + _serialDispatchQueue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL); + // set the clear colour [self.openGLContext makeCurrentContext]; glClearColor(0.0, 0.0, 0.0, 1.0); @@ -51,31 +54,35 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawAtTime:(const CVTimeStamp *)now frequency:(double)frequency { const uint32_t processingMask = 0x01; -// const uint32_t drawingMask = 0x02; + const uint32_t drawingMask = 0x02; - // Always post a -openGLView:didUpdateToTime:. This is the hook upon which the substantial processing occurs. + // Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs. if(!OSAtomicTestAndSet(processingMask, &_updateIsOngoing)) { CVTimeStamp time = *now; BOOL didSkip = _hasSkipped; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + dispatch_async(_serialDispatchQueue, ^{ [self.delegate openGLView:self didUpdateToTime:time didSkipPreviousUpdate:didSkip frequency:frequency]; [self drawViewOnlyIfDirty:YES]; OSAtomicTestAndClear(processingMask, &_updateIsOngoing); }); _hasSkipped = NO; - } else _hasSkipped = YES; + } + else + { + _hasSkipped = YES; + } // Draw the display only if a previous draw is not still ongoing. -drawViewOnlyIfDirty: is guaranteed // to be safe to call concurrently with -openGLView:updateToTime: so there's no need to worry about // the above interrupting the below or vice versa. -// if(_hasSkipped && !OSAtomicTestAndSet(drawingMask, &_updateIsOngoing)) -// { -// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ -// [self drawViewOnlyIfDirty:YES]; -// OSAtomicTestAndClear(drawingMask, &_updateIsOngoing); -// }); -// } + if(_hasSkipped && !OSAtomicTestAndSet(drawingMask, &_updateIsOngoing)) + { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + [self drawViewOnlyIfDirty:YES]; + OSAtomicTestAndClear(drawingMask, &_updateIsOngoing); + }); + } } - (void)invalidate From e3b95b8d2bdd159a516e5b89b22ad025379b5fc9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 2 Jun 2016 21:22:55 -0400 Subject: [PATCH 51/53] Back to asynchronous updates and drawing, to try to improve guarantees on audio latency; experimenting with whether other parts of the approach are fundamentally flawed; added a broad-phase for scheduled updates on the 2600. --- Machines/Atari2600/Atari2600.cpp | 85 ++++++++++--------- Machines/Atari2600/Atari2600.hpp | 2 +- .../Mac/Clock Signal/Views/CSOpenGLView.m | 9 +- 3 files changed, 53 insertions(+), 43 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 118d451c2..3271da44b 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -283,57 +283,63 @@ void Machine::output_pixels(unsigned int count) { while(count--) { - // apply any queued changes and flush the record - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveSetup) + if(_upcomingEvents[_upcomingEventsPointer].updates) { - _stateByTime = _stateByExtendTime[1]; - - // clear any ongoing moves - if(_hMoveFlags) + // apply any queued changes and flush the record + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveSetup) { - for(int c = 0; c < number_of_upcoming_events; c++) + // schedule an extended left border + _stateByTime = _stateByExtendTime[1]; + + // clear any ongoing moves + if(_hMoveFlags) { - _upcomingEvents[c].updates &= ~(Event::Action::HMoveCompare | Event::Action::HMoveDecrement); + for(int c = 0; c < number_of_upcoming_events; c++) + { + _upcomingEvents[c].updates &= ~(Event::Action::HMoveCompare | Event::Action::HMoveDecrement); + } + } + + // schedule new moves + _hMoveFlags = 0x1f; + _hMoveCounter = 15; + + // follow-through into a compare immediately + _upcomingEvents[_upcomingEventsPointer].updates |= Event::Action::HMoveCompare; + } + + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveCompare) + { + for(int c = 0; c < 5; c++) + { + if(((_objectMotion[c] >> 4)^_hMoveCounter) == 7) + { + _hMoveFlags &= ~(1 << c); + } + } + if(_hMoveFlags) + { + if(_hMoveCounter) _hMoveCounter--; + _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; + _upcomingEvents[(_upcomingEventsPointer+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; } } - // schedule new moves - _hMoveFlags = 0x1f; - _hMoveCounter = 15; - - // follow-through into a compare immediately - _upcomingEvents[_upcomingEventsPointer].updates |= Event::Action::HMoveCompare; - } - - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveCompare) - { - for(int c = 0; c < 5; c++) + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveDecrement) { - if(((_objectMotion[c] >> 4)^_hMoveCounter) == 7) - { - _hMoveFlags &= ~(1 << c); - } + update_timers(_hMoveFlags); } - if(_hMoveFlags) + + if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ResetCounter) { - if(_hMoveCounter) _hMoveCounter--; - _upcomingEvents[(_upcomingEventsPointer+4)%number_of_upcoming_events].updates |= Event::Action::HMoveCompare; - _upcomingEvents[(_upcomingEventsPointer+2)%number_of_upcoming_events].updates |= Event::Action::HMoveDecrement; + _objectCounter[_objectCounterPointer][_upcomingEvents[_upcomingEventsPointer].counter].count = 0; } + + // zero out current update event + _upcomingEvents[_upcomingEventsPointer].updates = 0; } - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::HMoveDecrement) - { - update_timers(_hMoveFlags); - } - - if(_upcomingEvents[_upcomingEventsPointer].updates & Event::Action::ResetCounter) - { - _objectCounter[_objectCounterPointer][_upcomingEvents[_upcomingEventsPointer].counter].count = 0; - } - - // zero out current update event, progress to next - _upcomingEvents[_upcomingEventsPointer].updates = 0; + // progress to next event _upcomingEventsPointer = (_upcomingEventsPointer + 1)%number_of_upcoming_events; // determine which output state is currently active @@ -392,6 +398,7 @@ void Machine::output_pixels(unsigned int count) _horizontalTimer = (_horizontalTimer + 1) % horizontalTimerPeriod; if(!_horizontalTimer) { + // switch back to a normal length left border _stateByTime = _stateByExtendTime[0]; set_ready_line(false); } diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index c6c8e99a4..656cc844d 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -16,7 +16,7 @@ namespace Atari2600 { -const unsigned int number_of_upcoming_events = 16; +const unsigned int number_of_upcoming_events = 6; const unsigned int number_of_recorded_counters = 7; class Speaker: public ::Outputs::Filter { diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 08188405d..2645c368b 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -34,7 +34,9 @@ CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat); - _serialDispatchQueue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL); + // create a serial dispatch queue + _serialDispatchQueue = dispatch_queue_create("OpenGLView", DISPATCH_QUEUE_SERIAL); +// dispatch_set_target_queue(_serialDispatchQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); // set the clear colour [self.openGLContext makeCurrentContext]; @@ -63,20 +65,21 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt BOOL didSkip = _hasSkipped; dispatch_async(_serialDispatchQueue, ^{ [self.delegate openGLView:self didUpdateToTime:time didSkipPreviousUpdate:didSkip frequency:frequency]; - [self drawViewOnlyIfDirty:YES]; OSAtomicTestAndClear(processingMask, &_updateIsOngoing); }); _hasSkipped = NO; + NSLog(@"+"); } else { _hasSkipped = YES; + NSLog(@"-"); } // Draw the display only if a previous draw is not still ongoing. -drawViewOnlyIfDirty: is guaranteed // to be safe to call concurrently with -openGLView:updateToTime: so there's no need to worry about // the above interrupting the below or vice versa. - if(_hasSkipped && !OSAtomicTestAndSet(drawingMask, &_updateIsOngoing)) + if(!OSAtomicTestAndSet(drawingMask, &_updateIsOngoing)) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self drawViewOnlyIfDirty:YES]; From 68a8851c52ff558f192080bf13f4284f7b9ef9ee Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 2 Jun 2016 22:29:09 -0400 Subject: [PATCH 52/53] Increased parallelism, allowing a simplification in the GL view. --- .../Documents/MachineDocument.swift | 7 +-- .../Mac/Clock Signal/Views/CSOpenGLView.m | 15 +------ Outputs/CRT/Internals/CRTOpenGL.cpp | 43 +++++++++++-------- Outputs/CRT/Internals/CRTOpenGL.hpp | 1 + 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 491941257..e752f032f 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -86,9 +86,10 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe skippedFrames = 0 } - if skippedFrames > 4 { - numberOfCycles = min(numberOfCycles, Int64(Double(intendedCyclesPerSecond) * frequency)) - } + // run for at most three frames up to and until that causes overshoots in the + // permitted processing window for at least four consecutive frames, in which + // case limit to one + numberOfCycles = min(numberOfCycles, Int64(Double(intendedCyclesPerSecond) * frequency * ((skippedFrames > 4) ? 3.0 : 1.0))) runForNumberOfCycles(Int32(numberOfCycles)) } lastTime = time diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index 2645c368b..ce4a19046 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -56,7 +56,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawAtTime:(const CVTimeStamp *)now frequency:(double)frequency { const uint32_t processingMask = 0x01; - const uint32_t drawingMask = 0x02; // Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs. if(!OSAtomicTestAndSet(processingMask, &_updateIsOngoing)) @@ -68,24 +67,14 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt OSAtomicTestAndClear(processingMask, &_updateIsOngoing); }); _hasSkipped = NO; - NSLog(@"+"); } else { _hasSkipped = YES; - NSLog(@"-"); } - // Draw the display only if a previous draw is not still ongoing. -drawViewOnlyIfDirty: is guaranteed - // to be safe to call concurrently with -openGLView:updateToTime: so there's no need to worry about - // the above interrupting the below or vice versa. - if(!OSAtomicTestAndSet(drawingMask, &_updateIsOngoing)) - { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - [self drawViewOnlyIfDirty:YES]; - OSAtomicTestAndClear(drawingMask, &_updateIsOngoing); - }); - } + // Draw the display now regardless of other activity. + [self drawViewOnlyIfDirty:YES]; } - (void)invalidate diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index 94df2e3b6..e01ea78a5 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -108,6 +108,7 @@ namespace { OpenGLOutputBuilder::OpenGLOutputBuilder(unsigned int buffer_depth) : _output_mutex(new std::mutex), + _draw_mutex(new std::mutex), _visible_area(Rect(0, 0, 1, 1)), _composite_src_output_y(0), _cleared_composite_output_y(0), @@ -172,8 +173,8 @@ OpenGLOutputBuilder::~OpenGLOutputBuilder() void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) { - // lock down any further work on the current frame - _output_mutex->lock(); + // lock down any other draw_frames + _draw_mutex->lock(); // establish essentials if(!output_shader_program) @@ -195,21 +196,6 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glDeleteSync(_fence); } - // release the mapping, giving up on trying to draw if data has been lost - GLsizei submitted_output_data = submitArrayData(output_array_buffer, _output_buffer_data.get(), &_output_buffer_data_pointer); - - // bind and flush the source array buffer - GLsizei submitted_source_data = submitArrayData(source_array_buffer, _source_buffer_data.get(), &_source_buffer_data_pointer); - - // determine how many lines are newly reclaimed; they'll need to be cleared - Range clearing_zones[2]; - - // the clearing zones for the composite output Y are calculated with a fixed offset of '1' which has the effect of clearing - // one ahead of the expected drawing area this frame; that's because the current _composite_src_output_y may or may not have been - // written to during the last update, so we want it to have been cleared during the last update. - int number_of_clearing_zones = getCircularRanges(&_cleared_composite_output_y, &_composite_src_output_y, IntermediateBufferHeight, 1, 1, clearing_zones); - uint16_t completed_texture_y = _buffer_builder->get_and_finalise_current_line(); - // make sure there's a target to draw to if(!framebuffer || framebuffer->get_height() != output_height || framebuffer->get_width() != output_width) { @@ -228,6 +214,24 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out framebuffer = std::move(new_framebuffer); } + // lock out the machine emulation until data is copied + _output_mutex->lock(); + + // release the mapping, giving up on trying to draw if data has been lost + GLsizei submitted_output_data = submitArrayData(output_array_buffer, _output_buffer_data.get(), &_output_buffer_data_pointer); + + // bind and flush the source array buffer + GLsizei submitted_source_data = submitArrayData(source_array_buffer, _source_buffer_data.get(), &_source_buffer_data_pointer); + + // determine how many lines are newly reclaimed; they'll need to be cleared + Range clearing_zones[2]; + + // the clearing zones for the composite output Y are calculated with a fixed offset of '1' which has the effect of clearing + // one ahead of the expected drawing area this frame; that's because the current _composite_src_output_y may or may not have been + // written to during the last update, so we want it to have been cleared during the last update. + int number_of_clearing_zones = getCircularRanges(&_cleared_composite_output_y, &_composite_src_output_y, IntermediateBufferHeight, 1, 1, clearing_zones); + uint16_t completed_texture_y = _buffer_builder->get_and_finalise_current_line(); + // upload new source pixels if(completed_texture_y) { @@ -239,6 +243,9 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out _buffer_builder->get_image_pointer()); } + // data having been grabbed, allow the machine to continue + _output_mutex->unlock(); + struct RenderStage { OpenGL::TextureTarget *const target; OpenGL::Shader *const shader; @@ -330,7 +337,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out framebuffer->draw((float)output_width / (float)output_height); _fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - _output_mutex->unlock(); + _draw_mutex->unlock(); } void OpenGLOutputBuilder::reset_all_OpenGL_state() diff --git a/Outputs/CRT/Internals/CRTOpenGL.hpp b/Outputs/CRT/Internals/CRTOpenGL.hpp index e562767a8..4f0a79a32 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.hpp +++ b/Outputs/CRT/Internals/CRTOpenGL.hpp @@ -58,6 +58,7 @@ class OpenGLOutputBuilder { // the run and input data buffers std::unique_ptr _buffer_builder; std::unique_ptr _output_mutex; + std::unique_ptr _draw_mutex; // transient buffers indicating composite data not yet decoded GLsizei _composite_src_output_y, _cleared_composite_output_y; From 604862b20bd716b65ab93ad39ffcecec91c092ae Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 2 Jun 2016 22:36:52 -0400 Subject: [PATCH 53/53] Fixed playfield/missile and playfield/ball collisions. --- Machines/Atari2600/Atari2600.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 3271da44b..e479780f5 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -267,7 +267,7 @@ void Machine::setup_reported_collisions() _reportedCollisions[c][7] |= ((playerPixels[0] & playerPixels[1]) << 7); } - if(_playfieldOutput | ballPixel) { + if(playfieldPixel | ballPixel) { _reportedCollisions[c][4] |= ((playfieldPixel & missilePixels[0]) << 7) | ((ballPixel & missilePixels[0]) << 6); _reportedCollisions[c][5] |= ((playfieldPixel & missilePixels[1]) << 7) | ((ballPixel & missilePixels[1]) << 6);