Hi there,
i am working on a Cairo[1] backend for mcclim. While doing so i noticed two issues with the current implementation of displayed-output-records:
a) displayed-output-records want to store transformed graphics. It however is not always possible to fully transform a graphics request. This most notably is the case with DRAW-TEXT* while :transform-glyphs is non-NIL.
Also line styles are hard to transform.
I recognize that that storing transformed graphics request seems easier from the perspective of the CLX backend, since CLX itself can't do transformations; so you need to transform the graphics request anyway before hitting the backend. This however looks different from the perspective of the Cairo backend (and also of the PS backend and a possible OpenGL), as these graphics systems can perfectly well transform graphics requests.
[In case of OpenGL they perhaps can do this even faster than we will ever be due to HW acceleration].
b) the bounding boxes are not always quite correct. Which I believe is because of anti-aliasing, since some pixels at the border of a shape spill over.
PROPOSAL
I propose that we open an opportunity for the backend to implement its own output recording classes. Which would have the following benefits:
- bounding boxes are likely to be correct (E.g. the Cairo backend can tell me the bounding box of graphics requests).
- the backend can decide if it is more economic to store transformed or untransformed coordinates.
- the backend perhaps has a faster way to capture, save and restore the drawing options of a medium than we can have sticking to the medium protocol.
- the backend can decide on the representation of the graphics request. E.g. it might want to save the fully tessellated representation of a shape, it might want to keep references to rasterizied ink in case of drawing with patterns, it might construct display lists. etc.
PROTOCOL
We could specify MEDIUM-RECORD-xyz* methods to complement MEDIUM-DRAW-xyz, like this:
MEDIUM-RECORD-LINE* medium x1 y1 x2 y2 drawp recordp [Generic Function]
If /drawp/ is non-NIL, behave as MEDIUM-DRAW-LINE*.
If /recordp/ is non-NIL, return a displayed output record which, when replayed has the effect or MEDIUM-DRAW-LINE* with the given arguments and the current drawing options installed in /medium/.
Since some work to be done on recording and drawing perhaps is similar, i chose to go with this combined record and/or draw protocol function.
We could specify that an implementation of MEDIUM-RECORD-LINE* for a medium is optional and that a default implementation is provided by CLIM.
So the implementation of MEDIUM-DRAW-LINE* on output recording streams would look like this:
(defmethod medium-draw-line* ((stream output-record-stream) x1 y1 x2 y2) (let ((record (medium-record-line* stream x1 y1 x2 y2 (stream-drawing-p stream) (stream-recording-p stream)))) (when (stream-recording-p stream) (stream-add-output-record stream record))))
Before heading to implement this, i wanted to gather some comments on this.
Hello,
Just some immediate comments:
Gilbert Baumann writes:
PROPOSAL
I propose that we open an opportunity for the backend to implement its own output recording classes
The question is when the specific subclass of output-record is then determined. The output record is created by an :around method that specializes on the stream, but the stream is not backend-specific AFAIK; the medium is.
PROTOCOL
We could specify MEDIUM-RECORD-xyz* methods to complement MEDIUM-DRAW-xyz, like this:
MEDIUM-RECORD-LINE* medium x1 y1 x2 y2 drawp recordp [Generic Function]
If /drawp/ is non-NIL, behave as MEDIUM-DRAW-LINE*. If /recordp/ is non-NIL, return a displayed output record which, when replayed has the effect or MEDIUM-DRAW-LINE* with the given arguments and the current drawing options installed in /medium/.
This in itself looks OK to me, but I have a problem with the way it is used below.
We could specify that an implementation of MEDIUM-RECORD-LINE* for a medium is optional and that a default implementation is provided by CLIM.
OK
So the implementation of MEDIUM-DRAW-LINE* on output recording streams would look like this:
(defmethod medium-draw-line* ((stream output-record-stream) x1 y1 x2 y2) (let ((record (medium-record-line* stream x1 y1 x2 y2 (stream-drawing-p stream) (stream-recording-p stream)))) (when (stream-recording-p stream) (stream-add-output-record stream record))))
I don't see how this can work. According to the spec, the output record is created by the :around method on medium-draw-line* specialized on the stream. The main method has no business creating output records, if I read the spec right. It looks to me like you would have to do:
(defmethod medium-draw-line* :around ((stream output-recording-stream) x1 y1 x2 y2) (when (stream-recording-p stream) (let ((record (medium-record-line* (sheet-medium stream) x1 y1 x2 y2 nil t))) (stream-add-output-record stream record))) (when (stream-drawing-p stream) (medium-record-line* (sheet-medium stream) x1 y1 x2 y2 t nil)))
which somewhat defeats the purpose of having a single medium-record-line, since you can replace the last call with one to medium-draw-line and skip the {recording,drawing}-p arguments to medium-record-line*.
But you can still have medium-specific output records, and a medium-specific medium-record-line*.
Also, we must not forget that the spec allows the user to define his or her own output record subclasses. This must be possible to do without taking into account the backend. There are already too many places in McCLIM where the implementation makes too many assumptions about the superclasses of certain classes, and by doing so makes it impossible for client code to extend it by using the documented protocols.
We must therefore be careful and make sure that any subclass of output-record continue to work properly and independently of the backend.