mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-14 22:41:53 +00:00
298e465e22
SVN r871 (trunk)
596 lines
15 KiB
C++
596 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);
|
|
old->ObjectFlags |= OF_YesReallyDelete;
|
|
if (old == screen) screen = NULL;
|
|
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 -----------------------------------------------
|