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

Makes a first serious attempt at Master System line interrupts.

This commit is contained in:
Thomas Harte 2018-10-09 20:51:09 -04:00
parent 2d8ab72e22
commit dccf17e770

View File

@ -305,142 +305,26 @@ void TMS9918::run_for(const HalfCycles cycles) {
// -----------------
/*
// --------------
// Output pixels.
// --------------
case LineMode::Character: {
// If this is the start of the visible area, seed sprite shifter positions.
SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1];
if(output_column_ == first_pixel_column_) {
int c = sprite_set.active_sprite_slot;
while(c--) {
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
sprite.shift_position = -sprite.info[1];
if(sprite.info[3] & 0x80) {
sprite.shift_position += 32;
if(sprite.shift_position > 0 && !sprites_magnified_)
sprite.shift_position *= 2;
}
}
}
// Paint the background tiles.
const int pixels_left = pixels_end - output_column_;
if(current_mode_ == ScreenMode::MultiColour) {
int pixel_location = output_column_ - first_pixel_column_;
for(int c = 0; c < pixels_left; ++c) {
pixel_target_[c] = palette[
(pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15
];
}
pixel_target_ += pixels_left;
} else {
const int shift = (output_column_ - first_pixel_column_) & 7;
int byte_column = (output_column_ - first_pixel_column_) >> 3;
int length = std::min(pixels_left, 8 - shift);
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
uint8_t colour = colour_buffer_[byte_column];
uint32_t colours[2] = {
palette[(colour & 15) ? (colour & 15) : background_colour_],
palette[(colour >> 4) ? (colour >> 4) : background_colour_]
};
int background_pixels_left = pixels_left;
while(true) {
background_pixels_left -= length;
for(int c = 0; c < length; ++c) {
pixel_target_[c] = colours[pattern&0x01];
pattern >>= 1;
}
pixel_target_ += length;
if(!background_pixels_left) break;
length = std::min(8, background_pixels_left);
byte_column++;
pattern = reverse_table.map[pattern_buffer_[byte_column]];
colour = colour_buffer_[byte_column];
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
}
}
// Paint sprites and check for collisions, but only if at least one sprite is active
// on this line.
if(sprite_set.active_sprite_slot) {
int sprite_pixels_left = pixels_left;
const int shift_advance = sprites_magnified_ ? 1 : 2;
static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
while(sprite_pixels_left--) {
// sprite_colour is the colour that's going to reach the display after sprite logic has been
// applied; by default assume that nothing is going to be drawn.
uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_];
// The sprite_mask is used to keep track of whether two sprites have both sought to output
// a pixel at the same location, and to feed that into the status register's sprite
// collision bit.
int sprite_mask = 0;
int c = sprite_set.active_sprite_slot;
while(c--) {
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
if(sprite.shift_position < 0) {
sprite.shift_position++;
continue;
} else if(sprite.shift_position < 32) {
int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1);
mask = (mask >> 7) & 1;
// Ignore the right half of whatever was collected if sprites are not in 16x16 mode.
if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) {
// If any previous sprite has been painted in this column and this sprite
// has this pixel set, set the sprite collision status bit.
status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift;
sprite_mask |= mask;
// Check that the sprite colour is not transparent
mask &= colour_masks[sprite.info[3]&15];
sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
}
sprite.shift_position += shift_advance;
}
}
// Output whichever sprite colour was on top.
pixel_base_[output_column_ - first_pixel_column_] = sprite_colour;
output_column_++;
}
}
output_column_ = pixels_end;
} break;
}
}*/
if(column_ < mode_timing_.line_interrupt_position && end_column >= mode_timing_.line_interrupt_position) {
if(row_ <= mode_timing_.pixel_lines) {
--line_interrupt_counter;
if(line_interrupt_counter == 0xff) {
// line_interrupt_pending_ = true;
// 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_ <= mode_timing_.pixel_lines) {
--line_interrupt_counter;
if(line_interrupt_counter == 0xff) {
line_interrupt_pending_ = true;
line_interrupt_counter = line_interrupt_target;
}
} else {
line_interrupt_counter = line_interrupt_target;
}
} else {
line_interrupt_counter = line_interrupt_target;
}
// TODO: the V9938 provides line interrupts from direct specification of the target line.
// So life is easy.
}
@ -652,28 +536,37 @@ uint8_t TMS9918::get_register(int address) {
const int half_cycles_per_frame = mode_timing_.total_lines * 228 * 2;
const int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame;
const auto time_until_frame_interrupt = HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame);
if(!enable_line_interrupts_) return time_until_frame_interrupt;
// Calculate the number of times the line interrupt position will be decremented this frame.
// return HalfCycles(20);
/* auto time_until_line_count = mode_timing_.line_interrupt_position - row_;
auto decrements_left_this_frame = mode_timing_.pixel_lines - row_;
if(time_until_line_count > 0) {
++decrements_left_this_frame;
// Calculate the row upon which the next line interrupt will occur;
int next_line_interrupt_row = -1;
if(is_sega_vdp(personality_)) {
// 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;
} else {
if(line_interrupt_target <= mode_timing_.pixel_lines)
next_line_interrupt_row = mode_timing_.total_lines + line_interrupt_target;
}
}
// If that's enough to underflow the line counter, there's the next interupt.
HalfCycles time_until_line_interrupt;
if(decrements_left_this_frame >= line_interrupt_counter+1) {
time_until_line_interrupt = HalfCycles
// If there's actually no interrupt upcoming, despite being enabled, either return
// the frame end interrupt or no interrupt pending as appropriate.
if(next_line_interrupt_row == -1) {
return generate_interrupts_ ? time_until_frame_interrupt : HalfCycles(-1);
}
if(!enable_line_interrupts_) {
return time_until_frame_interrupt;
} else if(!generate_interrupts_) {
// Figure out the number of internal cycles until the next line interrupt.
const int local_cycles_until_line_interrupt = ((mode_timing_.line_interrupt_position - column_ + 342) % 342) + (next_line_interrupt_row - row_) * 342;
}*/
// Map that to input cycles by multiplying by 2/3 (and incrementing on any carry).
auto time_until_line_interrupt = HalfCycles( (local_cycles_until_line_interrupt * 6 + 7) / 4);
return time_until_frame_interrupt;
// Return whichever interrupt is closer.
return std::min(time_until_frame_interrupt, time_until_line_interrupt);
}
bool TMS9918::get_interrupt_line() {
@ -688,6 +581,130 @@ void Base::draw_tms_character(int start, int end) {
// for(int c = start; c < end; ++c) {
// pixel_target_[c] = static_cast<uint32_t>(c * 0x01010101);
// }
/*
// --------------
// Output pixels.
// --------------
case LineMode::Character: {
// If this is the start of the visible area, seed sprite shifter positions.
SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1];
if(output_column_ == first_pixel_column_) {
int c = sprite_set.active_sprite_slot;
while(c--) {
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
sprite.shift_position = -sprite.info[1];
if(sprite.info[3] & 0x80) {
sprite.shift_position += 32;
if(sprite.shift_position > 0 && !sprites_magnified_)
sprite.shift_position *= 2;
}
}
}
// Paint the background tiles.
const int pixels_left = pixels_end - output_column_;
if(current_mode_ == ScreenMode::MultiColour) {
int pixel_location = output_column_ - first_pixel_column_;
for(int c = 0; c < pixels_left; ++c) {
pixel_target_[c] = palette[
(pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15
];
}
pixel_target_ += pixels_left;
} else {
const int shift = (output_column_ - first_pixel_column_) & 7;
int byte_column = (output_column_ - first_pixel_column_) >> 3;
int length = std::min(pixels_left, 8 - shift);
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
uint8_t colour = colour_buffer_[byte_column];
uint32_t colours[2] = {
palette[(colour & 15) ? (colour & 15) : background_colour_],
palette[(colour >> 4) ? (colour >> 4) : background_colour_]
};
int background_pixels_left = pixels_left;
while(true) {
background_pixels_left -= length;
for(int c = 0; c < length; ++c) {
pixel_target_[c] = colours[pattern&0x01];
pattern >>= 1;
}
pixel_target_ += length;
if(!background_pixels_left) break;
length = std::min(8, background_pixels_left);
byte_column++;
pattern = reverse_table.map[pattern_buffer_[byte_column]];
colour = colour_buffer_[byte_column];
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
}
}
// Paint sprites and check for collisions, but only if at least one sprite is active
// on this line.
if(sprite_set.active_sprite_slot) {
int sprite_pixels_left = pixels_left;
const int shift_advance = sprites_magnified_ ? 1 : 2;
static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
while(sprite_pixels_left--) {
// sprite_colour is the colour that's going to reach the display after sprite logic has been
// applied; by default assume that nothing is going to be drawn.
uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_];
// The sprite_mask is used to keep track of whether two sprites have both sought to output
// a pixel at the same location, and to feed that into the status register's sprite
// collision bit.
int sprite_mask = 0;
int c = sprite_set.active_sprite_slot;
while(c--) {
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
if(sprite.shift_position < 0) {
sprite.shift_position++;
continue;
} else if(sprite.shift_position < 32) {
int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1);
mask = (mask >> 7) & 1;
// Ignore the right half of whatever was collected if sprites are not in 16x16 mode.
if(sprite.shift_position < (sprites_16x16_ ? 32 : 16)) {
// If any previous sprite has been painted in this column and this sprite
// has this pixel set, set the sprite collision status bit.
status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift;
sprite_mask |= mask;
// Check that the sprite colour is not transparent
mask &= colour_masks[sprite.info[3]&15];
sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
}
sprite.shift_position += shift_advance;
}
}
// Output whichever sprite colour was on top.
pixel_base_[output_column_ - first_pixel_column_] = sprite_colour;
output_column_++;
}
}
output_column_ = pixels_end;
} break;
}
}*/
}
void Base::draw_tms_text(int start, int end) {