1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 23:52:26 +00:00

Makes a flawed attempt to reformulate this exactly as two separate processes on a common clock with an interchange buffer.

Specifically because closer inspection of the TMS modes shows it isn't quite valid to model output of one line as having fully completed prior to fetching of the next. So some sort of extra buffer is required. At which point it is most natural to continue with the logic that each fetch routine is oriented around the fetching process for a single line, and each output routine has the same view, suggesting separate read/write addresses.

Something is wrong though, as video data is being output too rapidly (I think) and with occasional sync issues (again: subject to investigation).
This commit is contained in:
Thomas Harte 2018-10-14 16:23:45 -04:00
parent 9e52ead09a
commit 6c09abc6cb
2 changed files with 407 additions and 344 deletions

View File

@ -68,6 +68,14 @@ Base::Base(Personality p) :
mode_timing_.end_of_frame_interrupt_position.column = 63;
mode_timing_.end_of_frame_interrupt_position.row = 193;
}
// Establish that output is 10 cycles after values have been read.
// This is definitely correct for the TMS; more research may be
// necessary for the other implemented VDPs.
read_pointer_.row = 0;
read_pointer_.column = 0;
write_pointer_.row = 0;
write_pointer_.column = 10; // i.e. 10 cycles ahead of the read pointer.
}
TMS9918::TMS9918(Personality p):
@ -94,45 +102,44 @@ Outputs::CRT::CRT *TMS9918::get_crt() {
return crt_.get();
}
void Base::reset_sprite_collection() {
sprite_set_.sprites_stopped = false;
sprite_set_.fetched_sprite_slot = sprite_set_.active_sprite_slot;
sprite_set_.active_sprite_slot = 0;
void Base::LineBuffer::reset_sprite_collection() {
sprites_stopped = false;
active_sprite_slot = 0;
for(int c = 0; c < sprite_set_.fetched_sprite_slot; ++c) {
sprite_set_.active_sprites[c].shift_position = 0;
for(int c = 0; c < 8; ++c) {
active_sprites[c].shift_position = 0;
}
}
void Base::posit_sprite(int sprite_number, int sprite_position, int screen_row) {
void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_position, int screen_row) {
if(!(status_ & StatusSpriteOverflow)) {
status_ = static_cast<uint8_t>((status_ & ~0x1f) | (sprite_number & 0x1f));
}
if(sprite_set_.sprites_stopped)
if(buffer.sprites_stopped)
return;
// const int sprite_position = ram_[sprite_attribute_table_address_ + static_cast<size_t>(sprite_number << 2)];
// A sprite Y of 208 means "don't scan the list any further".
if(mode_timing_.allow_sprite_terminator && sprite_position == 208) {
sprite_set_.sprites_stopped = true;
buffer.sprites_stopped = true;
return;
}
const int sprite_row = (screen_row - sprite_position)&255;
if(sprite_row < 0 || sprite_row >= sprite_height_) return;
if(sprite_set_.active_sprite_slot == mode_timing_.maximum_visible_sprites) {
if(buffer.active_sprite_slot == mode_timing_.maximum_visible_sprites) {
status_ |= StatusSpriteOverflow;
return;
}
SpriteSet::ActiveSprite &sprite = sprite_set_.active_sprites[sprite_set_.active_sprite_slot];
LineBuffer::ActiveSprite &sprite = buffer.active_sprites[buffer.active_sprite_slot];
sprite.index = sprite_number;
sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0);
++sprite_set_.active_sprite_slot;
++buffer.active_sprite_slot;
}
void Base::get_sprite_contents(int field, int cycles_left, int screen_row) {
//void Base::get_sprite_contents(int field, int cycles_left, int screen_row) {
/* int sprite_id = field / 6;
field %= 6;
@ -163,7 +170,7 @@ void Base::get_sprite_contents(int field, int cycles_left, int screen_row) {
field = 0;
sprite_id++;
}*/
}
//}
void TMS9918::run_for(const HalfCycles cycles) {
// As specific as I've been able to get:
@ -179,28 +186,38 @@ void TMS9918::run_for(const HalfCycles cycles) {
int_cycles >>= 2;
if(!int_cycles) return;
while(int_cycles) {
// Determine how much time has passed in the remainder of this line, and proceed.
const int cycles_left = std::min(342 - column_, int_cycles);
const int end_column = column_ + cycles_left;
// There are two intertwined processes here, 'writing' (which means writing to the
// line buffers, i.e. it's everything to do with collecting a line) and 'reading'
// (which means reading from the line buffers and generating video).
int write_cycles_pool = int_cycles;
int read_cycles_pool = int_cycles;
while(write_cycles_pool || read_cycles_pool) {
if(write_cycles_pool) {
// Determine how much writing to do.
const int write_cycles = std::min(342 - write_pointer_.column, write_cycles_pool);
const int end_column = write_pointer_.column + write_cycles;
LineBuffer &line_buffer = line_buffers_[0];//write_pointer_.row & 1];
// ---------------------------------------
// Latch scrolling position, if necessary.
// ---------------------------------------
if(column_ < 61 && end_column >= 61) {
if(!row_) {
if(write_pointer_.column < 61 && end_column >= 61) {
if(!write_pointer_.row) {
master_system_.latched_vertical_scroll = master_system_.vertical_scroll;
}
master_system_.latched_horizontal_scroll = master_system_.horizontal_scroll;
line_buffer.latched_horizontal_scroll = master_system_.horizontal_scroll;
}
// ------------------------
// Perform memory accesses.
// ------------------------
#define fetch(function) \
if(end_column < 171) { \
if(final_window < 171) { \
function<true>(first_window, final_window);\
} else {\
function<false>(first_window, final_window);\
@ -208,10 +225,10 @@ void TMS9918::run_for(const HalfCycles cycles) {
// column_ and end_column are in 342-per-line cycles;
// adjust them to a count of windows.
const int first_window = column_ >> 1;
const int first_window = write_pointer_.column >> 1;
const int final_window = end_column >> 1;
if(first_window != final_window) {
switch(line_mode_) {
switch(line_buffer.line_mode) {
case LineMode::Text: fetch(fetch_tms_text); break;
case LineMode::Character: fetch(fetch_tms_character); break;
case LineMode::SMS: fetch(fetch_sms); break;
@ -222,110 +239,16 @@ void TMS9918::run_for(const HalfCycles cycles) {
#undef fetch
// --------------------
// Output video stream.
// --------------------
#define intersect(left, right, code) \
{ \
const int start = std::max(column_, left); \
const int end = std::min(end_column, right); \
if(end > start) {\
code;\
}\
}
if(line_mode_ == LineMode::Refresh || row_ > mode_timing_.pixel_lines) {
if(row_ >= mode_timing_.first_vsync_line && row_ < mode_timing_.first_vsync_line+4) {
// Vertical sync.
if(end_column == 342) {
crt_->output_sync(342 * 4);
}
} else {
// Right border.
intersect(0, 15, output_border(end - start));
// Blanking region.
if(column_ < 73 && end_column >= 73) {
crt_->output_blank(8*4);
crt_->output_sync(26*4);
crt_->output_blank(2*4);
crt_->output_default_colour_burst(14*4);
crt_->output_blank(8*4);
}
// Most of line.
intersect(73, 342, output_border(end - start));
}
} else {
// Right border.
intersect(0, 15, output_border(end - start));
// Blanking region.
if(column_ < 73 && end_column >= 73) {
crt_->output_blank(8*4);
crt_->output_sync(26*4);
crt_->output_blank(2*4);
crt_->output_default_colour_burst(14*4);
crt_->output_blank(8*4);
}
// Left border.
intersect(73, mode_timing_.first_pixel_output_column, output_border(end - start));
// Pixel region.
intersect(
mode_timing_.first_pixel_output_column,
mode_timing_.next_border_column,
if(start == mode_timing_.first_pixel_output_column) {
pixel_origin_ = pixel_target_ = reinterpret_cast<uint32_t *>(
crt_->allocate_write_area(static_cast<unsigned int>(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column) + 8) // TODO: the +8 is really for the SMS only; make it optional.
);
}
if(pixel_target_) {
const int relative_start = start - mode_timing_.first_pixel_output_column;
const int relative_end = end - mode_timing_.first_pixel_output_column;
switch(line_mode_) {
case LineMode::SMS: draw_sms(relative_start, relative_end); break;
case LineMode::Character: draw_tms_character(relative_start, relative_end); break;
case LineMode::Text: draw_tms_text(relative_start, relative_end); break;
case LineMode::Refresh: break; /* Dealt with elsewhere. */
}
}
if(end == mode_timing_.next_border_column) {
const unsigned int length = static_cast<unsigned int>(mode_timing_.next_border_column - mode_timing_.first_pixel_output_column);
crt_->output_data(length * 4, length);
pixel_origin_ = pixel_target_ = nullptr;
}
);
// Additional right border, if called for.
if(mode_timing_.next_border_column != 342) {
intersect(mode_timing_.next_border_column, 342, output_border(end - start));
}
}
#undef intersect
// -----------------
// End video stream.
// -----------------
// -------------------------------
// Check for interrupt conditions.
// -------------------------------
if(column_ < mode_timing_.line_interrupt_position && end_column >= mode_timing_.line_interrupt_position) {
if(write_pointer_.column < mode_timing_.line_interrupt_position && end_column >= mode_timing_.line_interrupt_position) {
// The Sega VDP offers a decrementing counter for triggering line interrupts;
// it is reloaded either when it overflows or upon every non-pixel line after the first.
// It is otherwise decremented.
if(is_sega_vdp(personality_)) {
if(row_ >= 0 && row_ <= mode_timing_.pixel_lines) {
if(write_pointer_.row >= 0 && write_pointer_.row <= mode_timing_.pixel_lines) {
--line_interrupt_counter;
if(line_interrupt_counter == 0xff) {
line_interrupt_pending_ = true;
@ -341,8 +264,8 @@ void TMS9918::run_for(const HalfCycles cycles) {
}
if(
row_ == mode_timing_.end_of_frame_interrupt_position.row &&
column_ < mode_timing_.end_of_frame_interrupt_position.column &&
write_pointer_.row == mode_timing_.end_of_frame_interrupt_position.row &&
write_pointer_.column < mode_timing_.end_of_frame_interrupt_position.column &&
end_column >= mode_timing_.end_of_frame_interrupt_position.column
) {
status_ |= StatusInterrupt;
@ -353,42 +276,154 @@ void TMS9918::run_for(const HalfCycles cycles) {
// -------------
// Advance time.
// -------------
write_pointer_.column = end_column;
write_cycles_pool -= write_cycles;
column_ = end_column; // column_ is now the column that has been reached in this line.
int_cycles -= cycles_left; // Count down duration to run for.
// -----------------------------------
// Prepare for next line, potentially.
// -----------------------------------
if(column_ == 342) {
column_ = 0;
row_ = (row_ + 1) % mode_timing_.total_lines;
if(write_pointer_.column == 342) {
write_pointer_.column = 0;
write_pointer_.row = (write_pointer_.row + 1) % mode_timing_.total_lines;
line_buffer = line_buffers_[0];//write_pointer_.row & 1];
// Establish the output mode for the next line.
set_current_mode();
set_current_screen_mode();
// Based on the output mode, pick a line mode.
mode_timing_.first_pixel_output_column = 86;
mode_timing_.next_border_column = 342;
line_buffer.first_pixel_output_column = 86;
line_buffer.next_border_column = 342;
mode_timing_.maximum_visible_sprites = 4;
switch(screen_mode_) {
case ScreenMode::Text:
line_mode_ = LineMode::Text;
mode_timing_.first_pixel_output_column = 94;
mode_timing_.next_border_column = 334;
line_buffer.line_mode = LineMode::Text;
line_buffer.first_pixel_output_column = 94;
line_buffer.next_border_column = 334;
break;
case ScreenMode::SMSMode4:
line_mode_ = LineMode::SMS;
line_buffer.line_mode = LineMode::SMS;
mode_timing_.maximum_visible_sprites = 8;
break;
default:
line_mode_ = LineMode::Character;
line_buffer.line_mode = LineMode::Character;
break;
}
if((screen_mode_ == ScreenMode::Blank) || (row_ >= mode_timing_.pixel_lines && row_ != mode_timing_.total_lines-1)) line_mode_ = LineMode::Refresh;
if(
(screen_mode_ == ScreenMode::Blank) ||
(write_pointer_.row >= mode_timing_.pixel_lines && write_pointer_.row != mode_timing_.total_lines-1))
line_buffer.line_mode = LineMode::Refresh;
}
}
if(read_cycles_pool) {
// Determine how much time has passed in the remainder of this line, and proceed.
const int read_cycles = std::min(342 - read_pointer_.column, read_cycles_pool);
const int end_column = read_pointer_.column + read_cycles;
LineBuffer &line_buffer = line_buffers_[0];//read_pointer_.row & 1];
// --------------------
// Output video stream.
// --------------------
#define intersect(left, right, code) \
{ \
const int start = std::max(read_pointer_.column, left); \
const int end = std::min(end_column, right); \
if(end > start) {\
code;\
}\
}
if(line_buffer.line_mode == LineMode::Refresh) {
if(read_pointer_.row >= mode_timing_.first_vsync_line && read_pointer_.row < mode_timing_.first_vsync_line+4) {
// Vertical sync.
if(end_column == 342) {
crt_->output_sync(342 * 4);
}
} else {
// Right border.
intersect(0, 15, output_border(end - start));
// Blanking region; total length is 58 cycles,
// and 58+15 = 73. So output the lot when the
// cursor passes 73.
if(read_pointer_.column < 73 && end_column >= 73) {
crt_->output_blank(8*4);
crt_->output_sync(26*4);
crt_->output_blank(2*4);
crt_->output_default_colour_burst(14*4);
crt_->output_blank(8*4);
}
// Border colour for the rest of the line.
intersect(73, 342, output_border(end - start));
}
} else {
// Right border.
intersect(0, 15, output_border(end - start));
// Blanking region.
if(read_pointer_.column < 73 && end_column >= 73) {
crt_->output_blank(8*4);
crt_->output_sync(26*4);
crt_->output_blank(2*4);
crt_->output_default_colour_burst(14*4);
crt_->output_blank(8*4);
}
// Left border.
intersect(73, line_buffer.first_pixel_output_column, output_border(end - start));
// Pixel region.
intersect(
line_buffer.first_pixel_output_column,
line_buffer.next_border_column,
if(start == line_buffer.first_pixel_output_column) {
pixel_origin_ = pixel_target_ = reinterpret_cast<uint32_t *>(
crt_->allocate_write_area(static_cast<unsigned int>(line_buffer.next_border_column - line_buffer.first_pixel_output_column))
);
}
if(pixel_target_) {
const int relative_start = start - line_buffer.first_pixel_output_column;
const int relative_end = end - line_buffer.first_pixel_output_column;
switch(line_buffer.line_mode) {
case LineMode::SMS: draw_sms(relative_start, relative_end); break;
case LineMode::Character: draw_tms_character(relative_start, relative_end); break;
case LineMode::Text: draw_tms_text(relative_start, relative_end); break;
case LineMode::Refresh: break; /* Dealt with elsewhere. */
}
}
if(end == line_buffer.next_border_column) {
const unsigned int length = static_cast<unsigned int>(line_buffer.next_border_column - line_buffer.first_pixel_output_column);
crt_->output_data(length * 4, length);
pixel_origin_ = pixel_target_ = nullptr;
}
);
// Additional right border, if called for.
if(line_buffer.next_border_column != 342) {
intersect(line_buffer.next_border_column, 342, output_border(end - start));
}
}
#undef intersect
// -------------
// Advance time.
// -------------
read_pointer_.column = end_column;
read_cycles_pool -= read_cycles;
if(read_pointer_.column == 342) {
read_pointer_.column = 0;
read_pointer_.row = (read_pointer_.row + 1) % mode_timing_.total_lines;
}
}
}
}
@ -537,7 +572,10 @@ void TMS9918::set_register(int address, uint8_t value) {
uint8_t TMS9918::get_current_line() {
// Determine the row to return.
static const int row_change_position = 62; // This is the proper Master System value; substitute if any other VDPs turn out to have this functionality.
int source_row = (column_ < row_change_position) ? (row_ + mode_timing_.total_lines - 1)%mode_timing_.total_lines : row_;
int source_row =
(write_pointer_.column < row_change_position)
? (write_pointer_.row + mode_timing_.total_lines - 1)%mode_timing_.total_lines
: write_pointer_.row;
// This assumes NTSC 192-line. TODO: other modes.
if(source_row >= 0xdb) source_row -= 6;
@ -568,7 +606,7 @@ uint8_t TMS9918::get_latched_horizontal_counter() {
}
void TMS9918::latch_horizontal_counter() {
latched_column_ = column_;
latched_column_ = write_pointer_.column;
}
uint8_t TMS9918::get_register(int address) {
@ -603,7 +641,7 @@ HalfCycles TMS9918::get_time_until_interrupt() {
const int time_until_frame_interrupt =
(
((mode_timing_.end_of_frame_interrupt_position.row * 342) + mode_timing_.end_of_frame_interrupt_position.column + frame_length) -
((row_ * 342) + column_)
((write_pointer_.row * 342) + write_pointer_.column)
) % frame_length;
if(!enable_line_interrupts_) return half_cycles_before_internal_cycles(time_until_frame_interrupt);
@ -615,8 +653,8 @@ HalfCycles TMS9918::get_time_until_interrupt() {
// If there is still time for a line interrupt this frame, that'll be it;
// otherwise it'll be on the next frame, supposing there's ever time for
// it at all.
if(row_+line_interrupt_counter <= mode_timing_.pixel_lines) {
next_line_interrupt_row = row_+line_interrupt_counter;
if(write_pointer_.row+line_interrupt_counter <= mode_timing_.pixel_lines) {
next_line_interrupt_row = write_pointer_.row+line_interrupt_counter;
} else {
if(line_interrupt_target <= mode_timing_.pixel_lines)
next_line_interrupt_row = mode_timing_.total_lines + line_interrupt_target;
@ -633,9 +671,9 @@ HalfCycles TMS9918::get_time_until_interrupt() {
// Figure out the number of internal cycles until the next line interrupt, which is the amount
// of time to the next tick over and then next_line_interrupt_row - row_ lines further.
int local_cycles_until_next_tick = (mode_timing_.line_interrupt_position - column_ + 342) % 342;
int local_cycles_until_next_tick = (mode_timing_.line_interrupt_position - write_pointer_.column + 342) % 342;
if(!local_cycles_until_next_tick) local_cycles_until_next_tick += 342;
const int local_cycles_until_line_interrupt = local_cycles_until_next_tick + (next_line_interrupt_row - row_) * 342;
const int local_cycles_until_line_interrupt = local_cycles_until_next_tick + (next_line_interrupt_row - write_pointer_.row) * 342;
if(!generate_interrupts_) return half_cycles_before_internal_cycles(time_until_frame_interrupt);
@ -782,11 +820,12 @@ void Base::draw_tms_character(int start, int end) {
}
void Base::draw_tms_text(int start, int end) {
LineBuffer &line_buffer = line_buffers_[0];//read_pointer_.row & 1];
const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] };
const int shift = start % 6;
int byte_column = start / 6;
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
int pattern = reverse_table.map[line_buffer.patterns[byte_column][0]] >> shift;
int pixels_left = end - start;
int length = std::min(pixels_left, 6 - shift);
while(true) {
@ -800,11 +839,12 @@ void Base::draw_tms_text(int start, int end) {
if(!pixels_left) break;
length = std::min(6, pixels_left);
byte_column++;
pattern = reverse_table.map[pattern_buffer_[byte_column]];
pattern = reverse_table.map[line_buffer.patterns[byte_column][0]];
}
}
void Base::draw_sms(int start, int end) {
LineBuffer &line_buffer = line_buffers_[0];//read_pointer_.row & 1];
int colour_buffer[256];
/*
@ -812,15 +852,15 @@ void Base::draw_sms(int start, int end) {
*/
int tile_start = start, tile_end = end;
int tile_offset = start;
if(row_ >= 16 || !master_system_.horizontal_scroll_lock) {
for(int c = start; c < (master_system_.latched_horizontal_scroll & 7); ++c) {
if(read_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) {
for(int c = start; c < (line_buffer.latched_horizontal_scroll & 7); ++c) {
colour_buffer[c] = 16 + background_colour_;
++tile_offset;
}
// Remove the border area from that to which tiles will be drawn.
tile_start = std::max(start - (master_system_.latched_horizontal_scroll & 7), 0);
tile_end = std::max(end - (master_system_.latched_horizontal_scroll & 7), 0);
tile_start = std::max(start - (line_buffer.latched_horizontal_scroll & 7), 0);
tile_end = std::max(end - (line_buffer.latched_horizontal_scroll & 7), 0);
}
@ -838,15 +878,15 @@ void Base::draw_sms(int start, int end) {
int pixels_left = tile_end - tile_start;
int length = std::min(pixels_left, 8 - shift);
pattern = *reinterpret_cast<uint32_t *>(master_system_.tile_graphics[byte_column]);
if(master_system_.names[byte_column].flags&2)
pattern = *reinterpret_cast<const uint32_t *>(line_buffer.patterns[byte_column]);
if(line_buffer.names[byte_column].flags&2)
pattern >>= shift;
else
pattern <<= shift;
while(true) {
const int palette_offset = (master_system_.names[byte_column].flags&0x18) << 1;
if(master_system_.names[byte_column].flags&2) {
const int palette_offset = (line_buffer.names[byte_column].flags&0x18) << 1;
if(line_buffer.names[byte_column].flags&2) {
for(int c = 0; c < length; ++c) {
colour_buffer[tile_offset] =
((pattern_index[3] & 0x01) << 3) |
@ -875,21 +915,21 @@ void Base::draw_sms(int start, int end) {
length = std::min(8, pixels_left);
byte_column++;
pattern = *reinterpret_cast<uint32_t *>(master_system_.tile_graphics[byte_column]);
pattern = *reinterpret_cast<const uint32_t *>(line_buffer.patterns[byte_column]);
}
}
/*
Apply sprites (if any).
*/
if(sprite_set_.fetched_sprite_slot) {
if(line_buffer.active_sprite_slot) {
int sprite_buffer[256];
int sprite_collision = 0;
memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(int));
// Draw all sprites into the sprite buffer.
for(int index = sprite_set_.fetched_sprite_slot - 1; index >= 0; --index) {
SpriteSet::ActiveSprite &sprite = sprite_set_.active_sprites[index];
for(int index = line_buffer.active_sprite_slot - 1; index >= 0; --index) {
LineBuffer::ActiveSprite &sprite = line_buffer.active_sprites[index];
if(sprite.shift_position < 16) {
const int pixel_start = std::max(start, sprite.x);

View File

@ -97,20 +97,24 @@ class Base {
bool generate_interrupts_ = false;
int sprite_height_ = 8;
size_t pattern_name_address_ = 0;
size_t colour_table_address_ = 0;
size_t pattern_generator_table_address_ = 0;
size_t sprite_attribute_table_address_ = 0;
size_t sprite_generator_table_address_ = 0;
size_t pattern_name_address_ = 0; // i.e. address of the tile map.
size_t colour_table_address_ = 0; // address of the colour map (if applicable).
size_t pattern_generator_table_address_ = 0; // address of the tile contents.
size_t sprite_attribute_table_address_ = 0; // address of the sprite list.
size_t sprite_generator_table_address_ = 0; // address of the sprite contents.
uint8_t text_colour_ = 0;
uint8_t background_colour_ = 0;
// Internal mechanisms for position tracking.
int column_ = 0, row_ = 0, latched_column_ = 0;
// This implementation of this chip officially accepts a 3.58Mhz clock, but runs
// internally at 5.37Mhz. The following two help to maintain a lossless conversion
// from the one to the other.
int cycles_error_ = 0;
HalfCycles half_cycles_before_internal_cycles(int internal_cycles);
// Internal mechanisms for position tracking.
int latched_column_ = 0;
// A helper function to output the current border colour for
// the number of cycles supplied.
void output_border(int cycles);
@ -130,25 +134,6 @@ class Base {
int pixel_lines = 192;
int first_vsync_line = 227;
/*
Horizontal layout (on a 342-cycle clock):
15 cycles right border
58 cycles blanking & sync
13 cycles left border
... i.e. to cycle 86, then:
border up to first_pixel_output_column;
pixels up to next_border_column;
border up to the end.
e.g. standard 256-pixel modes will want to set
first_pixel_output_column = 86, next_border_column = 342.
*/
int first_pixel_output_column;
int next_border_column;
// Maximum number of sprite slots to populate;
// if sprites beyond this number should be visible
// then the appropriate status information will be set.
@ -172,18 +157,91 @@ class Base {
bool enable_line_interrupts_ = false;
bool line_interrupt_pending_ = false;
// The line mode describes the proper timing diagram for the current line.
// The screen mode is a necessary predecessor to picking the line mode,
// which is the thing latched per line.
enum class ScreenMode {
Blank,
Text,
MultiColour,
ColouredText,
Graphics,
SMSMode4
} screen_mode_;
enum class LineMode {
Text,
Character,
Refresh,
SMS
} line_mode_ = LineMode::Text;
};
// Temporary buffers collect a representation of this line prior to pixel serialisation.
uint8_t pattern_names_[40];
uint8_t pattern_buffer_[40];
uint8_t colour_buffer_[40];
struct LineBuffer {
// The line mode describes the proper timing diagram for this line.
LineMode line_mode = LineMode::Text;
// Holds the horizontal scroll position to apply to this line;
// of those VDPs currently implemented, affects the Master System only.
uint8_t latched_horizontal_scroll = 0;
// The names array holds pattern names, as an offset into memory, and
// potentially flags also.
struct {
size_t offset;
uint8_t flags;
} names[40];
// The patterns array holds tile patterns, corresponding 1:1 with names.
// Four bytes per pattern is the maximum required by any
// currently-implemented VDP.
uint8_t patterns[40][4];
/*
Horizontal layout (on a 342-cycle clock):
15 cycles right border
58 cycles blanking & sync
13 cycles left border
... i.e. to cycle 86, then:
border up to first_pixel_output_column;
pixels up to next_border_column;
border up to the end.
e.g. standard 256-pixel modes will want to set
first_pixel_output_column = 86, next_border_column = 342.
*/
int first_pixel_output_column;
int next_border_column;
// An active sprite is one that has been selected for composition onto
// this line.
struct ActiveSprite {
int index = 0; // The original in-table index of this sprite.
int row = 0; // The row of the sprite that should be drawn.
int x = 0; // The sprite's x position on screen.
uint8_t image[4]; // Up to four bytes of image information.
int shift_position = 0; // An offset representing how much of the image information has already been drawn.
} active_sprites[8];
int active_sprite_slot = 0; // A pointer to the slot into which a new active sprite will be deposited, if required.
bool sprites_stopped = false; // A special TMS feature is that a sentinel value can be used to prevent any further sprites
// being evaluated for display. This flag determines whether the sentinel has yet been reached.
void reset_sprite_collection();
} line_buffers_[2];
void posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_y, int screen_row);
// There is a delay between reading into the line buffer and outputting from there to the screen. That delay
// is observeable because reading time affects availability of memory accesses and therefore time in which
// to update sprites and tiles, but writing time affects when the palette is used and when the collision flag
// may end up being set. So the two processes are slightly decoupled. The end of reading one line may overlap
// with the beginning of writing the next, hence the two separate line buffers.
struct LineBufferPointer {
int row, column;
} read_pointer_, write_pointer_;
// Extra information that affects the Master System output mode.
struct {
@ -200,50 +258,12 @@ class Base {
uint32_t colour_ram[32];
bool cram_is_selected = false;
// Temporary buffers for a line of Master System graphics,
// and latched scrolling offsets.
struct {
size_t offset;
uint8_t flags;
} names[32];
uint8_t tile_graphics[32][4];
// Holds the vertical scroll position for this frame; this is latched
// once and cannot dynamically be changed until the next frame.
uint8_t latched_vertical_scroll = 0;
uint8_t latched_horizontal_scroll = 0;
} master_system_;
// Holds results of sprite data fetches that occur on this
// line. Therefore has to contain: up to four or eight sets
// of sprite data for this line, and its horizontal position,
// plus a growing list of which sprites are selected for
// the next line.
struct SpriteSet {
struct ActiveSprite {
int index = 0;
int row = 0;
uint8_t image[4];
int x = 0;
int shift_position = 0;
} active_sprites[8];
int active_sprite_slot = 0;
int fetched_sprite_slot = 0;
bool sprites_stopped = false;
} sprite_set_;
inline void reset_sprite_collection();
inline void posit_sprite(int sprite_number, int sprite_y, int screen_row);
inline void get_sprite_contents(int start, int cycles, int screen_row);
enum class ScreenMode {
Blank,
Text,
MultiColour,
ColouredText,
Graphics,
SMSMode4
} screen_mode_;
void set_current_mode() {
void set_current_screen_mode() {
if(blank_display_) {
screen_mode_ = ScreenMode::Blank;
return;
@ -283,7 +303,6 @@ class Base {
screen_mode_ = ScreenMode::Blank;
}
void do_external_slot() {
switch(queued_access_) {
default: return;
@ -307,36 +326,7 @@ class Base {
queued_access_ = MemoryAccess::None;
}
#define slot(n) \
if(use_end && end+1 == n) return;\
case n
#define external_slot(n) \
slot(n): do_external_slot();
#define external_slots_2(n) \
external_slot(n); \
external_slot(n+1);
#define external_slots_4(n) \
external_slots_2(n); \
external_slots_2(n+2);
#define external_slots_8(n) \
external_slots_4(n); \
external_slots_4(n+4);
#define external_slots_16(n) \
external_slots_8(n); \
external_slots_8(n+8);
#define external_slots_32(n) \
external_slots_16(n); \
external_slots_16(n+16);
/*
TODO: explain offset by four windows in data gathering.
Fetching routines follow below; they obey the following rules:
1) input is a start position and an end position; they should perform the proper
@ -368,13 +358,42 @@ class Base {
for the exceptions.
*/
#define slot(n) \
if(use_end && end+1 == n) return;\
case n
#define external_slot(n) \
slot(n): do_external_slot();
#define external_slots_2(n) \
external_slot(n); \
external_slot(n+1);
#define external_slots_4(n) \
external_slots_2(n); \
external_slots_2(n+2);
#define external_slots_8(n) \
external_slots_4(n); \
external_slots_4(n+4);
#define external_slots_16(n) \
external_slots_8(n); \
external_slots_8(n+8);
#define external_slots_32(n) \
external_slots_16(n); \
external_slots_16(n+16);
/***********************************************
TMS9918 Fetching Code
************************************************/
template<bool use_end> void fetch_tms_refresh(int start, int end) {
#define refresh(location) external_slot(location+1)
#define refresh(location) \
slot(location): \
external_slot(location+1);
#define refreshes_2(location) \
refresh(location); \
@ -389,9 +408,9 @@ class Base {
refreshes_4(location+8);
switch(start) {
default:
default: assert(false);
/* 44 external slots (= 44 windows) */
/* 44 external slots */
external_slots_32(0)
external_slots_8(32)
external_slots_4(40)
@ -416,8 +435,8 @@ class Base {
}
template<bool use_end> void fetch_tms_text(int start, int end) {
#define fetch_tile_name(location, column) slot(location): pattern_names_[column] = ram_[row_base + column];
#define fetch_tile_pattern(location, column) slot(location): pattern_buffer_[column] = ram_[row_offset + size_t(pattern_names_[column] << 3)];
#define fetch_tile_name(location, column) slot(location): line_buffer.names[column].offset = ram_[row_base + column];
#define fetch_tile_pattern(location, column) slot(location): line_buffer.patterns[column][0] = ram_[row_offset + size_t(line_buffer.names[column].offset << 3)];
#define fetch_column(location, column) \
fetch_tile_name(location, column); \
@ -436,11 +455,12 @@ class Base {
fetch_columns_4(location, column); \
fetch_columns_4(location+12, column+4);
const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast<size_t>(row_ >> 3) * 40);
const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (row_ & 7));
LineBuffer &line_buffer = line_buffers_[0];//write_pointer_.row & 1];
const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast<size_t>(write_pointer_.row >> 3) * 40);
const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7));
switch(start) {
default:
default: assert(false);
/* 47 external slots (= 47 windows) */
external_slots_32(0)
@ -456,8 +476,9 @@ class Base {
fetch_columns_8(119, 24);
fetch_columns_8(143, 32);
/* 4 more external slots */
/* 5 more external slots */
external_slots_4(167);
external_slot(171);
return;
}
@ -492,11 +513,11 @@ class Base {
#define sprite_y_read(location, sprite) \
slot(location):
#define fetch_tile_name(column) pattern_names_[column] = ram_[(row_base + column) & 0x3fff];
#define fetch_tile_name(column) line_buffer.names[column].offset = ram_[(row_base + column) & 0x3fff];
#define fetch_tile(column) {\
colour_buffer_[column] = ram_[(colour_base + static_cast<size_t>((pattern_names_[column] << 3) >> colour_name_shift)) & 0x3fff]; \
pattern_buffer_[column] = ram_[(pattern_base + static_cast<size_t>(pattern_names_[column] << 3)) & 0x3fff]; \
line_buffer.patterns[column][0] = ram_[(colour_base + static_cast<size_t>((line_buffer.names[column].offset << 3) >> colour_name_shift)) & 0x3fff]; \
line_buffer.patterns[column][1] = ram_[(pattern_base + static_cast<size_t>(line_buffer.names[column].offset << 3)) & 0x3fff]; \
}
#define background_fetch_block(location, column) \
@ -517,7 +538,8 @@ class Base {
slot(location+14): \
slot(location+15): fetch_tile(column+3)
const size_t row_base = pattern_name_address_ + static_cast<size_t>((row_ << 2)&~31);
LineBuffer &line_buffer = line_buffers_[0];//write_pointer_.row & 1];
const size_t row_base = pattern_name_address_ + static_cast<size_t>((write_pointer_.row << 2)&~31);
size_t pattern_base = pattern_generator_table_address_;
size_t colour_base = colour_table_address_;
@ -525,21 +547,22 @@ class Base {
if(screen_mode_ == ScreenMode::Graphics) {
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
pattern_base &= static_cast<size_t>(0x2000 | ((row_ & 0xc0) << 5));
colour_base &= static_cast<size_t>(0x2000 | ((row_ & 0xc0) << 5));
pattern_base &= static_cast<size_t>(0x2000 | ((write_pointer_.row & 0xc0) << 5));
colour_base &= static_cast<size_t>(0x2000 | ((write_pointer_.row & 0xc0) << 5));
colour_base += static_cast<size_t>(row_ & 7);
colour_base += static_cast<size_t>(write_pointer_.row & 7);
colour_name_shift = 0;
}
if(screen_mode_ == ScreenMode::MultiColour) {
pattern_base += static_cast<size_t>((row_ >> 2) & 7);
pattern_base += static_cast<size_t>((write_pointer_.row >> 2) & 7);
} else {
pattern_base += static_cast<size_t>(row_ & 7);
pattern_base += static_cast<size_t>(write_pointer_.row & 7);
}
switch(start) {
default:
default: assert(false);
external_slots_2(0);
sprite_fetch_block(2, 0);
@ -591,18 +614,18 @@ class Base {
template<bool use_end> void fetch_sms(int start, int end) {
#define sprite_fetch(sprite) {\
sprite_set_.active_sprites[sprite].x = \
line_buffer.active_sprites[sprite].x = \
ram_[\
sprite_attribute_table_address_ & size_t(0x3f80 | (sprite_set_.active_sprites[sprite].index << 1))\
sprite_attribute_table_address_ & size_t(0x3f80 | (line_buffer.active_sprites[sprite].index << 1))\
] - (master_system_.shift_sprites_8px_left ? 8 : 0); \
const uint8_t name = ram_[\
sprite_attribute_table_address_ & size_t(0x3f81 | (sprite_set_.active_sprites[sprite].index << 1))\
sprite_attribute_table_address_ & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 1))\
] & (sprites_16x16_ ? ~1 : ~0);\
const size_t graphic_location = sprite_generator_table_address_ & size_t(0x2000 | (name << 5) | (sprite_set_.active_sprites[sprite].row << 2)); \
sprite_set_.active_sprites[sprite].image[0] = ram_[graphic_location]; \
sprite_set_.active_sprites[sprite].image[1] = ram_[graphic_location+1]; \
sprite_set_.active_sprites[sprite].image[2] = ram_[graphic_location+2]; \
sprite_set_.active_sprites[sprite].image[3] = ram_[graphic_location+3]; \
const size_t graphic_location = sprite_generator_table_address_ & size_t(0x2000 | (name << 5) | (line_buffer.active_sprites[sprite].row << 2)); \
line_buffer.active_sprites[sprite].image[0] = ram_[graphic_location]; \
line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+1]; \
line_buffer.active_sprites[sprite].image[2] = ram_[graphic_location+2]; \
line_buffer.active_sprites[sprite].image[3] = ram_[graphic_location+3]; \
}
#define sprite_fetch_block(location, sprite) \
@ -617,23 +640,23 @@ class Base {
#define sprite_y_read(location, sprite) \
slot(location): \
posit_sprite(sprite, ram_[sprite_attribute_table_address_ & (sprite | 0x3f00)], row_); \
posit_sprite(sprite+1, ram_[sprite_attribute_table_address_ & ((sprite + 1) | 0x3f00)], row_); \
posit_sprite(line_buffer, sprite, ram_[sprite_attribute_table_address_ & (sprite | 0x3f00)], write_pointer_.row); \
posit_sprite(line_buffer, sprite+1, ram_[sprite_attribute_table_address_ & ((sprite + 1) | 0x3f00)], write_pointer_.row); \
#define fetch_tile_name(column, row_info) {\
const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\
const size_t address = row_info.pattern_address_base + (scrolled_column << 1); \
master_system_.names[column].flags = ram_[address+1]; \
master_system_.names[column].offset = static_cast<size_t>( \
(((master_system_.names[column].flags&1) << 8) | ram_[address]) << 5 \
) + row_info.sub_row[(master_system_.names[column].flags&4) >> 2]; \
line_buffer.names[column].flags = ram_[address+1]; \
line_buffer.names[column].offset = static_cast<size_t>( \
(((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5 \
) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2]; \
}
#define fetch_tile(column) \
master_system_.tile_graphics[column][0] = ram_[master_system_.names[column].offset]; \
master_system_.tile_graphics[column][1] = ram_[master_system_.names[column].offset+1]; \
master_system_.tile_graphics[column][2] = ram_[master_system_.names[column].offset+2]; \
master_system_.tile_graphics[column][3] = ram_[master_system_.names[column].offset+3];
line_buffer.patterns[column][0] = ram_[line_buffer.names[column].offset]; \
line_buffer.patterns[column][1] = ram_[line_buffer.names[column].offset+1]; \
line_buffer.patterns[column][2] = ram_[line_buffer.names[column].offset+2]; \
line_buffer.patterns[column][3] = ram_[line_buffer.names[column].offset+3];
#define background_fetch_block(location, column, sprite, row_info) \
slot(location): fetch_tile_name(column, row_info) \
@ -660,11 +683,12 @@ class Base {
slot(location+15): fetch_tile(column+3)
// Determine the coarse horizontal scrolling offset; this isn't applied on the first two lines if the programmer has requested it.
const int horizontal_offset = (row_ >= 16 || !master_system_.horizontal_scroll_lock) ? (master_system_.latched_horizontal_scroll >> 3) : 0;
LineBuffer &line_buffer = line_buffers_[0];//write_pointer_ .row & 1];
const int horizontal_offset = (write_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) ? (line_buffer.latched_horizontal_scroll >> 3) : 0;
// Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't.
// The programmer can opt out of applying vertical scrolling to the right-hand portion of the display.
const int scrolled_row = (row_ + master_system_.latched_vertical_scroll) % 224;
const int scrolled_row = (write_pointer_.row + master_system_.latched_vertical_scroll) % 224;
struct RowInfo {
size_t pattern_address_base;
size_t sub_row[2];
@ -675,15 +699,14 @@ class Base {
};
RowInfo row_info;
if(master_system_.vertical_scroll_lock) {
row_info.pattern_address_base = pattern_name_address_ & static_cast<size_t>(((row_ & ~7) << 3) | 0x3800);
row_info.sub_row[0] = size_t((row_ & 7) << 2);
row_info.sub_row[1] = 28 ^ size_t((row_ & 7) << 2);
row_info.pattern_address_base = pattern_name_address_ & static_cast<size_t>(((write_pointer_.row & ~7) << 3) | 0x3800);
row_info.sub_row[0] = size_t((write_pointer_.row & 7) << 2);
row_info.sub_row[1] = 28 ^ size_t((write_pointer_.row & 7) << 2);
} else row_info = scrolled_row_info;
// ... and do the actual fetching, which follows this routine:
switch(start) {
default:
assert(false);
default: assert(false);
sprite_fetch_block(0, 0);
sprite_fetch_block(6, 2);
@ -695,7 +718,7 @@ class Base {
sprite_fetch_block(23, 6);
slot(29):
reset_sprite_collection();
line_buffer.reset_sprite_collection();
do_external_slot();
external_slot(30);