/* display.c: Routines for printing the Spectrum screen Copyright (c) 1999-2006 Philip Kendall, Thomas Harte, Witold Filipczyk and Fredrick Meunier $Id: display.c,v 1.59.2.1 2007/03/06 11:39:47 fredm Exp $ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Author contact information: E-mail: philip-fuse@shadowmagic.org.uk */ #include #include #include #include #include #include "display.h" #include "event.h" #include "fuse.h" #include "machine.h" #include "screenshot.h" #include "settings.h" #include "spectrum.h" #include "ui/ui.h" #include "ui/uidisplay.h" #include "scld.h" /* Set once we have initialised the UI */ int display_ui_initialised = 0; /* The current border colour */ libspectrum_byte display_lores_border; libspectrum_byte display_hires_border; libspectrum_byte display_last_border; /* Stores the pixel, attribute and SCLD screen mode information used to draw each 8x1 group of pixels (including border) last frame */ static libspectrum_dword display_last_screen[ DISPLAY_SCREEN_WIDTH_COLS * DISPLAY_SCREEN_HEIGHT ]; /* Offsets as to where the data and the attributes for each pixel line start */ libspectrum_word display_line_start[ DISPLAY_HEIGHT ]; libspectrum_word display_attr_start[ DISPLAY_HEIGHT ]; /* If you write to the byte at display_dirty_?table[n+0x4000], then the eight pixels starting at (8*xtable[n],ytable[n]) must be replotted */ static libspectrum_word display_dirty_ytable[ DISPLAY_WIDTH_COLS * DISPLAY_HEIGHT ]; static libspectrum_word display_dirty_xtable[ DISPLAY_WIDTH_COLS * DISPLAY_HEIGHT ]; /* If you write to the byte at display_dirty_?table2[n+0x5800], then the 64 pixels starting at (8*xtable2[n],ytable2[n]) must be replotted */ static libspectrum_word display_dirty_ytable2[ DISPLAY_WIDTH_COLS * DISPLAY_HEIGHT_ROWS ]; static libspectrum_word display_dirty_xtable2[ DISPLAY_WIDTH_COLS * DISPLAY_HEIGHT_ROWS ]; /* The number of frames mod 32 that have elapsed. 0<=d_f_c<16 => Flashing characters are normal 16<=d_f_c<32 => Flashing characters are reversed */ static int display_frame_count; static int display_flash_reversed; /* Which eight-pixel chunks on each line (including border) need to be redisplayed. Bit 0 corresponds to pixels 0-7, bit 39 to pixels 311-319. */ static libspectrum_qword display_is_dirty[ DISPLAY_SCREEN_HEIGHT ]; /* Which eight-pixel chunks on each line may need to be redisplayed. Bit 0 corresponds to pixels 0-7, bit 31 to pixels 248-255. */ static libspectrum_dword display_maybe_dirty[ DISPLAY_HEIGHT ]; /* This value signifies that the entire line must be redisplayed */ static libspectrum_qword display_all_dirty; /* Used to signify that we're redrawing the entire screen */ static int display_redraw_all; /* Value used to signify a border line has more than one colour on it. */ static const int display_border_mixed = 0xff; /* Used for grouping screen writes together */ struct rectangle { int x,y; int w,h; }; /* Those rectangles which were modified on the last line to be displayed */ struct rectangle *active_rectangle = NULL; size_t active_rectangle_count = 0, active_rectangle_allocated = 0; /* Those rectangles which weren't */ struct rectangle *inactive_rectangle = NULL; size_t inactive_rectangle_count = 0, inactive_rectangle_allocated = 0; /* The last point at which we updated the screen display */ int critical_region_x = 0, critical_region_y = 0; /* The border colour changes which have occured in this frame */ struct border_change_t { int x, y; int colour; }; static struct border_change_t border_change_end_sentinel = { DISPLAY_SCREEN_WIDTH_COLS, DISPLAY_SCREEN_HEIGHT - 1, 0 }; /* The current border colour */ int current_border[ DISPLAY_SCREEN_HEIGHT ][ DISPLAY_SCREEN_WIDTH_COLS ]; static void display_dirty8( libspectrum_word address ); static void display_dirty64( libspectrum_word address ); static void display_get_attr( int x, int y, libspectrum_byte *ink, libspectrum_byte *paper); static int add_rectangle( int y, int x, int w ); static int end_line( int y ); static void display_dirty_flashing(void); static int border_changes_last = 0; static struct border_change_t *border_changes = NULL; struct border_change_t * alloc_change(void) { static int border_changes_size = 0; if( border_changes_size == border_changes_last ) { border_changes_size += 10; border_changes = realloc( border_changes, border_changes_size* sizeof( struct border_change_t ) ); if( !border_changes ) { ui_error( UI_ERROR_ERROR, "out of memory at %s:%d", __FILE__, __LINE__ ); fuse_abort(); } } return border_changes + border_changes_last++; } static int add_border_sentinel( void ) { struct border_change_t *sentinel = alloc_change(); sentinel->x = sentinel->y = 0; sentinel->colour = scld_last_dec.name.hires ? display_hires_border : display_lores_border; return 0; } int display_init( int *argc, char ***argv ) { int i, j, k, x, y; int error; if(ui_init(argc, argv)) return 1; /* Set up the 'all pixels must be refreshed' marker */ display_all_dirty = 0; for( i = 0; i < DISPLAY_SCREEN_WIDTH_COLS; i++ ) display_all_dirty = ( display_all_dirty << 1 ) | 0x01; for(i=0;i<3;i++) for(j=0;j<8;j++) for(k=0;k<8;k++) display_line_start[ (64*i) + (8*j) + k ] = 32 * ( (64*i) + j + (k*8) ); for(y=0;y active_rectangle_allocated ) { size_t new_alloc; new_alloc = active_rectangle_allocated ? 2 * active_rectangle_allocated : 8; ptr = realloc( active_rectangle, new_alloc * sizeof( struct rectangle ) ); if( !ptr ) { ui_error( UI_ERROR_ERROR, "Out of memory at %s:%d", __FILE__, __LINE__ ); return 1; } active_rectangle_allocated = new_alloc; active_rectangle = ptr; } ptr = &active_rectangle[ active_rectangle_count - 1 ]; ptr->x = x; ptr->y = y; ptr->w = w; ptr->h = 1; return 0; } #ifndef MAX #define MAX(a,b) (((a) > (b)) ? (a) : (b)) #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif inline static int compare_and_merge_rectangles( struct rectangle *source ) { size_t z; /* Now look to see if there is an overlapping rectangle in the inactive list. These occur when frame skip is on and the same lines are covered more than once... */ for( z = 0; z < inactive_rectangle_count; z++ ) { if( inactive_rectangle[z].x == source->x && inactive_rectangle[z].w == source->w ) { if( inactive_rectangle[z].y == source->y && inactive_rectangle[z].h == source->h ) return 1; if( ( inactive_rectangle[z].y < source->y && ( source->y < ( inactive_rectangle[z].y + inactive_rectangle[z].h + 1 ) ) ) || ( source->y < inactive_rectangle[z].y && ( inactive_rectangle[z].y < ( source->y + source->h + 1 ) ) ) ) { /* rects overlap or touch in the y dimension, merge */ inactive_rectangle[z].h = MAX( inactive_rectangle[z].y + inactive_rectangle[z].h, source->y + source->h ) - MIN( inactive_rectangle[z].y, source->y ); inactive_rectangle[z].y = MIN( inactive_rectangle[z].y, source->y ); return 1; } } if( inactive_rectangle[z].y == source->y && inactive_rectangle[z].h == source->h ) { if( (inactive_rectangle[z].x < source->x && ( source->x < ( inactive_rectangle[z].x + inactive_rectangle[z].w + 1 ) ) ) || ( source->x < inactive_rectangle[z].x && ( inactive_rectangle[z].x < ( source->x + source->w + 1 ) ) ) ) { /* rects overlap or touch in the x dimension, merge */ inactive_rectangle[z].w = MAX( inactive_rectangle[z].x + inactive_rectangle[z].w, source->x + source->w ) - MIN( inactive_rectangle[z].x, source->x ); inactive_rectangle[z].x = MIN( inactive_rectangle[z].x, source->x ); return 1; } } /* Handle overlaps offset by both x and y? how much overlap and hence overdraw can be tolerated? */ } return 0; } /* Move all rectangles not updated on this line to the inactive list */ static int end_line( int y ) { size_t i; struct rectangle *ptr; for( i = 0; i < active_rectangle_count; i++ ) { /* Skip if this rectangle was updated this line */ if( active_rectangle[i].y + active_rectangle[i].h == y + 1 ) continue; if ( settings_current.frame_rate > 1 && compare_and_merge_rectangles( &active_rectangle[i] ) ) { /* Mark the active rectangle as done */ active_rectangle[i].h = 0; continue; } /* We couldn't find a rectangle to extend, so create a new one */ if( ++inactive_rectangle_count > inactive_rectangle_allocated ) { size_t new_alloc; new_alloc = inactive_rectangle_allocated ? 2 * inactive_rectangle_allocated : 8; ptr = realloc( inactive_rectangle, new_alloc * sizeof( struct rectangle ) ); if( !ptr ) { ui_error( UI_ERROR_ERROR, "Out of memory at %s:%d", __FILE__, __LINE__ ); return 1; } inactive_rectangle_allocated = new_alloc; inactive_rectangle = ptr; } inactive_rectangle[ inactive_rectangle_count - 1 ] = active_rectangle[i]; /* Mark the active rectangle as done */ active_rectangle[i].h = 0; } /* Compress the list of active rectangles */ for( i = 0, ptr = active_rectangle; i < active_rectangle_count; i++ ) { if( active_rectangle[i].h == 0 ) continue; *ptr = active_rectangle[i]; ptr++; } active_rectangle_count = ptr - active_rectangle; return 0; } /* Mark as 'dirty' the pixels which have been changed by a write to 'offset' within the RAM page containing the screen */ void display_dirty( libspectrum_word offset ) { switch ( scld_last_dec.mask.scrnmode ) { case STANDARD: /* standard Speccy screen */ case HIRESATTR: /* strange mode */ if( offset >= 0x1b00 ) break; if( offset < 0x1800 ) { /* 0x1800 = first attributes byte */ display_dirty8( offset ); } else { display_dirty64( offset ); } break; case ALTDFILE: /* second screen */ case HIRESATTRALTD: /* strange mode using second screen */ if( offset < 0x2000 || offset >= 0x3b00 ) break; if( offset < 0x3800 ) { /* 0x3800 = first attributes byte */ display_dirty8( offset - ALTDFILE_OFFSET ); } else { display_dirty64( offset - ALTDFILE_OFFSET ); } break; case EXTCOLOUR: /* extended colours */ case HIRES: /* hires mode */ if( offset >= 0x3800 ) break; if( offset >= 0x1800 && offset < 0x2000 ) break; if( offset >= 0x2000 ) offset -= ALTDFILE_OFFSET; display_dirty8( offset ); break; default: /* case EXTCOLALTD: extended colours, but attributes and data taken from second screen */ /* case HIRESDOUBLECOL: hires mode, but data taken only from second screen */ if( offset >= 0x2000 && offset < 0x3800 ) display_dirty8( offset - ALTDFILE_OFFSET ); break; } } /* Get the attribute byte or equivalent for the eight pixels starting at ( (8*x) , y ) */ static inline libspectrum_byte display_get_attr_byte( int x, int y ) { libspectrum_byte attr; if ( scld_last_dec.name.hires ) { attr = hires_get_attr(); } else { libspectrum_word offset; if( scld_last_dec.name.b1 ) { offset = display_line_start[y] + x + ALTDFILE_OFFSET; } else if( scld_last_dec.name.altdfile ) { offset = display_attr_start[y] + x + ALTDFILE_OFFSET; } else { offset = display_attr_start[y] + x; } attr = RAM[ memory_current_screen ][ offset ]; } return attr; } static void update_dirty_rects() { int start, y, error; for( y=0; y>= 1; x++; } start = x; /* Walk to the end of the dirty region */ do { display_is_dirty[y] >>= 1; x++; } while( display_is_dirty[y] & 0x01 ); error = add_rectangle( y, start, x - start ); if( error ) return; } /* compress the active rectangles list */ error = end_line( y ); if( error ) return; } /* Force all rectangles into the inactive list */ error = end_line( DISPLAY_SCREEN_HEIGHT ); if( error ) return; } static void write_if_dirty( int x, int y ) { int beam_x, beam_y; int index; libspectrum_word offset; libspectrum_byte *screen; libspectrum_dword data, data2; libspectrum_dword mode_data; libspectrum_dword last_chunk_detail; beam_x = x + DISPLAY_BORDER_WIDTH_COLS; beam_y = y + DISPLAY_BORDER_HEIGHT; offset = display_get_addr( x, y ); /* Read byte, atrr/byte, and screen mode */ screen = RAM[ memory_current_screen ]; data = screen[ offset ]; mode_data = scld_last_dec.byte; if( scld_last_dec.name.hires ) { switch( scld_last_dec.mask.scrnmode ) { case HIRESATTRALTD: offset = display_attr_start[ y ] + x + ALTDFILE_OFFSET; data2 = screen[ offset ]; break; case HIRES: data2 = screen[ offset + ALTDFILE_OFFSET ]; break; case HIRESDOUBLECOL: data2 = data; break; default: /* case HIRESATTR: */ offset = display_attr_start[ y ] + x; data2 = screen[ offset ]; break; } } else { data2 = display_get_attr_byte( x, y ); } last_chunk_detail = (display_flash_reversed << 24) | (mode_data << 16) | (data2 << 8) | data; /* And draw it if it is different to what was there last time */ index = beam_x + beam_y * DISPLAY_SCREEN_WIDTH_COLS; if( display_last_screen[ index ] != last_chunk_detail ) { libspectrum_byte ink, paper; display_get_attr( x, y, &ink, &paper ); if( scld_last_dec.name.hires ) { libspectrum_word hires_data = (data << 8) + data2; uidisplay_plot16( beam_x, beam_y, hires_data, ink, paper ); } else { uidisplay_plot8( beam_x, beam_y, data, ink, paper ); } /* Update last display record */ display_last_screen[ index ] = last_chunk_detail; /* And now mark it dirty */ display_is_dirty[ beam_y ] |= ( (libspectrum_qword)1 << beam_x ); } } /* Plot any dirty data from ( x, y ) to ( end, y ) of the critical region to the drawing region */ static void copy_critical_region_line( int y, int x, int end ) { int start; libspectrum_dword bit_mask, dirty; if( x < DISPLAY_WIDTH_COLS ) { /* Build a mask for the bits we're interested in */ bit_mask = display_all_dirty; bit_mask >>= x; bit_mask <<= x + ( 32 - end ); bit_mask >>= ( 32 - end ); /* Get the bits we're interested in */ dirty = ( display_maybe_dirty[y] & bit_mask ) >> x; /* And remove those bits from the dirty mask */ display_maybe_dirty[y] &= ~bit_mask; } else { dirty = 0; } while( dirty ) { /* Find the first dirty chunk on this row */ while( !( dirty & 0x01 ) ) { dirty >>= 1; x++; } start = x; /* Walk to the end of the dirty region, writing the bytes to the drawing area along the way */ do { write_if_dirty( x, y ); dirty >>= 1; x++; } while( dirty & 0x01 ); } } /* Copy any dirty data from the critical region to the drawing region */ static void copy_critical_region( int beam_x, int beam_y ) { if( critical_region_y == beam_y ) { copy_critical_region_line( critical_region_y, critical_region_x, beam_x ); } else { copy_critical_region_line( critical_region_y++, critical_region_x, DISPLAY_WIDTH_COLS ); for( ; critical_region_y < beam_y; critical_region_y++ ) copy_critical_region_line( critical_region_y, 0, DISPLAY_WIDTH_COLS ); copy_critical_region_line( critical_region_y, 0, beam_x ); } critical_region_x = beam_x; } inline static void get_beam_position( int *x, int *y ) { if( tstates < machine_current->line_times[ 0 ] ) { *x = *y = -1; return; } *y = ( tstates - machine_current->line_times[ 0 ] ) / machine_current->timings.tstates_per_line; if( *y >= 0 && *y <= DISPLAY_SCREEN_HEIGHT ) *x = ( tstates - machine_current->line_times[ *y ] ) / 4; else *x = 0; } void display_update_critical( int x, int y ) { int beam_x, beam_y; get_beam_position( &beam_x, &beam_y ); beam_x -= DISPLAY_BORDER_WIDTH_COLS; beam_y -= DISPLAY_BORDER_HEIGHT; if( beam_y < 0 ) { beam_x = beam_y = 0; } else if( beam_y >= DISPLAY_HEIGHT ) { beam_x = DISPLAY_WIDTH_COLS; beam_y = DISPLAY_HEIGHT - 1; } if( beam_x < 0 ) { beam_x = 0; } else if( beam_x > DISPLAY_WIDTH_COLS ) { beam_x = DISPLAY_WIDTH_COLS; } if( y < beam_y || ( y == beam_y && x < beam_x ) ) copy_critical_region( beam_x, beam_y ); } /* Mark the 8-pixel chunk at (x,y) as maybe dirty and update the critical region as appropriate */ inline static void display_dirty_chunk( int x, int y ) { /* If the write is between the start of the critical region and the current beam position, then we must copy the critical region now */ if( y > critical_region_y || ( y == critical_region_y && x >= critical_region_x ) ) { display_update_critical( x, y ); } display_maybe_dirty[y] |= ( (libspectrum_dword)1 << x ); } static void display_dirty8( libspectrum_word offset ) { int x, y; x=display_dirty_xtable[ offset ]; y=display_dirty_ytable[ offset ]; display_dirty_chunk( x, y ); } static void display_dirty64( libspectrum_word offset ) { int i, x, y; x=display_dirty_xtable2[ offset - 0x1800 ]; y=display_dirty_ytable2[ offset - 0x1800 ]; for( i = 0; i < 8; i++ ) display_dirty_chunk( x, y + i ); } /* Get the attributes for the eight pixels starting at ( (8*x) , y ) */ static void display_get_attr( int x, int y, libspectrum_byte *ink, libspectrum_byte *paper ) { display_parse_attr( display_get_attr_byte( x, y ), ink, paper ); } void display_parse_attr( libspectrum_byte attr, libspectrum_byte *ink, libspectrum_byte *paper ) { if( (attr & 0x80) && display_flash_reversed ) { *ink = (attr & ( 0x0f << 3 ) ) >> 3; *paper= (attr & 0x07) + ( (attr & 0x40) >> 3 ); } else { *ink= (attr & 0x07) + ( (attr & 0x40) >> 3 ); *paper= (attr & ( 0x0f << 3 ) ) >> 3; } } static void push_border_change( int colour ) { int beam_x, beam_y; struct border_change_t *change; get_beam_position( &beam_x, &beam_y ); if( beam_y >= DISPLAY_SCREEN_HEIGHT ) return; if( beam_x < 0 ) beam_x = 0; if( beam_x > DISPLAY_SCREEN_WIDTH_COLS ) beam_x = DISPLAY_SCREEN_WIDTH_COLS; if( beam_y < 0 ) beam_y = 0; change = alloc_change(); change->x = beam_x; change->y = beam_y; change->colour = colour; } /* Change border colour if the colour in use changes */ static void check_border_change() { if( scld_last_dec.name.hires && display_hires_border != display_last_border ) { push_border_change( display_hires_border ); display_last_border = display_hires_border; } else if( !scld_last_dec.name.hires && display_lores_border != display_last_border ) { push_border_change( display_lores_border ); display_last_border = display_lores_border; } } void display_set_lores_border( int colour ) { if( display_lores_border != colour ) { display_lores_border = colour; } check_border_change(); } void display_set_hires_border( int colour ) { if( display_hires_border != colour ) { display_hires_border = colour; } check_border_change(); } static void set_border( int y, int start, int end, int colour ) { libspectrum_dword chunk_detail = 0x000000ff | colour << 8; int index = start + y * DISPLAY_SCREEN_WIDTH_COLS; for( ; start < end; start++ ) { /* Draw it if it is different to what was there last time - we know that data and mode will have been the same */ if( display_last_screen[ index ] != chunk_detail ) { uidisplay_plot8( start, y, 0xff, colour, 0 ); /* Update last display record */ display_last_screen[ index ] = chunk_detail; /* And now mark it dirty */ display_is_dirty[y] |= ( (libspectrum_qword)1 << start ); } index++; } } static void border_change_write( int y, int start, int end, int colour ) { if( y < DISPLAY_BORDER_HEIGHT || ( y >= DISPLAY_BORDER_HEIGHT + DISPLAY_HEIGHT ) ) { /* Top and bottom borders */ set_border( y, start, end, colour ); return; } /* Left border */ if( start < DISPLAY_BORDER_WIDTH_COLS ) { int left_end = end > DISPLAY_BORDER_WIDTH_COLS ? DISPLAY_BORDER_WIDTH_COLS : end; set_border( y, start, left_end, colour ); } /* Right border */ if( end > DISPLAY_BORDER_WIDTH_COLS + DISPLAY_WIDTH_COLS ) { if( start < DISPLAY_BORDER_WIDTH_COLS + DISPLAY_WIDTH_COLS ) start = DISPLAY_BORDER_WIDTH_COLS + DISPLAY_WIDTH_COLS; set_border( y, start, end, colour ); } } static void border_change_line_part( int y, int start, int end, int colour ) { border_change_write( y, start, end, colour ); } static void border_change_line( int y, int colour ) { border_change_write( y, 0, DISPLAY_SCREEN_WIDTH_COLS, colour ); } static void do_border_change( struct border_change_t *first, struct border_change_t *second ) { if( first->x ) { if( first->x != DISPLAY_SCREEN_WIDTH_COLS ) border_change_line_part( first->y, first->x, DISPLAY_SCREEN_WIDTH_COLS, first->colour ); /* Don't extend region past the end of the screen */ if( first->y < DISPLAY_SCREEN_HEIGHT - 1 ) first->y++; } for( ; first->y < second->y; first->y++ ) { border_change_line( first->y, first->colour ); } if( second->x ) { if( second->x == DISPLAY_SCREEN_WIDTH_COLS ) { border_change_line( first->y, first->colour ); } else { border_change_line_part( first->y, 0, second->x, first->colour ); } } } /* Take account of all the border colour changes which happened in this frame */ static void update_border( void ) { int pos; int error; /* Put the final sentinel onto the list */ struct border_change_t *end_sentinel = alloc_change(); memcpy( end_sentinel, &border_change_end_sentinel, sizeof( struct border_change_t ) ); for( pos = 0; pos < border_changes_last-1; pos++ ) { do_border_change( border_changes+pos, border_changes+pos+1 ); } border_changes_last = 0; error = add_border_sentinel(); if( error ) return; } /* Send the updated screen to the UI-specific code */ static void update_ui_screen( void ) { static int frame_count = 0; int scale = machine_current->timex ? 2 : 1; size_t i; struct rectangle *ptr; if( settings_current.frame_rate <= ++frame_count ) { frame_count = 0; if( display_redraw_all ) { uidisplay_area( 0, 0, scale * DISPLAY_ASPECT_WIDTH, scale * DISPLAY_SCREEN_HEIGHT ); display_redraw_all = 0; } else { for( i = 0, ptr = inactive_rectangle; i < inactive_rectangle_count; i++, ptr++ ) { uidisplay_area( 8 * scale * ptr->x, scale * ptr->y, 8 * scale * ptr->w, scale * ptr->h ); } } inactive_rectangle_count = 0; uidisplay_frame_end(); } } int display_frame( void ) { /* Copy all the critical region to the display */ copy_critical_region( DISPLAY_WIDTH_COLS, DISPLAY_HEIGHT - 1 ); critical_region_x = critical_region_y = 0; update_border(); update_dirty_rects(); update_ui_screen(); if( screenshot_movie_record == 1 ) { snprintf( screenshot_movie_name, SCREENSHOT_MOVIE_FILE_MAX, "%s-frame-%09ld.scr", screenshot_movie_file, screenshot_movie_frame++ ); screenshot_scr_write( screenshot_movie_name ); #ifdef USE_LIBPNG } else if( screenshot_movie_record == 2 ) { snprintf( screenshot_movie_name, SCREENSHOT_MOVIE_FILE_MAX, "%s-frame-%09ld.scr", screenshot_movie_file, screenshot_movie_frame++ ); screenshot_write_fast( screenshot_movie_name, screenshot_movie_scaler ); #endif /* #ifdef USE_LIBPNG */ } display_frame_count++; if(display_frame_count==16) { display_flash_reversed=1; display_dirty_flashing(); } else if(display_frame_count==32) { display_flash_reversed=0; display_dirty_flashing(); display_frame_count=0; } return 0; } static void display_dirty_flashing(void) { libspectrum_word offset; libspectrum_byte *screen, attr; screen = RAM[ memory_current_screen ]; if( !scld_last_dec.name.hires ) { if( scld_last_dec.name.b1 ) { for( offset = ALTDFILE_OFFSET; offset < 0x3800; offset++ ) { attr = screen[ offset ]; if( attr & 0x80 ) display_dirty8( offset - ALTDFILE_OFFSET ); } } else if( scld_last_dec.name.altdfile ) { for( offset= 0x3800; offset < 0x3b00; offset++ ) { attr = screen[ offset ]; if( attr & 0x80 ) display_dirty64( offset - ALTDFILE_OFFSET ); } } else { /* Standard Speccy screen */ for( offset = 0x1800; offset < 0x1b00; offset++ ) { attr = screen[ offset ]; if( attr & 0x80 ) display_dirty64( offset ); } } } } void display_refresh_main_screen(void) { size_t i; for( i = 0; i < DISPLAY_HEIGHT; i++ ) display_maybe_dirty[i] = display_all_dirty; } void display_refresh_all(void) { size_t i; display_redraw_all = 1; display_refresh_main_screen(); for( i = 0; i < DISPLAY_SCREEN_HEIGHT; i++ ) display_is_dirty[i] = display_all_dirty; memset( display_last_screen, 0xff, DISPLAY_SCREEN_WIDTH_COLS * DISPLAY_SCREEN_HEIGHT * sizeof(libspectrum_dword) ); } /* Fetch pixel (x, y). On a Timex this will be a point on a 640x480 canvas, on a Sinclair/Amstrad/Russian clone this will be a point on a 320x240 canvas */ int display_getpixel( int x, int y ) { libspectrum_byte ink, paper; libspectrum_byte data, data2; int mask = 1 << (7 - (x % 8)); int index; if( machine_current->timex ) { int column = x >> 4; y >>= 1; index = column + y * DISPLAY_SCREEN_WIDTH_COLS; data = display_last_screen[ index ] & 0xff; data2 = (display_last_screen[ index ] & 0xff00)>>8; scld mode_data; mode_data.byte = (display_last_screen[ index ] & 0xff0000)>>16; if( mode_data.name.hires ) { if( x % 16 > 7 ) data = data2; display_parse_attr( hires_convert_dec( mode_data.byte ), &ink, &paper ); } else { /* divide x by two to get the same value for adjacent pixels */ mask = 1 << (7 - ((x>>1) % 8)); display_parse_attr( data2, &ink, &paper ); } } else { int column = x >> 3; index = column + y * DISPLAY_SCREEN_WIDTH_COLS; data = display_last_screen[ index ] & 0xff; data2 = (display_last_screen[ index ] & 0xff00)>>8; display_parse_attr( data2, &ink, &paper ); } if( data & mask ) return ink; return paper; }