-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathXDC_DELT.PAS
333 lines (292 loc) · 9.85 KB
/
XDC_DELT.PAS
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
unit xdc_deltas;
{
Contains all structures and procedures related to managing
the list of changes ("deltas") needed to reconstruct
a new video frame.
More "meta" operations such as estimation and optimization are not included
here; consider this a base class.
}
interface
uses
objects;
{object-oriented data structure for managing and optimizing deltas}
type
deltaType=(slice,run); {slice=not all bytes identical; run=all bytes same}
PDelta=^TDelta;
TDelta=object(TObject)
startOfs:word; {starting offset of delta in buffer (first changed byte)}
endOfs:word; {ending offset of delta in buffer (last changed byte)}
{these are calculated during insert or other operations:}
dtype:deltaType; {slice or run}
fillvalue:byte; {for informational purposes only, not used for anything}
blength:word; {length of delta in bytes}
numPixelsChanged:word; {# of pixels changed in the entire delta}
REPcycleCost:real; {different whether slice or run}
frozen:boolean; {flag used to control various optimization phases}
Constructor Init(starto,endo:word);
Destructor Done; virtual;
end;
PDeltas=^TDeltas;
TDeltas=object(TSortedCollection)
function Compare(Key1, Key2: Pointer): Integer; virtual;
{the following used mostly for debugging:}
function GetSize:word; {returns entire list in raw output bytes}
function GetCycles:real; {returns entire list in estimated REP cycles}
(* function GetEncodedBytes:word; {returns entire list in estimated REP cycles} *)
function DumpAll:word;
procedure SplitAll; {splits every delta in two; used for error recovery}
end;
{This structure used only for temporary management of deltas in the
"combine" optimization phase}
PDeltaStarts=^TDeltaStarts;
TDeltaStarts=object(TDeltas)
{Must sort by starting offset so that we can navigate geographically-
close structures efficiently}
function Compare(Key1, Key2: Pointer): Integer; virtual;
end;
var
{frame buffers:}
nextframe,prevframe,CGARAM:pointer;
function deltaInfo(d:pdelta):string;
implementation
uses
xdc_globals,xdc_log,xdc_common,xdc_codegeneration,support;
Constructor tdelta.init;
var
w,loop:word;
bpn,bpp:^byte;
b:byte;
begin
Inherited Init;
if endo<starto then fatalerr('end offset '+inttostr(endo)+' < start offset '+inttostr(starto)+'?');
startofs:=starto;
endofs:=endo;
blength:=1+(endofs-startofs);
if blength=0 then fatalerr('blength 0?');
if blength>screenRAMsize then fatalerr('blength > screen?');
{determine if what was just submitted is a slice or a run by checking the
entire delta to see if all bytes equal the first byte}
if blength<minRunLength
then dtype:=slice
else begin
bpn:=nextFrame;
word(bpn):=startofs;
{assume it is a run until discover otherwise}
dtype:=run;
b:=bpn^;
for w:=startofs to endofs do begin
word(bpn):=w;
if bpn^<>b then begin
dtype:=slice;
break;
end;
end;
{if we are a run, record what value we fill with}
if dtype=run
then fillvalue:=b
else fillvalue:=0;
end;
if dtype=run
then if debug>1
then stderr('run found: '+inttostr(startofs)+', len: '+inttostr(blength));
numPixelsChanged:=$FFFF; {dummy impossible value for now}
frozen:=false;
{determine the REP MOVSW or REP STOSW cycle cost}
if dtype=slice
then REPcycleCost:=(blength*REPMOVSWcycleCost) / 2
else REPcycleCost:=(blength*REPSTOSWcycleCost) / 2;
{if delta is one byte long, determine the # of pixels that have changed}
if blength=1 then begin
numPixelsChanged:=0;
bpn:=nextframe;
bpp:=prevframe;
inc(word(bpn),startofs);
inc(word(bpp),startofs);
b:=bpn^ XOR bpp^;
if b=0 then begin
{This "should never happen" but occasionally does due to design of the
program (decoupled delta optimization phases).
Fixing this would require re-engineering the entire
optimization phase, and since this is not a fatal error, it is okay
to just warn the programmer and keep going.}
if debug>2 then stderr('Non-empty byte is empty at '+hexword(startofs)
+' with values '+bytetohex(bpp^)+' and '+bytetohex(bpn^));
numPixelsChanged:=0;
end else begin
case pixelBitDepth of
1:begin
for loop:=0 to 7 do begin
inc(numPixelsChanged,b AND 1);
b:=b shr 1;
end;
end;
4:begin
if ((b AND $F0) SHR 4) <> 0 then inc(numPixelsChanged);
if (b AND $0F) <> 0 then inc(numPixelsChanged);
end;
8:numPixelsChanged:=1;
else
fatalerr('Cannot process pixels for this bit depth (write more code!)');
end;
end;
end;
end;
Destructor TDelta.Done;
begin
Inherited Done;
end;
Function TDeltas.Compare;
{
We want to maximize motion fidelity, so we are going to make our lives
easier and sort deltas by size.
Note that the comparisons are reversed -- that's because we want the largest
deltas at the beginning of the list. (-1 means beginning of collection.)
- We add a second comparison so that deltas of the same length are sorted
by cycle execution time; this keeps runs prioritized over slices in terms
of what to prioritize if our bandwidth gets starved.
- We add a third comparison so that runs of the same run value
are kept together, so that we can cache the run value.
- We can optionally add a fourth comparison so that, all things being equal,
deltas are sorted by start offset compensated for CGA interlaced memory layout
(this is what I'm colloquially calling "Dave Murry interlace handling"
based on how Dave handled it in the PC booter Sierra Championship Boxing).
}
var
k1so,k2so:word;
begin
k1so:=PDelta(Key1)^.startofs;
k2so:=PDelta(Key2)^.startofs;
if MurryHandling then begin
{if k1so > screenRAMsize div 2 then k1so:=k1so-(screenRAMsize div 2);
if k2so > screenRAMsize div 2 then k2so:=k2so-(screenRAMsize div 2);}
k1so:=k1so AND $1fff;
k2so:=k2so AND $1fff;
end;
{sort by delta length}
if PDelta(Key1)^.blength > PDelta(Key2)^.blength
then Compare := -1
else if PDelta(Key1)^.blength < PDelta(Key2)^.blength
then Compare := 1
{sort runs at a higher priority than slices}
else if PDelta(Key1)^.REPcycleCost < PDelta(Key2)^.REPcycleCost
then Compare := -1
else if PDelta(Key1)^.REPcycleCost > PDelta(Key2)^.REPcycleCost
then Compare := 1
{sort runs by fill value}
else if PDelta(Key1)^.fillvalue > PDelta(Key2)^.fillvalue
then Compare := -1
else if PDelta(Key1)^.fillvalue < PDelta(Key2)^.fillvalue
then Compare := 1
{sort deltas by start offset}
else if k1so < k2so
then Compare := -1
else if k1so > k2so
then Compare := 1
else Compare := 0;
end;
Procedure TDeltas.SplitAll;
{Edge cases exist where the simplest recovery option is to just split
every delta into two parts of equal sizes. This method does that quickly by
taking advantage of the fact that the list is sorted by size, with largest
at the top. In a single iteration from the bottom of the list to the top,
each delta is subdivided. Newer, smaller deltas created by splits are
ignored due to the nature of the foreach counting down.}
var
w,blen:word;
dtmp,dsplit1,dsplit2:pDelta;
begin
for w:=Count-1 downto 0 do begin
dtmp:=at(w);
if dtmp^.blength<2 then continue; {can't split a 1-byte delta!}
blen:=dtmp^.blength div 2;
dsplit1:=new(pdelta,init(dtmp^.startofs,dtmp^.startofs+blen));
dsplit2:=new(pdelta,init(dtmp^.startofs+blen+1,dtmp^.endofs));
insert(dsplit1);
insert(dsplit2);
free(dtmp);
end;
end;
Function TDeltas.GetSize;
{returns total size of all deltas in bytes. This is for informal use only,
as it doesn't take into account the size of the generated code necessary
to replay these deltas.}
var
w:word;
Procedure addsize(d:PDelta); far;
begin
inc(w,d^.blength);
end;
begin
w:=0;
ForEach(@addsize);
GetSize:=w;
end;
Function TDeltas.GetCycles;
{returns total time of all deltas in cycles. This is for informal use only,
as it doesn't take into account the other execution time of the generated code
necessary to replay these deltas.}
var
r:real;
Procedure addCycles(d:PDelta); far;
begin
r:=r+d^.REPcycleCost;
end;
begin
r:=0;
ForEach(@addCycles);
GetCycles:=r;
end;
Function TDeltas.DumpAll;
{writes all deltas to the program log}
Procedure dumpDelta(d:PDelta); far;
begin
if debug>1 then stdout(deltaInfo(d));
end;
begin
if debug>2 then stdout('Dumping '+inttostr(count)+' deltas '
+'(output bytes '+inttostr(getsize)
+', total cycles '+realtostr(getcycles)+'):');
ForEach(@dumpDelta);
end;
Function TDeltaStarts.Compare;
{Sort by starting offset in ascending order}
begin
if PDelta(Key1)^.startofs < PDelta(Key2)^.startofs
then Compare := -1
else if PDelta(Key1)^.startofs > PDelta(Key2)^.startofs
then Compare := 1
else Compare := 0;
end;
function deltaInfo(d:pdelta):string;
var
s:string;
begin
s:=' delta info: ';
s:=s+hexword(d^.startofs)+'-'+hexword(d^.endofs)+':'+intpadded(d^.blength,5,#32);
s:=s+' cost: '+realtostr(d^.REPcycleCost);
if d^.dtype=run then s:=s+', run of '+hex(d^.fillvalue);
if debug>2 then if d^.blength=1 then s:=s+', # changed pixels '+inttostr(d^.numPixelsChanged);
if d^.frozen then s:=s+' (FROZEN)';
deltaInfo:=s;
end;
(*
Function TDeltas.GetEncodedBytes;
{returns total time of all deltas in cycles. This is for informal use only,
as it doesn't take into account the other execution time of the generated code
necessary to replay these deltas.}
var
w:word;
Procedure addEncoded(d:PDelta); far;
var
_tmpe:TEncodeTarget;
begin
encodeDelta(d,_tmpe);
inc(w,_tmpe.totalBytes);
end;
begin
w:=0;
ForEach(@addEncoded);
GetEncodedBytes:=w;
end;
*)
end.