2017-11-26 18:28:26 +00:00
//
// 9918.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
2018-05-13 19:19:52 +00:00
// Copyright 2017 Thomas Harte. All rights reserved.
2017-11-26 18:28:26 +00:00
//
2023-01-01 19:01:19 +00:00
# include "../9918.hpp"
2017-11-26 18:28:26 +00:00
2017-12-07 01:24:29 +00:00
# include <cassert>
2017-12-06 03:39:03 +00:00
# include <cstring>
2018-10-27 01:02:56 +00:00
# include <cstdlib>
2023-01-01 19:01:19 +00:00
# include "../../../Outputs/Log.hpp"
2017-12-06 03:39:03 +00:00
2018-09-18 02:59:16 +00:00
using namespace TI : : TMS ;
2017-11-26 18:28:26 +00:00
2017-11-28 03:05:40 +00:00
namespace {
2018-10-20 01:36:13 +00:00
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
2019-12-22 05:22:17 +00:00
constexpr unsigned int CRTCyclesPerLine = 1365 ;
constexpr unsigned int CRTCyclesDivider = 4 ;
2018-10-20 01:36:13 +00:00
2017-11-28 03:05:40 +00:00
}
2023-01-01 02:47:05 +00:00
template < Personality personality >
2023-01-01 02:50:57 +00:00
Base < personality > : : Base ( ) :
2018-11-15 03:25:19 +00:00
crt_ ( CRTCyclesPerLine , CRTCyclesDivider , Outputs : : Display : : Type : : NTSC60 , Outputs : : Display : : InputDataType : : Red8Green8Blue8 ) {
2018-11-23 03:47:29 +00:00
// 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.
2018-09-18 02:59:16 +00:00
2023-01-01 02:50:57 +00:00
if constexpr ( is_sega_vdp ( personality ) ) {
2023-04-26 03:16:21 +00:00
// Cf. https://www.smspower.org/forums/8161-SMSDisplayTiming
2023-04-23 16:17:55 +00:00
2023-04-26 03:16:21 +00:00
// "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."
2023-05-18 20:50:46 +00:00
//
// i.e. it's 304 internal clocks after the end of the left border.
2023-05-12 03:49:12 +00:00
mode_timing_ . line_interrupt_position = ( LineLayout < personality > : : EndOfLeftBorder + 304 ) % LineLayout < personality > : : CyclesPerLine ;
2018-10-12 22:57:07 +00:00
2023-04-26 03:16:21 +00:00
// 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.
2023-05-18 20:50:46 +00:00
//
// i.e. it's 1/2 cycle before the line interrupt position, which I have rounded. Ugh.
2023-04-26 03:16:21 +00:00
mode_timing_ . end_of_frame_interrupt_position . column = mode_timing_ . line_interrupt_position - 1 ;
2023-05-18 20:50:46 +00:00
mode_timing_ . end_of_frame_interrupt_position . row = 192 + ( LineLayout < personality > : : EndOfLeftBorder + 304 ) / LineLayout < personality > : : CyclesPerLine ;
2018-10-05 02:50:35 +00:00
}
2018-10-14 20:23:45 +00:00
2023-01-31 02:24:53 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
2023-05-16 17:01:23 +00:00
// 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 ;
2023-01-31 02:24:53 +00:00
}
2023-02-14 02:10:14 +00:00
// Establish that output is delayed after reading by `output_lag` cycles,
// i.e. the fetch pointer is currently _ahead_ of the output pointer.
2023-03-30 04:20:03 +00:00
output_pointer_ . row = output_pointer_ . column = 0 ;
2023-03-12 03:24:11 +00:00
fetch_pointer_ = output_pointer_ ;
fetch_pointer_ . column + = output_lag ;
2023-03-30 04:20:03 +00:00
fetch_line_buffer_ = line_buffers_ . begin ( ) ;
draw_line_buffer_ = line_buffers_ . begin ( ) ;
fetch_sprite_buffer_ = sprite_buffers_ . begin ( ) ;
2018-09-18 02:59:16 +00:00
}
2017-12-15 01:27:26 +00:00
2022-12-31 20:08:33 +00:00
template < Personality personality >
2023-01-01 02:50:57 +00:00
TMS9918 < personality > : : TMS9918 ( ) {
2023-01-01 02:47:05 +00:00
this - > crt_ . set_display_type ( Outputs : : Display : : DisplayType : : RGB ) ;
2023-03-07 23:19:08 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
this - > crt_ . set_visible_area ( Outputs : : Display : : Rect ( 0.07f , 0.065f , 0.875f , 0.875f ) ) ;
} else {
this - > crt_ . set_visible_area ( Outputs : : Display : : Rect ( 0.07f , 0.0375f , 0.875f , 0.875f ) ) ;
}
2018-03-03 18:53:00 +00:00
// 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
// the colour clock. It was eyeballed rather than derived from any knowledge of the TMS
// colour burst generator because I've yet to find any.
2023-01-01 02:47:05 +00:00
this - > crt_ . set_immediate_default_phase ( 0.85f ) ;
2017-11-26 18:28:26 +00:00
}
2022-12-31 20:08:33 +00:00
template < Personality personality >
void TMS9918 < personality > : : set_tv_standard ( TVStandard standard ) {
2023-05-18 20:50:46 +00:00
// TODO: the Yamaha is programmable on this at runtime.
2023-01-01 02:47:05 +00:00
this - > tv_standard_ = standard ;
2018-10-20 01:36:13 +00:00
switch ( standard ) {
case TVStandard : : PAL :
2023-01-01 02:47:05 +00:00
this - > mode_timing_ . total_lines = 313 ;
this - > mode_timing_ . first_vsync_line = 253 ;
this - > crt_ . set_new_display_type ( CRTCyclesPerLine , Outputs : : Display : : Type : : PAL50 ) ;
2018-10-20 01:36:13 +00:00
break ;
default :
2023-01-01 02:47:05 +00:00
this - > mode_timing_ . total_lines = 262 ;
this - > mode_timing_ . first_vsync_line = 227 ;
this - > crt_ . set_new_display_type ( CRTCyclesPerLine , Outputs : : Display : : Type : : NTSC60 ) ;
2018-10-20 01:36:13 +00:00
break ;
}
}
2022-12-31 20:08:33 +00:00
template < Personality personality >
void TMS9918 < personality > : : set_scan_target ( Outputs : : Display : : ScanTarget * scan_target ) {
2023-01-01 02:47:05 +00:00
this - > crt_ . set_scan_target ( scan_target ) ;
2017-11-26 18:28:26 +00:00
}
2022-12-31 20:08:33 +00:00
template < Personality personality >
Outputs : : Display : : ScanStatus TMS9918 < personality > : : get_scaled_scan_status ( ) const {
2020-01-23 00:34:10 +00:00
// The input was scaled by 3/4 to convert half cycles to internal ticks,
// so undo that and also allow for: (i) the multiply by 4 that it takes
// to reach the CRT; and (ii) the fact that the half-cycles value was scaled,
// and this should really reply in whole cycles.
2023-01-01 02:47:05 +00:00
return this - > crt_ . get_scaled_scan_status ( ) * ( 4.0f / ( 3.0f * 8.0f ) ) ;
2020-01-21 02:45:10 +00:00
}
2022-12-31 20:08:33 +00:00
template < Personality personality >
void TMS9918 < personality > : : set_display_type ( Outputs : : Display : : DisplayType display_type ) {
2023-01-01 02:47:05 +00:00
this - > crt_ . set_display_type ( display_type ) ;
2018-11-30 04:44:21 +00:00
}
2022-12-31 20:08:33 +00:00
template < Personality personality >
Outputs : : Display : : DisplayType TMS9918 < personality > : : get_display_type ( ) const {
2023-01-01 02:47:05 +00:00
return this - > crt_ . get_display_type ( ) ;
2020-03-18 04:06:52 +00:00
}
2023-03-30 04:20:03 +00:00
void SpriteBuffer : : reset_sprite_collection ( ) {
2018-10-14 20:23:45 +00:00
sprites_stopped = false ;
active_sprite_slot = 0 ;
2018-10-09 02:43:10 +00:00
2018-10-14 20:23:45 +00:00
for ( int c = 0 ; c < 8 ; + + c ) {
active_sprites [ c ] . shift_position = 0 ;
2018-10-09 02:43:10 +00:00
}
2018-10-06 23:27:19 +00:00
}
2023-01-01 02:47:05 +00:00
template < Personality personality >
2023-03-30 04:20:03 +00:00
void Base < personality > : : posit_sprite ( int sprite_number , int sprite_position , uint8_t screen_row ) {
// Evaluation of visibility of sprite 0 is always the first step in
// populating a sprite buffer; so use it to uncork a new one.
if ( ! sprite_number ) {
advance ( fetch_sprite_buffer_ ) ;
2023-04-11 03:03:39 +00:00
fetched_sprites_ = & * fetch_sprite_buffer_ ;
2023-03-30 04:20:03 +00:00
fetch_sprite_buffer_ - > reset_sprite_collection ( ) ;
2023-03-30 23:11:00 +00:00
fetch_sprite_buffer_ - > sprite_terminator = mode_timing_ . sprite_terminator ( fetch_line_buffer_ - > screen_mode ) ;
2023-04-04 02:46:49 +00:00
2023-04-06 03:33:42 +00:00
if constexpr ( SpriteBuffer : : test_is_filling ) {
fetch_sprite_buffer_ - > is_filling = true ;
}
2023-03-30 04:20:03 +00:00
}
2018-10-06 23:27:19 +00:00
if ( ! ( status_ & StatusSpriteOverflow ) ) {
2020-05-10 03:00:39 +00:00
status_ = uint8_t ( ( status_ & ~ 0x1f ) | ( sprite_number & 0x1f ) ) ;
2017-12-06 03:39:03 +00:00
}
2023-03-30 04:20:03 +00:00
if ( fetch_sprite_buffer_ - > sprites_stopped ) return ;
2017-12-06 03:39:03 +00:00
// A sprite Y of 208 means "don't scan the list any further".
2023-03-30 04:20:03 +00:00
if ( mode_timing_ . allow_sprite_terminator & & sprite_position = = fetch_sprite_buffer_ - > sprite_terminator ) {
fetch_sprite_buffer_ - > sprites_stopped = true ;
2017-12-06 03:39:03 +00:00
return ;
}
2023-03-30 04:20:03 +00:00
const auto sprite_row = uint8_t ( screen_row - sprite_position ) ;
2017-12-07 01:24:29 +00:00
if ( sprite_row < 0 | | sprite_row > = sprite_height_ ) return ;
2017-12-09 03:12:39 +00:00
2023-03-30 04:20:03 +00:00
if ( fetch_sprite_buffer_ - > active_sprite_slot = = mode_timing_ . maximum_visible_sprites ) {
2018-10-06 23:27:19 +00:00
status_ | = StatusSpriteOverflow ;
2017-12-06 03:39:03 +00:00
return ;
}
2023-03-30 04:20:03 +00:00
auto & sprite = fetch_sprite_buffer_ - > active_sprites [ fetch_sprite_buffer_ - > active_sprite_slot ] ;
2017-12-09 03:20:21 +00:00
sprite . index = sprite_number ;
2017-12-10 01:30:12 +00:00
sprite . row = sprite_row > > ( sprites_magnified_ ? 1 : 0 ) ;
2023-03-30 04:20:03 +00:00
+ + fetch_sprite_buffer_ - > active_sprite_slot ;
2017-12-06 03:39:03 +00:00
}
2022-12-31 20:08:33 +00:00
template < Personality personality >
void TMS9918 < personality > : : run_for ( const HalfCycles cycles ) {
2017-11-28 00:43:33 +00:00
// As specific as I've been able to get:
2017-12-10 01:30:12 +00:00
// Scanline time is always 228 cycles.
2017-11-28 00:43:33 +00:00
// PAL output is 313 lines total. NTSC output is 262 lines total.
// Interrupt is signalled upon entering the lower border.
2017-12-14 03:37:27 +00:00
// 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
// for this part. So multiply by three quarters.
2023-01-02 20:04:08 +00:00
const int int_cycles = this - > clock_converter_ . to_internal ( cycles . as < int > ( ) ) ;
2017-11-28 02:36:12 +00:00
if ( ! int_cycles ) return ;
2017-11-28 00:43:33 +00:00
2023-01-27 17:30:09 +00:00
// There are two intertwined processes here, 'fetching' (i.e. writing to the
// line buffers with newly-fetched video contents) and 'output' (reading from
// the line buffers and generating video).
int fetch_cycles_pool = int_cycles ;
int output_cycles_pool = int_cycles ;
2018-10-14 20:23:45 +00:00
2023-01-27 17:30:09 +00:00
while ( fetch_cycles_pool | | output_cycles_pool ) {
2020-09-16 22:15:57 +00:00
# ifndef NDEBUG
2023-01-21 01:29:15 +00:00
LineBufferPointer backup = this - > output_pointer_ ;
2020-09-16 22:15:57 +00:00
# endif
2018-10-14 22:19:11 +00:00
2023-01-27 17:30:09 +00:00
if ( fetch_cycles_pool ) {
2023-01-28 03:20:36 +00:00
// Determine how much writing to do; at the absolute most go to the end of this line.
2023-01-27 17:30:09 +00:00
const int fetch_cycles = std : : min (
2023-05-12 03:49:12 +00:00
LineLayout < personality > : : CyclesPerLine - this - > fetch_pointer_ . column ,
2023-01-27 17:30:09 +00:00
fetch_cycles_pool
2023-01-01 19:20:45 +00:00
) ;
2023-01-27 17:30:09 +00:00
const int end_column = this - > fetch_pointer_ . column + fetch_cycles ;
2018-10-14 20:23:45 +00:00
2023-01-28 03:20:36 +00:00
// ... and to any pending Yamaha commands.
if constexpr ( is_yamaha_vdp ( personality ) ) {
if ( Storage < personality > : : command_ ) {
Storage < personality > : : minimum_command_column_ =
this - > fetch_pointer_ . column + Storage < personality > : : command_ - > cycles ;
Storage < personality > : : command_ - > cycles - = fetch_cycles ;
}
}
2018-10-02 03:03:17 +00:00
2018-10-14 20:23:45 +00:00
// ---------------------------------------
// Latch scrolling position, if necessary.
// ---------------------------------------
2023-05-18 20:50:46 +00:00
// TODO: shouldn't this happen one per frame?
2023-01-01 02:47:05 +00:00
if constexpr ( is_sega_vdp ( personality ) ) {
2023-05-18 20:50:46 +00:00
constexpr auto latch_time = to_internal < personality , Clock : : Grauw > ( 61 ) ; // TODO: where did this magic constant come from? Is it the same for the Game Gear, etc?
if ( this - > fetch_pointer_ . column < latch_time & & end_column > = latch_time ) {
2023-01-21 01:29:15 +00:00
if ( ! this - > fetch_pointer_ . row ) {
2023-01-19 20:09:16 +00:00
Storage < personality > : : latched_vertical_scroll_ = Storage < personality > : : vertical_scroll_ ;
2018-10-24 01:20:44 +00:00
2023-01-19 20:09:16 +00:00
if ( Storage < personality > : : mode4_enable_ ) {
2023-01-01 02:47:05 +00:00
this - > mode_timing_ . pixel_lines = 192 ;
if ( this - > mode2_enable_ & & this - > mode1_enable_ ) this - > mode_timing_ . pixel_lines = 224 ;
if ( this - > mode2_enable_ & & this - > mode3_enable_ ) this - > mode_timing_ . pixel_lines = 240 ;
2018-10-24 01:20:44 +00:00
2023-01-01 02:47:05 +00:00
this - > mode_timing_ . allow_sprite_terminator = this - > mode_timing_ . pixel_lines = = 192 ;
this - > mode_timing_ . first_vsync_line = ( this - > mode_timing_ . total_lines + this - > mode_timing_ . pixel_lines ) > > 1 ;
2018-10-24 01:20:44 +00:00
2023-01-01 02:47:05 +00:00
this - > mode_timing_ . end_of_frame_interrupt_position . row = this - > mode_timing_ . pixel_lines + 1 ;
2018-10-24 01:20:44 +00:00
}
}
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > latched_horizontal_scroll = Storage < personality > : : horizontal_scroll_ ;
2018-10-14 20:23:45 +00:00
}
2018-10-12 02:36:27 +00:00
}
2018-10-14 20:23:45 +00:00
// ------------------------
// Perform memory accesses.
// ------------------------
2023-02-17 03:46:19 +00:00
# define fetch(function, clock, offset) { \
const int first_window = from_internal < personality , clock > ( this - > fetch_pointer_ . column ) ; \
const int final_window = from_internal < personality , clock > ( end_column ) ; \
if ( first_window = = final_window ) break ; \
2023-03-31 03:45:19 +00:00
const auto y = uint8_t ( \
this - > fetch_line_buffer_ - > vertical_state = = VerticalState : : Prefetch ? \
offset - 1 : ( this - > fetch_pointer_ . row + offset ) ) ; \
2023-02-17 03:46:19 +00:00
if ( final_window ! = clock_rate < personality , clock > ( ) ) { \
2023-03-31 03:45:19 +00:00
function < true > ( y , first_window , final_window ) ; \
2023-02-17 03:46:19 +00:00
} else { \
2023-03-31 03:45:19 +00:00
function < false > ( y , first_window , final_window ) ; \
2023-02-17 03:46:19 +00:00
} \
2023-01-22 03:47:16 +00:00
}
2023-03-31 03:45:19 +00:00
2023-03-08 23:28:13 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
fetch ( this - > template fetch_yamaha , Clock : : Internal , Storage < personality > : : vertical_offset_ ) ;
} else {
2023-03-30 04:20:03 +00:00
switch ( this - > fetch_line_buffer_ - > fetch_mode ) {
2023-03-08 23:28:13 +00:00
case FetchMode : : Text : fetch ( this - > template fetch_tms_text , Clock : : TMSMemoryWindow , 0 ) ; break ;
case FetchMode : : Character : fetch ( this - > template fetch_tms_character , Clock : : TMSMemoryWindow , 0 ) ; break ;
case FetchMode : : SMS : fetch ( this - > template fetch_sms , Clock : : TMSMemoryWindow , 0 ) ; break ;
case FetchMode : : Refresh : fetch ( this - > template fetch_tms_refresh , Clock : : TMSMemoryWindow , 0 ) ; break ;
default : break ;
}
2018-10-03 01:05:30 +00:00
}
2017-12-14 03:37:27 +00:00
2018-10-02 03:03:17 +00:00
# undef fetch
2017-12-14 03:37:27 +00:00
2018-10-14 20:23:45 +00:00
// -------------------------------
// Check for interrupt conditions.
// -------------------------------
2023-05-16 17:01:23 +00:00
if constexpr ( is_sega_vdp ( personality ) ) {
2018-10-14 20:23:45 +00:00
// 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.
2023-05-16 17:01:23 +00:00
if (
this - > fetch_pointer_ . column < this - > mode_timing_ . line_interrupt_position & &
end_column > = this - > mode_timing_ . line_interrupt_position
) {
2023-01-21 01:29:15 +00:00
if ( this - > fetch_pointer_ . row > = 0 & & this - > fetch_pointer_ . row < = this - > mode_timing_ . pixel_lines ) {
2023-03-11 02:04:35 +00:00
if ( ! this - > line_interrupt_counter_ ) {
2023-01-01 02:47:05 +00:00
this - > line_interrupt_pending_ = true ;
2023-01-19 20:09:16 +00:00
this - > line_interrupt_counter_ = this - > line_interrupt_target_ ;
2023-03-11 02:04:35 +00:00
} else {
- - this - > line_interrupt_counter_ ;
2018-10-14 20:23:45 +00:00
}
} else {
2023-01-19 20:09:16 +00:00
this - > line_interrupt_counter_ = this - > line_interrupt_target_ ;
2018-10-14 20:23:45 +00:00
}
}
2023-05-16 17:01:23 +00:00
}
2018-10-14 20:23:45 +00:00
2023-05-16 17:01:23 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
// The Yamaha VDPs allow the user to specify which line an interrupt should occur on,
// which is relative to the current vertical base. Such an interrupt will occur immediately
// after pixels have ended.
if (
this - > vertical_active_ & &
this - > fetch_pointer_ . column < Storage < personality > : : mode_description_ . end_cycle & &
end_column > = Storage < personality > : : mode_description_ . end_cycle & &
this - > fetch_pointer_ . row = = ( ( this - > line_interrupt_target_ - Storage < personality > : : vertical_offset_ ) & 0xff )
) {
this - > line_interrupt_pending_ = true ;
Storage < personality > : : line_matches_ = true ;
}
if (
this - > fetch_pointer_ . column < Storage < personality > : : mode_description_ . start_cycle & &
end_column > = Storage < personality > : : mode_description_ . start_cycle
) {
Storage < personality > : : line_matches_ = false ;
2023-01-31 02:24:53 +00:00
}
2018-10-14 20:23:45 +00:00
}
if (
2023-01-21 01:29:15 +00:00
this - > fetch_pointer_ . row = = this - > mode_timing_ . end_of_frame_interrupt_position . row & &
this - > fetch_pointer_ . column < this - > mode_timing_ . end_of_frame_interrupt_position . column & &
2023-01-01 02:47:05 +00:00
end_column > = this - > mode_timing_ . end_of_frame_interrupt_position . column
2018-10-14 20:23:45 +00:00
) {
2023-01-01 02:47:05 +00:00
this - > status_ | = StatusInterrupt ;
2018-10-14 20:23:45 +00:00
}
// -------------
// Advance time.
// -------------
2023-01-21 01:29:15 +00:00
this - > fetch_pointer_ . column = end_column ;
2023-01-27 17:30:09 +00:00
fetch_cycles_pool - = fetch_cycles ;
2018-10-14 20:23:45 +00:00
2023-01-28 16:55:12 +00:00
// Check for end of line.
2023-05-12 03:49:12 +00:00
if ( this - > fetch_pointer_ . column = = LineLayout < personality > : : CyclesPerLine ) {
2023-01-21 01:29:15 +00:00
this - > fetch_pointer_ . column = 0 ;
this - > fetch_pointer_ . row = ( this - > fetch_pointer_ . row + 1 ) % this - > mode_timing_ . total_lines ;
2023-02-18 02:59:39 +00:00
2023-03-01 03:28:14 +00:00
this - > vertical_active_ | = ! this - > fetch_pointer_ . row ;
this - > vertical_active_ & = this - > fetch_pointer_ . row ! = this - > mode_timing_ . pixel_lines ;
2023-02-18 02:59:39 +00:00
// Yamaha: handle blinking.
2023-02-16 01:18:56 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
if ( ! this - > fetch_pointer_ . row & & Storage < personality > : : blink_periods_ ) {
- - Storage < personality > : : blink_counter_ ;
while ( ! Storage < personality > : : blink_counter_ ) {
Storage < personality > : : in_blink_ ^ = 1 ;
Storage < personality > : : blink_counter_ = ( Storage < personality > : : blink_periods_ > > ( Storage < personality > : : in_blink_ < < 2 ) ) & 0xf ;
}
}
}
2023-01-28 16:55:12 +00:00
// Progress towards any delayed events.
this - > minimum_access_column_ =
std : : max (
0 ,
2023-05-12 03:49:12 +00:00
this - > minimum_access_column_ - LineLayout < personality > : : CyclesPerLine
2023-01-28 16:55:12 +00:00
) ;
if constexpr ( is_yamaha_vdp ( personality ) ) {
Storage < personality > : : minimum_command_column_ =
std : : max (
0 ,
2023-05-12 03:49:12 +00:00
Storage < personality > : : minimum_command_column_ - LineLayout < personality > : : CyclesPerLine
2023-01-28 16:55:12 +00:00
) ;
}
2023-03-30 04:20:03 +00:00
this - > advance ( this - > fetch_line_buffer_ ) ;
2023-04-11 03:13:36 +00:00
if ( this - > fetched_sprites_ & & this - > fetched_sprites_ - > active_sprite_slot ) {
2023-04-11 03:03:39 +00:00
this - > fetch_line_buffer_ - > sprites = this - > fetched_sprites_ ;
this - > fetched_sprites_ = nullptr ;
} else {
this - > fetch_line_buffer_ - > sprites = nullptr ;
}
2023-03-30 04:20:03 +00:00
2023-01-07 19:37:06 +00:00
// Establish the current screen output mode, which will be captured as a
// line mode momentarily.
2023-02-02 17:03:33 +00:00
this - > screen_mode_ = this - > template current_screen_mode < true > ( ) ;
this - > underlying_mode_ = this - > template current_screen_mode < false > ( ) ;
2018-10-14 20:23:45 +00:00
2023-03-19 03:07:33 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
auto & desc = Storage < personality > : : mode_description_ ;
desc . pixels_per_byte = pixels_per_byte ( this - > underlying_mode_ ) ;
desc . width = width ( this - > underlying_mode_ ) ;
desc . rotate_address = interleaves_banks ( this - > underlying_mode_ ) ;
2023-05-16 17:01:23 +00:00
if ( is_text ( this - > underlying_mode_ ) ) {
desc . start_cycle = LineLayout < personality > : : TextModeEndOfLeftBorder ;
desc . end_cycle = LineLayout < personality > : : TextModeEndOfPixels ;
} else {
desc . start_cycle = LineLayout < personality > : : EndOfLeftBorder ;
desc . end_cycle = LineLayout < personality > : : EndOfPixels ;
}
2023-03-19 03:07:33 +00:00
}
2018-10-14 20:23:45 +00:00
// Based on the output mode, pick a line mode.
2023-04-24 02:18:36 +00:00
this - > fetch_line_buffer_ - > first_pixel_output_column = LineLayout < personality > : : EndOfLeftBorder ;
this - > fetch_line_buffer_ - > next_border_column = LineLayout < personality > : : EndOfPixels ;
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > pixel_count = 256 ;
this - > fetch_line_buffer_ - > screen_mode = this - > screen_mode_ ;
2023-01-01 02:47:05 +00:00
this - > mode_timing_ . maximum_visible_sprites = 4 ;
switch ( this - > screen_mode_ ) {
2018-10-14 20:23:45 +00:00
case ScreenMode : : Text :
2023-02-14 03:24:39 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > fetch_mode = FetchMode : : Yamaha ;
2023-02-14 03:24:39 +00:00
} else {
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > fetch_mode = FetchMode : : Text ;
2023-02-14 03:24:39 +00:00
}
2023-04-24 02:18:36 +00:00
this - > fetch_line_buffer_ - > first_pixel_output_column = LineLayout < personality > : : TextModeEndOfLeftBorder ;
this - > fetch_line_buffer_ - > next_border_column = LineLayout < personality > : : TextModeEndOfPixels ;
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > pixel_count = 240 ;
2018-10-14 20:23:45 +00:00
break ;
2023-02-15 01:13:51 +00:00
case ScreenMode : : YamahaText80 :
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > fetch_mode = FetchMode : : Yamaha ;
2023-04-24 02:18:36 +00:00
this - > fetch_line_buffer_ - > first_pixel_output_column = LineLayout < personality > : : TextModeEndOfLeftBorder ;
this - > fetch_line_buffer_ - > next_border_column = LineLayout < personality > : : TextModeEndOfPixels ;
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > pixel_count = 480 ;
2023-02-15 01:13:51 +00:00
break ;
2018-10-14 20:23:45 +00:00
case ScreenMode : : SMSMode4 :
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > fetch_mode = FetchMode : : SMS ;
2023-01-01 02:47:05 +00:00
this - > mode_timing_ . maximum_visible_sprites = 8 ;
2018-10-14 20:23:45 +00:00
break ;
2023-02-15 01:13:51 +00:00
2023-01-21 19:35:26 +00:00
case ScreenMode : : YamahaGraphics3 :
case ScreenMode : : YamahaGraphics4 :
2023-01-25 04:07:29 +00:00
case ScreenMode : : YamahaGraphics7 :
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > fetch_mode = FetchMode : : Yamaha ;
2023-01-25 04:07:29 +00:00
this - > mode_timing_ . maximum_visible_sprites = 8 ;
break ;
2023-01-21 19:35:26 +00:00
case ScreenMode : : YamahaGraphics5 :
case ScreenMode : : YamahaGraphics6 :
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > pixel_count = 512 ;
this - > fetch_line_buffer_ - > fetch_mode = FetchMode : : Yamaha ;
2023-01-25 04:07:29 +00:00
this - > mode_timing_ . maximum_visible_sprites = 8 ;
2023-01-21 19:35:26 +00:00
break ;
2018-10-14 20:23:45 +00:00
default :
2023-01-21 19:35:26 +00:00
// This covers both MultiColour and Graphics modes.
2023-02-17 03:07:18 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > fetch_mode = FetchMode : : Yamaha ;
2023-02-17 03:07:18 +00:00
} else {
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > fetch_mode = FetchMode : : Character ;
2023-02-17 03:07:18 +00:00
}
2018-10-14 20:23:45 +00:00
break ;
}
2023-04-25 02:43:11 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
this - > fetch_line_buffer_ - > first_pixel_output_column + = Storage < personality > : : adjustment_ [ 0 ] ;
this - > fetch_line_buffer_ - > next_border_column + = Storage < personality > : : adjustment_ [ 0 ] ;
}
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > vertical_state =
2023-02-02 03:25:00 +00:00
this - > screen_mode_ = = ScreenMode : : Blank ?
VerticalState : : Blank :
this - > vertical_state ( ) ;
2023-03-30 04:20:03 +00:00
const bool is_refresh = this - > fetch_line_buffer_ - > vertical_state = = VerticalState : : Blank ;
2023-01-23 03:00:28 +00:00
2023-02-16 01:18:56 +00:00
Storage < personality > : : begin_line ( this - > screen_mode_ , is_refresh ) ;
2023-01-23 03:11:01 +00:00
2023-02-02 03:25:00 +00:00
if ( is_refresh ) {
2023-01-23 03:11:01 +00:00
// The Yamaha handles refresh lines via its own microprogram; other VDPs
// can fall back on the regular refresh mechanic.
if constexpr ( is_yamaha_vdp ( personality ) ) {
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > fetch_mode = FetchMode : : Yamaha ;
2023-01-23 03:11:01 +00:00
} else {
2023-03-30 04:20:03 +00:00
this - > fetch_line_buffer_ - > fetch_mode = FetchMode : : Refresh ;
2023-01-23 03:11:01 +00:00
}
}
2018-10-14 20:23:45 +00:00
}
}
2020-09-16 22:15:57 +00:00
# ifndef NDEBUG
2023-01-21 01:29:15 +00:00
assert ( backup . row = = this - > output_pointer_ . row & & backup . column = = this - > output_pointer_ . column ) ;
backup = this - > fetch_pointer_ ;
2020-09-16 22:15:57 +00:00
# endif
2018-10-14 22:19:11 +00:00
2023-01-27 17:30:09 +00:00
if ( output_cycles_pool ) {
2018-10-14 20:23:45 +00:00
// Determine how much time has passed in the remainder of this line, and proceed.
2023-01-27 17:30:09 +00:00
const int target_output_cycles = std : : min (
2023-05-12 03:49:12 +00:00
LineLayout < personality > : : CyclesPerLine - this - > output_pointer_ . column ,
2023-01-27 17:30:09 +00:00
output_cycles_pool
2023-01-01 19:20:45 +00:00
) ;
2023-01-27 17:30:09 +00:00
int output_cycles_performed = 0 ;
2018-10-26 23:26:46 +00:00
uint32_t next_cram_value = 0 ;
2023-01-27 17:30:09 +00:00
while ( output_cycles_performed < target_output_cycles ) {
int output_cycles = target_output_cycles - output_cycles_performed ;
if ( ! output_cycles ) continue ;
2023-01-01 19:20:45 +00:00
2023-01-07 19:57:32 +00:00
// Grab the next CRAM dot value and schedule a break in output if applicable.
2018-10-26 23:26:46 +00:00
const uint32_t cram_value = next_cram_value ;
2023-01-01 19:20:45 +00:00
if constexpr ( is_sega_vdp ( personality ) ) {
2023-01-07 19:57:32 +00:00
next_cram_value = 0 ;
2023-01-21 01:29:15 +00:00
if ( ! this - > upcoming_cram_dots_ . empty ( ) & & this - > upcoming_cram_dots_ . front ( ) . location . row = = this - > output_pointer_ . row ) {
int time_until_dot = this - > upcoming_cram_dots_ . front ( ) . location . column - this - > output_pointer_ . column ;
2018-10-26 23:26:46 +00:00
2023-01-27 17:30:09 +00:00
if ( time_until_dot < output_cycles ) {
output_cycles = time_until_dot ;
2023-01-01 19:20:45 +00:00
next_cram_value = this - > upcoming_cram_dots_ . front ( ) . value ;
this - > upcoming_cram_dots_ . erase ( this - > upcoming_cram_dots_ . begin ( ) ) ;
}
2018-10-26 23:26:46 +00:00
}
}
2018-10-14 20:23:45 +00:00
2023-01-27 17:30:09 +00:00
output_cycles_performed + = output_cycles ;
2018-10-14 20:23:45 +00:00
2023-01-27 17:30:09 +00:00
const int end_column = this - > output_pointer_ . column + output_cycles ;
2018-10-26 03:12:03 +00:00
2018-10-14 20:23:45 +00:00
2018-10-26 23:26:46 +00:00
// --------------------
// Output video stream.
// --------------------
2017-12-03 03:13:43 +00:00
2023-01-10 03:34:56 +00:00
# define crt_convert(action, time) this->crt_.action(from_internal<personality, Clock::CRT>(time))
2023-01-08 19:10:06 +00:00
# define output_sync(x) crt_convert(output_sync, x)
# define output_blank(x) crt_convert(output_blank, x)
# define output_default_colour_burst(x) crt_convert(output_default_colour_burst, x)
2019-12-22 05:00:23 +00:00
# define intersect(left, right, code) { \
2023-01-21 01:29:15 +00:00
const int start = std : : max ( this - > output_pointer_ . column , left ) ; \
2018-10-03 01:18:28 +00:00
const int end = std : : min ( end_column , right ) ; \
if ( end > start ) { \
code ; \
} \
}
2023-01-01 02:47:05 +00:00
# define border(left, right) intersect(left, right, this->output_border(end - start, cram_value))
2018-10-26 23:26:46 +00:00
2023-04-24 02:02:41 +00:00
const auto left_blank = [ & ] ( ) {
// Blanking region: output the entire sequence when the cursor
// crosses the start-of-border point.
if (
this - > output_pointer_ . column < LineLayout < personality > : : EndOfLeftErase & &
end_column > = LineLayout < personality > : : EndOfLeftErase
) {
output_sync ( LineLayout < personality > : : EndOfSync ) ;
output_blank ( LineLayout < personality > : : StartOfColourBurst - LineLayout < personality > : : EndOfSync ) ;
output_default_colour_burst ( LineLayout < personality > : : EndOfColourBurst - LineLayout < personality > : : StartOfColourBurst ) ;
output_blank ( LineLayout < personality > : : EndOfLeftErase - LineLayout < personality > : : EndOfColourBurst ) ;
}
} ;
2023-04-24 02:21:22 +00:00
const auto right_blank = [ & ] ( ) {
2023-05-12 03:49:12 +00:00
if ( end_column = = LineLayout < personality > : : CyclesPerLine ) {
output_blank ( LineLayout < personality > : : CyclesPerLine - LineLayout < personality > : : EndOfRightBorder ) ;
2023-04-24 02:21:22 +00:00
}
} ;
2023-03-30 04:20:03 +00:00
if ( this - > draw_line_buffer_ - > vertical_state ! = VerticalState : : Pixels ) {
2023-01-07 19:57:32 +00:00
if (
2023-01-21 01:29:15 +00:00
this - > output_pointer_ . row > = this - > mode_timing_ . first_vsync_line & &
this - > output_pointer_ . row < this - > mode_timing_ . first_vsync_line + 4
2023-01-07 19:57:32 +00:00
) {
2018-10-26 23:26:46 +00:00
// Vertical sync.
2023-01-25 04:07:29 +00:00
// TODO: the Yamaha and Mega Drive both support interlaced video.
2023-05-12 03:49:12 +00:00
if ( end_column = = LineLayout < personality > : : CyclesPerLine ) {
output_sync ( LineLayout < personality > : : CyclesPerLine ) ;
2018-10-26 23:26:46 +00:00
}
} else {
2023-04-24 02:02:41 +00:00
left_blank ( ) ;
2023-04-23 16:17:55 +00:00
border ( LineLayout < personality > : : EndOfLeftErase , LineLayout < personality > : : EndOfRightBorder ) ;
2023-04-24 02:21:22 +00:00
right_blank ( ) ;
2018-10-14 20:23:45 +00:00
}
} else {
2023-04-24 02:02:41 +00:00
left_blank ( ) ;
2018-10-14 20:23:45 +00:00
2018-10-26 23:26:46 +00:00
// Left border.
2023-04-23 16:17:55 +00:00
border ( LineLayout < personality > : : EndOfLeftErase , this - > draw_line_buffer_ - > first_pixel_output_column ) ;
2018-10-26 23:26:46 +00:00
2023-03-30 04:20:03 +00:00
# define draw(function, clock) { \
const int relative_start = from_internal < personality , clock > ( start - this - > draw_line_buffer_ - > first_pixel_output_column ) ; \
const int relative_end = from_internal < personality , clock > ( end - this - > draw_line_buffer_ - > first_pixel_output_column ) ; \
if ( relative_start = = relative_end ) break ; \
2023-01-08 22:31:08 +00:00
this - > function ; }
2018-10-26 23:26:46 +00:00
// Pixel region.
intersect (
2023-03-30 04:20:03 +00:00
this - > draw_line_buffer_ - > first_pixel_output_column ,
this - > draw_line_buffer_ - > next_border_column ,
2023-01-01 02:47:05 +00:00
if ( ! this - > asked_for_write_area_ ) {
this - > asked_for_write_area_ = true ;
2023-01-09 02:25:22 +00:00
2023-01-01 02:47:05 +00:00
this - > pixel_origin_ = this - > pixel_target_ = reinterpret_cast < uint32_t * > (
2023-03-30 04:20:03 +00:00
this - > crt_ . begin_data ( size_t ( this - > draw_line_buffer_ - > pixel_count ) )
2018-10-26 23:26:46 +00:00
) ;
}
2018-03-03 04:08:01 +00:00
2023-01-01 02:47:05 +00:00
if ( this - > pixel_target_ ) {
2023-03-17 02:00:47 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
2023-05-16 20:16:10 +00:00
draw ( draw_yamaha ( 0 , relative_start , relative_end ) , Clock : : Internal ) ; // TODO: what is the correct 'y'?
2023-03-17 02:00:47 +00:00
} else {
2023-03-30 04:20:03 +00:00
switch ( this - > draw_line_buffer_ - > fetch_mode ) {
2023-03-17 02:00:47 +00:00
case FetchMode : : SMS : draw ( draw_sms ( relative_start , relative_end , cram_value ) , Clock : : TMSPixel ) ; break ;
case FetchMode : : Character : draw ( draw_tms_character ( relative_start , relative_end ) , Clock : : TMSPixel ) ; break ;
2023-03-30 04:20:03 +00:00
case FetchMode : : Text : draw ( template draw_tms_text < false > ( relative_start , relative_end ) , Clock : : TMSPixel ) ; break ;
2023-03-17 02:00:47 +00:00
default : break ; /* Dealt with elsewhere. */
}
2018-10-26 23:26:46 +00:00
}
}
2017-12-01 03:19:53 +00:00
2023-03-30 04:20:03 +00:00
if ( end = = this - > draw_line_buffer_ - > next_border_column ) {
const int length = this - > draw_line_buffer_ - > next_border_column - this - > draw_line_buffer_ - > first_pixel_output_column ;
this - > crt_ . output_data ( from_internal < personality , Clock : : CRT > ( length ) , size_t ( this - > draw_line_buffer_ - > pixel_count ) ) ;
2023-01-01 02:47:05 +00:00
this - > pixel_origin_ = this - > pixel_target_ = nullptr ;
this - > asked_for_write_area_ = false ;
2018-10-14 20:23:45 +00:00
}
2018-10-26 23:26:46 +00:00
) ;
2017-12-02 22:48:31 +00:00
2023-01-08 22:31:08 +00:00
# undef draw
2023-04-23 16:17:55 +00:00
// Right border.
border ( this - > draw_line_buffer_ - > next_border_column , LineLayout < personality > : : EndOfRightBorder ) ;
2023-04-24 02:21:22 +00:00
right_blank ( ) ;
2018-10-05 02:50:35 +00:00
}
2018-10-10 00:51:09 +00:00
2018-10-26 03:12:03 +00:00
# undef border
# undef intersect
2018-10-05 02:50:35 +00:00
2023-01-08 19:10:06 +00:00
# undef crt_convert
# undef output_sync
# undef output_blank
# undef output_default_colour_burst
2018-10-11 01:07:39 +00:00
2018-10-05 02:50:35 +00:00
2018-10-26 23:26:46 +00:00
// -------------
// Advance time.
// -------------
2023-01-21 01:29:15 +00:00
this - > output_pointer_ . column = end_column ;
2023-05-12 03:49:12 +00:00
if ( end_column = = LineLayout < personality > : : CyclesPerLine ) {
2023-04-03 02:45:02 +00:00
// Advance line buffer.
2023-03-30 04:20:03 +00:00
this - > advance ( this - > draw_line_buffer_ ) ;
}
2018-10-26 23:26:46 +00:00
}
2018-10-05 02:50:35 +00:00
2023-01-27 17:30:09 +00:00
output_cycles_pool - = target_output_cycles ;
2023-05-12 03:49:12 +00:00
if ( this - > output_pointer_ . column = = LineLayout < personality > : : CyclesPerLine ) {
2023-01-21 01:29:15 +00:00
this - > output_pointer_ . column = 0 ;
this - > output_pointer_ . row = ( this - > output_pointer_ . row + 1 ) % this - > mode_timing_ . total_lines ;
2017-11-29 02:10:30 +00:00
}
2017-11-28 00:43:33 +00:00
}
2018-10-14 22:19:11 +00:00
2023-01-21 01:29:15 +00:00
assert ( backup . row = = this - > fetch_pointer_ . row & & backup . column = = this - > fetch_pointer_ . column ) ;
2017-11-28 00:43:33 +00:00
}
2017-11-26 18:28:26 +00:00
}
2017-11-26 21:47:59 +00:00
2023-01-01 02:47:05 +00:00
template < Personality personality >
2023-01-08 22:04:19 +00:00
void Base < personality > : : output_border ( int cycles , [[maybe_unused]] uint32_t cram_dot ) {
2023-01-10 03:34:56 +00:00
cycles = from_internal < personality , Clock : : CRT > ( cycles ) ;
2018-10-26 23:26:46 +00:00
2023-01-19 19:09:31 +00:00
uint32_t border_colour ;
2023-01-08 22:04:19 +00:00
if constexpr ( is_sega_vdp ( personality ) ) {
2023-01-19 19:09:31 +00:00
border_colour = Storage < personality > : : colour_ram_ [ 16 + background_colour_ ] ;
2023-01-08 22:04:19 +00:00
if ( cram_dot ) {
2018-11-17 23:23:42 +00:00
uint32_t * const pixel_target = reinterpret_cast < uint32_t * > ( crt_ . begin_data ( 1 ) ) ;
if ( pixel_target ) {
2023-01-08 22:04:19 +00:00
* pixel_target = border_colour | cram_dot ;
2018-11-17 23:23:42 +00:00
}
2023-01-08 22:04:19 +00:00
// Four CRT cycles is one pixel width, so this doesn't need clock conversion.
// TODO: on the Mega Drive it may be only 3 colour cycles, depending on mode.
crt_ . output_level ( 4 ) ;
cycles - = 4 ;
}
2023-01-19 19:09:31 +00:00
} else {
2023-01-30 02:17:00 +00:00
border_colour = palette ( ) [ background_colour_ ] ;
2023-01-08 22:04:19 +00:00
}
if ( ! cycles ) {
return ;
}
// If the border colour is 0, that can be communicated
// more efficiently as an explicit blank.
if ( border_colour ) {
uint32_t * const pixel_target = reinterpret_cast < uint32_t * > ( crt_ . begin_data ( 1 ) ) ;
if ( pixel_target ) {
* pixel_target = border_colour ;
2018-11-15 03:32:33 +00:00
}
2023-01-08 22:04:19 +00:00
crt_ . output_level ( cycles ) ;
} else {
crt_ . output_blank ( cycles ) ;
2018-10-02 03:03:17 +00:00
}
2017-11-28 03:05:40 +00:00
}
2023-01-18 17:36:57 +00:00
// MARK: - External interface.
2022-12-31 20:08:33 +00:00
template < Personality personality >
2023-01-21 03:29:49 +00:00
int Base < personality > : : masked_address ( int address ) const {
2023-01-18 17:36:57 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
return address & 3 ;
} else {
return address & 1 ;
}
}
2017-12-14 03:37:27 +00:00
2023-01-18 17:36:57 +00:00
template < Personality personality >
void Base < personality > : : write_vram ( uint8_t value ) {
2023-01-19 17:32:42 +00:00
write_phase_ = false ;
2017-12-14 03:37:27 +00:00
2023-01-18 17:36:57 +00:00
// Enqueue the write to occur at the next available slot.
2023-01-19 17:32:42 +00:00
read_ahead_buffer_ = value ;
queued_access_ = MemoryAccess : : Write ;
2023-05-12 03:49:12 +00:00
minimum_access_column_ = fetch_pointer_ . column + LineLayout < personality > : : VRAMAccessDelay ;
2023-01-18 17:36:57 +00:00
}
2017-11-27 01:01:11 +00:00
2023-01-18 17:36:57 +00:00
template < Personality personality >
2023-01-21 04:14:57 +00:00
void Base < personality > : : commit_register ( int reg , uint8_t value ) {
if constexpr ( is_yamaha_vdp ( personality ) ) {
reg & = 0x3f ;
} else if constexpr ( is_sega_vdp ( personality ) ) {
if ( reg & 0x40 ) {
Storage < personality > : : cram_is_selected_ = true ;
return ;
}
reg & = 0xf ;
} else {
reg & = 0x7 ;
}
2018-10-11 01:59:08 +00:00
2023-01-21 19:12:46 +00:00
//
// Generic TMS functionality.
//
2023-01-21 04:14:57 +00:00
switch ( reg ) {
case 0 :
mode2_enable_ = value & 0x02 ;
break ;
case 1 :
blank_display_ = ! ( value & 0x40 ) ;
generate_interrupts_ = value & 0x20 ;
mode1_enable_ = value & 0x10 ;
mode3_enable_ = value & 0x08 ;
sprites_16x16_ = value & 0x02 ;
sprites_magnified_ = value & 0x01 ;
sprite_height_ = 8 ;
if ( sprites_16x16_ ) sprite_height_ < < = 1 ;
if ( sprites_magnified_ ) sprite_height_ < < = 1 ;
break ;
2023-02-01 19:17:49 +00:00
case 2 : install_field < 10 > ( pattern_name_address_ , value ) ; break ;
case 3 : install_field < 6 > ( colour_table_address_ , value ) ; break ;
case 4 : install_field < 11 > ( pattern_generator_table_address_ , value ) ; break ;
case 5 : install_field < 7 > ( sprite_attribute_table_address_ , value ) ; break ;
case 6 : install_field < 11 > ( sprite_generator_table_address_ , value ) ; break ;
2023-01-21 04:14:57 +00:00
case 7 :
text_colour_ = value > > 4 ;
background_colour_ = value & 0xf ;
break ;
default : break ;
2017-11-27 01:01:11 +00:00
}
2023-01-21 19:12:46 +00:00
//
// Sega extensions.
//
2023-01-21 04:14:57 +00:00
if constexpr ( is_sega_vdp ( personality ) ) {
switch ( reg ) {
default : break ;
2018-10-11 01:47:48 +00:00
2023-01-21 04:14:57 +00:00
case 0 :
Storage < personality > : : vertical_scroll_lock_ = value & 0x80 ;
Storage < personality > : : horizontal_scroll_lock_ = value & 0x40 ;
Storage < personality > : : hide_left_column_ = value & 0x20 ;
enable_line_interrupts_ = value & 0x10 ;
Storage < personality > : : shift_sprites_8px_left_ = value & 0x08 ;
Storage < personality > : : mode4_enable_ = value & 0x04 ;
break ;
case 2 :
Storage < personality > : : pattern_name_address_ = pattern_name_address_ | ( ( personality = = TMS : : SMSVDP ) ? 0x000 : 0x400 ) ;
break ;
case 5 :
Storage < personality > : : sprite_attribute_table_address_ = sprite_attribute_table_address_ | ( ( personality = = TMS : : SMSVDP ) ? 0x00 : 0x80 ) ;
break ;
case 6 :
Storage < personality > : : sprite_generator_table_address_ = sprite_generator_table_address_ | ( ( personality = = TMS : : SMSVDP ) ? 0x0000 : 0x1800 ) ;
break ;
case 8 :
Storage < personality > : : horizontal_scroll_ = value ;
break ;
case 9 :
Storage < personality > : : vertical_scroll_ = value ;
break ;
case 10 :
line_interrupt_target_ = value ;
break ;
2018-09-23 19:58:23 +00:00
}
2023-01-21 04:14:57 +00:00
}
2023-01-21 19:12:46 +00:00
//
// Yamaha extensions.
//
2023-01-21 04:14:57 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
switch ( reg ) {
default : break ;
2018-09-23 19:58:23 +00:00
2017-11-27 01:01:11 +00:00
case 0 :
2023-01-21 19:35:26 +00:00
Storage < personality > : : mode_ = uint8_t (
( Storage < personality > : : mode_ & 3 ) |
( ( value & 0xe ) < < 1 )
) ;
2023-01-31 02:24:53 +00:00
enable_line_interrupts_ = value & 0x10 ;
2023-01-21 19:35:26 +00:00
2023-01-21 04:14:57 +00:00
// b1– b3: M3– M5
// b4: enable horizontal retrace interrupt
// b5: enable light pen interrupts
// b6: set colour bus to input or output mode
2017-11-27 01:01:11 +00:00
break ;
2023-01-21 19:35:26 +00:00
case 1 :
Storage < personality > : : mode_ = uint8_t (
( Storage < personality > : : mode_ & 0x1c ) |
( ( value & 0x10 ) > > 4 ) |
( ( value & 0x08 ) > > 2 )
) ;
break ;
2023-02-26 18:42:59 +00:00
case 7 :
Storage < personality > : : background_palette_ [ 0 ] = Storage < personality > : : palette_ [ background_colour_ ] ;
break ;
2023-01-21 04:14:57 +00:00
case 8 :
2023-02-26 18:42:59 +00:00
Storage < personality > : : solid_background_ = value & 0x20 ;
2023-03-17 02:00:47 +00:00
Storage < personality > : : sprites_enabled_ = ! ( value & 0x02 ) ;
if ( value & 0x01 ) {
LOG ( " TODO: Yamaha greyscale " ) ;
}
2023-01-21 04:14:57 +00:00
// b7: "1 = input on colour bus, enable mouse; 1 = output on colour bus, disable mouse" [documentation clearly in error]
// b6: 1 = enable light pen
// b5: sets the colour of code 0 to the colour of the palette (???)
// b4: 1 = colour bus in input mode; 0 = colour bus in output mode
// b3: 1 = VRAM is 64kx1 or 64kx4; 0 = 16kx1 or 16kx4; affects refresh.
// b1: 1 = disable sprites (and release sprite access slots)
// b0: 1 = output in grayscale
2017-11-27 01:01:11 +00:00
break ;
2023-01-21 04:14:57 +00:00
case 9 :
2023-03-01 03:28:14 +00:00
mode_timing_ . pixel_lines = ( value & 0x80 ) ? 212 : 192 ;
2023-03-08 03:12:06 +00:00
mode_timing_ . end_of_frame_interrupt_position . row = mode_timing_ . pixel_lines + 1 ;
// TODO: on the Yamaha, at least, tie this interrupt overtly to vertical state.
2023-03-17 02:00:47 +00:00
if ( value & 0x08 ) {
LOG ( " TODO: Yamaha interlace mode " ) ;
}
2023-01-21 04:14:57 +00:00
// b7: 1 = 212 lines of pixels; 0 = 192
// b5 & b4: select simultaneous mode (seems to relate to line length and in-phase colour?)
// b3: 1 = interlace on
// b2: 1 = display two graphic screens interchangeably by even/odd field
// b1: 1 = PAL mode; 0 = NTSC mode
// b0: 1 = [dot clock] DLCLK is input; 0 = DLCLK is output
2017-11-27 01:01:11 +00:00
break ;
2023-02-01 19:17:49 +00:00
// b0– b2: A14– A16 of the colour table.
case 10 : install_field < 14 > ( colour_table_address_ , value ) ; break ;
2017-11-27 01:01:11 +00:00
2023-02-01 19:17:49 +00:00
// b0– b1: A15– A16 of the sprite table.
case 11 : install_field < 15 > ( sprite_attribute_table_address_ , value ) ; break ;
2017-11-27 01:01:11 +00:00
2023-01-21 04:14:57 +00:00
case 12 :
2023-02-16 01:18:56 +00:00
Storage < personality > : : blink_text_colour_ = value > > 4 ;
Storage < personality > : : blink_background_colour_ = value & 0xf ;
2023-01-21 04:14:57 +00:00
// as per register 7, but in blink mode.
2017-11-27 01:01:11 +00:00
break ;
2023-01-21 04:14:57 +00:00
case 13 :
2023-02-16 01:18:56 +00:00
Storage < personality > : : blink_periods_ = value ;
if ( ! value ) {
Storage < personality > : : in_blink_ = 0 ;
}
2023-01-21 04:14:57 +00:00
// b0– b3: display time for odd page;
// b4– b7: display time for even page.
2017-11-27 01:01:11 +00:00
break ;
2023-02-07 03:16:42 +00:00
case 14 : install_field < 14 > ( ram_pointer_ , value ) ; break ;
2018-09-29 02:37:10 +00:00
2023-01-21 04:14:57 +00:00
case 15 :
Storage < personality > : : selected_status_ = value & 0xf ;
break ;
2023-01-21 04:00:33 +00:00
2023-01-21 04:14:57 +00:00
case 16 :
2023-01-21 19:12:46 +00:00
Storage < personality > : : palette_entry_ = value ;
2023-01-21 04:14:57 +00:00
// b0– b3: palette entry for writing on port 2; autoincrements upon every write.
break ;
2018-10-05 02:50:35 +00:00
2023-01-21 04:14:57 +00:00
case 17 :
Storage < personality > : : increment_indirect_register_ = ! ( value & 0x80 ) ;
Storage < personality > : : indirect_register_ = value & 0x3f ;
break ;
case 18 :
2023-04-25 02:43:11 +00:00
Storage < personality > : : adjustment_ [ 0 ] = ( 8 - ( ( value & 15 ) ^ 8 ) ) * 4 ;
Storage < personality > : : adjustment_ [ 1 ] = 8 - ( ( value > > 4 ) ^ 8 ) ;
2023-01-21 04:14:57 +00:00
// b0-b3: horizontal adjustment
// b4-b7: vertical adjustment
break ;
case 19 :
2023-01-31 02:24:53 +00:00
line_interrupt_target_ = value ;
2023-01-21 04:14:57 +00:00
// b0– b7: line to match for interrupts (if eabled)
break ;
case 20 :
case 21 :
case 22 :
2023-03-17 02:00:47 +00:00
// LOG("TODO: Yamaha colour burst selection; " << PADHEX(2) << +value);
2023-01-21 04:14:57 +00:00
// Documentation is "fill with 0s for no colour burst; magic pattern for colour burst"
break ;
case 23 :
2023-01-27 02:38:51 +00:00
Storage < personality > : : vertical_offset_ = value ;
2023-01-21 04:14:57 +00:00
break ;
2023-01-27 00:51:56 +00:00
case 32 : Storage < personality > : : command_context_ . source . template set < 0 , false > ( value ) ; break ;
case 33 : Storage < personality > : : command_context_ . source . template set < 0 , true > ( value ) ; break ;
case 34 : Storage < personality > : : command_context_ . source . template set < 1 , false > ( value ) ; break ;
case 35 : Storage < personality > : : command_context_ . source . template set < 1 , true > ( value ) ; break ;
case 36 : Storage < personality > : : command_context_ . destination . template set < 0 , false > ( value ) ; break ;
case 37 : Storage < personality > : : command_context_ . destination . template set < 0 , true > ( value ) ; break ;
case 38 : Storage < personality > : : command_context_ . destination . template set < 1 , false > ( value ) ; break ;
case 39 : Storage < personality > : : command_context_ . destination . template set < 1 , true > ( value ) ; break ;
case 40 : Storage < personality > : : command_context_ . size . template set < 0 , false > ( value ) ; break ;
case 41 : Storage < personality > : : command_context_ . size . template set < 0 , true > ( value ) ; break ;
case 42 : Storage < personality > : : command_context_ . size . template set < 1 , false > ( value ) ; break ;
case 43 : Storage < personality > : : command_context_ . size . template set < 1 , true > ( value ) ; break ;
2023-01-21 04:14:57 +00:00
case 44 :
2023-02-05 02:14:20 +00:00
Storage < personality > : : command_context_ . colour . set ( value ) ;
2023-01-29 02:30:45 +00:00
// Check whether a command was blocked on this.
if (
Storage < personality > : : command_ & &
2023-01-29 18:22:56 +00:00
Storage < personality > : : command_ - > access = = Command : : AccessType : : WaitForColourReceipt
2023-01-29 02:30:45 +00:00
) {
2023-03-19 03:07:33 +00:00
Storage < personality > : : command_ - > advance ( ) ;
2023-01-29 02:30:45 +00:00
Storage < personality > : : update_command_step ( fetch_pointer_ . column ) ;
}
2023-01-21 04:14:57 +00:00
break ;
case 45 :
2023-01-26 16:59:27 +00:00
Storage < personality > : : command_context_ . arguments = value ;
2023-02-21 03:27:30 +00:00
// b6: MXC, i.e. destination for INed/OUTed video data; 0 = video RAM; 1 = expansion RAM.
// b5: MXD, destination for command engine.
// b4: MXS, source for command engine.
2023-01-21 04:14:57 +00:00
// b3: DIY
// b2: DIX
// b1: EQ
// b0: MAJ
break ;
case 46 :
2023-01-27 03:08:36 +00:00
// b0– b3: LO0– LO3 (i.e. operation to apply if this is a logical command)
// b4– b7: CM0-CM3 (i.e. command to perform)
// If a command is already ongoing and this is not a stop, ignore it.
if ( Storage < personality > : : command_ & & ( value > > 4 ) ! = 0b0000 ) {
break ;
}
2023-01-26 17:09:06 +00:00
2023-03-18 17:39:47 +00:00
# define Begin(x) Storage<personality>::command_ = std::make_unique<Commands::x>(Storage<personality>::command_context_, Storage<personality>::mode_description_);
2023-03-09 03:36:06 +00:00
using MoveType = Commands : : MoveType ;
2023-01-26 17:09:06 +00:00
switch ( value > > 4 ) {
2023-02-26 19:28:24 +00:00
// All codes not listed below are invalid; treat them as STOP.
default :
2023-05-12 18:14:45 +00:00
case 0b0000 : Storage < personality > : : command_ = nullptr ; break ; // STOP.
2023-01-26 17:09:06 +00:00
2023-03-14 02:51:01 +00:00
case 0b0100 : Begin ( Point < true > ) ; break ; // POINT [read a pixel colour].
case 0b0101 : Begin ( Point < false > ) ; break ; // PSET [plot a pixel].
2023-01-27 03:25:10 +00:00
case 0b0110 : break ; // TODO: srch. [search horizontally for a colour]
2023-03-14 02:51:01 +00:00
case 0b0111 : Begin ( Line ) ; break ; // LINE [draw a Bresenham line].
2023-01-26 17:09:06 +00:00
2023-03-22 00:05:34 +00:00
case 0b1000 : Begin ( Fill < true > ) ; break ; // LMMV [logical move, VDP to VRAM, i.e. solid-colour fill].
2023-03-14 02:51:01 +00:00
case 0b1001 : Begin ( Move < MoveType : : Logical > ) ; break ; // LMMM [logical move, VRAM to VRAM].
2023-01-27 03:25:10 +00:00
case 0b1010 : break ; // TODO: lmcm. [logical move, VRAM to CPU]
2023-03-14 02:51:01 +00:00
case 0b1011 : Begin ( MoveFromCPU < true > ) ; break ; // LMMC [logical move, CPU to VRAM].
2023-01-26 17:09:06 +00:00
2023-03-22 00:05:34 +00:00
case 0b1100 : Begin ( Fill < false > ) ; break ; // HMMV [high-speed move, VDP to VRAM, i.e. single-byte fill].
2023-03-14 02:51:01 +00:00
case 0b1101 : Begin ( Move < MoveType : : HighSpeed > ) ; break ; // HMMM [high-speed move, VRAM to VRAM].
case 0b1110 : Begin ( Move < MoveType : : YOnly > ) ; break ; // YMMM [high-speed move, y only, VRAM to VRAM].
case 0b1111 : Begin ( MoveFromCPU < false > ) ; break ; // HMMC [high-speed move, CPU to VRAM].
2023-01-26 17:09:06 +00:00
}
2023-01-27 03:02:40 +00:00
# undef Begin
2023-01-26 17:09:06 +00:00
2023-01-29 23:28:49 +00:00
Storage < personality > : : command_context_ . pixel_operation = CommandContext : : LogicalOperation ( value & 7 ) ;
Storage < personality > : : command_context_ . test_source = value & 8 ;
2023-01-29 18:29:19 +00:00
2023-01-29 02:30:45 +00:00
// Kill the command immediately if it's done in zero operations
// (e.g. a line of length 0).
2023-02-26 19:28:24 +00:00
if ( ! Storage < personality > : : command_ & & ( value > > 4 ) ) {
2023-01-27 03:08:36 +00:00
LOG ( " TODO: Yamaha command " < < PADHEX ( 2 ) < < + value ) ;
2023-01-26 17:55:08 +00:00
}
2023-01-29 02:30:45 +00:00
// Seed timing information if a command was found.
Storage < personality > : : update_command_step ( fetch_pointer_ . column ) ;
2023-01-21 04:14:57 +00:00
break ;
2017-11-27 01:01:11 +00:00
}
2023-01-21 04:14:57 +00:00
}
}
template < Personality personality >
void Base < personality > : : write_register ( uint8_t value ) {
// Writes to address 1 are performed in pairs; if this is the
// low byte of a value, store it and wait for the high byte.
if ( ! write_phase_ ) {
low_write_ = value ;
write_phase_ = true ;
2023-02-11 15:35:42 +00:00
// The initial write should half update the access pointer, other than
// on the Yamaha.
if constexpr ( ! is_yamaha_vdp ( personality ) ) {
install_field < 0 > ( ram_pointer_ , value ) ;
}
2023-01-21 04:14:57 +00:00
return ;
}
// The RAM pointer is always set on a second write, regardless of
2023-02-01 19:55:33 +00:00
// whether the caller is intending to enqueue a VDP operation.
2023-02-12 03:43:29 +00:00
// If this isn't a Yamaha VDP then the RAM address is updated
// regardless of whether this turns out to be a register access.
2023-02-02 17:12:11 +00:00
//
// The top two bits are used to determine the type of write; only
// the lower six are actual address.
2023-02-12 03:43:29 +00:00
if constexpr ( ! is_yamaha_vdp ( personality ) ) {
install_field < 8 , 6 > ( ram_pointer_ , value ) ;
2023-02-11 15:35:42 +00:00
}
2023-01-21 04:14:57 +00:00
write_phase_ = false ;
if ( value & 0x80 ) {
commit_register ( value , low_write_ ) ;
2017-11-27 01:01:11 +00:00
} else {
2023-02-12 03:43:29 +00:00
// This is an access via the RAM pointer; if this is a Yamaha VDP then update
// the low 14-bits of the RAM pointer now.
if constexpr ( is_yamaha_vdp ( personality ) ) {
install_field < 0 > ( ram_pointer_ , low_write_ ) ;
install_field < 8 , 6 > ( ram_pointer_ , value ) ;
}
2017-11-27 01:01:11 +00:00
if ( ! ( value & 0x40 ) ) {
2018-10-07 22:55:35 +00:00
// A read request is enqueued upon setting the address; conversely a write
// won't be enqueued unless and until some actual data is supplied.
2023-01-19 17:32:42 +00:00
queued_access_ = MemoryAccess : : Read ;
2023-05-12 03:49:12 +00:00
minimum_access_column_ = fetch_pointer_ . column + LineLayout < personality > : : VRAMAccessDelay ;
2017-11-27 01:01:11 +00:00
}
2023-01-19 19:09:31 +00:00
if constexpr ( is_sega_vdp ( personality ) ) {
Storage < personality > : : cram_is_selected_ = false ;
}
2017-11-27 01:01:11 +00:00
}
2017-11-26 21:47:59 +00:00
}
2023-01-18 17:36:57 +00:00
template < Personality personality >
void Base < personality > : : write_palette ( uint8_t value ) {
2023-01-21 19:12:46 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
2023-02-07 00:12:02 +00:00
if ( ! Storage < personality > : : palette_write_phase_ ) {
2023-01-21 19:12:46 +00:00
Storage < personality > : : new_colour_ = value ;
2023-02-07 00:12:02 +00:00
Storage < personality > : : palette_write_phase_ = true ;
2023-01-21 19:12:46 +00:00
return ;
}
2023-02-07 00:12:02 +00:00
Storage < personality > : : palette_write_phase_ = false ;
2023-01-21 19:12:46 +00:00
2023-01-21 19:19:52 +00:00
const uint8_t r = ( ( Storage < personality > : : new_colour_ > > 4 ) & 7 ) * 255 / 7 ;
const uint8_t g = ( value & 7 ) * 255 / 7 ;
const uint8_t b = ( Storage < personality > : : new_colour_ & 7 ) * 255 / 7 ;
2023-01-21 19:12:46 +00:00
Storage < personality > : : palette_ [ Storage < personality > : : palette_entry_ & 0xf ] = palette_pack ( r , g , b ) ;
2023-02-26 18:42:59 +00:00
Storage < personality > : : background_palette_ [ Storage < personality > : : palette_entry_ & 0xf ] = palette_pack ( r , g , b ) ;
Storage < personality > : : background_palette_ [ 0 ] = Storage < personality > : : palette_ [ background_colour_ ] ;
2023-01-21 19:12:46 +00:00
+ + Storage < personality > : : palette_entry_ ;
}
2023-01-18 17:36:57 +00:00
}
template < Personality personality >
2023-01-21 04:07:14 +00:00
void Base < personality > : : write_register_indirect ( [[maybe_unused]] uint8_t value ) {
if constexpr ( is_yamaha_vdp ( personality ) ) {
2023-01-21 04:14:57 +00:00
// Register 17 cannot be written to indirectly.
if ( Storage < personality > : : indirect_register_ ! = 17 ) {
commit_register ( Storage < personality > : : indirect_register_ , value ) ;
2023-01-21 04:07:14 +00:00
}
2023-01-21 04:14:57 +00:00
Storage < personality > : : indirect_register_ + = Storage < personality > : : increment_indirect_register_ ;
2023-01-21 04:07:14 +00:00
}
2023-01-18 17:36:57 +00:00
}
template < Personality personality >
void TMS9918 < personality > : : write ( int address , uint8_t value ) {
switch ( this - > masked_address ( address ) ) {
default : break ;
case 0 : this - > write_vram ( value ) ; break ;
case 1 : this - > write_register ( value ) ; break ;
case 2 : this - > write_palette ( value ) ; break ;
case 3 : this - > write_register_indirect ( value ) ; break ;
}
}
template < Personality personality >
uint8_t Base < personality > : : read_vram ( ) {
// Take whatever is currently in the read-ahead buffer and
// enqueue a further read to occur at the next available slot.
2023-01-19 17:32:42 +00:00
const uint8_t result = read_ahead_buffer_ ;
queued_access_ = MemoryAccess : : Read ;
2023-01-18 17:36:57 +00:00
return result ;
}
template < Personality personality >
uint8_t Base < personality > : : read_register ( ) {
2023-01-19 03:23:19 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
switch ( Storage < personality > : : selected_status_ ) {
2023-01-30 02:20:50 +00:00
default :
2023-01-19 03:23:19 +00:00
case 0 : break ;
2023-01-31 02:24:53 +00:00
case 1 : {
2023-01-31 01:20:33 +00:00
// b7 = light pen; set when light is detected, reset on read;
// or: mouse button 2 currently down.
// b6 = light pen button or mouse button 1.
2023-02-22 00:35:11 +00:00
// b5– b1 = VDP identification (0 = 9938; 2 = 9958)
2023-01-31 01:20:33 +00:00
// b0 = set when the VDP reaches the line provided in the line interrupt register.
// Reset upon read.
2023-01-31 02:24:53 +00:00
const uint8_t result =
2023-02-22 00:35:11 +00:00
( personality = = Personality : : V9938 ? 0x0 : 0x4 ) |
2023-05-08 13:45:54 +00:00
( ( line_interrupt_pending_ & & enable_line_interrupts_ ) ? 0x01 : 0x00 ) ;
2023-01-31 02:24:53 +00:00
line_interrupt_pending_ = false ;
return result ;
} break ;
2023-01-29 02:30:45 +00:00
case 2 : {
2023-01-19 03:23:19 +00:00
// b7 = transfer ready flag (i.e. VDP ready for next transfer)
// b6 = 1 during vblank
// b5 = 1 during hblank
// b4 = set if colour detected during search command
// b1 = display field odd/even
// b0 = command ongoing
2023-01-29 02:30:45 +00:00
const uint8_t transfer_ready =
( queued_access_ = = MemoryAccess : : None ? 0x80 : 0x00 ) &
( (
! Storage < personality > : : command_ | |
2023-01-29 02:45:05 +00:00
! Storage < personality > : : command_ - > is_cpu_transfer | |
2023-01-29 18:22:56 +00:00
Storage < personality > : : command_ - > access = = Command : : AccessType : : WaitForColourReceipt
2023-01-29 02:30:45 +00:00
) ? 0x80 : 0x00 ) ;
2023-01-19 03:23:19 +00:00
return
2023-01-29 02:30:45 +00:00
transfer_ready |
2023-02-02 03:25:00 +00:00
( vertical_state ( ) ! = VerticalState : : Pixels ? 0x40 : 0x00 ) |
2023-01-28 16:55:12 +00:00
( is_horizontal_blank ( ) ? 0x20 : 0x00 ) |
( Storage < personality > : : command_ ? 0x01 : 0x00 ) ;
2023-01-19 03:23:19 +00:00
2023-01-29 02:30:45 +00:00
} break ;
2023-01-30 02:20:50 +00:00
2023-03-16 03:06:32 +00:00
case 3 : return uint8_t ( Storage < personality > : : collision_location_ [ 0 ] ) ;
case 4 : return uint8_t ( ( Storage < personality > : : collision_location_ [ 0 ] > > 8 ) | 0xfe ) ;
case 5 : return uint8_t ( Storage < personality > : : collision_location_ [ 1 ] ) ;
case 6 : return uint8_t ( ( Storage < personality > : : collision_location_ [ 1 ] > > 8 ) | 0xfc ) ;
2023-03-14 02:51:01 +00:00
case 7 : return Storage < personality > : : colour_status_ ;
2023-03-16 03:06:32 +00:00
case 8 : return uint8_t ( Storage < personality > : : colour_location_ ) ;
case 9 : return uint8_t ( ( Storage < personality > : : colour_location_ > > 8 ) | 0xfe ) ;
2023-01-19 03:23:19 +00:00
}
}
2023-01-18 17:36:57 +00:00
// Gets the status register.
2023-01-19 17:32:42 +00:00
const uint8_t result = status_ ;
status_ & = ~ ( StatusInterrupt | StatusSpriteOverflow | StatusSpriteCollision ) ;
2023-02-21 04:43:23 +00:00
if constexpr ( is_sega_vdp ( personality ) ) {
line_interrupt_pending_ = false ;
}
2023-01-18 17:36:57 +00:00
return result ;
}
template < Personality personality >
uint8_t TMS9918 < personality > : : read ( int address ) {
2023-02-07 01:32:24 +00:00
const int target = this - > masked_address ( address ) ;
2023-01-18 17:36:57 +00:00
2023-02-07 01:32:24 +00:00
if ( target < 2 ) {
this - > write_phase_ = false ;
}
switch ( target ) {
2023-01-18 17:36:57 +00:00
default : return 0xff ;
case 0 : return this - > read_vram ( ) ;
case 1 : return this - > read_register ( ) ;
}
}
// MARK: - Ephemeral state.
2022-12-31 20:08:33 +00:00
template < Personality personality >
2023-01-21 03:29:49 +00:00
int Base < personality > : : fetch_line ( ) const {
// This is the proper Master System value; TODO: what's correct for Yamaha, etc?
2023-04-26 03:16:21 +00:00
constexpr int row_change_position = 31 ;
2023-01-21 03:29:49 +00:00
return
2023-01-21 01:29:15 +00:00
( this - > fetch_pointer_ . column < row_change_position )
? ( this - > fetch_pointer_ . row + this - > mode_timing_ . total_lines - 1 ) % this - > mode_timing_ . total_lines
: this - > fetch_pointer_ . row ;
2023-01-21 03:29:49 +00:00
}
template < Personality personality >
2023-02-02 03:25:00 +00:00
VerticalState Base < personality > : : vertical_state ( ) const {
2023-03-16 02:37:47 +00:00
if ( vertical_active_ ) {
return VerticalState : : Pixels ;
} else if ( fetch_pointer_ . row = = mode_timing_ . total_lines - 1 ) {
return VerticalState : : Prefetch ;
} else {
return VerticalState : : Blank ;
}
2023-01-21 03:29:49 +00:00
}
template < Personality personality >
bool Base < personality > : : is_horizontal_blank ( ) const {
2023-04-24 02:18:36 +00:00
return fetch_pointer_ . column < LineLayout < personality > : : EndOfLeftErase | | fetch_pointer_ . column > = LineLayout < personality > : : EndOfRightBorder ;
2023-01-21 03:29:49 +00:00
}
template < Personality personality >
uint8_t TMS9918 < personality > : : get_current_line ( ) const {
int source_row = this - > fetch_line ( ) ;
2018-10-12 01:42:09 +00:00
2023-01-01 02:47:05 +00:00
if ( this - > tv_standard_ = = TVStandard : : NTSC ) {
if ( this - > mode_timing_ . pixel_lines = = 240 ) {
2018-10-20 02:37:56 +00:00
// NTSC 256x240: 00-FF, 00-06
2023-01-01 02:47:05 +00:00
} else if ( this - > mode_timing_ . pixel_lines = = 224 ) {
2018-10-20 02:37:56 +00:00
// NTSC 256x224: 00-EA, E5-FF
if ( source_row > = 0xeb ) source_row - = 6 ;
} else {
// NTSC 256x192: 00-DA, D5-FF
if ( source_row > = 0xdb ) source_row - = 6 ;
}
} else {
2023-01-01 02:47:05 +00:00
if ( this - > mode_timing_ . pixel_lines = = 240 ) {
2018-10-20 02:37:56 +00:00
// PAL 256x240: 00-FF, 00-0A, D2-FF
if ( source_row > = 267 ) source_row - = 0x39 ;
2023-01-01 02:47:05 +00:00
} else if ( this - > mode_timing_ . pixel_lines = = 224 ) {
2018-10-20 02:37:56 +00:00
// PAL 256x224: 00-FF, 00-02, CA-FF
if ( source_row > = 259 ) source_row - = 0x39 ;
} else {
// PAL 256x192: 00-F2, BA-FF
if ( source_row > = 0xf3 ) source_row - = 0x39 ;
}
}
2018-10-12 01:42:09 +00:00
2020-05-10 03:00:39 +00:00
return uint8_t ( source_row ) ;
2018-10-11 23:56:32 +00:00
}
2022-12-31 20:08:33 +00:00
template < Personality personality >
2023-01-01 18:49:11 +00:00
HalfCycles TMS9918 < personality > : : get_next_sequence_point ( ) const {
2023-01-01 02:47:05 +00:00
if ( ! this - > generate_interrupts_ & & ! this - > enable_line_interrupts_ ) return HalfCycles : : max ( ) ;
2021-04-06 01:02:37 +00:00
if ( get_interrupt_line ( ) ) return HalfCycles : : max ( ) ;
2017-11-30 01:31:55 +00:00
2018-10-05 02:50:35 +00:00
// Calculate the amount of time until the next end-of-frame interrupt.
2023-05-12 03:49:12 +00:00
const int frame_length = LineLayout < personality > : : CyclesPerLine * this - > mode_timing_ . total_lines ;
2018-10-21 22:42:49 +00:00
int time_until_frame_interrupt =
2018-10-11 01:07:39 +00:00
(
2023-05-12 03:49:12 +00:00
( ( this - > mode_timing_ . end_of_frame_interrupt_position . row * LineLayout < personality > : : CyclesPerLine ) + this - > mode_timing_ . end_of_frame_interrupt_position . column + frame_length ) -
( ( this - > fetch_pointer_ . row * LineLayout < personality > : : CyclesPerLine ) + this - > fetch_pointer_ . column )
2018-10-11 01:07:39 +00:00
) % frame_length ;
2018-10-21 22:42:49 +00:00
if ( ! time_until_frame_interrupt ) time_until_frame_interrupt = frame_length ;
2018-10-11 01:07:39 +00:00
2023-01-09 02:25:22 +00:00
if ( ! this - > enable_line_interrupts_ ) {
return this - > clock_converter_ . half_cycles_before_internal_cycles ( time_until_frame_interrupt ) ;
}
2018-10-10 00:51:09 +00:00
2018-10-21 22:42:49 +00:00
// Calculate when the next line interrupt will occur.
2018-10-10 00:51:09 +00:00
int next_line_interrupt_row = - 1 ;
2018-10-05 02:50:35 +00:00
2023-01-21 01:29:15 +00:00
int cycles_to_next_interrupt_threshold = this - > mode_timing_ . line_interrupt_position - this - > fetch_pointer_ . column ;
int line_of_next_interrupt_threshold = this - > fetch_pointer_ . row ;
2018-10-21 22:42:49 +00:00
if ( cycles_to_next_interrupt_threshold < = 0 ) {
2023-05-12 03:49:12 +00:00
cycles_to_next_interrupt_threshold + = LineLayout < personality > : : CyclesPerLine ;
2018-10-21 22:42:49 +00:00
+ + line_of_next_interrupt_threshold ;
}
2023-01-01 02:47:05 +00:00
if constexpr ( is_sega_vdp ( personality ) ) {
2018-10-10 00:51:09 +00:00
// 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.
2023-01-19 20:09:16 +00:00
if ( line_of_next_interrupt_threshold + this - > line_interrupt_counter_ < = this - > mode_timing_ . pixel_lines ) {
next_line_interrupt_row = line_of_next_interrupt_threshold + this - > line_interrupt_counter_ ;
2018-10-10 00:51:09 +00:00
} else {
2023-01-19 20:09:16 +00:00
if ( this - > line_interrupt_target_ < = this - > mode_timing_ . pixel_lines )
next_line_interrupt_row = this - > mode_timing_ . total_lines + this - > line_interrupt_target_ ;
2018-10-10 00:51:09 +00:00
}
2018-10-05 02:50:35 +00:00
}
2023-01-31 02:24:53 +00:00
if constexpr ( is_yamaha_vdp ( personality ) ) {
2023-03-07 03:49:21 +00:00
next_line_interrupt_row = ( this - > line_interrupt_target_ - Storage < personality > : : vertical_offset_ ) & 0xff ;
2023-01-31 02:24:53 +00:00
}
2018-10-10 00:51:09 +00:00
// 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 ) {
2023-01-01 02:47:05 +00:00
return this - > generate_interrupts_ ?
2023-01-01 19:20:45 +00:00
this - > clock_converter_ . half_cycles_before_internal_cycles ( time_until_frame_interrupt ) :
2021-06-05 02:38:07 +00:00
HalfCycles : : max ( ) ;
2018-10-05 02:50:35 +00:00
}
2018-10-10 02:14:35 +00:00
// 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.
2023-02-26 19:23:38 +00:00
const int lines_until_interrupt = ( next_line_interrupt_row - line_of_next_interrupt_threshold + this - > mode_timing_ . total_lines ) % this - > mode_timing_ . total_lines ;
2023-05-12 03:49:12 +00:00
const int local_cycles_until_line_interrupt = cycles_to_next_interrupt_threshold + lines_until_interrupt * LineLayout < personality > : : CyclesPerLine ;
2023-01-01 19:20:45 +00:00
if ( ! this - > generate_interrupts_ ) return this - > clock_converter_ . half_cycles_before_internal_cycles ( local_cycles_until_line_interrupt ) ;
2018-10-05 02:50:35 +00:00
2018-10-10 00:51:09 +00:00
// Return whichever interrupt is closer.
2023-01-01 19:20:45 +00:00
return this - > clock_converter_ . half_cycles_before_internal_cycles ( std : : min ( local_cycles_until_line_interrupt , time_until_frame_interrupt ) ) ;
2017-11-30 01:31:55 +00:00
}
2022-12-31 20:08:33 +00:00
template < Personality personality >
HalfCycles TMS9918 < personality > : : get_time_until_line ( int line ) {
2023-01-01 02:47:05 +00:00
if ( line < 0 ) line + = this - > mode_timing_ . total_lines ;
2018-10-25 01:59:30 +00:00
2023-01-21 01:29:15 +00:00
int cycles_to_next_interrupt_threshold = this - > mode_timing_ . line_interrupt_position - this - > fetch_pointer_ . column ;
int line_of_next_interrupt_threshold = this - > fetch_pointer_ . row ;
2018-10-25 01:59:30 +00:00
if ( cycles_to_next_interrupt_threshold < = 0 ) {
2023-05-12 03:49:12 +00:00
cycles_to_next_interrupt_threshold + = LineLayout < personality > : : CyclesPerLine ;
2018-10-25 01:59:30 +00:00
+ + line_of_next_interrupt_threshold ;
}
if ( line_of_next_interrupt_threshold > line ) {
2023-01-01 02:47:05 +00:00
line + = this - > mode_timing_ . total_lines ;
2018-10-25 01:59:30 +00:00
}
2023-05-12 03:49:12 +00:00
return this - > clock_converter_ . half_cycles_before_internal_cycles ( cycles_to_next_interrupt_threshold + ( line - line_of_next_interrupt_threshold ) * LineLayout < personality > : : CyclesPerLine ) ;
2018-10-25 01:59:30 +00:00
}
2022-12-31 20:08:33 +00:00
template < Personality personality >
2023-01-01 18:49:11 +00:00
bool TMS9918 < personality > : : get_interrupt_line ( ) const {
2023-01-01 02:47:05 +00:00
return
( ( this - > status_ & StatusInterrupt ) & & this - > generate_interrupts_ ) | |
( this - > enable_line_interrupts_ & & this - > line_interrupt_pending_ ) ;
2017-11-30 01:31:55 +00:00
}
2018-10-03 01:05:30 +00:00
2023-04-26 03:16:21 +00:00
// TODO: [potentially] remove Master System timing assumptions in latch and get_latched below, if any other VDP uses these calls.
2023-01-07 14:10:41 +00:00
template < Personality personality > uint8_t TMS9918 < personality > : : get_latched_horizontal_counter ( ) const {
2023-04-27 02:49:46 +00:00
// Translate from internal numbering to the public numbering,
2023-01-07 14:10:41 +00:00
// which counts the 256 pixels as items 0– 255, starts
2023-01-05 18:18:10 +00:00
// counting at -48, and returns only the top 8 bits of the number.
2023-04-27 02:49:46 +00:00
int public_counter = this - > latched_column_ - LineLayout < personality > : : EndOfLeftBorder ;
2023-05-12 03:49:12 +00:00
if ( public_counter < - 46 ) public_counter + = LineLayout < personality > : : CyclesPerLine ;
2023-01-05 18:18:10 +00:00
return uint8_t ( public_counter > > 1 ) ;
2018-10-03 01:05:30 +00:00
}
2023-01-01 02:47:05 +00:00
template < Personality personality >
2023-01-05 18:18:10 +00:00
void TMS9918 < personality > : : latch_horizontal_counter ( ) {
2023-01-21 01:29:15 +00:00
this - > latched_column_ = this - > fetch_pointer_ . column ;
2018-10-03 01:05:30 +00:00
}
2022-12-31 20:08:33 +00:00
template class TI : : TMS : : TMS9918 < Personality : : TMS9918A > ;
template class TI : : TMS : : TMS9918 < Personality : : V9938 > ;
2023-01-19 03:23:19 +00:00
//template class TI::TMS::TMS9918<Personality::V9958>;
2022-12-31 20:08:33 +00:00
template class TI : : TMS : : TMS9918 < Personality : : SMSVDP > ;
template class TI : : TMS : : TMS9918 < Personality : : SMS2VDP > ;
2023-01-19 19:09:31 +00:00
//template class TI::TMS::TMS9918<Personality::GGVDP>;
2023-01-19 03:23:19 +00:00
//template class TI::TMS::TMS9918<Personality::MDVDP>;