1
0
mirror of https://git.code.sf.net/p/fuse-emulator/fuse synced 2026-01-28 14:20:54 +03:00
Files
fuse/hacking/implementation_notes.txt
Philip Kendall 7458a0a914 Add brief info on scalers, etc.
Legacy-ID: 1086
2003-03-15 18:59:00 +00:00

127 lines
6.0 KiB
Plaintext

Implementation notes for some of Fuse's systems
===============================================
Contents:
1. The main emulation loop
2. The display code
2.1. Building an image of the Spectrum's screen
1. The main emulation loop
==========================
The main emulation loop (the while loop in fuse.c:main) is essentially
fairly simple: it just runs Z80 opcodes until something `interesting'
happens, at which point it deals with the `interesting' thing.
The question here is how do we know when something `interesting' has
occured: the simple answer is that something interesting occurs when
the `tstates' global variable (which counts tstates since the last
interrupt occured) reaches `event_next_event'. It should be noted here
that these events are purely a Fuse concept, and not related to any OS
feature.
Perhaps the most obvious type of event which can occur is an
interrupt, which (on the 48K machine anyway) will occur 69888 tstates
after the last interrupt. When each interrupt occurs, the code sets
another interrupt to occur one frame later (the event_add() call in
spectrum.c:spectrum_interrupt). `event_next_event' will then be set by
the code in event.c.
The above paragraph is actually overly simplistic: the execution of
Z80 opcodes is also stopped to draw the lines of the Spectrum's
screen; this produces an event of type EVENT_TYPE_LINE at the start of
each of the 240 lines which are drawn by Fuse (the main screen and the
24 lines in each of the top and bottom borders), starting 8936 tstates
after the interrupt (on the 48K machine. FWIW, 8936 is 40 lines at 224
tstates each, minus 24 for the left border; there are 64 lines of
overscan/border after the interrupt but before the screen is
displayed; Fuse shows the last 24 of these as the top border).
TZX playback is handled in much the same way: an event (of type
EVENT_TYPE_EDGE) is scheduled to occur whenever the signal from the
(emulated) tape changes. That event then toggles the input to the
ULA's EAR bit, and schedules another event to occur whenever the next
edge is due from the tape.
RZX playback cannot be handled in the same way, as it is not known
after how many tstates the interrupt will occur. In this case, the
interrupt is forced when it is due to occur by scheduling an event
from the main Z80 emulation loop (z80/z80_ops.c:z80_do_opcodes) and
then breaking out of the loop.
2. The display code
===================
There are two stages to producing the Spectrum's screen on the
emulating machine's screen: firstly, builiding an image of the
Spectrum's screen in memory, and then translating that image onto the
emulating machine's screen.
The first of these functions is accomplished by the code in display.c,
whilst the second is fulfilled by each user interface separately,
although generally in ui/<name>/<name>display.c.
2.1. Building an image of the Spectrum's screen
-----------------------------------------------
The function of almost all the code in display.c is to build an image
of the Spectrum's screen in the display.c:display_image array. For the
`normal' (non-Timex) machines, this array has a size 320x240 and each
pixel represents one pixel on the Spectrum's screen (including 32
pixels of left and right border, and 24 pixels of top and bottom
border). For the Timex machines, this array is sized 640x480 to
accomodate the hires modes and each Spectrum pixel is represented by
two vertically adjacent pixels in the array (as the hires modes double
only the horizontal resolution, not the vertical resolution). In both
cases, the values in this array are the Spectrum colours (0 to 15).
For each line, Fuse keeps track of which pixels on the line need to be
updated in the display.c:display_is_dirty array. Each bit in each
entry represents an 8 (non-Timex) or 16 (Timex) pixel chunk of the
screen plus border which must be updated. The least significant bit
represents the left-most 8 (16) pixels, the second bit the next 8 (16)
and so on. (For the rest of this section, I'll take the doubling in
pixel numbers for Timex machines as read).
The display_is_dirty array is updated whenever video memory is written
to by display.c:display_dirty(); if the `data' area of memory is
written to, one 8 pixel chunk is marked as `dirty', whilst 64 pixels
(in an 8x8 square) are marked as dirty if the attributes area is
written to. `FLASH'ing characters will also cause 8x8 pixel chunks to
be marked as dirty every 16 frames (display.c:display_dirty_flashing()).
The arrays display_dirty_xtable and display_dirty_ytable store the
address to coordinate mappings for the data area of the Spectrum's
screen, whilst display_dirty_xtable2 and display_dirty_ytable2 serves
the same function for the attributes area.
When the time comes for a line to be rendered to the screen, the main
emulation loop (see Section 1) will call display.c:display_draw_line().
This then walks through the line, finding each `dirty' chunk and
writing the colours of those pixels to display_image[]. Each run of
dirty pixels is noted, and a list of rectangles of such pixels is
built up by display.c:add_rectangle() and display.c:end_line().
At the end of the frame, each of these rectangles is passed off to the
user-interface specific rendering code to be drawn onto the emulating
machine's screen.
2.2. From display_image to the emulating machine's screen
---------------------------------------------------------
At the end of every frame, Fuse calls uidisplay_area repeatedly to get
the user interface to update the emulating machine's screen.
If a user interface is outputting the same number of pixels as in
display_image (320x240 for non-Timex, 640x480 for Timex), this can be
very simple, but user interfaces which implement scaling (either
upwards, or downwards as is necessary for displaying the Timex modes
in a 320x240 mode) may wish to make use of the 'scalers' defined in
ui/scaler: a generalised set of routines for accomplishing this, as
well as various smoothing options and the like (for example,
scanlines). See `scalers.txt' for more information on these.
(FIXME: write scalers.txt)
$Id$