mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-23 03:32:32 +00:00
Merge pull request #576 from TomHarte/CRAMDots
Adds display of CRAM dots and enforces VRAM delays.
This commit is contained in:
commit
1fc9356796
@ -73,11 +73,9 @@ Base::Base(Personality p) :
|
||||
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.
|
||||
// Establish that output is delayed after reading by `output_lag` cycles.
|
||||
read_pointer_.row = 0;
|
||||
read_pointer_.column = 342 - 11; // i.e. 11 cycles behind the write pointer.
|
||||
read_pointer_.column = 342 - output_lag;
|
||||
write_pointer_.row = 1;
|
||||
write_pointer_.column = 0;
|
||||
}
|
||||
@ -93,7 +91,6 @@ TMS9918::TMS9918(Personality p):
|
||||
"}");
|
||||
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
|
||||
crt_->set_input_gamma(2.8f);
|
||||
|
||||
// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
|
||||
// intended to produce the correct relationship between the hard edges between pixels and
|
||||
@ -187,6 +184,9 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
const int end_column = write_pointer_.column + write_cycles;
|
||||
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
|
||||
|
||||
// Determine what this does to any enqueued VRAM access.
|
||||
minimum_access_column_ = write_pointer_.column + cycles_until_access_;
|
||||
cycles_until_access_ -= write_cycles;
|
||||
|
||||
|
||||
// ---------------------------------------
|
||||
@ -321,19 +321,34 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
|
||||
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_[read_pointer_.row];
|
||||
const int target_read_cycles = std::min(342 - read_pointer_.column, read_cycles_pool);
|
||||
int read_cycles_performed = 0;
|
||||
uint32_t next_cram_value = 0;
|
||||
|
||||
while(read_cycles_performed < target_read_cycles) {
|
||||
const uint32_t cram_value = next_cram_value;
|
||||
next_cram_value = 0;
|
||||
int read_cycles = target_read_cycles - read_cycles_performed;
|
||||
if(!upcoming_cram_dots_.empty() && upcoming_cram_dots_.front().location.row == read_pointer_.row) {
|
||||
int time_until_dot = upcoming_cram_dots_.front().location.column - read_pointer_.column;
|
||||
|
||||
if(time_until_dot < read_cycles) {
|
||||
read_cycles = time_until_dot;
|
||||
next_cram_value = upcoming_cram_dots_.front().value;
|
||||
upcoming_cram_dots_.erase(upcoming_cram_dots_.begin());
|
||||
}
|
||||
}
|
||||
|
||||
if(!read_cycles) continue;
|
||||
read_cycles_performed += read_cycles;
|
||||
|
||||
const int end_column = read_pointer_.column + read_cycles;
|
||||
LineBuffer &line_buffer = line_buffers_[read_pointer_.row];
|
||||
|
||||
|
||||
// TODO: actually perform these dots, at least in part by further subdividing
|
||||
// the period to run for.
|
||||
upcoming_cram_dots_.clear();
|
||||
|
||||
|
||||
// --------------------
|
||||
// Output video stream.
|
||||
// --------------------
|
||||
// --------------------
|
||||
// Output video stream.
|
||||
// --------------------
|
||||
|
||||
#define intersect(left, right, code) \
|
||||
{ \
|
||||
@ -344,21 +359,37 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
}\
|
||||
}
|
||||
|
||||
#define border(left, right) intersect(left, right, output_border(end - start))
|
||||
#define border(left, right) intersect(left, right, output_border(end - start, cram_value))
|
||||
|
||||
if(line_buffer.line_mode == LineMode::Refresh || read_pointer_.row > mode_timing_.pixel_lines) {
|
||||
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);
|
||||
if(line_buffer.line_mode == LineMode::Refresh || read_pointer_.row > mode_timing_.pixel_lines) {
|
||||
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.
|
||||
border(0, 15);
|
||||
|
||||
// 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.
|
||||
border(73, 342);
|
||||
}
|
||||
} else {
|
||||
// Right border.
|
||||
border(0, 15);
|
||||
|
||||
// Blanking region; total length is 58 cycles,
|
||||
// and 58+15 = 73. So output the lot when the
|
||||
// cursor passes 73.
|
||||
// Blanking region.
|
||||
if(read_pointer_.column < 73 && end_column >= 73) {
|
||||
crt_->output_blank(8*4);
|
||||
crt_->output_sync(26*4);
|
||||
@ -367,73 +398,58 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
crt_->output_blank(8*4);
|
||||
}
|
||||
|
||||
// Border colour for the rest of the line.
|
||||
border(73, 342);
|
||||
}
|
||||
} else {
|
||||
// Right border.
|
||||
border(0, 15);
|
||||
// Left border.
|
||||
border(73, line_buffer.first_pixel_output_column);
|
||||
|
||||
// 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.
|
||||
border(73, line_buffer.first_pixel_output_column);
|
||||
|
||||
// Pixel region.
|
||||
intersect(
|
||||
line_buffer.first_pixel_output_column,
|
||||
line_buffer.next_border_column,
|
||||
if(!asked_for_write_area_) {
|
||||
asked_for_write_area_ = true;
|
||||
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, 0); 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. */
|
||||
// Pixel region.
|
||||
intersect(
|
||||
line_buffer.first_pixel_output_column,
|
||||
line_buffer.next_border_column,
|
||||
if(!asked_for_write_area_) {
|
||||
asked_for_write_area_ = true;
|
||||
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(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;
|
||||
asked_for_write_area_ = false;
|
||||
}
|
||||
);
|
||||
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, cram_value); break;
|
||||
case LineMode::Character: draw_tms_character(relative_start, relative_end); break;
|
||||
case LineMode::Text: draw_tms_text(relative_start, relative_end); break;
|
||||
|
||||
// Additional right border, if called for.
|
||||
if(line_buffer.next_border_column != 342) {
|
||||
border(line_buffer.next_border_column, 342);
|
||||
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;
|
||||
asked_for_write_area_ = false;
|
||||
}
|
||||
);
|
||||
|
||||
// Additional right border, if called for.
|
||||
if(line_buffer.next_border_column != 342) {
|
||||
border(line_buffer.next_border_column, 342);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef border
|
||||
#undef intersect
|
||||
|
||||
|
||||
|
||||
// -------------
|
||||
// Advance time.
|
||||
// -------------
|
||||
read_pointer_.column = end_column;
|
||||
read_cycles_pool -= read_cycles;
|
||||
// -------------
|
||||
// Advance time.
|
||||
// -------------
|
||||
read_pointer_.column = end_column;
|
||||
}
|
||||
|
||||
read_cycles_pool -= target_read_cycles;
|
||||
if(read_pointer_.column == 342) {
|
||||
read_pointer_.column = 0;
|
||||
read_pointer_.row = (read_pointer_.row + 1) % mode_timing_.total_lines;
|
||||
@ -444,16 +460,25 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
void Base::output_border(int cycles) {
|
||||
uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
|
||||
if(pixel_target) {
|
||||
if(is_sega_vdp(personality_)) {
|
||||
*pixel_target = master_system_.colour_ram[16 + background_colour_];
|
||||
} else {
|
||||
*pixel_target = palette[background_colour_];
|
||||
}
|
||||
void Base::output_border(int cycles, uint32_t cram_dot) {
|
||||
cycles *= 4;
|
||||
uint32_t border_colour =
|
||||
is_sega_vdp(personality_) ?
|
||||
master_system_.colour_ram[16 + background_colour_] :
|
||||
palette[background_colour_];
|
||||
|
||||
if(cram_dot) {
|
||||
uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
|
||||
*pixel_target = border_colour | cram_dot;
|
||||
crt_->output_level(4);
|
||||
cycles -= 4;
|
||||
}
|
||||
|
||||
if(cycles) {
|
||||
uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
|
||||
*pixel_target = border_colour;
|
||||
crt_->output_level(static_cast<unsigned int>(cycles));
|
||||
}
|
||||
crt_->output_level(static_cast<unsigned int>(cycles) * 4);
|
||||
}
|
||||
|
||||
void TMS9918::set_register(int address, uint8_t value) {
|
||||
@ -465,6 +490,7 @@ void TMS9918::set_register(int address, uint8_t value) {
|
||||
// Enqueue the write to occur at the next available slot.
|
||||
read_ahead_buffer_ = value;
|
||||
queued_access_ = MemoryAccess::Write;
|
||||
cycles_until_access_ = vram_access_delay();
|
||||
|
||||
return;
|
||||
}
|
||||
@ -487,11 +513,11 @@ void TMS9918::set_register(int address, uint8_t value) {
|
||||
write_phase_ = false;
|
||||
if(value & 0x80) {
|
||||
if(is_sega_vdp(personality_)) {
|
||||
if(value & 0x40) {
|
||||
master_system_.cram_is_selected = true;
|
||||
return;
|
||||
}
|
||||
value &= 0xf;
|
||||
if(value & 0x40) {
|
||||
master_system_.cram_is_selected = true;
|
||||
return;
|
||||
}
|
||||
value &= 0xf;
|
||||
} else {
|
||||
value &= 0x7;
|
||||
}
|
||||
@ -580,6 +606,7 @@ void TMS9918::set_register(int address, uint8_t value) {
|
||||
// A read request is enqueued upon setting the address; conversely a write
|
||||
// won't be enqueued unless and until some actual data is supplied.
|
||||
queued_access_ = MemoryAccess::Read;
|
||||
cycles_until_access_ = vram_access_delay();
|
||||
}
|
||||
master_system_.cram_is_selected = false;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ enum class TVStandard {
|
||||
NTSC
|
||||
};
|
||||
|
||||
#define is_sega_vdp(x) x >= SMSVDP
|
||||
#define is_sega_vdp(x) ((x) >= SMSVDP)
|
||||
|
||||
class Base {
|
||||
public:
|
||||
@ -51,6 +51,8 @@ class Base {
|
||||
}
|
||||
|
||||
protected:
|
||||
const static int output_lag = 11; // i.e. pixel output will occur 11 cycles after corresponding data read.
|
||||
|
||||
// The default TMS palette.
|
||||
const uint32_t palette[16] = {
|
||||
palette_pack(0, 0, 0),
|
||||
@ -89,6 +91,14 @@ class Base {
|
||||
enum class MemoryAccess {
|
||||
Read, Write, None
|
||||
} queued_access_ = MemoryAccess::None;
|
||||
int cycles_until_access_ = 0;
|
||||
int minimum_access_column_ = 0;
|
||||
int vram_access_delay() {
|
||||
// The Sega VDP seems to allow slightly quicker access;
|
||||
// Sega types generally claim 26 Z80 cycles are sufficient.
|
||||
// The received wisdom in MSX land is that it's 27.
|
||||
return is_sega_vdp(personality_) ? 7 : 8;
|
||||
}
|
||||
|
||||
// Holds the main status register.
|
||||
uint8_t status_ = 0;
|
||||
@ -127,7 +137,7 @@ class Base {
|
||||
|
||||
// A helper function to output the current border colour for
|
||||
// the number of cycles supplied.
|
||||
void output_border(int cycles);
|
||||
void output_border(int cycles, uint32_t cram_dot);
|
||||
|
||||
// A struct to contain timing information for the current mode.
|
||||
struct {
|
||||
@ -326,7 +336,11 @@ class Base {
|
||||
}
|
||||
|
||||
void do_external_slot(int access_column) {
|
||||
// TODO: is queued access ready yet?
|
||||
// Don't do anything if the required time for the access to become executable
|
||||
// has yet to pass.
|
||||
if(access_column < minimum_access_column_) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch(queued_access_) {
|
||||
default: return;
|
||||
@ -340,11 +354,24 @@ class Base {
|
||||
static_cast<uint8_t>(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
|
||||
);
|
||||
|
||||
// Schedule a CRAM dot.
|
||||
// Schedule a CRAM dot; this is scheduled for wherever it should appear
|
||||
// on screen. So it's wherever the output stream would be now. Which
|
||||
// is output_lag cycles ago from the point of view of the input stream.
|
||||
upcoming_cram_dots_.emplace_back();
|
||||
CRAMDot &dot = upcoming_cram_dots_.back();
|
||||
|
||||
dot.location.column = write_pointer_.column - output_lag;
|
||||
dot.location.row = write_pointer_.row;
|
||||
dot.location.column = access_column;
|
||||
|
||||
// Handle before this row conditionally; then handle after (or, more realistically,
|
||||
// exactly at the end of) naturally.
|
||||
if(dot.location.column < 0) {
|
||||
--dot.location.row;
|
||||
dot.location.column += 342;
|
||||
}
|
||||
dot.location.row += dot.location.column / 342;
|
||||
dot.location.column %= 342;
|
||||
|
||||
dot.value = master_system_.colour_ram[ram_pointer_ & 0x1f];
|
||||
} else {
|
||||
ram_[ram_pointer_ & 16383] = read_ahead_buffer_;
|
||||
|
Loading…
Reference in New Issue
Block a user