HatariWii/src/video.c

3901 lines
160 KiB
C

/*
Hatari - video.c
This file is distributed under the GNU General Public License, version 2
or at your option any later version. Read the file gpl.txt for details.
Video hardware handling. This code handling all to do with the video chip.
So, we handle VBLs, HBLs, copying the ST screen to a buffer to simulate the
TV raster trace, border removal, palette changes per HBL, the 'video address
pointer' etc...
*/
/* 2007/03/xx [NP] Support for cycle precise border removal / hardware scrolling by using */
/* Cycles_GetCounterOnWriteAccess (support left/right border and lines with*/
/* length of +26, +2, -2, +44, -106 bytes). */
/* Add support for 'Enchanted Lands' second removal of right border. */
/* More precise support for reading video counter $ff8205/07/09. */
/* 2007/04/14 [NP] Precise reloading of $ff8201/03 into $ff8205/07/09 at line 310 on cycle */
/* RESTART_VIDEO_COUNTER_CYCLE (ULM DSOTS Demo). */
/* 2007/04/16 [NP] Better Video_CalculateAddress. We must subtract a "magic" 12 cycles to */
/* Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO) to get a correct */
/* value (No Cooper's video synchro protection is finally OK :) ). */
/* 2007/04/17 [NP] - Switch to 60 Hz to remove top border on line 33 should occur before */
/* LINE_REMOVE_TOP_CYCLE (a few cycles before the HBL) */
/* 2007/04/23 [NP] - Slight change in Video_StoreResolution to ignore hi res if the line */
/* has left/right border removed -> assume of lo res line. */
/* - Handle simultaneous removal of right border and bottom border with */
/* the same long switch to 60 Hz (Sync Screen in SNY II). */
/* 2007/05/06 [NP] More precise tests for top border's removal. */
/* 2007/05/11 [NP] Add support for med res overscan (No Cooper Greetings). */
/* 2007/05/12 [NP] - LastCycleSync50 and LastCycleSync60 for better top border's removal */
/* in Video_EndHBL. */
/* - Use VideoOffset in Video_CopyScreenLineColor to handle missing planes */
/* depending on line (med/lo and borders). */
/* 2007/09/25 [NP] Replace printf by calls to HATARI_TRACE. */
/* 2007/10/02 [NP] Use the new int.c to add interrupts with INT_CPU_CYCLE / INT_MFP_CYCLE. */
/* 2007/10/23 [NP] Add support for 0 byte line (60/50 switch at cycle 56). Allow 5 lines */
/* hardscroll (e.g. SHFORSTV.EXE by Paulo Simmoes). */
/* 2007/10/31 [NP] Use BORDERMASK_LEFT_OFF_MED when left border is removed with hi/med */
/* switch (ST CNX in PYM). */
/* 2007/11/02 [NP] Add support for 4 pixel hardware scrolling ("Let's Do The Twist" by */
/* ST CNX in Punish Your Machine). */
/* 2007/11/05 [NP] Depending on the position of the med res switch, the planes will be */
/* shifted when doing med res overscan (Best Part Of the Creation in PYM */
/* or No Cooper Greetings). */
/* 2007/11/30 [NP] A hi/med switch to remove the left border can be either used to initiate*/
/* a right hardware scrolling in low res (St Cnx) or a complete med res */
/* overscan line (Dragonnels Reset Part). */
/* Use bit 0-15, 16-19 and 20-23 in ScreenBorderMask[] to track border */
/* trick, STF hardware scrolling and plane shifting. */
/* 2007/12/22 [NP] Very precise values for VBL_VIDEO_CYCLE_OFFSET, HBL_VIDEO_CYCLE_OFFSET */
/* TIMERB_VIDEO_CYCLE_OFFSET and RESTART_VIDEO_COUNTER_CYCLE. These values */
/* were calculated using sttiming.s on a real STF and should give some very*/
/* accurate results (also uses 56 cycles instead of 44 to process an */
/* HBL/VBL/MFP exception). */
/* 2007/12/29 [NP] Better support for starting line 2 bytes earlier (if the line starts in */
/* 60 Hz and goes back to 50 Hz later), when combined with top border */
/* removal (Mindbomb Demo - D.I. No Shit). */
/* 2007/12/30 [NP] Slight improvement of VideoAdress in Video_CalculateAddress when reading*/
/* during the top border. */
/* Correct the case where removing top border on line 33 could also be */
/* interpreted as a right border removal (which is not possible since the */
/* display is still off at that point). */
/* 2008/01/03 [NP] Better handling of nStartHBL and nEndHBL when switching freq from */
/* 50 to 60 Hz. Allows emulation of a "short" 50 Hz screen of 171 lines */
/* and a more precise removal of bottom border in 50 and 60 Hz. */
/* 2008/01/04 [NP] More generic detection for removing 2 bytes to the right of the line */
/* when switching from 60 to 50 Hz (works even with a big number of cycles */
/* between the freq changes) (Phaleon's Menus). */
/* 2008/01/06 [NP] More generic detection for stopping the display in the middle of a line */
/* with a hi / lo res switch (-106 bytes per line). Although switch to */
/* hi res should occur at cycle 160, some demos use 164 (Phaleon's Menus). */
/* 2008/01/06 [NP] Better bottom border's removal in 50 Hz : switch to 60 Hz must occur */
/* before cycle LINE_REMOVE_BOTTOM_CYCLE on line 263 and switch back to 50 */
/* Hz must occur after LINE_REMOVE_BOTTOM_CYCLE on line 263 (this means */
/* we can already be in 50 Hz when Video_EndHBL is called and still remove */
/* the bottom border). This is similar to the tests used to remove the */
/* top border. */
/* 2008/01/12 [NP] In Video_SetHBLPaletteMaskPointers, consider that if a color's change */
/* occurs after cycle LINE_END_CYCLE_NO_RIGHT, then it's related to the */
/* next line. */
/* FIXME : it would be better to handle all color changes through spec512.c*/
/* and drop the 16 colors palette per line. */
/* FIXME : we should use Cycles_GetCounterOnWriteAccess, but it doesn't */
/* support multiple accesses like move.l or movem. */
/* 2008/01/12 [NP] Handle 60 Hz switch during the active display of the last line to remove*/
/* the bottom border : this should also shorten line by 2 bytes (F.N.I.L. */
/* Demo by TNT). */
/* 2008/01/15 [NP] Don't do 'left+2' if switch back to 50 Hz occurs when line is not active*/
/* (after cycle LINE_END_CYCLE_60) (XXX International Demos). */
/* 2008/01/31 [NP] Improve left border detection : allow switch to low res on cycle <= 28 */
/* instead of <= 20 (Vodka Demo Main Menu). */
/* 2008/02/02 [NP] Added 0 byte line detection when switching hi/lo res at position 28 */
/* (Lemmings screen in Nostalgic-o-demo). */
/* 2008/02/03 [NP] On STE, write to video counter $ff8205/07/09 should only be applied */
/* immediately if display has not started for the line (before cycle */
/* LINE_END_CYCLE_50). If write occurs after, the change to pVideoRaster */
/* should be delayed to the end of the line, after processing the current */
/* line with Video_CopyScreenLineColor (Stardust Tunnel Demo). */
/* 2008/02/04 [NP] The problem is similar when writing to hwscroll $ff8264, we must delay */
/* the change until the end of the line if display was already started */
/* (Mindrewind by Reservoir Gods). */
/* 2008/02/06 [NP] On STE, when left/right borders are off and hwscroll > 0, we must read */
/* 6 bytes less than the expected value (E605 by Light). */
/* 2008/02/17 [NP] In Video_CopyScreenLine, LineWidth*2 bytes should be added after */
/* pNewVideoRaster is copied to pVideoRaster (Braindamage Demo). */
/* When reading a byte at ff8205/07/09, all video address bytes should be */
/* updated in Video_ScreenCounter_ReadByte, not just the byte that was */
/* read. Fix programs that just modify one byte in the video address */
/* counter (e.g. sub #1,$ff8207 in Braindamage Demo). */
/* 2008/02/19 [NP] In Video_CalculateAddress, use pVideoRaster instead of VideoBase to */
/* determine the video address when display is off in the upper part of */
/* the screen (in case ff8205/07/09 were modified on STE). */
/* 2008/02/20 [NP] Better handling in Video_ScreenCounter_WriteByte by changing only one */
/* byte and keeping the other (Braindamage End Part). */
/* 2008/03/08 [NP] Use M68000_INT_VIDEO when calling M68000_Exception(). */
/* 2008/03/13 [NP] On STE, LineWidth value in $ff820f is added to the shifter counter just */
/* when display is turned off on a line (when right border is started, */
/* which is usually on cycle 376). */
/* This means a write to $ff820f should be applied immediately only if it */
/* occurs before cycle LineEndCycle. Else, it is stored in NewLineWidth */
/* and used after Video_CopyScreenLine has processed the current line */
/* (improve the bump mapping part in Pacemaker by Paradox). */
/* LineWidth should be added to pVideoRaster before checking the possible */
/* modification of $ff8205/07/09 in Video_CopyScreenLine. */
/* 2008/03/14 [NP] Rename ScanLineSkip to LineWidth (more consistent with STE docs). */
/* On STE, better support for writing to video counter, line width and */
/* hw scroll. If write to register occurs just at the start of a new line */
/* but before Video_EndHBL (because the move started just before cycle 512)*/
/* then the new value should not be set immediately but stored and set */
/* during Video_EndHBL (fix the bump mapping part in Pacemaker by Paradox).*/
/* 2008/03/25 [NP] On STE, when bSteBorderFlag is true, we should add 16 pixels to the left*/
/* border, not to the right one (Just Musix 2 Menu by DHS). */
/* 2008/03/26 [NP] Clear the rest of the border when using border tricks left+2, left+8 */
/* or right-106 (remove garbage pixels when hatari resolution changes). */
/* 2008/03/29 [NP] Function Video_SetSystemTimings to use different values depending on */
/* the machine type. On STE, top/bottom border removal can occur at cycle */
/* 500 instead of 504 on STF. */
/* 2008/04/02 [NP] Correct a rare case in Video_Sync_WriteByte at the end of line 33 : */
/* nStartHBL was set to 33 instead of 64, which gave a wrong address in */
/* Video_CalculateAddress. */
/* 2008/04/04 [NP] The value of RestartVideoCounterCycle is slightly different between */
/* an STF and an STE. */
/* 2008/04/05 [NP] The value of VblVideoCycleOffset is different of 4 cycles between */
/* STF and STE (fix end part in Pacemaker by Paradox). */
/* 2008/04/09 [NP] Preliminary support for lines using different frequencies in the same */
/* screen. In Video_InterruptHandler_EndLine, if the current freq is 50 Hz,*/
/* then next int should be scheduled in 512 cycles ; if freq is 60 Hz, */
/* next int should be in 508 cycles (used by timer B event count mode). */
/* 2008/04/10 [NP] Update LineEndCycle after changing freq to 50 or 60 Hz. */
/* Set EndLine interrupt to happen 28 cycles after LineEndCycle. This way */
/* Timer B occurs at cycle 404 in 50 Hz, or cycle 400 in 60 Hz (improve */
/* flickering bottom border in B.I.G. Demo screen 1). */
/* 2008/04/12 [NP] In the case of a 'right-2' line, we should not change the EndLine's int */
/* position when switching back to 50 Hz ; the int should happen at */
/* position LINE_END_CYCLE_60 + 28 (Anomaly Demo main menu). */
/* 2008/05/31 [NP] Ignore consecutives writes of the same value in the freq/res register. */
/* Only the 1st write matters, else this could confuse the code to remove */
/* top/bottom border (fix OSZI.PRG demo by ULM). */
/* 2008/06/07 [NP] In Video_SetHBLPaletteMaskPointers, use LineStartCycle instead of the */
/* 50 Hz constant SCREEN_START_CYCLE. */
/* Rename SCREEN_START_HBL_xxx to VIDEO_START_HBL_xxx. */
/* Rename SCREEN_END_HBL_xxx to VIDEO_END_HBL_xxx. */
/* Rename SCREEN_HEIGHT_HBL_xxx to VIDEO_HEIGHT_HBL_xxx. */
/* Use VIDEO_HEIGHT_BOTTOM_50HZ instead of OVERSCAN_BOTTOM. */
/* 2008/06/16 [NP] When Hatari is configured to display the screen's borders, 274 lines */
/* will be rendered on screen, but if the shifter is in 60 Hz, the last */
/* 16 lines will never be used, which can leave some bad pixels on */
/* screen. We clear the remaining lines before calling 'Screen_Draw'. */
/* (in FNIL by Delta Force, fix flickering gfx in the bottom border of the */
/* F2 screen : last 16 lines were the ones from the menu where bottom */
/* border was removed ). */
/* 2008/06/26 [NP] Improve STE scrolling : handle $ff8264 (no prefetch) and $ff8265 */
/* (prefetch). See Video_HorScroll_Write for details on both registers. */
/* More generic support for starting display 16 pixels earlier on STE */
/* by writing to $ff8265 and settting $ff8264=0 just after. */
/* (fix Digiworld 2 by ICE, which uses $ff8264 for horizontal scroll). */
/* 2008/07/07 [NP] Ignore other 50/60 Hz switches once the right border was removed, keep */
/* the timer B to occur at pos 460+28 (fix Oxygene screen in Transbeauce 2)*/
/* 2008/07/14 [NP] When removing only left border in 60Hz, line size is 26+158 bytes */
/* instead of 26+160 bytes in 50 Hz (HigResMode demo by Paradox). */
/* 2008/07/19 [NP] If $ff8260==3 (which is not a valid resolution mode), we use 0 instead */
/* (low res) (fix Omegakul screen in old Omega Demo from 1988). */
/* 2008/09/05 [NP] No need to test 60/50 switch if HblCounterVideo < nStartHBL (display */
/* has not started yet). */
/* 2008/09/25 [NP] Use nLastVisibleHbl to store the number of the last hbl line that should*/
/* be copied to the emulator's screen buffer. */
/* On STE, allow to change immediately video address, hw scroll and */
/* linewidth when nHBL>=nLastVisibleHbl instead of nHBL>=nEndHBL */
/* (fix Power Rise / Xtrem D demo). */
/* 2008/11/15 [NP] For STE registers, add in the TRACE call if the write is delayed or */
/* not (linewidth, hwscroll, video address). */
/* On STE, allow to change linewdith, hwscroll and video address with no */
/* delay as soon as nHBL >= nEndHBL (revert previous changes). Power Rise */
/* is still working due to NewHWScrollCount=-1 when setting immediate */
/* hwscroll. Fix regression in Braindamage. */
/* 2008/11/29 [NP] Increment jitter's index for HBL and VBL each time a possible interrupt */
/* occurs. Each interrupt can have a jitter between 0, 4 and 8 cycles ; the*/
/* jitter follows a predefined pattern of 5 values. The HBL and the VBL */
/* have their own pattern. See InterruptAddJitter() in uae-cpu/newcpu.c */
/* (fix Fullscreen tunnel in Suretrip 49% by Checkpoint and digi sound in */
/* Swedish New Year's TCB screen). */
/* 2008/12/10 [NP] Enhance support for 0 byte line. The 60/50 Hz switch can happen at */
/* cycles 56/64, but also at 58/66 (because access to $ff820a doesn't */
/* require to be on a 4 cycles boundary). As hatari doesn't handle */
/* multiple of 2 cycles, we allow cycles 56/64 and 60/68 (fix nosync.tos */
/* that uses the STOP instruction to produce a 0 byte line on the first */
/* displayed line (found on atari-forum.com)). */
/* 2008/12/26 [NP] When reading $ff8260 on STF, set unused bits to 1 instead of 0 */
/* (fix wrong TOS resolution in Awesome Menu Disk 16). */
/* Set unused bit to 1 when reading $ff820a too. */
/* 2009/01/16 [NP] Handle special case when writing only in upper byte of a color reg. */
/* 2009/01/21 [NP] Implement STE horizontal scroll for medium res (fixes cool_ste.prg). */
/* Take the current res into account in Video_CopyScreenLineColor to */
/* allow mixing low/med res with horizontal scroll on STE. */
/* 2009/01/24 [NP] Better detection of 'right-2' when freq is changed to 60 Hz and */
/* restored to 50 after the end of the current line (fixes games menu on */
/* BBC compil 10). */
/* 2009/01/31 [NP] Handle a rare case where 'move.b #8,$fffa1f' to start the timer B is */
/* done just a few cycles before the actual signal for end of line. In that*/
/* case we must ensure that the write was really effective before the end */
/* of line (else no interrupt should be made) (fix Pompey Pirate Menu #57).*/
/* 2009/02/08 [NP] Handle special case for simultaneous HBL exceptions (fixes flickering in*/
/* Monster Business and Super Monaco GP). */
/* 2009/02/25 [NP] Ignore other 50/60 Hz switches after display was stopped in the middle */
/* of the line with a hi/lo switch. Correct missing end of line timer B */
/* interrupt in that case (fix flickering Dragon Ball part in Blood disk 2 */
/* by Holocaust). */
/* 2008/02/02 [NP] Added 0 byte line detection in STE mode when switching hi/lo res */
/* at position 32 (Lemmings screen in Nostalgic-o-demo). */
/* 2009/03/28 [NP] Depending on bit 3 of MFP's AER, timer B will count end of line events */
/* (bit=0) or start of line events (bit=1) (fix Seven Gates Of Jambala). */
/* 2009/04/02 [NP] Add another method to obtain a 0 byte line, by switching to hi/lo res */
/* at position 500/508 (fix the game No Buddies Land). */
/* 2009/04/xx [NP] Rewrite of many parts : add SHIFTER_FRAME structure, better accuracy */
/* when mixing 50/60 Hz lines and reading $ff8209, better emulation of */
/* HBL and Timer B position when changing freq/res, better emulation of */
/* freq changes for top/bottom/right borders. */
/* 2009/07/16 [NP] In Video_SetHBLPaletteMaskPointers, if LineCycle>460 we consider the */
/* color's change should be applied to next line (used when spec512 mode */
/* if off). */
/* 2009/10/31 [NP] Depending on the overscan mode, the displayed lines must be shifted */
/* left or right (fix Spec 512 images in the Overscan Demos, fix pixels */
/* alignment in screens mixing normal lines and overscan lines). */
/* 2009/12/02 [NP] If we switch hi/lo around position 464 (as in Enchanted Lands) and */
/* right border was not removed, then we get an empty line on the next */
/* HBL (fix Pax Plax Parralax in Beyond by Kruz). */
/* 2009/12/06 [NP] Add support for STE 224 bytes overscan without stabiliser by switching */
/* hi/lo at cycle 504/4 to remove left border (fix More Or Less Zero and */
/* Cernit Trandafir by DHS, as well as Save The Earth by Defence Force). */
/* 2009/12/13 [NP] Improve STE 224 bytes lines : correctly set leftmost 16 pixels to color */
/* 0 and correct small glitches when combined with hscroll ($ff8264). */
/* 2009/12/13 [NP] Line scrolling caused by hi/lo switch (STF_PixelScroll) should be */
/* applied after STE's hardware scrolling, else in overscan 4 color 0 */
/* pixels will appear in the right border (because overscan shift the */
/* whole displayed area 4 pixels to the left) (fix possible regression on */
/* STE introduced on 2009/10/31). */
/* 2010/01/10 [NP] In Video_CalculateAddress, take bSteBorderFlag into account (+16 pixels */
/* in left border on STE). */
/* 2010/01/10 [NP] In Video_CalculateAddress, take HWScrollPrefetch into account (shifter */
/* starts 16 pixels earlier) (fix EPSS demo by Unit 17). */
/* 2010/02/05 [NP] In Video_CalculateAddress, take STE's LineWidth into account when */
/* display is disabled in the right border (fix flickering in Utopos). */
/* 2010/02/07 [NP] Better support for modifying $ff8205/07/09 while display is on */
/* (fix EPSS demo by Unit 17). */
/* 2010/04/12 [NP] Improve timings when writing to $ff8205/07/09 when hscroll is used, */
/* using Video_GetMMUStartCycle (fix Pacemaker's Bump Part by Paradox). */
/* 2010/05/02 [NP] In Video_ConvertPosition, handle the case where we read the position */
/* between the last HBL and the start of the next VBL. During 64 cycles */
/* FrameCycles can be >= CYCLES_PER_FRAME (harmless fix, only useful when */
/* using --trace to get correct positions in the logs). */
/* 2010/05/04 [NP] Improve Video_ConvertPosition, use CyclesPerVBL instead of evaluating */
/* CYCLES_PER_FRAME (whose value could have changed this the start of the */
/* VBL). */
/* 2010/05/15 [NP] In Video_StartInterrupts() when running in monochrome (224 cycles per */
/* line), the VBL could sometimes be delayed by 160 cycles (divs) and */
/* hbl/timer B interrupts for line 0 were not called, which could cause an */
/* assert/crash in Hatari when setting timer B on line 2. */
/* If we detect VBL was delayed too much, we add hbl/timer b in the next */
/* 4 cycles. */
/* 2010/07/05 [NP] When removing left border, allow up to 32 cycles between hi and low */
/* res switching (fix Megabeer by Invizibles). */
/* 2010/11/01 [NP] On STE, the 224 bytes overscan will shift the screen 8 pixels to the */
/* left. */
/* For 230 bytes overscan, handle scrolling prefetching when computing */
/* pVideoRaster for the next line. */
/* 2010/12/12 [NP] In Video_CopyScreenLineColor, use pVideoRasterEndLine to improve */
/* STE's horizontal scrolling for any line's length (160, 224, 230, ...). */
/* Fix the last 16 pixels for 224 bytes overscan (More Or Less Zero and */
/* Cernit Trandafir by DHS, Save The Earth by Defence Force). */
/* 2011/04/03 [NP] Call DmaSnd_HBL_Update() on each HBL to handle programs that modify */
/* the samples data while those data are played by the DMA sound. */
/* (fixes the game Power Up Plus and the demo Mental Hangover). */
/* 2011/07/30 [NP] Add blank line detection in STF mode when switching 60/50 Hz at cycle */
/* 28. The shifter will still read bytes and border removal is possible, */
/* but the line will be blank (we use color 0 for now, but the line should */
/* be black). */
/* (fix spectrum 512 part in Overscan Demo and shforstv by Paulo Simoes */
/* by removing "parasite" pixels on the 1st line). */
/* 2011/11/17 [NP] Improve timings used for the 0 byte line when switching hi/lo at the */
/* end of the line. The hi/lo switch can be at 496/508 or 500/508 */
/* (fix NGC screen in Delirious Demo IV). */
/* 2011/11/18 [NP] Add support for another method to do 4 pixel hardware scrolling by doing*/
/* a med/lo switch after the hi/lo switch to remove left border */
/* (fix NGC screen in Delirious Demo IV). */
/* 2011/11/19 [NP] The 0 byte line obtained by switching hi/lo at the end of the line has */
/* no video signal at all (blank). In that case, the screen is shifted one */
/* line down, and bottom border removal will happen one line later too */
/* (fix NGC screen in Delirious Demo IV). */
/* 2012/01/11 [NP] Don't remove left border when the hi/lo switch is made at cycle >= 12 */
/* (fix 'Kill The Beast 2' in the Vodka Demo) */
/* 2012/05/19 [NP] Allow bottom border to be removed when switch back to 50 Hz is made at */
/* cycle 504 and more (instead of 508 and more). Same for top border */
/* (fix 'Musical Wonders 1990' by Offbeat). */
/* 2013/03/05 [NP] An extra 4 cycle delay is added by the MFP to set IRQ when the timer B */
/* expires in event count mode. Update TIMERB_VIDEO_CYCLE_OFFSET to 24 */
/* cycles instead of 28 to compensate for this and keep the same position. */
/* 2013/04/26 [NP] Cancel changes from 2012/05/19, 'Musical Wonders 1990' is really broken */
/* on a real STF and bottom border is not removed. */
/* 2013/05/03 [NP] Add support for IACK sequence when handling HBL/VBL exceptions. Allow */
/* to handle the case where interrupt pending bit is set twice (correct */
/* fix for Super Monaco GP, Super Hang On, Monster Business, European */
/* Demo's Intro, BBC Menu 52). */
/* 2013/07/17 [NP] Handle a special case when writing only in lower byte of a color reg. */
/* 2013/12/02 [NP] If $ff8260==3 (which is not a valid resolution mode), we use 2 instead */
/* (high res) (cancel wrong change from 2008/07/19 and fix 'The World Is */
/* My Oyster - Convention Report Part' by Aura). */
/* 2013/12/24 [NP] In Video_ColorReg_ReadWord, randomly return 0 or 1 for unused bits */
/* in STF's color registers (fix 'UMD 8730' by PHF in STF mode) */
/* 2013/12/28 [NP] For bottom border removal on a 60 Hz screen, max position to go back */
/* to 60 Hz should be 4 cycles earlier, as a 60 Hz line starts 4 cycles */
/* earlier (fix STE demo "It's a girl 2" by Paradox). */
/* 2014/02/22 [NP] In Video_ColorReg_ReadWord(), don't set unused STF bits to rand() if */
/* the PC is not executing from the RAM between 0 and 4MB (fix 'Union Demo'*/
/* 2014/03/21 [NP] For STE in med res overscan at 60 Hz, add a 3 pixels shift to have */
/* bitmaps and color changes synchronised (fix 'HighResMode' by Paradox). */
/* protection code running at address $ff8240). */
/* 2014/05/08 [NP] In case we're mixing 50 Hz and 60 Hz lines (512 or 508 cycles), we must */
/* update the position where the VBL interrupt will happen (fix "keyboard */
/* no jitter" test program by Nyh, with 4 lines at 60 Hz and 160240 cycles */
/* per VBL). */
/* 2014/05/31 [NP] Ensure pVideoRaster always points into a 24 bit space region. In case */
/* video address at $ff8201/03 is set into IO space $ffxxxx, the new value */
/* for video pointer should not be >= $1000000 (fix "Leavin' Teramis" */
/* which sets video address to $ffe100 to display "loading please wait". */
/* In that case, we must display $ffe100-$ffffff then $0-$5e00) */
/* 2015/06/19 [NP] In Video_CalculateAddress, handle a special/simplified case when reading*/
/* video pointer in hi res (fix protection in 'My Socks Are Weapons' demo */
/* by 'Legacy'). */
/* 2015/0818 [NP] In Video_CalculateAddress, handle the case when reading overlaps end */
/* of line / start of next line and STE's linewidth at $FF820F != 0. */
const char Video_fileid[] = "Hatari video.c : " __DATE__ " " __TIME__;
#include <SDL_endian.h>
#include "main.h"
#include "configuration.h"
#include "cycles.h"
#include "fdc.h"
#include "cycInt.h"
#include "ioMem.h"
#include "keymap.h"
#include "m68000.h"
#include "hatari-glue.h"
#include "memorySnapShot.h"
#include "mfp.h"
#include "printer.h"
#include "screen.h"
#include "screenSnapShot.h"
#include "shortcut.h"
#include "sound.h"
#include "dmaSnd.h"
#include "spec512.h"
#include "stMemory.h"
#include "vdi.h"
#include "video.h"
#include "ymFormat.h"
#include "falcon/videl.h"
#include "falcon/hostscreen.h"
#include "avi_record.h"
#include "ikbd.h"
#include "floppy_ipf.h"
/* The border's mask allows to keep track of all the border tricks */
/* applied to one video line. The masks for all lines are stored in the array */
/* ScreenBorderMask[]. */
/* - bits 0-15 are used to describe the border tricks. */
/* - bits 20-23 are used to store the bytes offset to apply for some particular */
/* tricks (for example med res overscan can shift display by 0 or 2 bytes */
/* depending on when the switch to med res is done after removing the left */
/* border). */
#define BORDERMASK_NONE 0x00 /* no effect on this line */
#define BORDERMASK_LEFT_OFF 0x01 /* removal of left border with hi/lo res switch -> +26 bytes */
#define BORDERMASK_LEFT_PLUS_2 0x02 /* line starts earlier in 60 Hz -> +2 bytes */
#define BORDERMASK_STOP_MIDDLE 0x04 /* line ends in hires at cycle 160 -> -106 bytes */
#define BORDERMASK_RIGHT_MINUS_2 0x08 /* line ends earlier in 60 Hz -> -2 bytes */
#define BORDERMASK_RIGHT_OFF 0x10 /* removal of right border -> +44 bytes */
#define BORDERMASK_RIGHT_OFF_FULL 0x20 /* full removal of right border and next left border -> +22 bytes */
#define BORDERMASK_OVERSCAN_MED_RES 0x40 /* some borders were removed and the line is in med res instead of low res */
#define BORDERMASK_EMPTY_LINE 0x80 /* 60/50 Hz switch prevents the line to start, video counter is not incremented */
#define BORDERMASK_LEFT_OFF_MED 0x100 /* removal of left border with hi/med res switch -> +26 bytes (for 4 pixels hardware scrolling) */
#define BORDERMASK_LEFT_OFF_2_STE 0x200 /* shorter removal of left border with hi/lo res switch -> +20 bytes (STE only)*/
#define BORDERMASK_BLANK_LINE 0x400 /* 60/50 Hz switch blanks the rest of the line, but video counter is still incremented */
int STRes = ST_LOW_RES; /* current ST resolution */
int TTRes; /* TT shifter resolution mode */
int nFrameSkips; /* speed up by skipping video frames */
bool bUseHighRes; /* Use hi-res (ie Mono monitor) */
int OverscanMode; /* OVERSCANMODE_xxxx for current display frame */
Uint16 HBLPalettes[HBL_PALETTE_LINES]; /* 1x16 colour palette per screen line, +1 line just incase write after line 200 */
Uint16 *pHBLPalettes; /* Pointer to current palette lists, one per HBL */
Uint32 HBLPaletteMasks[HBL_PALETTE_MASKS]; /* Bit mask of palette colours changes, top bit set is resolution change */
Uint32 *pHBLPaletteMasks;
int nScreenRefreshRate = 50; /* 50 or 60 Hz in color, 71 Hz in mono */
Uint32 VideoBase; /* Base address in ST Ram for screen (read on each VBL) */
int nVBLs; /* VBL Counter */
int nHBL; /* HBL line */
int nStartHBL; /* Start HBL for visible screen */
int nEndHBL; /* End HBL for visible screen */
int nScanlinesPerFrame = 313; /* Number of scan lines per frame */
int nCyclesPerLine = 512; /* Cycles per horizontal line scan */
static int nFirstVisibleHbl = FIRST_VISIBLE_HBL_50HZ; /* The first line of the ST screen that is copied to the PC screen buffer */
static int nLastVisibleHbl = FIRST_VISIBLE_HBL_50HZ+NUM_VISIBLE_LINES; /* The last line of the ST screen that is copied to the PC screen buffer */
static int CyclesPerVBL = 313*512; /* Number of cycles per VBL */
static Uint8 HWScrollCount; /* HW scroll pixel offset, STE only (0...15) */
static int NewHWScrollCount = -1; /* Used in STE mode when writing to the scrolling registers $ff8264/65 */
static Uint8 HWScrollPrefetch; /* 0 when scrolling with $ff8264, 1 when scrolling with $ff8265 */
static int NewHWScrollPrefetch = -1; /* Used in STE mode when writing to the scrolling registers $ff8264/65 */
static Uint8 LineWidth; /* Scan line width add, STe only (words, minus 1) */
static int NewLineWidth = -1; /* Used in STE mode when writing to the line width register $ff820f */
static int VideoCounterDelayedOffset = 0; /* Used in STE mode when changing video counter while display is on */
static Uint8 *pVideoRasterDelayed = NULL; /* Used in STE mode when changing video counter while display is off in the right border */
static Uint8 *pVideoRaster; /* Pointer to Video raster, after VideoBase in PC address space. Use to copy data on HBL */
static bool bSteBorderFlag; /* true when screen width has been switched to 336 (e.g. in Obsession) */
static int NewSteBorderFlag = -1; /* New value for next line */
static bool bTTColorsSync, bTTColorsSTSync; /* whether TT colors need conversion to SDL */
bool bTTSampleHold = false; /* TT special video mode */
static bool bTTHypermono = false; /* TT special video mode */
static int TTSpecialVideoMode = 0; /* TT special video mode */
static int nPrevTTSpecialVideoMode = 0; /* TT special video mode */
static int LastCycleScroll8264; /* value of Cycles_GetCounterOnWriteAccess last time ff8264 was set for the current VBL */
static int LastCycleScroll8265; /* value of Cycles_GetCounterOnWriteAccess last time ff8265 was set for the current VBL */
static int LineRemoveTopCycle = LINE_REMOVE_TOP_CYCLE_STF;
static int LineRemoveBottomCycle = LINE_REMOVE_BOTTOM_CYCLE_STF;
static int RestartVideoCounterCycle = RESTART_VIDEO_COUNTER_CYCLE_STF;
static int VblVideoCycleOffset = VBL_VIDEO_CYCLE_OFFSET_STF;
int LineTimerBCycle = LINE_END_CYCLE_50 + TIMERB_VIDEO_CYCLE_OFFSET; /* position of the Timer B interrupt on active lines */
int TimerBEventCountCycleStart = -1; /* value of Cycles_GetCounterOnWriteAccess last time timer B was started for the current VBL */
int HblJitterIndex = 0;
const int HblJitterArray[] = {
8,4,4,0,0 /* measured on STF */
};
const int HblJitterArrayPending[] = {
4,4,4,4,4 // { 8,8,12,8,12 }; /* measured on STF, not always accurate */
};
int VblJitterIndex = 0;
const int VblJitterArray[] = {
8,0,4,0,4 /* measured on STF */
};
const int VblJitterArrayPending[] = {
8,8,12,8,12 /* not verified on STF, use the same as HBL */
};
static int BlankLines = 0; /* Number of empty line with no signal (by switching hi/lo near cycles 500) */
typedef struct
{
int VBL; /* VBL for this Pos (or -1 if Pos not defined for now) */
int FrameCycles; /* Number of cycles since this VBL */
int HBL; /* HBL in the VBL */
int LineCycles; /* cycles in the HBL */
} SHIFTER_POS;
typedef struct
{
int StartCycle; /* first cycle of this line, as returned by Cycles_GetCounter */
Uint32 BorderMask; /* borders' states for this line */
int DisplayPixelShift; /* number of pixels to shift the whole line (<0 shift to the left, >0 shift to the right) */
/* On STF, this is obtained when switching hi/med for a variable number of cycles, */
/* but just removing left border will shift the line too. */
int DisplayStartCycle; /* cycle where display starts for this line (0-512) : 0, 52 or 56 */
int DisplayEndCycle; /* cycle where display ends for this line (0-512) : 0, 160, 372, 376, 460 or 512 */
int DisplayBytes; /* how many bytes to display for this line */
} SHIFTER_LINE;
typedef struct
{
int HBL_CyclePos; /* cycle position for the HBL int (depends on freq/res) */
int TimerB_CyclePos; /* cycle position for the Timer B int (depends on freq/res) */
int Freq; /* value of ff820a & 2, or -1 if not set */
int Res; /* value of ff8260 & 3, or -1 if not set */
SHIFTER_POS FreqPos50; /* position of latest freq change to 50 Hz*/
SHIFTER_POS FreqPos60; /* position of latest freq change to 60 Hz*/
SHIFTER_POS ResPosLo; /* position of latest change to low res */
SHIFTER_POS ResPosMed; /* position of latest change to med res */
SHIFTER_POS ResPosHi; /* position of latest change to high res */
SHIFTER_POS Scroll8264Pos; /* position of latest write to $ff8264 */
SHIFTER_POS Scroll8265Pos; /* position of latest write to $ff8265 */
SHIFTER_LINE ShifterLines[ MAX_SCANLINES_PER_FRAME ];
} SHIFTER_FRAME;
static SHIFTER_FRAME ShifterFrame;
/*--------------------------------------------------------------*/
/* Local functions prototypes */
/*--------------------------------------------------------------*/
static void Video_SetSystemTimings ( void );
static Uint32 Video_CalculateAddress ( void );
static int Video_GetMMUStartCycle ( int DisplayStartCycle );
static void Video_WriteToShifter ( Uint8 Res );
static void Video_Sync_SetDefaultStartEnd ( Uint8 Freq , int HblCounterVideo , int LineCycles );
static int Video_HBL_GetPos ( void );
static int Video_TimerB_GetDefaultPos ( void );
static void Video_EndHBL ( void );
static void Video_StartHBL ( void );
static void Video_StoreFirstLinePalette(void);
static void Video_StoreResolution(int y);
static void Video_CopyScreenLineMono(void);
static void Video_CopyScreenLineColor(void);
static void Video_CopyVDIScreen(void);
static void Video_SetHBLPaletteMaskPointers(void);
static void Video_UpdateTTPalette(int bpp);
static void Video_DrawScreen(void);
static void Video_ResetShifterTimings(void);
static void Video_InitShifterLines(void);
static void Video_ClearOnVBL(void);
static void Video_AddInterrupt ( int Pos , interrupt_id Handler );
static void Video_AddInterruptHBL ( int Pos );
static void Video_ColorReg_WriteWord(void);
static void Video_ColorReg_ReadWord(void);
/*-----------------------------------------------------------------------*/
/**
* Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type)
*/
void Video_MemorySnapShot_Capture(bool bSave)
{
Uint32 addr;
/* Save/Restore details */
MemorySnapShot_Store(&TTRes, sizeof(TTRes));
MemorySnapShot_Store(&bUseHighRes, sizeof(bUseHighRes));
MemorySnapShot_Store(&nVBLs, sizeof(nVBLs));
MemorySnapShot_Store(&nHBL, sizeof(nHBL));
MemorySnapShot_Store(&nStartHBL, sizeof(nStartHBL));
MemorySnapShot_Store(&nEndHBL, sizeof(nEndHBL));
MemorySnapShot_Store(&OverscanMode, sizeof(OverscanMode));
MemorySnapShot_Store(HBLPalettes, sizeof(HBLPalettes));
MemorySnapShot_Store(HBLPaletteMasks, sizeof(HBLPaletteMasks));
MemorySnapShot_Store(&VideoBase, sizeof(VideoBase));
if ( bSave )
{
addr = pVideoRaster - STRam;
MemorySnapShot_Store(&addr, sizeof(addr));
}
else
{
MemorySnapShot_Store(&addr, sizeof(addr));
pVideoRaster = &STRam[VideoBase];
}
MemorySnapShot_Store(&LineWidth, sizeof(LineWidth));
MemorySnapShot_Store(&HWScrollCount, sizeof(HWScrollCount));
MemorySnapShot_Store(&nScanlinesPerFrame, sizeof(nScanlinesPerFrame));
MemorySnapShot_Store(&nCyclesPerLine, sizeof(nCyclesPerLine));
MemorySnapShot_Store(&nFirstVisibleHbl, sizeof(nFirstVisibleHbl));
MemorySnapShot_Store(&bSteBorderFlag, sizeof(bSteBorderFlag));
MemorySnapShot_Store(&HblJitterIndex, sizeof(HblJitterIndex));
MemorySnapShot_Store(&VblJitterIndex, sizeof(VblJitterIndex));
MemorySnapShot_Store(&ShifterFrame, sizeof(ShifterFrame));
MemorySnapShot_Store(&bTTSampleHold, sizeof(bTTSampleHold));
MemorySnapShot_Store(&bTTHypermono, sizeof(bTTHypermono));
MemorySnapShot_Store(&TTSpecialVideoMode, sizeof(TTSpecialVideoMode));
}
/*-----------------------------------------------------------------------*/
/**
* Reset video chip
*/
void Video_Reset(void)
{
/* NOTE! Must reset all of these register type things here!!!! */
Video_Reset_Glue();
/* Set system specific timings */
Video_SetSystemTimings();
/* Reset VBL counter */
nVBLs = 0;
/* Reset addresses */
VideoBase = 0L;
/* Reset shifter's state variables */
ShifterFrame.Freq = -1;
ShifterFrame.Res = -1;
ShifterFrame.FreqPos50.VBL = -1;
ShifterFrame.FreqPos60.VBL = -1;
ShifterFrame.ResPosLo.VBL = -1;
ShifterFrame.ResPosMed.VBL = -1;
ShifterFrame.ResPosHi.VBL = -1;
ShifterFrame.Scroll8264Pos.VBL = -1;
ShifterFrame.Scroll8265Pos.VBL = -1;
Video_InitShifterLines ();
/* Reset STE screen variables */
LineWidth = 0;
HWScrollCount = 0;
bSteBorderFlag = false;
NewLineWidth = -1; /* cancel pending modifications set before the reset */
NewHWScrollCount = -1;
VideoCounterDelayedOffset = 0;
pVideoRasterDelayed = NULL;
/* Reset jitter indexes */
HblJitterIndex = 0;
VblJitterIndex = 0;
/* Clear framecycles counter */
Cycles_SetCounter(CYCLES_COUNTER_VIDEO, 0);
/* Clear ready for new VBL */
Video_ClearOnVBL();
}
/*-----------------------------------------------------------------------*/
/**
* Reset the GLUE chip responsible for generating the H/V sync signals.
* When the 68000 RESET instruction is called, frequency and resolution
* should be reset to 0.
*/
void Video_Reset_Glue(void)
{
Uint8 VideoShifterByte;
IoMem_WriteByte(0xff820a,0); /* Video frequency */
/* Are we in high-res? */
if (bUseHighRes)
VideoShifterByte = ST_HIGH_RES; /* Mono monitor */
else
VideoShifterByte = ST_LOW_RES;
if (bUseVDIRes)
VideoShifterByte = VDIRes;
IoMem_WriteByte(0xff8260, VideoShifterByte);
}
/*-----------------------------------------------------------------------*/
/*
* Set specific video timings, depending on the system being emulated.
*/
static void Video_SetSystemTimings(void)
{
if ( ConfigureParams.System.nMachineType == MACHINE_ST )
{
LineRemoveTopCycle = LINE_REMOVE_TOP_CYCLE_STF;
LineRemoveBottomCycle = LINE_REMOVE_BOTTOM_CYCLE_STF;
RestartVideoCounterCycle = RESTART_VIDEO_COUNTER_CYCLE_STF;
VblVideoCycleOffset = VBL_VIDEO_CYCLE_OFFSET_STF;
}
else /* STE, TT */
{
LineRemoveTopCycle = LINE_REMOVE_TOP_CYCLE_STE;
LineRemoveBottomCycle = LINE_REMOVE_BOTTOM_CYCLE_STE;
RestartVideoCounterCycle = RESTART_VIDEO_COUNTER_CYCLE_STE;
VblVideoCycleOffset = VBL_VIDEO_CYCLE_OFFSET_STE;
}
}
/*-----------------------------------------------------------------------*/
/**
* Convert the elapsed number of cycles since the start of the VBL
* into the corresponding HBL number and the cycle position in the current
* HBL. We use the starting cycle position of the closest HBL to compute
* the cycle position on the line (this allows to mix lines with different
* values for nCyclesPerLine).
* We can have 2 cases on the limit where the real video line count can be
* different from nHBL :
* - when reading video address between cycle 0 and 12, LineCycle will be <0,
* so we need to use the data from line nHBL-1
* - if LineCycle >= nCyclesPerLine, this means the HBL int was not processed
* yet, so the video line number is in fact nHBL+1
*/
void Video_ConvertPosition ( int FrameCycles , int *pHBL , int *pLineCycles )
{
if ( 0 && FrameCycles >= CyclesPerVBL ) /* rare case between end of last hbl and start of next VBL (during 64 cycles) */
{
*pHBL = ( FrameCycles - CyclesPerVBL ) / nCyclesPerLine;
*pLineCycles = ( FrameCycles - CyclesPerVBL ) % nCyclesPerLine;
//fprintf ( stderr , "out of vbl FrameCycles %d CyclesPerVBL %d nHBL=%d %d %d\n" , FrameCycles , CyclesPerVBL, nHBL , *pHBL , *pLineCycles );
}
else /* most common case */
{
*pHBL = nHBL;
*pLineCycles = FrameCycles - ShifterFrame.ShifterLines[ nHBL ].StartCycle;
if ( *pLineCycles < 0 ) /* reading from the previous video line */
{
*pHBL = nHBL-1;
*pLineCycles = FrameCycles - ShifterFrame.ShifterLines[ nHBL-1 ].StartCycle;
}
else if ( *pLineCycles >= nCyclesPerLine ) /* reading on the next line, but HBL int was delayed */
{
*pHBL = nHBL+1;
*pLineCycles -= nCyclesPerLine;
}
}
if ( *pLineCycles < 0 )
fprintf ( stderr , "bug nHBL=%d %d %d\n" , nHBL , *pHBL , *pLineCycles );
//if ( ( *pHBL != FrameCycles / nCyclesPerLine ) || ( *pLineCycles != FrameCycles % nCyclesPerLine ) )
// LOG_TRACE ( TRACE_VIDEO_ADDR , "conv pos %d %d - %d %d\n" , *pHBL , FrameCycles / nCyclesPerLine , *pLineCycles , FrameCycles % nCyclesPerLine );
// LOG_TRACE ( TRACE_VIDEO_ADDR , "conv pos %d %d %d\n" , FrameCycles , *pHBL , *pLineCycles );
}
void Video_GetPosition ( int *pFrameCycles , int *pHBL , int *pLineCycles )
{
*pFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);
Video_ConvertPosition ( *pFrameCycles , pHBL , pLineCycles );
}
void Video_GetPosition_OnWriteAccess ( int *pFrameCycles , int *pHBL , int *pLineCycles )
{
*pFrameCycles = Cycles_GetCounterOnWriteAccess(CYCLES_COUNTER_VIDEO);
Video_ConvertPosition ( *pFrameCycles , pHBL , pLineCycles );
}
void Video_GetPosition_OnReadAccess ( int *pFrameCycles , int *pHBL , int *pLineCycles )
{
*pFrameCycles = Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO);
Video_ConvertPosition ( *pFrameCycles , pHBL , pLineCycles );
}
/*-----------------------------------------------------------------------*/
/**
* Calculate and return video address pointer.
*/
static Uint32 Video_CalculateAddress ( void )
{
int FrameCycles, HblCounterVideo, LineCycles;
int X, NbBytes;
Uint32 VideoAddress; /* Address of video display in ST screen space */
int nSyncByte;
int Res;
int LineBorderMask;
int PrevSize;
int CurSize;
int LineStartCycle , LineEndCycle;
/* Find number of cycles passed during frame */
/* We need to subtract '12' for correct video address calculation */
FrameCycles = Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO) - 12;
/* Now find which pixel we are on (ignore left/right borders) */
Video_ConvertPosition ( FrameCycles , &HblCounterVideo , &LineCycles );
Res = IoMem_ReadByte ( 0xff8260 ) & 3;
if ( Res & 2 ) /* hi res */
{
LineStartCycle = LINE_START_CYCLE_71;
LineEndCycle = LINE_END_CYCLE_71;
HblCounterVideo = FrameCycles / nCyclesPerLine;
LineCycles = FrameCycles % nCyclesPerLine;
}
else
{
nSyncByte = IoMem_ReadByte(0xff820a) & 2; /* only keep bit 1 */
if (nSyncByte) /* 50 Hz */
{
LineStartCycle = LINE_START_CYCLE_50;
LineEndCycle = LINE_END_CYCLE_50;
}
else /* 60 Hz */
{
LineStartCycle = LINE_START_CYCLE_60;
LineEndCycle = LINE_END_CYCLE_60;
}
}
X = LineCycles;
/* Top of screen is usually 63 lines from VBL in 50 Hz */
if ( HblCounterVideo < nStartHBL )
{
/* pVideoRaster was set during Video_ClearOnVBL using VideoBase */
/* and it could also have been modified on STE by writing to ff8205/07/09 */
/* So, we should not use ff8201/ff8203 which are reloaded in ff8205/ff8207 only once per VBL */
/* but use pVideoRaster - STRam instead to get current shifter video address */
VideoAddress = pVideoRaster - STRam;
}
/* Special case when reading video counter in hi-res (used in the demo 'My Socks Are Weapons' by Legacy) */
/* This assumes a standard 640x400 resolution with no border removed, so code is simpler */
/* [NP] TODO : this should be handled in a more generic way with low/med cases */
/* even when Hatari is not started in monochrome mode */
else if ( Res & 2 ) /* Hi res */
{
if ( X < LineStartCycle )
X = LineStartCycle; /* display is disabled in the left border */
else if ( X > LineEndCycle )
X = LineEndCycle; /* display is disabled in the right border */
NbBytes = ( (X-LineStartCycle)>>1 ) & (~1); /* 2 cycles per byte */
/* One line uses 80 bytes instead of the standard 160 bytes in low/med res */
if ( HblCounterVideo < nStartHBL + VIDEO_HEIGHT_HBL_MONO )
VideoAddress = VideoBase + ( HblCounterVideo - nStartHBL ) * ( BORDERBYTES_NORMAL / 2 ) + NbBytes;
else
VideoAddress = VideoBase + VIDEO_HEIGHT_HBL_MONO * ( BORDERBYTES_NORMAL / 2 );
}
else if (FrameCycles > RestartVideoCounterCycle)
{
/* This is where ff8205/ff8207 are reloaded with the content of ff8201/ff8203 on a real ST */
/* (used in ULM DSOTS demos). VideoBase is also reloaded in Video_ClearOnVBL to be sure */
VideoBase = (Uint32)IoMem_ReadByte(0xff8201)<<16 | (Uint32)IoMem_ReadByte(0xff8203)<<8;
if (ConfigureParams.System.nMachineType != MACHINE_ST)
{
/* on STe 2 aligned, on TT 8 aligned. We do STe. */
VideoBase |= IoMem_ReadByte(0xff820d) & ~1;
}
VideoAddress = VideoBase;
}
else
{
VideoAddress = pVideoRaster - STRam; /* pVideoRaster is updated by Video_CopyScreenLineColor */
/* Now find which pixel we are on (ignore left/right borders) */
// X = ( Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO) - 12 ) % nCyclesPerLine;
/* Get real video line count (can be different from nHBL) */
// HblCounterVideo = ( Cycles_GetCounterOnReadAccess(CYCLES_COUNTER_VIDEO) - 12 ) / nCyclesPerLine;
/* Correct the case when read overlaps end of line / start of next line */
/* Video_CopyScreenLineColor was not called yet to update VideoAddress */
/* so we need to determine the size of the previous line to get the */
/* correct value of VideoAddress. */
PrevSize = 0;
if ( HblCounterVideo < nHBL )
X = 0;
else if ( ( HblCounterVideo > nHBL ) /* HblCounterVideo = nHBL+1 */
&& ( nHBL >= nStartHBL ) ) /* if nHBL was not visible, PrevSize = 0 */
{
LineBorderMask = ShifterFrame.ShifterLines[ HblCounterVideo-1 ].BorderMask; /* get border mask for nHBL */
PrevSize = BORDERBYTES_NORMAL; /* normal line */
if (LineBorderMask & BORDERMASK_LEFT_OFF)
PrevSize += BORDERBYTES_LEFT;
else if (LineBorderMask & BORDERMASK_LEFT_PLUS_2)
PrevSize += 2;
if (LineBorderMask & BORDERMASK_STOP_MIDDLE)
PrevSize -= 106;
else if (LineBorderMask & BORDERMASK_RIGHT_MINUS_2)
PrevSize -= 2;
else if (LineBorderMask & BORDERMASK_RIGHT_OFF)
PrevSize += BORDERBYTES_RIGHT;
if (LineBorderMask & BORDERMASK_EMPTY_LINE)
PrevSize = 0;
/* On STE, the Shifter skips the given amount of words as soon as display is disabled */
/* which is the case here when reading overlaps end/start of line (LineWidth is 0 on STF) */
PrevSize += LineWidth*2;
}
LineBorderMask = ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask;
CurSize = BORDERBYTES_NORMAL; /* normal line */
if (LineBorderMask & BORDERMASK_LEFT_OFF)
CurSize += BORDERBYTES_LEFT;
else if (LineBorderMask & BORDERMASK_LEFT_PLUS_2)
CurSize += 2;
else if (bSteBorderFlag) /* bigger line by 8 bytes on the left (STE specific) */
CurSize += 8;
else if ( ( HWScrollCount > 0 ) && ( HWScrollPrefetch == 1 ) )
CurSize += 8; /* 8 more bytes are loaded when scrolling with prefetching */
if (LineBorderMask & BORDERMASK_STOP_MIDDLE)
CurSize -= 106;
else if (LineBorderMask & BORDERMASK_RIGHT_MINUS_2)
CurSize -= 2;
else if (LineBorderMask & BORDERMASK_RIGHT_OFF)
CurSize += BORDERBYTES_RIGHT;
if (LineBorderMask & BORDERMASK_RIGHT_OFF_FULL)
CurSize += BORDERBYTES_RIGHT_FULL;
if ( LineBorderMask & BORDERMASK_LEFT_PLUS_2)
LineStartCycle = LINE_START_CYCLE_60;
else if ( LineBorderMask & BORDERMASK_LEFT_OFF )
LineStartCycle = LINE_START_CYCLE_71;
else if ( bSteBorderFlag )
LineStartCycle -= 16; /* display starts 16 pixels earlier */
else if ( ( HWScrollCount > 0 ) && ( HWScrollPrefetch == 1 ) )
LineStartCycle -= 16; /* shifter starts reading 16 pixels earlier when scrolling with prefetching */
LineEndCycle = LineStartCycle + CurSize*2;
if ( X < LineStartCycle )
X = LineStartCycle; /* display is disabled in the left border */
else if ( X > LineEndCycle )
{
X = LineEndCycle; /* display is disabled in the right border */
/* On STE, the Shifter skips the given amount of words as soon as display is disabled */
/* (LineWidth is 0 on STF) */
VideoAddress += LineWidth*2;
}
NbBytes = ( (X-LineStartCycle)>>1 ) & (~1); /* 2 cycles per byte */
/* when left border is open, we have 2 bytes less than theorical value */
/* (26 bytes in left border, which is not a multiple of 4 cycles) */
if ( LineBorderMask & BORDERMASK_LEFT_OFF )
NbBytes -= 2;
if ( LineBorderMask & BORDERMASK_EMPTY_LINE )
NbBytes = 0;
/* Add line cycles if we have not reached end of screen yet */
if ( HblCounterVideo < nEndHBL + BlankLines )
VideoAddress += PrevSize + NbBytes;
}
LOG_TRACE(TRACE_VIDEO_ADDR , "video base=%x raster=%x addr=%x video_cyc=%d "
"line_cyc=%d/X=%d @ nHBL=%d/video_hbl=%d %d<->%d pc=%x instr_cyc=%d\n",
VideoBase, (int)(pVideoRaster - STRam), VideoAddress,
Cycles_GetCounter(CYCLES_COUNTER_VIDEO), LineCycles, X, nHBL,
HblCounterVideo, LineStartCycle, LineEndCycle, M68000_GetPC(), CurrentInstrCycles);
return VideoAddress;
}
/*-----------------------------------------------------------------------*/
/**
* Calculate the cycle where the STF/STE's MMU starts reading
* data to send them to the shifter.
* On STE, if hscroll is used, prefetch will cause this position to
* happen 16 cycles earlier.
* This function should use the same logic as in Video_CalculateAddress.
* NOTE : this function is not completly accurate, as even when there's
* no hscroll (on STF) the mmu starts reading 16 cycles before display starts.
* But it's good enough to emulate writing to ff8205/07/09 on STE.
*/
static int Video_GetMMUStartCycle ( int DisplayStartCycle )
{
if ( bSteBorderFlag )
DisplayStartCycle -= 16; /* display starts 16 pixels earlier */
else if ( ( HWScrollCount > 0 ) && ( HWScrollPrefetch == 1 ) )
DisplayStartCycle -= 16; /* shifter starts reading 16 pixels earlier when scrolling with prefetching */
return DisplayStartCycle;
}
/*-----------------------------------------------------------------------*/
/**
* Write to VideoShifter (0xff8260), resolution bits
*/
static void Video_WriteToShifter ( Uint8 Res )
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE(TRACE_VIDEO_RES ,"shifter=0x%2.2X video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n",
Res, FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles );
/* Ignore consecutive writes of the same value */
if ( Res == ShifterFrame.Res )
return; /* do nothing */
if ( Res == 0x02 ) /* switch to high res */
{
if ( LineCycles < ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle ) /* start could be 0,52,56 */
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = LINE_START_CYCLE_71;
if ( ( LineCycles < ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle ) /* end could be 160,372,376,460 */
&& ( LineCycles < LINE_END_CYCLE_71 ) )
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = LINE_END_CYCLE_71;
}
else /* switch to lo/med res */
{
/* In lo/med res, display start/end depends on the freq register in $ff820a */
Video_Sync_SetDefaultStartEnd ( IoMem[0xff820a] & 2 , HblCounterVideo , LineCycles );
}
/* Remove left border : +26 bytes */
/* This can be done with a hi/lo res switch or a hi/med res switch */
if ( ( ShifterFrame.Res == 0x02 ) && ( Res == 0x00 ) /* switched from hi res to lo res */
// && ( LineCycles >= 12 ) /* switch back to low res should be after cycle 8 */
&& ( ( ShifterFrame.ResPosHi.LineCycles < 12 ) || ( ShifterFrame.ResPosHi.LineCycles >= 504 ) ) /* switch to hi between 504 and 8 */
&& ( LineCycles <= (LINE_START_CYCLE_71+28) )
&& ( FrameCycles - ShifterFrame.ResPosHi.FrameCycles <= 32 ) )
{
if ( ( ( ConfigureParams.System.nMachineType == MACHINE_STE ) /* special case for 504/4 and 508/4 on STE -> add 20 bytes to left border */
|| ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) )
&& ( ( ( ShifterFrame.ResPosHi.LineCycles == 504 ) && ( LineCycles == 4 ) )
|| ( ( ShifterFrame.ResPosHi.LineCycles == 508 ) && ( LineCycles == 4 ) ) ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_LEFT_OFF_2_STE;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = LINE_START_CYCLE_71+16; /* starts 16 pixels later */
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = -8; /* screen is shifted 8 pixels to the left */
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove left 2 ste %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
else /* other case for STF/STE -> add 26 bytes */
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_LEFT_OFF;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = LINE_START_CYCLE_71;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = -4; /* screen is shifted 4 pixels to the left */
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove left %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
}
if ( ( ShifterFrame.Res == 0x02 ) && ( Res == 0x01 ) /* switched from hi res to med res */
&& ( LineCycles <= (LINE_START_CYCLE_71+20) )
&& ( FrameCycles - ShifterFrame.ResPosHi.FrameCycles <= 30 ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_LEFT_OFF_MED; /* a later switch to low res might gives right scrolling */
/* By default, this line will be in med res, except if we detect hardware scrolling later */
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_OVERSCAN_MED_RES | ( 2 << 20 );
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = LINE_START_CYCLE_71;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove left med %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
/* Empty line switching res on STF : switch to hi res on cycle 28, then go back to med/lo res */
/* This creates a 0 byte line, the video counter won't change for this line */
else if ( ( ShifterFrame.Res == 0x02 ) /* switched from hi res */
&& ( FrameCycles - ShifterFrame.ResPosHi.FrameCycles <= 16 )
&& ( ShifterFrame.ResPosHi.LineCycles == LINE_EMPTY_CYCLE_71_STF )
&& ( ConfigureParams.System.nMachineType == MACHINE_ST ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_EMPTY_LINE;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = 0;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = 0;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect empty line res %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
/* Empty line switching res on STE (switch is 4 cycles later than on STF) */
else if ( ( ShifterFrame.Res == 0x02 ) /* switched from hi res */
&& ( FrameCycles - ShifterFrame.ResPosHi.FrameCycles <= 16 )
&& ( ShifterFrame.ResPosHi.LineCycles == LINE_EMPTY_CYCLE_71_STE )
&& ( ( ConfigureParams.System.nMachineType == MACHINE_STE )
|| ( ConfigureParams.System.nMachineType == MACHINE_MEGA_STE ) ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_EMPTY_LINE;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = 0;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = 0;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect empty line res %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
/* Empty line switching res on STF : switch to hi res just before the HBL then go back to lo/med res */
/* Next HBL will be an empty line (used in 'No Buddies Land' and 'Delirious Demo IV / NGC') */
else if ( ( ShifterFrame.Res == 0x02 ) /* switched from hi res */
&& ( ( ShifterFrame.ResPosHi.LineCycles == 500-4 ) || ( ShifterFrame.ResPosHi.LineCycles == 500 ) )
&& ( LineCycles == 508 ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask |= BORDERMASK_EMPTY_LINE;
ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = 0;
ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayEndCycle = 0;
BlankLines++; /* no video signal at all for this line */
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect empty line res 2 %d<->%d for nHBL=%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayEndCycle , nHBL+1 );
}
/* Start right border near middle of the line : -106 bytes */
/* Switch to hi res just before the start of the right border in hi res, then go back to lo/mid res */
if ( ( ShifterFrame.Res == 0x02 ) /* switched from hi res */
&& ( ShifterFrame.ResPosHi.HBL == HblCounterVideo ) /* switch during the same line */
&& ( ShifterFrame.ResPosHi.LineCycles <= LINE_END_CYCLE_71+4 ) /* switched to hi res before cycle 164 */
&& ( LineCycles >= LINE_END_CYCLE_71+4 ) ) /* switch to lo res after cycle 164 */
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_STOP_MIDDLE;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = LINE_END_CYCLE_71;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect stop middle %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
/* Remove right border a second time after removing it a first time. Display will */
/* stop at cycle 512 instead of 460. */
/* This removes left border on next line too (used in 'Enchanted Lands') */
/* If right border was not removed, then we will get an empty line for the next HBL (used in Beyond by Kruz) */
if ( ( ShifterFrame.Res == 0x02 ) /* switched from hi res */
&& ( LineCycles > LINE_END_CYCLE_50_2 ) /* switch to low just after end of right border */
&& ( ShifterFrame.ResPosHi.LineCycles <= LINE_END_CYCLE_50_2 ) /* switch to hi just before end of right border */
&& ( FrameCycles - ShifterFrame.ResPosHi.FrameCycles <= 20 ) )
{
if ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_RIGHT_OFF ) /* Enchanted Lands */
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_RIGHT_OFF_FULL;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = LINE_END_CYCLE_FULL;
ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask |= BORDERMASK_LEFT_OFF; /* no left border on next line */
ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = LINE_START_CYCLE_71;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove right full %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
else /* Pax Plax Parralax in Beyond by Kruz */
{
ShifterFrame.ShifterLines[ HblCounterVideo+1 ].BorderMask = BORDERMASK_EMPTY_LINE;
ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle = 0;
ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayEndCycle = 0;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect empty line res 3 %d<->%d for nHBL=%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo+1 ].DisplayEndCycle , nHBL+1 );
}
}
/* If left border is opened and we switch to medium resolution during the next cycles, */
/* then we assume a med res overscan line instead of a low res overscan line. */
/* Note that in that case, the switch to med res can shift the display by 0-3 words */
/* Used in 'No Cooper' greetings by 1984 and 'Punish Your Machine' by Delta Force */
if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_LEFT_OFF )
&& ( Res == 0x01 ) )
{
if ( ( LineCycles == LINE_LEFT_MED_CYCLE_1 ) /* 'No Cooper' timing */
|| ( LineCycles == LINE_LEFT_MED_CYCLE_1+16 ) ) /* 'No Cooper' timing while removing bottom border */
{
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect med res overscan offset 0 byte\n" );
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_OVERSCAN_MED_RES | ( 0 << 20 );
}
else if ( LineCycles == LINE_LEFT_MED_CYCLE_2 ) /* 'Best Part Of The Creation / PYM' timing */
{
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect med res overscan offset 2 bytes\n" );
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_OVERSCAN_MED_RES | ( 2 << 20 );
}
}
/* If left border was opened with a hi/med res switch we need to check */
/* if the switch to low res can trigger a right hardware scrolling. */
/* We store the pixels count in DisplayPixelShift */
if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_LEFT_OFF_MED )
&& ( Res == 0x00 ) && ( LineCycles <= LINE_SCROLL_1_CYCLE_50 ) )
{
/* The hi/med switch was a switch to do low res hardware scrolling, */
/* so we must cancel the med res overscan bit. */
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask &= (~BORDERMASK_OVERSCAN_MED_RES);
if ( LineCycles == LINE_SCROLL_13_CYCLE_50 ) /* cycle 20 */
{
LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 13 pixels right scroll\n" );
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 13;
}
else if ( LineCycles == LINE_SCROLL_9_CYCLE_50 ) /* cycle 24 */
{
LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 9 pixels right scroll\n" );
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 9;
}
else if ( LineCycles == LINE_SCROLL_5_CYCLE_50 ) /* cycle 28 */
{
LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 5 pixels right scroll\n" );
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 5;
}
else if ( LineCycles == LINE_SCROLL_1_CYCLE_50 ) /* cycle 32 */
{
LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 1 pixel right scroll\n" );
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 1;
}
}
#define SCROLL2_4PX
#ifdef SCROLL2_4PX
/* Left border was removed with a hi/lo switch, then a med res switch was made */
/* Depending on the low res switch, the screen will be shifted as a low res overscan line */
/* This is a different method than the one used by ST Connexion with only 3 res switches */
/* (so we must cancel the med res overscan bit) */
if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & BORDERMASK_OVERSCAN_MED_RES )
&& ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask & ( 0xf << 20 ) ) == 0 )
&& ( Res == 0x00 ) && ( LineCycles <= 40 ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask &= (~BORDERMASK_OVERSCAN_MED_RES); /* cancel mid res */
if ( LineCycles == 28 )
{
LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 13 pixels right scroll 2\n" );
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 13;
}
else if ( LineCycles == 32 )
{
LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 9 pixels right scroll 2\n" );
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 9;
}
else if ( LineCycles == 36 )
{
LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 5 pixels right scroll 2\n" );
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 5;
}
else if ( LineCycles == 40 )
{
LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect 1 pixel right scroll 2\n" );
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayPixelShift = 1;
}
}
#endif
/* Update HBL's position only if display has not reached pos LINE_START_CYCLE_50 */
/* and HBL interrupt was already handled at the beginning of this line. */
/* This also changes the number of cycles per line. */
if ( ( LineCycles <= LINE_START_CYCLE_50 ) && ( HblCounterVideo == nHBL ) )
{
nCyclesPerLine = Video_HBL_GetPos();
Video_AddInterruptHBL ( nCyclesPerLine );
}
/* Update Timer B's position */
LineTimerBCycle = Video_TimerB_GetPos ( HblCounterVideo );
Video_AddInterruptTimerB ( LineTimerBCycle );
ShifterFrame.Res = Res;
if ( Res == 0x02 ) /* high res */
{
ShifterFrame.ResPosHi.VBL = nVBLs;
ShifterFrame.ResPosHi.FrameCycles = FrameCycles;
ShifterFrame.ResPosHi.HBL = HblCounterVideo;
ShifterFrame.ResPosHi.LineCycles = LineCycles;
}
else if ( Res == 0x01 ) /* med res */
{
ShifterFrame.ResPosMed.VBL = nVBLs;
ShifterFrame.ResPosMed.FrameCycles = FrameCycles;
ShifterFrame.ResPosMed.HBL = HblCounterVideo;
ShifterFrame.ResPosMed.LineCycles = LineCycles;
}
else /* low res */
{
ShifterFrame.ResPosLo.VBL = nVBLs;
ShifterFrame.ResPosLo.FrameCycles = FrameCycles;
ShifterFrame.ResPosLo.HBL = HblCounterVideo;
ShifterFrame.ResPosLo.LineCycles = LineCycles;
}
}
/*-----------------------------------------------------------------------*/
/**
* Set some default values for DisplayStartCycle/DisplayEndCycle
* when changing frequency in lo/med res (testing orders are important
* because the line can already have some borders changed).
* This is necessary as some freq changes can modify start/end
* even if they're not made at the exact borders' positions.
* These values will be modified later if some borders are changed.
*/
static void Video_Sync_SetDefaultStartEnd ( Uint8 Freq , int HblCounterVideo , int LineCycles )
{
if ( Freq == 0x02 ) /* switch to 50 Hz */
{
if ( ( LineCycles <= ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle ) /* start could be 0,52,56 */
&& ( ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle == LINE_START_CYCLE_60 ) )
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = LINE_START_CYCLE_50;
if ( ( LineCycles <= ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle ) /* end could be 160,372,376,460 */
&& ( ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle < LINE_END_CYCLE_50 ) )
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = LINE_END_CYCLE_50;
}
else /* switch to 60 Hz */
{
if ( LineCycles < ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle ) /* start could be 0,52,56 */
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = LINE_START_CYCLE_60;
if ( ( LineCycles < ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle ) /* end could be 160,372,376,460 */
&& ( ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle <= LINE_END_CYCLE_50 ) )
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = LINE_END_CYCLE_60;
}
//fprintf ( stderr , "sync default pos %d %d %d\n", HblCounterVideo , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
/*-----------------------------------------------------------------------*/
/**
* Write to VideoSync (0xff820a), Hz setting
*/
void Video_Sync_WriteByte ( void )
{
int FrameCycles, HblCounterVideo, LineCycles;
Uint8 Freq;
if ( bUseVDIRes )
return; /* no 50/60 Hz freq in VDI mode */
/* We're only interested in bit 1 (50/60Hz) */
Freq = IoMem[0xff820a] & 2;
Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE(TRACE_VIDEO_SYNC ,"sync=0x%2.2X video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n",
Freq, FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles );
/* Ignore consecutive writes of the same value */
if ( Freq == ShifterFrame.Freq )
return; /* do nothing */
/* Ignore freq changes if we are in high res */
/* 2009/04/26 : don't ignore for now (see ST Cnx in Punish Your Machine) */
// if ( ShifterFrame.Res == 0x02 )
// return; /* do nothing */
/* Set some default values for DisplayStartCycle/DisplayEndCycle before checking for border removal */
Video_Sync_SetDefaultStartEnd ( Freq , HblCounterVideo , LineCycles );
if ( ( ShifterFrame.Freq == 0x00 ) && ( Freq == 0x02 ) /* switched from 60 Hz to 50 Hz ? */
// && ( ShifterFrame.FreqPos60.VBL == nVBLs ) /* switched during the same VBL */
&& ( HblCounterVideo >= nStartHBL ) /* only if display is on */
&& ( HblCounterVideo < nEndHBL + BlankLines ) ) /* only if display is on */
{
/* Blank line switching freq on STF : switch to 60 Hz on cycle 28, then go back to 50 Hz on cycle 36 */
/* This creates a blank line where no signal is displayed, but the video counter will still change for this line */
/* This blank line can be combined with left/right border changes */
if ( ( FrameCycles - ShifterFrame.FreqPos60.FrameCycles <= 16 )
&& ( ShifterFrame.FreqPos60.LineCycles == LINE_EMPTY_CYCLE_71_STF )
&& ( ConfigureParams.System.nMachineType == MACHINE_ST ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_BLANK_LINE;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect blank line freq stf\n" );
}
/* Add 2 bytes to left border : switch to 60 Hz before LINE_START_CYCLE_60 to force an early start */
/* of the DE signal, then go back to 50 Hz. Note that depending on where the 50 Hz switch is made */
/* the HBL signal will be at position 508 (60 Hz line) or 512 (50 Hz line) */
/* Obtaining a +2 line with 512 cycles requires a 2 cycles precision and is "wake up" state dependent : */
/* - On STF, switch must be on cycles 36/56 or 36/54 (depending on wake up state) */
/* - On STE, switch can be on cycles 36/56 or 36/54 (no wake up state in STE) */
/* TODO : we should change HBL signal to be on cycles 508 or 512 (it will always be 512 for now) */
if ( ( ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle == LINE_START_CYCLE_60 )
&& ( LineCycles >= LINE_START_CYCLE_50 ) /* The line started in 60 Hz and continues in 50 Hz */
&& ( LineCycles <= ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle ) ) /* change when line is active */
{
/* [FIXME] 'Panic' by Paulo Simoes, dont' trigger left+2 (need 2 cycles precision) */
/* The switch to 50 Hz on line 34 cycle 56 should just start a normal 50 Hz line, not a left+2 */
/* For now, we detect that we're running 'Panic' and if so we don't do left+2 (ugly hack...) */
if ( ( STMemory_ReadLong ( M68000_GetPC() ) == 0x4e7352b8 )
&& ( STMemory_ReadLong ( M68000_GetPC()+4 ) == 0x04664e73 )
&& ( HblCounterVideo == 34 ) && ( LineCycles == 56 ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = LINE_START_CYCLE_50;
}
/* Same for WinUAE's cpu core : GetPC() points to the current instr, not to the next one */
if ( ( STMemory_ReadLong ( M68000_GetPC()+2 ) == 0x4e7352b8 )
&& ( STMemory_ReadLong ( M68000_GetPC()+4+2 ) == 0x04664e73 )
&& ( HblCounterVideo == 34 ) && ( LineCycles == 56 ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = LINE_START_CYCLE_50;
}
/* [FIXME] 'Gen 4 Demo' by Ziggy Stardust / OVR. Same problem as 'Panic' above */
else if ( ( STMemory_ReadLong ( M68000_GetPC()-4 ) == 0x0002820a )
&& ( STMemory_ReadLong ( M68000_GetPC()+6 ) == 0x10388209 )
&& ( HblCounterVideo == 34 ) && ( LineCycles == 56 ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = LINE_START_CYCLE_50;
}
/* Normal case where left+2 should be made */
else
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_LEFT_PLUS_2;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = LINE_END_CYCLE_50;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect left+2 %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
}
/* Empty line switching freq on STF : start the line in 50 Hz, change to 60 Hz at the exact place */
/* where display is enabled in 50 Hz, then go back to 50 Hz. */
/* Due to 4 cycles precision instead of 2, we must accept a 60 Hz switch at pos 56 or 56+4 */
else if ( ( FrameCycles - ShifterFrame.FreqPos60.FrameCycles <= 24 )
&& ( ( ShifterFrame.FreqPos60.LineCycles == LINE_START_CYCLE_50 ) || ( ShifterFrame.FreqPos60.LineCycles == LINE_START_CYCLE_50+4 ) )
&& ( LineCycles > LINE_START_CYCLE_50 )
&& ( ConfigureParams.System.nMachineType == MACHINE_ST ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_EMPTY_LINE;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = 0;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = 0;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect empty line freq stf %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
/* Empty line switching freq on STE : similar to STF above, but doesn't require a 2 cycles precision */
/* The switches are made at cycles 40/52 */
else if ( ( FrameCycles - ShifterFrame.FreqPos60.FrameCycles <= 24 )
&& ( ShifterFrame.FreqPos60.LineCycles == 40 )
&& ( LineCycles == LINE_START_CYCLE_60 )
&& ( ConfigureParams.System.nMachineType == MACHINE_STE ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_EMPTY_LINE;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle = 0;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = 0;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect empty line freq ste %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
/* Remove 2 bytes to the right : start the line in 50 Hz (pos 0 or 56), change to 60 Hz before the position */
/* where display is disabled in 60 Hz, then go back to 50 Hz */
if ( ( LineCycles > LINE_END_CYCLE_60 ) /* back to 50 Hz after end of 60 Hz line */
&& ( ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle != LINE_START_CYCLE_60 ) /* start could be 0 or 56 */
&& ( ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle == LINE_END_CYCLE_60 ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_RIGHT_MINUS_2;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect right-2 %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
}
if ( ( ShifterFrame.Freq == 0x02 && Freq == 0x00 ) /* switched from 50 Hz to 60 Hz ? */
&& ( HblCounterVideo >= nStartHBL ) /* only if display is on */
&& ( HblCounterVideo < nEndHBL + BlankLines ) ) /* only if display is on */
{
/* remove right border, display 44 bytes more : switch to 60 Hz at the position where */
/* the line ends in 50 Hz. Some programs don't switch back to 50 Hz immediately */
/* (sync screen in SNY II), so we just check if freq changes to 60 Hz at the position where line should end in 50 Hz */
if ( ( LineCycles == LINE_END_CYCLE_50 )
&& ( ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle == LINE_END_CYCLE_50 ) )
{
ShifterFrame.ShifterLines[ HblCounterVideo ].BorderMask |= BORDERMASK_RIGHT_OFF;
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle = LINE_END_CYCLE_NO_RIGHT;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect remove right %d<->%d\n" ,
ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayStartCycle , ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle );
}
}
/* Store cycle position of freq 50/60 to check for top/bottom border removal in Video_EndHBL. */
if ( Freq == 0x02 ) /* switch to 50 Hz */
{
if ( ( HblCounterVideo < VIDEO_START_HBL_50HZ ) /* nStartHBL can change only if display is not ON yet */
&& ( OverscanMode & OVERSCANMODE_TOP ) == 0 ) /* update only if top was not removed */
nStartHBL = VIDEO_START_HBL_50HZ;
if ( ( HblCounterVideo < VIDEO_END_HBL_50HZ ) /* nEndHBL can change only if display is not OFF yet */
&& ( OverscanMode & OVERSCANMODE_BOTTOM ) == 0 ) /* update only if bottom was not removed */
nEndHBL = VIDEO_END_HBL_50HZ; /* 263 */
}
else if ( Freq == 0x00 ) /* switch to 60 Hz */
{
if ( ( HblCounterVideo < VIDEO_START_HBL_60HZ-1 ) /* nStartHBL can change only if display is not ON yet */
|| ( ( HblCounterVideo == VIDEO_START_HBL_60HZ-1 ) && ( LineCycles <= LineRemoveTopCycle ) ) )
nStartHBL = VIDEO_START_HBL_60HZ;
if ( ( HblCounterVideo < VIDEO_END_HBL_60HZ ) /* nEndHBL can change only if display is not OFF yet */
&& ( OverscanMode & OVERSCANMODE_BOTTOM ) == 0 ) /* update only if bottom was not removed */
nEndHBL = VIDEO_END_HBL_60HZ; /* 234 */
}
/* If the frequence changed, we need to update the EndLine interrupt */
/* so that it happens TIMERB_VIDEO_CYCLE_OFFSET cycles after the current DisplayEndCycle.*/
/* We check if the change affects the current line or the next one. */
/* We also need to check if the HBL interrupt and nCyclesPerLine need */
/* to be updated first. */
if ( Freq != ShifterFrame.Freq )
{
/* Update HBL's position only if display has not reached pos LINE_START_CYCLE_50 */
/* and HBL interrupt was already handled at the beginning of this line. */
/* This also changes the number of cycles per line. */
if ( ( LineCycles <= LINE_START_CYCLE_50 ) && ( HblCounterVideo == nHBL ) )
{
int CyclesPerLine_old = nCyclesPerLine;
nCyclesPerLine = Video_HBL_GetPos();
Video_AddInterruptHBL ( nCyclesPerLine );
/* In case we're mixing 50 Hz (512 cycles) and 60 Hz (508 cycles) lines on the same screen, */
/* we must update the position where the next VBL will happen (instead of the initial value in CyclesPerVBL) */
/* We check if number of cycles per line changes, and if so, we update the VBL's position */
if ( CyclesPerLine_old != nCyclesPerLine )
{
CyclesPerVBL += ( nCyclesPerLine - CyclesPerLine_old ); /* +4 or -4 */
CycInt_ModifyInterrupt ( nCyclesPerLine - CyclesPerLine_old , INT_CPU_CYCLE , INTERRUPT_VIDEO_VBL );
}
}
/* Update Timer B's position */
LineTimerBCycle = Video_TimerB_GetPos ( HblCounterVideo );
Video_AddInterruptTimerB ( LineTimerBCycle );
}
ShifterFrame.Freq = Freq;
if ( Freq == 0x02 ) /* 50 Hz */
{
ShifterFrame.FreqPos50.VBL = nVBLs;
ShifterFrame.FreqPos50.FrameCycles = FrameCycles;
ShifterFrame.FreqPos50.HBL = HblCounterVideo;
ShifterFrame.FreqPos50.LineCycles = LineCycles;
}
else
{
ShifterFrame.FreqPos60.VBL = nVBLs;
ShifterFrame.FreqPos60.FrameCycles = FrameCycles;
ShifterFrame.FreqPos60.HBL = HblCounterVideo;
ShifterFrame.FreqPos60.LineCycles = LineCycles;
}
}
/*-----------------------------------------------------------------------*/
/**
* Compute the cycle position where the HBL should happen on each line.
* In low/med res, the position depends on the video frequency (50/60 Hz)
* In high res, the position is always the same.
* This position also gives the number of CPU cycles per video line.
*/
static int Video_HBL_GetPos ( void )
{
int Pos;
if ( ( IoMem_ReadByte ( 0xff8260 ) & 3 ) == 2 ) /* hi res */
Pos = CYCLES_PER_LINE_71HZ;
else /* low res or med res */
{
if ( IoMem_ReadByte ( 0xff820a ) & 2 ) /* 50 Hz, pos 512 */
Pos = CYCLES_PER_LINE_50HZ;
else /* 60 Hz, pos 508 */
Pos = CYCLES_PER_LINE_60HZ;
}
return Pos;
}
/*-----------------------------------------------------------------------*/
/**
* Compute the cycle position where the timer B should happen on each
* visible line.
* We compute Timer B position for the given LineNumber, using start/end
* display cycles from ShifterLines[ LineNumber ].
* The position depends on the start of line / end of line positions
* (which depend on the current frequency / border tricks) and
* on the value of the bit 3 in the MFP's AER.
* If bit is 0, timer B will count end of line events (usual case),
* but if bit is 1, timer B will count start of line events (eg Seven Gates Of Jambala)
*/
int Video_TimerB_GetPos ( int LineNumber )
{
int Pos;
if ( ( IoMem[0xfffa03] & ( 1 << 3 ) ) == 0 ) /* we're counting end of line events */
{
Pos = ShifterFrame.ShifterLines[ LineNumber ].DisplayEndCycle + TIMERB_VIDEO_CYCLE_OFFSET;
}
else /* we're counting start of line events */
{
Pos = ShifterFrame.ShifterLines[ LineNumber ].DisplayStartCycle + TIMERB_VIDEO_CYCLE_OFFSET;
}
//fprintf ( stderr , "timerb pos=%d\n" , Pos );
return Pos;
}
/*-----------------------------------------------------------------------*/
/**
* Compute the default cycle position where the timer B should happen
* on the next line, when restarting the INTERRUPT_VIDEO_ENDLINE handler.
* In low/med res, the position depends on the video frequency (50/60 Hz)
* In high res, the position is always the same.
*/
static int Video_TimerB_GetDefaultPos ( void )
{
int Pos;
if ( ( IoMem[0xfffa03] & ( 1 << 3 ) ) == 0 ) /* we're counting end of line events */
{
if ( ( IoMem_ReadByte ( 0xff8260 ) & 3 ) == 2 ) /* hi res */
Pos = LINE_END_CYCLE_71;
else /* low res or med res */
{
if ( IoMem_ReadByte ( 0xff820a ) & 2 ) /* 50 Hz, pos 376 */
Pos = LINE_END_CYCLE_50;
else /* 60 Hz, pos 372 */
Pos = LINE_END_CYCLE_60;
}
}
else /* we're counting start of line events */
{
if ( ( IoMem_ReadByte ( 0xff8260 ) & 3 ) == 2 ) /* hi res */
Pos = LINE_START_CYCLE_71;
else /* low res or med res */
{
if ( IoMem_ReadByte ( 0xff820a ) & 2 ) /* 50 Hz, pos 56 */
Pos = LINE_START_CYCLE_50;
else /* 60 Hz, pos 52 */
Pos = LINE_START_CYCLE_60;
}
}
Pos += TIMERB_VIDEO_CYCLE_OFFSET;
//fprintf ( stderr , "timerb default pos=%d\n" , Pos );
return Pos;
}
/*-----------------------------------------------------------------------*/
/**
* HBL interrupt : this occurs at the end of every line, on cycle 512 (in 50 Hz)
* It takes 56 cycles to handle the 68000's exception.
*/
void Video_InterruptHandler_HBL ( void )
{
int FrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);
int PendingCyclesOver;
int NewHBLPos;
/* How many cycle was this HBL delayed (>= 0) */
PendingCyclesOver = -INT_CONVERT_FROM_INTERNAL ( PendingInterruptCount , INT_CPU_CYCLE );
/* Remove this interrupt from list and re-order */
CycInt_AcknowledgeInterrupt();
/* Videl Vertical counter increment (To be removed when Videl emulation is finished) */
/* VFC is incremented every half line, here, we increment it every line (should be completed) */
if (ConfigureParams.System.nMachineType == MACHINE_FALCON) {
vfc_counter += 1;
}
/* Increment the hbl jitter index */
HblJitterIndex++;
HblJitterIndex %= HBL_JITTER_ARRAY_SIZE;
LOG_TRACE ( TRACE_VIDEO_HBL , "HBL %d video_cyc=%d pending_cyc=%d jitter=%d\n" ,
nHBL , FrameCycles , PendingCyclesOver , HblJitterArray[ HblJitterIndex ] );
/* Default cycle position for next HBL */
NewHBLPos = Video_HBL_GetPos();
/* Generate new HBL, if need to - there are 313 HBLs per frame in 50 Hz */
if (nHBL < nScanlinesPerFrame-1)
Video_AddInterruptHBL ( NewHBLPos );
/* In case we're mixing 50 Hz (512 cycles) and 60 Hz (508 cycles) lines on the same screen, */
/* we must update the position where the next VBL will happen (instead of the initial value in CyclesPerVBL) */
/* During a 50 Hz screen, each 60 Hz line will make the VBL happen 4 cycles earlier */
if ( ( nScanlinesPerFrame == SCANLINES_PER_FRAME_50HZ )
&& ( NewHBLPos == CYCLES_PER_LINE_60HZ ) )
{
CyclesPerVBL -= 4;
CycInt_ModifyInterrupt ( -4 , INT_CPU_CYCLE , INTERRUPT_VIDEO_VBL );
}
/* During a 60 Hz screen, each 50 Hz line will make the VBL happen 4 cycles later */
else if ( ( nScanlinesPerFrame == SCANLINES_PER_FRAME_60HZ )
&& ( NewHBLPos == CYCLES_PER_LINE_50HZ ) )
{
CyclesPerVBL += 4;
CycInt_ModifyInterrupt ( 4 , INT_CPU_CYCLE , INTERRUPT_VIDEO_VBL );
}
/* Print traces if pending HBL bit changed just before IACK when HBL interrupt is allowed */
if ( ( CPU_IACK == true ) && ( regs.intmask < 2 ) )
{
if ( pendingInterrupts & ( 1 << 2 ) )
{
LOG_TRACE ( TRACE_VIDEO_HBL , "HBL %d, pending set again just before iack, skip one HBL interrupt VBL=%d video_cyc=%d pending_cyc=%d jitter=%d\n" ,
nHBL , nVBLs , FrameCycles , PendingCyclesOver , VblJitterArray[ VblJitterIndex ] );
}
else
{
LOG_TRACE ( TRACE_VIDEO_HBL , "HBL %d, new pending HBL set just before iack VBL=%d video_cyc=%d pending_cyc=%d jitter=%d\n" ,
nHBL , nVBLs , FrameCycles , PendingCyclesOver , VblJitterArray[ VblJitterIndex ] );
}
}
/* Set pending bit for HBL interrupt in the CPU IPL */
M68000_Exception(EXCEPTION_NR_HBLANK , M68000_EXC_SRC_AUTOVEC); /* Horizontal blank interrupt, level 2 */
Video_EndHBL(); /* Check some borders removal and copy line to display buffer */
DmaSnd_STE_HBL_Update(); /* Update STE DMA sound if needed */
/* TEMP IPF */
IPF_Emulate();
/* TEMP IPF */
nHBL++; /* Increase HBL count */
if (nHBL < nScanlinesPerFrame)
{
/* Update start cycle for next HBL */
ShifterFrame.ShifterLines[ nHBL ].StartCycle = FrameCycles - PendingCyclesOver;
LOG_TRACE(TRACE_VIDEO_HBL, "HBL %d start=%d %x\n", nHBL,
ShifterFrame.ShifterLines[nHBL].StartCycle, ShifterFrame.ShifterLines[nHBL].StartCycle);
/* Setup next HBL */
Video_StartHBL();
}
}
/*-----------------------------------------------------------------------*/
/**
* Check at end of each HBL to see if any Shifter hardware tricks have been attempted
* and copy the line to the screen buffer.
* This is the place to check if top/bottom border were removed, as well as if some
* left/right border changes were not validated before.
* NOTE : the tests must be made with nHBL in ascending order.
*/
static void Video_EndHBL(void)
{
//
// Handle top/bottom borders removal when switching freq
//
/* Remove top border if the switch to 60 Hz was made during this vbl before cycle */
/* LineRemoveTopCycle on line 33 and if the switch to 50 Hz has not yet occurred or */
/* occurred before the 60 Hz or occurred after cycle LineRemoveTopCycle on line 33. */
if ( ( nHBL == VIDEO_START_HBL_60HZ-1 ) /* last HBL before first line of a 60 Hz screen */
&& ( ShifterFrame.FreqPos60.VBL == nVBLs ) /* switch to 60 Hz during this VBL */
&& ( ( ShifterFrame.FreqPos60.HBL < nHBL )
|| ( ( ShifterFrame.FreqPos60.HBL == nHBL ) && ( ShifterFrame.FreqPos60.LineCycles <= LineRemoveTopCycle ) ) )
&& ( ( ShifterFrame.FreqPos50.VBL < nVBLs )
|| ( ShifterFrame.FreqPos50.FrameCycles < ShifterFrame.FreqPos60.FrameCycles )
|| ( ShifterFrame.FreqPos50.HBL > nHBL )
|| ( ( ShifterFrame.FreqPos50.HBL == nHBL ) && ( ShifterFrame.FreqPos50.LineCycles > LineRemoveTopCycle ) ) ) )
{
/* Top border */
LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect remove top\n" );
OverscanMode |= OVERSCANMODE_TOP; /* Set overscan bit */
nStartHBL = VIDEO_START_HBL_60HZ; /* New start screen line */
pHBLPaletteMasks -= OVERSCAN_TOP; // FIXME useless ?
pHBLPalettes -= OVERSCAN_TOP; // FIXME useless ?
}
/* Remove bottom border for a 60 Hz screen (tests are similar to the ones for top border) */
else if ( ( nHBL == VIDEO_END_HBL_60HZ + BlankLines - 1 ) /* last displayed line in 60 Hz */
&& ( nStartHBL == VIDEO_START_HBL_60HZ ) /* screen started in 60 Hz */
&& ( ( OverscanMode & OVERSCANMODE_TOP ) == 0 ) /* and top border was not removed : this screen is only 60 Hz */
&& ( ShifterFrame.FreqPos50.VBL == nVBLs ) /* switch to 50 Hz during this VBL */
&& ( ( ShifterFrame.FreqPos50.HBL < nHBL )
|| ( ( ShifterFrame.FreqPos50.HBL == nHBL ) && ( ShifterFrame.FreqPos50.LineCycles <= LineRemoveBottomCycle-4 ) ) )
&& ( ( ShifterFrame.FreqPos60.VBL < nVBLs )
|| ( ShifterFrame.FreqPos60.FrameCycles < ShifterFrame.FreqPos50.FrameCycles )
|| ( ShifterFrame.FreqPos60.HBL > nHBL )
|| ( ( ShifterFrame.FreqPos60.HBL == nHBL ) && ( ShifterFrame.FreqPos60.LineCycles > LineRemoveBottomCycle-4 ) ) ) )
{
LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect remove bottom 60Hz\n" );
OverscanMode |= OVERSCANMODE_BOTTOM;
nEndHBL = SCANLINES_PER_FRAME_60HZ; /* new end for a 60 Hz screen */
}
/* Remove bottom border for a 50 Hz screen (tests are similar to the ones for top border) */
else if ( ( nHBL == VIDEO_END_HBL_50HZ + BlankLines - 1 ) /* last displayed line in 50 Hz */
&& ( ( OverscanMode & OVERSCANMODE_BOTTOM ) == 0 ) /* border was not already removed at line VIDEO_END_HBL_60HZ-1 */
&& ( ShifterFrame.FreqPos60.VBL == nVBLs ) /* switch to 60 Hz during this VBL */
&& ( ( ShifterFrame.FreqPos60.HBL < nHBL )
|| ( ( ShifterFrame.FreqPos60.HBL == nHBL ) && ( ShifterFrame.FreqPos60.LineCycles <= LineRemoveBottomCycle ) ) )
&& ( ( ShifterFrame.FreqPos50.VBL < nVBLs )
|| ( ShifterFrame.FreqPos50.FrameCycles < ShifterFrame.FreqPos60.FrameCycles )
|| ( ShifterFrame.FreqPos50.HBL > nHBL )
|| ( ( ShifterFrame.FreqPos50.HBL == nHBL ) && ( ShifterFrame.FreqPos50.LineCycles > LineRemoveBottomCycle ) ) ) )
{
LOG_TRACE ( TRACE_VIDEO_BORDER_V , "detect remove bottom\n" );
OverscanMode |= OVERSCANMODE_BOTTOM;
nEndHBL = VIDEO_END_HBL_50HZ+VIDEO_HEIGHT_BOTTOM_50HZ; /* new end for a 50 Hz screen */
}
//
// Check some left/right borders effects that were not detected earlier
// (this is usually due to staying in 60 Hz for too long, which is often a bad
// coding practice as it can distort the display on a real ST)
//
/* Special case when the line was not started in 60 Hz, then switched to 60 Hz */
/* and was not restored to 50 Hz before the end of the line. In that case, the */
/* line ends 2 bytes earlier on the right (line can start at LINE_START_CYCLE_71/50) */
/* Some programs also turn to 60 Hz too early during the active display of the last */
/* line to remove the bottom border (FNIL by TNT), in that case, we should also remove */
/* 2 bytes to this line */
if ( ( ( ShifterFrame.ShifterLines[ nHBL ].BorderMask & BORDERMASK_RIGHT_MINUS_2 ) == 0 )
&& ( ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle != LINE_START_CYCLE_60 ) /* start could be 0 or 56 */
&& ( ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle == LINE_END_CYCLE_60 ) )
{
ShifterFrame.ShifterLines[ nHBL ].BorderMask |= BORDERMASK_RIGHT_MINUS_2;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect late right-2 %d<->%d\n" ,
ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle , ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle );
}
/* Similar case when line started in 60 Hz but did not end at the usual LINE_END_CYCLE_60 position */
/* (line can end at LINE_END_CYCLE_71/50 or have right border removed) */
/* This means left border had 2 bytes more to display */
if ( ( ( ShifterFrame.ShifterLines[ nHBL ].BorderMask & BORDERMASK_LEFT_PLUS_2 ) == 0 )
&& ( ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle == LINE_START_CYCLE_60 )
&& ( ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle != LINE_END_CYCLE_60 ) ) /* end could be 160, 372 or 460 */
{
ShifterFrame.ShifterLines[ nHBL ].BorderMask |= BORDERMASK_LEFT_PLUS_2;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "detect late left+2 %d<->%d\n" ,
ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle , ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle );
}
/* Although a 'left+2' was detected earlier, the freq was switched back to 60 Hz during DE, so the line is just */
/* a normal 60 Hz line ; we must cancel the 'left+2' flag */
else if ( ( ShifterFrame.ShifterLines[ nHBL ].BorderMask & BORDERMASK_LEFT_PLUS_2 )
&& ( ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle == LINE_END_CYCLE_60 ) )
{
ShifterFrame.ShifterLines[ nHBL ].BorderMask &= ~BORDERMASK_LEFT_PLUS_2;
LOG_TRACE ( TRACE_VIDEO_BORDER_H , "cancel late left+2 %d<->%d\n" ,
ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle , ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle );
}
/* Store palette for very first line on screen - HBLPalettes[0] */
if (nHBL == nFirstVisibleHbl-1)
{
/* Store ALL palette for this line into raster table for datum */
Video_StoreFirstLinePalette();
}
if (bUseHighRes)
{
/* Copy for hi-res (no overscan) */
if (nHBL >= nFirstVisibleHbl && nHBL < nLastVisibleHbl)
Video_CopyScreenLineMono();
}
/* Are we in possible visible color display (including borders)? */
else if (nHBL >= nFirstVisibleHbl && nHBL < nLastVisibleHbl)
{
/* Store resolution for every line so can check for mix low/med screens */
Video_StoreResolution(nHBL-nFirstVisibleHbl);
/* Copy line of screen to buffer to simulate TV raster trace
* - required for mouse cursor display/game updates
* Eg, Lemmings and The Killing Game Show are good examples */
Video_CopyScreenLineColor();
}
}
/*-----------------------------------------------------------------------*/
/**
* Set default values for the next HBL, depending on the current res/freq.
* We set the number of cycles per line, as well as some default values
* for display start/end cycle.
*/
static void Video_StartHBL(void)
{
if ((IoMem_ReadByte(0xff8260) & 3) == 2) /* hi res */
{
nCyclesPerLine = CYCLES_PER_LINE_71HZ;
ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle = LINE_START_CYCLE_71;
ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle = LINE_END_CYCLE_71;
}
else
{
if ( IoMem_ReadByte ( 0xff820a ) & 2 ) /* 50 Hz */
{
nCyclesPerLine = CYCLES_PER_LINE_50HZ;
if ( ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle == -1 ) /* start not set yet */
ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle = LINE_START_CYCLE_50;
ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle = LINE_END_CYCLE_50;
}
else /* 60 Hz */
{
nCyclesPerLine = CYCLES_PER_LINE_60HZ;
if ( ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle == -1 ) /* start not set yet */
ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle = LINE_START_CYCLE_60;
ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle = LINE_END_CYCLE_60;
}
}
//fprintf (stderr , "Video_StartHBL %d %d %d\n", nHBL , ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle , ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle );
}
/*-----------------------------------------------------------------------*/
/**
* End Of Line interrupt
* This interrupt is started on cycle position 404 in 50 Hz and on cycle
* position 400 in 60 Hz. 50 Hz display ends at cycle 376 and 60 Hz displays
* ends at cycle 372. This means the EndLine interrupt happens 24 cycles
* after DisplayEndCycle.
* Note that if bit 3 of MFP AER is 1, then timer B will count start of line
* instead of end of line (at cycle 52+24 or 56+24)
*/
void Video_InterruptHandler_EndLine(void)
{
int FrameCycles, HblCounterVideo, LineCycles;
int PendingCycles = -INT_CONVERT_FROM_INTERNAL ( PendingInterruptCount , INT_CPU_CYCLE );
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE ( TRACE_VIDEO_HBL , "EndLine TB %d video_cyc=%d line_cyc=%d pending_int_cnt=%d\n" ,
nHBL , FrameCycles , LineCycles , PendingCycles );
/* Remove this interrupt from list and re-order */
CycInt_AcknowledgeInterrupt();
/* Ignore HBLs in VDI mode */
if (bUseVDIRes)
return;
/* Generate new Endline, if need to - there are 313 HBLs per frame */
if (nHBL < nScanlinesPerFrame-1)
{
/* By default, next EndLine's int will be on line nHBL+1 at pos 376+24 or 372+24 */
if ( ( IoMem[0xfffa03] & ( 1 << 3 ) ) == 0 ) /* count end of line */
{
/* If EndLine int is delayed too much (more than 100 cycles), nLineCycles will */
/* be in the range 0..xxx instead of 400..512. In that case, we need to add */
/* nCyclesPerLine to be in the range 512..x+512 */
/* Maximum possible delay should be around 160 cycles on STF (DIVS) */
/* In that case, HBL int will be delayed too, so we will have HblCounterVideo == nHBL+1 */
if ( HblCounterVideo == nHBL+1 ) /* int happened in fact on the next line nHBL+1 */
LineCycles += nCyclesPerLine;
LineTimerBCycle = Video_TimerB_GetDefaultPos ();
}
else /* count start of line, no possible delay to handle */
{
LineTimerBCycle = Video_TimerB_GetDefaultPos ();
}
//fprintf ( stderr , "new tb %d %d %d\n" , LineTimerBCycle , nCyclesPerLine , LineTimerBCycle - LineCycles + nCyclesPerLine );
CycInt_AddRelativeInterrupt ( LineTimerBCycle - LineCycles + nCyclesPerLine,
INT_CPU_CYCLE, INTERRUPT_VIDEO_ENDLINE );
}
/* Timer B occurs at END of first visible screen line in Event Count mode */
if ( ( nHBL >= nStartHBL ) && ( nHBL < nEndHBL + BlankLines ) )
{
/* Handle Timer B when using Event Count mode */
/* We must ensure that the write to fffa1b to activate timer B was */
/* completed before the point where the end of line signal was generated */
/* (in the case of a move.b #8,$fffa1b that would happen 4 cycles */
/* before end of line, the interrupt should not be generated) */
if ( (MFP_TBCR == 0x08) /* Is timer in Event Count mode ? */
&& ( ( TimerBEventCountCycleStart == -1 ) /* timer B was started during a previous VBL */
|| ( TimerBEventCountCycleStart < FrameCycles-PendingCycles ) ) ) /* timer B was started before this possible interrupt */
MFP_TimerB_EventCount_Interrupt ( PendingCycles ); /* we have a valid timer B interrupt */
}
}
/*-----------------------------------------------------------------------*/
/**
* Store whole palette on first line so have reference to work from
*/
static void Video_StoreFirstLinePalette(void)
{
Uint16 *pp2;
int i;
pp2 = (Uint16 *)&IoMem[0xff8240];
for (i = 0; i < 16; i++)
{
HBLPalettes[i] = SDL_SwapBE16(*pp2++);
if ( ConfigureParams.System.nMachineType == MACHINE_ST)
HBLPalettes[i] &= 0x777; /* Force unused "random" bits to 0 */
}
/* And set mask flag with palette and resolution */
// FIXME ; enlever PALETTEMASK_RESOLUTION
// if ( ShifterFrame.ShifterLines[ nFirstVisibleHbl ].BorderMask == BORDERMASK_NONE ) // no border trick, store the current res
HBLPaletteMasks[0] = (PALETTEMASK_RESOLUTION|PALETTEMASK_PALETTE) | (((Uint32)IoMem_ReadByte(0xff8260)&0x3)<<16);
// else // border removal, assume low res for the whole line
// HBLPaletteMasks[0] = (PALETTEMASK_RESOLUTION|PALETTEMASK_PALETTE) | (0<<16);
}
/*-----------------------------------------------------------------------*/
/**
* Store resolution on each line (used to test if mixed low/medium resolutions)
*/
static void Video_StoreResolution(int y)
{
Uint8 res;
int Mask;
/* Clear resolution, and set with current value */
if (!(bUseHighRes || bUseVDIRes))
{
if ( y >= HBL_PALETTE_MASKS ) /* we're above the limit (res was switched to mono for more than 1 VBL in color mode ?) */
{
// fprintf ( stderr , "store res %d line %d hbl %d %x %x %d\n" , res , y , nHBL, Mask , HBLPaletteMasks[y] , sizeof(HBLPalettes) );
y = HBL_PALETTE_MASKS - 1; /* store in the last palette line */
}
HBLPaletteMasks[y] &= ~(0x3<<16);
res = IoMem_ReadByte(0xff8260)&0x3;
Mask = ShifterFrame.ShifterLines[ y+nFirstVisibleHbl ].BorderMask;
if ( Mask & BORDERMASK_OVERSCAN_MED_RES ) /* special case for med res to render the overscan line */
res = 1; /* med res instead of low res */
else if ( Mask != BORDERMASK_NONE ) /* border removal : assume low res for the whole line */
res = 0;
HBLPaletteMasks[y] |= PALETTEMASK_RESOLUTION|((Uint32)res)<<16;
#if 0
if ( ( Mask == BORDERMASK_NONE ) /* no border trick, store the current res */
|| ( res == 0 ) || ( res == 1 ) ) /* if border trick, ignore passage to hi res */
HBLPaletteMasks[y] |= PALETTEMASK_RESOLUTION|((Uint32)res)<<16;
else /* border removal or hi res : assume low res for the whole line */
HBLPaletteMasks[y] |= (0)<<16;
/* special case for med res to render the overscan line */
if ( Mask & BORDERMASK_OVERSCAN_MED_RES )
HBLPaletteMasks[y] |= PALETTEMASK_RESOLUTION|((Uint32)1)<<16; /* med res instead of low res */
#endif
// fprintf ( stderr , "store res %d line %d %x %x\n" , res , y , Mask , HBLPaletteMasks[y] );
}
}
/*-----------------------------------------------------------------------*/
/**
* Copy one line of monochrome screen into buffer for conversion later.
*/
static void Video_CopyScreenLineMono(void)
{
/* Copy one line - 80 bytes in ST high resolution */
memcpy(pSTScreen, pVideoRaster, SCREENBYTES_MONOLINE);
pVideoRaster += SCREENBYTES_MONOLINE;
/* Handle STE fine scrolling (HWScrollCount is zero on ST). */
if (HWScrollCount)
{
Uint16 *pScrollAdj;
int nNegScrollCnt;
pScrollAdj = (Uint16 *)pSTScreen;
nNegScrollCnt = 16 - HWScrollCount;
/* Shift the whole line by the given scroll count */
while ((Uint8*)pScrollAdj < pSTScreen + SCREENBYTES_MONOLINE-2)
{
do_put_mem_word(pScrollAdj, (do_get_mem_word(pScrollAdj) << HWScrollCount)
| (do_get_mem_word(pScrollAdj+1) >> nNegScrollCnt));
++pScrollAdj;
}
/* Handle the last 16 pixels of the line */
do_put_mem_word(pScrollAdj, (do_get_mem_word(pScrollAdj) << HWScrollCount)
| (do_get_mem_word(pVideoRaster) >> nNegScrollCnt));
/* HW scrolling advances Shifter video counter by one */
pVideoRaster += 1 * 2;
}
/* LineWidth is zero on ST. */
/* On STE, the Shifter skips the given amount of words. */
pVideoRaster += LineWidth*2;
/* On STE, handle modifications of the video counter address $ff8205/07/09 */
/* that occurred while the display was already ON */
if ( VideoCounterDelayedOffset != 0 )
{
pVideoRaster += ( VideoCounterDelayedOffset & ~1 );
VideoCounterDelayedOffset = 0;
}
if ( pVideoRasterDelayed != NULL )
{
pVideoRaster = pVideoRasterDelayed;
pVideoRasterDelayed = NULL;
}
/* On STE, if we wrote to the hwscroll register, we set the */
/* new value here, once the current line was processed */
if ( NewHWScrollCount >= 0 )
{
HWScrollCount = NewHWScrollCount;
NewHWScrollCount = -1;
}
/* On STE, if we wrote to the linewidth register, we set the */
/* new value here, once the current line was processed */
if ( NewLineWidth >= 0 )
{
LineWidth = NewLineWidth;
NewLineWidth = -1;
}
/* Each screen line copied to buffer is always same length */
pSTScreen += SCREENBYTES_MONOLINE;
/* We must keep the new video address in a 24 bit space */
/* (in case it pointed to IO space and is now >= 0x1000000) */
pVideoRaster = ( ( pVideoRaster - STRam ) & 0xffffff ) + STRam;
}
/*-----------------------------------------------------------------------*/
/**
* Copy one line of color screen into buffer for conversion later.
* Possible lines may be top/bottom border, and/or left/right borders.
*/
static void Video_CopyScreenLineColor(void)
{
int LineBorderMask;
int VideoOffset = 0;
int STF_PixelScroll = 0;
int LineRes;
Uint8 *pVideoRasterEndLine; /* addr of the last byte copied from pVideoRaster to pSTScreen (for HWScrollCount) */
int i;
LineBorderMask = ShifterFrame.ShifterLines[ nHBL ].BorderMask;
STF_PixelScroll = ShifterFrame.ShifterLines[ nHBL ].DisplayPixelShift;
/* Get resolution for this line (in case of mixed low/med screen) */
i = nHBL-nFirstVisibleHbl;
if ( i >= HBL_PALETTE_MASKS )
i = HBL_PALETTE_MASKS - 1;
LineRes = ( HBLPaletteMasks[i] >> 16 ) & 1; /* 0=low res 1=med res */
//fprintf(stderr , "copy line %d start %d end %d 0x%x 0x%x\n" , nHBL, nStartHBL, nEndHBL, LineBorderMask, pVideoRaster - STRam);
/* FIXME [NP] : when removing left border and displaying med res at 60 Hz on STE, we have a 3 pixel shift */
/* to correct to have bitmaps and color changes in sync. */
/* For now we only shift for med @ 60 Hz, but this should be measured for all */
/* freq and low / med res combinations on a real STE (fix "HighResMode" demo by Paradox). */
if ( ( ConfigureParams.System.nMachineType == MACHINE_STE )
&& ( LineBorderMask & BORDERMASK_LEFT_OFF_MED )
&& ( nCyclesPerLine == 508 )
)
{
STF_PixelScroll = 3;
}
/* If left border is opened, we need to compensate one missing word in low res (1 plan) */
/* If overscan is in med res, the offset is variable */
if ( LineBorderMask & BORDERMASK_OVERSCAN_MED_RES )
VideoOffset = - ( ( LineBorderMask >> 20 ) & 0x0f ); /* No Cooper=0 PYM=-2 in med res overscan */
else if ( LineBorderMask & BORDERMASK_LEFT_OFF )
{
#ifdef SCROLL2_4PX
int ShiftPixels = 0;
if ( STF_PixelScroll == 13 ) { VideoOffset = 2; ShiftPixels = 8; }
else if ( STF_PixelScroll == 9 ) { VideoOffset = 0; ShiftPixels = 8; }
else if ( STF_PixelScroll == 5 ) { VideoOffset = -2; ShiftPixels = 8; }
else if ( STF_PixelScroll == 1 ) { VideoOffset = -4; ShiftPixels = 8; }
else VideoOffset = -2; /* Normal low res left border removal without 4 pixels scrolling */
STF_PixelScroll -= ShiftPixels;
#else
VideoOffset = -2; /* always 2 bytes in low res overscan */
#endif
}
else if ( LineBorderMask & BORDERMASK_LEFT_OFF_2_STE )
VideoOffset = -4; /* 4 first bytes of the line are not shown */
/* Handle 4 pixels hardware scrolling ('ST Cnx' demo in 'Punish Your Machine') */
/* Depending on the number of pixels, we need to compensate for some skipped words */
else if ( LineBorderMask & BORDERMASK_LEFT_OFF_MED )
{
if ( STF_PixelScroll == 13 ) VideoOffset = 2;
else if ( STF_PixelScroll == 9 ) VideoOffset = 0;
else if ( STF_PixelScroll == 5 ) VideoOffset = -2;
else if ( STF_PixelScroll == 1 ) VideoOffset = -4;
else VideoOffset = 0; /* never used ? */
STF_PixelScroll -= 8; /* removing left border in mid res also shifts display to the left */
// fprintf(stderr , "scr off %d %d\n" , STF_PixelScroll , VideoOffset);
}
/* Is total blank line? I.e. top/bottom border? */
if ((nHBL < nStartHBL) || (nHBL >= nEndHBL + BlankLines)
|| (LineBorderMask & BORDERMASK_EMPTY_LINE))
{
/* Clear line to color '0' */
memset(pSTScreen, 0, SCREENBYTES_LINE);
}
else
{
/* Does have left border ? */
if ( LineBorderMask & ( BORDERMASK_LEFT_OFF | BORDERMASK_LEFT_OFF_MED ) ) /* bigger line by 26 bytes on the left */
{
pVideoRaster += BORDERBYTES_LEFT-SCREENBYTES_LEFT+VideoOffset;
memcpy(pSTScreen, pVideoRaster, SCREENBYTES_LEFT);
pVideoRaster += SCREENBYTES_LEFT;
}
else if ( LineBorderMask & BORDERMASK_LEFT_OFF_2_STE ) /* bigger line by 20 bytes on the left (STE specific) */
{ /* bytes 0-3 are not shown, only next 16 bytes (32 pixels, 4 bitplanes) */
if ( SCREENBYTES_LEFT > BORDERBYTES_LEFT_2_STE )
{
memset ( pSTScreen, 0, SCREENBYTES_LEFT-BORDERBYTES_LEFT_2_STE+4 ); /* clear unused pixels + bytes 0-3 */
memcpy ( pSTScreen+SCREENBYTES_LEFT-BORDERBYTES_LEFT_2_STE+4, pVideoRaster+VideoOffset+4, BORDERBYTES_LEFT_2_STE-4 );
}
else
memcpy ( pSTScreen, pVideoRaster+BORDERBYTES_LEFT_2_STE-SCREENBYTES_LEFT+VideoOffset, SCREENBYTES_LEFT );
pVideoRaster += BORDERBYTES_LEFT_2_STE+VideoOffset;
}
else if (LineBorderMask & BORDERMASK_LEFT_PLUS_2) /* bigger line by 2 bytes on the left */
{
if ( SCREENBYTES_LEFT > 2 )
{
memset(pSTScreen,0,SCREENBYTES_LEFT-2); /* clear unused pixels */
memcpy(pSTScreen+SCREENBYTES_LEFT-2, pVideoRaster, 2);
}
else
{ /* nothing to copy, left border is not large enough */
}
pVideoRaster += 2;
}
else if (bSteBorderFlag) /* bigger line by 8 bytes on the left (STE specific) */
{
if ( SCREENBYTES_LEFT > 4*2 )
{
memset(pSTScreen,0,SCREENBYTES_LEFT-4*2); /* clear unused pixels */
memcpy(pSTScreen+SCREENBYTES_LEFT-4*2, pVideoRaster, 4*2);
}
else
{ /* nothing to copy, left border is not large enough */
}
pVideoRaster += 4*2;
}
else
memset(pSTScreen,0,SCREENBYTES_LEFT); /* left border not removed, clear to color '0' */
/* Short line due to hires in the middle ? */
if (LineBorderMask & BORDERMASK_STOP_MIDDLE)
{
/* 106 bytes less in the line */
memcpy(pSTScreen+SCREENBYTES_LEFT, pVideoRaster, SCREENBYTES_MIDDLE-106);
memset(pSTScreen+SCREENBYTES_LEFT+SCREENBYTES_MIDDLE-106, 0, 106); /* clear unused pixels */
pVideoRaster += (SCREENBYTES_MIDDLE-106);
}
else
{
/* normal middle part (160 bytes) */
memcpy(pSTScreen+SCREENBYTES_LEFT, pVideoRaster, SCREENBYTES_MIDDLE);
pVideoRaster += SCREENBYTES_MIDDLE;
}
/* Does have right border ? */
if (LineBorderMask & BORDERMASK_RIGHT_OFF)
{
memcpy(pSTScreen+SCREENBYTES_LEFT+SCREENBYTES_MIDDLE, pVideoRaster, SCREENBYTES_RIGHT);
pVideoRasterEndLine = pVideoRaster + SCREENBYTES_RIGHT;
pVideoRaster += BORDERBYTES_RIGHT;
}
else if (LineBorderMask & BORDERMASK_RIGHT_MINUS_2)
{
/* Shortened line by 2 bytes */
memset(pSTScreen+SCREENBYTES_LEFT+SCREENBYTES_MIDDLE-2, 0, SCREENBYTES_RIGHT+2);
pVideoRaster -= 2;
pVideoRasterEndLine = pVideoRaster;
}
else
{
/* Simply clear right border to '0' */
memset(pSTScreen+SCREENBYTES_LEFT+SCREENBYTES_MIDDLE,0,SCREENBYTES_RIGHT);
pVideoRasterEndLine = pVideoRaster;
}
/* Shifter read bytes and borders can change, but display is blank, so finally clear the line with color 0 */
if (LineBorderMask & BORDERMASK_BLANK_LINE)
memset(pSTScreen, 0, SCREENBYTES_LINE);
/* Full right border removal up to the end of the line (cycle 512) */
if (LineBorderMask & BORDERMASK_RIGHT_OFF_FULL)
pVideoRaster += BORDERBYTES_RIGHT_FULL;
/* Correct the offset for pVideoRaster from BORDERMASK_LEFT_OFF above if needed */
pVideoRaster -= VideoOffset; /* VideoOffset is 0 or -2 */
/* STE specific */
if (!bSteBorderFlag && HWScrollCount) /* Handle STE fine scrolling (HWScrollCount is zero on ST) */
{
Uint16 *pScrollAdj; /* Pointer to actual position in line */
int nNegScrollCnt;
Uint16 *pScrollEndAddr; /* Pointer to end of the line */
nNegScrollCnt = 16 - HWScrollCount;
if (LineBorderMask & BORDERMASK_LEFT_OFF)
pScrollAdj = (Uint16 *)pSTScreen;
else if (LineBorderMask & BORDERMASK_LEFT_OFF_2_STE)
{
if ( SCREENBYTES_LEFT > BORDERBYTES_LEFT_2_STE )
pScrollAdj = (Uint16 *)(pSTScreen+8); /* don't scroll the 8 first bytes (keep color 0)*/
else
pScrollAdj = (Uint16 *)pSTScreen; /* we render less bytes on screen than a real ST, scroll the whole line */
}
else
pScrollAdj = (Uint16 *)(pSTScreen + SCREENBYTES_LEFT);
/* When shifting the line to the left, we will have 'HWScrollCount' missing pixels at */
/* the end of the line. We must complete these last 16 pixels with pixels from the */
/* video counter last accessed value in pVideoRasterEndLine. */
/* There're 2 passes : */
/* - shift whole line except the last 16 pixels */
/* - shift/complete the last 16 pixels */
/* Addr of the last byte to shift in the 1st pass (excluding the last 16 pixels of the line) */
if (LineBorderMask & BORDERMASK_RIGHT_OFF)
pScrollEndAddr = (Uint16 *)(pSTScreen + SCREENBYTES_LINE - 8);
else
pScrollEndAddr = (Uint16 *)(pSTScreen + SCREENBYTES_LEFT + SCREENBYTES_MIDDLE - 8);
if ( LineRes == 1 ) /* med res */
{
/* in med res, 16 pixels are 4 bytes, not 8 as in low res, so only the last 4 bytes need a special case */
pScrollEndAddr += 2; /* 2 Uint16 = 4 bytes = 16 pixels */
/* Shift the whole line to the left by the given scroll count (except the last 16 pixels) */
while (pScrollAdj < pScrollEndAddr)
{
do_put_mem_word(pScrollAdj, (do_get_mem_word(pScrollAdj) << HWScrollCount)
| (do_get_mem_word(pScrollAdj+2) >> nNegScrollCnt));
++pScrollAdj;
}
/* Handle the last 16 pixels of the line (complete the line with pixels from pVideoRasterEndLine) */
for ( i=0 ; i<2 ; i++ )
do_put_mem_word(pScrollAdj+i, (do_get_mem_word(pScrollAdj+i) << HWScrollCount)
| (do_get_mem_word(pVideoRasterEndLine+i*2) >> nNegScrollCnt));
/* Depending on whether $ff8264 or $ff8265 was used to scroll, */
/* we prefetched 16 pixel (4 bytes) */
if ( HWScrollPrefetch == 1 ) /* $ff8265 prefetches 16 pixels */
pVideoRaster += 2 * 2; /* 2 bitplans */
/* If scrolling with $ff8264, there's no prefetch, which means display starts */
/* 16 pixels later but still stops at the normal point (eg we display */
/* (320-16) pixels in low res). We shift the whole line 4 bytes to the right to */
/* get the correct result (using memmove, as src/dest are overlapping). */
else
{
if (LineBorderMask & BORDERMASK_RIGHT_OFF)
memmove ( pSTScreen+4 , pSTScreen , SCREENBYTES_LINE - 4 );
else
memmove ( pSTScreen+4 , pSTScreen , SCREENBYTES_LEFT + SCREENBYTES_MIDDLE - 4 );
memset ( pSTScreen , 0 , 4 ); /* first 16 pixels are color '0' */
}
}
else /* low res */
{
/* Shift the whole line to the left by the given scroll count (except the last 16 pixels) */
while (pScrollAdj < pScrollEndAddr)
{
do_put_mem_word(pScrollAdj, (do_get_mem_word(pScrollAdj) << HWScrollCount)
| (do_get_mem_word(pScrollAdj+4) >> nNegScrollCnt));
++pScrollAdj;
}
/* Handle the last 16 pixels of the line (complete the line with pixels from pVideoRasterEndLine) */
for ( i=0 ; i<4 ; i++ )
do_put_mem_word(pScrollAdj+i, (do_get_mem_word(pScrollAdj+i) << HWScrollCount)
| (do_get_mem_word(pVideoRasterEndLine+i*2) >> nNegScrollCnt));
/* Depending on whether $ff8264 or $ff8265 was used to scroll, */
/* we prefetched 16 pixel (8 bytes) */
if ( HWScrollPrefetch == 1 ) /* $ff8265 prefetches 16 pixels */
pVideoRaster += 4 * 2; /* 4 bitplans */
/* If scrolling with $ff8264, there's no prefetch, which means display starts */
/* 16 pixels later but still stops at the normal point (eg we display */
/* (320-16) pixels in low res). We shift the whole line 8 bytes to the right to */
/* get the correct result (using memmove, as src/dest are overlapping). */
else
{
if (LineBorderMask & BORDERMASK_RIGHT_OFF)
memmove ( pSTScreen+8 , pSTScreen , SCREENBYTES_LINE - 8 );
else
memmove ( pSTScreen+8 , pSTScreen , SCREENBYTES_LEFT + SCREENBYTES_MIDDLE - 8 );
memset ( pSTScreen , 0 , 8 ); /* first 16 pixels are color '0' */
}
/* On STE, when we have a 230 bytes overscan line and HWScrollCount > 0 */
/* we must read 6 bytes less than expected if scrolling is using prefetching ($ff8265) */
/* (this is not the case for the 224 bytes overscan which is a multiple of 8) */
if ( (LineBorderMask & BORDERMASK_LEFT_OFF) && (LineBorderMask & BORDERMASK_RIGHT_OFF) )
{
if ( HWScrollPrefetch == 1 )
pVideoRaster -= 6; /* we don't add 8 bytes (see above), but 2 */
else
pVideoRaster -= 0;
}
}
}
/* LineWidth is zero on ST. */
/* On STE, the Shifter skips the given amount of words. */
pVideoRaster += LineWidth*2;
/* On STE, handle modifications of the video counter address $ff8205/07/09 */
/* that occurred while the display was already ON */
if ( VideoCounterDelayedOffset != 0 )
{
pVideoRaster += ( VideoCounterDelayedOffset & ~1 );
// fprintf ( stderr , "adjust video counter offset=%d new video=%x\n" , VideoCounterDelayedOffset , pVideoRaster-STRam );
VideoCounterDelayedOffset = 0;
}
if ( pVideoRasterDelayed != NULL )
{
pVideoRaster = pVideoRasterDelayed;
// fprintf ( stderr , "adjust video counter const new video=%x\n" , pVideoRaster-STRam );
pVideoRasterDelayed = NULL;
}
/* On STE, if we wrote to the hwscroll register, we set the */
/* new value here, once the current line was processed */
if ( NewHWScrollCount >= 0 )
{
HWScrollCount = NewHWScrollCount;
HWScrollPrefetch = NewHWScrollPrefetch;
NewHWScrollCount = -1;
NewHWScrollPrefetch = -1;
}
/* On STE, if we trigger the left border + 16 pixels trick, we set the */
/* new value here, once the current line was processed */
if ( NewSteBorderFlag >= 0 )
{
if ( NewSteBorderFlag == 0 )
bSteBorderFlag = false;
else
bSteBorderFlag = true;
NewSteBorderFlag = -1;
}
/* On STE, if we wrote to the linewidth register, we set the */
/* new value here, once the current line was processed */
if ( NewLineWidth >= 0 )
{
LineWidth = NewLineWidth;
NewLineWidth = -1;
}
/* Handle 4 pixels hardware scrolling ('ST Cnx' demo in 'Punish Your Machine') */
/* as well as scrolling occurring when removing the left border. */
/* If >0, shift the line by STF_PixelScroll pixels to the right */
/* If <0, shift the line by -STF_PixelScroll pixels to the left */
/* This should be handled after the STE's hardware scrolling as it will scroll */
/* the whole displayed area (while the STE scrolls pixels inside the displayed area) */
if ( STF_PixelScroll > 0 )
{
Uint16 *pScreenLineEnd;
int count;
pScreenLineEnd = (Uint16 *) ( pSTScreen + SCREENBYTES_LINE - 2 );
if ( LineRes == 0 ) /* low res */
{
for ( count = 0 ; count < ( SCREENBYTES_LINE - 8 ) / 2 ; count++ , pScreenLineEnd-- )
do_put_mem_word ( pScreenLineEnd , ( ( do_get_mem_word ( pScreenLineEnd - 4 ) << 16 ) | ( do_get_mem_word ( pScreenLineEnd ) ) ) >> STF_PixelScroll );
/* Handle the first 16 pixels of the line (add color 0 pixels to the extreme left) */
do_put_mem_word ( pScreenLineEnd-0 , ( do_get_mem_word ( pScreenLineEnd-0 ) >> STF_PixelScroll ) );
do_put_mem_word ( pScreenLineEnd-1 , ( do_get_mem_word ( pScreenLineEnd-1 ) >> STF_PixelScroll ) );
do_put_mem_word ( pScreenLineEnd-2 , ( do_get_mem_word ( pScreenLineEnd-2 ) >> STF_PixelScroll ) );
do_put_mem_word ( pScreenLineEnd-3 , ( do_get_mem_word ( pScreenLineEnd-3 ) >> STF_PixelScroll ) );
}
else /* med res */
{
for ( count = 0 ; count < ( SCREENBYTES_LINE - 4 ) / 2 ; count++ , pScreenLineEnd-- )
do_put_mem_word ( pScreenLineEnd , ( ( do_get_mem_word ( pScreenLineEnd - 2 ) << 16 ) | ( do_get_mem_word ( pScreenLineEnd ) ) ) >> STF_PixelScroll );
/* Handle the first 16 pixels of the line (add color 0 pixels to the extreme left) */
do_put_mem_word ( pScreenLineEnd-0 , ( do_get_mem_word ( pScreenLineEnd-0 ) >> STF_PixelScroll ) );
do_put_mem_word ( pScreenLineEnd-1 , ( do_get_mem_word ( pScreenLineEnd-1 ) >> STF_PixelScroll ) );
}
}
else if ( STF_PixelScroll < 0 )
{
Uint16 *pScreenLineStart;
int count;
STF_PixelScroll = -STF_PixelScroll;
pScreenLineStart = (Uint16 *)pSTScreen;
if ( LineRes == 0 ) /* low res */
{
for ( count = 0 ; count < ( SCREENBYTES_LINE - 8 ) / 2 ; count++ , pScreenLineStart++ )
do_put_mem_word ( pScreenLineStart , ( ( do_get_mem_word ( pScreenLineStart ) << STF_PixelScroll ) | ( do_get_mem_word ( pScreenLineStart + 4 ) >> (16-STF_PixelScroll) ) ) );
/* Handle the last 16 pixels of the line (add color 0 pixels to the extreme right) */
do_put_mem_word ( pScreenLineStart+0 , ( do_get_mem_word ( pScreenLineStart+0 ) << STF_PixelScroll ) );
do_put_mem_word ( pScreenLineStart+1 , ( do_get_mem_word ( pScreenLineStart+1 ) << STF_PixelScroll ) );
do_put_mem_word ( pScreenLineStart+2 , ( do_get_mem_word ( pScreenLineStart+2 ) << STF_PixelScroll ) );
do_put_mem_word ( pScreenLineStart+3 , ( do_get_mem_word ( pScreenLineStart+3 ) << STF_PixelScroll ) );
}
else /* med res */
{
for ( count = 0 ; count < ( SCREENBYTES_LINE - 4 ) / 2 ; count++ , pScreenLineStart++ )
do_put_mem_word ( pScreenLineStart , ( ( do_get_mem_word ( pScreenLineStart ) << STF_PixelScroll ) | ( do_get_mem_word ( pScreenLineStart + 2 ) >> (16-STF_PixelScroll) ) ) );
/* Handle the last 16 pixels of the line (add color 0 pixels to the extreme right) */
do_put_mem_word ( pScreenLineStart+0 , ( do_get_mem_word ( pScreenLineStart+0 ) << STF_PixelScroll ) );
do_put_mem_word ( pScreenLineStart+1 , ( do_get_mem_word ( pScreenLineStart+1 ) << STF_PixelScroll ) );
}
}
}
/* Each screen line copied to buffer is always same length */
pSTScreen += SCREENBYTES_LINE;
/* We must keep the new video address in a 24 bit space */
/* (in case it pointed to IO space and is now >= 0x1000000) */
pVideoRaster = ( ( pVideoRaster - STRam ) & 0xffffff ) + STRam;
}
/*-----------------------------------------------------------------------*/
/**
* Copy extended GEM resolution screen
*/
static void Video_CopyVDIScreen(void)
{
/* Copy whole screen, don't care about being exact as for GEM only */
memcpy(pSTScreen, pVideoRaster, ((VDIWidth*VDIPlanes)/8)*VDIHeight);
}
/*-----------------------------------------------------------------------*/
/**
* Clear raster line table to store changes in palette/resolution on a line
* basic. Called once on VBL interrupt.
*/
void Video_SetScreenRasters(void)
{
pHBLPaletteMasks = HBLPaletteMasks;
pHBLPalettes = HBLPalettes;
memset(pHBLPaletteMasks, 0, sizeof(Uint32)*NUM_VISIBLE_LINES); /* Clear array */
}
/*-----------------------------------------------------------------------*/
/**
* Set pointers to HBLPalette tables to store correct colours/resolutions
*/
static void Video_SetHBLPaletteMaskPointers(void)
{
int FrameCycles, HblCounterVideo, LineCycles;
int Line;
/* FIXME [NP] We should use Cycles_GetCounterOnWriteAccess, but it wouldn't */
/* work when using multiple accesses instructions like move.l or movem */
/* To correct this, we assume a delay of 8 cycles (should give a good approximation */
/* of a move.w or movem.l for example) */
// FrameCycles = Cycles_GetCounterOnWriteAccess(CYCLES_COUNTER_VIDEO);
FrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO) + 8;
/* Find 'line' into palette - screen starts 63 lines down, less 29 for top overscan */
Video_ConvertPosition ( FrameCycles , &HblCounterVideo , &LineCycles );
Line = HblCounterVideo - nFirstVisibleHbl;
/* FIXME [NP] if the color change occurs after the last visible pixel of a line */
/* we consider the palette should be modified on the next line. This is quite */
/* a hack, we should handle all color changes through spec512.c to have cycle */
/* accuracy all the time. */
if ( LineCycles >= LINE_END_CYCLE_NO_RIGHT )
Line++;
if (Line < 0) /* Limit to top/bottom of possible visible screen */
Line = 0;
if (Line >= NUM_VISIBLE_LINES)
Line = NUM_VISIBLE_LINES-1;
/* Store pointers */
pHBLPaletteMasks = &HBLPaletteMasks[Line]; /* Next mask entry */
pHBLPalettes = &HBLPalettes[16*Line]; /* Next colour raster list x16 colours */
}
/*-----------------------------------------------------------------------*/
/**
* Set video shifter timing variables according to screen refresh rate.
* Note: The following equation must be satisfied for correct timings:
*
* nCyclesPerLine * nScanlinesPerFrame * nScreenRefreshRate = 8 MHz
*/
static void Video_ResetShifterTimings(void)
{
Uint8 nSyncByte;
nSyncByte = IoMem_ReadByte(0xff820a);
if ((IoMem_ReadByte(0xff8260) & 3) == 2)
{
/* 71 Hz, monochrome */
nScreenRefreshRate = 71;
nScanlinesPerFrame = SCANLINES_PER_FRAME_71HZ;
nCyclesPerLine = CYCLES_PER_LINE_71HZ;
nStartHBL = VIDEO_START_HBL_71HZ;
nFirstVisibleHbl = FIRST_VISIBLE_HBL_71HZ;
nLastVisibleHbl = FIRST_VISIBLE_HBL_71HZ + VIDEO_HEIGHT_HBL_MONO;
}
else if (nSyncByte & 2) /* Check if running in 50 Hz or in 60 Hz */
{
/* 50 Hz */
nScreenRefreshRate = 50;
nScanlinesPerFrame = SCANLINES_PER_FRAME_50HZ;
nCyclesPerLine = CYCLES_PER_LINE_50HZ;
nStartHBL = VIDEO_START_HBL_50HZ;
nFirstVisibleHbl = FIRST_VISIBLE_HBL_50HZ;
nLastVisibleHbl = FIRST_VISIBLE_HBL_50HZ + NUM_VISIBLE_LINES;
}
else
{
/* 60 Hz */
nScreenRefreshRate = 60;
nScanlinesPerFrame = SCANLINES_PER_FRAME_60HZ;
nCyclesPerLine = CYCLES_PER_LINE_60HZ;
nStartHBL = VIDEO_START_HBL_60HZ;
nFirstVisibleHbl = FIRST_VISIBLE_HBL_60HZ;
nLastVisibleHbl = FIRST_VISIBLE_HBL_60HZ + NUM_VISIBLE_LINES;
}
if (bUseHighRes)
{
nEndHBL = nStartHBL + VIDEO_HEIGHT_HBL_MONO;
}
else
{
nEndHBL = nStartHBL + VIDEO_HEIGHT_HBL_COLOR;
}
/* Reset freq changes position for the next VBL to come */
LastCycleScroll8264 = -1;
LastCycleScroll8265 = -1;
TimerBEventCountCycleStart = -1; /* reset timer B activation cycle for this VBL */
BlankLines = 0;
}
/*-----------------------------------------------------------------------*/
/**
* Clear the array indicating the state of each video line.
*/
static void Video_InitShifterLines ( void )
{
int i;
for ( i=0 ; i<MAX_SCANLINES_PER_FRAME ; i++ )
{
ShifterFrame.ShifterLines[i].BorderMask = 0;
ShifterFrame.ShifterLines[i].DisplayPixelShift = 0;
ShifterFrame.ShifterLines[i].DisplayStartCycle = -1;
}
ShifterFrame.ShifterLines[0].StartCycle = 0; /* 1st HBL starts at cycle 0 */
}
/*-----------------------------------------------------------------------*/
/**
* Called on VBL, set registers ready for frame
*/
static void Video_ClearOnVBL(void)
{
/* New screen, so first HBL */
nHBL = 0;
OverscanMode = OVERSCANMODE_NONE;
Video_ResetShifterTimings();
/* Get screen address pointer, aligned to 256 bytes on ST (ie ignore lowest byte) */
VideoBase = (Uint32)IoMem_ReadByte(0xff8201)<<16 | (Uint32)IoMem_ReadByte(0xff8203)<<8;
if (ConfigureParams.System.nMachineType != MACHINE_ST)
{
/* on STe 2 aligned, on TT 8 aligned. We do STe. */
VideoBase |= IoMem_ReadByte(0xff820d) & ~1;
}
pVideoRaster = &STRam[VideoBase];
pSTScreen = pFrameBuffer->pSTScreen;
Video_SetScreenRasters();
Video_InitShifterLines();
Spec512_StartVBL();
Video_StartHBL(); /* Init ShifterFrame.ShifterLines[0] */
}
/*-----------------------------------------------------------------------*/
/**
* Get width, height and bpp according to TT-Resolution
*/
void Video_GetTTRes(int *width, int *height, int *bpp)
{
switch (TTRes)
{
case ST_LOW_RES: *width = 320; *height = 200; *bpp = 4; break;
case ST_MEDIUM_RES:*width = 640; *height = 200; *bpp = 2; break;
case ST_HIGH_RES: *width = 640; *height = 400; *bpp = 1; break;
case TT_LOW_RES: *width = 320; *height = 480; *bpp = 8; break;
case TT_MEDIUM_RES:*width = 640; *height = 480; *bpp = 4; break;
case TT_HIGH_RES: *width = 1280; *height = 960; *bpp = 1; break;
default:
fprintf(stderr, "TT res error!\n");
*width = 320; *height = 200; *bpp = 4;
break;
}
}
/*-----------------------------------------------------------------------*/
/**
* Convert TT palette to SDL palette
*/
static void Video_UpdateTTPalette(int bpp)
{
Uint32 ttpalette, src, dst;
Uint8 r,g,b, lowbyte, highbyte;
Uint16 stcolor, ttcolor;
int i, offset, colors;
ttpalette = 0xff8400;
if (!bTTColorsSTSync)
{
/* sync TT ST-palette to TT-palette */
src = 0xff8240; /* ST-palette */
offset = (IoMem_ReadWord(0xff8262) & 0x0f);
/*fprintf(stderr, "offset: %d\n", offset);*/
dst = ttpalette + offset * 16*SIZE_WORD;
for (i = 0; i < 16; i++)
{
stcolor = IoMem_ReadWord(src);
ttcolor = ((stcolor&0x777) << 1) | ((stcolor&0x888) >> 3);
IoMem_WriteWord(dst, ttcolor);
src += SIZE_WORD;
dst += SIZE_WORD;
}
bTTColorsSTSync = true;
}
colors = 1 << bpp;
if ((bpp == 1) && (TTRes == TT_HIGH_RES))
{
/* Monochrome mode... palette is hardwired (?) */
HostScreen_setPaletteColor(0, 255, 255, 255);
HostScreen_setPaletteColor(1, 0, 0, 0);
}
else if (bpp == 1)
{
/* Monochrome mode... palette is taken from first and last TT color */
ttpalette = 0xff8400;
lowbyte = IoMem_ReadByte(ttpalette++);
highbyte = IoMem_ReadByte(ttpalette++);
r = (lowbyte & 0x0f) << 4;
g = (highbyte & 0xf0);
b = (highbyte & 0x0f) << 4;
//printf("%d: (%d,%d,%d)\n", 0,r,g,b);
if(bTTHypermono)
{
r = g = b = highbyte;
}
HostScreen_setPaletteColor(0, r,g,b);
ttpalette = 0xff85fe;
lowbyte = IoMem_ReadByte(ttpalette++);
highbyte = IoMem_ReadByte(ttpalette++);
r = (lowbyte & 0x0f) << 4;
g = (highbyte & 0xf0);
b = (highbyte & 0x0f) << 4;
if(bTTHypermono)
{
r = g = b = highbyte;
}
//printf("%d: (%d,%d,%d)\n", 1,r,g,b);
HostScreen_setPaletteColor(1, r,g,b);
}
else
{
for (i = 0; i < colors; i++)
{
lowbyte = IoMem_ReadByte(ttpalette++);
highbyte = IoMem_ReadByte(ttpalette++);
r = (lowbyte & 0x0f) << 4;
g = (highbyte & 0xf0);
b = (highbyte & 0x0f) << 4;
//printf("%d: (%d,%d,%d)\n", i,r,g,b);
if(bTTHypermono)
{
r = g = b = highbyte;
}
HostScreen_setPaletteColor(i, r,g,b);
}
}
HostScreen_updatePalette(colors);
bTTColorsSync = true;
}
/*-----------------------------------------------------------------------*/
/**
* Update TT palette and blit TT screen using VIDEL code.
* @return true if the screen contents changed
*/
bool Video_RenderTTScreen(void)
{
static int nPrevTTRes = -1;
int width, height, bpp;
Video_GetTTRes(&width, &height, &bpp);
if (TTRes != nPrevTTRes)
{
HostScreen_setWindowSize(width, height, 8, false);
nPrevTTRes = TTRes;
if (bpp == 1) /* Assert that mono palette will be used in mono mode */
bTTColorsSync = false;
}
/* colors need synching? */
if (!(bTTColorsSync && bTTColorsSTSync))
{
Video_UpdateTTPalette(bpp);
}
else if (TTSpecialVideoMode != nPrevTTSpecialVideoMode)
{
Video_UpdateTTPalette(bpp);
nPrevTTSpecialVideoMode = TTSpecialVideoMode;
}
/* Yes, we are abusing the Videl routines for rendering the TT modes! */
if (!HostScreen_renderBegin())
return false;
if (nScreenZoomX * nScreenZoomY != 1)
VIDEL_ConvertScreenZoom(width, height, bpp, width * bpp / 16);
else
VIDEL_ConvertScreenNoZoom(width, height, bpp, width * bpp / 16);
HostScreen_update1(HostScreen_renderEnd(), false);
return true;
}
/*-----------------------------------------------------------------------*/
/**
* Draw screen (either with ST/STE shifter drawing functions or with
* Videl drawing functions)
*/
static void Video_DrawScreen(void)
{
/* Skip frame if need to */
if (nVBLs % (nFrameSkips+1))
return;
/* Use extended VDI resolution?
* If so, just copy whole screen on VBL rather than per HBL */
if (bUseVDIRes)
Video_CopyVDIScreen();
/* Now draw the screen! */
if (ConfigureParams.System.nMachineType == MACHINE_FALCON && !bUseVDIRes)
{
VIDEL_renderScreen();
}
else if (ConfigureParams.System.nMachineType == MACHINE_TT && !bUseVDIRes)
{
Video_RenderTTScreen();
}
else
{
/* Before drawing the screen, ensure all unused lines are cleared to color 0 */
/* (this can happen in 60 Hz when hatari is displaying the screen's border) */
/* pSTScreen was set during Video_CopyScreenLineColor */
if (!bUseVDIRes && nHBL < nLastVisibleHbl)
memset(pSTScreen, 0, SCREENBYTES_LINE * ( nLastVisibleHbl - nHBL ) );
Screen_Draw();
}
}
/*-----------------------------------------------------------------------*/
/**
* Start HBL, Timer B and VBL interrupts.
*/
/**
* Start HBL or Timer B interrupt at position Pos. If position Pos was
* already reached, then the interrupt is set on the next line.
*/
static void Video_AddInterrupt ( int Pos , interrupt_id Handler )
{
int FrameCycles , HblCounterVideo , LineCycles;
if ( nHBL >= nScanlinesPerFrame )
return; /* don't set a new hbl/timer B if we're on the last line, as the vbl will happen first */
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
//fprintf ( stderr , "add int pos=%d handler=%d LineCycles=%d nCyclesPerLine=%d \n" , Pos , Handler , LineCycles , nCyclesPerLine );
if ( LineCycles < Pos ) /* changed before reaching the new Pos on the current line */
CycInt_AddRelativeInterrupt ( Pos - LineCycles , INT_CPU_CYCLE, Handler );
else /* Pos will be applied on next line */
CycInt_AddRelativeInterrupt ( Pos - LineCycles + nCyclesPerLine , INT_CPU_CYCLE, Handler );
}
static void Video_AddInterruptHBL ( int Pos )
{
//fprintf ( stderr , "add hbl pos=%d\n" , Pos );
if ( !bUseVDIRes )
Video_AddInterrupt ( Pos , INTERRUPT_VIDEO_HBL );
}
void Video_AddInterruptTimerB ( int Pos )
{
//fprintf ( stderr , "add timerb pos=%d\n" , Pos );
if ( !bUseVDIRes )
Video_AddInterrupt ( Pos , INTERRUPT_VIDEO_ENDLINE );
}
/**
* Add some video interrupts to handle the first HBL and the first Timer B
* in a new VBL. Also add an interrupt to trigger the next VBL.
* This function is called from the VBL, so we use PendingCycleOver to take into account
* the possible delay occurring when the VBL was executed.
* In monochrome mode (71 Hz) a line is 224 cycles, which means if VBL is delayed
* by a DIVS, FrameCycles can already be > 224 and we need to add an immediate
* interrupt for hbl/timer in the next 4/8 cycles (else crash might happen as
* line 0 processing would be skipped).
*/
void Video_StartInterrupts ( int PendingCyclesOver )
{
int FrameCycles , HblCounterVideo , LineCycles;
int Pos;
/* HBL/Timer B are not emulated in VDI mode */
if (!bUseVDIRes)
{
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
/* Set Timer B interrupt for line 0 */
Pos = Video_TimerB_GetPos ( 0 );
if ( Pos > FrameCycles ) /* check Pos for line 0 was not already reached */
Video_AddInterruptTimerB ( Pos );
else /* the VBL was delayed by more than 1 HBL, add an immediate timer B */
{
LOG_TRACE(TRACE_VIDEO_VBL , "VBL %d delayed too much video_cyc=%d >= pos=%d for first timer B, add immediate timer B\n" ,
nVBLs , FrameCycles , Pos );
CycInt_AddRelativeInterrupt ( 4 , INT_CPU_CYCLE, INTERRUPT_VIDEO_ENDLINE );
}
/* Set HBL interrupt for line 0 */
Pos = Video_HBL_GetPos();
if ( Pos > FrameCycles ) /* check Pos for line 0 was not already reached */
Video_AddInterruptHBL ( Pos );
else /* the VBL was delayed by more than 1 HBL, add an immediate HBL */
{
LOG_TRACE(TRACE_VIDEO_VBL , "VBL %d delayed too much video_cyc=%d >= pos=%d for first HBL, add immediate HBL\n" ,
nVBLs , FrameCycles , Pos );
CycInt_AddRelativeInterrupt ( 8 , INT_CPU_CYCLE, INTERRUPT_VIDEO_HBL ); /* use 8 instead of 4 to happen after immediate timer b */
}
}
/* TODO replace CYCLES_PER_FRAME */
CyclesPerVBL = CYCLES_PER_FRAME;
/* Note: Refresh rate less than 50 Hz does not make sense! */
assert(CyclesPerVBL <= CPU_FREQ/49);
/* Add new VBL interrupt: */
CycInt_AddRelativeInterrupt(CyclesPerVBL - PendingCyclesOver, INT_CPU_CYCLE, INTERRUPT_VIDEO_VBL);
}
/*-----------------------------------------------------------------------*/
/**
* VBL interrupt : set new interrupts, draw screen, generate sound,
* reset counters, ...
*/
void Video_InterruptHandler_VBL ( void )
{
int PendingCyclesOver;
/* Store cycles we went over for this frame(this is our initial count) */
PendingCyclesOver = -INT_CONVERT_FROM_INTERNAL ( PendingInterruptCount , INT_CPU_CYCLE ); /* +ve */
/* Remove this interrupt from list and re-order */
CycInt_AcknowledgeInterrupt();
/* Increment the vbl jitter index */
VblJitterIndex++;
VblJitterIndex %= VBL_JITTER_ARRAY_SIZE;
/* Set frame cycles, used for Video Address */
Cycles_SetCounter(CYCLES_COUNTER_VIDEO, PendingCyclesOver + VblVideoCycleOffset);
/* Clear any key presses which are due to be de-bounced (held for one ST frame) */
Keymap_DebounceAllKeys();
Video_DrawScreen();
/* Check printer status */
Printer_CheckIdleStatus();
/* Update counter for number of screen refreshes per second */
nVBLs++;
/* Set video registers for frame */
Video_ClearOnVBL();
/* Videl Vertical counter reset (To be removed when Videl emulation is finished) */
if (ConfigureParams.System.nMachineType == MACHINE_FALCON) {
vfc_counter = 0;
}
/* Since we don't execute HBL functions in VDI mode, we've got to
* initialize the first HBL palette here when VDI mode is enabled. */
if (bUseVDIRes)
Video_StoreFirstLinePalette();
/* Start VBL, HBL and Timer B interrupts (this must be done after resetting
* video cycle counter setting default freq values in Video_ClearOnVBL) */
Video_StartInterrupts(PendingCyclesOver);
/* Act on shortcut keys */
ShortCut_ActKey();
/* Update the IKBD's internal clock */
IKBD_UpdateClockOnVBL ();
/* Record video frame is necessary */
if ( bRecordingAvi )
Avi_RecordVideoStream ();
/* Store off PSG registers for YM file, is enabled */
YMFormat_UpdateRecording();
/* Generate 1/50th second of sound sample data, to be played by sound thread */
Sound_Update_VBL();
LOG_TRACE(TRACE_VIDEO_VBL , "VBL %d video_cyc=%d pending_cyc=%d jitter=%d\n" ,
nVBLs , Cycles_GetCounter(CYCLES_COUNTER_VIDEO) , PendingCyclesOver , VblJitterArray[ VblJitterIndex ] );
/* Print traces if pending VBL bit changed just before IACK when VBL interrupt is allowed */
if ( ( CPU_IACK == true ) && ( regs.intmask < 4 ) )
{
if ( pendingInterrupts & ( 1 << 4 ) )
{
LOG_TRACE ( TRACE_VIDEO_VBL , "VBL %d, pending set again just before iack, skip one VBL interrupt video_cyc=%d pending_cyc=%d jitter=%d\n" ,
nVBLs , Cycles_GetCounter(CYCLES_COUNTER_VIDEO) , PendingCyclesOver , VblJitterArray[ VblJitterIndex ] );
}
else
{
LOG_TRACE ( TRACE_VIDEO_VBL , "VBL %d, new pending VBL set just before iack video_cyc=%d pending_cyc=%d jitter=%d\n" ,
nVBLs , Cycles_GetCounter(CYCLES_COUNTER_VIDEO) , PendingCyclesOver , VblJitterArray[ VblJitterIndex ] );
}
}
/* Set pending bit for VBL interrupt in the CPU IPL */
M68000_Exception(EXCEPTION_NR_VBLANK, M68000_EXC_SRC_AUTOVEC); /* Vertical blank interrupt, level 4 */
Main_WaitOnVbl();
}
/*-----------------------------------------------------------------------*/
/**
* Write to video address base high, med and low register (0xff8201/03/0d).
* On STE, when a program writes to high or med registers, base low register
* is reset to zero.
*/
void Video_ScreenBaseSTE_WriteByte(void)
{
if ( ( IoAccessCurrentAddress == 0xff8201 ) || ( IoAccessCurrentAddress == 0xff8203 ) )
IoMem[0xff820d] = 0; /* Reset screen base low register */
if (LOG_TRACE_LEVEL(TRACE_VIDEO_STE))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT ( "write ste video base=0x%x video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n" ,
(IoMem[0xff8201]<<16)+(IoMem[0xff8203]<<8)+IoMem[0xff820d] ,
FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles );
}
}
/*-----------------------------------------------------------------------*/
/**
* Read video address counter and update ff8205/07/09
*/
void Video_ScreenCounter_ReadByte(void)
{
Uint32 addr;
addr = Video_CalculateAddress(); /* get current video address */
IoMem[0xff8205] = ( addr >> 16 ) & 0xff;
IoMem[0xff8207] = ( addr >> 8 ) & 0xff;
IoMem[0xff8209] = addr & 0xff;
}
/*-----------------------------------------------------------------------*/
/**
* Write to video address counter (0xff8205, 0xff8207 and 0xff8209).
* Called on STE only and like with base address, you cannot set lowest bit.
*
* As Hatari processes/converts one complete video line at a time, we have 3 cases :
* - If display has not started yet for this line (left border), we can change pVideoRaster now.
* We must take into account that the MMU starts 16 cycles earlier when hscroll is used.
* - If display has stopped for this line (right border), we will change pVideoRaster
* in Video_CopyScreenLineColor using pVideoRasterDelayed once the line has been processed.
* - If the write is made while display is on, then we must compute an offset of what
* the new address should have been, to correctly emulate the video address at the
* end of the line while taking into account the fact that the video pointer is incrementing
* during the active part of the line (this is the most "tricky" case)
*
* To compute the new address, we must change only the byte that was modified and keep the two others ones.
*/
void Video_ScreenCounter_WriteByte(void)
{
Uint8 AddrByte;
Uint32 addr_cur;
Uint32 addr_new = 0;
int FrameCycles, HblCounterVideo, LineCycles;
int Delayed;
int MMUStartCycle;
Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles );
AddrByte = IoMem[ IoAccessCurrentAddress ];
/* Get current video address from the shifter */
addr_cur = Video_CalculateAddress();
/* Correct the address in case a modification of ff8205/07/09 was already delayed */
addr_new = addr_cur + VideoCounterDelayedOffset;
/* Correct the address in case video counter was already modified in the right border */
if ( pVideoRasterDelayed != NULL )
addr_new = pVideoRasterDelayed - STRam;
/* addr_new should now be the same as on a real STE */
/* Compute the new video address with one modified byte */
if ( IoAccessCurrentAddress == 0xff8205 )
addr_new = ( addr_new & 0x00ffff ) | ( ( AddrByte & 0x3f ) << 16 );
else if ( IoAccessCurrentAddress == 0xff8207 )
addr_new = ( addr_new & 0xff00ff ) | ( AddrByte << 8 );
else if ( IoAccessCurrentAddress == 0xff8209 )
addr_new = ( addr_new & 0xffff00 ) | ( AddrByte );
addr_new &= ~1; /* clear bit 0 */
MMUStartCycle = Video_GetMMUStartCycle ( ShifterFrame.ShifterLines[ nHBL ].DisplayStartCycle );
/* If display has not started, we can still modify pVideoRaster */
/* We must also check the write does not overlap the end of the line (to be sure Video_EndHBL is called first) */
if ( ( ( LineCycles <= MMUStartCycle ) && ( nHBL == HblCounterVideo ) )
|| ( nHBL < nStartHBL ) || ( nHBL >= nEndHBL + BlankLines ) )
{
pVideoRaster = &STRam[addr_new]; /* set new video address */
VideoCounterDelayedOffset = 0;
pVideoRasterDelayed = NULL;
Delayed = false;
}
/* Display is OFF (right border) but we can't change pVideoRaster now, we must process Video_CopyScreenLineColor first */
else if ( ( nHBL >= nStartHBL ) && ( nHBL < nEndHBL + BlankLines ) /* line should be active */
&& ( ( LineCycles > ShifterFrame.ShifterLines[ nHBL ].DisplayEndCycle ) /* we're in the right border */
|| ( HblCounterVideo == nHBL+1 ) ) ) /* or the write overlaps the next line and Video_EndHBL was not called yet */
{
VideoCounterDelayedOffset = 0;
pVideoRasterDelayed = &STRam[addr_new]; /* new value for pVideoRaster at the end of Video_CopyScreenLineColor */
Delayed = true;
}
/* Counter is modified while display is ON, store the bytes offset for Video_CopyScreenLineColor */
/* Even on a real STE, modifying video address in this case will cause artefacts */
else
{
VideoCounterDelayedOffset = addr_new - addr_cur;
pVideoRasterDelayed = NULL;
Delayed = true;
/* [FIXME] 'RGBeast' by Aggression : write to FF8209 on STE while display is on, */
/* in that case video counter is not correct */
if ( STMemory_ReadLong ( M68000_InstrPC ) == 0x03cafffb ) /* movep.l d1,$fffb(a2) */
VideoCounterDelayedOffset += 2;
}
LOG_TRACE(TRACE_VIDEO_STE , "write ste video %x val=0x%x video_old=%x video_new=%x offset=%x delayed=%s"
" video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n" ,
IoAccessCurrentAddress, AddrByte, addr_cur , addr_new , VideoCounterDelayedOffset , Delayed ? "yes" : "no" ,
FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles );
}
/*-----------------------------------------------------------------------*/
/**
* Read video sync register (0xff820a)
*/
void Video_Sync_ReadByte(void)
{
if ( (ConfigureParams.System.nMachineType == MACHINE_ST)
|| (ConfigureParams.System.nMachineType == MACHINE_STE)
|| (ConfigureParams.System.nMachineType == MACHINE_MEGA_STE) )
IoMem[0xff820a] |= 0xfc; /* set unused bits 2-7 to 1 */
}
/*-----------------------------------------------------------------------*/
/**
* Read video base address low byte (0xff820d). A plain ST can only store
* screen addresses rounded to 256 bytes (i.e. no lower byte).
*/
void Video_BaseLow_ReadByte(void)
{
if (ConfigureParams.System.nMachineType == MACHINE_ST)
IoMem[0xff820d] = 0; /* On ST this is always 0 */
/* Note that you should not do anything here for STe because
* VideoBase address is set in an interrupt and would be wrong
* here. It's fine like this.
*/
}
/*-----------------------------------------------------------------------*/
/**
* Read video line width register (0xff820f)
*/
void Video_LineWidth_ReadByte(void)
{
if (ConfigureParams.System.nMachineType == MACHINE_ST)
IoMem[0xff820f] = 0; /* On ST this is always 0 */
/* If we're not in STF mode, we use the value already stored in $ff820f */
}
/*-----------------------------------------------------------------------*/
/**
* Read video shifter mode register (0xff8260)
*/
void Video_ShifterMode_ReadByte(void)
{
if (bUseHighRes)
IoMem[0xff8260] = 2; /* If mono monitor, force to high resolution */
if (ConfigureParams.System.nMachineType == MACHINE_ST)
IoMem[0xff8260] |= 0xfc; /* On STF, set unused bits 2-7 to 1 */
else
IoMem[0xff8260] &= 0x03; /* Only use bits 0 and 1, unused bits 2-7 are set to 0 */
}
/*-----------------------------------------------------------------------*/
/**
* Read horizontal scroll register (0xff8265)
*/
void Video_HorScroll_Read(void)
{
IoMem[0xff8265] = HWScrollCount;
}
/*-----------------------------------------------------------------------*/
/**
* Write video line width register (0xff820f) - STE only.
* Content of LineWidth is added to the shifter counter when display is
* turned off (start of the right border, usually at cycle 376)
*/
void Video_LineWidth_WriteByte(void)
{
Uint8 NewWidth;
int FrameCycles, HblCounterVideo, LineCycles;
int Delayed;
Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles );
NewWidth = IoMem_ReadByte(0xff820f);
/* We must also check the write does not overlap the end of the line */
if ( ( ( nHBL == HblCounterVideo ) && ( LineCycles <= ShifterFrame.ShifterLines[ HblCounterVideo ].DisplayEndCycle ) )
|| ( nHBL < nStartHBL ) || ( nHBL >= nEndHBL + BlankLines ) )
{
LineWidth = NewWidth; /* display is on, we can still change */
NewLineWidth = -1; /* cancel 'pending' change */
Delayed = false;
}
else
{
NewLineWidth = NewWidth; /* display is off, can't change LineWidth once in right border */
Delayed = true;
}
LOG_TRACE(TRACE_VIDEO_STE , "write ste linewidth=0x%x delayed=%s video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n",
NewWidth, Delayed ? "yes" : "no" ,
FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles );
}
/*-----------------------------------------------------------------------*/
/**
* Write to video shifter palette registers (0xff8240-0xff825e)
*
* Note that there's a special "strange" case when writing only to the upper byte
* of the color reg (instead of writing 16 bits at once with .W/.L).
* In that case, the byte written to address x is automatically written
* to address x+1 too (but we shouldn't copy x in x+1 after masking x ; we apply the mask at the end)
* Similarly, when writing a byte to address x+1, it's also written to address x
* So : move.w #0,$ff8240 -> color 0 is now $000
* move.b #7,$ff8240 -> color 0 is now $707 !
* move.b #$55,$ff8241 -> color 0 is now $555 !
* move.b #$71,$ff8240 -> color 0 is now $171 (bytes are first copied, then masked)
*/
static void Video_ColorReg_WriteWord(void)
{
if (!bUseHighRes && !bUseVDIRes) /* Don't store if hi-res or VDI resolution */
{
int idx;
Uint16 col;
Uint32 addr;
addr = IoAccessCurrentAddress;
Video_SetHBLPaletteMaskPointers(); /* Set 'pHBLPalettes' etc.. according cycles into frame */
/* Handle special case when writing only to the upper byte of the color reg */
if ( ( nIoMemAccessSize == SIZE_BYTE ) && ( ( IoAccessCurrentAddress & 1 ) == 0 ) )
col = ( IoMem_ReadByte(addr) << 8 ) + IoMem_ReadByte(addr); /* copy upper byte into lower byte */
/* Same when writing only to the lower byte of the color reg */
else if ( ( nIoMemAccessSize == SIZE_BYTE ) && ( ( IoAccessCurrentAddress & 1 ) == 1 ) )
col = ( IoMem_ReadByte(addr) << 8 ) + IoMem_ReadByte(addr); /* copy lower byte into upper byte */
/* Usual case, writing a word or a long (2 words) */
else
col = IoMem_ReadWord(addr);
if (ConfigureParams.System.nMachineType == MACHINE_ST)
col &= 0x777; /* Mask off to ST 512 palette */
else
col &= 0xfff; /* Mask off to STe 4096 palette */
addr &= 0xfffffffe; /* Ensure addr is even to store the 16 bit color */
IoMem_WriteWord(addr, col); /* (some games write 0xFFFF and read back to see if STe) */
Spec512_StoreCyclePalette(col, addr); /* Store colour into CyclePalettes[] */
idx = (addr-0xff8240)/2; /* words */
pHBLPalettes[idx] = col; /* Set colour x */
*pHBLPaletteMasks |= 1 << idx; /* And mask */
if (LOG_TRACE_LEVEL(TRACE_VIDEO_COLOR))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT ( "write col addr=%x col=%x video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n" ,
IoAccessCurrentAddress, col,
FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles );
}
}
}
/*
* Read from video shifter palette registers (0xff8240-0xff825e)
*
* NOTE [NP] : On STF, only 3 bits are used for RGB (instead of 4 on STE) ;
* the content of bits 3, 7 and 11 is not defined and will be 0 or 1
* depending on the latest activity on the BUS (last word access by the CPU or
* the shifter). As precisely emulating these bits is quite complicated,
* we use random values for now.
* NOTE [NP] : When executing code from the IO addresses between 0xff8240-0xff825e
* the unused bits on STF are set to '0' (used in "The Union Demo" protection).
* So we use rand() only if PC is located in RAM.
*/
static void Video_ColorReg_ReadWord(void)
{
Uint16 col;
Uint32 addr;
addr = IoAccessCurrentAddress;
col = IoMem_ReadWord(addr);
if ( (ConfigureParams.System.nMachineType == MACHINE_ST)
&& ( M68000_GetPC() < 0x400000 ) ) /* PC in RAM < 4MB */
{
col = ( col & 0x777 ) | ( rand() & 0x888 );
IoMem_WriteWord ( addr , col );
}
if (LOG_TRACE_LEVEL(TRACE_VIDEO_COLOR))
{
int FrameCycles, HblCounterVideo, LineCycles;
Video_GetPosition_OnReadAccess ( &FrameCycles , &HblCounterVideo , &LineCycles );
LOG_TRACE_PRINT ( "read col addr=%x col=%x video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n" ,
IoAccessCurrentAddress, col,
FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles );
}
}
/*
* [NP] TODO : due to how .L accesses are handled in ioMem.c, we can't call directly
* Video_ColorReg_WriteWord from ioMemTabST.c / ioMemTabSTE.c, we must use an intermediate
* function, else .L accesses will not change 2 .W color regs, but only one.
* This should be changed in ioMem.c to do 2 separate .W accesses, as would do a real 68000
*/
void Video_Color0_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color1_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color2_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color3_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color4_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color5_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color6_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color7_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color8_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color9_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color10_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color11_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color12_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color13_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color14_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color15_WriteWord(void)
{
Video_ColorReg_WriteWord();
}
void Video_Color0_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color1_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color2_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color3_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color4_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color5_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color6_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color7_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color8_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color9_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color10_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color11_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color12_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color13_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color14_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
void Video_Color15_ReadWord(void)
{
Video_ColorReg_ReadWord();
}
/*-----------------------------------------------------------------------*/
/**
* Write video shifter mode register (0xff8260)
*/
void Video_ShifterMode_WriteByte(void)
{
Uint8 VideoShifterByte;
if (ConfigureParams.System.nMachineType == MACHINE_TT)
{
TTRes = IoMem_ReadByte(0xff8260) & 7;
/* Copy to TT shifter mode register: */
IoMem_WriteByte(0xff8262, TTRes);
bTTSampleHold = false;
bTTHypermono = false;
}
else if (!bUseVDIRes) /* ST and STE mode */
{
/* We only care for lower 2-bits */
VideoShifterByte = IoMem[0xff8260] & 3;
/* 3 is not a valid resolution, use high res instead */
if ( VideoShifterByte == 3 )
{
VideoShifterByte = 2;
IoMem_WriteByte(0xff8260,2);
}
Video_WriteToShifter(VideoShifterByte);
Video_SetHBLPaletteMaskPointers();
*pHBLPaletteMasks &= 0xff00ffff;
/* Store resolution after palette mask and set resolution write bit: */
*pHBLPaletteMasks |= (((Uint32)VideoShifterByte|0x04)<<16);
}
}
/*-----------------------------------------------------------------------*/
/**
* Handle horizontal scrolling to the left.
* On STE, there're 2 registers that can scroll the line :
* - $ff8264 : scroll without prefetch
* - $ff8265 : scroll with prefetch
* Both registers will scroll the line to the left by skipping the amount
* of pixels in $ff8264 or $ff8265 (from 0 to 15).
* As some pixels will be skipped, this means the shifter needs to read
* 16 other pixels in advance in some internal registers to have an uninterrupted flow of pixels.
*
* These 16 pixels can be prefetched before the display starts (on cycle 56 for example) when using
* $ff8265 to scroll the line. In that case 8 more bytes per line (low res) will be read. Most programs
* are using $ff8265 to scroll the line.
*
* When using $ff8264, the next 16 pixels will not be prefetched before the display
* starts, they will be read when the display normally starts (cycle 56). While
* reading these 16 pixels, the shifter won't be able to display anything, which will
* result in 16 pixels having the color 0. So, reading the 16 pixels will in fact delay
* the real start of the line, which will look as if it started 16 pixels later. As the
* shifter will stop the display at cycle 56+320 anyway, this means the last 16 pixels
* of each line won't be displayed and you get the equivalent of a shorter 304 pixels line.
* As a consequence, this register is rarely used to scroll the line.
*
* By writing a value > 0 in $ff8265 (to start prefetching) and immediately after a value of 0
* in $ff8264 (no scroll and no prefetch), it's possible to fill the internal registers used
* for the scrolling even if scrolling is set to 0. In that case, the shifter will start displaying
* each line 16 pixels earlier (as the data are already available in the internal registers).
* This allows to have 336 pixels per line (instead of 320) for all the remaining lines on the screen.
*
* Although some programs are using this sequence :
* move.w #1,$ffff8264 ; Word access!
* clr.b $ffff8264 ; Byte access!
* It is also possible to add 16 pixels by doing :
* move.b #X,$ff8265 ; with X > 0
* move.b #0,$ff8264
* Some games (Obsession, Skulls) and demos (Pacemaker by Paradox) use this
* feature to increase the resolution, so we have to emulate this bug, too!
*
* So considering a low res line of 320 pixels (160 bytes) :
* - if both $ff8264/65 are 0, no scrolling happens, the shifter reads 160 bytes and displays 320 pixels (same as STF)
* - if $ff8265 > 0, line is scrolled, the shifter reads 168 bytes and displays 320 pixels.
* - if $ff8264 > 0, line is scrolled, the shifter reads 160 bytes and displays 304 pixels,
* the display starts 16 pixels later.
* - if $ff8265 > 0 and then $ff8264 = 0, there's no scrolling, the shifter reads 168 bytes and displays 336 pixels,
* the display starts 16 pixels earlier.
*/
void Video_HorScroll_Write_8264(void)
{
Video_HorScroll_Write();
}
void Video_HorScroll_Write_8265(void)
{
Video_HorScroll_Write();
}
void Video_HorScroll_Write(void)
{
Uint32 RegAddr;
Uint8 ScrollCount;
Uint8 Prefetch;
int FrameCycles, HblCounterVideo, LineCycles;
bool Add16px = false;
static Uint8 LastVal8265 = 0;
int Delayed;
Video_GetPosition_OnWriteAccess ( &FrameCycles , &HblCounterVideo , &LineCycles );
RegAddr = IoAccessCurrentAddress; /* 0xff8264 or 0xff8265 */
ScrollCount = IoMem[ RegAddr ];
ScrollCount &= 0x0f;
if ( RegAddr == 0xff8264 )
{
Prefetch = 0; /* scroll without prefetch */
LastCycleScroll8264 = FrameCycles;
ShifterFrame.Scroll8264Pos.VBL = nVBLs;
ShifterFrame.Scroll8264Pos.FrameCycles = FrameCycles;
ShifterFrame.Scroll8264Pos.HBL = HblCounterVideo;
ShifterFrame.Scroll8264Pos.LineCycles = LineCycles;
if ( ( ScrollCount == 0 ) && ( LastVal8265 > 0 )
&& ( ShifterFrame.Scroll8265Pos.VBL > 0 ) /* a write to ff8265 has been made */
&& ( ShifterFrame.Scroll8265Pos.VBL == ShifterFrame.Scroll8264Pos.VBL ) /* during the same VBL */
&& ( ShifterFrame.Scroll8264Pos.FrameCycles - ShifterFrame.Scroll8265Pos.FrameCycles <= 40 ) )
{
LOG_TRACE(TRACE_VIDEO_BORDER_H , "detect ste left+16 pixels\n" );
Add16px = true;
}
}
else
{
Prefetch = 1; /* scroll with prefetch */
LastCycleScroll8265 = FrameCycles;
ShifterFrame.Scroll8265Pos.VBL = nVBLs;
ShifterFrame.Scroll8265Pos.FrameCycles = FrameCycles;
ShifterFrame.Scroll8265Pos.HBL = HblCounterVideo;
ShifterFrame.Scroll8265Pos.LineCycles = LineCycles;
LastVal8265 = ScrollCount;
Add16px = false;
}
/* If the write was made before display starts on the current line, then */
/* we can still change the value now. Else, the new values will be used */
/* for line n+1. */
/* We must also check the write does not overlap the end of the line */
if ( ( ( LineCycles <= LINE_START_CYCLE_50 ) && ( nHBL == HblCounterVideo ) )
|| ( nHBL < nStartHBL ) || ( nHBL >= nEndHBL + BlankLines ) )
{
HWScrollCount = ScrollCount; /* display has not started, we can still change */
HWScrollPrefetch = Prefetch;
bSteBorderFlag = Add16px;
NewHWScrollCount = -1; /* cancel 'pending' change */
Delayed = false;
}
else
{
NewHWScrollCount = ScrollCount; /* display has started, can't change HWScrollCount now */
NewHWScrollPrefetch = Prefetch;
if ( Add16px )
NewSteBorderFlag = 1;
else
NewSteBorderFlag = 0;
Delayed = true;
}
LOG_TRACE(TRACE_VIDEO_STE , "write ste %x hwscroll=%x delayed=%s video_cyc_w=%d line_cyc_w=%d @ nHBL=%d/video_hbl_w=%d pc=%x instr_cyc=%d\n" ,
RegAddr , ScrollCount, Delayed ? "yes" : "no" ,
FrameCycles, LineCycles, nHBL, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles );
}
/*-----------------------------------------------------------------------*/
/**
* Write to TT shifter mode register (0xff8262)
*/
void Video_TTShiftMode_WriteWord(void)
{
TTRes = IoMem_ReadByte(0xff8262) & 7;
TTSpecialVideoMode = IoMem_ReadByte(0xff8262) & 0x90;
/*fprintf(stderr, "Write to FF8262: %x, res=%i\n", IoMem_ReadWord(0xff8262), TTRes);*/
/* Is it an ST compatible resolution? */
if (TTRes <= 2)
{
IoMem_WriteByte(0xff8260, TTRes);
Video_ShifterMode_WriteByte();
IoMem_WriteByte(0xff8262, TTRes | TTSpecialVideoMode);
}
if(TTSpecialVideoMode & 0x80)
{
bTTSampleHold = true;
}
else
{
bTTSampleHold = false;
}
if(TTSpecialVideoMode & 0x10)
{
bTTHypermono = true;
}
else
{
bTTHypermono = false;
}
}
/*-----------------------------------------------------------------------*/
/**
* Write to TT color register (0xff8400)
*/
void Video_TTColorRegs_WriteWord(void)
{
bTTColorsSync = false;
}
/*-----------------------------------------------------------------------*/
/**
* Write to ST color register on TT (0xff8240)
*/
void Video_TTColorSTRegs_WriteWord(void)
{
bTTColorsSTSync = false;
}
/*-----------------------------------------------------------------------*/
/**
* display video related information (for debugger info command)
*/
void Video_Info(FILE *fp, Uint32 dummy)
{
const char *mode;
switch (OverscanMode) {
case OVERSCANMODE_NONE:
mode = "none";
break;
case OVERSCANMODE_TOP:
mode = "top";
break;
case OVERSCANMODE_BOTTOM:
mode = "bottom";
break;
case OVERSCANMODE_TOP|OVERSCANMODE_BOTTOM:
mode = "top+bottom";
break;
default:
mode = "unknown";
}
fprintf(fp, "Video base : 0x%x\n", VideoBase);
fprintf(fp, "VBL counter : %d\n", nVBLs);
fprintf(fp, "HBL line : %d\n", nHBL);
fprintf(fp, "V-overscan : %s\n", mode);
fprintf(fp, "Refresh rate : %d Hz\n", nScreenRefreshRate);
fprintf(fp, "Frame skips : %d\n", nFrameSkips);
/* TODO: any other information that would be useful to show? */
}