/
BalloonEngineBase.class.st
3835 lines (3309 loc) · 127 KB
/
BalloonEngineBase.class.st
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"
This is the main class for the Balloon graphics Engine.
BalloonEnginePlugin should be translated but its superclass should not since it is incorporated within that class's translation process. Nor should the simulation subclass be translated
"
Class {
#name : #BalloonEngineBase,
#superclass : #InterpreterPlugin,
#instVars : [
'workBuffer',
'objBuffer',
'getBuffer',
'aetBuffer',
'spanBuffer',
'engine',
'formArray',
'engineStopped',
'geProfileTime',
'dispatchedValue',
'dispatchReturnValue',
'objUsed',
'doProfileStats',
'copyBitsFn',
'loadBBFn',
'bbPluginName'
],
#classVars : [
'EdgeInitTable',
'EdgeStepTable',
'FillTable',
'WideLineFillTable',
'WideLineWidthTable'
],
#pools : [
'BalloonEngineConstants'
],
#category : #'VMMaker-Plugins'
}
{ #category : #documentation }
BalloonEngineBase class >> a1EngineOutline [
"The following is a brief outline on how the engine works.
In general, we're using a pretty straight-forward active edge approach, e.g.,
we classify all edges into three different states:
a) Waiting for processing
b) Active (e.g., being processed)
c) Finished
Before the engine starts all edges are sorted by their y-value in a so-called
'global edge table' (furthermore referred to as GET) and processed in top
to bottom order (the edges are also sorted by x-value but this is only for
simplifying the insertion when adding edges).
Then, we start at the first visible scan line and execute the following steps:
1) Move all edges starting at the current scan line from state a) to state b)
This step requires the GET to be sorted so that we only need to check
the first edges of the GET. After the initial state of the edge (e.g., it's current
pixel value and data required for incremental updates) the edges are then
inserted in the 'active edge table' (called AET). The sort order in the AET is
defined by the pixel position of each edge at the current scan line and thus
edges are kept in increasing x-order.
This step does occur for every edge only once and is therefore not the most
time-critical part of the approach.
2) Draw the current scan line
This step includes two sub-parts. In the first part, the scan line is assembled.
This involves walking through the AET and drawing the pixels between
each two neighbour edges. Since each edge can have two associated fills
(a 'left' and a 'right' fill) we need to make sure that edges falling on the
same pixel position do not affect the painted image. This issue is discussed
in the aetScanningProblems documentation.
Wide edges (e.g., edges having an associated width) are also handled during
this step. Wide edges are always preferred over interior fills - this ensures
that the outline of an object cannot be overdrawn by any interior fill of
a shape that ends very close to the edge (for more information see wideEdges
documentation).
After the scan is assembled it is blitted to the screen. This only happens all
'aaLevel' scan lines (for further information see the antiAliasing documentation).
This second step is done at each scan line in the image, and is usually the most
time-critical part.
3) Update all currently active edges
Updating the active edges basically means either to remove the edge from the AET
(if it is at the end y value) or incrementally computing the pixel value for the
next scan line. Based on the information gathered in the first step, this part
should be executed as fast as possible - it happens for each edge in the AET
at each scan line and may be the bottleneck if many edges are involved in
the drawing operations (see the TODO list; part of it probably deals with the
issue).
"
^self error:'Comment only'
]
{ #category : #documentation }
BalloonEngineBase class >> a2AntiAliasing [
"The engine currently used a very simple, but efficient anti-aliasing scheme. It is based on a square unweighted filter of size 1, 2, or 4 resulting in three levels of anti-aliasing:
* No anti-aliasing (filter size 1)
This simply draws each pixel 'as is' on the screen
* Slight anti-aliasing (filter size 2)
Doubles the rasterization size in each direction and assembles the pixel value as the medium of the four sub-pixels falling into the full pixel
* Full anti-aliasing (filter size 4)
Quadruples the rasterization in each direction and assembles the pixel value as the medium of the sixteen sub-pixels falling into the full pixel
The reason for using these three AA levels is simply efficiency of computing. Since the above filters (1x1, 2x2, 4x4) have all power of two elements (1, 4, and 16) we can compute the weighted sum of the final pixel by computing
destColor := destColor + (srcColor // subPixels)
And, since we're only working on 32bit destination buffer we do not need to compute the components of each color separately but can neatly put the entire color into a single formula:
destPixel32 := destPixel32 + ((srcPixel32 bitAnd: aaMask) >> aaShift).
with aaMask = 16rFFFFFFFF for aaLevel = 1, aaMask = 16rFCFCFCFC for aaLevel = 2, aaMask = 16rF0F0F0F0 for aaLevel = 4 and aaShift = 0, 2, or 4 for the different levels. However, while the above is efficient to compute, it also drops accuracy. So, for the 4x4 anti-aliasing we're effectively only using the high 4 bits of each color component. While is generally not a problem (we add 16 sub-pixels into this value) there is a simple arithmetic difficulty because the above cannot fill the entire range of values, e.g.,
16 * (255 // 16) = 16 * 15 = 240
and not 255 as expected. We solve this problem by replicating the top n (n=0, 2, 4) bits of each component as the low bits in an adjustment step before blitting to scan line to the screen. This has the nice effect that a zero pixel value (e.g., transparent) will remain zero, a white pixel (as computed above) will result in a value of 255 for each component (defining opaque white) and each color inbetween linearly mapped between 0 and 255.
"
^self error:'Comment only'
]
{ #category : #documentation }
BalloonEngineBase class >> a3RasterizationRules [
^self error:'Comment only'
]
{ #category : #documentation }
BalloonEngineBase class >> a4WideEdges [
]
{ #category : #documentation }
BalloonEngineBase class >> a5AETScanningProblems [
"Due to having two fill entries (one left and one right) there can be problems while scanning the active edge table. In general, the AET should look like the following (ri - regions, ei - edges, fi - fills):
| \ |
r1 | r2 \ r3 | r4
| \ |
e1 e2 e3
with:
f(r1) = fLeft(e1) = 0 (empty fill, denoted -)
f(r2) = fRight(e1) = fLeft(e2) (denoted x)
f(r3) = fRight(e2) = fLeft(e3) (denoted o)
f(r4) = fRight(e3) = 0
However, due to integer arithmetic used during computations the AET may look like the following:
X
\| |
| \ |
| \ |
r1 | r2 \ r3 | r4
| \ |
e1 e2 e3
In this case, the starting point of e1 and e2 have the same x value at the first scan line but e2 has been sorted before e1 (Note: This can happen in *many* cases - the above is just a very simple example). Given the above outlined fill relations we have a problem. So, for instance, using the left/right fills as defined by the edges would lead to the effect that in the first scan line region r3 is actually filled with the right fill of e1 while it should actually be filled with the right fill of e2. This leads to noticable artifacts in the image and increasing resolution does not help.
What we do here is defining an arbitrary sort order between fills (you can think of it as a depth value but the only thing that matters is that you can order the fills by this number and that the empty fill is always sorted at the end), and toggle the fills between an 'active' and an 'inactive' state at each edge. This is done as follows:
For each edge ei in the AET do:
* if fLeft(ei) isActive then removeActive(fLeft(ei)) else addActive(fLeft(ei))
* if fRight(ei) isActive then removeActive(fRight(ei)) else addActive(fRight(ei))
* draw the span from ei to ei+1 with currentActive
where addActive adds the fill to the list of currently active fills, removeActive() removes the fill from the active list and currentActive returns the fill AS DEFINED BY THE SORT ORDER from the list of active fills. Note that this does not change anything in the first example above because the list will only contain one entry (besides the empty fill). In the second case however, it will lead to the following sequence:
* toggle fLeft(e2) = f(r2) = 'x'
- makes fLeft(e2) active
- activeList = 'x'
* toggle fRight(e2) = f(r3) = 'o'
- makes fRight(e2) active
- activeList = 'xo'
* draw span from e2 to e1
Depending on the sort order between 'x' and 'o' the region will be drawn with either one of the fills. It is significant to note here that the occurence of such a problem is generally only *very* few pixels large (in the above example zero pixels) and will therefore not be visually noticable. In any case, there is a unique decision for the fill to use here and that is what we need if the problem did not happen accidentally (e.g., someone has manually changed one fill of an edge but not the fill of the opposite edge).
* toggle fLeft(e1) = f(r1) = '-'
- makes fLeft(r1) visible
- activeList = 'xo-'
[Note: empty fills are a special case.
They can be ignored since they sort last
and the activeList can return the empty
fill if it is itself empty].
* toggle fRight(e1) = f(r2) = 'x'
- makes fRight(e1) invisible
- activeList = 'o-'
* draw span from e2 to e3
Since the active list contains (besides the empty fill) only one fill value this will be used. Fortunately, this is the correct fill because it is the fill we had initially defined for the region r2.
An interesting side effect of the above is that there is no such notion as a 'left' or 'right' fill anymore. Another (not-so-nice) side effect is that the entire AET has to be scanned from the beginning even if only the last few edges actually affect the visible region.
PS. I need to find a way of clipping the edges for this. More on it later...
"
^self error:'Comment only'
]
{ #category : #documentation }
BalloonEngineBase class >> a6StuffTODO [
"This is an unordered list of things to do:
BalloonEnginePlugin>>stepToFirstBezierIn:at:
1) Check if reducing maxSteps from 2*deltaY to deltaY
brings a *significant* performance improvement.
In theory this should make for double step performance
but will cost in quality. Might be that the AA stuff will
compensate for this - but I'm not really sure.
BalloonEngineBase>>dispatchOn:in:
1) Check what dispatches cost most and must be inlined
by an #inlinedDispatchOn:in: Probably this will be
stepping and eventually wide line stuff but we'll see.
BalloonEngineBase
1) Check which variables should become inst vars, if any.
This will remove an indirection during memory access
and might allow a couple of optimizations by the C compiler.
Anti-Aliasing:
1) Check if we can use a weighted 3x3 filter function of the form
1 2 1
2 4 2
1 2 1
Which should be *extremely* nice for fonts (it's sharpening
edges). The good thing about the above is that it sums up to
16 (as in the 4x4 case) but I don't know how to keep a history
without needing two extra scan lines.
2) Check if we can - somehow - integrate more general filters.
3) Unroll the loops during AA so we can copy and mask aaLevel pixels
in each step between start and end. This should speed up filling
by a factor of 2-4 (in particular for difficult stuff like radial gradients).
Clipping
1) Find a way of clipping edges left of the clip rectangle
or at least ignoring most of them after the first scan line.
The AET scanning problems discuss the issue but it should be
possible to keep the color list between spans (if not empty)
and speed up drawing at the very right (such as in the
Winnie Pooh example where a lot of stuff is between the
left border and the clipping rect.
2) Check if we can determine empty states of the color list and
an edge that is longer than anything left of it. This should
work in theory but might be relatively expensive to compute.
"
^self error:'Comment only'
]
{ #category : #translation }
BalloonEngineBase class >> declareCVarsIn: cg [
"Buffers"
cg var: #workBuffer type: #'int*'.
cg var: #objBuffer type: #'int*'.
cg var: #getBuffer type: #'int*'.
cg var: #aetBuffer type: #'int*'.
cg var: #spanBuffer type: #'unsigned int*'.
cg var: #edgeTransform declareC: 'float edgeTransform[6]'.
cg var: #doProfileStats declareC: 'int doProfileStats = 0'.
cg var: 'bbPluginName' declareC:'char bbPluginName[256] = "BitBltPlugin"'.
"Functions"
cg var: 'copyBitsFn' type: 'void *'.
cg var: 'loadBBFn' type: 'void *'.
]
{ #category : #'class initialization' }
BalloonEngineBase class >> initialize [
"BalloonEngineBase initialize"
"BalloonEnginePlugin translateDoInlining: true."
EdgeInitTable := self initializeEdgeInitTable.
EdgeStepTable := self initializeEdgeStepTable.
WideLineWidthTable := self initializeWideLineWidthTable.
WideLineFillTable := self initializeWideLineFillTable.
FillTable := self initializeFillTable.
(Smalltalk classNamed: #BalloonEngineConstants) ifNotNil:
[:balloonEngineConstants|
(balloonEngineConstants classPool anySatisfy: [:classVarValue| classVarValue isNil]) ifTrue:
[balloonEngineConstants initialize]]
]
{ #category : #'class initialization' }
BalloonEngineBase class >> initializeEdgeInitTable [
"BalloonEngineBase initialize"
^#(
errorWrongIndex
errorWrongIndex
errorWrongIndex
errorWrongIndex
stepToFirstLine
stepToFirstWideLine
stepToFirstBezier
stepToFirstWideBezier
)
]
{ #category : #'class initialization' }
BalloonEngineBase class >> initializeEdgeStepTable [
"BalloonEngineBase initialize"
^#(
errorWrongIndex
errorWrongIndex
errorWrongIndex
errorWrongIndex
stepToNextLine
stepToNextWideLine
stepToNextBezier
stepToNextWideBezier
)
]
{ #category : #'class initialization' }
BalloonEngineBase class >> initializeFillTable [
"BalloonEngineBase initialize"
^#(
errorWrongIndex "Type zero - undefined"
errorWrongIndex "Type one - external fill"
fillLinearGradient "Linear gradient fill"
fillRadialGradient "Radial gradient fill"
fillBitmapSpan "Clipped bitmap fill"
fillBitmapSpan "Repeated bitmap fill"
)
]
{ #category : #'class initialization' }
BalloonEngineBase class >> initializeWideLineFillTable [
"BalloonEngineBase initialize"
^#(
errorWrongIndex
errorWrongIndex
returnWideLineFill
returnWideBezierFill
)
]
{ #category : #'class initialization' }
BalloonEngineBase class >> initializeWideLineWidthTable [
"BalloonEngineBase initialize"
^#(
errorWrongIndex
errorWrongIndex
returnWideLineWidth
returnWideBezierWidth
)
]
{ #category : #translation }
BalloonEngineBase class >> moduleName [
^'B2DPlugin'
]
{ #category : #translation }
BalloonEngineBase class >> shouldBeTranslated [
"BalloonEnginePlugin should be translated but its superclasse should not since it is incorporated within this class's translation process. Nor should the simulation subclass be translated"
^self == BalloonEnginePlugin
]
{ #category : #simulation }
BalloonEngineBase class >> simulatorClass [
^BalloonEngineSimulation
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaColorMaskGet [
^workBuffer at: GWAAColorMask
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaColorMaskPut: value [
^workBuffer at: GWAAColorMask put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaColorShiftGet [
^workBuffer at: GWAAColorShift
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaColorShiftPut: value [
^workBuffer at: GWAAColorShift put: value
]
{ #category : #displaying }
BalloonEngineBase >> aaFirstPixelFrom: leftX to: rightX [
"Common function to compute the first full pixel for AA drawing"
| firstPixel |
<inline: true>
firstPixel := (leftX + self aaLevelGet - 1) bitAnd: (self aaLevelGet - 1) bitInvert32.
firstPixel > rightX
ifTrue:[^rightX]
ifFalse:[^firstPixel]
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaHalfPixelGet [
^workBuffer at: GWAAHalfPixel
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaHalfPixelPut: value [
^workBuffer at: GWAAHalfPixel put: value
]
{ #category : #displaying }
BalloonEngineBase >> aaLastPixelFrom: leftX to: rightX [
"Common function to compute the last full pixel for AA drawing"
<inline: true>
^(rightX - 1) bitAnd: (self aaLevelGet - 1) bitInvert32.
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaLevelGet [
^workBuffer at: GWAALevel
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaLevelPut: value [
^workBuffer at: GWAALevel put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaScanMaskGet [
^workBuffer at: GWAAScanMask
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaScanMaskPut: value [
^workBuffer at: GWAAScanMask put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaShiftGet [
^workBuffer at: GWAAShift
]
{ #category : #'accessing state' }
BalloonEngineBase >> aaShiftPut: value [
^workBuffer at: GWAAShift put: value
]
{ #category : #other }
BalloonEngineBase >> accurateLengthOf: deltaX with: deltaY [
"Return the accurate length of the vector described by deltaX and deltaY"
| length2 |
deltaX = 0 ifTrue:[deltaY < 0 ifTrue:[^0-deltaY] ifFalse:[^deltaY]].
deltaY = 0 ifTrue:[deltaX < 0 ifTrue:[^0-deltaX] ifFalse:[^deltaX]].
length2 := (deltaX * deltaX) + (deltaY * deltaY).
^self computeSqrt: length2
]
{ #category : #'GET processing' }
BalloonEngineBase >> addEdgeToGET: edge [
<inline: false>
(self allocateGETEntry: 1) ifFalse:[^0].
"Install edge in the GET"
getBuffer at: self getUsedGet put: edge.
self getUsedPut: self getUsedGet + 1.
]
{ #category : #displaying }
BalloonEngineBase >> adjustAALevel [
"NOTE: This method is (hopefully) obsolete due to unrolling
the fill loops to deal with full pixels."
"Adjust the span buffers values by the appropriate color offset for anti-aliasing.
We do this by replicating the top bits of each color in the lower bits. The idea is that we can scale each color value uniquely from 0 to 255 and thus fill the entire range of colors."
| adjustShift adjustMask x0 x1 pixelValue |
<inline: false>
adjustShift := 8 - self aaColorShiftGet.
adjustMask := self aaColorMaskGet bitInvert32.
x0 := self spanStartGet >> self aaShiftGet.
x1 := self spanEndGet >> self aaShiftGet.
[x0 < x1] whileTrue:[
pixelValue := spanBuffer at: x0.
spanBuffer at: x0 put: (pixelValue bitOr: (pixelValue >> adjustShift bitAnd: adjustMask)).
x0 := x0 + 1].
]
{ #category : #'accessing state' }
BalloonEngineBase >> aetStartGet [
^workBuffer at: GWAETStart
]
{ #category : #'accessing state' }
BalloonEngineBase >> aetStartPut: value [
^workBuffer at: GWAETStart put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> aetUsedGet [
^workBuffer at: GWAETUsed
]
{ #category : #'accessing state' }
BalloonEngineBase >> aetUsedPut: value [
^workBuffer at: GWAETUsed put: value
]
{ #category : #allocating }
BalloonEngineBase >> allocateAETEntry: nSlots [
"Allocate n slots in the active edge table"
^self needAvailableSpace: nSlots
]
{ #category : #allocating }
BalloonEngineBase >> allocateGETEntry: nSlots [
"Allocate n slots in the global edge table"
| srcIndex dstIndex |
<inline: false>
"First allocate nSlots in the AET"
(self allocateAETEntry: nSlots) ifFalse:[^false].
self aetUsedGet = 0 ifFalse:["Then move the AET upwards"
srcIndex := self aetUsedGet.
dstIndex := self aetUsedGet + nSlots.
1 to: self aetUsedGet do:[:i|
aetBuffer at: (dstIndex := dstIndex - 1) put: (aetBuffer at: (srcIndex := srcIndex - 1))].
].
aetBuffer := aetBuffer + nSlots.
^true
]
{ #category : #allocating }
BalloonEngineBase >> allocateObjEntry: nSlots [
"Allocate n slots in the object buffer"
| srcIndex dstIndex |
<inline: false>
"First allocate nSlots in the GET"
(self allocateGETEntry: nSlots) ifFalse:[^false].
self getUsedGet = 0 ifFalse:["Then move the GET upwards"
srcIndex := self getUsedGet.
dstIndex := self getUsedGet + nSlots.
1 to: self getUsedGet do:[:i|
getBuffer at: (dstIndex := dstIndex - 1) put: (getBuffer at: (srcIndex := srcIndex - 1))].
].
getBuffer := getBuffer + nSlots.
^true
]
{ #category : #allocating }
BalloonEngineBase >> allocateStackEntry: nSlots [
"AET and Stack allocation are symmetric"
^self needAvailableSpace: nSlots
]
{ #category : #allocating }
BalloonEngineBase >> allocateStackFillEntry [
^self wbStackPush: self stackFillEntryLength
]
{ #category : #testing }
BalloonEngineBase >> areEdgeFillsValid: edge [
^((self objectHeaderOf: edge) bitAnd: GEEdgeFillsInvalid) = 0
]
{ #category : #displaying }
BalloonEngineBase >> clearSpanBuffer [
"Clear the current span buffer.
The span buffer is only cleared in the area that has been used by the previous scan line."
| x0 x1 |
<inline: false>
x0 := self spanStartGet >> self aaShiftGet.
x1 := self spanEndGet >> self aaShiftGet + 1.
x0 < 0 ifTrue:[x0 := 0].
x1 > self spanSizeGet ifTrue:[x1 := self spanSizeGet].
[x0 < x1] whileTrue:[
spanBuffer at: x0 put: 0.
x0 := x0 + 1].
self spanStartPut: self spanSizeGet.
self spanEndPut: 0.
]
{ #category : #'accessing state' }
BalloonEngineBase >> clearSpanBufferGet [
^workBuffer at: GWClearSpanBuffer
]
{ #category : #'accessing state' }
BalloonEngineBase >> clearSpanBufferPut: value [
^workBuffer at: GWClearSpanBuffer put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> clipMaxXGet [
^workBuffer at: GWClipMaxX
]
{ #category : #'accessing state' }
BalloonEngineBase >> clipMaxXPut: value [
^workBuffer at: GWClipMaxX put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> clipMaxYGet [
^workBuffer at: GWClipMaxY
]
{ #category : #'accessing state' }
BalloonEngineBase >> clipMaxYPut: value [
^workBuffer at: GWClipMaxY put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> clipMinXGet [
^workBuffer at: GWClipMinX
]
{ #category : #'accessing state' }
BalloonEngineBase >> clipMinXPut: value [
^workBuffer at: GWClipMinX put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> clipMinYGet [
^workBuffer at: GWClipMinY
]
{ #category : #'accessing state' }
BalloonEngineBase >> clipMinYPut: value [
^workBuffer at: GWClipMinY put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> colorTransform [
<returnTypeC:'float *'>
^self cCoerce: workBuffer + GWColorTransform to:'float *'
]
{ #category : #other }
BalloonEngineBase >> computeSqrt: length2 [
length2 < 32
ifTrue:[^self smallSqrtTable at: length2]
ifFalse:[^(length2 asFloat sqrt + 0.5) asInteger]
]
{ #category : #private }
BalloonEngineBase >> copyBitsFrom: x0 to: x1 at: yValue [
copyBitsFn = 0 ifTrue: [
"We need copyBits here so try to load it implicitly"
self initialiseModule ifFalse: [^false].
].
^self cCode: '((sqInt (*)(sqInt, sqInt, sqInt))copyBitsFn)(x0, x1, yValue)'
]
{ #category : #'GET processing' }
BalloonEngineBase >> createGlobalEdgeTable [
"Create the global edge table"
| object end |
<inline: false>
object := 0.
end := objUsed.
[object < end] whileTrue:[
"Note: addEdgeToGET: may fail on insufficient space but that's not a problem here"
(self isEdge: object) ifTrue:[
"Check if the edge starts below fillMaxY."
(self edgeYValueOf: object) >= self fillMaxYGet ifFalse:[
self checkedAddEdgeToGET: object.
].
].
object := object + (self objectLengthOf: object).
].
]
{ #category : #'accessing state' }
BalloonEngineBase >> currentYGet [
^workBuffer at: GWCurrentY
]
{ #category : #'accessing state' }
BalloonEngineBase >> currentYPut: value [
^workBuffer at: GWCurrentY put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> currentZGet [
^workBuffer at: GWCurrentZ
]
{ #category : #'accessing state' }
BalloonEngineBase >> currentZPut: value [
^workBuffer at: GWCurrentZ put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> destOffsetXGet [
^workBuffer at: GWDestOffsetX
]
{ #category : #'accessing state' }
BalloonEngineBase >> destOffsetXPut: value [
^workBuffer at: GWDestOffsetX put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> destOffsetYGet [
^workBuffer at: GWDestOffsetY
]
{ #category : #'accessing state' }
BalloonEngineBase >> destOffsetYPut: value [
^workBuffer at: GWDestOffsetY put: value
]
{ #category : #displaying }
BalloonEngineBase >> displaySpanBufferAt: y [
"Display the span buffer at the current scan line."
| targetX0 targetX1 targetY |
<inline: false>
"self aaLevelGet > 1 ifTrue:[self adjustAALevel]."
targetX0 := self spanStartGet >> self aaShiftGet.
targetX0 < self clipMinXGet ifTrue:[targetX0 := self clipMinXGet].
targetX1 := (self spanEndGet + self aaLevelGet - 1) >> self aaShiftGet.
targetX1 > self clipMaxXGet ifTrue:[targetX1 := self clipMaxXGet].
targetY := y >> self aaShiftGet.
(targetY < self clipMinYGet or:[targetY >= self clipMaxYGet or:[
targetX1 < self clipMinXGet or:[targetX0 >= self clipMaxXGet]]]) ifTrue:[^0].
self copyBitsFrom: targetX0 to: targetX1 at: targetY.
]
{ #category : #displaying }
BalloonEngineBase >> drawWideEdge: edge from: leftX [
"Draw the given edge starting from leftX with the edge's fill.
Return the end value of the drawing operation."
| rightX fill type lineWidth |
<inline: false> "Not for the moment"
type := self edgeTypeOf: edge.
dispatchedValue := edge.
self dispatchOn: type in: WideLineWidthTable.
lineWidth := dispatchReturnValue.
self dispatchOn: type in: WideLineFillTable.
fill := self makeUnsignedFrom: dispatchReturnValue.
fill = 0 ifTrue:[^leftX].
"Check if this line is only partially visible"
"self assert:(self isFillColor: fill)."
rightX := leftX + lineWidth.
self fillSpan: fill from: leftX to: rightX.
^rightX
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeFillsInvalidate: edge [
^self objectTypeOf: edge put:
((self objectTypeOf: edge) bitOr: GEEdgeFillsInvalid)
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeFillsValidate: edge [
^self objectTypeOf: edge put:
((self objectTypeOf: edge) bitAnd: GEEdgeFillsInvalid bitInvert32)
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeLeftFillOf: edge [
^self obj: edge at: GEFillIndexLeft
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeLeftFillOf: edge put: value [
^self obj: edge at: GEFillIndexLeft put: value
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeNumLinesOf: edge [
^self obj: edge at: GENumLines
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeNumLinesOf: edge put: value [
^self obj: edge at: GENumLines put: value
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeRightFillOf: edge [
^self obj: edge at: GEFillIndexRight
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeRightFillOf: edge put: value [
^self obj: edge at: GEFillIndexRight put: value
]
{ #category : #'accessing state' }
BalloonEngineBase >> edgeTransform [
<returnTypeC:'float *'>
^self cCoerce: workBuffer + GWEdgeTransform to:'float *'
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeTypeOf: edge [
"Return the edge type (e.g., witout the wide edge flag)"
^(self objectTypeOf: edge) >> 1
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeXValueOf: edge [
^self obj: edge at: GEXValue
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeXValueOf: edge put: value [
^self obj: edge at: GEXValue put: value
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeYValueOf: edge [
^self obj: edge at: GEYValue
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeYValueOf: edge put: value [
^self obj: edge at: GEYValue put: value
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeZValueOf: edge [
^self obj: edge at: GEZValue
]
{ #category : #'accessing edges' }
BalloonEngineBase >> edgeZValueOf: edge put: value [
^self obj: edge at: GEZValue put: value
]
{ #category : #private }
BalloonEngineBase >> errorWrongIndex [
"Ignore dispatch errors when translating to C
(since we have no entry point for #error in the VM proxy)"
self cCode:'' inSmalltalk:[self error:'BalloonEngine: Fatal dispatch error']
]
{ #category : #other }
BalloonEngineBase >> estimatedLengthOf: deltaX with: deltaY [
"Estimate the length of the vector described by deltaX and deltaY.
This method may be extremely inaccurate - use it only
if you know exactly that this doesn't matter. Otherwise
use #accurateLengthOf:width:"
| absDx absDy |
deltaX >= 0 ifTrue:[absDx := deltaX] ifFalse:[absDx := 0 - deltaX].
deltaY >= 0 ifTrue:[absDy := deltaY] ifFalse:[absDy := 0 - deltaY].
absDx > absDy
ifTrue:[^absDx + (absDy // 2)]
ifFalse:[^absDy + (absDx // 2)]
]
{ #category : #displaying }
BalloonEngineBase >> fillAllFrom: leftX to: rightX [
"Fill the span buffer from leftX to rightX with the given fill."
| fill startX stopX |
<inline: true>
fill := self topFill.
startX := leftX.
stopX := self topRightX.
[stopX < rightX] whileTrue:[
fill := self makeUnsignedFrom: self topFill.
fill = 0 ifFalse:[
(self fillSpan: fill from: startX to: stopX) ifTrue:[^true]].
self quickRemoveInvalidFillsAt: stopX.
startX := stopX.
stopX := self topRightX].
fill := self makeUnsignedFrom: self topFill.
fill = 0 ifFalse:[^self fillSpan: fill from: startX to: rightX].
^false
]
{ #category : #displaying }
BalloonEngineBase >> fillBitmapSpan: bits from: leftX to: rightX [
"Fill the span buffer between leftEdge and rightEdge using the given bits.
Note: We always start from zero - this avoids using huge bitmap buffers if the bitmap is to be displayed at the very far right hand side and also gives us a chance of using certain bitmaps (e.g., those with depth 32) directly."
| x0 x1 x bitX colorMask colorShift baseShift fillValue |
<inline: false>
<var: #bits type:'int *'>
x0 := leftX.
x1 := rightX.
bitX := -1. "Hack for pre-increment"
self aaLevelGet = 1 ifTrue:["Speedy version for no anti-aliasing"
[x0 < x1] whileTrue:[
fillValue := (self cCoerce: bits to: 'int *') at: (bitX := bitX + 1).
spanBuffer at: x0 put: fillValue.
x0 := x0 + 1.
].
] ifFalse:["Generic version with anti-aliasing"
colorMask := self aaColorMaskGet.
colorShift := self aaColorShiftGet.
baseShift := self aaShiftGet.
[x0 < x1] whileTrue:[
x := x0 >> baseShift.
fillValue := (self cCoerce: bits to: 'int *') at: (bitX := bitX + 1).
fillValue := (fillValue bitAnd: colorMask) >> colorShift.
spanBuffer at: x put: (spanBuffer at: x) + fillValue.
x0 := x0 + 1.
].
].
x1 > self spanEndGet ifTrue:[self spanEndPut: x1].
x1 > self spanEndAAGet ifTrue:[self spanEndAAPut: x1].
]
{ #category : #displaying }
BalloonEngineBase >> fillColorSpan: pixelValue32 from: leftX to: rightX [
"Fill the span buffer between leftEdge and rightEdge with the given pixel value."
| x0 x1 |
<inline: true>
"Use a unrolled version for anti-aliased fills..."
self aaLevelGet = 1
ifFalse:[^self fillColorSpanAA: pixelValue32 x0: leftX x1: rightX].
x0 := leftX.
x1 := rightX.
"Unroll the inner loop four times, since we're only storing data."
[x0 + 4 < x1] whileTrue:[
spanBuffer at: x0 put: pixelValue32.
spanBuffer at: x0+1 put: pixelValue32.
spanBuffer at: x0+2 put: pixelValue32.
spanBuffer at: x0+3 put: pixelValue32.
x0 := x0+4.
].
[x0 < x1] whileTrue:[
spanBuffer at: x0 put: pixelValue32.
x0 := x0 + 1.
].
]
{ #category : #displaying }
BalloonEngineBase >> fillColorSpanAA: pixelValue32 x0: leftX x1: rightX [
"This is the inner loop for solid color fills with anti-aliasing.
This loop has been unrolled for speed and quality into three parts:
a) copy all pixels that fall into the first full pixel.
b) copy aaLevel pixels between the first and the last full pixel
c) copy all pixels that fall in the last full pixel"
| colorMask baseShift x idx firstPixel lastPixel aaLevel pv32 |
<inline: false> "Not now -- maybe later"
"Compute the pixel boundaries."
firstPixel := self aaFirstPixelFrom: leftX to: rightX.
lastPixel := self aaLastPixelFrom: leftX to: rightX.
aaLevel := self aaLevelGet.
baseShift := self aaShiftGet.
x := leftX.
"Part a: Deal with the first n sub-pixels"
x < firstPixel ifTrue:[
pv32 := (pixelValue32 bitAnd: self aaColorMaskGet) >> self aaColorShiftGet.
[x < firstPixel] whileTrue:[
idx := x >> baseShift.
spanBuffer at: idx put: (spanBuffer at: idx) + pv32.
x := x + 1.
].
].
"Part b: Deal with the full pixels"
x < lastPixel ifTrue:[
colorMask := (self aaColorMaskGet >> self aaShiftGet) bitOr: 16rF0F0F0F0.
pv32 := (pixelValue32 bitAnd: colorMask) >> self aaShiftGet.
[x < lastPixel] whileTrue:[
idx := x >> baseShift.
spanBuffer at: idx put: (spanBuffer at: idx) + pv32.
x := x + aaLevel.
].
].
"Part c: Deal with the last n sub-pixels"
x < rightX ifTrue:[
pv32 := (pixelValue32 bitAnd: self aaColorMaskGet) >> self aaColorShiftGet.
[x < rightX] whileTrue:[
idx := x >> baseShift.
spanBuffer at: idx put: (spanBuffer at: idx) + pv32.