/[gxemul]/trunk/src/devices/dev_fb.c
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /trunk/src/devices/dev_fb.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 12 - (hide annotations)
Mon Oct 8 16:18:38 2007 UTC (16 years, 6 months ago) by dpavlin
File MIME type: text/plain
File size: 29442 byte(s)
++ trunk/HISTORY	(local)
$Id: HISTORY,v 1.905 2005/08/16 09:16:24 debug Exp $
20050628	Continuing the work on the ARM translation engine. end_of_page
		works. Experimenting with load/store translation caches
		(virtual -> physical -> host).
20050629	More ARM stuff (memory access translation cache, mostly). This
		might break a lot of stuff elsewhere, probably some MIPS-
		related translation things.
20050630	Many load/stores are now automatically generated and included
		into cpu_arm_instr.c; 1024 functions in total (!).
		Fixes based on feedback from Alec Voropay: only print 8 hex
		digits instead of 16 in some cases when emulating 32-bit
		machines; similar 8 vs 16 digit fix for breakpoint addresses;
		4Kc has 16 TLB entries, not 48; the MIPS config select1
		register is now printed with "reg ,0".
		Also changing many other occurances of 16 vs 8 digit output.
		Adding cache associativity fields to mips_cpu_types.h; updating
		some other cache fields; making the output of
		mips_cpu_dumpinfo() look nicer.
		Generalizing the bintrans stuff for device accesses to also
		work with the new translation system. (This might also break
		some MIPS things.)
		Adding multi-load/store instructions to the ARM disassembler
		and the translator, and some optimizations of various kinds.
20050701	Adding a simple dev_disk (it can read/write sectors from
		disk images).
20050712	Adding dev_ether (a simple ethernet send/receive device).
		Debugger command "ninstrs" for toggling show_nr_of_instructions
		during runtime.
		Removing the framebuffer logo.
20050713	Continuing on dev_ether.
		Adding a dummy cpu_alpha (again).
20050714	More work on cpu_alpha.
20050715	More work on cpu_alpha. Many instructions work, enough to run
		a simple framebuffer fill test (similar to the ARM test).
20050716	More Alpha stuff.
20050717	Minor updates (Alpha stuff).
20050718	Minor updates (Alpha stuff).
20050719	Generalizing some Alpha instructions.
20050720	More Alpha-related updates.
20050721	Continuing on cpu_alpha. Importing rpb.h from NetBSD/alpha.
20050722	Alpha-related updates: userland stuff (Hello World using
		write() compiled statically for FreeBSD/Alpha runs fine), and
		more instructions are now implemented.
20050723	Fixing ldq_u and stq_u.
		Adding more instructions (conditional moves, masks, extracts,
		shifts).
20050724	More FreeBSD/Alpha userland stuff, and adding some more
		instructions (inserts).
20050725	Continuing on the Alpha stuff. (Adding dummy ldt/stt.)
		Adding a -A command line option to turn off alignment checks
		in some cases (for translated code).
		Trying to remove the old bintrans code which updated the pc
		and nr_of_executed_instructions for every instruction.
