1// Copyright 2016 The SwiftShader Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "FrameBufferDD.hpp"
16
17#include "Debug.hpp"
18
19namespace sw
20{
21	extern bool forceWindowed;
22
23	GUID secondaryDisplay = {0};
24
25	int __stdcall enumDisplayCallback(GUID* guid, char *driverDescription, char *driverName, void *context, HMONITOR monitor)
26	{
27		if(strcmp(driverName, "\\\\.\\DISPLAY2") == 0)
28		{
29			secondaryDisplay = *guid;
30		}
31
32		return 1;
33	}
34
35	FrameBufferDD::FrameBufferDD(HWND windowHandle, int width, int height, bool fullscreen, bool topLeftOrigin) : FrameBufferWin(windowHandle, width, height, fullscreen, topLeftOrigin)
36	{
37		directDraw = 0;
38		frontBuffer = 0;
39		backBuffer = 0;
40
41		locked = 0;
42
43		ddraw = LoadLibrary("ddraw.dll");
44		DirectDrawCreate = (DIRECTDRAWCREATE)GetProcAddress(ddraw, "DirectDrawCreate");
45		DirectDrawEnumerateExA = (DIRECTDRAWENUMERATEEXA)GetProcAddress(ddraw, "DirectDrawEnumerateExA");
46
47		if(!windowed)
48		{
49			initFullscreen();
50		}
51		else
52		{
53			initWindowed();
54		}
55	}
56
57	FrameBufferDD::~FrameBufferDD()
58	{
59		releaseAll();
60
61		FreeLibrary(ddraw);
62	}
63
64	void FrameBufferDD::createSurfaces()
65	{
66		if(backBuffer)
67		{
68			backBuffer->Release();
69			backBuffer = 0;
70		}
71
72		if(frontBuffer)
73		{
74			frontBuffer->Release();
75			frontBuffer = 0;
76		}
77
78		if(!windowed)
79		{
80			DDSURFACEDESC surfaceDescription = {0};
81			surfaceDescription.dwSize = sizeof(surfaceDescription);
82			surfaceDescription.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
83			surfaceDescription.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
84			surfaceDescription.dwBackBufferCount = 1;
85			long result = directDraw->CreateSurface(&surfaceDescription, &frontBuffer, 0);
86
87			if(frontBuffer)
88			{
89				DDSCAPS surfaceCapabilties = {0};
90				surfaceCapabilties.dwCaps = DDSCAPS_BACKBUFFER;
91				frontBuffer->GetAttachedSurface(&surfaceCapabilties, &backBuffer);
92				backBuffer->AddRef();
93			}
94		}
95		else
96		{
97			IDirectDrawClipper *clipper;
98
99			DDSURFACEDESC ddsd = {0};
100			ddsd.dwSize = sizeof(ddsd);
101			ddsd.dwFlags = DDSD_CAPS;
102			ddsd.ddsCaps.dwCaps	= DDSCAPS_PRIMARYSURFACE;
103
104			long result = directDraw->CreateSurface(&ddsd, &frontBuffer, 0);
105			directDraw->GetDisplayMode(&ddsd);
106
107			switch(ddsd.ddpfPixelFormat.dwRGBBitCount)
108			{
109			case 32: destFormat = FORMAT_X8R8G8B8; break;
110			case 24: destFormat = FORMAT_R8G8B8;   break;
111			case 16: destFormat = FORMAT_R5G6B5;   break;
112			default: destFormat = FORMAT_NULL;     break;
113			}
114
115			if((result != DD_OK && result != DDERR_PRIMARYSURFACEALREADYEXISTS) || (destFormat == FORMAT_NULL))
116			{
117				assert(!"Failed to initialize graphics: Incompatible display mode.");
118			}
119			else
120			{
121				ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
122				ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
123				ddsd.dwWidth = width;
124				ddsd.dwHeight = height;
125
126				directDraw->CreateSurface(&ddsd, &backBuffer, 0);
127
128				directDraw->CreateClipper(0, &clipper, 0);
129				clipper->SetHWnd(0, windowHandle);
130				frontBuffer->SetClipper(clipper);
131				clipper->Release();
132			}
133		}
134	}
135
136	bool FrameBufferDD::readySurfaces()
137	{
138		if(!frontBuffer || !backBuffer)
139		{
140			createSurfaces();
141		}
142
143		if(frontBuffer && backBuffer)
144		{
145			if(frontBuffer->IsLost() || backBuffer->IsLost())
146			{
147				restoreSurfaces();
148			}
149
150			if(frontBuffer && backBuffer)
151			{
152				if(!frontBuffer->IsLost() && !backBuffer->IsLost())
153				{
154					return true;
155				}
156			}
157		}
158
159		return false;
160	}
161
162	void FrameBufferDD::updateClipper(HWND windowOverride)
163	{
164		if(windowed)
165		{
166			if(frontBuffer)
167			{
168				HWND window = windowOverride ? windowOverride : windowHandle;
169
170				IDirectDrawClipper *clipper;
171				frontBuffer->GetClipper(&clipper);
172				clipper->SetHWnd(0, window);
173				clipper->Release();
174			}
175		}
176	}
177
178	void FrameBufferDD::restoreSurfaces()
179	{
180		long result1 = frontBuffer->Restore();
181		long result2 = backBuffer->Restore();
182
183		if(result1 != DD_OK || result2 != DD_OK)   // Surfaces could not be restored; recreate them
184		{
185			createSurfaces();
186		}
187	}
188
189	void FrameBufferDD::initFullscreen()
190	{
191		releaseAll();
192
193		if(true)   // Render to primary display
194		{
195			DirectDrawCreate(0, &directDraw, 0);
196		}
197		else   // Render to secondary display
198		{
199			DirectDrawEnumerateEx(&enumDisplayCallback, 0, DDENUM_ATTACHEDSECONDARYDEVICES);
200			DirectDrawCreate(&secondaryDisplay, &directDraw, 0);
201		}
202
203		directDraw->SetCooperativeLevel(windowHandle, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
204
205		long result;
206
207		do
208		{
209			destFormat = FORMAT_X8R8G8B8;
210			result = directDraw->SetDisplayMode(width, height, 32);
211
212			if(result == DDERR_INVALIDMODE)
213			{
214				destFormat = FORMAT_R8G8B8;
215				result = directDraw->SetDisplayMode(width, height, 24);
216
217				if(result == DDERR_INVALIDMODE)
218				{
219					destFormat = FORMAT_R5G6B5;
220					result = directDraw->SetDisplayMode(width, height, 16);
221
222					if(result == DDERR_INVALIDMODE)
223					{
224						assert(!"Failed to initialize graphics: Display mode not supported.");
225					}
226				}
227			}
228
229			if(result != DD_OK)
230			{
231				Sleep(1);
232			}
233		}
234		while(result != DD_OK);
235
236		createSurfaces();
237
238		updateBounds(windowHandle);
239	}
240
241	void FrameBufferDD::initWindowed()
242	{
243		releaseAll();
244
245		DirectDrawCreate(0, &directDraw, 0);
246		directDraw->SetCooperativeLevel(windowHandle, DDSCL_NORMAL);
247
248		createSurfaces();
249
250		updateBounds(windowHandle);
251	}
252
253	void FrameBufferDD::flip(void *source, Format sourceFormat, size_t sourceStride)
254	{
255		copy(source, sourceFormat, sourceStride);
256
257		if(!readySurfaces())
258		{
259			return;
260		}
261
262		while(true)
263		{
264			long result;
265
266			if(windowed)
267			{
268				result = frontBuffer->Blt(&bounds, backBuffer, 0, DDBLT_WAIT, 0);
269			}
270			else
271			{
272				result = frontBuffer->Flip(0, DDFLIP_NOVSYNC);
273			}
274
275			if(result != DDERR_WASSTILLDRAWING)
276			{
277				break;
278			}
279
280			Sleep(0);
281		}
282	}
283
284	void FrameBufferDD::blit(void *source, const Rect *sourceRect, const Rect *destRect, Format sourceFormat, size_t sourceStride)
285	{
286		copy(source, sourceFormat, sourceStride);
287
288		if(!readySurfaces())
289		{
290			return;
291		}
292
293		RECT dRect;
294
295		if(destRect)
296		{
297			dRect.bottom = bounds.top + destRect->y1;
298			dRect.left = bounds.left + destRect->x0;
299			dRect.right = bounds.left + destRect->x1;
300			dRect.top = bounds.top + destRect->y0;
301		}
302		else
303		{
304			dRect.bottom = bounds.top + height;
305			dRect.left = bounds.left + 0;
306			dRect.right = bounds.left + width;
307			dRect.top = bounds.top + 0;
308		}
309
310		while(true)
311		{
312			long result = frontBuffer->Blt(&dRect, backBuffer, (LPRECT)sourceRect, DDBLT_WAIT, 0);
313
314			if(result != DDERR_WASSTILLDRAWING)
315			{
316				break;
317			}
318
319			Sleep(0);
320		}
321	}
322
323	void FrameBufferDD::flip(HWND windowOverride, void *source, Format sourceFormat, size_t sourceStride)
324	{
325		updateClipper(windowOverride);
326		updateBounds(windowOverride);
327
328		flip(source, sourceFormat, sourceStride);
329	}
330
331	void FrameBufferDD::blit(HWND windowOverride, void *source, const Rect *sourceRect, const Rect *destRect, Format sourceFormat, size_t sourceStride)
332	{
333		updateClipper(windowOverride);
334		updateBounds(windowOverride);
335
336		blit(source, sourceRect, destRect, sourceFormat, sourceStride);
337	}
338
339	void FrameBufferDD::screenshot(void *destBuffer)
340	{
341		if(!readySurfaces())
342		{
343			return;
344		}
345
346		DDSURFACEDESC DDSD;
347		DDSD.dwSize = sizeof(DDSD);
348
349		long result = frontBuffer->Lock(0, &DDSD, DDLOCK_WAIT, 0);
350
351		if(result == DD_OK)
352		{
353			int width = DDSD.dwWidth;
354			int height = DDSD.dwHeight;
355			int bitDepth = DDSD.ddpfPixelFormat.dwRGBBitCount;
356			int stride = DDSD.lPitch;
357
358			void *sourceBuffer = DDSD.lpSurface;
359
360			for(int y = 0; y < height; y++)
361			{
362				memcpy(destBuffer, sourceBuffer, width * 4);   // FIXME: Assumes 32-bit buffer
363
364				(char*&)sourceBuffer += stride;
365				(char*&)destBuffer += 4 * width;
366			}
367
368			frontBuffer->Unlock(0);
369		}
370	}
371
372	void FrameBufferDD::setGammaRamp(GammaRamp *gammaRamp, bool calibrate)
373	{
374		IDirectDrawGammaControl *gammaControl = 0;
375
376		if(frontBuffer)
377		{
378			frontBuffer->QueryInterface(IID_IDirectDrawGammaControl, (void**)&gammaControl);
379
380			if(gammaControl)
381			{
382				gammaControl->SetGammaRamp(calibrate ? DDSGR_CALIBRATE : 0, (DDGAMMARAMP*)gammaRamp);
383
384				gammaControl->Release();
385			}
386		}
387	}
388
389	void FrameBufferDD::getGammaRamp(GammaRamp *gammaRamp)
390	{
391		IDirectDrawGammaControl *gammaControl = 0;
392
393		if(frontBuffer)
394		{
395			frontBuffer->QueryInterface(IID_IDirectDrawGammaControl, (void**)&gammaControl);
396
397			if(gammaControl)
398			{
399				gammaControl->GetGammaRamp(0, (DDGAMMARAMP*)gammaRamp);
400
401				gammaControl->Release();
402			}
403		}
404	}
405
406	void *FrameBufferDD::lock()
407	{
408		if(locked)
409		{
410			return locked;
411		}
412
413		if(!readySurfaces())
414		{
415			return 0;
416		}
417
418		DDSURFACEDESC DDSD;
419		DDSD.dwSize = sizeof(DDSD);
420
421		long result = backBuffer->Lock(0, &DDSD, DDLOCK_WAIT, 0);
422
423		if(result == DD_OK)
424		{
425			width = DDSD.dwWidth;
426			height = DDSD.dwHeight;
427			int bitDepth = DDSD.ddpfPixelFormat.dwRGBBitCount;
428			stride = DDSD.lPitch;
429
430			locked = DDSD.lpSurface;
431
432			return locked;
433		}
434
435		return 0;
436	}
437
438	void FrameBufferDD::unlock()
439	{
440		if(!locked || !backBuffer) return;
441
442		backBuffer->Unlock(0);
443
444		locked = 0;
445	}
446
447	void FrameBufferDD::drawText(int x, int y, const char *string, ...)
448	{
449		char buffer[256];
450		va_list arglist;
451
452		va_start(arglist, string);
453		vsprintf(buffer, string, arglist);
454		va_end(arglist);
455
456		HDC hdc;
457
458		backBuffer->GetDC(&hdc);
459
460		SetBkColor(hdc, RGB(0, 0, 255));
461		SetTextColor(hdc, RGB(255, 255, 255));
462
463		TextOut(hdc, x, y, buffer, lstrlen(buffer));
464
465		backBuffer->ReleaseDC(hdc);
466	}
467
468	bool FrameBufferDD::getScanline(bool &inVerticalBlank, unsigned int &scanline)
469	{
470		HRESULT result = directDraw->GetScanLine((unsigned long*)&scanline);
471
472		if(result == DD_OK)
473		{
474			inVerticalBlank = false;
475		}
476		else if(result == DDERR_VERTICALBLANKINPROGRESS)
477		{
478			inVerticalBlank = true;
479		}
480		else if(result == DDERR_UNSUPPORTED)
481		{
482			return false;
483		}
484		else ASSERT(false);
485
486		return true;
487	}
488
489	void FrameBufferDD::releaseAll()
490	{
491		unlock();
492
493		if(backBuffer)
494		{
495			backBuffer->Release();
496			backBuffer = 0;
497		}
498
499		if(frontBuffer)
500		{
501			frontBuffer->Release();
502			frontBuffer = 0;
503		}
504
505		if(directDraw)
506		{
507			directDraw->SetCooperativeLevel(0, DDSCL_NORMAL);
508			directDraw->Release();
509			directDraw = 0;
510		}
511	}
512}
513