gzdoom-gles/src/win32/win32video.cpp
Randy Heit 59801d2b00 - Added pixel-doubled and -quadrupled screen modes to D3DFB so that users with
recent NVidia drivers can still use resolutions below 640x480, since NVidia
  saw fit to remove all support for them.


SVN r724 (trunk)
2008-01-29 18:20:05 +00:00

594 lines
15 KiB
C++

/*
** win32video.cpp
** Code to let ZDoom draw to the screen
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 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.
**---------------------------------------------------------------------------
**
*/
#ifdef _DEBUG
#define D3D_DEBUG_INFO
#endif
#define DIRECTDRAW_VERSION 0x0300
#define DIRECT3D_VERSION 0x0900
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ddraw.h>
#include <d3d9.h>
// HEADER FILES ------------------------------------------------------------
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <ddraw.h>
#include <d3d9.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"
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
IMPLEMENT_ABSTRACT_CLASS(BaseWinFB)
typedef IDirect3D9 *(WINAPI *DIRECT3DCREATE9FUNC)(UINT SDKVersion);
typedef HRESULT (WINAPI *DIRECTDRAWCREATEFUNC)(GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter);
// 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 FullscreenReset;
extern bool VidResizing;
EXTERN_CVAR (Bool, fullscreen)
EXTERN_CVAR (Float, Gamma)
// PRIVATE DATA DEFINITIONS ------------------------------------------------
static HMODULE D3D9_dll;
static HMODULE DDraw_dll;
// PUBLIC DATA DEFINITIONS -------------------------------------------------
IDirectDraw2 *DDraw;
IDirect3D9 *D3D;
IDirect3DDevice9 *D3Device;
CVAR (Bool, vid_forceddraw, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
// CODE --------------------------------------------------------------------
Win32Video::Win32Video (int parm)
: m_Modes (NULL),
m_IsFullscreen (false)
{
I_SetWndProc();
if (!InitD3D9())
{
InitDDraw();
}
}
Win32Video::~Win32Video ()
{
FreeModes ();
if (DDraw != NULL)
{
if (m_IsFullscreen)
{
DDraw->SetCooperativeLevel (NULL, DDSCL_NORMAL);
}
DDraw->Release();
DDraw = NULL;
}
if (D3D != NULL)
{
D3D->Release();
D3D = NULL;
}
STOPLOG;
}
bool Win32Video::InitD3D9 ()
{
DIRECT3DCREATE9FUNC direct3d_create_9;
if (vid_forceddraw)
{
return false;
}
// Load the Direct3D 9 library.
if ((D3D9_dll = LoadLibraryA ("d3d9.dll")) == NULL)
{
return false;
}
// Obtain an IDirect3D interface.
if ((direct3d_create_9 = (DIRECT3DCREATE9FUNC)GetProcAddress (D3D9_dll, "Direct3DCreate9")) == NULL)
{
goto closelib;
}
if ((D3D = direct3d_create_9 (D3D_SDK_VERSION)) == NULL)
{
goto closelib;
}
// Check that we have at least PS 1.4 available.
D3DCAPS9 devcaps;
if (FAILED(D3D->GetDeviceCaps (D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &devcaps)))
{
goto d3drelease;
}
if ((devcaps.PixelShaderVersion & 0xFFFF) < 0x104)
{
goto d3drelease;
}
if (!(devcaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES))
{
goto d3drelease;
}
// Enumerate available display modes.
FreeModes ();
AddD3DModes (D3DFMT_X8R8G8B8);
AddD3DModes (D3DFMT_R5G6B5);
AddLowResModes ();
AddLetterboxModes ();
if (m_Modes == NULL)
{ // Too bad. We didn't find any modes for D3D9. We probably won't find any
// for DDraw either...
goto d3drelease;
}
return true;
d3drelease:
D3D->Release();
D3D = NULL;
closelib:
FreeLibrary (D3D9_dll);
return false;
}
void Win32Video::InitDDraw ()
{
DIRECTDRAWCREATEFUNC directdraw_create;
LPDIRECTDRAW ddraw1;
STARTLOG;
HRESULT dderr;
// Load the DirectDraw library.
if ((DDraw_dll = LoadLibraryA ("ddraw.dll")) == NULL)
{
I_FatalError ("Could not load ddraw.dll");
}
// Obtain an IDirectDraw interface.
if ((directdraw_create = (DIRECTDRAWCREATEFUNC)GetProcAddress (DDraw_dll, "DirectDrawCreate")) == NULL)
{
I_FatalError ("The system file ddraw.dll is missing the DirectDrawCreate export");
}
dderr = directdraw_create (NULL, &ddraw1, NULL);
if (FAILED(dderr))
I_FatalError ("Could not create DirectDraw object: %08lx", dderr);
dderr = ddraw1->QueryInterface (IID_IDirectDraw2, (LPVOID*)&DDraw);
if (FAILED(dderr))
{
ddraw1->Release ();
DDraw = NULL;
I_FatalError ("Could not initialize IDirectDraw2 interface: %08lx", dderr);
}
// Okay, we have the IDirectDraw2 interface now, so we can release the
// really old-fashioned IDirectDraw one.
ddraw1->Release ();
DDraw->SetCooperativeLevel (Window, DDSCL_NORMAL);
FreeModes ();
dderr = DDraw->EnumDisplayModes (0, NULL, this, EnumDDModesCB);
if (FAILED(dderr))
{
DDraw->Release ();
DDraw = NULL;
I_FatalError ("Could not enumerate display modes: %08lx", dderr);
}
if (m_Modes == NULL)
{
DDraw->Release ();
DDraw = NULL;
I_FatalError ("DirectDraw returned no display modes.\n\n"
"If you started ZDoom from a fullscreen DOS box, run it from "
"a DOS window instead. If that does not work, you may need to reboot.");
}
if (OSPlatform == os_Win95)
{
// Windows 95 will let us use Mode X. If we didn't find any linear
// modes in the loop above, add the Mode X modes here.
AddMode (320, 200, 8, 200, 0);
AddMode (320, 240, 8, 240, 0);
}
AddLetterboxModes ();
}
// Returns true if fullscreen, false otherwise
bool Win32Video::GoFullscreen (bool yes)
{
static const char *const yestypes[2] = { "windowed", "fullscreen" };
HRESULT hr[2];
int count;
// FIXME: Do this right for D3D. (This function is only called by the movie player when using D3D.)
if (D3D != NULL)
{
return yes;
}
if (m_IsFullscreen == yes)
return yes;
for (count = 0; count < 2; ++count)
{
LOG1 ("fullscreen: %d\n", yes);
hr[count] = DDraw->SetCooperativeLevel (Window, !yes ? DDSCL_NORMAL :
DDSCL_ALLOWMODEX | DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
if (SUCCEEDED(hr[count]))
{
if (count != 0)
{
// Ack! Cannot print because the screen does not exist right now.
// Printf ("Setting %s mode failed. Error %08lx\n",
// yestypes[!yes], hr[0]);
}
m_IsFullscreen = yes;
return yes;
}
yes = !yes;
}
I_FatalError ("Could not set %s mode: %08lx\n"
"Could not set %s mode: %08lx\n",
yestypes[yes], hr[0], yestypes[!yes], hr[1]);
// Appease the compiler, even though we never return if we get here.
return false;
}
// Flips to the GDI surface and clears it; used by the movie player
void Win32Video::BlankForGDI ()
{
static_cast<BaseWinFB *> (screen)->Blank ();
}
// Mode enumeration --------------------------------------------------------
HRESULT WINAPI Win32Video::EnumDDModesCB (LPDDSURFACEDESC desc, void *data)
{
((Win32Video *)data)->AddMode (desc->dwWidth, desc->dwHeight, 8, desc->dwHeight, 0);
return DDENUMRET_OK;
}
void Win32Video::AddD3DModes (D3DFORMAT format)
{
UINT modecount, i;
D3DDISPLAYMODE mode;
modecount = D3D->GetAdapterModeCount (D3DADAPTER_DEFAULT, format);
for (i = 0; i < modecount; ++i)
{
if (D3D_OK == D3D->EnumAdapterModes (D3DADAPTER_DEFAULT, format, i, &mode))
{
AddMode (mode.Width, mode.Height, 8, mode.Height, 0);
}
}
}
//==========================================================================
//
// Win32Video :: AddLowResModes
//
// Recent NVidia drivers no longer support resolutions below 640x480, even
// if you try to add them as a custom resolution. With D3DFB, pixel doubling
// is quite easy to do and hardware-accelerated. If you have 1280x800, then
// you can have 320x200, but don't be surprised if it shows up as widescreen
// on a widescreen monitor, since that's what it is.
//
//==========================================================================
void Win32Video::AddLowResModes()
{
ModeInfo *mode, *nextmode;
for (mode = m_Modes; mode != NULL; mode = nextmode)
{
nextmode = mode->next;
if (mode->realheight == mode->height &&
mode->doubling == 0&&
mode->height >= 200*2 &&
mode->height <= 480*2 &&
mode->width >= 320*2 &&
mode->width <= 640*2)
{
AddMode (mode->width / 2, mode->height / 2, mode->bits, mode->height / 2, 1);
}
}
for (mode = m_Modes; mode != NULL; mode = nextmode)
{
nextmode = mode->next;
if (mode->realheight == mode->height &&
mode->doubling == 0&&
mode->height >= 200*4 &&
mode->height <= 480*4 &&
mode->width >= 320*4 &&
mode->width <= 640*4)
{
AddMode (mode->width / 4, mode->height / 4, mode->bits, mode->height / 4, 2);
}
}
}
// Add 16:9 and 16:10 resolutions you can use in a window or letterboxed
void Win32Video::AddLetterboxModes ()
{
ModeInfo *mode, *nextmode;
for (mode = m_Modes; mode != NULL; mode = nextmode)
{
nextmode = mode->next;
if (mode->realheight == mode->height && mode->height * 4/3 == mode->width)
{
if (mode->width >= 360)
{
AddMode (mode->width, mode->width * 9/16, mode->bits, mode->height, mode->doubling);
}
if (mode->width > 640)
{
AddMode (mode->width, mode->width * 10/16, mode->bits, mode->height, mode->doubling);
}
}
}
}
void Win32Video::AddMode (int x, int y, int bits, int y2, int doubling)
{
// Reject modes that do not meet certain criteria.
if ((x & 7) != 0 ||
y > MAXHEIGHT ||
x > MAXWIDTH ||
y < 200 ||
x < 320)
{
return;
}
ModeInfo **probep = &m_Modes;
ModeInfo *probe = m_Modes;
// This mode may have been already added to the list because it is
// enumerated multiple times at different refresh rates. If it's
// not present, add it to the right spot in the list; otherwise, do nothing.
// Modes are sorted first by width, then by height, then by depth. In each
// case the order is ascending.
for (; probe != 0; probep = &probe->next, probe = probe->next)
{
if (probe->width > x) break;
if (probe->width < x) continue;
// Width is equal
if (probe->height > y) break;
if (probe->height < y) continue;
// Height is equal
if (probe->bits > bits) break;
if (probe->bits < bits) continue;
// Bits is equal
return;
}
*probep = new ModeInfo (x, y, bits, y2, doubling);
(*probep)->next = probe;
}
void Win32Video::FreeModes ()
{
ModeInfo *mode = m_Modes;
while (mode)
{
ModeInfo *tempmode = mode;
mode = mode->next;
delete tempmode;
}
m_Modes = NULL;
}
void Win32Video::StartModeIterator (int bits, bool fs)
{
m_IteratorMode = m_Modes;
m_IteratorBits = bits;
m_IteratorFS = fs;
}
bool Win32Video::NextMode (int *width, int *height, bool *letterbox)
{
if (m_IteratorMode)
{
while (m_IteratorMode && m_IteratorMode->bits != m_IteratorBits)
{
m_IteratorMode = m_IteratorMode->next;
}
if (m_IteratorMode)
{
*width = m_IteratorMode->width;
*height = m_IteratorMode->height;
if (letterbox != NULL) *letterbox = m_IteratorMode->realheight != m_IteratorMode->height;
m_IteratorMode = m_IteratorMode->next;
return true;
}
}
return false;
}
DFrameBuffer *Win32Video::CreateFrameBuffer (int width, int height, bool fullscreen, DFrameBuffer *old)
{
static int retry = 0;
static int owidth, oheight;
BaseWinFB *fb;
PalEntry flashColor;
int flashAmount;
LOG4 ("CreateFB %d %d %d %p\n", width, height, fullscreen, old);
if (old != NULL)
{ // Reuse the old framebuffer if its attributes are the same
BaseWinFB *fb = static_cast<BaseWinFB *> (old);
if (fb->Width == width &&
fb->Height == height &&
fb->Windowed == !fullscreen)
{
return old;
}
old->GetFlash (flashColor, flashAmount);
delete old;
}
else
{
flashColor = 0;
flashAmount = 0;
}
if (D3D != NULL)
{
fb = new D3DFB (width, height, fullscreen);
}
else
{
fb = new DDrawFB (width, height, fullscreen);
}
LOG1 ("New fb created @ %p\n", fb);
// If we could not create the framebuffer, try again with slightly
// different parameters in this order:
// 1. Try with the closest size
// 2. Try in the opposite screen mode with the original size
// 3. Try in the opposite screen mode with the closest size
// This is a somewhat confusing mass of recursion here.
while (fb == NULL || !fb->IsValid ())
{
static HRESULT hr;
if (fb != NULL)
{
if (retry == 0)
{
hr = fb->GetHR ();
}
delete fb;
LOG1 ("fb is bad: %08lx\n", hr);
}
else
{
LOG ("Could not create fb at all\n");
}
screen = NULL;
LOG1 ("Retry number %d\n", retry);
switch (retry)
{
case 0:
owidth = width;
oheight = height;
case 2:
// Try a different resolution. Hopefully that will work.
I_ClosestResolution (&width, &height, 8);
LOG2 ("Retry with size %d,%d\n", width, height);
break;
case 1:
// Try changing fullscreen mode. Maybe that will work.
width = owidth;
height = oheight;
fullscreen = !fullscreen;
LOG1 ("Retry with fullscreen %d\n", fullscreen);
break;
default:
// I give up!
LOG3 ("Could not create new screen (%d x %d): %08lx", owidth, oheight, hr);
I_FatalError ("Could not create new screen (%d x %d): %08lx", owidth, oheight, hr);
}
++retry;
fb = static_cast<DDrawFB *>(CreateFrameBuffer (width, height, fullscreen, NULL));
}
retry = 0;
fb->SetFlash (flashColor, flashAmount);
return fb;
}
void Win32Video::SetWindowedScale (float scale)
{
// FIXME
}
// FrameBuffer implementation -----------------------------------------------