Paul Murrell
Department of Statistics
The University of Auckland
paul@stat.auckland.ac.nz
The basic idea of these changes is to separate the graphics into three components: graphics devices, graphics engine, and graphics systems. R's main graphics are an example of a graphics system -- I refer to these as "base graphics".
This separation means that R's base graphics do not have any particularly special status (apart from the fact that they get loaded by default). Other graphics systems can be written and can get equal access to R's graphics engine and graphics devices (prior to these changes, other graphics systems had to go via R's base graphics, which was not convenient for some things. For example, controlling the clipping region on devices was very awkward.)
The interface to the graphics device component is given in
GraphicsDevice.h
.
This describes the data and functions that every device must store -- the
graphics engine assumes this information about every device.
Graphics calls to the device must specify all locations and dimensions
in device units.
The interface to the graphics engine component is given in
GraphicsEngine.h
.
This describes the functions that the graphics engine can perform and is
used by graphics systems to produce graphical output.
Calls to the graphics engine must use device coordinates -- the graphics
engine provides some helper functions for converting between device
coordinates, normalised device coordinates, and inches.
The interface to R's base graphics is still given by Rgraphics.h
until further changes are made (see below).
Both the graphics engine and the graphics devices must contain no information
about a particular graphics system. Graphics systems must register with the
graphics engine using GEregisterSystem
. The system must provide
a callback function when it registers -- the graphics engine will use
this callback to signal the graphics system when important events occur
(e.g., when a new device has been created). GEregisterSystem
returns an
integer that the graphics system must record -- the graphics engine records
system-specific information with every device amd this integer
is used within the
callback function to get system-specific information back out of a device.
Calls to the graphics engine (and devices) must specify any relevant
graphical parameters. For example, a call to GELine
must
specify a line colour, a line type, and a line width. (It used to be the
case that the graphics engine and even graphics devices would look back
up to the graphics system to see what the current settings were for
graphcal parameters. This reflected the assumption that there would only
ever be one graphics system to look back up to.)
This reduces the allowable set of graphics
parameters to those understood by the graphics engine; these are:
col (for lines and borders), fill (for filling rects, polygons, ...),
lwd (line width), lty (line type), font (font face), ps (point size of
text), cex (character scaling factor). Other par()
parameters are restricted to base graphics (as they should be).
The creation, destruction, copying, ... of devices is handled by the graphics engine.
There are three types of information stored about each R device: device-level information (including device-specific information) in the NewDevDesc structure; graphics engine information in the GEDevDesc structure; and graphics system-specific information in the GESystemDesc structure. The graphics engine is the master both in relation to the devices and in relation to graphics systems. The GEDevDesc structure (per device) contains information about the device in a NewDevDesc and information about every registered graphics system (e.g., current graphics system state) in an array of GESystemDesc's.
GE_InitState
event. This code can rely on the system knowing its register index,
but the dd->gesd[systemRegisterIndex]
structure is empty.
It is the callback code's responsibility to place a system state object
within the dd->gesd[systemRegisterIndex]
structure before
trying to call code that will access that structure.
GEDevDesc
.
(stored in the global R_Devices
array).
This structure represent the graphics engine's record of
the device. There is information about the device itself, in a
NewDevDesc
structure, and graphics-state information
for the device for each registered graphics system in an array
of GESystemDesc
structures.
GEDevDesc NewDevDesc *dev GESystemDesc gesd[]
NewDevDesc
structure contains basic generic
information about
the device, PLUS device-specific information in a device-specific
structure, PLUS pointers to device-specific implementations of the
required set of graphical functions.
NewDevDesc < generic info > < graphics function pointers > void *deviceSpecific
GESystemDesc
structure contains system-specific
information in a system-specific structure, PLUS a callback function
pointer.
GESystemDesc void *systemSpecific GEcallback callback
GESystemDesc
structure (stored in the global registeredDevices
array).
This is just used to retain a record of which systems are registered
and the respective callback hooks.
device creates NewDevDesc device sets basic information and graphics function pointers device creates deviceSpecific device puts deviceSpecific in NewDevDesc engine creates GEDevDesc engine puts NewDevDesc in GEDevDesc engine creates GESystemDesc for each registered system each system creates systemSpecific system puts systemSpecific in GESystemDesc engine puts GESystemDescs in GEDevDesc
device frees deviceSpecific each system frees systemSpecific engine frees GESystemDesc for each registered system engine frees NewDevDesc engine frees GEDevDesc
engine creates GESystemDesc for each existing device system creates systemSpecific for each GESystemDesc system puts systemSpecific in GESystemDesc engine puts GESystemDescs in GEDevDesc engine creates GESystemDesc for global array
system frees systemSpecific for each existing device engine frees GESystemDesc for each existing device engine frees GESystemDesc in global array
grid
, that would need to be considered when writing another
graphics system.
recordPlot()
and replayPlot()
(R-level functions)
are only base graphics functions at the
moment -- they could be made graphics engine functions.
recordPlot()
and replayPlot()
are
compatible with "snapshots" (used in Windows plot history), but only
by good fortune at this stage; they need thinking about to ensure
compatibility for any future graphics system, in particular saving
such objects and loading them into a new session. An important
consideration when making the change will be how to handle
incompatibility with old snapshots (pre device changes), which is
also only fortuitously achieved at this point.
graphics.c
still consists of a mixture of
base graphics code, code to support the Graphics.h interface,
and graphics engine code (e.g., display list code).
This needs separating so that graphics engine code ends up in
engine.c
and base graphics code ends up in base.c
and only Graphics.h interface support is left in graphics.c.
Here's a list of the various code blocks in graphics.c and where
they should end up:
A particular example of this is the overlap between the base GPar structure and the NewDevDesc structure. The NewDevDesc structure respresents information about a device and this can be removed from the base GPar structure, which should contain only information about base graphics graphical parameters. This change will require modifications of the base graphics code (e.g., in graphics.c and plot.c) to access device information from a NewDevDesc rather than a GPar.
Another important issue here is that there currently exists two copies of
some code, where there is a G* function and a GE* equivalent. Need to
at least modify the G* functions so that they call the GE* equivalent.
This has now been done (in development version post 1.8.0).
Most G* functions in graphics.c
(basically the ones that are to do with drawing things)
are now simply wrappers that call the corresponding GE* function in
engine.c. This allows
allows code in places like plot.c to remain unchanged. Modifying
plot.c to go directly to the GE* functions will require a bit of care.
(NOTE: the graphicsQC package has proved invaluable for checking that
changes to the C code do NOT produce changes in output and would be
an important tool in further changes.)
The current state of the code is represented in the following diagram
(most of the high-level base C code goes through Graphics.h to
graphics.c which goes through GraphicsEngine.h to engine.c):
Rf_gpptr
et al; lots of stuff in
graphics.c
;
all pointer coercions from/to
DevDesc
, NewDevDesc
, and GEDevDesc
!!