having different compositing layer render element in the wrong order.
Take this code. The intent is the have the shadow of the backdrop behind the front element.
container:= BlElement new geometry: BlRectangleGeometry new; size: 150@150; background: Color lightGray .
backdrop:= BlElement new geometry: BlCircleGeometry new; size: 100 asPoint;
effect: (BlGaussianShadowEffect color: Color black offset: 0@ 0 width: 80);
background: Color black.
front:= BlElement new geometry: BlCircleGeometry new; size: 100 asPoint; position:5@5; background: Color red.
container addChild: backdrop.
container addChild: front.
container.
However, in current Bloc, we get this
Instead of expected effect:
Reason is: In
aeFullDrawOn: aCanvas
"Main entry point to draw myself and my children on an Alexandrie canvas."
self aeDrawInSameLayerOn: aCanvas.
self aeCompositionLayersSortedByElevationDo: [ :each | each paintOn: aCanvas ].
we call:
aeCompositionLayersSortedByElevationDo: aBlock
(self isTransparent or: [ self isVisible not ])
ifTrue: [ ^ self ].
self wantsSeparateCompositingLayer ifTrue: [
self hasCompositionLayer ifFalse: [
self compositionLayer:
(BAAxisAlignedCompositionLayer newFor: self) ].
aBlock value: self compositionLayer ].
From BAAxisAlignedCompositionLayer comment: Basically, when an element has to draw itself on a canvas and responds true to wantsSeparateCompositingLayer, then it's drawn in a new Cairo surface, and then "painted" into the host space canvas. Next pulse with drawing phase needed, if the element wasn't invalidated, the Cairo surface will be painted again into the host space canvas without need to redraw again. Element effect's have wantsCompositionLayer on their API, which the element queries to decide if it has a composition layer. For example, BlGaussianShadowEffect answers true.
In current situation, when you apply effect: to an element, the order of element is not properly managed.
If you change BlGaussianShadowEffect >> wantsCompositionLayer to return ^ false, you get the desired effect. However, I doubt this is the correct fix 😥
This shows a bug when the composition layer is enabled, so you gave a good workaround until it is fixed (making BlGaussianShadowEffect >> wantsCompositionLayer to return false)
Maybe duplicated from https://github.com/pharo-graphics/Bloc/issues/342 but this is a better report
I pushed @rvillemeur's workaround. This issue can stay open to remember to fix it.
Note for later - as of august 2024, current render loops look like:
It start with
BASpaceRenderer >> renderSpace: aBlSpace
aBlSpace aeFullDrawOn: aeCanvas.
BlSpace default root being a BlElement, the full list of call to AeDraw* function which are called follow this order:
BlElement >> aeFullDrawOn: aCanvas
BlElement >> aeDrawInSameLayerOn: aCanvas.
BlElement >> aeDrawOn: aeCanvas
BlElement >> aeDrawIgnoringOpacityAndTransformationOn: aeCanvas
BlElement >> aeDrawEffectBelowGeometryOn: aeCanvas.
BlElement >> aeDrawGeometryOn: aeCanvas.
BlElement >> aeDrawEffectAboveGeometryOn: aeCanvas.
BlElement >> aeDrawChildrenOn: aeCanvas. "Z-index: children sorted by elevation"
BlElement >> aeDrawInSameLayerOn: aCanvas "and recursive repeat"
BlElement >> aeCompositionLayersSortedByElevationDo: [ :each | each paintOn: aCanvas ].
BlElement >> aeCompositionLayersSortedByElevationDo: call
BAAxisAlignedCompositionLayer >> paintOn: aCanvas
BAAxisAlignedCompositionLayer>> ensureReadyToPaintOn: aCanvas
BlElement >> aeDrawAsSeparatedLayerOn: layerCanvas
BlElement >> aeDrawIgnoringOpacityAndTransformationOn: layerCanvas
At one point, both end calling aeDrawIgnoringOpacityAndTransformationOn. The logic here is to separate rendering into different layer (Z-index is called elevation). Composition layer is mostly a cached rendering, used when the underlying element doesn't need frequent refresh (Identified use case are: drawing a graph, element moving in a drag&drop, shadow gaussian effect).
Potential solution could be to merge Layer rendering into one global loop
@tinchodias looking at the drawing logic used, I updated Bloc code to:
BlElement >>aeFullDrawOn: aCanvas
"Main entry point to draw myself and my children on an Alexandrie canvas."
self aeDrawInSameLayerOn: aCanvas.
"self aeCompositionLayersSortedByElevationDo: [ :each | each paintOn: aCanvas ]"
and updated aeDrawInSameLayerOn: aCanvas to include composition layer.
BlElement >> aeDrawInSameLayerOn: aCanvas
(self isTransparent not and: [
self isVisible and: [ self wantsSeparateCompositingLayer not ] ])
ifTrue: [ self aeDrawOn: aCanvas ]
ifFalse: [
self hasCompositionLayer ifFalse: [
self compositionLayer:
(BAAxisAlignedCompositionLayer newFor: self) ].
self compositionLayer paintOn: aCanvas ]
I can see it fix the issue. I also run code you mention at https://discord.com/channels/223421264751099906/375240886319316994/1301051442030706772, and can see performance improvement as well. I'm wondering what I'm missing with this solution. Any hint ?
Code need to be refactored for a complete solution, but I would like your opinion first. To explain further my change. In current logic, we first draw all element that are not in a composition layer. Once its done, we then draw element in composition layer, when then show on top of the rendering. Those steps shows on below, and this is how we have the bug.
BlElement >> aeFullDrawOn: aCanvas
BlElement >> aeDrawInSameLayerOn: aCanvas.
BlElement >> aeDrawOn: aeCanvas
BlElement >> aeDrawIgnoringOpacityAndTransformationOn: aeCanvas
BlElement >> aeDrawEffectBelowGeometryOn: aeCanvas.
BlElement >> aeDrawGeometryOn: aeCanvas.
BlElement >> aeDrawEffectAboveGeometryOn: aeCanvas.
BlElement >> aeDrawChildrenOn: aeCanvas. "Z-index: children sorted by elevation"
BlElement >> aeDrawInSameLayerOn: aCanvas "and recursive repeat"
BlElement >> aeCompositionLayersSortedByElevationDo: [ :each | each paintOn: aCanvas ].
However, if we look at BlElement >>aeDrawInSameLayerOn: and BlElement >> aeCompositionLayersSortedByElevationDo: we can notice they begin with similar logic.
BlElement >> aeDrawInSameLayerOn: aCanvas
(self isTransparent not and: [
self isVisible and: [
self wantsSeparateCompositingLayer not ] ])
ifTrue: [ self aeDrawOn: aCanvas ]
BlElement >> aeCompositionLayersSortedByElevationDo:
(self isTransparent or: [ self isVisible not ])
ifTrue: [ ^ self ].
self wantsSeparateCompositingLayer ifTrue: [
self hasCompositionLayer ifFalse: [
self compositionLayer:
(BAAxisAlignedCompositionLayer newFor: self) ].
aBlock value: self compositionLayer ].
Code in aeCompositionLayersSortedByElevationDo: look for element in composition layer, compute them and cache them if its not already done, and later loop on element children. This loop is already in place for regular element. By Adding the compute of composition layer in the same loop, we keep them cache if needed, while having them place in the correct order in the children hierarchy.