1 /*
2 * libtilemcore - Graphing calculator emulation library
3 *
4 * Copyright (C) 2010-2011 Benjamin Moody
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public License
8 * as published by the Free Software Foundation; either version 2.1 of
9 * the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see
18 * <http://www.gnu.org/licenses/>.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <stdio.h>
26 #include <string.h>
27 #include "tilem.h"
28 #include "graylcd.h"
29
30 /* Read screen contents and update pixels that have changed */
tmr_screen_update(TilemCalc * calc,void * data)31 static void tmr_screen_update(TilemCalc *calc, void *data)
32 {
33 TilemGrayLCD *glcd = data;
34 byte *np, *op, nb, ob, d;
35 int i, j, n;
36 dword delta;
37
38 glcd->t++;
39
40 if (calc->z80.lastlcdwrite == glcd->lcdupdatetime)
41 return;
42 glcd->lcdupdatetime = calc->z80.lastlcdwrite;
43
44 (*calc->hw.get_lcd)(calc, glcd->newbits);
45
46 np = glcd->newbits;
47 op = glcd->oldbits;
48 glcd->oldbits = np;
49 glcd->newbits = op;
50 n = 0;
51
52 for (i = 0; i < glcd->bwidth * glcd->height; i++) {
53 nb = *np;
54 ob = *op;
55 d = nb ^ ob;
56 for (j = 0; j < 8; j++) {
57 if (d & (0x80 >> j)) {
58 delta = glcd->t - glcd->tchange[n];
59 glcd->tchange[n] = glcd->t;
60
61 if (ob & (0x80 >> j)) {
62 glcd->curpixels[n].ndark += delta;
63 glcd->curpixels[n].ndarkseg++;
64 }
65 else {
66 glcd->curpixels[n].nlight += delta;
67 glcd->curpixels[n].nlightseg++;
68 }
69 }
70 n++;
71 }
72
73 np++;
74 op++;
75 }
76 }
77
tilem_gray_lcd_new(TilemCalc * calc,int windowsize,int sampleint)78 TilemGrayLCD* tilem_gray_lcd_new(TilemCalc *calc, int windowsize, int sampleint)
79 {
80 TilemGrayLCD *glcd = tilem_new(TilemGrayLCD, 1);
81 int nbytes, npixels, i;
82
83 glcd->bwidth = (calc->hw.lcdwidth + 7) / 8;
84 glcd->height = calc->hw.lcdheight;
85 nbytes = glcd->bwidth * glcd->height;
86 npixels = nbytes * 8;
87
88 glcd->oldbits = tilem_new_atomic(byte, nbytes);
89 glcd->newbits = tilem_new_atomic(byte, nbytes);
90 glcd->tchange = tilem_new_atomic(dword, npixels);
91 glcd->tframestart = tilem_new_atomic(dword, windowsize);
92 glcd->framestamp = tilem_new_atomic(dword, windowsize);
93 glcd->curpixels = tilem_new_atomic(TilemGrayLCDPixel, npixels);
94 glcd->framebasepixels = tilem_new_atomic(TilemGrayLCDPixel,
95 npixels * windowsize);
96
97 memset(glcd->oldbits, 0, nbytes);
98 memset(glcd->tchange, 0, npixels * sizeof(dword));
99 memset(glcd->tframestart, 0, windowsize * sizeof(dword));
100 memset(glcd->curpixels, 0, npixels * sizeof(TilemGrayLCDPixel));
101 memset(glcd->framebasepixels, 0, (npixels * windowsize
102 * sizeof(TilemGrayLCDPixel)));
103
104 glcd->calc = calc;
105 glcd->timer_id = tilem_z80_add_timer(calc, sampleint / 2, sampleint, 1,
106 &tmr_screen_update, glcd);
107
108 /* assign arbitrary but unique timestamps to the initial n
109 frames */
110 for (i = 0; i < windowsize; i++)
111 glcd->framestamp[i] = calc->z80.lastlcdwrite - i;
112
113 glcd->lcdupdatetime = calc->z80.lastlcdwrite - 1;
114 glcd->t = 0;
115 glcd->windowsize = windowsize;
116 glcd->sampleint = sampleint;
117 glcd->framenum = 0;
118
119 return glcd;
120 }
121
tilem_gray_lcd_free(TilemGrayLCD * glcd)122 void tilem_gray_lcd_free(TilemGrayLCD *glcd)
123 {
124 tilem_z80_remove_timer(glcd->calc, glcd->timer_id);
125
126 tilem_free(glcd->oldbits);
127 tilem_free(glcd->newbits);
128 tilem_free(glcd->tchange);
129 tilem_free(glcd->tframestart);
130 tilem_free(glcd->framestamp);
131 tilem_free(glcd->curpixels);
132 tilem_free(glcd->framebasepixels);
133 tilem_free(glcd);
134 }
135
136 /* Update levelbuf with values based on the accumulated grayscale
137 data */
tilem_gray_lcd_get_frame(TilemGrayLCD * restrict glcd,TilemLCDBuffer * restrict buf)138 void tilem_gray_lcd_get_frame(TilemGrayLCD * restrict glcd,
139 TilemLCDBuffer * restrict buf)
140 {
141 int i, j, n;
142 unsigned int current, delta, fd, fl;
143 word ndark, nlight, ndarkseg, nlightseg;
144 dword tbase, tlimit;
145 dword lastwrite;
146 byte * restrict bp;
147 byte * restrict op;
148 TilemGrayLCDPixel * restrict pix;
149 TilemGrayLCDPixel * restrict basepix;
150 dword * restrict tchange;
151
152 if (TILEM_UNLIKELY(buf->height != glcd->height
153 || buf->rowstride != glcd->bwidth * 8)) {
154 /* reallocate data buffer */
155 tilem_free(buf->data);
156 buf->data = tilem_new_atomic(byte,
157 glcd->height * glcd->bwidth * 8);
158 buf->rowstride = glcd->bwidth * 8;
159 buf->height = glcd->height;
160 }
161
162 buf->width = glcd->calc->hw.lcdwidth;
163
164 if (!glcd->calc->lcd.active
165 || (glcd->calc->z80.halted && !glcd->calc->poweronhalt)) {
166 /* screen is turned off */
167 buf->stamp = glcd->calc->z80.lastlcdwrite;
168 buf->contrast = 0;
169 return;
170 }
171
172 buf->contrast = glcd->calc->lcd.contrast;
173
174 /* If LCD remains unchanged throughout the window, set
175 timestamp to the time when the LCD was last changed, so
176 that consecutive frames have the same timestamp. If LCD
177 has changed during the window, values of gray pixels will
178 vary from one frame to another, so use a unique timestamp
179 for each frame */
180 lastwrite = glcd->calc->z80.lastlcdwrite;
181 if (glcd->framestamp[glcd->framenum] == lastwrite)
182 buf->stamp = lastwrite;
183 else
184 buf->stamp = glcd->calc->z80.clock + 0x80000000;
185 glcd->framestamp[glcd->framenum] = lastwrite;
186
187 /* set tbase to the sample number where the window began; this
188 is used to limit the weight of unchanging pixels */
189 tbase = glcd->tframestart[glcd->framenum];
190 glcd->tframestart[glcd->framenum] = glcd->t;
191 tlimit = glcd->t - tbase; /* number of samples per window */
192
193 bp = glcd->newbits;
194 op = buf->data;
195 pix = glcd->curpixels;
196 basepix = glcd->framebasepixels + (glcd->framenum * glcd->height
197 * glcd->bwidth * 8);
198 tchange = glcd->tchange;
199
200 (*glcd->calc->hw.get_lcd)(glcd->calc, bp);
201
202 n = 0;
203
204 for (i = 0; i < glcd->bwidth * glcd->height; i++) {
205 for (j = 0; j < 8; j++) {
206 /* check if pixel is currently set */
207 current = *bp & (0x80 >> j);
208
209 /* compute number of dark and light samples
210 within the window */
211 ndark = pix[n].ndark - basepix[n].ndark;
212 nlight = pix[n].nlight - basepix[n].nlight;
213
214 /* compute number of dark and light segments
215 within the window */
216 ndarkseg = pix[n].ndarkseg - basepix[n].ndarkseg;
217 nlightseg = pix[n].nlightseg - basepix[n].nlightseg;
218
219 /* average light segment in this window is
220 (nlight / nlightseg); average dark segment
221 is (ndark / ndarkseg) */
222
223 /* ensure tchange is later than or equal to tbase */
224 if (tchange[n] - tbase > tlimit) {
225 tchange[n] = tbase;
226 }
227
228 /* if current segment is longer than average,
229 count it as well */
230 delta = glcd->t - tchange[n];
231
232 if (current) {
233 if (delta * ndarkseg >= ndark) {
234 ndark += delta;
235 ndarkseg++;
236 }
237 }
238 else {
239 if (delta * nlightseg >= nlight) {
240 nlight += delta;
241 nlightseg++;
242 }
243 }
244
245 fd = ndark * nlightseg;
246 fl = nlight * ndarkseg;
247
248 if (fd + fl == 0)
249 *op = (ndark ? 128 : 0);
250 else
251 *op = ((fd * 128) / (fd + fl));
252
253 n++;
254 op++;
255 }
256 bp++;
257 }
258
259 memcpy(basepix, pix, (glcd->height * glcd->bwidth * 8
260 * sizeof(TilemGrayLCDPixel)));
261
262 glcd->framenum = (glcd->framenum + 1) % glcd->windowsize;
263 }
264