1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-01-13 07:30:21 +00:00

Starts to reformulate TMS collection as coroutines.

For the time being, thereby breaks all video. A static screen of the border colour is all you'll see.
This commit is contained in:
Thomas Harte 2018-10-01 23:03:17 -04:00
parent cc99b0f532
commit 60bab8fdf1
2 changed files with 587 additions and 367 deletions

View File

@ -15,38 +15,6 @@ using namespace TI::TMS;
namespace {
const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
uint32_t result = 0;
uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
result_ptr[0] = r;
result_ptr[1] = g;
result_ptr[2] = b;
result_ptr[3] = 0;
return result;
}
const uint32_t palette[16] = {
palette_pack(0, 0, 0),
palette_pack(0, 0, 0),
palette_pack(33, 200, 66),
palette_pack(94, 220, 120),
palette_pack(84, 85, 237),
palette_pack(125, 118, 252),
palette_pack(212, 82, 77),
palette_pack(66, 235, 245),
palette_pack(252, 85, 84),
palette_pack(255, 121, 120),
palette_pack(212, 193, 84),
palette_pack(230, 206, 128),
palette_pack(33, 176, 59),
palette_pack(201, 91, 186),
palette_pack(204, 204, 204),
palette_pack(255, 255, 255)
};
const uint8_t StatusInterrupt = 0x80;
const uint8_t StatusFifthSprite = 0x40;
@ -120,7 +88,7 @@ Outputs::CRT::CRT *TMS9918::get_crt() {
}
void Base::test_sprite(int sprite_number, int screen_row) {
if(!(status_ & StatusFifthSprite)) {
/* if(!(status_ & StatusFifthSprite)) {
status_ = static_cast<uint8_t>((status_ & ~31) | sprite_number);
}
if(sprites_stopped_)
@ -145,11 +113,11 @@ void Base::test_sprite(int sprite_number, int screen_row) {
SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[active_sprite_slot];
sprite.index = sprite_number;
sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0);
sprite_sets_[active_sprite_set_].active_sprite_slot++;
sprite_sets_[active_sprite_set_].active_sprite_slot++;*/
}
void Base::get_sprite_contents(int field, int cycles_left, int screen_row) {
int sprite_id = field / 6;
/* int sprite_id = field / 6;
field %= 6;
while(true) {
@ -178,7 +146,7 @@ void Base::get_sprite_contents(int field, int cycles_left, int screen_row) {
if(!cycles_left) return;
field = 0;
sprite_id++;
}
}*/
}
void TMS9918::run_for(const HalfCycles cycles) {
@ -189,7 +157,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
// Keep a count of cycles separate from internal counts to avoid
// potential errors mapping back and forth.
half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(frame_lines_ * 228 * 2);
half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(mode_timing_.total_lines * 228 * 2);
// Convert 456 clocked half cycles per line to 342 internal cycles per line;
// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
@ -201,203 +169,114 @@ void TMS9918::run_for(const HalfCycles cycles) {
while(int_cycles) {
// Determine how much time has passed in the remainder of this line, and proceed.
int cycles_left = std::min(342 - column_, int_cycles);
const int cycles_left = std::min(342 - column_, int_cycles);
const int end_column = column_ + cycles_left;
// ------------------------
// Perform memory accesses.
// ------------------------
#define fetch(function) \
if(end_column < 171) { \
function<true>(column_, end_column);\
} else {\
function<false>(column_, end_column);\
}
// ------------------------------------
// Potentially perform a memory access.
// ------------------------------------
if(queued_access_ != MemoryAccess::None) {
int time_until_access_slot = 0;
switch(line_mode_) {
case LineMode::SMS:
time_until_access_slot = 0; // TODO.
break;
case LineMode::Refresh:
if(column_ < 53 || column_ >= 307) time_until_access_slot = column_&1;
else time_until_access_slot = 3 - ((column_ - 53)&3);
// i.e. 53 -> 3, 52 -> 2, 51 -> 1, 50 -> 0, etc
break;
case LineMode::Text:
if(column_ < 59 || column_ >= 299) time_until_access_slot = column_&1;
else time_until_access_slot = 5 - ((column_ + 3)%6);
// i.e. 59 -> 3, 60 -> 2, 61 -> 1, etc
break;
case LineMode::Character:
if(column_ < 9) time_until_access_slot = column_&1;
else if(column_ < 30) time_until_access_slot = 30 - column_;
else if(column_ < 37) time_until_access_slot = column_&1;
else if(column_ < 311) time_until_access_slot = 31 - ((column_ + 7)&31);
// i.e. 53 -> 3, 54 -> 2, 55 -> 1, 56 -> 0, 57 -> 31, etc
else if(column_ < 313) time_until_access_slot = column_&1;
else time_until_access_slot = 342 - column_;
break;
}
if(cycles_left >= time_until_access_slot) {
if(queued_access_ == MemoryAccess::Write) {
ram_[ram_pointer_ & 16383] = read_ahead_buffer_;
} else {
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
}
ram_pointer_++;
queued_access_ = MemoryAccess::None;
}
// TODO: column_ and end_column are in 342-per-line cycles;
// adjust them to a count of windows.
switch(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;
case LineMode::Refresh: fetch(fetch_tms_refresh); break;
}
column_ += cycles_left; // column_ is now the column that has been reached in this line.
int_cycles -= cycles_left; // Count down duration to run for.
// ------------------------------
// Perform video memory accesses.
// ------------------------------
if(((row_ < 192) || (row_ == frame_lines_-1)) && (current_mode_ != ScreenMode::Blank)) {
const int sprite_row = (row_ < 192) ? row_ : -1;
const int access_slot = column_ >> 1; // There are only 171 available memory accesses per line.
switch(line_mode_) {
default: break;
case LineMode::SMS:
if(access_slot < 171) {
fetch_sms<true>(access_pointer_ >> 1, access_slot);
} else {
fetch_sms<false>(access_pointer_ >> 1, access_slot);
}
access_pointer_ = column_;
break;
case LineMode::Text:
access_pointer_ = std::min(30, access_slot);
if(access_pointer_ >= 30 && access_pointer_ < 150) {
const size_t row_base = pattern_name_address_ + static_cast<size_t>(row_ >> 3) * 40;
const int end = std::min(150, access_slot);
// Pattern names are collected every third window starting from window 30.
const int pattern_names_start = (access_pointer_ - 30 + 2) / 3;
const int pattern_names_end = (end - 30 + 2) / 3;
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
// Patterns are collected every third window starting from window 32.
const int pattern_buffer_start = (access_pointer_ - 32 + 2) / 3;
const int pattern_buffer_end = (end - 32 + 2) / 3;
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
pattern_buffer_[column] = ram_[pattern_generator_table_address_ + (pattern_names_[column] << 3) + (row_ & 7)];
}
}
break;
case LineMode::Character:
// Four access windows: no collection.
if(access_pointer_ < 5)
access_pointer_ = std::min(5, access_slot);
// Then ten access windows are filled with collection of sprite 3 and 4 details.
if(access_pointer_ >= 5 && access_pointer_ < 15) {
int end = std::min(15, access_slot);
get_sprite_contents(access_pointer_ - 5 + 14, end - access_pointer_, sprite_row - 1);
access_pointer_ = std::min(15, access_slot);
}
// Four more access windows: no collection.
if(access_pointer_ >= 15 && access_pointer_ < 19) {
access_pointer_ = std::min(19, access_slot);
// Start new sprite set if this is location 19.
if(access_pointer_ == 19) {
active_sprite_set_ ^= 1;
sprite_sets_[active_sprite_set_].active_sprite_slot = 0;
sprites_stopped_ = false;
}
}
// Then eight access windows fetch the y position for the first eight sprites.
while(access_pointer_ < 27 && access_pointer_ < access_slot) {
test_sprite(access_pointer_ - 19, sprite_row);
access_pointer_++;
}
// The next 128 access slots are video and sprite collection interleaved.
if(access_pointer_ >= 27 && access_pointer_ < 155) {
int end = std::min(155, access_slot);
size_t row_base = pattern_name_address_;
size_t pattern_base = pattern_generator_table_address_;
size_t colour_base = colour_table_address_;
if(current_mode_ == ScreenMode::Graphics) {
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
pattern_base &= 0x2000 | ((row_ & 0xc0) << 5);
colour_base &= 0x2000 | ((row_ & 0xc0) << 5);
}
row_base += (row_ << 2)&~31;
// Pattern names are collected every fourth window starting from window 27.
const int pattern_names_start = (access_pointer_ - 27 + 3) >> 2;
const int pattern_names_end = (end - 27 + 3) >> 2;
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
// Colours are collected every fourth window starting from window 29.
const int colours_start = (access_pointer_ - 29 + 3) >> 2;
const int colours_end = (end - 29 + 3) >> 2;
if(current_mode_ != ScreenMode::Graphics) {
for(int column = colours_start; column < colours_end; ++column) {
colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] >> 3)];
}
} else {
for(int column = colours_start; column < colours_end; ++column) {
colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] << 3) + (row_ & 7)];
}
}
// Patterns are collected ever fourth window starting from window 30.
const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2;
const int pattern_buffer_end = (end - 30 + 3) >> 2;
// Multicolour mode uses a different function of row to pick bytes.
const int row = (current_mode_ != ScreenMode::MultiColour) ? (row_ & 7) : ((row_ >> 2) & 7);
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + row];
}
// Sprite slots occur in three quarters of ever fourth window starting from window 28.
const int sprite_start = (access_pointer_ - 28 + 3) >> 2;
const int sprite_end = (end - 28 + 3) >> 2;
for(int column = sprite_start; column < sprite_end; ++column) {
if(column&3) {
test_sprite(7 + column - (column >> 2), sprite_row);
}
}
access_pointer_ = std::min(155, access_slot);
}
// Two access windows: no collection.
if(access_pointer_ < 157)
access_pointer_ = std::min(157, access_slot);
// Fourteen access windows: collect initial sprite information.
if(access_pointer_ >= 157 && access_pointer_ < 171) {
int end = std::min(171, access_slot);
get_sprite_contents(access_pointer_ - 157, end - access_pointer_, sprite_row);
access_pointer_ = std::min(171, access_slot);
}
break;
}
}
// --------------------------
// End video memory accesses.
// --------------------------
#undef fetch
// --------------------
// Output video stream.
// --------------------
if(row_ < 192 && current_mode_ != ScreenMode::Blank) {
if(line_mode_ == LineMode::Refresh) {
if(row_ >= mode_timing_.first_vsync_line && row_ < mode_timing_.first_vsync_line+3) {
// Vertical sync.
if(column_ == 342) {
crt_->output_sync(342 * 4);
}
} else {
// Right border.
if(column_ < 15 && end_column >= 15) {
output_border(15);
}
// 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.
if(end_column == 342) {
output_border(342 - 73);
}
}
} else {
// Right border.
if(column_ < 15 && end_column >= 15) {
output_border(15);
}
// 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.
if(column_ < mode_timing_.first_pixel_output_column && end_column >= mode_timing_.first_pixel_output_column) {
output_border(mode_timing_.first_pixel_output_column - 73);
}
// In pixel area:
const int pixel_start = std::max(column_, mode_timing_.first_pixel_output_column);
const int pixel_end = std::min(end_column, mode_timing_.next_border_column);
if(pixel_end > pixel_start) {
output_border(pixel_end - pixel_start);
// switch(screen_mode_) {
// case ScreenMode::Text:
// break;
//
// case ScreenMode::ColouredText:
// case ScreenMode::Graphics:
// break;
//
// case ScreenMode::SMSMode4:
// break;
//
// default: break;
// }
}
// Additional right border, if called for.
if(mode_timing_.next_border_column != 342 && column_ == 342) {
output_border(342 - mode_timing_.next_border_column);
}
}
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.
/* if(row_ < 192 && current_mode_ != ScreenMode::Blank) {
// ----------------------
// Output horizontal sync
// ----------------------
@ -616,23 +495,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
output_border(column_ - output_column_);
output_column_ = column_;
}
} else if(row_ >= first_vsync_line_ && row_ < first_vsync_line_+3) {
// Vertical sync.
if(column_ == 342) {
crt_->output_sync(342 * 4);
}
} else {
// Blank.
if(!output_column_ && column_ >= 26) {
crt_->output_sync(13 * 4);
crt_->output_default_colour_burst(13 * 4);
output_column_ = 26;
}
if(output_column_ >= 26) {
output_border(column_ - output_column_);
output_column_ = column_;
}
}
} }*/
// -----------------
// End video stream.
// -----------------
@ -643,40 +506,46 @@ void TMS9918::run_for(const HalfCycles cycles) {
// Prepare for next line, potentially.
// -----------------------------------
if(column_ == 342) {
access_pointer_ = column_ = output_column_ = 0;
row_ = (row_ + 1) % frame_lines_;
if(row_ == 192) status_ |= StatusInterrupt;
column_ = 0;
row_ = (row_ + 1) % mode_timing_.total_lines;
if(row_ == mode_timing_.pixel_lines) status_ |= StatusInterrupt;
// Establish the output mode for the next line.
set_current_mode();
// Based on the output mode, pick a line mode.
switch(current_mode_) {
mode_timing_.first_pixel_output_column = 88;
mode_timing_.next_border_column = 344;
mode_timing_.maximum_visible_sprites = 4;
switch(screen_mode_) {
case ScreenMode::Text:
line_mode_ = LineMode::Text;
first_pixel_column_ = 69;
first_right_border_column_ = 309;
mode_timing_.first_pixel_output_column = 48;
mode_timing_.next_border_column = 168;
mode_timing_.maximum_visible_sprites = 8;
break;
case ScreenMode::SMSMode4:
line_mode_ = LineMode::SMS;
master_system_.next_column = 0;
first_pixel_column_ = 63;
first_right_border_column_ = 319;
break;
default:
line_mode_ = LineMode::Character;
first_pixel_column_ = 63;
first_right_border_column_ = 319;
break;
}
if((current_mode_ == ScreenMode::Blank) || (row_ >= 192 && row_ != frame_lines_-1)) line_mode_ = LineMode::Refresh;
if((screen_mode_ == ScreenMode::Blank) || (row_ >= mode_timing_.pixel_lines && row_ != mode_timing_.total_lines-1)) line_mode_ = LineMode::Refresh;
}
}
}
void Base::output_border(int cycles) {
pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
if(pixel_target_) *pixel_target_ = palette[background_colour_];
if(pixel_target_) {
if(is_sega_vdp(personality_)) {
*pixel_target_ = master_system_.colour_ram[16 + background_colour_];
} else {
*pixel_target_ = palette[background_colour_];
}
}
crt_->output_level(static_cast<unsigned int>(cycles) * 4);
}
@ -686,19 +555,9 @@ void TMS9918::set_register(int address, uint8_t value) {
if(!(address & 1)) {
write_phase_ = false;
if(master_system_.cram_is_selected) {
master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
static_cast<uint8_t>(((value >> 0) & 3) * 255 / 3),
static_cast<uint8_t>(((value >> 2) & 3) * 255 / 3),
static_cast<uint8_t>(((value >> 4) & 3) * 255 / 3)
);
++ram_pointer_;
// TODO: insert a CRAM dot here.
} else {
// Enqueue the write to occur at the next available slot.
read_ahead_buffer_ = value;
queued_access_ = MemoryAccess::Write;
}
// Enqueue the write to occur at the next available slot.
read_ahead_buffer_ = value;
queued_access_ = MemoryAccess::Write;
return;
}
@ -720,16 +579,14 @@ void TMS9918::set_register(int address, uint8_t value) {
case TI::TMS::SMSVDP:
if(value & 0x40) {
ram_pointer_ = static_cast<uint16_t>(low_write_ | (value << 8));
queued_access_ = MemoryAccess::Write;
master_system_.cram_is_selected = true;
queued_access_ = MemoryAccess::None;
return;
}
value &= 0xf;
break;
}
// printf("%02x to %d\n", low_write_, value);
// This is a write to a register.
switch(value) {
case 0:
@ -823,10 +680,11 @@ uint8_t TMS9918::get_register(int address) {
}
HalfCycles TMS9918::get_time_until_interrupt() {
// TODO: line interrupts.
if(!generate_interrupts_) return HalfCycles(-1);
if(get_interrupt_line()) return HalfCycles(0);
const int half_cycles_per_frame = frame_lines_ * 228 * 2;
const int half_cycles_per_frame = mode_timing_.total_lines * 228 * 2;
int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame;
return HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame);
}

View File

@ -29,26 +29,64 @@ enum Personality {
#define is_sega_vdp(x) x >= SMSVDP
class Base {
public:
static const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
uint32_t result = 0;
uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
result_ptr[0] = r;
result_ptr[1] = g;
result_ptr[2] = b;
result_ptr[3] = 0;
return result;
}
protected:
// The default TMS palette.
const uint32_t palette[16] = {
palette_pack(0, 0, 0),
palette_pack(0, 0, 0),
palette_pack(33, 200, 66),
palette_pack(94, 220, 120),
palette_pack(84, 85, 237),
palette_pack(125, 118, 252),
palette_pack(212, 82, 77),
palette_pack(66, 235, 245),
palette_pack(252, 85, 84),
palette_pack(255, 121, 120),
palette_pack(212, 193, 84),
palette_pack(230, 206, 128),
palette_pack(33, 176, 59),
palette_pack(201, 91, 186),
palette_pack(204, 204, 204),
palette_pack(255, 255, 255)
};
Base(Personality p);
Personality personality_;
std::unique_ptr<Outputs::CRT::CRT> crt_;
// Holds the contents of this VDP's connected DRAM.
std::vector<uint8_t> ram_;
// Holds the state of the DRAM/CRAM-access mechanism.
uint16_t ram_pointer_ = 0;
uint8_t read_ahead_buffer_ = 0;
enum class MemoryAccess {
Read, Write, None
} queued_access_ = MemoryAccess::None;
// Holds the main status register.
uint8_t status_ = 0;
bool write_phase_ = false;
uint8_t low_write_ = 0;
// Current state of programmer input.
bool write_phase_ = false; // Determines whether the VDP is expecting the low or high byte of a write.
uint8_t low_write_ = 0; // Buffers the low byte of a write.
// The various register flags.
// Various programmable flags.
bool mode1_enable_ = false;
bool mode2_enable_ = false;
bool mode3_enable_ = false;
@ -57,6 +95,7 @@ class Base {
bool sprites_magnified_ = false;
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;
@ -66,30 +105,102 @@ class Base {
uint8_t text_colour_ = 0;
uint8_t background_colour_ = 0;
// Internal mechanisms for position tracking.
HalfCycles half_cycles_into_frame_;
int column_ = 0, row_ = 0, output_column_ = 0;
int cycles_error_ = 0;
int access_pointer_ = 0;
// Internal storage for the pixel part of line serialisation.
uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr;
// A helper function to output the current border colour for
// the number of cycles supplied.
void output_border(int cycles);
// Vertical timing details.
int frame_lines_ = 262;
int first_vsync_line_ = 227;
// A struct to contain timing information for the current mode.
struct {
/*
Vertical layout:
// Horizontal selections.
Lines 0 to [pixel_lines]: standard data fetch and drawing will occur.
... to [first_vsync_line]: refresh fetches will occur and border will be output.
.. to [2.5 or 3 lines later]: vertical sync is output.
... to [total lines - 1]: refresh fetches will occur and border will be output.
... for one line: standard data fetch will occur, without drawing.
*/
int total_lines = 262;
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.
int maximum_visible_sprites = 4;
} mode_timing_;
// The line mode describes the proper timing diagram for the current line.
enum class LineMode {
Text,
Character,
Refresh,
SMS
} line_mode_ = LineMode::Text;
int first_pixel_column_, first_right_border_column_;
// 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];
// Extra information that affects the Master System output mode.
struct {
// Programmer-set flags.
bool vertical_scroll_lock = false;
bool horizontal_scroll_lock = false;
bool hide_left_column = false;
bool enable_line_interrupts = false;
bool shift_sprites_8px_left = false;
bool mode4_enable = false;
uint8_t horizontal_scroll = 0;
uint8_t vertical_scroll = 0;
// The Master System's additional colour RAM.
uint32_t colour_ram[32];
bool cram_is_selected = false;
// Temporary buffers for a line of Master System graphics.
struct {
size_t offset;
uint8_t flags;
} names[32];
uint8_t tile_graphics[32][4];
size_t next_column = 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;
@ -100,38 +211,14 @@ class Base {
int shift_position = 0;
} active_sprites[8];
int active_sprite_slot = 0;
} sprite_sets_[2];
int active_sprite_set_ = 0;
bool sprites_stopped_ = false;
int access_pointer_ = 0;
int active_sprite_slot = 0;
bool sprites_stopped_ = false;
} sprite_set_;
inline void test_sprite(int sprite_number, int screen_row);
inline void get_sprite_contents(int start, int cycles, int screen_row);
struct {
bool vertical_scroll_lock = false;
bool horizontal_scroll_lock = false;
bool hide_left_column = false;
bool enable_line_interrupts = false;
bool shift_sprites_8px_left = false;
bool mode4_enable = false;
uint32_t colour_ram[32];
struct {
size_t offset;
uint8_t flags;
} names[32];
uint8_t tile_graphics[32][4];
size_t next_column = 0;
uint8_t horizontal_scroll = 0;
uint8_t vertical_scroll = 0;
bool cram_is_selected = false;
} master_system_;
enum class ScreenMode {
Blank,
Text,
@ -139,50 +226,72 @@ class Base {
ColouredText,
Graphics,
SMSMode4
} current_mode_;
int height_ = 192;
} screen_mode_;
void set_current_mode() {
if(blank_display_) {
current_mode_ = ScreenMode::Blank;
screen_mode_ = ScreenMode::Blank;
return;
}
if(is_sega_vdp(personality_) && master_system_.mode4_enable) {
current_mode_ = ScreenMode::SMSMode4;
height_ = 192;
if(mode2_enable_ && mode1_enable_) height_ = 224;
if(mode2_enable_ && mode3_enable_) height_ = 240;
screen_mode_ = ScreenMode::SMSMode4;
mode_timing_.pixel_lines = 192;
if(mode2_enable_ && mode1_enable_) mode_timing_.pixel_lines = 224;
if(mode2_enable_ && mode3_enable_) mode_timing_.pixel_lines = 240;
mode_timing_.maximum_visible_sprites = 8;
return;
}
mode_timing_.maximum_visible_sprites = 8;
if(!mode1_enable_ && !mode2_enable_ && !mode3_enable_) {
current_mode_ = ScreenMode::ColouredText;
screen_mode_ = ScreenMode::ColouredText;
return;
}
if(mode1_enable_ && !mode2_enable_ && !mode3_enable_) {
current_mode_ = ScreenMode::Text;
screen_mode_ = ScreenMode::Text;
return;
}
if(!mode1_enable_ && mode2_enable_ && !mode3_enable_) {
current_mode_ = ScreenMode::Graphics;
screen_mode_ = ScreenMode::Graphics;
return;
}
if(!mode1_enable_ && !mode2_enable_ && mode3_enable_) {
current_mode_ = ScreenMode::MultiColour;
screen_mode_ = ScreenMode::MultiColour;
return;
}
// TODO: undocumented TMS modes.
current_mode_ = ScreenMode::Blank;
screen_mode_ = ScreenMode::Blank;
}
void external_slot() {
// TODO: write or read a value if one is queued and ready to read/write.
// (and, later: update the command engine, if this is an MSX2).
switch(queued_access_) {
default: return;
case MemoryAccess::Write:
if(master_system_.cram_is_selected) {
master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
static_cast<uint8_t>(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
static_cast<uint8_t>(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
static_cast<uint8_t>(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
);
} else {
ram_[ram_pointer_ & 16383] = read_ahead_buffer_;
}
break;
case MemoryAccess::Read:
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
break;
}
++ram_pointer_;
queued_access_ = MemoryAccess::None;
}
#define slot(n) \
@ -190,10 +299,282 @@ class Base {
case n
#define external_slot(n) \
slot(n): external_slot()
slot(n): 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);
/*
Fetching routines follow below; they obey the following rules:
1) input is a start position and an end position; they should perform the proper
operations for the period: start <= time < end.
2) times are measured relative to a 172-cycles-per-line clock (so: they directly
count access windows on the TMS and Master System).
3) time 0 is the beginning of the access window immediately after the last pattern/data
block fetch that would contribute to this line, in a normal 32-column mode. So:
* it's cycle 309 on Mattias' TMS diagram;
* it's cycle 1238 on his V9938 diagram;
* it's after the last background render block in Mask of Destiny's Master System timing diagram.
That division point was selected, albeit arbitrarily, because it puts all the tile
fetches for a single line into the same [0, 171] period.
4) all of these functions are templated with a `use_end` parameter. That will be true if
end is < 172, false otherwise. So functions can use it to eliminate should-exit-not checks,
for the more usual path of execution.
Provided for the benefit of the methods below:
* the function external_slot(), which will perform any pending VRAM read/write.
* the macros slot(n) and external_slot(n) which can be used to schedule those things inside a
switch(start)-based implementation.
All functions should just spool data to intermediary storage. This is because for most VDPs there is
a decoupling between fetch pattern and output pattern, and it's neater to keep the same division
for the exceptions.
*/
/***********************************************
TMS9918 Fetching Code
************************************************/
template<bool use_end> void fetch_tms_refresh(int start, int end) {
#define refresh(location) external_slot(location+1)
#define refreshes_2(location) \
refresh(location); \
refresh(location+2);
#define refreshes_4(location) \
refreshes_2(location); \
refreshes_2(location+4);
#define refreshes_8(location) \
refreshes_4(location); \
refreshes_4(location+8);
switch(start) {
default:
/* 44 external slots (= 44 windows) */
external_slots_32(0)
external_slots_8(32)
external_slots_4(40)
/* 64 refresh/external slot pairs (= 128 windows) */
refreshes_8(44);
refreshes_8(60);
refreshes_8(76);
refreshes_8(92);
refreshes_8(108);
refreshes_8(124);
refreshes_8(140);
refreshes_8(156);
return;
}
#undef refreshes_8
#undef refreshes_4
#undef refreshes_2
#undef refresh
}
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 + static_cast<size_t>(pattern_names_[column] << 3)];
#define fetch_column(location, column) \
fetch_tile_name(location, column); \
external_slot(location+1); \
fetch_tile_pattern(location+2, column);
#define fetch_columns_2(location, column) \
fetch_column(location, column); \
fetch_column(location+3, column+1);
#define fetch_columns_4(location, column) \
fetch_columns_2(location, column); \
fetch_columns_2(location+6, column+2);
#define fetch_columns_8(location, column) \
fetch_columns_4(location, column); \
fetch_columns_4(location+12, column+4);
const size_t row_base = pattern_name_address_ + static_cast<size_t>(row_ >> 3) * 40;
const size_t row_offset = pattern_generator_table_address_ + (row_ & 7);
switch(start) {
default:
/* 47 external slots (= 47 windows) */
external_slots_32(0)
external_slots_8(32)
external_slots_4(40)
external_slots_2(44)
external_slot(46)
/* 40 column fetches (= 120 windows) */
fetch_columns_8(47, 0);
fetch_columns_8(71, 8);
fetch_columns_8(95, 16);
fetch_columns_8(119, 24);
fetch_columns_8(143, 32);
/* 4 more external slots */
external_slots_4(167);
return;
}
#undef fetch_columns_8
#undef fetch_columns_4
#undef fetch_columns_2
#undef fetch_column
#undef fetch_tile_name
}
template<bool use_end> void fetch_tms_character(int start, int end) {
#define sprite_fetch_coordinates(location, sprite) \
slot(location): \
slot(location+1): \
#define sprite_fetch_graphics(location, sprite) \
slot(location): \
slot(location+1): \
slot(location+2): \
slot(location+3): \
#define sprite_fetch_block(location, sprite) \
slot(location): \
slot(location+1): \
slot(location+2): \
slot(location+3): \
slot(location+4): \
slot(location+5):
#define sprite_y_read(location, sprite) \
slot(location):
#define fetch_tile_name(column) pattern_names_[column] = ram_[row_base + column];
#define fetch_tile(column) {\
colour_buffer_[column] = ram_[colour_base + static_cast<size_t>((pattern_names_[column] << 3) >> colour_name_shift)]; \
pattern_buffer_[column] = ram_[pattern_base + static_cast<size_t>(pattern_names_[column] << 3)]; \
}
#define background_fetch_block(location, column) \
slot(location): fetch_tile_name(column) \
external_slot(location+1); \
slot(location+2): \
slot(location+3): fetch_tile(column) \
slot(location+4): fetch_tile_name(column+1) \
sprite_y_read(location+5, column+8); \
slot(location+6): \
slot(location+7): fetch_tile(column+1) \
slot(location+8): fetch_tile_name(column+2) \
sprite_y_read(location+9, column+9); \
slot(location+10): \
slot(location+11): fetch_tile(column+2) \
slot(location+12): fetch_tile_name(column+3) \
sprite_y_read(location+13, column+10); \
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);
size_t pattern_base = pattern_generator_table_address_;
size_t colour_base = colour_table_address_;
int colour_name_shift = 6;
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));
colour_base += static_cast<size_t>(row_ & 7);
colour_name_shift = 0;
}
if(screen_mode_ == ScreenMode::MultiColour) {
pattern_base += static_cast<size_t>((row_ >> 2) & 7);
} else {
pattern_base += static_cast<size_t>(row_ & 7);
}
switch(start) {
default:
external_slots_2(0);
sprite_fetch_block(2, 0);
sprite_fetch_block(8, 1);
sprite_fetch_coordinates(14, 2);
external_slots_4(16);
external_slot(20);
sprite_fetch_graphics(21, 2);
sprite_fetch_block(25, 3);
external_slots_4(31);
sprite_y_read(35, 0);
sprite_y_read(36, 1);
sprite_y_read(37, 2);
sprite_y_read(38, 3);
sprite_y_read(39, 4);
sprite_y_read(40, 5);
sprite_y_read(41, 6);
sprite_y_read(42, 7);
background_fetch_block(43, 0);
background_fetch_block(59, 4);
background_fetch_block(75, 8);
background_fetch_block(91, 12);
background_fetch_block(107, 16);
background_fetch_block(123, 20);
background_fetch_block(139, 24);
background_fetch_block(155, 28);
return;
}
#undef background_fetch_block
#undef fetch_tile
#undef fetch_tile_name
#undef sprite_y_read
#undef sprite_fetch_block
#undef sprite_fetch_graphics
#undef sprite_fetch_coordinates
}
/***********************************************
Master System Fetching Code
************************************************/
template<bool use_end> void fetch_sms(int start, int end) {
#define sprite_render_block(location, sprite) \
#define sprite_fetch_block(location, sprite) \
slot(location): \
slot(location+1): \
slot(location+2): \
@ -220,7 +601,8 @@ class Base {
*/
#define fetch_tile_name(column) {\
size_t address = pattern_address_base + ((column) << 1); \
const size_t scrolled_column = (column - (master_system_.horizontal_scroll >> 3)) & 0x1f;\
const size_t address = 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 \
@ -252,25 +634,6 @@ class Base {
slot(location+14): \
slot(location+15): fetch_tile(column+3)
/*
TODO: background_render_block should fetch:
- column n, name
(external slot)
- column n, tile graphic first word
- column n, tile graphic second word
- column n+1, name
(sprite y fetch)
- column n+1, tile graphic first word
- column n+1, tile graphic second word
- column n+2, name
(sprite y fetch)
- column n+2, tile graphic first word
- column n+2, tile graphic second word
- column n+3, name
(sprite y fetch)
- column n+3, tile graphic first word
- column n+3, tile graphic second word
*/
const int scrolled_row = (row_ + master_system_.vertical_scroll) % 224;
const size_t pattern_address_base = (pattern_name_address_ | size_t(0x3ff)) & static_cast<size_t>(((scrolled_row & ~7) << 3) | 0x3800);
@ -285,37 +648,36 @@ class Base {
switch(start) {
default:
sprite_render_block(0, 0);
sprite_render_block(6, 2);
external_slot(12);
external_slot(13);
external_slot(14);
external_slot(15);
external_slot(16);
sprite_render_block(17, 4);
sprite_render_block(23, 6);
external_slot(29);
external_slot(30);
sprite_y_read(31);
sprite_y_read(32);
sprite_y_read(33);
sprite_y_read(34);
external_slots_4(0);
sprite_fetch_block(4, 0);
sprite_fetch_block(10, 2);
external_slots_4(16);
external_slot(20);
sprite_fetch_block(21, 4);
sprite_fetch_block(27, 6);
external_slots_2(33);
sprite_y_read(35);
sprite_y_read(36);
sprite_y_read(37);
sprite_y_read(38);
background_render_block(39, 0);
background_render_block(55, 4);
background_render_block(71, 8);
background_render_block(87, 12);
background_render_block(103, 16);
background_render_block(119, 20);
background_render_block(135, 24);
background_render_block(151, 28);
external_slot(167);
external_slot(168);
external_slot(169);
external_slot(170);
sprite_y_read(39);
sprite_y_read(40);
sprite_y_read(41);
sprite_y_read(42);
background_render_block(43, 0);
background_render_block(59, 4);
background_render_block(75, 8);
background_render_block(91, 12);
background_render_block(107, 16);
background_render_block(123, 20);
background_render_block(139, 24);
background_render_block(156, 28);
return;
}