qzdoom-gpl/src/win32/fb_ddraw.cpp
Randy Heit 4f04fb4fbd Don't use AdjustWindowRectEx to determine window sizes
- GetSystemMetrics can lie about the window border sizes, so we can't
  trust it if the executable is flagged as Vista-targetting
  (default behavior for VS2012/2013).
2015-03-11 13:59:51 -05:00

1336 lines
33 KiB
C++

/*
** fb_ddraw.cpp
** Code to let ZDoom use DirectDraw 3
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#define DIRECTDRAW_VERSION 0x0300
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ddraw.h>
#include <stdio.h>
#define USE_WINDOWS_DWORD
#include "doomtype.h"
#include "c_dispatch.h"
#include "templates.h"
#include "i_system.h"
#include "i_video.h"
#include "v_video.h"
#include "v_pfx.h"
#include "stats.h"
#include "doomerrors.h"
#include "win32iface.h"
#include "v_palette.h"
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
IMPLEMENT_CLASS(DDrawFB)
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
void DoBlending (const PalEntry *from, PalEntry *to, int count, int r, int g, int b, int a);
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
extern HWND Window;
extern IVideo *Video;
extern BOOL AppActive;
extern int SessionState;
extern bool VidResizing;
EXTERN_CVAR (Bool, fullscreen)
EXTERN_CVAR (Float, Gamma)
EXTERN_CVAR (Int, vid_refreshrate)
extern IDirectDraw2 *DDraw;
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
CVAR (Bool, vid_palettehack, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Bool, vid_attachedsurfaces, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Bool, vid_noblitter, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Int, vid_displaybits, 8, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CUSTOM_CVAR (Float, rgamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{
if (screen != NULL)
{
screen->SetGamma (Gamma);
}
}
CUSTOM_CVAR (Float, ggamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{
if (screen != NULL)
{
screen->SetGamma (Gamma);
}
}
CUSTOM_CVAR (Float, bgamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{
if (screen != NULL)
{
screen->SetGamma (Gamma);
}
}
cycle_t BlitCycles;
// CODE --------------------------------------------------------------------
DDrawFB::DDrawFB (int width, int height, bool fullscreen)
: BaseWinFB (width, height)
{
int i;
LastHR = 0;
Palette = NULL;
PrimarySurf = NULL;
BackSurf = NULL;
BackSurf2 = NULL;
BlitSurf = NULL;
Clipper = NULL;
GDIPalette = NULL;
ClipSize = 0;
BufferCount = 1;
Gamma = 1.0;
BufferPitch = Pitch;
FlipFlags = vid_vsync ? DDFLIP_WAIT : DDFLIP_WAIT|DDFLIP_NOVSYNC;
PixelDoubling = 0;
NeedGammaUpdate = false;
NeedPalUpdate = false;
NeedResRecreate = false;
PaletteChangeExpected = false;
MustBuffer = false;
BufferingNow = false;
WasBuffering = false;
Write8bit = false;
UpdatePending = false;
UseBlitter = false;
FlashAmount = 0;
if (MemBuffer == NULL)
{
return;
}
for (i = 0; i < 256; i++)
{
PalEntries[i].peRed = GPalette.BaseColors[i].r;
PalEntries[i].peGreen = GPalette.BaseColors[i].g;
PalEntries[i].peBlue = GPalette.BaseColors[i].b;
GammaTable[0][i] = GammaTable[1][i] = GammaTable[2][i] = (BYTE)i;
}
memcpy (SourcePalette, GPalette.BaseColors, sizeof(PalEntry)*256);
MustBuffer = false;
Windowed = !(static_cast<Win32Video *>(Video)->GoFullscreen (fullscreen));
if (vid_noblitter)
{
LOG ("Blitter forced off\n");
}
else
{
DDCAPS hwcaps = { sizeof(DDCAPS) };
HRESULT hr = DDraw->GetCaps (&hwcaps, NULL);
if (SUCCEEDED(hr))
{
LOG2 ("dwCaps = %08lx, dwSVBCaps = %08lx\n", hwcaps.dwCaps, hwcaps.dwSVBCaps);
if (hwcaps.dwCaps & DDCAPS_BLT)
{
LOG ("Driver supports blits\n");
if (hwcaps.dwSVBCaps & DDCAPS_CANBLTSYSMEM)
{
LOG ("Driver can blit from system memory\n");
if (hwcaps.dwCaps & DDCAPS_BLTQUEUE)
{
LOG ("Driver supports asynchronous blits\n");
UseBlitter = true;
}
else
{
LOG ("Driver does not support asynchronous blits\n");
}
}
else
{
LOG ("Driver cannot blit from system memory\n");
}
}
else
{
LOG ("Driver does not support blits\n");
}
}
}
if (!CreateResources ())
{
SAFE_RELEASE( PrimarySurf );
}
}
DDrawFB::~DDrawFB ()
{
I_SaveWindowedPos ();
ReleaseResources ();
}
bool DDrawFB::CreateResources ()
{
DDSURFACEDESC ddsd = { sizeof(ddsd), };
HRESULT hr;
int bits;
BufferCount = 1;
if (!Windowed)
{
// Remove the window border in fullscreen mode
SetWindowLong (Window, GWL_STYLE, WS_POPUP|WS_VISIBLE|WS_SYSMENU);
TrueHeight = Height;
for (Win32Video::ModeInfo *mode = static_cast<Win32Video *>(Video)->m_Modes; mode != NULL; mode = mode->next)
{
if (mode->width == Width && mode->height == Height)
{
TrueHeight = mode->realheight;
PixelDoubling = mode->doubling;
break;
}
}
hr = DDraw->SetDisplayMode (Width << PixelDoubling, TrueHeight << PixelDoubling, bits = vid_displaybits, vid_refreshrate, 0);
if (FAILED(hr))
{
hr = DDraw->SetDisplayMode (Width << PixelDoubling, TrueHeight << PixelDoubling, bits = vid_displaybits, 0, 0);
bits = 32;
while (FAILED(hr) && bits >= 8)
{
hr = DDraw->SetDisplayMode (Width << PixelDoubling, Height << PixelDoubling, bits, vid_refreshrate, 0);
if (FAILED(hr))
{
hr = DDraw->SetDisplayMode (Width << PixelDoubling, Height << PixelDoubling, bits, 0, 0);
}
bits -= 8;
}
if (FAILED(hr))
{
LastHR = hr;
return false;
}
}
LOG3 ("Mode set to %d x %d x %d\n", Width, Height, bits);
if (vid_attachedsurfaces && OSPlatform == os_WinNT4)
{
if (!CreateSurfacesAttached ())
return false;
}
else
{
if (!CreateSurfacesComplex ())
return false;
}
if (UseBlitter)
{
UseBlitter = CreateBlitterSource ();
}
}
else
{
MustBuffer = true;
LOG ("Running in a window\n");
TrueHeight = Height;
// Create the primary surface
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
// PixelDoubling = 1;
do
{
hr = DDraw->CreateSurface (&ddsd, &PrimarySurf, NULL);
LOG1 ("Create primary: %08lx\n", hr);
} while (0);
if (FAILED(hr))
{
LastHR = hr;
return false;
}
MaybeCreatePalette ();
// Resize the window to match desired dimensions
RECT rect = { 0, 0, Width << PixelDoubling, Height << PixelDoubling };
AdjustWindowRectEx(&rect, WS_VISIBLE|WS_OVERLAPPEDWINDOW, FALSE, WS_EX_APPWINDOW);
int sizew = rect.right - rect.left;
int sizeh = rect.bottom - rect.top;
LOG2 ("Resize window to %dx%d\n", sizew, sizeh);
VidResizing = true;
// Make sure the window has a border in windowed mode
SetWindowLong (Window, GWL_STYLE, WS_VISIBLE|WS_OVERLAPPEDWINDOW);
if (!SetWindowPos (Window, NULL, 0, 0, sizew, sizeh,
SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER))
{
LOG1 ("SetWindowPos failed because %08lx\n", GetLastError());
}
I_RestoreWindowedPos ();
VidResizing = false;
// Create the clipper
hr = DDraw->CreateClipper (0, &Clipper, NULL);
LOG1 ("Create clipper: %08lx\n", hr);
if (FAILED(hr))
{
LastHR = hr;
return false;
}
// Associate the clipper with the window
Clipper->SetHWnd (0, Window);
PrimarySurf->SetClipper (Clipper);
LOG1 ("Clipper @ %p set\n", Clipper);
// Create the backbuffer
ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
ddsd.dwWidth = Width << PixelDoubling;
ddsd.dwHeight = Height << PixelDoubling;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | (UseBlitter ? DDSCAPS_SYSTEMMEMORY : 0);
hr = DDraw->CreateSurface (&ddsd, &BackSurf, NULL);
LOG1 ("Create backbuffer: %08lx\n", hr);
if (FAILED(hr))
{
LastHR = hr;
return false;
}
LockingSurf = BackSurf;
LOG1 ("LockingSurf and BackSurf @ %p\n", BackSurf);
LOG ("Created backbuf\n");
}
SetGamma (Gamma);
SetFlash (Flash, FlashAmount);
return true;
}
bool DDrawFB::CreateSurfacesAttached ()
{
DDSURFACEDESC ddsd = { sizeof(ddsd), };
HRESULT hr;
LOG ("creating surfaces using AddAttachedSurface\n");
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_VIDEOMEMORY;
hr = DDraw->CreateSurface (&ddsd, &PrimarySurf, NULL);
if (FAILED(hr))
{
LastHR = hr;
return false;
}
LOG1 ("Primary surface @ %p\n", PrimarySurf);
// Under NT 4 and with bad DDraw drivers under 9x (and maybe Win2k?)
// if the palette is not attached to the primary surface before any
// back buffers are added to it, colors 0 and 255 will remain black
// and white respectively.
MaybeCreatePalette ();
// Try for triple buffering. Unbuffered output is only allowed if
// we manage to get triple buffering. Even with double buffering,
// framerate can slow significantly compared to triple buffering,
// so we force buffering in that case, which effectively emulates
// triple buffering (after a fashion).
if (!AddBackBuf (&BackSurf, 1) || !AddBackBuf (&BackSurf2, 2))
{
// MustBuffer = true;
}
if (BackSurf != NULL)
{
DDSCAPS caps = { DDSCAPS_BACKBUFFER, };
hr = PrimarySurf->GetAttachedSurface (&caps, &LockingSurf);
if (FAILED (hr))
{
LOG1 ("Could not get attached surface: %08lx\n", hr);
if (BackSurf2 != NULL)
{
PrimarySurf->DeleteAttachedSurface (0, BackSurf2);
BackSurf2->Release ();
BackSurf2 = NULL;
}
PrimarySurf->DeleteAttachedSurface (0, BackSurf);
BackSurf->Release ();
BackSurf = NULL;
// MustBuffer = true;
LockingSurf = PrimarySurf;
}
else
{
BufferCount = (BackSurf2 != NULL) ? 3 : 2;
LOG ("Got attached surface\n");
}
}
else
{
LOG ("No flip chain\n");
LockingSurf = PrimarySurf;
}
return true;
}
bool DDrawFB::AddBackBuf (LPDIRECTDRAWSURFACE *surface, int num)
{
DDSURFACEDESC ddsd = { sizeof(ddsd), };
HRESULT hr;
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.dwWidth = Width;
ddsd.dwHeight = Height;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
hr = DDraw->CreateSurface (&ddsd, surface, NULL);
if (FAILED(hr))
{
LOG2 ("could not create back buf %d: %08lx\n", num, hr);
return false;
}
else
{
LOG2 ("BackBuf %d created @ %p\n", num, *surface);
hr = PrimarySurf->AddAttachedSurface (*surface);
if (FAILED(hr))
{
LOG2 ("could not add back buf %d: %08lx\n", num, hr);
(*surface)->Release ();
*surface = NULL;
return false;
}
else
{
LOG1 ("Attachment of back buf %d succeeded\n", num);
}
}
return true;
}
bool DDrawFB::CreateSurfacesComplex ()
{
DDSURFACEDESC ddsd = { sizeof(ddsd), };
HRESULT hr;
int tries = 2;
LOG ("creating surfaces using a complex primary\n");
// Try for triple buffering first.
// If that fails, try for double buffering.
// If that fails, settle for single buffering.
// If that fails, then give up.
//
// However, if using the blitter, then do not triple buffer the
// primary surface, because that is effectively like quadruple
// buffering and player response starts feeling too sluggish.
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_VIDEOMEMORY
| DDSCAPS_FLIP | DDSCAPS_COMPLEX;
do
{
LOG1 ("Try #%d\n", tries);
ddsd.dwBackBufferCount = UseBlitter ? 1 : 2;
hr = DDraw->CreateSurface (&ddsd, &PrimarySurf, NULL);
if (FAILED(hr))
{
if (hr == DDERR_NOEXCLUSIVEMODE)
{
LOG ("Exclusive mode was lost, so restoring it now.\n");
hr = DDraw->SetCooperativeLevel (Window, DDSCL_ALLOWMODEX | DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
LOG1 ("SetCooperativeLevel result: %08lx\n", hr);
hr = DDraw->SetDisplayMode (Width, Height, 8, 0, 0);
//hr = DDraw->RestoreDisplayMode ();
LOG1 ("SetDisplayMode result: %08lx\n", hr);
++tries;
hr = E_FAIL;
continue;
}
LOG1 ("Could not create with 2 backbuffers: %lx\n", hr);
ddsd.dwBackBufferCount = 1;
hr = DDraw->CreateSurface (&ddsd, &PrimarySurf, NULL);
if (FAILED(hr))
{
LOG1 ("Could not create with 1 backbuffer: %lx\n", hr);
ddsd.ddsCaps.dwCaps &= ~DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 0;
hr = DDraw->CreateSurface (&ddsd, &PrimarySurf, NULL);
if (FAILED (hr))
{
LOG1 ("Could not create with 0 backbuffers: %lx\n", hr);
if (tries == 2)
{
LOG ("Retrying without DDSCAPS_VIDEOMEMORY\n");
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE
| DDSCAPS_FLIP | DDSCAPS_COMPLEX;
}
}
}
}
} while (FAILED(hr) && --tries);
if (FAILED(hr))
{
LastHR = hr;
return false;
}
LOG1 ("Complex surface chain @ %p\n", PrimarySurf);
if (PrimarySurf == NULL)
{
LOG ("It's NULL but it didn't fail?!?\n");
LastHR = E_FAIL;
return false;
}
if (ddsd.dwBackBufferCount == 0)
{
LOG ("No flip chain\n");
// MustBuffer = true;
LockingSurf = PrimarySurf;
}
else
{
DDSCAPS caps = { DDSCAPS_BACKBUFFER, };
hr = PrimarySurf->GetAttachedSurface (&caps, &LockingSurf);
if (FAILED (hr))
{
LOG1 ("Could not get attached surface: %08lx\n", hr);
// MustBuffer = true;
LockingSurf = PrimarySurf;
}
else
{
BufferCount = ddsd.dwBackBufferCount + 1;
LOG1 ("Got attached surface. %d buffers\n", BufferCount);
}
}
MaybeCreatePalette ();
return true;
}
bool DDrawFB::CreateBlitterSource ()
{
DDSURFACEDESC ddsd = { sizeof(ddsd), };
HRESULT hr;
LOG ("Creating surface for blitter source\n");
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY
| DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 2;
ddsd.dwWidth = (Width==1024?1024+16:Width);
ddsd.dwHeight = Height;
hr = DDraw->CreateSurface (&ddsd, &BlitSurf, NULL);
if (FAILED(hr))
{
LOG1 ("Trying to create blitter source with only one surface (%08lx)\n", hr);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
hr = DDraw->CreateSurface (&ddsd, &BlitSurf, NULL);
if (FAILED(hr))
{
LOG1 ("Could not create blitter source: %08lx\n", hr);
MustBuffer = true;
return false;
}
BufferCount = MAX (BufferCount, 1);
}
else
{
BufferCount = MAX (BufferCount, 2);
}
LOG1 ("Blitter source created successfully @ %p\n", BlitSurf);
return true;
}
void DDrawFB::MaybeCreatePalette ()
{
DDPIXELFORMAT fmt = { sizeof(fmt), };
HRESULT hr;
int i;
UsePfx = false;
// If the surface needs a palette, try to create one. If the palette
// cannot be created, the result is ugly but non-fatal.
hr = PrimarySurf->GetPixelFormat (&fmt);
if (SUCCEEDED (hr) && (fmt.dwFlags & DDPF_PALETTEINDEXED8))
{
LOG ("Surface is paletted\n");
GPfx.SetFormat (fmt.dwRGBBitCount,
fmt.dwRBitMask, fmt.dwGBitMask, fmt.dwBBitMask);
if (Windowed)
{
struct { LOGPALETTE head; PALETTEENTRY filler[255]; } pal;
LOG ("Writing in a window\n");
Write8bit = true;
pal.head.palVersion = 0x300;
pal.head.palNumEntries = 256;
memcpy (pal.head.palPalEntry, PalEntries, 256*sizeof(PalEntries[0]));
for (i = 0; i < 256; i++)
{
pal.head.palPalEntry[i].peFlags = 0;
}
GDIPalette = ::CreatePalette (&pal.head);
LOG ("Created GDI palette\n");
if (GDIPalette != NULL)
{
HDC dc = GetDC (Window);
SelectPalette (dc, GDIPalette, FALSE);
RealizePalette (dc);
ReleaseDC (Window, dc);
RebuildColorTable ();
}
}
else
{
hr = DDraw->CreatePalette (DDPCAPS_8BIT|DDPCAPS_ALLOW256, PalEntries, &Palette, NULL);
if (FAILED(hr))
{
LOG ("Could not create palette\n");
Palette = NULL; // NULL it just to be safe
}
else
{
hr = PrimarySurf->SetPalette (Palette);
if (FAILED(hr))
{
LOG ("Could not attach palette to surface\n");
Palette->Release ();
Palette = NULL;
}
else
{
// The palette was supposed to have been initialized with
// the correct colors, but some drivers don't do that.
// (On the other hand, the docs for the SetPalette method
// don't state that the surface will be set to the
// palette's colors when it gets set, so this might be
// legal behavior. Wish I knew...)
NeedPalUpdate = true;
}
}
if (PixelDoubling)
{
UsePfx = true;
GPfx.SetFormat (-8, 0, 0, 0);
}
}
}
else
{
LOG ("Surface is direct color\n");
UsePfx = true;
GPfx.SetFormat (fmt.dwRGBBitCount,
fmt.dwRBitMask, fmt.dwGBitMask, fmt.dwBBitMask);
GPfx.SetPalette (GPalette.BaseColors);
}
}
void DDrawFB::ReleaseResources ()
{
if (LockCount)
{
LockCount = 1;
Unlock ();
}
SAFE_RELEASE( Clipper );
SAFE_RELEASE( PrimarySurf );
SAFE_RELEASE( BackSurf );
SAFE_RELEASE( BackSurf2 );
SAFE_RELEASE( BlitSurf );
SAFE_RELEASE( Palette );
if (GDIPalette != NULL)
{
HDC dc = GetDC (Window);
SelectPalette (dc, (HPALETTE)GetStockObject (DEFAULT_PALETTE), TRUE);
DeleteObject (GDIPalette);
ReleaseDC (Window, dc);
GDIPalette = NULL;
}
LockingSurf = NULL;
}
int DDrawFB::GetPageCount ()
{
return MustBuffer ? 1 : BufferCount+1;
}
void DDrawFB::PaletteChanged ()
{
// Somebody else changed the palette. If we are running fullscreen,
// they are obviously jerks, and we need to restore our own palette.
if (!Windowed)
{
if (!PaletteChangeExpected && Palette != NULL)
{
// It is not enough to set NeedPalUpdate to true. Some palette
// entries might now be reserved for system usage, and nothing
// we do will change them. The only way I have found to fix this
// is to recreate all our surfaces and the palette from scratch.
// IMPORTANT: Do not recreate the resources here. The screen might
// be locked for a drawing operation. Do it later the next time
// somebody tries to lock it.
NeedResRecreate = true;
}
PaletteChangeExpected = false;
}
else
{
QueryNewPalette ();
}
}
int DDrawFB::QueryNewPalette ()
{
LOG ("QueryNewPalette\n");
if (GDIPalette == NULL && Windowed)
{
if (Write8bit)
{
RebuildColorTable ();
}
return 0;
}
HDC dc = GetDC (Window);
HPALETTE oldPal = SelectPalette (dc, GDIPalette, FALSE);
int i = RealizePalette (dc);
SelectPalette (dc, oldPal, TRUE);
RealizePalette (dc);
ReleaseDC (Window, dc);
if (i != 0)
{
RebuildColorTable ();
}
return i;
}
void DDrawFB::RebuildColorTable ()
{
int i;
if (Write8bit)
{
PALETTEENTRY syspal[256];
HDC dc = GetDC (Window);
GetSystemPaletteEntries (dc, 0, 256, syspal);
for (i = 0; i < 256; i++)
{
swapvalues (syspal[i].peRed, syspal[i].peBlue);
}
for (i = 0; i < 256; i++)
{
GPfxPal.Pal8[i] = (BYTE)BestColor ((uint32 *)syspal, PalEntries[i].peRed,
PalEntries[i].peGreen, PalEntries[i].peBlue);
}
}
}
bool DDrawFB::Is8BitMode()
{
if (Windowed)
{
return Write8bit;
}
DDPIXELFORMAT fmt = { sizeof(fmt), };
HRESULT hr;
hr = PrimarySurf->GetPixelFormat(&fmt);
if (SUCCEEDED(hr))
{
return !!(fmt.dwFlags & DDPF_PALETTEINDEXED8);
}
// Can't get the primary surface's pixel format, so assume
// vid_displaybits is accurate.
return vid_displaybits == 8;
}
bool DDrawFB::IsValid ()
{
return PrimarySurf != NULL;
}
HRESULT DDrawFB::GetHR ()
{
return LastHR;
}
bool DDrawFB::Lock (bool useSimpleCanvas)
{
static int lock_num;
bool wasLost;
// LOG2 (" Lock (%d) <%d>\n", buffered, LockCount);
LOG3("Lock %5x <%d> %d\n", (AppActive << 16) | (SessionState << 12) | (MustBuffer << 8) |
(useSimpleCanvas << 4) | (int)UseBlitter, LockCount, lock_num++);
if (LockCount++ > 0)
{
return false;
}
wasLost = false;
if (NeedResRecreate && LockCount == 1)
{
LOG("Recreating resources\n");
NeedResRecreate = false;
ReleaseResources ();
CreateResources ();
// ReleaseResources sets LockCount to 0.
LockCount = 1;
}
if (!AppActive || SessionState || MustBuffer || useSimpleCanvas || !UseBlitter)
{
Buffer = MemBuffer;
Pitch = BufferPitch;
BufferingNow = true;
}
else
{
HRESULT hr GCCNOWARN = BlitSurf->Flip (NULL, DDFLIP_WAIT);
hr;
LOG1 ("Blit flip = %08lx\n", hr);
LockSurfRes res = LockSurf (NULL, BlitSurf);
if (res == NoGood)
{ // We must have a surface locked before returning,
// but we could not lock the hardware surface, so buffer
// for this frame.
Buffer = MemBuffer;
Pitch = BufferPitch;
BufferingNow = true;
}
else
{
wasLost = (res == GoodWasLost);
}
}
wasLost = wasLost || (BufferingNow != WasBuffering);
WasBuffering = BufferingNow;
return wasLost;
}
void DDrawFB::Unlock ()
{
LOG1 ("Unlock <%d>\n", LockCount);
if (LockCount == 0)
{
LOG("Unlock called when already unlocked\n");
return;
}
if (UpdatePending && LockCount == 1)
{
Update ();
}
else if (--LockCount == 0)
{
if (!BufferingNow)
{
if (BlitSurf == NULL)
{
LockingSurf->Unlock (NULL);
}
else
{
BlitSurf->Unlock (NULL);
}
}
Buffer = NULL;
}
}
DDrawFB::LockSurfRes DDrawFB::LockSurf (LPRECT lockrect, LPDIRECTDRAWSURFACE toLock)
{
HRESULT hr;
DDSURFACEDESC desc = { sizeof(desc), };
bool wasLost = false;
bool lockingLocker = false;
if (toLock == NULL)
{
lockingLocker = true;
if (LockingSurf == NULL)
{
LOG("LockingSurf lost\n");
if (!CreateResources ())
{
if (LastHR != DDERR_UNSUPPORTEDMODE)
{
I_FatalError ("Could not rebuild framebuffer: %08lx", LastHR);
}
else
{
LOG ("Display is in unsupported mode right now.\n");
return NoGood;
}
}
}
toLock = LockingSurf;
}
hr = toLock->Lock (lockrect, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL);
LOG3 ("LockSurf %p (%d): %08lx\n", toLock, lockingLocker, hr);
if (hr == DDERR_SURFACELOST)
{
wasLost = true;
if (FAILED (AttemptRestore ()))
{
return NoGood;
}
if (BlitSurf && FAILED(BlitSurf->IsLost ()))
{
LOG ("Restore blitter surface\n");
hr = BlitSurf->Restore ();
if (FAILED (hr))
{
LOG1 ("Could not restore blitter surface: %08lx", hr);
BlitSurf->Release ();
if (BlitSurf == toLock)
{
BlitSurf = NULL;
return NoGood;
}
BlitSurf = NULL;
}
}
if (lockingLocker)
{
toLock = LockingSurf;
}
LOG ("Trying to lock again\n");
hr = toLock->Lock (lockrect, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL);
if (hr == DDERR_SURFACELOST && Windowed)
{ // If this is NT, the user probably opened the Windows NT Security dialog.
// If this is not NT, trying to recreate everything from scratch won't hurt.
ReleaseResources ();
if (!CreateResources ())
{
if (LastHR != DDERR_UNSUPPORTEDMODE)
{
I_FatalError ("Could not rebuild framebuffer: %08lx", LastHR);
}
else
{
LOG ("Display is in unsupported mode right now.\n");
return NoGood;
}
}
if (lockingLocker)
{
toLock = LockingSurf;
}
hr = toLock->Lock (lockrect, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL);
}
}
if (FAILED (hr))
{ // Still could not restore the surface, so don't draw anything
//I_FatalError ("Could not lock framebuffer: %08lx", hr);
LOG1 ("Final result after restoration attempts: %08lx\n", hr);
return NoGood;
}
Buffer = (BYTE *)desc.lpSurface;
Pitch = desc.lPitch;
BufferingNow = false;
return wasLost ? GoodWasLost : Good;
}
HRESULT DDrawFB::AttemptRestore ()
{
LOG ("Restore primary\n");
HRESULT hr = PrimarySurf->Restore ();
if (hr == DDERR_WRONGMODE && Windowed)
{ // The user changed the screen mode
LOG ("DDERR_WRONGMODE and windowed, so recreating all resources\n");
ReleaseResources ();
if (!CreateResources ())
{
LOG1 ("Could not recreate framebuffer: %08lx", LastHR);
return LastHR;
}
}
else if (FAILED (hr))
{
LOG1 ("Could not restore primary surface: %08lx", hr);
return hr;
}
if (BackSurf && FAILED(BackSurf->IsLost ()))
{
LOG ("Restore backbuffer\n");
hr = BackSurf->Restore ();
if (FAILED (hr))
{
I_FatalError ("Could not restore backbuffer: %08lx", hr);
}
}
if (BackSurf2 && FAILED(BackSurf2->IsLost ()))
{
LOG ("Restore backbuffer 2\n");
hr = BackSurf2->Restore ();
if (FAILED (hr))
{
I_FatalError ("Could not restore backbuffer 2: %08lx", hr);
}
}
return 0;
}
void DDrawFB::Update ()
{
bool pchanged = false;
int i;
LOG3 ("Update <%d,%c:%d>\n", LockCount, AppActive?'Y':'N', SessionState);
if (LockCount != 1)
{
//I_FatalError ("Framebuffer must have exactly 1 lock to be updated");
if (LockCount > 0)
{
UpdatePending = true;
--LockCount;
}
return;
}
DrawRateStuff ();
if (NeedGammaUpdate)
{
NeedGammaUpdate = false;
CalcGamma (Windowed || rgamma == 0.f ? Gamma : Gamma * rgamma, GammaTable[0]);
CalcGamma (Windowed || ggamma == 0.f ? Gamma : Gamma * ggamma, GammaTable[1]);
CalcGamma (Windowed || bgamma == 0.f ? Gamma : Gamma * bgamma, GammaTable[2]);
NeedPalUpdate = true;
}
if (NeedPalUpdate || vid_palettehack)
{
NeedPalUpdate = false;
if (Palette != NULL || GDIPalette != NULL)
{
for (i = 0; i < 256; i++)
{
PalEntries[i].peRed = GammaTable[0][SourcePalette[i].r];
PalEntries[i].peGreen = GammaTable[1][SourcePalette[i].g];
PalEntries[i].peBlue = GammaTable[2][SourcePalette[i].b];
}
if (FlashAmount)
{
DoBlending ((PalEntry *)PalEntries, (PalEntry *)PalEntries,
256, GammaTable[2][Flash.b], GammaTable[1][Flash.g], GammaTable[0][Flash.r],
FlashAmount);
}
if (Palette != NULL)
{
pchanged = true;
}
else
{
/* Argh! Too slow!
SetPaletteEntries (GDIPalette, 0, 256, PalEntries);
HDC dc = GetDC (Window);
SelectPalette (dc, GDIPalette, FALSE);
RealizePalette (dc);
ReleaseDC (Window, dc);
*/
RebuildColorTable ();
}
}
else
{
for (i = 0; i < 256; i++)
{
((PalEntry *)PalEntries)[i].r = GammaTable[0][SourcePalette[i].r];
((PalEntry *)PalEntries)[i].g = GammaTable[1][SourcePalette[i].g];
((PalEntry *)PalEntries)[i].b = GammaTable[2][SourcePalette[i].b];
}
if (FlashAmount)
{
DoBlending ((PalEntry *)PalEntries, (PalEntry *)PalEntries,
256, GammaTable[0][Flash.r], GammaTable[1][Flash.g], GammaTable[2][Flash.b],
FlashAmount);
}
GPfx.SetPalette ((PalEntry *)PalEntries);
}
}
BlitCycles.Reset();
BlitCycles.Clock();
if (BufferingNow)
{
LockCount = 0;
if ((Windowed || AppActive) && !SessionState && !PaintToWindow())
{
if (LockSurf (NULL, NULL) != NoGood)
{
BYTE *writept = Buffer + (TrueHeight - Height)/2*Pitch;
LOG3 ("Copy %dx%d (%d)\n", Width, Height, BufferPitch);
if (UsePfx)
{
GPfx.Convert (MemBuffer, BufferPitch,
writept, Pitch, Width << PixelDoubling, Height << PixelDoubling,
FRACUNIT >> PixelDoubling, FRACUNIT >> PixelDoubling, 0, 0);
}
else
{
CopyFromBuff (MemBuffer, BufferPitch, Width, Height, writept);
}
if (TrueHeight != Height)
{
// Letterbox time! Draw black top and bottom borders.
int topborder = (TrueHeight - Height) / 2;
int botborder = TrueHeight - topborder - Height;
memset (Buffer, 0, Pitch*topborder);
memset (writept + Height*Pitch, 0, Pitch*botborder);
}
LockingSurf->Unlock (NULL);
}
}
}
else
{
if (BlitSurf != NULL)
{
HRESULT hr;
BlitSurf->Unlock (NULL);
RECT srcRect = { 0, 0, Width, Height };
hr = LockingSurf->BltFast (0, 0, BlitSurf, &srcRect, DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT);
if (FAILED (hr))
{
LOG1 ("Could not blit: %08lx\n", hr);
if (hr == DDERR_SURFACELOST)
{
if (SUCCEEDED (AttemptRestore ()))
{
hr = LockingSurf->BltFast (0, 0, BlitSurf, &srcRect, DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT);
if (FAILED (hr))
{
LOG1 ("Blit retry also failed: %08lx\n", hr);
}
}
}
}
else
{
LOG ("Blit ok\n");
}
}
else
{
LockingSurf->Unlock (NULL);
}
}
BlitCycles.Unclock();
LOG1 ("cycles = %.1f ms\n", BlitCycles.TimeMS());
Buffer = NULL;
LockCount = 0;
UpdatePending = false;
if (FPSLimitEvent != NULL)
{
WaitForSingleObject(FPSLimitEvent, 1000);
}
if (!Windowed && AppActive && !SessionState /*&& !UseBlitter && !MustBuffer*/)
{
HRESULT hr = PrimarySurf->Flip (NULL, FlipFlags);
LOG1 ("Flip = %08lx\n", hr);
if (hr == DDERR_INVALIDPARAMS)
{
if (FlipFlags & DDFLIP_NOVSYNC)
{
FlipFlags &= ~DDFLIP_NOVSYNC;
Printf ("Can't disable vsync\n");
PrimarySurf->Flip (NULL, FlipFlags);
}
}
}
if (pchanged && AppActive && !SessionState)
{
PaletteChangeExpected = true;
Palette->SetEntries (0, 0, 256, PalEntries);
}
}
bool DDrawFB::PaintToWindow ()
{
if (Windowed && LockCount == 0)
{
HRESULT hr;
RECT rect;
GetClientRect (Window, &rect);
if (rect.right != 0 && rect.bottom != 0)
{
// Use blit to copy/stretch to window's client rect
ClientToScreen (Window, (POINT*)&rect.left);
ClientToScreen (Window, (POINT*)&rect.right);
LOG ("Paint to window\n");
if (LockSurf (NULL, NULL) != NoGood)
{
GPfx.Convert (MemBuffer, BufferPitch,
Buffer, Pitch, Width << PixelDoubling, Height << PixelDoubling,
FRACUNIT >> PixelDoubling, FRACUNIT >> PixelDoubling, 0, 0);
LockingSurf->Unlock (NULL);
if (FAILED (hr = PrimarySurf->Blt (&rect, BackSurf, NULL, DDBLT_WAIT|DDBLT_ASYNC, NULL)))
{
if (hr == DDERR_SURFACELOST)
{
PrimarySurf->Restore ();
}
PrimarySurf->Blt (&rect, BackSurf, NULL, DDBLT_WAIT, NULL);
}
}
Buffer = NULL;
LOG ("Did paint to window\n");
}
return true;
}
return false;
}
PalEntry *DDrawFB::GetPalette ()
{
return SourcePalette;
}
void DDrawFB::UpdatePalette ()
{
NeedPalUpdate = true;
}
bool DDrawFB::SetGamma (float gamma)
{
LOG1 ("SetGamma %g\n", gamma);
Gamma = gamma;
NeedGammaUpdate = true;
return true;
}
bool DDrawFB::SetFlash (PalEntry rgb, int amount)
{
Flash = rgb;
FlashAmount = amount;
NeedPalUpdate = true;
return true;
}
void DDrawFB::GetFlash (PalEntry &rgb, int &amount)
{
rgb = Flash;
amount = FlashAmount;
}
// Q: Should I gamma adjust the returned palette?
// A: No. PNG screenshots save the gamma value, so there is no need.
void DDrawFB::GetFlashedPalette (PalEntry pal[256])
{
memcpy (pal, SourcePalette, 256*sizeof(PalEntry));
if (FlashAmount)
{
DoBlending (pal, pal, 256, Flash.r, Flash.g, Flash.b, FlashAmount);
}
}
void DDrawFB::SetVSync (bool vsync)
{
LOG1 ("vid_vsync set to %d\n", vsync);
FlipFlags = vsync ? DDFLIP_WAIT : DDFLIP_WAIT|DDFLIP_NOVSYNC;
}
void DDrawFB::NewRefreshRate()
{
if (!Windowed)
{
NeedResRecreate = true;
}
}
void DDrawFB::Blank ()
{
if (IsFullscreen ())
{
DDBLTFX blitFX = { sizeof(blitFX) };
blitFX.dwFillColor = 0;
DDraw->FlipToGDISurface ();
PrimarySurf->Blt (NULL, NULL, NULL, DDBLT_COLORFILL, &blitFX);
}
}
ADD_STAT (blit)
{
FString out;
out.Format ("blit=%04.1f ms", BlitCycles.TimeMS());
return out;
}