Use DrawFrameControl instead of bitmaps in certain cases.
[wine] / dlls / winedos / vga.c
1 /*
2  * VGA hardware emulation
3  * 
4  * Copyright 1998 Ove Kåven (with some help from Marcus Meissner)
5  *
6  */
7
8 #include <string.h>
9 #include "winbase.h"
10 #include "wingdi.h"
11 #include "winuser.h"
12 #include "wincon.h"
13 #include "miscemu.h"
14 #include "dosexe.h"
15 #include "vga.h"
16 #include "ddraw.h"
17 #include "services.h"
18 #include "debugtools.h"
19
20 DEFAULT_DEBUG_CHANNEL(ddraw);
21
22 static IDirectDraw *lpddraw = NULL;
23 static IDirectDrawSurface *lpddsurf;
24 static IDirectDrawPalette *lpddpal;
25 static DDSURFACEDESC sdesc;
26 static LONG vga_polling,vga_refresh;
27 static HANDLE poll_timer;
28
29 typedef HRESULT WINAPI (*DirectDrawCreateProc)(LPGUID,LPDIRECTDRAW *,LPUNKNOWN);
30 static DirectDrawCreateProc pDirectDrawCreate;
31
32 static PALETTEENTRY vga_def_palette[256]={
33 /* red  green  blue */
34   {0x00, 0x00, 0x00}, /* 0 - Black */
35   {0x00, 0x00, 0x80}, /* 1 - Blue */
36   {0x00, 0x80, 0x00}, /* 2 - Green */
37   {0x00, 0x80, 0x80}, /* 3 - Cyan */
38   {0x80, 0x00, 0x00}, /* 4 - Red */
39   {0x80, 0x00, 0x80}, /* 5 - Magenta */
40   {0x80, 0x80, 0x00}, /* 6 - Brown */
41   {0xC0, 0xC0, 0xC0}, /* 7 - Light gray */
42   {0x80, 0x80, 0x80}, /* 8 - Dark gray */
43   {0x00, 0x00, 0xFF}, /* 9 - Light blue */
44   {0x00, 0xFF, 0x00}, /* A - Light green */
45   {0x00, 0xFF, 0xFF}, /* B - Light cyan */
46   {0xFF, 0x00, 0x00}, /* C - Light red */
47   {0xFF, 0x00, 0xFF}, /* D - Light magenta */
48   {0xFF, 0xFF, 0x00}, /* E - Yellow */
49   {0xFF, 0xFF, 0xFF}, /* F - White */
50   {0,0,0} /* FIXME: a series of continuous rainbow hues should follow */
51 };
52
53 static void VGA_DeinstallTimer(void)
54 {
55     if (poll_timer) {
56         SERVICE_Delete( poll_timer );
57         poll_timer = 0;
58     }
59 }
60
61 static void VGA_InstallTimer(unsigned Rate)
62 {
63     VGA_DeinstallTimer();
64     if (!poll_timer)
65         poll_timer = SERVICE_AddTimer( Rate, VGA_Poll, 0 );
66 }
67
68 HANDLE VGA_AlphaConsole(void)
69 {
70     /* this assumes that no Win32 redirection has taken place, but then again,
71      * only 16-bit apps are likely to use this part of Wine... */
72     return GetStdHandle(STD_OUTPUT_HANDLE);
73 }
74
75 char*VGA_AlphaBuffer(void)
76 {
77     return DOSMEM_MapDosToLinear(0xb8000);
78 }
79
80 /*** GRAPHICS MODE ***/
81
82 typedef struct {
83   unsigned Xres, Yres, Depth;
84   int ret;
85 } ModeSet;
86
87 static void WINAPI VGA_DoSetMode(ULONG_PTR arg)
88 {
89     LRESULT     res;
90     HWND        hwnd;
91     ModeSet *par = (ModeSet *)arg;
92     par->ret=1;
93
94     if (lpddraw) VGA_Exit();
95     if (!lpddraw) {
96         if (!pDirectDrawCreate)
97         {
98             HMODULE hmod = LoadLibraryA( "ddraw.dll" );
99             if (hmod) pDirectDrawCreate = (DirectDrawCreateProc)GetProcAddress( hmod, "DirectDrawCreate" );
100             if (!pDirectDrawCreate) {
101                 ERR("Can't lookup DirectDrawCreate from ddraw.dll.\n");
102                 return;
103             }
104         }
105         res = pDirectDrawCreate(NULL,&lpddraw,NULL);
106         if (!lpddraw) {
107             ERR("DirectDraw is not available (res = %lx)\n",res);
108             return;
109         }
110         hwnd = CreateWindowExA(0,"STATIC","WINEDOS VGA",WS_POPUP|WS_BORDER|WS_CAPTION|WS_SYSMENU,0,0,par->Xres,par->Yres,0,0,0,NULL);
111         if (!hwnd) {
112             ERR("Failed to create user window.\n");
113         }
114         if ((res=IDirectDraw_SetCooperativeLevel(lpddraw,hwnd,DDSCL_FULLSCREEN|DDSCL_EXCLUSIVE))) {
115             ERR("Could not set cooperative level to exclusive (%lx)\n",res);
116         }
117
118         if ((res=IDirectDraw_SetDisplayMode(lpddraw,par->Xres,par->Yres,par->Depth))) {
119             ERR("DirectDraw does not support requested display mode (%dx%dx%d), res = %lx!\n",par->Xres,par->Yres,par->Depth,res);
120             IDirectDraw_Release(lpddraw);
121             lpddraw=NULL;
122             return;
123         }
124
125         res=IDirectDraw_CreatePalette(lpddraw,DDPCAPS_8BIT,NULL,&lpddpal,NULL);
126         if (res) {
127             ERR("Could not create palette (res = %lx)\n",res);
128             IDirectDraw_Release(lpddraw);
129             lpddraw=NULL;
130             return;
131         }
132         if ((res=IDirectDrawPalette_SetEntries(lpddpal,0,0,256,vga_def_palette))) {
133             ERR("Could not set default palette entries (res = %lx)\n", res);
134         }
135
136         memset(&sdesc,0,sizeof(sdesc));
137         sdesc.dwSize=sizeof(sdesc);
138         sdesc.dwFlags = DDSD_CAPS;
139         sdesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
140         if (IDirectDraw_CreateSurface(lpddraw,&sdesc,&lpddsurf,NULL)||(!lpddsurf)) {
141             ERR("DirectDraw surface is not available\n");
142             IDirectDraw_Release(lpddraw);
143             lpddraw=NULL;
144             return;
145         }
146         IDirectDrawSurface_SetPalette(lpddsurf,lpddpal);
147         vga_refresh=0;
148         /* poll every 20ms (50fps should provide adequate responsiveness) */
149         VGA_InstallTimer(20);
150     }
151     par->ret=0;
152     return;
153 }
154
155 int VGA_SetMode(unsigned Xres,unsigned Yres,unsigned Depth)
156 {
157     ModeSet par;
158     par.Xres = Xres;
159     par.Yres = Yres;
160     par.Depth = Depth;
161     MZ_RunInThread(VGA_DoSetMode, (ULONG_PTR)&par);
162     return par.ret;
163 }
164
165 int VGA_GetMode(unsigned*Height,unsigned*Width,unsigned*Depth)
166 {
167     if (!lpddraw) return 1;
168     if (!lpddsurf) return 1;
169     if (Height) *Height=sdesc.dwHeight;
170     if (Width) *Width=sdesc.dwWidth;
171     if (Depth) *Depth=sdesc.ddpfPixelFormat.u1.dwRGBBitCount;
172     return 0;
173 }
174
175 static void WINAPI VGA_DoExit(ULONG_PTR arg)
176 {
177     VGA_DeinstallTimer();
178     IDirectDrawSurface_SetPalette(lpddsurf,NULL);
179     IDirectDrawSurface_Release(lpddsurf);
180     lpddsurf=NULL;
181     IDirectDrawPalette_Release(lpddpal);
182     lpddpal=NULL;
183     IDirectDraw_Release(lpddraw);
184     lpddraw=NULL;
185 }
186
187 void VGA_Exit(void)
188 {
189     if (lpddraw) MZ_RunInThread(VGA_DoExit, 0);
190 }
191
192 void VGA_SetPalette(PALETTEENTRY*pal,int start,int len)
193 {
194     if (!lpddraw) return;
195     IDirectDrawPalette_SetEntries(lpddpal,0,start,len,pal);
196 }
197
198 void VGA_SetQuadPalette(RGBQUAD*color,int start,int len)
199 {
200     PALETTEENTRY pal[256];
201     int c;
202
203     if (!lpddraw) return;
204     for (c=0; c<len; c++) {
205         pal[c].peRed  =color[c].rgbRed;
206         pal[c].peGreen=color[c].rgbGreen;
207         pal[c].peBlue =color[c].rgbBlue;
208         pal[c].peFlags=0;
209     }
210     IDirectDrawPalette_SetEntries(lpddpal,0,start,len,pal);
211 }
212
213 LPSTR VGA_Lock(unsigned*Pitch,unsigned*Height,unsigned*Width,unsigned*Depth)
214 {
215     if (!lpddraw) return NULL;
216     if (!lpddsurf) return NULL;
217     if (IDirectDrawSurface_Lock(lpddsurf,NULL,&sdesc,0,0)) {
218         ERR("could not lock surface!\n");
219         return NULL;
220     }
221     if (Pitch) *Pitch=sdesc.u1.lPitch;
222     if (Height) *Height=sdesc.dwHeight;
223     if (Width) *Width=sdesc.dwWidth;
224     if (Depth) *Depth=sdesc.ddpfPixelFormat.u1.dwRGBBitCount;
225     return sdesc.lpSurface;
226 }
227
228 void VGA_Unlock(void)
229 {
230     IDirectDrawSurface_Unlock(lpddsurf,sdesc.lpSurface);
231 }
232
233 /*** TEXT MODE ***/
234
235 int VGA_SetAlphaMode(unsigned Xres,unsigned Yres)
236 {
237     COORD siz;
238
239     if (lpddraw) VGA_Exit();
240
241     /* the xterm is slow, so refresh only every 200ms (5fps) */
242     VGA_InstallTimer(200);
243
244     siz.X = Xres;
245     siz.Y = Yres;
246     SetConsoleScreenBufferSize(VGA_AlphaConsole(),siz);
247     return 0;
248 }
249
250 void VGA_GetAlphaMode(unsigned*Xres,unsigned*Yres)
251 {
252     CONSOLE_SCREEN_BUFFER_INFO info;
253     GetConsoleScreenBufferInfo(VGA_AlphaConsole(),&info);
254     if (Xres) *Xres=info.dwSize.X;
255     if (Yres) *Yres=info.dwSize.Y;
256 }
257
258 void VGA_SetCursorPos(unsigned X,unsigned Y)
259 {
260     COORD pos;
261
262     if (!poll_timer) VGA_SetAlphaMode(80, 25);
263     pos.X = X;
264     pos.Y = Y;
265     SetConsoleCursorPosition(VGA_AlphaConsole(),pos);
266 }
267
268 void VGA_GetCursorPos(unsigned*X,unsigned*Y)
269 {
270     CONSOLE_SCREEN_BUFFER_INFO info;
271     GetConsoleScreenBufferInfo(VGA_AlphaConsole(),&info);
272     if (X) *X=info.dwCursorPosition.X;
273     if (Y) *Y=info.dwCursorPosition.Y;
274 }
275
276 void VGA_WriteChars(unsigned X,unsigned Y,unsigned ch,int attr,int count)
277 {
278     unsigned XR, YR;
279     char*dat;
280
281     VGA_GetAlphaMode(&XR, &YR);
282     dat = VGA_AlphaBuffer() + ((XR*Y + X) * 2);
283     /* FIXME: also call WriteConsoleOutputA, for better responsiveness */
284     while (count--) {
285         *dat++ = ch;
286         if (attr>=0) *dat = attr;
287         dat++;
288     }
289 }
290
291 /*** CONTROL ***/
292
293 void CALLBACK VGA_Poll( ULONG_PTR arg )
294 {
295     char *dat;
296     unsigned int Pitch,Height,Width,Y,X;
297     char *surf;
298
299     if (!InterlockedExchangeAdd(&vga_polling, 1)) {
300         /* FIXME: optimize by doing this only if the data has actually changed
301          *        (in a way similar to DIBSection, perhaps) */
302         if (lpddraw) {
303           /* graphics mode */
304           surf = VGA_Lock(&Pitch,&Height,&Width,NULL);
305           if (!surf) return;
306           dat = DOSMEM_MapDosToLinear(0xa0000);
307           /* copy from virtual VGA frame buffer to DirectDraw surface */
308           for (Y=0; Y<Height; Y++,surf+=Pitch,dat+=Width) {
309               memcpy(surf,dat,Width);
310               /*for (X=0; X<Width; X++) if (dat[X]) TRACE(ddraw,"data(%d) at (%d,%d)\n",dat[X],X,Y);*/
311           }
312           VGA_Unlock();
313         } else {
314           /* text mode */
315           CHAR_INFO ch[80];
316           COORD siz, off;
317           SMALL_RECT dest;
318           HANDLE con = VGA_AlphaConsole();
319
320           VGA_GetAlphaMode(&Width,&Height);
321           dat = VGA_AlphaBuffer();
322           siz.X = 80; siz.Y = 1;
323           off.X = 0; off.Y = 0;
324           /* copy from virtual VGA frame buffer to console */
325           for (Y=0; Y<Height; Y++) {
326               dest.Top=Y; dest.Bottom=Y;
327               for (X=0; X<Width; X++) {
328                   ch[X].Char.AsciiChar = *dat++;
329                   /* WriteConsoleOutputA doesn't like "dead" chars */
330                   if (ch[X].Char.AsciiChar == '\0')
331                       ch[X].Char.AsciiChar = ' ';
332                   ch[X].Attributes = *dat++;
333               }
334               dest.Left=0; dest.Right=Width+1;
335               WriteConsoleOutputA(con, ch, siz, off, &dest);
336           }
337         }
338         vga_refresh=1;
339     }
340     InterlockedDecrement(&vga_polling);
341 }
342
343 static BYTE palreg,palcnt;
344 static PALETTEENTRY paldat;
345
346 void VGA_ioport_out( WORD port, BYTE val )
347 {
348     switch (port) {
349         case 0x3c8:
350             palreg=val; palcnt=0; break;
351         case 0x3c9:
352             ((BYTE*)&paldat)[palcnt++]=val << 2;
353             if (palcnt==3) {
354                 VGA_SetPalette(&paldat,palreg++,1);
355                 palcnt=0;
356             }
357             break;
358     }
359 }
360
361 BYTE VGA_ioport_in( WORD port )
362 {
363     BYTE ret;
364
365     switch (port) {
366         case 0x3da:
367             /* since we don't (yet?) serve DOS VM requests while VGA_Poll is running,
368                we need to fake the occurrence of the vertical refresh */
369             ret=vga_refresh?0x00:0x08;
370             vga_refresh=0;
371             break;
372         default:
373             ret=0xff;
374     }
375     return ret;
376 }
377
378 void VGA_Clean(void)
379 {
380     VGA_Exit();
381     VGA_DeinstallTimer();
382 }