blob: b1a2ddb0353135b8512fc216424e54194e240299 [file] [log] [blame]
//
// Copyright (c) 2002-2010 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Surface.cpp: Implements the egl::Surface class, representing a drawing surface
// such as the client area of a window, including any back buffers.
// Implements EGLSurface and related functionality. [EGL 1.4] section 2.2 page 3.
#include "libEGL/Surface.h"
#include "common/debug.h"
#include "libEGL/main.h"
#include "libEGL/Display.h"
namespace egl
{
Surface::Surface(Display *display, const Config *config, HWND window)
: mDisplay(display), mConfig(config), mWindow(window)
{
mSwapChain = NULL;
mDepthStencil = NULL;
mBackBuffer = NULL;
mRenderTarget = NULL;
mFlipTexture = NULL;
mFlipState = NULL;
mPreFlipState = NULL;
mPixelAspectRatio = (EGLint)(1.0 * EGL_DISPLAY_SCALING); // FIXME: Determine actual pixel aspect ratio
mRenderBuffer = EGL_BACK_BUFFER;
mSwapBehavior = EGL_BUFFER_PRESERVED;
mSwapInterval = -1;
setSwapInterval(1);
resetSwapChain();
}
Surface::~Surface()
{
release();
}
void Surface::release()
{
if (mSwapChain)
{
mSwapChain->Release();
mSwapChain = NULL;
}
if (mBackBuffer)
{
mBackBuffer->Release();
mBackBuffer = NULL;
}
if (mRenderTarget)
{
mRenderTarget->Release();
mRenderTarget = NULL;
}
if (mDepthStencil)
{
mDepthStencil->Release();
mDepthStencil = NULL;
}
if (mFlipTexture)
{
mFlipTexture->Release();
mFlipTexture = NULL;
}
if (mFlipState)
{
mFlipState->Release();
mFlipState = NULL;
}
if (mPreFlipState)
{
mPreFlipState->Release();
mPreFlipState = NULL;
}
}
void Surface::resetSwapChain()
{
IDirect3DDevice9 *device = mDisplay->getDevice();
if (device == NULL)
{
return;
}
D3DPRESENT_PARAMETERS presentParameters = {0};
presentParameters.AutoDepthStencilFormat = mConfig->mDepthStencilFormat;
presentParameters.BackBufferCount = 1;
presentParameters.BackBufferFormat = mConfig->mRenderTargetFormat;
presentParameters.EnableAutoDepthStencil = FALSE;
presentParameters.Flags = 0;
presentParameters.hDeviceWindow = getWindowHandle();
presentParameters.MultiSampleQuality = 0; // FIXME: Unimplemented
presentParameters.MultiSampleType = D3DMULTISAMPLE_NONE; // FIXME: Unimplemented
presentParameters.PresentationInterval = mPresentInterval;
presentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
presentParameters.Windowed = TRUE;
RECT windowRect;
if (!GetClientRect(getWindowHandle(), &windowRect))
{
ASSERT(false);
return;
}
presentParameters.BackBufferWidth = windowRect.right - windowRect.left;
presentParameters.BackBufferHeight = windowRect.bottom - windowRect.top;
IDirect3DSwapChain9 *swapChain = NULL;
HRESULT result = device->CreateAdditionalSwapChain(&presentParameters, &swapChain);
if (FAILED(result))
{
ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
ERR("Could not create additional swap chains: %08lX", result);
return error(EGL_BAD_ALLOC);
}
IDirect3DSurface9 *depthStencilSurface = NULL;
result = device->CreateDepthStencilSurface(presentParameters.BackBufferWidth, presentParameters.BackBufferHeight,
presentParameters.AutoDepthStencilFormat, presentParameters.MultiSampleType,
presentParameters.MultiSampleQuality, FALSE, &depthStencilSurface, NULL);
if (FAILED(result))
{
ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
swapChain->Release();
ERR("Could not create depthstencil surface for new swap chain: %08lX", result);
return error(EGL_BAD_ALLOC);
}
IDirect3DSurface9 *renderTarget = NULL;
result = device->CreateRenderTarget(presentParameters.BackBufferWidth, presentParameters.BackBufferHeight, presentParameters.BackBufferFormat,
presentParameters.MultiSampleType, presentParameters.MultiSampleQuality, FALSE, &renderTarget, NULL);
if (FAILED(result))
{
ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
swapChain->Release();
depthStencilSurface->Release();
ERR("Could not create render target surface for new swap chain: %08lX", result);
return error(EGL_BAD_ALLOC);
}
ASSERT(SUCCEEDED(result));
IDirect3DTexture9 *flipTexture = NULL;
result = device->CreateTexture(presentParameters.BackBufferWidth, presentParameters.BackBufferHeight, 1, D3DUSAGE_RENDERTARGET,
presentParameters.BackBufferFormat, D3DPOOL_DEFAULT, &flipTexture, NULL);
if (FAILED(result))
{
ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
swapChain->Release();
depthStencilSurface->Release();
renderTarget->Release();
ERR("Could not create flip texture for new swap chain: %08lX", result);
return error(EGL_BAD_ALLOC);
}
IDirect3DSurface9 *backBuffer = NULL;
swapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backBuffer);
if (mSwapChain) mSwapChain->Release();
if (mDepthStencil) mDepthStencil->Release();
if (mBackBuffer) mBackBuffer->Release();
if (mRenderTarget) mRenderTarget->Release();
if (mFlipTexture) mFlipTexture->Release();
mWidth = presentParameters.BackBufferWidth;
mHeight = presentParameters.BackBufferHeight;
mSwapChain = swapChain;
mDepthStencil = depthStencilSurface;
mBackBuffer = backBuffer;
mRenderTarget = renderTarget;
mFlipTexture = flipTexture;
mPresentIntervalDirty = false;
// The flip state block recorded mFlipTexture so it is now invalid.
releaseRecordedState(device);
}
HWND Surface::getWindowHandle()
{
return mWindow;
}
void Surface::writeRecordableFlipState(IDirect3DDevice9 *device)
{
// Disable all pipeline operations
device->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
device->SetRenderState(D3DRS_STENCILENABLE, FALSE);
device->SetRenderState(D3DRS_CLIPPLANEENABLE, 0);
device->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_RED);
device->SetRenderState(D3DRS_SRGBWRITEENABLE, FALSE);
device->SetPixelShader(NULL);
device->SetVertexShader(NULL);
// Just sample the texture
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
device->SetTexture(0, NULL); // The actual texture will change after resizing. But the pre-flip state block must save/restore the texture.
device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
device->SetSamplerState(0, D3DSAMP_SRGBTEXTURE, FALSE);
device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
device->SetStreamSourceFreq(0, 1); // DrawPrimitiveUP only cares about stream 0, not the rest.
}
void Surface::applyFlipState(IDirect3DDevice9 *device)
{
HRESULT hr;
if (mFlipState == NULL)
{
// Create two state blocks both recording the states that are changed when swapping.
// mPreFlipState will record the original state each entry.
hr = device->BeginStateBlock();
ASSERT(SUCCEEDED(hr));
writeRecordableFlipState(device);
hr = device->EndStateBlock(&mPreFlipState);
ASSERT(SUCCEEDED(hr) || hr == D3DERR_OUTOFVIDEOMEMORY || hr == E_OUTOFMEMORY);
if (SUCCEEDED(hr))
{
mPreFlipState->Capture();
}
// mFlipState will record the state for the swap operation.
hr = device->BeginStateBlock();
ASSERT(SUCCEEDED(hr));
writeRecordableFlipState(device);
hr = device->EndStateBlock(&mFlipState);
ASSERT(SUCCEEDED(hr) || hr == D3DERR_OUTOFVIDEOMEMORY || hr == E_OUTOFMEMORY);
if (FAILED(hr))
{
mFlipState = NULL;
mPreFlipState->Release();
mPreFlipState = NULL;
}
else
{
hr = mFlipState->Apply();
ASSERT(SUCCEEDED(hr));
}
}
else
{
hr = mPreFlipState->Capture();
ASSERT(SUCCEEDED(hr));
hr = mFlipState->Apply();
ASSERT(SUCCEEDED(hr));
}
device->GetRenderTarget(0, &mPreFlipBackBuffer);
device->GetDepthStencilSurface(&mPreFlipDepthStencil);
device->SetRenderTarget(0, mBackBuffer);
device->SetDepthStencilSurface(NULL);
}
void Surface::restoreState(IDirect3DDevice9 *device)
{
mPreFlipState->Apply();
device->SetRenderTarget(0, mPreFlipBackBuffer);
device->SetDepthStencilSurface(mPreFlipDepthStencil);
if (mPreFlipBackBuffer)
{
mPreFlipBackBuffer->Release();
mPreFlipBackBuffer = NULL;
}
if (mPreFlipDepthStencil)
{
mPreFlipDepthStencil->Release();
mPreFlipDepthStencil = NULL;
}
}
// On the next flip, this will cause the state to be recorded from scratch.
// In particular we need to do this if the flip texture changes.
void Surface::releaseRecordedState(IDirect3DDevice9 *device)
{
if (mFlipState)
{
mFlipState->Release();
mFlipState = NULL;
}
if (mPreFlipState)
{
mPreFlipState->Release();
mPreFlipState = NULL;
}
}
bool Surface::checkForOutOfDateSwapChain()
{
RECT client;
if (!GetClientRect(getWindowHandle(), &client))
{
ASSERT(false);
return false;
}
if (getWidth() != client.right - client.left || getHeight() != client.bottom - client.top || mPresentIntervalDirty)
{
resetSwapChain();
if (static_cast<egl::Surface*>(getCurrentDrawSurface()) == this)
{
glMakeCurrent(glGetCurrentContext(), static_cast<egl::Display*>(getCurrentDisplay()), this);
}
return true;
}
return false;
}
DWORD Surface::convertInterval(EGLint interval)
{
switch(interval)
{
case 0: return D3DPRESENT_INTERVAL_IMMEDIATE;
case 1: return D3DPRESENT_INTERVAL_ONE;
case 2: return D3DPRESENT_INTERVAL_TWO;
case 3: return D3DPRESENT_INTERVAL_THREE;
case 4: return D3DPRESENT_INTERVAL_FOUR;
default: UNREACHABLE();
}
return D3DPRESENT_INTERVAL_DEFAULT;
}
bool Surface::swap()
{
if (mSwapChain)
{
IDirect3DTexture9 *flipTexture = mFlipTexture;
flipTexture->AddRef();
IDirect3DSurface9 *renderTarget = mRenderTarget;
renderTarget->AddRef();
EGLint oldWidth = mWidth;
EGLint oldHeight = mHeight;
checkForOutOfDateSwapChain();
IDirect3DDevice9 *device = mDisplay->getDevice();
IDirect3DSurface9 *textureSurface;
flipTexture->GetSurfaceLevel(0, &textureSurface);
mDisplay->endScene();
device->StretchRect(renderTarget, NULL, textureSurface, NULL, D3DTEXF_NONE);
renderTarget->Release();
applyFlipState(device);
device->SetTexture(0, flipTexture);
float xscale = (float)mWidth / oldWidth;
float yscale = (float)mHeight / oldHeight;
// Render the texture upside down into the back buffer
// Texcoords are chosen to pin a potentially resized image into the upper-left corner without scaling.
float quad[4][6] = {{ 0 - 0.5f, 0 - 0.5f, 0.0f, 1.0f, 0.0f, 1.0f },
{mWidth - 0.5f, 0 - 0.5f, 0.0f, 1.0f, xscale, 1.0f },
{mWidth - 0.5f, mHeight - 0.5f, 0.0f, 1.0f, xscale, 1.0f-yscale},
{ 0 - 0.5f, mHeight - 0.5f, 0.0f, 1.0f, 0.0f, 1.0f-yscale}}; // x, y, z, rhw, u, v
mDisplay->startScene();
device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, quad, 6 * sizeof(float));
flipTexture->Release();
textureSurface->Release();
restoreState(device);
mDisplay->endScene();
HRESULT result = mSwapChain->Present(NULL, NULL, NULL, NULL, 0);
if (result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY || result == D3DERR_DRIVERINTERNALERROR)
{
return error(EGL_BAD_ALLOC, false);
}
if (result == D3DERR_DEVICELOST)
{
return error(EGL_CONTEXT_LOST, false);
}
ASSERT(SUCCEEDED(result));
}
return true;
}
EGLint Surface::getWidth() const
{
return mWidth;
}
EGLint Surface::getHeight() const
{
return mHeight;
}
IDirect3DSurface9 *Surface::getRenderTarget()
{
if (mRenderTarget)
{
mRenderTarget->AddRef();
}
return mRenderTarget;
}
IDirect3DSurface9 *Surface::getDepthStencil()
{
if (mDepthStencil)
{
mDepthStencil->AddRef();
}
return mDepthStencil;
}
void Surface::setSwapInterval(EGLint interval)
{
if (mSwapInterval == interval)
{
return;
}
mSwapInterval = interval;
mSwapInterval = std::max(mSwapInterval, mDisplay->getMinSwapInterval());
mSwapInterval = std::min(mSwapInterval, mDisplay->getMaxSwapInterval());
mPresentInterval = convertInterval(mSwapInterval);
mPresentIntervalDirty = true;
}
}