The Inbox: Morphic-stlu.1682.mcz

Previous Topic Next Topic
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view

The Inbox: Morphic-stlu.1682.mcz

Stephan Lutz uploaded a new version of Morphic to project The Inbox:

==================== Summary ====================

Name: Morphic-stlu.1682
Author: stlu
Time: 10 September 2020, 1:34:13.030274 pm
UUID: c81abdd7-0400-4fe2-bc43-61704bff7fe7
Ancestors: Morphic-mt.1679

Fixes FormCanvas's fast path for pure translations when using MatrixTransform2x3.


FormCanvas's current fast path for DisplayTransforms that are pure translations calls #offset, a method that is not (formally) part of the DisplayTransform protocol. While all transform types currently in trunk implement it, these implementations differ in their meaning:

        A) MatrixTransform2x3 >> #offset describes the translation applied to objects transformed by it on the grid
        B) MorphicTransform >> #offset describes the translation applied to the grid which objects transformed by it are placed on

All this ultimately amounts to is a simple negation. However, FormCanvas >> #transformBy:clippingTo:during:smoothing: assumes all display transforms to behave like MorphicTransform, causing MatrixTransform2x3 to translate in the exact opposite direction. Here an example:
        | formCanvas transform |
        transform := MatrixTransform2x3 withOffset: -100@ -100.
        "uncomment line below for not pure translation which should look (almost) identical"
        "transform := transform composedWithLocal: (MatrixTransform2x3 withScale: 0.99)."
        formCanvas := FormCanvas extent: 400@400.
        formCanvas fillColor: Color white.
                translateBy: 200@200
                during: [:canvas |
                                transformBy: transform
                                clippingTo: (-1000@ -1000 corner: 1000@1000)
                                during: [:c | c fillRectangle: (0@0 extent: 100@100) color: Color red]].
        formCanvas form asMorph openInHand.

BUT since #offset has an entirely different meaning for the two transform types, it is IMO not a good idea to adapt either of its implementations to the other. I would not expect widely accepted matrix terminology to have a different meaning in Squeak. As a result, I currently only see 2 options:

        1. Introduce a new selector (e.g. #canvasOffset or #canvasTranslation)
        2. Use #localPointToGlobal:
I decided to go with the second option, since it reuses the existing interface and is hence automatically compatible with all existing display transforms. Also, while my imagination is failing to think of an example, assuming all transforms can answer to #offset might not be desirable? As for performance impact, the previous implementation is obviously faster, since it only required a quick method accessor. On my machine I benched the following:

        | transform |
        transform := MorphicTransform offset: 123@ -456.
        [transform offset] bench. " '152,000,000 per second. 6.59 nanoseconds per run. 0 % GC time.'"
        [transform localPointToGlobal: 0@0] bench. " '19,500,000 per second. 51.2 nanoseconds per run. 4.79808 % GC time.'"

I focused here on the MorphicTransform, since it is the only one actively used in Morphic (to my knowledge). To me this still seems like a rather tame slowdown, especially considering there only a couple hundred Morphs (at worst) calling #transformBy* in a typical Squeak image.

=============== Diff against Morphic-mt.1679 ===============

Item was changed:
  ----- Method: FormCanvas>>transformBy:clippingTo:during:smoothing: (in category 'drawing-support') -----
+ transformBy: aDisplayTransform clippingTo: aClipRect during: aBlock smoothing: cellSize
- transformBy: aDisplayTransform clippingTo: aClipRect during: aBlock smoothing: cellSize
  "Note: This method has been originally copied from TransformationMorph."
  | innerRect patchRect sourceQuad warp start subCanvas |
+ aDisplayTransform isPureTranslation ifTrue: [
+ ^ self
+ translateBy: (aDisplayTransform localPointToGlobal: 0@0) truncated
+ clippingTo: aClipRect
+ during: aBlock].
- (aDisplayTransform isPureTranslation) ifTrue:[
- ^aBlock value: (self copyOffset: aDisplayTransform offset negated truncated
- clipRect: aClipRect)
- ].
  "Prepare an appropriate warp from patch to innerRect"
  innerRect := aClipRect.
  patchRect := (aDisplayTransform globalBoundsToLocal: innerRect) truncated.
  sourceQuad := (aDisplayTransform sourceQuadFor: innerRect)
  collect: [:p | p - patchRect topLeft].
  warp := self warpFrom: sourceQuad toRect: innerRect.
  warp cellSize: cellSize.
  "Render the submorphs visible in the clipping rectangle, as patchForm"
  start := (self depth = 1 and: [self isShadowDrawing not])
  "If this is true B&W, then we need a first pass for erasure."
  ifTrue: [1] ifFalse: [2].
  start to: 2 do:
  [:i | "If i=1 we first make a shadow and erase it for opaque whites in B&W"
  subCanvas := self class extent: patchRect extent depth: self depth.
  i=1 ifTrue: [subCanvas shadowColor: Color black.
  warp combinationRule: Form erase]
  ifFalse: [self isShadowDrawing ifTrue:
  [subCanvas shadowColor: self shadowColor].
  warp combinationRule: (self depth = 32
  ifTrue: [Form blendAlphaScaled]
  ifFalse: [Form paint])].
  translateBy: patchRect topLeft negated
  during: aBlock.
  warp sourceForm: subCanvas form; warpBits.
  warp sourceForm: nil.  subCanvas := nil "release space for next loop"]