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