qzdoom-gpl/src/win32/fb_d3d9_wipe.cpp

692 lines
18 KiB
C++
Raw Normal View History

/*
** fb_d3d9_wipe.cpp
** Implements the different screen wipes using Direct3D calls.
**
**---------------------------------------------------------------------------
** 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 ------------------------------------------------------------
#ifdef _DEBUG
#define D3D_DEBUG_INFO
#endif
#define DIRECT3D_VERSION 0x0900
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <d3d9.h>
#include <stdio.h>
#define USE_WINDOWS_DWORD
#include "doomtype.h"
#include "f_wipe.h"
#include "win32iface.h"
#include "templates.h"
#include "m_random.h"
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
class D3DFB::Wiper_Crossfade : public D3DFB::Wiper
{
public:
Wiper_Crossfade();
bool Run(int ticks, D3DFB *fb);
private:
int Clock;
};
class D3DFB::Wiper_Melt : public D3DFB::Wiper
{
public:
Wiper_Melt();
bool Run(int ticks, D3DFB *fb);
private:
// Match the strip sizes that oldschool Doom used.
static const int WIDTH = 160, HEIGHT = 200;
int y[WIDTH];
};
class D3DFB::Wiper_Burn : public D3DFB::Wiper
{
public:
Wiper_Burn(D3DFB *fb);
~Wiper_Burn();
bool Run(int ticks, D3DFB *fb);
private:
static const int WIDTH = 64, HEIGHT = 64;
BYTE BurnArray[WIDTH * (HEIGHT + 5)];
IDirect3DTexture9 *BurnTexture;
int Density;
int BurnTime;
struct BURNVERTEX
{
FLOAT x, y, z, rhw;
FLOAT tu0, tv0;
FLOAT tu1, tv1;
};
#define D3DFVF_BURNVERTEX (D3DFVF_XYZRHW|D3DFVF_TEX2)
};
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
EXTERN_CVAR(Bool, test2d)
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// D3DFB :: WipeStartScreen
//
// Called before the current screen has started rendering. This needs to
// save what was drawn the previous frame so that it can be animated into
// what gets drawn this frame.
//
// In fullscreen mode, we use GetFrontBufferData() to grab the data that
// is visible on screen right now.
//
// In windowed mode, we can't do that because we'll get the whole desktop.
// Instead, we can conveniently use the TempRenderTexture, which is normally
// used for gamma-correcting copying the image to the back buffer.
//
//==========================================================================
bool D3DFB::WipeStartScreen(int type)
{
IDirect3DSurface9 *surf, *tsurf;
D3DSURFACE_DESC desc;
if (!test2d)
{
return Super::WipeStartScreen(type);
}
switch (type)
{
case wipe_Melt:
ScreenWipe = new Wiper_Melt;
break;
case wipe_Burn:
ScreenWipe = new Wiper_Burn(this);
break;
case wipe_Fade:
ScreenWipe = new Wiper_Crossfade;
break;
default:
return false;
}
if (Windowed)
{
// The InitialWipeScreen must have the same pixel format as
// the TempRenderTexture.
if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &tsurf)))
{
if (FAILED(tsurf->GetDesc(&desc)))
{
tsurf->Release();
return false;
}
tsurf->Release();
}
else
{
return false;
}
}
else
{
if (SUCCEEDED(D3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &tsurf)))
{
if (FAILED(tsurf->GetDesc(&desc)))
{
tsurf->Release();
return false;
}
tsurf->Release();
}
else
{
return false;
}
// GetFrontBufferData works only with this format
desc.Format = D3DFMT_A8R8G8B8;
}
if (FAILED(D3DDevice->CreateTexture(desc.Width, desc.Height, 1, 0,
desc.Format, D3DPOOL_SYSTEMMEM, &InitialWipeScreen, NULL)))
{
InitialWipeScreen = NULL;
return false;
}
if (FAILED(InitialWipeScreen->GetSurfaceLevel(0, &surf)))
{
InitialWipeScreen->Release();
InitialWipeScreen = NULL;
return false;
}
if (!Windowed)
{
if (FAILED(D3DDevice->GetFrontBufferData(0, surf)))
{
surf->Release();
InitialWipeScreen->Release();
InitialWipeScreen = NULL;
return false;
}
FinalWipeScreen = TempRenderTexture;
}
else
{
if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &tsurf)))
{
if (FAILED(D3DDevice->GetRenderTargetData(tsurf, surf)))
{
tsurf->Release();
InitialWipeScreen->Release();
InitialWipeScreen = NULL;
return false;
}
}
else
{
InitialWipeScreen->Release();
InitialWipeScreen = NULL;
return false;
}
// Create another texture to copy the final wipe screen to so
// we can still gamma correct the wipe. Since this is just for
// gamma correction, it's okay to fail (though not desirable.)
if (GammaFixerShader != NULL && Gamma != 1)
{
if (FAILED(tsurf->GetDesc(&desc)) ||
FAILED(D3DDevice->CreateTexture(desc.Width, desc.Height,
1, D3DUSAGE_RENDERTARGET, desc.Format, D3DPOOL_DEFAULT,
&FinalWipeScreen, NULL)))
{
FinalWipeScreen = TempRenderTexture;
}
}
else
{
FinalWipeScreen = TempRenderTexture;
}
tsurf->Release();
}
surf->Release();
// Even fullscreen will render to the TempRenderTexture, so we can have
// a copy of the new screen readily available.
GatheringWipeScreen = true;
return true;
}
//==========================================================================
//
// D3DFB :: WipeEndScreen
//
// The screen we want to animate to has just been drawn. This function is
// called in place of Update(), so it has not been Presented yet.
//
//==========================================================================
void D3DFB::WipeEndScreen()
{
if (!test2d)
{
Super::WipeEndScreen();
return;
}
// Don't do anything if there is no starting point.
if (InitialWipeScreen == NULL)
{
return;
}
// If the whole screen was drawn without 2D accel, get it in to
// video memory now.
if (!In2D)
{
Begin2D(true);
}
EndQuadBatch(); // Make sure all quads have been drawn.
// Don't do anything if there is no ending point.
if (OldRenderTarget == NULL)
{
return;
}
// If these are different, reverse their roles so we don't need to
// waste time copying from TempRenderTexture to FinalWipeScreen.
swap(FinalWipeScreen, TempRenderTexture);
// At this point, InitialWipeScreen holds the screen we are wiping from.
// FinalWipeScreen holds the screen we are wiping to, which may be the
// same texture as TempRenderTexture.
}
//==========================================================================
//
// D3DFB :: WipeDo
//
// Perform the actual wipe animation. The number of tics since the last
// time this function was called is passed in. Returns true when the wipe
// is over. The first time this function has been called, the screen is
// still locked from before and EndScene() still has not been called.
// Successive times need to call BeginScene().
//
//==========================================================================
bool D3DFB::WipeDo(int ticks)
{
if (!test2d)
{
return Super::WipeDo(ticks);
}
// Sanity checks.
if (InitialWipeScreen == NULL || FinalWipeScreen == NULL)
{
return true;
}
if (GatheringWipeScreen)
{ // This is the first time we've been called for this wipe.
GatheringWipeScreen = false;
if (OldRenderTarget == NULL)
{
return true;
}
D3DDevice->SetRenderTarget(0, OldRenderTarget);
}
else
{ // This is the second or later time we've been called for this wipe.
D3DDevice->BeginScene();
- Tried adding bilinear filtering support for paletted textures, but the shader seems to be producing crappy output, so it's disabled for now. Specifically, it produces distorted output at regular intervals for textures that aren't power-of-2-sized, and it's still doing visible filtering when the texture is rendered at its original size, so obviously it's not doing something right. - Fixed the use of power-of-2-sized native textures for smaller game textures again. - Fixed: D3DFB did not restore all the state it needed to after resetting the device. - Fixed: R_DrawTopBorder() must clip itself around the 3D view, since it's now drawn later. - With full software rendering, palette flashes once again effect the whole screen. Changes I neglected to put in the previous commit log: - Moved the view border drawing into the 2D mode part. When using Begin2D() now, the only part of the software buffer that gets updated to the screen is the part with the actual 3D scene and only if you tell it to. - Fixed a D3D memory leak on every frame in windowed mode and the same thing for the screen wipes. Note to self: If it's an interface, be sure to Release it, because it will be AddRef'ed before being returned to you. - Moved the BlendView() call out of FBaseStatusBar::Draw() so that it can be applied before copying the 3D scene to the screen underneath the 2D parts. - Restored the console's darkening level to its old table-based amount. - Fixed D3DFB::SetColorOverlay()'s incorrect calculations. - Fixed the D3D screen wipes for letterboxed modes. SVN r662 (trunk)
2008-01-03 05:39:36 +00:00
InScene = true;
}
if (OldRenderTarget != NULL)
{
OldRenderTarget->Release();
OldRenderTarget = NULL;
}
if (TempRenderTexture != NULL && TempRenderTexture != FinalWipeScreen &&
(Windowed && GammaFixerShader))
{
IDirect3DSurface9 *targetsurf;
if (SUCCEEDED(TempRenderTexture->GetSurfaceLevel(0, &targetsurf)))
{
if (SUCCEEDED(D3DDevice->GetRenderTarget(0, &OldRenderTarget)))
{
if (FAILED(D3DDevice->SetRenderTarget(0, targetsurf)))
{
// Setting the render target failed.
}
}
targetsurf->Release();
}
}
In2D = 3;
bool done = ScreenWipe->Run(ticks, this);
DrawLetterbox();
return done;
}
//==========================================================================
//
// D3DFB :: WipeCleanup
//
// Release any resources that were specifically created for the wipe.
//
//==========================================================================
void D3DFB::WipeCleanup()
{
if (ScreenWipe != NULL)
{
delete ScreenWipe;
ScreenWipe = NULL;
}
if (InitialWipeScreen != NULL)
{
InitialWipeScreen->Release();
InitialWipeScreen = NULL;
}
if (FinalWipeScreen != NULL && FinalWipeScreen != TempRenderTexture)
{
FinalWipeScreen->Release();
}
FinalWipeScreen = NULL;
GatheringWipeScreen = false;
if (!test2d)
{
Super::WipeCleanup();
return;
}
}
//==========================================================================
//
// D3DFB :: Wiper Constructor
//
//==========================================================================
D3DFB::Wiper::~Wiper()
{
}
// WIPE: CROSSFADE ---------------------------------------------------------
//==========================================================================
//
// D3DFB :: Wiper_Crossfade Constructor
//
//==========================================================================
D3DFB::Wiper_Crossfade::Wiper_Crossfade()
: Clock(0)
{
}
//==========================================================================
//
// D3DFB :: Wiper_Crossfade :: Run
//
// Fades the old screen into the new one over 32 ticks.
//
//==========================================================================
bool D3DFB::Wiper_Crossfade::Run(int ticks, D3DFB *fb)
{
Clock += ticks;
// Put the initial screen back to the buffer, presumably with DMA.
IDirect3DSurface9 *source = NULL, *target = NULL;
if (SUCCEEDED(fb->InitialWipeScreen->GetSurfaceLevel(0, &source)) &&
SUCCEEDED(fb->D3DDevice->GetRenderTarget(0, &target)))
{
fb->D3DDevice->UpdateSurface(source, NULL, target, NULL);
target->Release();
}
if (source != NULL)
{
source->Release();
}
// Draw the new screen on top of it.
FBVERTEX verts[4];
fb->CalcFullscreenCoords(verts, false, D3DCOLOR_COLORVALUE(0,0,0,Clock / 32.f), D3DCOLOR_RGBA(255,255,255,0));
fb->D3DDevice->SetFVF(D3DFVF_FBVERTEX);
fb->SetTexture(0, fb->FinalWipeScreen);
fb->SetAlphaBlend(TRUE, D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA);
fb->SetPixelShader(fb->PlainShader);
fb->D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX));
return Clock >= 32;
}
// WIPE: MELT --------------------------------------------------------------
//==========================================================================
//
// D3DFB :: Wiper_Melt Constructor
//
//==========================================================================
D3DFB::Wiper_Melt::Wiper_Melt()
{
int i, r;
// setup initial column positions
// (y<0 => not ready to scroll yet)
y[0] = -(M_Random() & 15);
for (i = 1; i < WIDTH; ++i)
{
r = (M_Random()%3) - 1;
y[i] = clamp(y[i-1] + r, -15, 0);
}
}
//==========================================================================
//
// D3DFB :: Wiper_Melt :: Run
//
// Fades the old screen into the new one over 32 ticks.
//
//==========================================================================
bool D3DFB::Wiper_Melt::Run(int ticks, D3DFB *fb)
{
IDirect3DSurface9 *source = NULL, *target;
if (FAILED(fb->InitialWipeScreen->GetSurfaceLevel(0, &source)) ||
FAILED(fb->D3DDevice->GetRenderTarget(0, &target)))
{
// A fat lot of good we can do if we can't get these two surfaces.
if (source != NULL)
{
source->Release();
}
return true;
}
// Draw the new screen on the bottom.
FBVERTEX verts[4];
fb->CalcFullscreenCoords(verts, false, 0, 0xFFFFFFFF);
fb->D3DDevice->SetFVF(D3DFVF_FBVERTEX);
fb->SetTexture(0, fb->FinalWipeScreen);
fb->SetAlphaBlend(FALSE);
fb->SetPixelShader(fb->PlainShader);
fb->D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(FBVERTEX));
int i, dy;
bool done;
// Copy the old screen in vertical strips on top of the new one.
while (ticks--)
{
done = true;
for (i = 0; i < WIDTH; i++)
{
if (y[i] < 0)
{
y[i]++;
done = false;
}
else if (y[i] < HEIGHT)
{
dy = (y[i] < 16) ? y[i]+1 : 8;
y[i] = MIN(y[i] + dy, HEIGHT);
done = false;
}
if (ticks == 0)
{ // Only draw for the final tick.
RECT rect;
POINT dpt;
dpt.x = i * fb->Width / WIDTH;
dpt.y = MAX(0, y[i] * fb->Height / HEIGHT);
rect.left = dpt.x;
rect.top = fb->LBOffsetI;
rect.right = (i + 1) * fb->Width / WIDTH;
rect.bottom = fb->Height - dpt.y + fb->LBOffsetI;
dpt.y += fb->LBOffsetI;
if (rect.bottom > rect.top)
{
fb->D3DDevice->UpdateSurface(source, &rect, target, &dpt);
}
}
}
}
target->Release();
source->Release();
return done;
}
// WIPE: BURN --------------------------------------------------------------
//==========================================================================
//
// D3DFB :: Wiper_Burn Constructor
//
//==========================================================================
D3DFB::Wiper_Burn::Wiper_Burn(D3DFB *fb)
{
Density = 4;
BurnTime = 0;
memset(BurnArray, 0, sizeof(BurnArray));
if (fb->BurnShader == NULL || FAILED(fb->D3DDevice->CreateTexture(WIDTH, HEIGHT, 1,
D3DUSAGE_DYNAMIC, D3DFMT_L8, D3DPOOL_DEFAULT, &BurnTexture, NULL)))
{
BurnTexture = NULL;
}
}
//==========================================================================
//
// D3DFB :: Wiper_Burn Destructor
//
//==========================================================================
D3DFB::Wiper_Burn::~Wiper_Burn()
{
if (BurnTexture != NULL)
{
BurnTexture->Release();
BurnTexture = NULL;
}
}
//==========================================================================
//
// D3DFB :: Wiper_Burn :: Run
//
//==========================================================================
bool D3DFB::Wiper_Burn::Run(int ticks, D3DFB *fb)
{
bool done;
BurnTime += ticks;
ticks *= 2;
// Make the fire burn
done = false;
while (!done && ticks--)
{
Density = wipe_CalcBurn(BurnArray, WIDTH, HEIGHT, Density);
done = (Density < 0);
}
// Update the burn texture with the new burn data
D3DLOCKED_RECT lrect;
if (SUCCEEDED(BurnTexture->LockRect(0, &lrect, NULL, D3DLOCK_DISCARD)))
{
const BYTE *src = BurnArray;
BYTE *dest = (BYTE *)lrect.pBits;
for (int y = HEIGHT; y != 0; --y)
{
for (int x = WIDTH; x != 0; --x)
{
*dest++ = *src++;
}
dest += lrect.Pitch - WIDTH;
}
BurnTexture->UnlockRect(0);
}
// Put the initial screen back to the buffer.
IDirect3DSurface9 *source = NULL, *target;
if (SUCCEEDED(fb->InitialWipeScreen->GetSurfaceLevel(0, &source)) &&
SUCCEEDED(fb->D3DDevice->GetRenderTarget(0, &target)))
{
fb->D3DDevice->UpdateSurface(source, NULL, target, NULL);
target->Release();
}
if (source != NULL)
{
source->Release();
}
// Burn the new screen on top of it.
float top = fb->LBOffset - 0.5f;
float right = float(fb->Width) - 0.5f;
float bot = float(fb->Height) + top;
float texright = float(fb->Width) / float(fb->FBWidth);
float texbot = float(fb->Height) / float(fb->FBHeight);
BURNVERTEX verts[4] =
{
{ -0.5f, top, 0.5f, 1.f, 0.f, 0.f, 0, 0 },
{ right, top, 0.5f, 1.f, texright, 0.f, 1, 0 },
{ right, bot, 0.5f, 1.f, texright, texbot, 1, 1 },
{ -0.5f, bot, 0.5f, 1.f, 0.f, texbot, 0, 1 }
};
fb->D3DDevice->SetFVF(D3DFVF_BURNVERTEX);
fb->SetTexture(0, fb->FinalWipeScreen);
fb->SetTexture(1, BurnTexture);
fb->SetAlphaBlend(TRUE, D3DBLEND_SRCALPHA, D3DBLEND_INVSRCALPHA);
fb->SetPixelShader(fb->BurnShader);
fb->D3DDevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
fb->D3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, verts, sizeof(BURNVERTEX));
fb->D3DDevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
- Tried adding bilinear filtering support for paletted textures, but the shader seems to be producing crappy output, so it's disabled for now. Specifically, it produces distorted output at regular intervals for textures that aren't power-of-2-sized, and it's still doing visible filtering when the texture is rendered at its original size, so obviously it's not doing something right. - Fixed the use of power-of-2-sized native textures for smaller game textures again. - Fixed: D3DFB did not restore all the state it needed to after resetting the device. - Fixed: R_DrawTopBorder() must clip itself around the 3D view, since it's now drawn later. - With full software rendering, palette flashes once again effect the whole screen. Changes I neglected to put in the previous commit log: - Moved the view border drawing into the 2D mode part. When using Begin2D() now, the only part of the software buffer that gets updated to the screen is the part with the actual 3D scene and only if you tell it to. - Fixed a D3D memory leak on every frame in windowed mode and the same thing for the screen wipes. Note to self: If it's an interface, be sure to Release it, because it will be AddRef'ed before being returned to you. - Moved the BlendView() call out of FBaseStatusBar::Draw() so that it can be applied before copying the 3D scene to the screen underneath the 2D parts. - Restored the console's darkening level to its old table-based amount. - Fixed D3DFB::SetColorOverlay()'s incorrect calculations. - Fixed the D3D screen wipes for letterboxed modes. SVN r662 (trunk)
2008-01-03 05:39:36 +00:00
fb->D3DDevice->SetFVF(D3DFVF_FBVERTEX);
// The fire may not always stabilize, so the wipe is forced to end
// after an arbitrary maximum time.
return done || (BurnTime > 40);
}