20050726	Making another attempt att removing the pc/nr of instructions
		code. This time it worked, huge performance increase for
		artificial test code, but performance loss for real-world
		code :-( so I'm scrapping that code for now.
		Tiny performance increase on Alpha (by using ret instead of
		jmp, to play nice with the Alpha's branch prediction) for the
		old MIPS bintrans backend.
20050727	Various minor fixes and cleanups.
20050728	Switching from a 2-level virtual to host/physical translation
		system for ARM emulation, to a 1-level translation.
		Trying to switch from 2-level to 1-level for the MIPS bintrans
		system as well (Alpha only, so far), but there is at least one
		problem: caches and/or how they work with device mappings.
20050730	Doing the 2-level to 1-level conversion for the i386 backend.
		The cache/device bug is still there for R2K/3K :(
		Various other minor updates (Malta etc).
		The mc146818 clock now updates the UIP bit in a way which works
		better with Linux for at least sgimips and Malta emulation.
		Beginning the work on refactoring the dyntrans system.
20050731	Continuing the dyntrans refactoring.
		Fixing a small but serious host alignment bug in memory_rw.
		Adding support for big-endian load/stores to the i386 bintrans
		backend.
		Another minor i386 bintrans backend update: stores from the
		zero register are now one (or two) loads shorter.
		The slt and sltu instructions were incorrectly implemented for
		the i386 backend; only using them for 32-bit mode for now.
20050801	Continuing the dyntrans refactoring.
		Cleanup of the ns16550 serial controller (removing unnecessary
		code).
		Bugfix (memory corruption bug) in dev_gt, and a patch/hack from
		Alec Voropay for Linux/Malta.
20050802	More cleanup/refactoring of the dyntrans subsystem: adding
		phys_page pointers to the lookup tables, for quick jumps
		between translated pages.
		Better fix for the ns16550 device (but still no real FIFO
		functionality).
		Converting cpu_ppc to the new dyntrans system. This means that
		I will have to start from scratch with implementing each
		instruction, and figure out how to implement dual 64/32-bit
		modes etc.
		Removing the URISC CPU family, because it was useless.
20050803	When selecting a machine type, the main type can now be omitted
		if the subtype name is unique. (I.e. -E can be omitted.)
		Fixing a dyntrans/device update bug. (Writes to offset 0 of
		a device could sometimes go unnoticed.)
		Adding an experimental "instruction combination" hack for
		ARM for memset-like byte fill loops.
20050804	Minor progress on cpu_alpha and related things.
		Finally fixing the MIPS dmult/dmultu bugs.
		Fixing some minor TODOs.
20050805	Generalizing the 8259 PIC. It now also works with Cobalt
		and evbmips emulation, in addition to the x86 hack.
		Finally converting the ns16550 device to use devinit.
		Continuing the work on the dyntrans system. Thinking about
		how to add breakpoints.
20050806	More dyntrans updates. Breakpoints seem to work now.
20050807	Minor updates: cpu_alpha and related things; removing
		dev_malta (as it isn't used any more).
		Dyntrans: working on general "show trace tree" support.
		The trace tree stuff now works with both the old MIPS code and
		with newer dyntrans modes. :)
		Continuing on Alpha-related stuff (trying to get *BSD to boot
		a bit further, adding more instructions, etc).
20050808	Adding a dummy IA64 cpu family, and continuing the refactoring
		of the dyntrans system.
		Removing the regression test stuff, because it was more or
		less useless.
		Adding loadlinked/storeconditional type instructions to the
		Alpha emulation. (Needed for Linux/alpha. Not very well tested
		yet.)
20050809	The function call trace tree now prints a per-function nr of
		arguments. (Semi-meaningless, since that data isn't read yet
		from the ELFs; some hardcoded symbols such as memcpy() and
		strlen() work fine, though.)
		More dyntrans refactoring; taking out more of the things that
		are common to all cpu families.
20050810	Working on adding support for "dual mode" for PPC dyntrans
		(i.e. both 64-bit and 32-bit modes).
		(Re)adding some simple PPC instructions.
20050811	Adding a dummy M68K cpu family. The dyntrans system isn't ready
		for variable-length ISAs yet, so it's completely bogus so far.
		Re-adding more PPC instructions.
		Adding a hack to src/file.c which allows OpenBSD/mac68k a.out
		kernels to be loaded.
		Beginning to add PPC loads/stores. So far they only work in
		32-bit mode.
20050812	The configure file option "add_remote" now accepts symbolic
		host names, in addition to numeric IPv4 addresses.
		Re-adding more PPC instructions.
20050814	Continuing to port back more PPC instructions.
		Found and fixed the cache/device write-update bug for 32-bit
		MIPS bintrans. :-)
		Triggered a really weird and annoying bug in Compaq's C
		compiler; ccc sometimes outputs code which loads from an
		address _before_ checking whether the pointer was NULL or not.
		(I'm not sure how to handle this problem.)
20050815	Removing all of the old x86 instruction execution code; adding
		a new (dummy) dyntrans module for x86.
		Taking the first steps to extend the dyntrans system to support
		variable-length instructions.
		Slowly preparing for the next release.
20050816	Adding a dummy SPARC cpu module.
		Minor updates (documentation etc) for the release.

==============  RELEASE 0.3.5  ==============


1 dpavlin 4 /*
2     * Copyright (C) 2003-2005 Anders Gavare. All rights reserved.
3     *
4     * Redistribution and use in source and binary forms, with or without
5     * modification, are permitted provided that the following conditions are met:
6     *
7     * 1. Redistributions of source code must retain the above copyright
8     * notice, this list of conditions and the following disclaimer.
9     * 2. Redistributions in binary form must reproduce the above copyright
10     * notice, this list of conditions and the following disclaimer in the
11     * documentation and/or other materials provided with the distribution.
12     * 3. The name of the author may not be used to endorse or promote products
13     * derived from this software without specific prior written permission.
14     *
15     * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18     * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19     * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21     * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25     * SUCH DAMAGE.
26     *
27     *
28 dpavlin 12 * $Id: dev_fb.c,v 1.106 2005/08/14 11:14:38 debug Exp $
29 dpavlin 4 *
30     * Generic framebuffer device.
31     *
32     * DECstation VFB01 monochrome framebuffer, 1024x864
33     * DECstation VFB02 8-bit color framebuffer, 1024x864
34     * DECstation Maxine, 1024x768 8-bit color
35     * HPCmips framebuffer
36     * Playstation 2 (24-bit color)
37     * generic (any resolution, several bit depths possible)
38     *
39     *
40     * TODO: There is still a bug when redrawing the cursor. The underlying
41     * image is moved 1 pixel (?), or something like that.
42     *
43     * TODO: This should actually be independant of X11, but that
44     * might be too hard to do right now.
45     *
46     * TODO: playstation 2 pixels are stored in another format, actually
47     */
48    
49     #include <stdio.h>
50     #include <stdlib.h>
51     #include <string.h>
52    
53     #include "console.h"
54     #include "cpu.h"
55     #include "devices.h"
56     #include "machine.h"
57     #include "memory.h"
58     #include "misc.h"
59     #include "x11.h"
60    
61     #ifdef WITH_X11
62     #include <X11/Xlib.h>
63     #include <X11/Xos.h>
64     #include <X11/Xutil.h>
65     #endif
66    
67    
68 dpavlin 12 #define FB_TICK_SHIFT 19
69 dpavlin 4
70    
71     /* #define FB_DEBUG */
72    
73     /*
74     * set_grayscale_palette():
75     *
76     * Fill d->rgb_palette with grayscale values. ncolors should
77     * be something like 2, 4, 16, or 256.
78     */
79     void set_grayscale_palette(struct vfb_data *d, int ncolors)
80     {
81     int i, gray;
82    
83     for (i=0; i<256; i++) {
84     gray = 255*i/(ncolors-1);
85     d->rgb_palette[i*3 + 0] = gray;
86     d->rgb_palette[i*3 + 1] = gray;
87     d->rgb_palette[i*3 + 2] = gray;
88     }
89     }
90    
91    
92     /*
93     * set_blackwhite_palette():
94     *
95     * Set color 0 = black, all others to white.
96     */
97     void set_blackwhite_palette(struct vfb_data *d, int ncolors)
98     {
99     int i, gray;
100    
101     for (i=0; i<256; i++) {
102     gray = i==0? 0 : 255;
103     d->rgb_palette[i*3 + 0] = gray;
104     d->rgb_palette[i*3 + 1] = gray;
105     d->rgb_palette[i*3 + 2] = gray;
106     }
107     }
108    
109    
110     /*
111 dpavlin 6 * dev_fb_resize():
112     *
113     * Resize a framebuffer window. (This functionality is probably a bit buggy,
114     * because I didn't think of including it from the start.)
115     */
116     void dev_fb_resize(struct vfb_data *d, int new_xsize, int new_ysize)
117     {
118     unsigned char *new_framebuffer;
119     int y, new_bytes_per_line;
120     size_t size;
121    
122     if (d == NULL) {
123     fatal("dev_fb_resize(): d == NULL\n");
124     return;
125     }
126    
127     new_bytes_per_line = new_xsize * d->bit_depth / 8;
128     size = new_ysize * new_bytes_per_line;
129    
130     new_framebuffer = malloc(size);
131     if (new_framebuffer == NULL) {
132     fprintf(stderr, "dev_fb_resize(): out of memory\n");
133     exit(1);
134     }
135    
136     /* Copy the old framebuffer to the new: */
137     if (d->framebuffer != NULL) {
138     for (y=0; y<new_ysize; y++) {
139     size_t fromofs = d->bytes_per_line * y;
140     size_t toofs = new_bytes_per_line * y;
141     size_t len_to_copy = d->bytes_per_line <
142     new_bytes_per_line? d->bytes_per_line
143     : new_bytes_per_line;
144     memset(new_framebuffer + toofs, 0, new_bytes_per_line);
145     if (y < d->x11_ysize)
146     memmove(new_framebuffer + toofs,
147     d->framebuffer + fromofs, len_to_copy);
148     }
149    
150     free(d->framebuffer);
151     }
152    
153     d->framebuffer = new_framebuffer;
154     d->framebuffer_size = size;
155    
156     if (new_xsize > d->x11_xsize || new_ysize > d->x11_ysize) {
157     d->update_x1 = d->update_y1 = 0;
158     d->update_x2 = new_xsize - 1;
159     d->update_y2 = new_ysize - 1;
160     }
161    
162     d->bytes_per_line = new_bytes_per_line;
163     d->x11_xsize = d->visible_xsize = new_xsize;
164     d->x11_ysize = d->visible_ysize = new_ysize;
165    
166     #ifdef WITH_X11
167     if (d->fb_window != NULL)
168     x11_fb_resize(d->fb_window, new_xsize, new_ysize);
169     #endif
170     }
171    
172    
173     /*
174 dpavlin 4 * dev_fb_setcursor():
175     */
176     void dev_fb_setcursor(struct vfb_data *d, int cursor_x, int cursor_y, int on,
177     int cursor_xsize, int cursor_ysize)
178     {
179     if (cursor_x < 0)
180     cursor_x = 0;
181     if (cursor_y < 0)
182     cursor_y = 0;
183     if (cursor_x + cursor_xsize >= d->xsize)
184     cursor_x = d->xsize - cursor_xsize;
185     if (cursor_y + cursor_ysize >= d->ysize)
186     cursor_y = d->ysize - cursor_ysize;
187    
188     #ifdef WITH_X11
189     if (d->fb_window != NULL) {
190     d->fb_window->cursor_x = cursor_x;
191     d->fb_window->cursor_y = cursor_y;
192     d->fb_window->cursor_on = on;
193     d->fb_window->cursor_xsize = cursor_xsize;
194     d->fb_window->cursor_ysize = cursor_ysize;
195     }
196     #endif
197    
198     if (d->fb_window != NULL)
199     console_set_framebuffer_mouse(cursor_x, cursor_y,
200     d->fb_window->fb_number);
201    
202     /* debug("dev_fb_setcursor(%i,%i, size %i,%i, on=%i)\n",
203     cursor_x, cursor_y, cursor_xsize, cursor_ysize, on); */
204     }
205    
206    
207     /*
208     * framebuffer_blockcopyfill():
209     *
210     * This function should be used by devices that are capable of doing
211     * block copy/fill.
212     *
213     * If fillflag is non-zero, then fill_[rgb] should contain the color
214     * with which to fill.
215     *
216     * If fillflag is zero, copy mode is used, and from_[xy] should contain
217     * the offset on the framebuffer where we should copy from.
218     *
219     * NOTE: Overlapping copies are undefined!
220     */
221     void framebuffer_blockcopyfill(struct vfb_data *d, int fillflag, int fill_r,
222     int fill_g, int fill_b, int x1, int y1, int x2, int y2,
223     int from_x, int from_y)
224     {
225     int y;
226     long from_ofs, dest_ofs, linelen;
227    
228     if (fillflag)
229     debug("framebuffer_blockcopyfill(FILL, %i,%i, %i,%i, "
230     "color %i,%i,%i)\n", x1,y1, x2,y2, fill_r, fill_g, fill_b);
231     else
232     debug("framebuffer_blockcopyfill(COPY, %i,%i, %i,%i, from "
233     "%i,%i)\n", x1,y1, x2,y2, from_x,from_y);
234    
235     /* Clip x: */
236     if (x1 < 0) x1 = 0;
237     if (x1 >= d->xsize) x1 = d->xsize-1;
238     if (x2 < 0) x2 = 0;
239     if (x2 >= d->xsize) x2 = d->xsize-1;
240    
241     dest_ofs = d->bytes_per_line * y1 + (d->bit_depth/8) * x1;
242     linelen = (x2-x1 + 1) * (d->bit_depth/8);
243     /* NOTE: linelen is nr of bytes, not pixels */
244    
245     if (fillflag) {
246     for (y=y1; y<=y2; y++) {
247     if (y>=0 && y<d->ysize) {
248     int x;
249     char buf[8192 * 3];
250     if (d->bit_depth == 24)
251     for (x=0; x<linelen; x+=3) {
252     buf[x] = fill_r;
253     buf[x+1] = fill_g;
254     buf[x+2] = fill_b;
255     }
256     else
257     printf("TODO: fill for non-24-bit"
258     " modes\n");
259    
260     memmove(d->framebuffer + dest_ofs, buf,
261     linelen);
262     }
263    
264     dest_ofs += d->bytes_per_line;
265     }
266     } else {
267     from_ofs = d->bytes_per_line * from_y +
268     (d->bit_depth/8) * from_x;
269    
270     for (y=y1; y<=y2; y++) {
271     if (y>=0 && y<d->ysize)
272     memmove(d->framebuffer + dest_ofs,
273     d->framebuffer + from_ofs, linelen);
274    
275     from_ofs += d->bytes_per_line;
276     dest_ofs += d->bytes_per_line;
277     }
278     }
279    
280     if (x1 < d->update_x1 || d->update_x1 == -1) d->update_x1 = x1;
281     if (x1 > d->update_x2 || d->update_x2 == -1) d->update_x2 = x1;
282     if (x2 < d->update_x1 || d->update_x1 == -1) d->update_x1 = x2;
283     if (x2 > d->update_x2 || d->update_x2 == -1) d->update_x2 = x2;
284    
285     if (y1 < d->update_y1 || d->update_y1 == -1) d->update_y1 = y1;
286     if (y1 > d->update_y2 || d->update_y2 == -1) d->update_y2 = y1;
287     if (y2 < d->update_y1 || d->update_y1 == -1) d->update_y1 = y2;
288     if (y2 > d->update_y2 || d->update_y2 == -1) d->update_y2 = y2;
289     }
290    
291    
292     #ifdef WITH_X11
293     #define macro_put_pixel() { \
294     /* Combine the color into an X11 long and display it: */ \
295     /* TODO: construct color in a more portable way: */ \
296     switch (d->fb_window->x11_screen_depth) { \
297     case 24: \
298     if (d->fb_window->fb_ximage->byte_order) \
299     color = (b << 16) + (g << 8) + r; \
300     else \
301     color = (r << 16) + (g << 8) + b; \
302     break; \
303     case 16: \
304     r >>= 3; g >>= 2; b >>= 3; \
305     if (d->fb_window->fb_ximage->byte_order) { \
306     /* Big endian 16-bit X server: */ \
307     static int first = 1; \
308     if (first) { \
309     fprintf(stderr, "\n*** Please report to the author whether 16-bit X11 colors are rendered correctly or not!\n\n"); \
310     first = 0; \
311     } \
312     color = (b << 11) + (g << 5) + r; \
313     } else { \
314     /* Little endian (eg PC) X servers: */ \
315     color = (r << 11) + (g << 5) + b; \
316     } \
317     break; \
318     case 15: \
319     r >>= 3; g >>= 3; b >>= 3; \
320     if (d->fb_window->fb_ximage->byte_order) { \
321     /* Big endian 15-bit X server: */ \
322     static int first = 1; \
323     if (first) { \
324     fprintf(stderr, "\n*** Please report to the author whether 15-bit X11 colors are rendered correctly or not!\n\n"); \
325     first = 0; \
326     } \
327     color = (b << 10) + (g << 5) + r; \
328     } else { \
329     /* Little endian (eg PC) X servers: */ \
330     color = (r << 10) + (g << 5) + b; \
331     } \
332     break; \
333     default: \
334     color = d->fb_window->x11_graycolor[15 * (r + g + b) / (255 * 3)].pixel; \
335     } \
336     if (x>=0 && x<d->x11_xsize && y>=0 && y<d->x11_ysize) \
337     XPutPixel(d->fb_window->fb_ximage, x, y, color); \
338     }
339     #else
340     /* If not WITH_X11: */
341     #define macro_put_pixel() { }
342     #endif
343    
344    
345     /*
346     * update_framebuffer():
347     *
348     * The framebuffer memory has been updated. This function tries to make
349     * sure that the XImage is also updated (1 or more pixels).
350     */
351     void update_framebuffer(struct vfb_data *d, int addr, int len)
352     {
353     int x, y, pixel, npixels;
354     long color_r, color_g, color_b;
355     #ifdef WITH_X11
356     long color;
357     #endif
358     int scaledown = d->vfb_scaledown;
359     int scaledownXscaledown = 1;
360    
361     if (scaledown == 1) {
362     /* Which framebuffer pixel does addr correspond to? */
363     pixel = addr * 8 / d->bit_depth;
364     y = pixel / d->xsize;
365     x = pixel % d->xsize;
366    
367     /* How many framebuffer pixels? */
368     npixels = len * 8 / d->bit_depth;
369     if (npixels == 0)
370     npixels = 1;
371    
372     if (d->bit_depth < 8) {
373     for (pixel=0; pixel<npixels; pixel++) {
374     int fb_addr, c, r, g, b;
375     color_r = color_g = color_b = 0;
376    
377     fb_addr = (y * d->xsize + x) * d->bit_depth;
378     /* fb_addr is now which _bit_ in
379     the framebuffer */
380    
381     c = d->framebuffer[fb_addr >> 3];
382     fb_addr &= 7;
383    
384     /* HPCmips is reverse: */
385     if (d->vfb_type == VFB_HPCMIPS)
386     fb_addr = 8 - d->bit_depth - fb_addr;
387    
388     c = (c >> fb_addr) & ((1<<d->bit_depth) - 1);
389     /* c <<= (8 - d->bit_depth); */
390    
391     r = d->rgb_palette[c*3 + 0];
392     g = d->rgb_palette[c*3 + 1];
393     b = d->rgb_palette[c*3 + 2];
394    
395     macro_put_pixel();
396     x++;
397     }
398     } else if (d->bit_depth == 8) {
399     for (pixel=0; pixel<npixels; pixel++) {
400     int fb_addr, c, r, g, b;
401     color_r = color_g = color_b = 0;
402    
403     fb_addr = y * d->xsize + x;
404     /* fb_addr is now which byte in framebuffer */
405     c = d->framebuffer[fb_addr];
406     r = d->rgb_palette[c*3 + 0];
407     g = d->rgb_palette[c*3 + 1];
408     b = d->rgb_palette[c*3 + 2];
409    
410     macro_put_pixel();
411     x++;
412     }
413     } else { /* d->bit_depth > 8 */
414     for (pixel=0; pixel<npixels; pixel++) {
415     int fb_addr, r, g, b;
416     color_r = color_g = color_b = 0;
417    
418     fb_addr = (y * d->xsize + x) * d->bit_depth;
419     /* fb_addr is now which byte in framebuffer */
420    
421     /* > 8 bits color. */
422     fb_addr >>= 3;
423     switch (d->bit_depth) {
424     case 24:
425     r = d->framebuffer[fb_addr];
426     g = d->framebuffer[fb_addr + 1];
427     b = d->framebuffer[fb_addr + 2];
428     break;
429     /* TODO: copy to the scaledown code below */
430     case 16:
431     if (d->vfb_type == VFB_HPCMIPS) {
432     b = d->framebuffer[fb_addr] +
433     (d->framebuffer[fb_addr+1] << 8);
434    
435     if (d->color32k) {
436     r = b >> 11;
437     g = b >> 5;
438     r = r & 31;
439     g = (g & 31) * 2;
440     b = b & 31;
441 dpavlin 10 } else if (d->psp_15bit) {
442     int tmp;
443     r = (b >> 10) & 0x1f;
444     g = (b >> 5) & 0x1f;
445     b = b & 0x1f;
446     g <<= 1;
447     tmp = r; r = b; b = tmp;
448 dpavlin 4 } else {
449     r = (b >> 11) & 0x1f;
450     g = (b >> 5) & 0x3f;
451     b = b & 0x1f;
452     }
453     } else {
454     r = d->framebuffer[fb_addr] >> 3;
455     g = (d->framebuffer[fb_addr] << 5) +
456     (d->framebuffer[fb_addr + 1] >> 5);
457     b = d->framebuffer[fb_addr + 1] & 0x1f;
458     }
459    
460     r *= 8;
461     g *= 4;
462     b *= 8;
463     break;
464     default:
465     r = g = b = random() & 255;
466     }
467    
468     macro_put_pixel();
469     x++;
470     }
471     }
472    
473     return;
474     }
475    
476 dpavlin 12 /* scaledown > 1: */
477 dpavlin 4
478     scaledown = d->vfb_scaledown;
479     scaledownXscaledown = scaledown * scaledown;
480    
481     /* Which framebuffer pixel does addr correspond to? */
482     pixel = addr * 8 / d->bit_depth;
483     y = pixel / d->xsize;
484     x = pixel % d->xsize;
485    
486     /* How many framebuffer pixels? */
487     npixels = len * 8 / d->bit_depth;
488    
489     /* Which x11 pixel? */
490     x /= scaledown;
491     y /= scaledown;
492    
493     /* How many x11 pixels: */
494     npixels /= scaledown;
495     if (npixels == 0)
496     npixels = 1;
497    
498     if (d->bit_depth < 8) {
499     for (pixel=0; pixel<npixels; pixel++) {
500     int subx, suby, r, g, b;
501     color_r = color_g = color_b = 0;
502     for (suby=0; suby<scaledown; suby++)
503     for (subx=0; subx<scaledown; subx++) {
504     int fb_x, fb_y, fb_addr, c;
505    
506     fb_x = x * scaledown + subx;
507     fb_y = y * scaledown + suby;
508     fb_addr = fb_y * d->xsize + fb_x;
509     fb_addr = fb_addr * d->bit_depth;
510     /* fb_addr is now which _bit_ in
511     the framebuffer */
512    
513     c = d->framebuffer[fb_addr >> 3];
514     fb_addr &= 7;
515    
516     /* HPCmips is reverse: */
517     if (d->vfb_type == VFB_HPCMIPS)
518     fb_addr = 8 - d->bit_depth - fb_addr;
519    
520     c = (c >> fb_addr) & ((1<<d->bit_depth) - 1);
521     /* c <<= (8 - d->bit_depth); */
522    
523     r = d->rgb_palette[c*3 + 0];
524     g = d->rgb_palette[c*3 + 1];
525     b = d->rgb_palette[c*3 + 2];
526    
527     color_r += r;
528     color_g += g;
529     color_b += b;
530     }
531    
532     r = color_r / scaledownXscaledown;
533     g = color_g / scaledownXscaledown;
534     b = color_b / scaledownXscaledown;
535     macro_put_pixel();
536     x++;
537     }
538     } else if (d->bit_depth == 8) {
539     for (pixel=0; pixel<npixels; pixel++) {
540     int subx, suby, r, g, b;
541     color_r = color_g = color_b = 0;
542     for (suby=0; suby<scaledown; suby++)
543     for (subx=0; subx<scaledown; subx++) {
544     int fb_x, fb_y, fb_addr, c;
545    
546     fb_x = x * scaledown + subx;
547     fb_y = y * scaledown + suby;
548     fb_addr = fb_y * d->xsize + fb_x;
549     /* fb_addr is which _byte_ in framebuffer */
550     c = d->framebuffer[fb_addr] * 3;
551     r = d->rgb_palette[c + 0];
552     g = d->rgb_palette[c + 1];
553     b = d->rgb_palette[c + 2];
554     color_r += r;
555     color_g += g;
556     color_b += b;
557     }
558    
559     r = color_r / scaledownXscaledown;
560     g = color_g / scaledownXscaledown;
561     b = color_b / scaledownXscaledown;
562     macro_put_pixel();
563     x++;
564     }
565     } else {
566     /* Generic > 8 bit bit-depth: */
567     for (pixel=0; pixel<npixels; pixel++) {
568     int subx, suby, r, g, b;
569     color_r = color_g = color_b = 0;
570     for (suby=0; suby<scaledown; suby++)
571     for (subx=0; subx<scaledown; subx++) {
572     int fb_x, fb_y, fb_addr;
573    
574     fb_x = x * scaledown + subx;
575     fb_y = y * scaledown + suby;
576     fb_addr = fb_y * d->xsize + fb_x;
577     fb_addr = (fb_addr * d->bit_depth) >> 3;
578     /* fb_addr is which _byte_ in framebuffer */
579    
580     /* > 8 bits color. */
581     switch (d->bit_depth) {
582     case 24:
583     r = d->framebuffer[fb_addr];
584     g = d->framebuffer[fb_addr + 1];
585     b = d->framebuffer[fb_addr + 2];
586     break;
587     default:
588     r = g = b = random() & 255;
589     }
590     color_r += r;
591     color_g += g;
592     color_b += b;
593     }
594     r = color_r / scaledownXscaledown;
595     g = color_g / scaledownXscaledown;
596     b = color_b / scaledownXscaledown;
597     macro_put_pixel();
598     x++;
599     }
600     }
601     }
602    
603    
604     /*
605     * dev_fb_tick():
606     *
607     */
608     void dev_fb_tick(struct cpu *cpu, void *extra)
609     {
610     struct vfb_data *d = extra;
611     #ifdef WITH_X11
612     int need_to_flush_x11 = 0;
613     int need_to_redraw_cursor = 0;
614     #endif
615    
616     if (!cpu->machine->use_x11)
617     return;
618    
619     do {
620 dpavlin 12 uint64_t high, low = (uint64_t)(int64_t) -1;
621 dpavlin 4 int x, y;
622    
623 dpavlin 12 memory_device_dyntrans_access(cpu, cpu->mem,
624 dpavlin 4 extra, &low, &high);
625     if ((int64_t)low == -1)
626     break;
627    
628     /* printf("low=%016llx high=%016llx\n",
629     (long long)low, (long long)high); */
630    
631     x = (low % d->bytes_per_line) * 8 / d->bit_depth;
632     y = low / d->bytes_per_line;
633     if (x < d->update_x1 || d->update_x1 == -1)
634     d->update_x1 = x;
635     if (x > d->update_x2 || d->update_x2 == -1)
636     d->update_x2 = x;
637     if (y < d->update_y1 || d->update_y1 == -1)
638     d->update_y1 = y;
639     if (y > d->update_y2 || d->update_y2 == -1)
640     d->update_y2 = y;
641    
642     x = ((low+7) % d->bytes_per_line) * 8 / d->bit_depth;
643     y = (low+7) / d->bytes_per_line;
644     if (x < d->update_x1 || d->update_x1 == -1)
645     d->update_x1 = x;
646     if (x > d->update_x2 || d->update_x2 == -1)
647     d->update_x2 = x;
648     if (y < d->update_y1 || d->update_y1 == -1)
649     d->update_y1 = y;
650     if (y > d->update_y2 || d->update_y2 == -1)
651     d->update_y2 = y;
652    
653     x = (high % d->bytes_per_line) * 8 / d->bit_depth;
654     y = high / d->bytes_per_line;
655     if (x < d->update_x1 || d->update_x1 == -1)
656     d->update_x1 = x;
657     if (x > d->update_x2 || d->update_x2 == -1)
658     d->update_x2 = x;
659     if (y < d->update_y1 || d->update_y1 == -1)
660     d->update_y1 = y;
661     if (y > d->update_y2 || d->update_y2 == -1)
662     d->update_y2 = y;
663    
664     x = ((high+7) % d->bytes_per_line) * 8 / d->bit_depth;
665     y = (high+7) / d->bytes_per_line;
666     if (x < d->update_x1 || d->update_x1 == -1)
667     d->update_x1 = x;
668     if (x > d->update_x2 || d->update_x2 == -1)
669     d->update_x2 = x;
670     if (y < d->update_y1 || d->update_y1 == -1)
671     d->update_y1 = y;
672     if (y > d->update_y2 || d->update_y2 == -1)
673     d->update_y2 = y;
674    
675     /*
676     * An update covering more than one line will automatically
677     * force an update of all the affected lines:
678     */
679     if (d->update_y1 != d->update_y2) {
680     d->update_x1 = 0;
681     d->update_x2 = d->xsize-1;
682     }
683     } while (0);
684    
685     #ifdef WITH_X11
686     /* Do we need to redraw the cursor? */
687     if (d->fb_window->cursor_on != d->fb_window->OLD_cursor_on ||
688     d->fb_window->cursor_x != d->fb_window->OLD_cursor_x ||
689     d->fb_window->cursor_y != d->fb_window->OLD_cursor_y ||
690     d->fb_window->cursor_xsize != d->fb_window->OLD_cursor_xsize ||
691     d->fb_window->cursor_ysize != d->fb_window->OLD_cursor_ysize)
692     need_to_redraw_cursor = 1;
693    
694     if (d->update_x2 != -1) {
695     if ( (d->update_x1 >= d->fb_window->OLD_cursor_x &&
696     d->update_x1 < (d->fb_window->OLD_cursor_x + d->fb_window->OLD_cursor_xsize)) ||
697     (d->update_x2 >= d->fb_window->OLD_cursor_x &&
698     d->update_x2 < (d->fb_window->OLD_cursor_x + d->fb_window->OLD_cursor_xsize)) ||
699     (d->update_x1 < d->fb_window->OLD_cursor_x &&
700     d->update_x2 >= (d->fb_window->OLD_cursor_x + d->fb_window->OLD_cursor_xsize)) ) {
701     if ( (d->update_y1 >= d->fb_window->OLD_cursor_y &&
702     d->update_y1 < (d->fb_window->OLD_cursor_y + d->fb_window->OLD_cursor_ysize)) ||
703     (d->update_y2 >= d->fb_window->OLD_cursor_y &&
704     d->update_y2 < (d->fb_window->OLD_cursor_y + d->fb_window->OLD_cursor_ysize)) ||
705     (d->update_y1 < d->fb_window->OLD_cursor_y &&
706     d->update_y2 >= (d->fb_window->OLD_cursor_y + d->fb_window->OLD_cursor_ysize)) )
707     need_to_redraw_cursor = 1;
708     }
709     }
710    
711     if (need_to_redraw_cursor) {
712     /* Remove old cursor, if any: */
713     if (d->fb_window->OLD_cursor_on) {
714     XPutImage(d->fb_window->x11_display,
715     d->fb_window->x11_fb_window,
716     d->fb_window->x11_fb_gc, d->fb_window->fb_ximage,
717     d->fb_window->OLD_cursor_x/d->vfb_scaledown,
718     d->fb_window->OLD_cursor_y/d->vfb_scaledown,
719     d->fb_window->OLD_cursor_x/d->vfb_scaledown,
720     d->fb_window->OLD_cursor_y/d->vfb_scaledown,
721     d->fb_window->OLD_cursor_xsize/d->vfb_scaledown + 1,
722     d->fb_window->OLD_cursor_ysize/d->vfb_scaledown + 1);
723     }
724     }
725     #endif
726    
727     if (d->update_x2 != -1) {
728     int y, addr, addr2, q = d->vfb_scaledown;
729    
730     if (d->update_x1 >= d->visible_xsize) d->update_x1 = d->visible_xsize - 1;
731     if (d->update_x2 >= d->visible_xsize) d->update_x2 = d->visible_xsize - 1;
732     if (d->update_y1 >= d->visible_ysize) d->update_y1 = d->visible_ysize - 1;
733     if (d->update_y2 >= d->visible_ysize) d->update_y2 = d->visible_ysize - 1;
734    
735     /* Without these, we might miss the right most / bottom pixel: */
736     d->update_x2 += (q - 1);
737     d->update_y2 += (q - 1);
738    
739     d->update_x1 = d->update_x1 / q * q;
740     d->update_x2 = d->update_x2 / q * q;
741     d->update_y1 = d->update_y1 / q * q;
742     d->update_y2 = d->update_y2 / q * q;
743    
744     addr = d->update_y1 * d->bytes_per_line + d->update_x1 * d->bit_depth / 8;
745     addr2 = d->update_y1 * d->bytes_per_line + d->update_x2 * d->bit_depth / 8;
746    
747     for (y=d->update_y1; y<=d->update_y2; y+=q) {
748     update_framebuffer(d, addr, addr2 - addr);
749     addr += d->bytes_per_line * q;
750     addr2 += d->bytes_per_line * q;
751     }
752    
753     #ifdef WITH_X11
754     XPutImage(d->fb_window->x11_display, d->fb_window->x11_fb_window, d->fb_window->x11_fb_gc, d->fb_window->fb_ximage,
755     d->update_x1/d->vfb_scaledown, d->update_y1/d->vfb_scaledown,
756     d->update_x1/d->vfb_scaledown, d->update_y1/d->vfb_scaledown,
757     (d->update_x2 - d->update_x1)/d->vfb_scaledown + 1,
758     (d->update_y2 - d->update_y1)/d->vfb_scaledown + 1);
759    
760     need_to_flush_x11 = 1;
761     #endif
762    
763     d->update_x1 = d->update_y1 = 99999;
764     d->update_x2 = d->update_y2 = -1;
765     }
766    
767     #ifdef WITH_X11
768     if (need_to_redraw_cursor) {
769     /* Paint new cursor: */
770     if (d->fb_window->cursor_on) {
771     x11_redraw_cursor(cpu->machine, d->fb_window->fb_number);
772     d->fb_window->OLD_cursor_on = d->fb_window->cursor_on;
773     d->fb_window->OLD_cursor_x = d->fb_window->cursor_x;
774     d->fb_window->OLD_cursor_y = d->fb_window->cursor_y;
775     d->fb_window->OLD_cursor_xsize = d->fb_window->cursor_xsize;
776     d->fb_window->OLD_cursor_ysize = d->fb_window->cursor_ysize;
777     }
778     }
779     #endif
780    
781     #ifdef WITH_X11
782     if (need_to_flush_x11)
783     XFlush(d->fb_window->x11_display);
784     #endif
785     }
786    
787    
788     /*
789     * dev_fb_access():
790     */
791     int dev_fb_access(struct cpu *cpu, struct memory *mem,
792     uint64_t relative_addr, unsigned char *data, size_t len,
793     int writeflag, void *extra)
794     {
795     struct vfb_data *d = extra;
796     int i;
797    
798     #ifdef FB_DEBUG
799     if (writeflag == MEM_WRITE) { if (data[0]) {
800     fatal("[ dev_fb: write to addr=%08lx, data = ",
801     (long)relative_addr);
802     for (i=0; i<len; i++)
803     fatal("%02x ", data[i]);
804     fatal("]\n");
805     } else {
806     fatal("[ dev_fb: read from addr=%08lx, data = ",
807     (long)relative_addr);
808     for (i=0; i<len; i++)
809     fatal("%02x ", d->framebuffer[relative_addr + i]);
810     fatal("]\n");
811     }
812     #endif
813    
814 dpavlin 6 if (relative_addr >= d->framebuffer_size)
815     return 0;
816    
817 dpavlin 4 /* See if a write actually modifies the framebuffer contents: */
818     if (writeflag == MEM_WRITE) {
819     for (i=0; i<len; i++) {
820     if (data[i] != d->framebuffer[relative_addr + i])
821     break;
822    
823     /* If all bytes are equal to what is already stored
824     in the framebuffer, then simply return: */
825     if (i==len-1)
826     return 1;
827     }
828     }
829    
830     /*
831     * If the framebuffer is modified, then we should keep a track
832     * of which area(s) we modify, so that the display isn't updated
833     * unnecessarily.
834     */
835     if (writeflag == MEM_WRITE && cpu->machine->use_x11) {
836     int x, y, x2,y2;
837    
838     x = (relative_addr % d->bytes_per_line) * 8 / d->bit_depth;
839     y = relative_addr / d->bytes_per_line;
840     x2 = ((relative_addr + len) % d->bytes_per_line)
841     * 8 / d->bit_depth;
842     y2 = (relative_addr + len) / d->bytes_per_line;
843    
844     if (x < d->update_x1 || d->update_x1 == -1)
845     d->update_x1 = x;
846     if (x > d->update_x2 || d->update_x2 == -1)
847     d->update_x2 = x;
848    
849     if (y < d->update_y1 || d->update_y1 == -1)
850     d->update_y1 = y;
851     if (y > d->update_y2 || d->update_y2 == -1)
852     d->update_y2 = y;
853    
854     if (x2 < d->update_x1 || d->update_x1 == -1)
855     d->update_x1 = x2;
856     if (x2 > d->update_x2 || d->update_x2 == -1)
857     d->update_x2 = x2;
858    
859     if (y2 < d->update_y1 || d->update_y1 == -1)
860     d->update_y1 = y2;
861     if (y2 > d->update_y2 || d->update_y2 == -1)
862     d->update_y2 = y2;
863    
864     /*
865     * An update covering more than one line will automatically
866     * force an update of all the affected lines:
867     */
868     if (y != y2) {
869     d->update_x1 = 0;
870     d->update_x2 = d->xsize-1;
871     }
872     }
873    
874     /*
875     * Read from/write to the framebuffer:
876     * (TODO: take the color_plane_mask into account)
877     *
878     * Calling memcpy() is probably overkill, as it usually is just one
879     * or a few bytes that are read/written at a time.
880     */
881     if (writeflag == MEM_WRITE) {
882     if (len > 8)
883     memcpy(d->framebuffer + relative_addr, data, len);
884     else
885     for (i=0; i<len; i++)
886     d->framebuffer[relative_addr + i] = data[i];
887     } else {
888     if (len > 8)
889     memcpy(data, d->framebuffer + relative_addr, len);
890     else
891     for (i=0; i<len; i++)
892     data[i] = d->framebuffer[relative_addr + i];
893     }
894    
895     return 1;
896     }
897    
898    
899     /*
900     * dev_fb_init():
901     *
902 dpavlin 10 * This function is big and ugly, but the point is to initialize a framebuffer
903     * device. :-)
904     *
905     * visible_xsize and visible_ysize are the sizes of the visible display area.
906     * xsize and ysize tell how much memory is actually allocated (for example
907     * visible_xsize could be 640, but xsize could be 1024, for better alignment).
908     *
909     * vfb_type is useful for selecting special features.
910     *
911     * type = VFB_GENERIC is the most useful type, especially when bit_depth = 24.
912     *
913     * VFB_DEC_VFB01, _VFB02, and VFB_DEC_MAXINE are DECstation specific.
914     *
915     * If type is VFB_HPCMIPS, then color encoding differs from the generic case.
916     *
917     * If bit_depth = -15 (note the minus sign), then a special hack is used for
918     * the Playstation Portable's 5-bit R, 5-bit G, 5-bit B.
919 dpavlin 4 */
920     struct vfb_data *dev_fb_init(struct machine *machine, struct memory *mem,
921     uint64_t baseaddr, int vfb_type, int visible_xsize, int visible_ysize,
922 dpavlin 12 int xsize, int ysize, int bit_depth, char *name)
923 dpavlin 4 {
924     struct vfb_data *d;
925 dpavlin 10 size_t size, nlen;
926 dpavlin 12 int flags;
927 dpavlin 4 char title[400];
928     char *name2;
929    
930     d = malloc(sizeof(struct vfb_data));
931     if (d == NULL) {
932     fprintf(stderr, "out of memory\n");
933     exit(1);
934     }
935     memset(d, 0, sizeof(struct vfb_data));
936    
937     d->vfb_type = vfb_type;
938    
939     /* Defaults: */
940     d->xsize = xsize; d->visible_xsize = visible_xsize;
941     d->ysize = ysize; d->visible_ysize = visible_ysize;
942    
943     d->bit_depth = bit_depth;
944    
945     if (bit_depth == 15) {
946     d->color32k = 1;
947     bit_depth = d->bit_depth = 16;
948 dpavlin 10 } else if (bit_depth == -15) {
949     d->psp_15bit = 1;
950     bit_depth = d->bit_depth = 16;
951 dpavlin 4 }
952    
953     /* Specific types: */
954     switch (vfb_type) {
955     case VFB_DEC_VFB01:
956     /* DECstation VFB01 (monochrome) */
957     d->xsize = 2048; d->visible_xsize = 1024;
958     d->ysize = 1024; d->visible_ysize = 864;
959     d->bit_depth = 1;
960     break;
961     case VFB_DEC_VFB02:
962     /* DECstation VFB02 (color) */
963     d->xsize = 1024; d->visible_xsize = 1024;
964     d->ysize = 1024; d->visible_ysize = 864;
965     d->bit_depth = 8;
966     break;
967     case VFB_DEC_MAXINE:
968     /* DECstation Maxine (1024x768x8) */
969     d->xsize = 1024; d->visible_xsize = d->xsize;
970     d->ysize = 768; d->visible_ysize = d->ysize;
971     d->bit_depth = 8;
972     break;
973     case VFB_PLAYSTATION2:
974     /* Playstation 2 */
975     d->xsize = xsize; d->visible_xsize = d->xsize;
976     d->ysize = ysize; d->visible_ysize = d->ysize;
977     d->bit_depth = 24;
978     break;
979     default:
980     ;
981     }
982    
983     if (d->bit_depth == 2 || d->bit_depth == 4)
984     set_grayscale_palette(d, 1 << d->bit_depth);
985     else if (d->bit_depth == 8 || d->bit_depth == 1)
986     set_blackwhite_palette(d, 1 << d->bit_depth);
987    
988     d->vfb_scaledown = machine->x11_scaledown;
989    
990     d->bytes_per_line = d->xsize * d->bit_depth / 8;
991     size = d->ysize * d->bytes_per_line;
992    
993     d->framebuffer = malloc(size);
994     if (d->framebuffer == NULL) {
995     fprintf(stderr, "out of memory\n");
996     exit(1);
997     }
998    
999     /* Clear the framebuffer (all black pixels): */
1000     d->framebuffer_size = size;
1001     memset(d->framebuffer, 0, size);
1002    
1003     d->x11_xsize = d->visible_xsize / d->vfb_scaledown;
1004     d->x11_ysize = d->visible_ysize / d->vfb_scaledown;
1005    
1006     d->update_x1 = d->update_y1 = 99999;
1007     d->update_x2 = d->update_y2 = -1;
1008    
1009    
1010 dpavlin 6 /* Don't set the title to include the size of the framebuffer for
1011     VGA, since then the resolution might change during runtime. */
1012     if (strcmp(name, "VGA") == 0)
1013     snprintf(title, sizeof(title),"GXemul: %s framebuffer", name);
1014     else
1015     snprintf(title, sizeof(title),"GXemul: %ix%ix%i %s framebuffer",
1016     d->visible_xsize, d->visible_ysize, d->bit_depth, name);
1017 dpavlin 4 title[sizeof(title)-1] = '\0';
1018    
1019     #ifdef WITH_X11
1020     if (machine->use_x11)
1021     d->fb_window = x11_fb_init(d->x11_xsize, d->x11_ysize,
1022     title, machine->x11_scaledown, machine);
1023     else
1024     #endif
1025     d->fb_window = NULL;
1026    
1027 dpavlin 10 nlen = strlen(name) + 10;
1028     name2 = malloc(nlen);
1029 dpavlin 4 if (name2 == NULL) {
1030     fprintf(stderr, "out of memory in dev_fb_init()\n");
1031     exit(1);
1032     }
1033 dpavlin 10 snprintf(name2, nlen, "fb [%s]", name);
1034 dpavlin 4
1035     flags = MEM_DEFAULT;
1036     if ((baseaddr & 0xfff) == 0)
1037 dpavlin 12 flags = MEM_DYNTRANS_OK | MEM_DYNTRANS_WRITE_OK;
1038 dpavlin 4
1039 dpavlin 6 flags |= MEM_READING_HAS_NO_SIDE_EFFECTS;
1040    
1041 dpavlin 4 memory_device_register(mem, name2, baseaddr, size, dev_fb_access,
1042     d, flags, d->framebuffer);
1043    
1044     machine_add_tickfunction(machine, dev_fb_tick, d, FB_TICK_SHIFT);
1045     return d;
1046     }
1047    

  ViewVC Help
Powered by ViewVC 1.1.26