mirror of
https://github.com/TomHarte/CLK.git
synced 2025-08-07 23:25:00 +00:00
Compare commits
9 Commits
286Decodin
...
SeparateFe
Author | SHA1 | Date | |
---|---|---|---|
|
e7299c16f6 | ||
|
e2dcb0a8e2 | ||
|
1797bab28f | ||
|
7f48cd6d9d | ||
|
8662f06ae5 | ||
|
3b67d48ebf | ||
|
2ab16867cb | ||
|
3a93b8059a | ||
|
9703fed9f8 |
@@ -30,30 +30,6 @@ Base<personality>::Base() :
|
|||||||
// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
|
// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
|
||||||
// into whether there's a more natural form. It feels unlikely given the diversity of chips modelled.
|
// into whether there's a more natural form. It feels unlikely given the diversity of chips modelled.
|
||||||
|
|
||||||
if constexpr (is_sega_vdp(personality)) {
|
|
||||||
// Cf. https://www.smspower.org/forums/8161-SMSDisplayTiming
|
|
||||||
|
|
||||||
// "For a line interrupt, /INT is pulled low 608 mclks into the appropriate scanline relative to pixel 0.
|
|
||||||
// This is 3 mclks before the rising edge of /HSYNC which starts the next scanline."
|
|
||||||
//
|
|
||||||
// i.e. it's 304 internal clocks after the end of the left border.
|
|
||||||
mode_timing_.line_interrupt_position = (LineLayout<personality>::EndOfLeftBorder + 304) % LineLayout<personality>::CyclesPerLine;
|
|
||||||
|
|
||||||
// For a frame interrupt, /INT is pulled low 607 mclks into scanline 192 (of scanlines 0 through 261) relative to pixel 0.
|
|
||||||
// This is 4 mclks before the rising edge of /HSYNC which starts the next scanline.
|
|
||||||
//
|
|
||||||
// i.e. it's 1/2 cycle before the line interrupt position, which I have rounded. Ugh.
|
|
||||||
mode_timing_.end_of_frame_interrupt_position.column = mode_timing_.line_interrupt_position - 1;
|
|
||||||
mode_timing_.end_of_frame_interrupt_position.row = 192 + (LineLayout<personality>::EndOfLeftBorder + 304) / LineLayout<personality>::CyclesPerLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
if constexpr (is_yamaha_vdp(personality)) {
|
|
||||||
// TODO: this is used for interrupt _prediction_ but won't handle text modes correctly, and indeed
|
|
||||||
// can't be just a single value where the programmer has changed into or out of text modes during the
|
|
||||||
// middle of a line, since screen mode is latched (so it'll be one value for that line, another from then onwards).a
|
|
||||||
mode_timing_.line_interrupt_position = LineLayout<personality>::EndOfPixels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Establish that output is delayed after reading by `output_lag` cycles,
|
// Establish that output is delayed after reading by `output_lag` cycles,
|
||||||
// i.e. the fetch pointer is currently _ahead_ of the output pointer.
|
// i.e. the fetch pointer is currently _ahead_ of the output pointer.
|
||||||
output_pointer_.row = output_pointer_.column = 0;
|
output_pointer_.row = output_pointer_.column = 0;
|
||||||
@@ -61,6 +37,22 @@ Base<personality>::Base() :
|
|||||||
fetch_pointer_ = output_pointer_;
|
fetch_pointer_ = output_pointer_;
|
||||||
fetch_pointer_.column += output_lag;
|
fetch_pointer_.column += output_lag;
|
||||||
|
|
||||||
|
// The fetch pointer is interpreted such that its zero is at the mode-latch cycle.
|
||||||
|
// Conversely the output pointer has zero be at start of sync. So the following
|
||||||
|
// is a mere change-of-origin.
|
||||||
|
//
|
||||||
|
// Logically, any mode latch time greater than 0 — i.e. beyond the start of sync — will
|
||||||
|
// cause fetch_pointer_ to **regress**. It will be set to the value it was at the start
|
||||||
|
// of sync, ready to overflow to 0 upon mode latch. When it overflows, the fetch row
|
||||||
|
// will be incremented.
|
||||||
|
//
|
||||||
|
// Therefore the nominal output row at instantiation needs to be one greater than the
|
||||||
|
// fetch row, as that's the first row that'll actually be fetched.
|
||||||
|
fetch_pointer_.column = to_internal<personality, Origin::ModeLatch>(output_pointer_.column);
|
||||||
|
if(LineLayout<personality>::ModeLatchCycle) {
|
||||||
|
++output_pointer_.row;
|
||||||
|
}
|
||||||
|
|
||||||
fetch_line_buffer_ = line_buffers_.begin();
|
fetch_line_buffer_ = line_buffers_.begin();
|
||||||
draw_line_buffer_ = line_buffers_.begin();
|
draw_line_buffer_ = line_buffers_.begin();
|
||||||
fetch_sprite_buffer_ = sprite_buffers_.begin();
|
fetch_sprite_buffer_ = sprite_buffers_.begin();
|
||||||
@@ -224,7 +216,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
|||||||
// TODO: where did this magic constant come from? https://www.smspower.org/forums/17970-RoadRashHow#111000 mentioned in passing
|
// TODO: where did this magic constant come from? https://www.smspower.org/forums/17970-RoadRashHow#111000 mentioned in passing
|
||||||
// that "the vertical scroll register is latched at the start of the active display" and this is two clocks before that, so it's
|
// that "the vertical scroll register is latched at the start of the active display" and this is two clocks before that, so it's
|
||||||
// not uncompelling. I can just no longer find my source.
|
// not uncompelling. I can just no longer find my source.
|
||||||
constexpr auto latch_time = LineLayout<personality>::EndOfLeftBorder - 2;
|
constexpr auto latch_time = to_internal<personality, Origin::ModeLatch>(LineLayout<personality>::EndOfLeftBorder - 2);
|
||||||
static_assert(latch_time > 0);
|
static_assert(latch_time > 0);
|
||||||
if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) {
|
if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) {
|
||||||
Storage<personality>::latched_vertical_scroll_ = Storage<personality>::vertical_scroll_;
|
Storage<personality>::latched_vertical_scroll_ = Storage<personality>::vertical_scroll_;
|
||||||
@@ -283,13 +275,15 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
|
|||||||
// -------------------------------
|
// -------------------------------
|
||||||
// Check for interrupt conditions.
|
// Check for interrupt conditions.
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
if constexpr (is_sega_vdp(personality)) {
|
if constexpr (LineLayout<personality>::HasFixedLineInterrupt) {
|
||||||
// The Sega VDP offers a decrementing counter for triggering line interrupts;
|
// 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 reloaded either when it overflows or upon every non-pixel line after the first.
|
||||||
// It is otherwise decremented.
|
// It is otherwise decremented.
|
||||||
|
|
||||||
|
constexpr int FixedLineInterrupt = to_internal<personality, Origin::ModeLatch>(LineLayout<personality>::FixedLineInterrupt);
|
||||||
if(
|
if(
|
||||||
this->fetch_pointer_.column < this->mode_timing_.line_interrupt_position &&
|
this->fetch_pointer_.column < FixedLineInterrupt &&
|
||||||
end_column >= this->mode_timing_.line_interrupt_position
|
end_column >= FixedLineInterrupt
|
||||||
) {
|
) {
|
||||||
if(this->fetch_pointer_.row >= 0 && this->fetch_pointer_.row <= this->mode_timing_.pixel_lines) {
|
if(this->fetch_pointer_.row >= 0 && this->fetch_pointer_.row <= this->mode_timing_.pixel_lines) {
|
||||||
if(!this->line_interrupt_counter_) {
|
if(!this->line_interrupt_counter_) {
|
||||||
|
@@ -170,16 +170,6 @@ template <Personality personality> struct Base: public Storage<personality> {
|
|||||||
// then the appropriate status information will be set.
|
// then the appropriate status information will be set.
|
||||||
int maximum_visible_sprites = 4;
|
int maximum_visible_sprites = 4;
|
||||||
|
|
||||||
// Set the position, in cycles, of the two interrupts,
|
|
||||||
// within a line.
|
|
||||||
//
|
|
||||||
// TODO: redetermine where this number came from.
|
|
||||||
struct {
|
|
||||||
int column = 313;
|
|
||||||
int row = 192;
|
|
||||||
} end_of_frame_interrupt_position;
|
|
||||||
int line_interrupt_position = -1;
|
|
||||||
|
|
||||||
// Enables or disabled the recognition of the sprite
|
// Enables or disabled the recognition of the sprite
|
||||||
// list terminator, and sets the terminator value.
|
// list terminator, and sets the terminator value.
|
||||||
bool allow_sprite_terminator = true;
|
bool allow_sprite_terminator = true;
|
||||||
|
@@ -24,9 +24,15 @@ enum class Clock {
|
|||||||
TMSMemoryWindow,
|
TMSMemoryWindow,
|
||||||
/// A fixed 1368-cycle/line clock that is used to count output to the CRT.
|
/// A fixed 1368-cycle/line clock that is used to count output to the CRT.
|
||||||
CRT,
|
CRT,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Origin {
|
||||||
|
///
|
||||||
|
ModeLatch,
|
||||||
|
|
||||||
/// Provides the same clock rate as ::Internal but is relocated so that 0 is the start of horizontal sync — very not coincidentally,
|
/// Provides the same clock rate as ::Internal but is relocated so that 0 is the start of horizontal sync — very not coincidentally,
|
||||||
/// where Grauw puts 0 on his detailed TMS and Yamaha timing diagrams.
|
/// where Grauw puts 0 on his detailed TMS and Yamaha timing diagrams.
|
||||||
FromStartOfSync,
|
StartOfSync,
|
||||||
};
|
};
|
||||||
|
|
||||||
template <Personality personality, Clock clk> constexpr int clock_rate() {
|
template <Personality personality, Clock clk> constexpr int clock_rate() {
|
||||||
@@ -41,7 +47,6 @@ template <Personality personality, Clock clk> constexpr int clock_rate() {
|
|||||||
case Clock::TMSMemoryWindow: return 171;
|
case Clock::TMSMemoryWindow: return 171;
|
||||||
case Clock::CRT: return 1368;
|
case Clock::CRT: return 1368;
|
||||||
case Clock::Internal:
|
case Clock::Internal:
|
||||||
case Clock::FromStartOfSync:
|
|
||||||
if constexpr (is_classic_vdp(personality)) {
|
if constexpr (is_classic_vdp(personality)) {
|
||||||
return 342;
|
return 342;
|
||||||
} else if constexpr (is_yamaha_vdp(personality)) {
|
} else if constexpr (is_yamaha_vdp(personality)) {
|
||||||
@@ -52,36 +57,50 @@ template <Personality personality, Clock clk> constexpr int clock_rate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Statelessly converts @c length to the internal clock for @c personality; applies conversions per the list of clocks in left-to-right order.
|
/// Scales @c length from @c clock to the internal clock rate.
|
||||||
template <Personality personality, Clock head, Clock... tail> constexpr int to_internal(int length) {
|
template <Personality personality, Clock clock> constexpr int to_internal(int length) {
|
||||||
if constexpr (head == Clock::FromStartOfSync) {
|
return length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, clock>();
|
||||||
length = (length + LineLayout<personality>::StartOfSync) % LineLayout<personality>::CyclesPerLine;
|
|
||||||
} else {
|
|
||||||
length = length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, head>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if constexpr (!sizeof...(tail)) {
|
|
||||||
return length;
|
|
||||||
} else {
|
|
||||||
return to_internal<personality, tail...>(length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Statelessly converts @c length to @c clock from the the internal clock used by VDPs of @c personality throwing away any remainder.
|
/// Moves @c position that is relative to @c Origin::StartOfSync so that it is relative to @c origin ;
|
||||||
template <Personality personality, Clock head, Clock... tail> constexpr int from_internal(int length) {
|
/// i.e. can be thought of as "to [internal with origin as specified]".
|
||||||
if constexpr (head == Clock::FromStartOfSync) {
|
template <Personality personality, Origin origin> constexpr int to_internal(int position) {
|
||||||
length =
|
if constexpr (origin == Origin::ModeLatch) {
|
||||||
(length + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::StartOfSync)
|
return (
|
||||||
% LineLayout<personality>::CyclesPerLine;
|
position + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::ModeLatchCycle
|
||||||
} else {
|
) % LineLayout<personality>::CyclesPerLine;
|
||||||
length = length * clock_rate<personality, head>() / clock_rate<personality, Clock::Internal>();
|
|
||||||
}
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
if constexpr (!sizeof...(tail)) {
|
/// Converts @c position from one that is measured at the rate implied by @c clock and relative to @c Origin::StartOfSync
|
||||||
return length;
|
/// to one that is at the internal clock rate and relative to @c origin.
|
||||||
} else {
|
template <Personality personality, Origin origin, Clock clock> constexpr int to_internal(int position) {
|
||||||
return to_internal<personality, tail...>(length);
|
position = to_internal<personality, clock>(position);
|
||||||
|
return to_internal<personality, origin>(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scales @c length from the internal clock rate to @c clock.
|
||||||
|
template <Personality personality, Clock clock> constexpr int from_internal(int length) {
|
||||||
|
return length * clock_rate<personality, clock>() / clock_rate<personality, Clock::Internal>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves @c position that is relative to @c origin so that it is relative to @c Origin::StartOfSync ;
|
||||||
|
/// i.e. can be thought of as "from [internal with origin as specified]".
|
||||||
|
template <Personality personality, Origin origin> constexpr int from_internal(int length) {
|
||||||
|
if constexpr (origin == Origin::ModeLatch) {
|
||||||
|
return (
|
||||||
|
length + LineLayout<personality>::ModeLatchCycle
|
||||||
|
) % LineLayout<personality>::CyclesPerLine;
|
||||||
}
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts @c position from one that is measured at the internal clock rate and relative to @c origin
|
||||||
|
/// to one that is at the rate implied by @c clock and relative to @c Origin::StartOfSync
|
||||||
|
template <Personality personality, Origin origin, Clock clock> constexpr int from_internal(int position) {
|
||||||
|
position = from_internal<personality, origin>(position);
|
||||||
|
return from_internal<personality, clock>(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@@ -54,7 +54,7 @@ template<bool use_end, typename SequencerT> void Base<personality>::dispatch(Seq
|
|||||||
#define index(n) \
|
#define index(n) \
|
||||||
if(use_end && end == n) return; \
|
if(use_end && end == n) return; \
|
||||||
[[fallthrough]]; \
|
[[fallthrough]]; \
|
||||||
case n: fetcher.template fetch<from_internal<personality, Clock::FromStartOfSync>(n)>();
|
case n: fetcher.template fetch<from_internal<personality, Origin::StartOfSync>(n)>();
|
||||||
|
|
||||||
switch(start) {
|
switch(start) {
|
||||||
default: assert(false);
|
default: assert(false);
|
||||||
@@ -373,7 +373,7 @@ struct RefreshSequencer {
|
|||||||
|
|
||||||
template <int cycle> void fetch() {
|
template <int cycle> void fetch() {
|
||||||
if(cycle < 26 || (cycle & 1) || cycle >= 154) {
|
if(cycle < 26 || (cycle & 1) || cycle >= 154) {
|
||||||
base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +387,7 @@ struct TextSequencer {
|
|||||||
template <int cycle> void fetch() {
|
template <int cycle> void fetch() {
|
||||||
// The first 30 and the final 4 slots are external.
|
// The first 30 and the final 4 slots are external.
|
||||||
if constexpr (cycle < 30 || cycle >= 150) {
|
if constexpr (cycle < 30 || cycle >= 150) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// For the 120 slots in between follow a three-step pattern of:
|
// For the 120 slots in between follow a three-step pattern of:
|
||||||
@@ -398,7 +398,7 @@ struct TextSequencer {
|
|||||||
fetcher.fetch_name(column);
|
fetcher.fetch_name(column);
|
||||||
break;
|
break;
|
||||||
case 1: // (2) external slot.
|
case 1: // (2) external slot.
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
break;
|
break;
|
||||||
case 2: // (3) fetch tile pattern.
|
case 2: // (3) fetch tile pattern.
|
||||||
fetcher.fetch_pattern(column);
|
fetcher.fetch_pattern(column);
|
||||||
@@ -419,7 +419,7 @@ struct CharacterSequencer {
|
|||||||
|
|
||||||
template <int cycle> void fetch() {
|
template <int cycle> void fetch() {
|
||||||
if(cycle < 5) {
|
if(cycle < 5) {
|
||||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 5) {
|
if(cycle == 5) {
|
||||||
@@ -430,7 +430,7 @@ struct CharacterSequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(cycle > 14 && cycle < 19) {
|
if(cycle > 14 && cycle < 19) {
|
||||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line.
|
// Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line.
|
||||||
@@ -448,7 +448,7 @@ struct CharacterSequencer {
|
|||||||
case 0: character_fetcher.fetch_name(block); break;
|
case 0: character_fetcher.fetch_name(block); break;
|
||||||
case 1:
|
case 1:
|
||||||
if(!(block & 3)) {
|
if(!(block & 3)) {
|
||||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
} else {
|
} else {
|
||||||
constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
|
constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
|
||||||
sprite_fetcher.fetch_y(sprite);
|
sprite_fetcher.fetch_y(sprite);
|
||||||
@@ -463,7 +463,7 @@ struct CharacterSequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(cycle >= 155 && cycle < 157) {
|
if(cycle >= 155 && cycle < 157) {
|
||||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 157) {
|
if(cycle == 157) {
|
||||||
@@ -511,7 +511,7 @@ struct SMSSequencer {
|
|||||||
// window 0 to HSYNC low.
|
// window 0 to HSYNC low.
|
||||||
template <int cycle> void fetch() {
|
template <int cycle> void fetch() {
|
||||||
if(cycle < 3) {
|
if(cycle < 3) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 3) {
|
if(cycle == 3) {
|
||||||
@@ -522,7 +522,7 @@ struct SMSSequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 15 || cycle == 16) {
|
if(cycle == 15 || cycle == 16) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 17) {
|
if(cycle == 17) {
|
||||||
@@ -543,7 +543,7 @@ struct SMSSequencer {
|
|||||||
case 0: fetcher.fetch_tile_name(block); break;
|
case 0: fetcher.fetch_tile_name(block); break;
|
||||||
case 1:
|
case 1:
|
||||||
if(!(block & 3)) {
|
if(!(block & 3)) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
} else {
|
} else {
|
||||||
constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
|
constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
|
||||||
fetcher.posit_sprite(sprite);
|
fetcher.posit_sprite(sprite);
|
||||||
@@ -555,7 +555,7 @@ struct SMSSequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(cycle >= 153 && cycle < 157) {
|
if(cycle >= 153 && cycle < 157) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cycle == 157) {
|
if(cycle == 157) {
|
||||||
@@ -566,7 +566,7 @@ struct SMSSequencer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(cycle >= 169) {
|
if(cycle >= 169) {
|
||||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
|
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,8 +28,11 @@ template <Personality personality, typename Enable = void> struct LineLayout;
|
|||||||
// * horizontal adjust on the Yamaha VDPs is applied to EndOfLeftBorder and EndOfPixels;
|
// * horizontal adjust on the Yamaha VDPs is applied to EndOfLeftBorder and EndOfPixels;
|
||||||
// * the Sega VDPs may programatically extend the left border; and
|
// * the Sega VDPs may programatically extend the left border; and
|
||||||
// * text mode on all VDPs adjusts border width.
|
// * text mode on all VDPs adjusts border width.
|
||||||
|
//
|
||||||
|
// ModeLaytchCycle is the cycle at which the video mode, blank disable/enable and
|
||||||
|
// sprite enable/disable are latched for the line.
|
||||||
|
|
||||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
|
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality) && !is_sega_vdp(personality)>> {
|
||||||
constexpr static int StartOfSync = 0;
|
constexpr static int StartOfSync = 0;
|
||||||
constexpr static int EndOfSync = 26;
|
constexpr static int EndOfSync = 26;
|
||||||
constexpr static int StartOfColourBurst = 29;
|
constexpr static int StartOfColourBurst = 29;
|
||||||
@@ -48,11 +51,34 @@ template <Personality personality> struct LineLayout<personality, std::enable_if
|
|||||||
// and falls into the collection gap between the final sprite
|
// and falls into the collection gap between the final sprite
|
||||||
// graphics and the initial tiles or pixels.
|
// graphics and the initial tiles or pixels.
|
||||||
|
|
||||||
|
constexpr static bool HasDynamicLineInterrupt = false;
|
||||||
|
constexpr static bool HasFixedLineInterrupt = false;
|
||||||
|
constexpr static int EndOfFrameInterrupt = 313;
|
||||||
|
|
||||||
/// The number of internal cycles that must elapse between a request to read or write and
|
/// The number of internal cycles that must elapse between a request to read or write and
|
||||||
/// it becoming a candidate for action.
|
/// it becoming a candidate for action.
|
||||||
constexpr static int VRAMAccessDelay = 6;
|
constexpr static int VRAMAccessDelay = 6;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_sega_vdp(personality)>> :
|
||||||
|
public LineLayout<Personality::TMS9918A> {
|
||||||
|
|
||||||
|
// Cf. https://www.smspower.org/forums/8161-SMSDisplayTiming
|
||||||
|
|
||||||
|
// "For a line interrupt, /INT is pulled low 608 mclks into the appropriate scanline relative to pixel 0.
|
||||||
|
// This is 3 mclks before the rising edge of /HSYNC which starts the next scanline."
|
||||||
|
//
|
||||||
|
// i.e. it's 304 internal clocks after the end of the left border.
|
||||||
|
constexpr static bool HasFixedLineInterrupt = false;
|
||||||
|
constexpr static int FixedLineInterrupt = (EndOfLeftBorder + 304) % CyclesPerLine;
|
||||||
|
|
||||||
|
// For a frame interrupt, /INT is pulled low 607 mclks into scanline 192 (of scanlines 0 through 261) relative to pixel 0.
|
||||||
|
// This is 4 mclks before the rising edge of /HSYNC which starts the next scanline.
|
||||||
|
//
|
||||||
|
// i.e. it's 1/2 cycle before the line interrupt position, which I have rounded. Ugh.
|
||||||
|
constexpr static int EndOfFrameInterrupt = 313;
|
||||||
|
};
|
||||||
|
|
||||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
||||||
constexpr static int StartOfSync = 0;
|
constexpr static int StartOfSync = 0;
|
||||||
constexpr static int EndOfSync = 100;
|
constexpr static int EndOfSync = 100;
|
||||||
@@ -70,6 +96,10 @@ template <Personality personality> struct LineLayout<personality, std::enable_if
|
|||||||
|
|
||||||
constexpr static int ModeLatchCycle = 144;
|
constexpr static int ModeLatchCycle = 144;
|
||||||
|
|
||||||
|
constexpr static bool HasDynamicLineInterrupt = true;
|
||||||
|
constexpr static bool HasFixedLineInterrupt = false;
|
||||||
|
constexpr static int EndOfFrameInterrupt = 313;
|
||||||
|
|
||||||
/// The number of internal cycles that must elapse between a request to read or write and
|
/// The number of internal cycles that must elapse between a request to read or write and
|
||||||
/// it becoming a candidate for action.
|
/// it becoming a candidate for action.
|
||||||
constexpr static int VRAMAccessDelay = 16;
|
constexpr static int VRAMAccessDelay = 16;
|
||||||
|
@@ -87,7 +87,8 @@ struct YamahaFetcher {
|
|||||||
size_t index = 0;
|
size_t index = 0;
|
||||||
for(int c = 0; c < 1368; c++) {
|
for(int c = 0; c < 1368; c++) {
|
||||||
// Specific personality doesn't matter here; both Yamahas use the same internal timing.
|
// Specific personality doesn't matter here; both Yamahas use the same internal timing.
|
||||||
const auto event = GeneratorT::event(from_internal<Personality::V9938, Clock::FromStartOfSync>(c));
|
const int mapped_location = from_internal<Personality::V9938, Origin::StartOfSync>(c);
|
||||||
|
const auto event = GeneratorT::event(mapped_location);
|
||||||
if(!event) {
|
if(!event) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1400"
|
LastUpgradeVersion = "1400"
|
||||||
version = "1.3">
|
version = "1.8">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
|
Reference in New Issue
Block a user