From 48ddd3c497f1019b6e4aa0fc9aa938850c50ae22 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 21 Jan 2016 21:17:47 -0500 Subject: [PATCH] Set about documenting the CRT; while doing so decided to add an optional clock divider for input; having done so decided to try to exploit it with the Electron. --- Machines/Atari2600/Atari2600.cpp | 2 +- Machines/Electron/Electron.cpp | 75 +++++++++++++++++++++++--------- Machines/Electron/Electron.hpp | 3 +- Outputs/CRT.cpp | 14 +++--- Outputs/CRT.hpp | 40 ++++++++++++++++- 5 files changed, 103 insertions(+), 31 deletions(-) diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 543e7a406..2450d9889 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -223,7 +223,7 @@ void Machine::output_pixels(unsigned int count) { case OutputState::Blank: _crt->output_blank(_lastOutputStateDuration); break; case OutputState::Sync: _crt->output_sync(_lastOutputStateDuration); break; - case OutputState::Pixel: _crt->output_data(_lastOutputStateDuration); break; + case OutputState::Pixel: _crt->output_data(_lastOutputStateDuration, 1); break; } _lastOutputStateDuration = 0; _lastOutputState = state; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index c26609cfa..37480da1b 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -12,10 +12,10 @@ using namespace Electron; -static const int cycles_per_line = 128; -static const int cycles_per_frame = 312*cycles_per_line; -static const int crt_cycles_multiplier = 8; -static const int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; +static const unsigned int cycles_per_line = 128; +static const unsigned int cycles_per_frame = 312*cycles_per_line; +static const unsigned int crt_cycles_multiplier = 8; +static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; Machine::Machine() : _interruptControl(0), @@ -459,8 +459,15 @@ inline void Machine::update_display() _crt->output_blank(15 * crt_cycles_multiplier); _displayOutputPosition += 15; - _crt->allocate_write_area(80 * crt_cycles_multiplier); - _currentLine = (uint8_t *)_crt->get_write_target_for_buffer(0); + switch(_screenMode) + { + case 0: case 3: _currentOutputDivider = 1; break; + case 1: case 4: case 6: _currentOutputDivider = 2; break; + case 2: case 5: _currentOutputDivider = 4; break; + } + + _crt->allocate_write_area(80 * crt_cycles_multiplier / _currentOutputDivider); + _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); if(current_line == first_graphics_line) _startLineAddress = _startScreenAddress; @@ -469,6 +476,32 @@ inline void Machine::update_display() if(line_position >= 24 && line_position < 104) { + unsigned int newDivider = 0; + switch(_screenMode) + { + case 0: case 3: newDivider = 1; break; + case 1: case 4: case 6: newDivider = 2; break; + case 2: case 5: newDivider = 4; break; + } + if(newDivider != _currentOutputDivider) + { + _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider * crt_cycles_multiplier), _currentOutputDivider); + _currentOutputDivider = newDivider; + _crt->allocate_write_area((int)((104 - (unsigned int)line_position) * crt_cycles_multiplier / _currentOutputDivider)); + _currentLine = _writePointer = (uint8_t *)_crt->get_write_target_for_buffer(0); + } + + // TODO: determine whether we need to change divider +// int pixels_to_output = std::max(_frameCycles - _displayOutputPosition, 104 - line_position); +// if(_screenMode >= 4) +// { +// // just shifting wouldn't be enough if both +// if(_displayOutputPosition&1) pixels_to_output++; +// pixels_to_output >>= 1; +// } +// +// swi + if(_currentLine && ((_screenMode < 4) || !(line_position&1))) { if(_currentScreenAddress&32768) @@ -477,7 +510,6 @@ inline void Machine::update_display() } uint8_t pixels = _ram[_currentScreenAddress]; _currentScreenAddress = _currentScreenAddress+8; - int output_ptr = (line_position - 24) << 3; switch(_screenMode) { @@ -486,49 +518,52 @@ inline void Machine::update_display() for(int c = 0; c < 8; c++) { uint8_t colour = (pixels&0x80) >> 4; - _currentLine[output_ptr + c] = _palette[colour]; + _writePointer[c] = _palette[colour]; pixels <<= 1; } + _writePointer += 8; break; case 1: - for(int c = 0; c < 8; c += 2) + for(int c = 0; c < 4; c ++) { uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x08) >> 2); - _currentLine[output_ptr + c + 0] = _currentLine[output_ptr + c + 1] = _palette[colour]; + _writePointer[c] = _palette[colour]; pixels <<= 1; } + _writePointer += 4; break; case 2: - for(int c = 0; c < 8; c += 4) + for(int c = 0; c < 2; c ++) { uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x20) >> 3) | ((pixels&0x08) >> 2) | ((pixels&0x02) >> 1); - _currentLine[output_ptr + c + 0] = _currentLine[output_ptr + c + 1] = - _currentLine[output_ptr + c + 2] = _currentLine[output_ptr + c + 3] = _palette[colour]; + _writePointer[c] = _palette[colour]; pixels <<= 1; } + _writePointer += 2; break; case 5: - for(int c = 0; c < 16; c += 4) + for(int c = 0; c < 4; c ++) { uint8_t colour = ((pixels&0x80) >> 4) | ((pixels&0x08) >> 2); - _currentLine[output_ptr + c + 0] = _currentLine[output_ptr + c + 1] = - _currentLine[output_ptr + c + 2] = _currentLine[output_ptr + c + 3] = _palette[colour]; + _writePointer[c] = _palette[colour]; pixels <<= 1; } + _writePointer += 4; break; default: case 4: case 6: - for(int c = 0; c < 16; c += 2) + for(int c = 0; c < 8; c ++) { uint8_t colour = (pixels&0x80) >> 4; - _currentLine[output_ptr + c] = _currentLine[output_ptr + c + 1] = _palette[colour]; + _writePointer[c] = _palette[colour]; pixels <<= 1; } + _writePointer += 8; break; } } @@ -545,10 +580,10 @@ inline void Machine::update_display() else _startLineAddress++; - _currentLine = nullptr; - _crt->output_data(80 * crt_cycles_multiplier); + _crt->output_data((unsigned int)((_writePointer - _currentLine) * _currentOutputDivider), _currentOutputDivider); _crt->output_blank(24 * crt_cycles_multiplier); _displayOutputPosition += 24; + _currentLine = nullptr; } } } diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 55b9cc375..ae711ed52 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -110,7 +110,8 @@ class Machine: public CPU6502::Processor { // Display generation. uint16_t _startLineAddress, _currentScreenAddress; int _currentOutputLine; - uint8_t *_currentLine; + unsigned int _currentOutputDivider; + uint8_t *_currentLine, *_writePointer; // Tape. struct Tape { diff --git a/Outputs/CRT.cpp b/Outputs/CRT.cpp index ffef2e553..307be6caa 100644 --- a/Outputs/CRT.cpp +++ b/Outputs/CRT.cpp @@ -175,7 +175,7 @@ CRT::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsi return proposedEvent; } -void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type) +void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, const bool vsync_charging, const Type type) { number_of_cycles *= _time_multiplier; @@ -252,7 +252,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo position_y(5) = InternalToUInt16(kCRTFixedPointOffset + _rasterPosition.y + _beamWidth[lengthMask].y); // if this is a data run then advance the buffer pointer - if(type == Type::Data) tex_x += next_run_length / _time_multiplier; + if(type == Type::Data) tex_x += next_run_length / (_time_multiplier * source_divider); // if this is a data or level run then store the end point tex_x(2) = tex_x(3) = tex_x(5) = tex_x; @@ -356,28 +356,28 @@ void CRT::output_sync(unsigned int number_of_cycles) { bool _hsync_requested = !_is_receiving_sync; // ensure this really is edge triggered; someone calling output_sync twice in succession shouldn't trigger it twice _is_receiving_sync = true; - advance_cycles(number_of_cycles, _hsync_requested, false, true, Type::Sync); + advance_cycles(number_of_cycles, 1, _hsync_requested, false, true, Type::Sync); } void CRT::output_blank(unsigned int number_of_cycles) { bool _vsync_requested = _is_receiving_sync; _is_receiving_sync = false; - advance_cycles(number_of_cycles, false, _vsync_requested, false, Type::Blank); + advance_cycles(number_of_cycles, 1, false, _vsync_requested, false, Type::Blank); } void CRT::output_level(unsigned int number_of_cycles) { bool _vsync_requested = _is_receiving_sync; _is_receiving_sync = false; - advance_cycles(number_of_cycles, false, _vsync_requested, false, Type::Level); + advance_cycles(number_of_cycles, 1, false, _vsync_requested, false, Type::Level); } -void CRT::output_data(unsigned int number_of_cycles) +void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) { bool _vsync_requested = _is_receiving_sync; _is_receiving_sync = false; - advance_cycles(number_of_cycles, false, _vsync_requested, false, Type::Data); + advance_cycles(number_of_cycles, source_divider, false, _vsync_requested, false, Type::Data); } #pragma mark - Buffer supply diff --git a/Outputs/CRT.hpp b/Outputs/CRT.hpp index 89e981da1..af87c4f29 100644 --- a/Outputs/CRT.hpp +++ b/Outputs/CRT.hpp @@ -53,10 +53,46 @@ class CRT { void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display); + /*! Output at the sync level. + + @param number_of_cycles The amount of time to putput sync for. + */ void output_sync(unsigned int number_of_cycles); + + /*! Output at the blanking level. + + @param number_of_cycles The amount of time to putput the blanking level for. + */ void output_blank(unsigned int number_of_cycles); + + /*! Outputs the first written to the most-recently created run of data repeatedly for a prolonged period. + + @param number_of_cycles The number of cycles to repeat the output for. + */ void output_level(unsigned int number_of_cycles); - void output_data(unsigned int number_of_cycles); + + /*! Declares that the caller has created a run of data via @c allocate_write_area and @c get_write_target_for_buffer + that is at least @c number_of_cycles long, and that the first @c number_of_cycles/source_divider should be spread + over that amount of time. + + @param number_of_cycles The amount of data to output. + + @param source_divider A divider for source data; if the divider is 1 then one source pixel is output every cycle, + if it is 2 then one source pixel covers two cycles; if it is n then one source pixel covers n cycles. + */ + void output_data(unsigned int number_of_cycles, unsigned int source_divider); + + /*! Outputs a colour burst. + + @param number_of_cycles The length of the colour burst. + + @param phase The initial phase of the colour burst in a measuring system with 256 units + per circle, e.g. 0 = 0 degrees, 128 = 180 degrees, 256 = 360 degree. + + @param magnitude The magnitude of the colour burst in 1/256ths of the magnitude of the + positive portion of the wave. + */ + void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t magnitude); class Delegate { public: @@ -110,7 +146,7 @@ class CRT { enum Type { Sync, Level, Data, Blank } type; - void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, bool vsync_charging, Type type); + void advance_cycles(unsigned int number_of_cycles, unsigned int source_divider, bool hsync_requested, bool vsync_requested, bool vsync_charging, Type type); // the inner entry point that determines whether and when the next sync event will occur within // the current output window