1
0
mirror of https://git.code.sf.net/p/fuse-emulator/fuse synced 2026-10-24 05:39:19 +03:00
Files
fuse/display_render.c
Fredrick Meunier 860baae489 Preserve display image data across refreshes (bug #537)
(thanks, Alberto Garcia)
2026-10-17 11:47:35 +11:00

363 lines
11 KiB
C

/* display_render.c: Spectrum screen rendering
Copyright (c) 1999-2026 Philip Kendall, Thomas Harte, Witold Filipczyk
and Fredrick Meunier
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.
*/
#include "config.h"
#include "display.h"
#include "display_internal.h"
#include "machine.h"
#include "peripherals/scld.h"
#include "spectrum.h"
#include "ui/uidisplay.h"
/* The number of frames mod 32 that have elapsed. */
static int display_frame_count;
static int display_flash_reversed;
display_write_if_dirty_fn display_write_if_dirty;
/* 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;
}
void
display_write_if_dirty_timex( int x, int y )
{
int beam_x, beam_y;
int index;
libspectrum_word offset;
libspectrum_byte *screen;
libspectrum_byte 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_force_redraw[ index ] ||
display_last_screen[ index ] != last_chunk_detail ) {
libspectrum_byte ink, paper;
display_force_redraw[ index ] = 0;
if( scld_last_dec.name.hires ) {
/* In hires mode the attr byte is not in data2, so we must look it up. */
display_parse_attr( display_get_attr_byte( x, y ), &ink, &paper );
libspectrum_word hires_data = ( data << 8 ) + data2;
uidisplay_plot16( beam_x, beam_y, hires_data, ink, paper );
} else {
/* In lores mode data2 already holds the attr byte (set above), so
parse it directly instead of reading it a second time. */
display_parse_attr( data2, &ink, &paper );
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_mark_screen_dirty( beam_x, beam_y );
}
}
static inline void
pentagon_16c_get_colour( libspectrum_byte data, libspectrum_byte *colour1,
libspectrum_byte *colour2 )
{
*colour1 = ( data & 0x07 ) + ( ( data & 0x40 ) >> 3 );
*colour2 = ( ( data & 0x38 ) >> 3 ) + ( ( data & 0x80 ) >> 4 );
}
/* In this mode we need to gather the pixel information for the 8 pixels to
be displayed, if current screen is 5 we need to read from pages 5 and 4,
and if current screen is 7 we need to read from pages 7 and 6. */
void
display_write_if_dirty_pentagon_16_col( int x, int y )
{
int beam_x, beam_y;
int index;
libspectrum_word offset;
libspectrum_byte *screen;
libspectrum_byte data1, data2, data3, data4;
libspectrum_dword last_chunk_detail;
libspectrum_byte colour1, colour2;
/* We need to read the pixels from the appropriate two pages and write them
out to the frame buffer */
int memory_screen_page_1 = 5;
int memory_screen_page_2 = 4;
if( memory_current_screen == 7 ) {
memory_screen_page_1 = 7;
memory_screen_page_2 = 6;
}
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_screen_page_1 ];
data2 = screen[ offset ];
data4 = screen[ offset + ALTDFILE_OFFSET ];
screen = RAM[ memory_screen_page_2 ];
data1 = screen[ offset ];
data3 = screen[ offset + ALTDFILE_OFFSET ];
/* This is a bit of a cheat - we'd normally encode the screen mode in here
as well to support screen mode mixing. I doubt there is much call for
mixing 16 colour mode with other modes so will assume that as long as
we are in 16 colour mode the screen we draw is in that mode as it seems
a shame to chuck more memory at supporting just this obscure mode */
last_chunk_detail = ( data4 << 24 ) | ( data3 << 16 ) | ( data2 <<
8 ) | data1;
/* And draw it if it is different to what was there last time */
index = beam_x + beam_y * DISPLAY_SCREEN_WIDTH_COLS;
if( display_force_redraw[ index ] ||
display_last_screen[ index ] != last_chunk_detail ) {
display_force_redraw[ index ] = 0;
/* Print pixel 1 & 2 from screen_page_2 base, pixel 3 & 4 from
screen_page_1 base, pixel 5 & 6 from screen_page_2 ALTDFILE_OFFSET,
pixel 7 & 8 from screen_page_1 ALTDFILE_OFFSET */
int draw_x = beam_x << 3;
pentagon_16c_get_colour( data1, &colour1, &colour2 );
uidisplay_putpixel( draw_x++, beam_y, colour1 );
uidisplay_putpixel( draw_x++, beam_y, colour2 );
pentagon_16c_get_colour( data2, &colour1, &colour2 );
uidisplay_putpixel( draw_x++, beam_y, colour1 );
uidisplay_putpixel( draw_x++, beam_y, colour2 );
pentagon_16c_get_colour( data3, &colour1, &colour2 );
uidisplay_putpixel( draw_x++, beam_y, colour1 );
uidisplay_putpixel( draw_x++, beam_y, colour2 );
pentagon_16c_get_colour( data4, &colour1, &colour2 );
uidisplay_putpixel( draw_x++, beam_y, colour1 );
uidisplay_putpixel( draw_x, beam_y, colour2 );
/* Update last display record */
display_last_screen[ index ] = last_chunk_detail;
/* And now mark it dirty */
display_mark_screen_dirty( beam_x, beam_y );
}
}
void
display_write_if_dirty_sinclair( int x, int y )
{
int beam_x, beam_y;
int index;
libspectrum_word offset;
libspectrum_byte *screen;
libspectrum_byte data, data2;
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 ];
data2 = display_get_attr_byte( x, y );
last_chunk_detail = ( display_flash_reversed << 24 ) | ( 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_force_redraw[ index ] ||
display_last_screen[ index ] != last_chunk_detail ) {
libspectrum_byte ink, paper;
display_force_redraw[ index ] = 0;
display_parse_attr( data2, &ink, &paper );
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_mark_screen_dirty( beam_x, beam_y );
}
}
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;
}
}
display_dirty_flashing_fn display_dirty_flashing;
void
display_dirty_flashing_timex( 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 < ALTDFILE_OFFSET + DISPLAY_PIXEL_BYTES;
offset++ ) {
attr = screen[ offset ];
if( attr & 0x80 ) display_dirty8( offset - ALTDFILE_OFFSET );
}
} else if( scld_last_dec.name.altdfile ) {
for( offset = ALTDFILE_OFFSET + DISPLAY_PIXEL_BYTES;
offset < ALTDFILE_OFFSET + DISPLAY_FILE_SIZE;
offset++ ) {
attr = screen[ offset ];
if( attr & 0x80 ) display_dirty64( offset - ALTDFILE_OFFSET );
}
} else { /* Standard Speccy screen */
display_dirty_flashing_sinclair();
}
}
}
void
display_dirty_flashing_pentagon_16_col( void )
{
/* No flash attribute in 16 colour mode */
}
void
display_dirty_flashing_sinclair( void )
{
libspectrum_word offset;
libspectrum_byte *screen, attr;
screen = RAM[ memory_current_screen ];
/* Standard Speccy screen */
for( offset = DISPLAY_PIXEL_BYTES; offset < DISPLAY_FILE_SIZE; offset++ ) {
attr = screen[ offset ];
if( attr & 0x80 ) display_dirty64( offset );
}
}
void
display_render_init( void )
{
display_frame_count = 0;
display_flash_reversed = 0;
}
void
display_render_frame( void )
{
display_frame_count++;
if( display_frame_count == DISPLAY_FLASH_HALF_PERIOD ) {
display_flash_reversed = 1;
display_dirty_flashing();
} else if( display_frame_count == DISPLAY_FLASH_PERIOD ) {
display_flash_reversed = 0;
display_dirty_flashing();
display_frame_count = 0;
}
}
#ifdef DISPLAYTEST
void
display_reset_frame_count( void )
{
/* We set the frame count to DISPLAY_FLASH_PERIOD - 1 so the next call
to display_frame() pushes us back to zero and resets
display_flash_reversed */
display_frame_count = DISPLAY_FLASH_PERIOD - 1;
}
void
display_set_flash_reversed( int reversed )
{
display_flash_reversed = reversed;
}
#endif /* #ifdef DISPLAYTEST */