// Copyright 2016 The SwiftShader Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "FrameBufferDD.hpp" #include "Common/Debug.hpp" namespace sw { extern bool forceWindowed; GUID secondaryDisplay = {0}; int __stdcall enumDisplayCallback(GUID* guid, char *driverDescription, char *driverName, void *context, HMONITOR monitor) { if(strcmp(driverName, "\\\\.\\DISPLAY2") == 0) { secondaryDisplay = *guid; } return 1; } FrameBufferDD::FrameBufferDD(HWND windowHandle, int width, int height, bool fullscreen, bool topLeftOrigin) : FrameBufferWin(windowHandle, width, height, fullscreen, topLeftOrigin) { directDraw = 0; frontBuffer = 0; backBuffer = 0; framebuffer = nullptr; ddraw = LoadLibrary("ddraw.dll"); DirectDrawCreate = (DIRECTDRAWCREATE)GetProcAddress(ddraw, "DirectDrawCreate"); DirectDrawEnumerateExA = (DIRECTDRAWENUMERATEEXA)GetProcAddress(ddraw, "DirectDrawEnumerateExA"); if(!windowed) { initFullscreen(); } else { initWindowed(); } } FrameBufferDD::~FrameBufferDD() { releaseAll(); FreeLibrary(ddraw); } void FrameBufferDD::createSurfaces() { if(backBuffer) { backBuffer->Release(); backBuffer = 0; } if(frontBuffer) { frontBuffer->Release(); frontBuffer = 0; } if(!windowed) { DDSURFACEDESC surfaceDescription = {0}; surfaceDescription.dwSize = sizeof(surfaceDescription); surfaceDescription.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; surfaceDescription.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; surfaceDescription.dwBackBufferCount = 1; directDraw->CreateSurface(&surfaceDescription, &frontBuffer, 0); if(frontBuffer) { DDSCAPS surfaceCapabilties = {0}; surfaceCapabilties.dwCaps = DDSCAPS_BACKBUFFER; frontBuffer->GetAttachedSurface(&surfaceCapabilties, &backBuffer); backBuffer->AddRef(); } } else { IDirectDrawClipper *clipper; DDSURFACEDESC ddsd = {0}; ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; long result = directDraw->CreateSurface(&ddsd, &frontBuffer, 0); directDraw->GetDisplayMode(&ddsd); switch(ddsd.ddpfPixelFormat.dwRGBBitCount) { case 32: format = FORMAT_X8R8G8B8; break; case 24: format = FORMAT_R8G8B8; break; case 16: format = FORMAT_R5G6B5; break; default: format = FORMAT_NULL; break; } if((result != DD_OK && result != DDERR_PRIMARYSURFACEALREADYEXISTS) || (format == FORMAT_NULL)) { assert(!"Failed to initialize graphics: Incompatible display mode."); } else { ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; ddsd.dwWidth = width; ddsd.dwHeight = height; directDraw->CreateSurface(&ddsd, &backBuffer, 0); directDraw->CreateClipper(0, &clipper, 0); clipper->SetHWnd(0, windowHandle); frontBuffer->SetClipper(clipper); clipper->Release(); } } } bool FrameBufferDD::readySurfaces() { if(!frontBuffer || !backBuffer) { createSurfaces(); } if(frontBuffer && backBuffer) { if(frontBuffer->IsLost() || backBuffer->IsLost()) { restoreSurfaces(); } if(frontBuffer && backBuffer) { if(!frontBuffer->IsLost() && !backBuffer->IsLost()) { return true; } } } return false; } void FrameBufferDD::updateClipper(HWND windowOverride) { if(windowed) { if(frontBuffer) { HWND window = windowOverride ? windowOverride : windowHandle; IDirectDrawClipper *clipper; frontBuffer->GetClipper(&clipper); clipper->SetHWnd(0, window); clipper->Release(); } } } void FrameBufferDD::restoreSurfaces() { long result1 = frontBuffer->Restore(); long result2 = backBuffer->Restore(); if(result1 != DD_OK || result2 != DD_OK) // Surfaces could not be restored; recreate them { createSurfaces(); } } void FrameBufferDD::initFullscreen() { releaseAll(); if(true) // Render to primary display { DirectDrawCreate(0, &directDraw, 0); } else // Render to secondary display { DirectDrawEnumerateEx(&enumDisplayCallback, 0, DDENUM_ATTACHEDSECONDARYDEVICES); DirectDrawCreate(&secondaryDisplay, &directDraw, 0); } directDraw->SetCooperativeLevel(windowHandle, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); long result; do { format = FORMAT_X8R8G8B8; result = directDraw->SetDisplayMode(width, height, 32); if(result == DDERR_INVALIDMODE) { format = FORMAT_R8G8B8; result = directDraw->SetDisplayMode(width, height, 24); if(result == DDERR_INVALIDMODE) { format = FORMAT_R5G6B5; result = directDraw->SetDisplayMode(width, height, 16); if(result == DDERR_INVALIDMODE) { assert(!"Failed to initialize graphics: Display mode not supported."); } } } if(result != DD_OK) { Sleep(1); } } while(result != DD_OK); createSurfaces(); updateBounds(windowHandle); } void FrameBufferDD::initWindowed() { releaseAll(); DirectDrawCreate(0, &directDraw, 0); directDraw->SetCooperativeLevel(windowHandle, DDSCL_NORMAL); createSurfaces(); updateBounds(windowHandle); } void FrameBufferDD::flip(sw::Surface *source) { copy(source); if(!readySurfaces()) { return; } while(true) { long result; if(windowed) { result = frontBuffer->Blt(&bounds, backBuffer, 0, DDBLT_WAIT, 0); } else { result = frontBuffer->Flip(0, DDFLIP_NOVSYNC); } if(result != DDERR_WASSTILLDRAWING) { break; } Sleep(0); } } void FrameBufferDD::blit(sw::Surface *source, const Rect *sourceRect, const Rect *destRect) { copy(source); if(!readySurfaces()) { return; } RECT dRect; if(destRect) { dRect.bottom = bounds.top + destRect->y1; dRect.left = bounds.left + destRect->x0; dRect.right = bounds.left + destRect->x1; dRect.top = bounds.top + destRect->y0; } else { dRect.bottom = bounds.top + height; dRect.left = bounds.left + 0; dRect.right = bounds.left + width; dRect.top = bounds.top + 0; } while(true) { long result = frontBuffer->Blt(&dRect, backBuffer, (LPRECT)sourceRect, DDBLT_WAIT, 0); if(result != DDERR_WASSTILLDRAWING) { break; } Sleep(0); } } void FrameBufferDD::flip(HWND windowOverride, sw::Surface *source) { updateClipper(windowOverride); updateBounds(windowOverride); flip(source); } void FrameBufferDD::blit(HWND windowOverride, sw::Surface *source, const Rect *sourceRect, const Rect *destRect) { updateClipper(windowOverride); updateBounds(windowOverride); blit(source, sourceRect, destRect); } void FrameBufferDD::screenshot(void *destBuffer) { if(!readySurfaces()) { return; } DDSURFACEDESC DDSD; DDSD.dwSize = sizeof(DDSD); long result = frontBuffer->Lock(0, &DDSD, DDLOCK_WAIT, 0); if(result == DD_OK) { int width = DDSD.dwWidth; int height = DDSD.dwHeight; int stride = DDSD.lPitch; void *sourceBuffer = DDSD.lpSurface; for(int y = 0; y < height; y++) { memcpy(destBuffer, sourceBuffer, width * 4); // FIXME: Assumes 32-bit buffer (char*&)sourceBuffer += stride; (char*&)destBuffer += 4 * width; } frontBuffer->Unlock(0); } } void FrameBufferDD::setGammaRamp(GammaRamp *gammaRamp, bool calibrate) { IDirectDrawGammaControl *gammaControl = 0; if(frontBuffer) { frontBuffer->QueryInterface(IID_IDirectDrawGammaControl, (void**)&gammaControl); if(gammaControl) { gammaControl->SetGammaRamp(calibrate ? DDSGR_CALIBRATE : 0, (DDGAMMARAMP*)gammaRamp); gammaControl->Release(); } } } void FrameBufferDD::getGammaRamp(GammaRamp *gammaRamp) { IDirectDrawGammaControl *gammaControl = 0; if(frontBuffer) { frontBuffer->QueryInterface(IID_IDirectDrawGammaControl, (void**)&gammaControl); if(gammaControl) { gammaControl->GetGammaRamp(0, (DDGAMMARAMP*)gammaRamp); gammaControl->Release(); } } } void *FrameBufferDD::lock() { if(framebuffer) { return framebuffer; } if(!readySurfaces()) { return nullptr; } DDSURFACEDESC DDSD; DDSD.dwSize = sizeof(DDSD); long result = backBuffer->Lock(0, &DDSD, DDLOCK_WAIT, 0); if(result == DD_OK) { width = DDSD.dwWidth; height = DDSD.dwHeight; stride = DDSD.lPitch; framebuffer = DDSD.lpSurface; return framebuffer; } return nullptr; } void FrameBufferDD::unlock() { if(!framebuffer || !backBuffer) return; backBuffer->Unlock(0); framebuffer = nullptr; } void FrameBufferDD::drawText(int x, int y, const char *string, ...) { char buffer[256]; va_list arglist; va_start(arglist, string); vsprintf(buffer, string, arglist); va_end(arglist); HDC hdc; backBuffer->GetDC(&hdc); SetBkColor(hdc, RGB(0, 0, 255)); SetTextColor(hdc, RGB(255, 255, 255)); TextOut(hdc, x, y, buffer, lstrlen(buffer)); backBuffer->ReleaseDC(hdc); } bool FrameBufferDD::getScanline(bool &inVerticalBlank, unsigned int &scanline) { HRESULT result = directDraw->GetScanLine((unsigned long*)&scanline); if(result == DD_OK) { inVerticalBlank = false; } else if(result == DDERR_VERTICALBLANKINPROGRESS) { inVerticalBlank = true; } else if(result == DDERR_UNSUPPORTED) { return false; } else ASSERT(false); return true; } void FrameBufferDD::releaseAll() { unlock(); if(backBuffer) { backBuffer->Release(); backBuffer = 0; } if(frontBuffer) { frontBuffer->Release(); frontBuffer = 0; } if(directDraw) { directDraw->SetCooperativeLevel(0, DDSCL_NORMAL); directDraw->Release(); directDraw = 0; } } }