1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-03-14 22:29:15 +01:00
dxvk/src/d3d8/d3d8_device.cpp

2180 lines
72 KiB
C++
Raw Normal View History

#include "d3d8_device.h"
#include "d3d8_interface.h"
#include "d3d8_shader.h"
#ifdef MSC_VER
#pragma fenv_access (on)
#endif
namespace dxvk {
static constexpr DWORD isFVF(DWORD Handle) {
return (Handle & D3DFVF_RESERVED0) == 0;
}
static constexpr DWORD getShaderHandle(DWORD Index) {
return (Index << 1) | D3DFVF_RESERVED0;
}
static constexpr DWORD getShaderIndex(DWORD Handle) {
if ((Handle & D3DFVF_RESERVED0) != 0) {
return ((Handle & ~(D3DFVF_RESERVED0)) >> 1) - 1;
} else {
return Handle;
}
}
struct D3D8VertexShaderInfo {
Com<d3d9::IDirect3DVertexDeclaration9> pVertexDecl;
Com<d3d9::IDirect3DVertexShader9> pVertexShader;
std::vector<DWORD> declaration;
std::vector<DWORD> function;
};
D3D8Device::D3D8Device(
D3D8Interface* pParent,
Com<d3d9::IDirect3DDevice9>&& pDevice,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS* pParams)
: D3D8DeviceBase(std::move(pDevice))
, m_d3d8Options(pParent->GetOptions())
, m_parent(pParent)
, m_presentParams(*pParams)
, m_deviceType(DeviceType)
, m_window(hFocusWindow)
, m_behaviorFlags(BehaviorFlags)
, m_multithread(BehaviorFlags & D3DCREATE_MULTITHREADED) {
// Get the bridge interface to D3D9.
2025-03-07 11:28:46 +02:00
if (FAILED(GetD3D9()->QueryInterface(__uuidof(IDxvkD3D8Bridge), reinterpret_cast<void**>(&m_bridge)))) {
throw DxvkError("D3D8Device: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!");
}
ResetState();
RecreateBackBuffersAndAutoDepthStencil();
if (m_d3d8Options.batching)
m_batcher = new D3D8Batcher(this, GetD3D9());
d3d9::D3DCAPS9 caps9;
HRESULT res = GetD3D9()->GetDeviceCaps(&caps9);
if (unlikely(SUCCEEDED(res) && caps9.PixelShaderVersion == D3DPS_VERSION(0, 0)))
m_isFixedFunctionOnly = true;
}
D3D8Device::~D3D8Device() {
if (m_batcher)
delete m_batcher;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetInfo(DWORD DevInfoID, void* pDevInfoStruct, DWORD DevInfoStructSize) {
Logger::debug(str::format("D3D8Device::GetInfo: ", DevInfoID));
if (unlikely(pDevInfoStruct == nullptr || DevInfoStructSize == 0))
return D3DERR_INVALIDCALL;
HRESULT res;
Com<d3d9::IDirect3DQuery9> pQuery;
switch (DevInfoID) {
// pre-D3D8 queries
case 0:
case D3DDEVINFOID_TEXTUREMANAGER:
case D3DDEVINFOID_D3DTEXTUREMANAGER:
case D3DDEVINFOID_TEXTURING:
return E_FAIL;
case D3DDEVINFOID_VCACHE:
// The query will return D3D_OK on Nvidia and D3DERR_NOTAVAILABLE on AMD/Intel
// in D3D9, however in the case of the latter we'll need to return a
// zeroed out query result and S_FALSE. This behavior has been observed both
// on modern native AMD drivers and D3D8-era native ATI drivers.
res = GetD3D9()->CreateQuery(d3d9::D3DQUERYTYPE_VCACHE, &pQuery);
struct D3DDEVINFO_VCACHE {
DWORD Pattern;
DWORD OptMethod;
DWORD CacheSize;
DWORD MagicNumber;
};
if (FAILED(res)) {
// The struct size needs to be at least equal or larger
if (DevInfoStructSize < sizeof(D3DDEVINFO_VCACHE))
return D3DERR_INVALIDCALL;
memset(pDevInfoStruct, 0, sizeof(D3DDEVINFO_VCACHE));
return S_FALSE;
}
break;
case D3DDEVINFOID_RESOURCEMANAGER: // Not yet implemented by D9VK.
res = GetD3D9()->CreateQuery(d3d9::D3DQUERYTYPE_RESOURCEMANAGER, &pQuery);
break;
case D3DDEVINFOID_VERTEXSTATS: // Not yet implemented by D9VK.
res = GetD3D9()->CreateQuery(d3d9::D3DQUERYTYPE_VERTEXSTATS, &pQuery);
break;
default:
Logger::warn(str::format("D3D8Device::GetInfo: Unsupported device info ID: ", DevInfoID));
return E_FAIL;
}
if (FAILED(res)) {
if (res == D3DERR_NOTAVAILABLE) // unsupported
return E_FAIL;
else // any unknown error
return S_FALSE;
}
if (pQuery != nullptr) {
// Immediately issue the query. D3D9 will begin it automatically before ending.
pQuery->Issue(D3DISSUE_END);
// TODO: Will immediately issuing the query actually yield meaingful results?
//
// Only relevant once RESOURCEMANAGER or VERTEXSTATS are implemented by D9VK,
// since VCACHE queries will immediately return data during this call.
res = pQuery->GetData(pDevInfoStruct, DevInfoStructSize, D3DGETDATA_FLUSH);
}
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::TestCooperativeLevel() {
// Equivalent of D3D11/DXGI present tests.
return GetD3D9()->TestCooperativeLevel();
}
UINT STDMETHODCALLTYPE D3D8Device::GetAvailableTextureMem() {
return GetD3D9()->GetAvailableTextureMem();
}
HRESULT STDMETHODCALLTYPE D3D8Device::ResourceManagerDiscardBytes(DWORD bytes) {
return GetD3D9()->EvictManagedResources();
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetDirect3D(IDirect3D8** ppD3D8) {
if (ppD3D8 == nullptr)
return D3DERR_INVALIDCALL;
*ppD3D8 = m_parent.ref();
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetDeviceCaps(D3DCAPS8* pCaps) {
d3d9::D3DCAPS9 caps9;
HRESULT res = GetD3D9()->GetDeviceCaps(&caps9);
if (likely(SUCCEEDED(res)))
ConvertCaps8(caps9, pCaps);
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetDisplayMode(D3DDISPLAYMODE* pMode) {
// swap chain 0
return GetD3D9()->GetDisplayMode(0, (d3d9::D3DDISPLAYMODE*)pMode);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS* pParameters) {
return GetD3D9()->GetCreationParameters((d3d9::D3DDEVICE_CREATION_PARAMETERS*)pParameters);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetCursorProperties(
UINT XHotSpot,
UINT YHotSpot,
IDirect3DSurface8* pCursorBitmap) {
D3D8Surface* surf = static_cast<D3D8Surface*>(pCursorBitmap);
return GetD3D9()->SetCursorProperties(XHotSpot, YHotSpot, D3D8Surface::GetD3D9Nullable(surf));
}
void STDMETHODCALLTYPE D3D8Device::SetCursorPosition(UINT XScreenSpace, UINT YScreenSpace, DWORD Flags) {
GetD3D9()->SetCursorPosition(XScreenSpace, YScreenSpace, Flags);
}
// Microsoft d3d8.h in the DirectX 9 SDK uses a different function signature...
void STDMETHODCALLTYPE D3D8Device::SetCursorPosition(int X, int Y, DWORD Flags) {
GetD3D9()->SetCursorPosition(X, Y, Flags);
}
BOOL STDMETHODCALLTYPE D3D8Device::ShowCursor(BOOL bShow) {
return GetD3D9()->ShowCursor(bShow);
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateAdditionalSwapChain(
D3DPRESENT_PARAMETERS* pPresentationParameters,
IDirect3DSwapChain8** ppSwapChain) {
InitReturnPtr(ppSwapChain);
if (unlikely(pPresentationParameters == nullptr || ppSwapChain == nullptr))
return D3DERR_INVALIDCALL;
Com<d3d9::IDirect3DSwapChain9> pSwapChain9;
d3d9::D3DPRESENT_PARAMETERS params = ConvertPresentParameters9(pPresentationParameters);
HRESULT res = GetD3D9()->CreateAdditionalSwapChain(
&params,
&pSwapChain9
);
if (likely(SUCCEEDED(res)))
*ppSwapChain = ref(new D3D8SwapChain(this, pPresentationParameters, std::move(pSwapChain9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::Reset(D3DPRESENT_PARAMETERS* pPresentationParameters) {
D3D8DeviceLock lock = LockDevice();
StateChange();
if (unlikely(pPresentationParameters == nullptr))
return D3DERR_INVALIDCALL;
// D3DSWAPEFFECT_COPY can not be used with more than one back buffer.
// This is also technically true for D3DSWAPEFFECT_COPY_VSYNC, however
// RC Cars depends on it not being rejected.
if (unlikely(pPresentationParameters->SwapEffect == D3DSWAPEFFECT_COPY
&& pPresentationParameters->BackBufferCount > 1))
return D3DERR_INVALIDCALL;
// In D3D8 nothing except D3DPRESENT_INTERVAL_DEFAULT can be used
// as a flag for windowed presentation.
if (unlikely(pPresentationParameters->Windowed
&& pPresentationParameters->FullScreen_PresentationInterval != D3DPRESENT_INTERVAL_DEFAULT))
return D3DERR_INVALIDCALL;
m_presentParams = *pPresentationParameters;
ResetState();
d3d9::D3DPRESENT_PARAMETERS params = ConvertPresentParameters9(pPresentationParameters);
HRESULT res = GetD3D9()->Reset(&params);
if (likely(SUCCEEDED(res)))
RecreateBackBuffersAndAutoDepthStencil();
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::Present(
const RECT* pSourceRect,
const RECT* pDestRect,
HWND hDestWindowOverride,
const RGNDATA* pDirtyRegion) {
D3D8DeviceLock lock = LockDevice();
m_batcher->EndFrame();
StateChange();
return GetD3D9()->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetBackBuffer(
UINT iBackBuffer,
D3DBACKBUFFER_TYPE Type,
IDirect3DSurface8** ppBackBuffer) {
D3D8DeviceLock lock = LockDevice();
InitReturnPtr(ppBackBuffer);
if (unlikely(ppBackBuffer == nullptr))
return D3DERR_INVALIDCALL;
if (iBackBuffer >= m_backBuffers.size() || m_backBuffers[iBackBuffer] == nullptr) {
Com<d3d9::IDirect3DSurface9> pSurface9;
HRESULT res = GetD3D9()->GetBackBuffer(0, iBackBuffer, (d3d9::D3DBACKBUFFER_TYPE)Type, &pSurface9);
if (likely(SUCCEEDED(res))) {
m_backBuffers[iBackBuffer] = new D3D8Surface(this, D3DPOOL_DEFAULT, std::move(pSurface9));
*ppBackBuffer = m_backBuffers[iBackBuffer].ref();
}
return res;
}
*ppBackBuffer = m_backBuffers[iBackBuffer].ref();
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetRasterStatus(D3DRASTER_STATUS* pRasterStatus) {
return GetD3D9()->GetRasterStatus(0, reinterpret_cast<d3d9::D3DRASTER_STATUS*>(pRasterStatus));
}
void STDMETHODCALLTYPE D3D8Device::SetGammaRamp(DWORD Flags, const D3DGAMMARAMP* pRamp) {
StateChange();
// For swap chain 0
GetD3D9()->SetGammaRamp(0, Flags, reinterpret_cast<const d3d9::D3DGAMMARAMP*>(pRamp));
}
void STDMETHODCALLTYPE D3D8Device::GetGammaRamp(D3DGAMMARAMP* pRamp) {
// For swap chain 0
GetD3D9()->GetGammaRamp(0, reinterpret_cast<d3d9::D3DGAMMARAMP*>(pRamp));
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateTexture(
UINT Width,
UINT Height,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DTexture8** ppTexture) {
// D3D8 returns D3DERR_INVALIDCALL for D3DFMT_UNKNOWN
// before clearing the content of ppTexture.
if (unlikely(Format == D3DFMT_UNKNOWN))
return D3DERR_INVALIDCALL;
InitReturnPtr(ppTexture);
if (unlikely(ppTexture == nullptr))
return D3DERR_INVALIDCALL;
// Nvidia & Intel workaround for The Lord of the Rings: The Fellowship of the Ring
if (m_d3d8Options.placeP8InScratch && Format == D3DFMT_P8)
Pool = D3DPOOL_SCRATCH;
Com<d3d9::IDirect3DTexture9> pTex9 = nullptr;
HRESULT res = GetD3D9()->CreateTexture(
Width,
Height,
Levels,
Usage,
d3d9::D3DFORMAT(Format),
d3d9::D3DPOOL(Pool),
&pTex9,
NULL);
if (likely(SUCCEEDED(res)))
*ppTexture = ref(new D3D8Texture2D(this, Pool, std::move(pTex9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateVolumeTexture(
UINT Width,
UINT Height,
UINT Depth,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DVolumeTexture8** ppVolumeTexture) {
// D3D8 returns D3DERR_INVALIDCALL for D3DFMT_UNKNOWN
// before clearing the content of ppVolumeTexture.
if (unlikely(Format == D3DFMT_UNKNOWN))
return D3DERR_INVALIDCALL;
InitReturnPtr(ppVolumeTexture);
if (unlikely(ppVolumeTexture == nullptr))
return D3DERR_INVALIDCALL;
Com<d3d9::IDirect3DVolumeTexture9> pVolume9 = nullptr;
HRESULT res = GetD3D9()->CreateVolumeTexture(
Width, Height, Depth, Levels,
Usage,
d3d9::D3DFORMAT(Format),
d3d9::D3DPOOL(Pool),
&pVolume9,
NULL);
if (likely(SUCCEEDED(res)))
*ppVolumeTexture = ref(new D3D8Texture3D(this, Pool, std::move(pVolume9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateCubeTexture(
UINT EdgeLength,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DCubeTexture8** ppCubeTexture) {
// D3D8 returns D3DERR_INVALIDCALL for D3DFMT_UNKNOWN
// before clearing the content of ppCubeTexture.
if (unlikely(Format == D3DFMT_UNKNOWN))
return D3DERR_INVALIDCALL;
InitReturnPtr(ppCubeTexture);
if (unlikely(ppCubeTexture == nullptr))
return D3DERR_INVALIDCALL;
Com<d3d9::IDirect3DCubeTexture9> pCube9 = nullptr;
HRESULT res = GetD3D9()->CreateCubeTexture(
EdgeLength,
Levels,
Usage,
d3d9::D3DFORMAT(Format),
d3d9::D3DPOOL(Pool),
&pCube9,
NULL);
if (likely(SUCCEEDED(res)))
*ppCubeTexture = ref(new D3D8TextureCube(this, Pool, std::move(pCube9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateVertexBuffer(
UINT Length,
DWORD Usage,
DWORD FVF,
D3DPOOL Pool,
IDirect3DVertexBuffer8** ppVertexBuffer) {
InitReturnPtr(ppVertexBuffer);
if (unlikely(ppVertexBuffer == nullptr))
return D3DERR_INVALIDCALL;
if (ShouldBatch()) {
*ppVertexBuffer = m_batcher->CreateVertexBuffer(Length, Usage, FVF, Pool);
return D3D_OK;
}
Com<d3d9::IDirect3DVertexBuffer9> pVertexBuffer9 = nullptr;
HRESULT res = GetD3D9()->CreateVertexBuffer(Length, Usage, FVF, d3d9::D3DPOOL(Pool), &pVertexBuffer9, NULL);
if (likely(SUCCEEDED(res)))
*ppVertexBuffer = ref(new D3D8VertexBuffer(this, std::move(pVertexBuffer9), Pool, Usage));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateIndexBuffer(
UINT Length,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DIndexBuffer8** ppIndexBuffer) {
InitReturnPtr(ppIndexBuffer);
if (unlikely(ppIndexBuffer == nullptr))
return D3DERR_INVALIDCALL;
Com<d3d9::IDirect3DIndexBuffer9> pIndexBuffer9 = nullptr;
HRESULT res = GetD3D9()->CreateIndexBuffer(Length, Usage, d3d9::D3DFORMAT(Format), d3d9::D3DPOOL(Pool), &pIndexBuffer9, NULL);
if (likely(SUCCEEDED(res)))
*ppIndexBuffer = ref(new D3D8IndexBuffer(this, std::move(pIndexBuffer9), Pool, Usage));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateRenderTarget(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DMULTISAMPLE_TYPE MultiSample,
BOOL Lockable,
IDirect3DSurface8** ppSurface) {
// D3D8 returns D3DERR_INVALIDCALL for D3DFMT_UNKNOWN
// before clearing the content of ppSurface.
if (unlikely(Format == D3DFMT_UNKNOWN))
return D3DERR_INVALIDCALL;
InitReturnPtr(ppSurface);
if (unlikely(ppSurface == nullptr))
return D3DERR_INVALIDCALL;
Com<d3d9::IDirect3DSurface9> pSurf9 = nullptr;
HRESULT res = GetD3D9()->CreateRenderTarget(
Width,
Height,
d3d9::D3DFORMAT(Format),
d3d9::D3DMULTISAMPLE_TYPE(MultiSample),
0,
Lockable,
&pSurf9,
NULL);
if (likely(SUCCEEDED(res)))
*ppSurface = ref(new D3D8Surface(this, D3DPOOL_DEFAULT, std::move(pSurf9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateDepthStencilSurface(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DMULTISAMPLE_TYPE MultiSample,
IDirect3DSurface8** ppSurface) {
// D3D8 returns D3DERR_INVALIDCALL for D3DFMT_UNKNOWN
// before clearing the content of ppSurface.
if (unlikely(Format == D3DFMT_UNKNOWN))
return D3DERR_INVALIDCALL;
InitReturnPtr(ppSurface);
if (unlikely(ppSurface == nullptr))
return D3DERR_INVALIDCALL;
Com<d3d9::IDirect3DSurface9> pSurf9 = nullptr;
HRESULT res = GetD3D9()->CreateDepthStencilSurface(
Width,
Height,
d3d9::D3DFORMAT(Format),
d3d9::D3DMULTISAMPLE_TYPE(MultiSample),
0,
FALSE, // z-buffer discarding is not used in D3D8
&pSurf9,
NULL);
if (likely(SUCCEEDED(res)))
*ppSurface = ref(new D3D8Surface(this, D3DPOOL_DEFAULT, std::move(pSurf9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateImageSurface(
UINT Width,
UINT Height,
D3DFORMAT Format,
IDirect3DSurface8** ppSurface) {
// Only D3D8 CreateImageSurface clears the content of ppSurface
// before checking if Format is equal to D3DFMT_UNKNOWN.
InitReturnPtr(ppSurface);
if (unlikely(Format == D3DFMT_UNKNOWN))
return D3DERR_INVALIDCALL;
if (unlikely(ppSurface == nullptr))
return D3DERR_INVALIDCALL;
D3DPOOL pool = isUnsupportedSurfaceFormat(Format) ? D3DPOOL_SCRATCH : D3DPOOL_SYSTEMMEM;
Com<d3d9::IDirect3DSurface9> pSurf = nullptr;
HRESULT res = GetD3D9()->CreateOffscreenPlainSurface(
Width,
Height,
d3d9::D3DFORMAT(Format),
d3d9::D3DPOOL(pool),
&pSurf,
NULL);
if (likely(SUCCEEDED(res)))
*ppSurface = ref(new D3D8Surface(this, pool, std::move(pSurf)));
return res;
}
// Copies texture rect in system mem using memcpy.
// Rects must be congruent, but need not be aligned.
HRESULT copyTextureBuffers(
D3D8Surface* src,
D3D8Surface* dst,
const d3d9::D3DSURFACE_DESC& srcDesc,
const d3d9::D3DSURFACE_DESC& dstDesc,
const RECT& srcRect,
const RECT& dstRect) {
HRESULT res = D3D_OK;
D3DLOCKED_RECT srcLocked, dstLocked;
bool compressed = isDXT(srcDesc.Format);
res = src->LockRect(&srcLocked, &srcRect, D3DLOCK_READONLY);
2025-03-07 11:28:46 +02:00
if (unlikely(FAILED(res)))
return res;
res = dst->LockRect(&dstLocked, &dstRect, 0);
2025-03-07 11:28:46 +02:00
if (unlikely(FAILED(res))) {
src->UnlockRect();
return res;
}
auto rows = srcRect.bottom - srcRect.top;
auto cols = srcRect.right - srcRect.left;
auto bpp = srcLocked.Pitch / srcDesc.Width;
if (!compressed
&& srcRect.left == 0
&& srcRect.right == LONG(srcDesc.Width)
&& srcDesc.Width == dstDesc.Width
&& srcLocked.Pitch == dstLocked.Pitch) {
// If copying the entire texture into a congruent destination,
// we can do this in one continuous copy.
std::memcpy(dstLocked.pBits, srcLocked.pBits, srcLocked.Pitch * rows);
} else {
// Bytes per row of the rect
auto amplitude = cols * bpp;
// Handle DXT compressed textures.
if (compressed) {
// DXT blocks are always 4x4 pixels.
constexpr UINT blockWidth = 4;
constexpr UINT blockHeight = 4;
// Compute rect dimensions in 4x4 blocks
UINT rectWidthBlocks = cols / blockWidth;
UINT rectHeightBlocks = rows / blockHeight;
// Compute total texture width in blocks
// to derive block size in bytes using the pitch.
UINT texWidthBlocks = std::max(srcDesc.Width / blockWidth, 1u);
UINT bytesPerBlock = srcLocked.Pitch / texWidthBlocks;
// Copy H/4 rows of W/4 blocks
amplitude = rectWidthBlocks * bytesPerBlock;
rows = rectHeightBlocks;
}
// Copy one row at a time
size_t srcOffset = 0, dstOffset = 0;
for (auto i = 0; i < rows; i++) {
std::memcpy(
2025-03-07 11:28:46 +02:00
reinterpret_cast<uint8_t*>(dstLocked.pBits) + dstOffset,
reinterpret_cast<uint8_t*>(srcLocked.pBits) + srcOffset,
amplitude);
srcOffset += srcLocked.Pitch;
dstOffset += dstLocked.Pitch;
}
}
res = dst->UnlockRect();
2025-03-07 11:28:46 +02:00
if (unlikely(FAILED(res))) {
src->UnlockRect();
return res;
}
res = src->UnlockRect();
2025-03-07 11:28:46 +02:00
return res;
}
/**
* \brief D3D8 CopyRects implementation
2024-07-08 23:55:44 +02:00
*
* \details
* The following table shows the possible combinations of source
* and destination surface pools, and how we handle each of them.
2024-07-08 23:55:44 +02:00
*
*
* Src/Dst DEFAULT MANAGED SYSTEMMEM SCRATCH
*
* DEFAULT StretchRect GetRenderTargetData GetRenderTargetData GetRenderTargetData
* MANAGED UpdateTextureFromBuffer memcpy memcpy memcpy
* SYSTEMMEM UpdateSurface memcpy memcpy memcpy
* SCRATCH memcpy + UpdateSurface memcpy memcpy memcpy
*
*/
HRESULT STDMETHODCALLTYPE D3D8Device::CopyRects(
IDirect3DSurface8* pSourceSurface,
const RECT* pSourceRectsArray,
UINT cRects,
IDirect3DSurface8* pDestinationSurface,
const POINT* pDestPointsArray) {
D3D8DeviceLock lock = LockDevice();
// The source and destination surfaces can not be identical.
if (unlikely(pSourceSurface == nullptr ||
pDestinationSurface == nullptr ||
pSourceSurface == pDestinationSurface)) {
return D3DERR_INVALIDCALL;
}
// TODO: No stretching or clipping of either source or destination rectangles.
2024-07-08 23:55:44 +02:00
// All src/dest rectangles must fit within the dest surface.
Com<D3D8Surface> src = static_cast<D3D8Surface*>(pSourceSurface);
Com<D3D8Surface> dst = static_cast<D3D8Surface*>(pDestinationSurface);
d3d9::D3DSURFACE_DESC srcDesc, dstDesc;
src->GetD3D9()->GetDesc(&srcDesc);
dst->GetD3D9()->GetDesc(&dstDesc);
// This method does not support format conversion.
if (unlikely(srcDesc.Format != dstDesc.Format))
return D3DERR_INVALIDCALL;
// This method cannot be applied to surfaces whose formats
// are classified as depth stencil formats.
2024-09-26 15:44:45 +03:00
if (unlikely(isDepthStencilFormat(D3DFORMAT(srcDesc.Format))))
return D3DERR_INVALIDCALL;
StateChange();
// If pSourceRectsArray is NULL, then the entire surface is copied
RECT rect;
POINT point = { 0, 0 };
if (pSourceRectsArray == NULL) {
cRects = 1;
rect.top = rect.left = 0;
rect.right = srcDesc.Width;
rect.bottom = srcDesc.Height;
pSourceRectsArray = &rect;
pDestPointsArray = &point;
}
for (UINT i = 0; i < cRects; i++) {
RECT srcRect, dstRect;
srcRect = pSourceRectsArray[i];
// True if the copy is asymmetric
bool asymmetric = true;
// True if the copy requires stretching (not technically supported)
bool stretch = true;
// True if the copy is not perfectly aligned (supported)
bool offset = true;
if (pDestPointsArray != NULL) {
dstRect.left = pDestPointsArray[i].x;
dstRect.right = dstRect.left + (srcRect.right - srcRect.left);
dstRect.top = pDestPointsArray[i].y;
dstRect.bottom = dstRect.top + (srcRect.bottom - srcRect.top);
asymmetric = dstRect.left != srcRect.left || dstRect.top != srcRect.top
|| dstRect.right != srcRect.right || dstRect.bottom != srcRect.bottom;
2024-07-08 23:55:44 +02:00
stretch = (dstRect.right-dstRect.left) != (srcRect.right-srcRect.left)
|| (dstRect.bottom-dstRect.top) != (srcRect.bottom-srcRect.top);
2024-07-08 23:55:44 +02:00
offset = !stretch && asymmetric;
} else {
dstRect = srcRect;
asymmetric = stretch = offset = false;
}
POINT dstPt = { dstRect.left, dstRect.top };
auto unsupported = [&] {
Logger::err(str::format("D3D8Device::CopyRects: Unsupported case from src pool ", srcDesc.Pool, " to dst pool ", dstDesc.Pool));
return D3DERR_INVALIDCALL;
};
2024-07-08 23:55:44 +02:00
auto logError = [&] (HRESULT res) {
if (FAILED(res)) {
// Only a debug message because some games mess up CopyRects every frame in a way
// that fails on native too but are perfectly fine with it.
Logger::debug(str::format("D3D8Device::CopyRects: Failed to copy from src pool ", srcDesc.Pool, " to dst pool ", dstDesc.Pool));
2024-07-08 23:55:44 +02:00
}
return res;
};
switch (dstDesc.Pool) {
2024-07-08 23:55:44 +02:00
// Dest: DEFAULT
2024-07-08 23:55:44 +02:00
case d3d9::D3DPOOL_DEFAULT:
switch (srcDesc.Pool) {
2024-07-08 23:55:44 +02:00
case d3d9::D3DPOOL_DEFAULT: {
// DEFAULT -> DEFAULT: use StretchRect
2024-07-08 23:55:44 +02:00
return logError(GetD3D9()->StretchRect(
src->GetD3D9(),
&srcRect,
dst->GetD3D9(),
&dstRect,
d3d9::D3DTEXF_NONE
2024-07-08 23:55:44 +02:00
));
}
2024-07-08 23:55:44 +02:00
case d3d9::D3DPOOL_MANAGED: {
// MANAGED -> DEFAULT: UpdateTextureFromBuffer
2024-07-08 23:55:44 +02:00
return logError(m_bridge->UpdateTextureFromBuffer(
src->GetD3D9(),
dst->GetD3D9(),
&srcRect,
&dstPt
2024-07-08 23:55:44 +02:00
));
}
2024-07-08 23:55:44 +02:00
case d3d9::D3DPOOL_SYSTEMMEM: {
// SYSTEMMEM -> DEFAULT: use UpdateSurface
2024-07-08 23:55:44 +02:00
return logError(GetD3D9()->UpdateSurface(
src->GetD3D9(),
&srcRect,
dst->GetD3D9(),
&dstPt
2024-07-08 23:55:44 +02:00
));
}
case d3d9::D3DPOOL_SCRATCH: {
// SCRATCH -> DEFAULT: memcpy to a SYSTEMMEM temporary buffer and use UpdateSurface
// UpdateSurface will not work on surface formats unsupported by D3DPOOL_DEFAULT
if (unlikely(isUnsupportedSurfaceFormat(D3DFORMAT(srcDesc.Format)))) {
return logError(D3DERR_INVALIDCALL);
}
Com<IDirect3DSurface8> pTempImageSurface;
// The temporary image surface is guaranteed to end up in SYSTEMMEM for supported formats
HRESULT res = CreateImageSurface(
srcDesc.Width,
srcDesc.Height,
D3DFORMAT(srcDesc.Format),
&pTempImageSurface
);
if (FAILED(res)) {
return logError(res);
}
Com<D3D8Surface> pBlitImage = static_cast<D3D8Surface*>(pTempImageSurface.ptr());
// Temporary image surface dimensions are identical, so we can reuse srcDesc/Rect
res = copyTextureBuffers(src.ptr(), pBlitImage.ptr(), srcDesc, srcDesc, srcRect, srcRect);
if (FAILED(res)) {
return logError(res);
}
return logError(GetD3D9()->UpdateSurface(
pBlitImage->GetD3D9(),
&srcRect,
dst->GetD3D9(),
&dstPt
));
}
default: {
return unsupported();
}
} break;
2024-07-08 23:55:44 +02:00
// Dest: MANAGED
2024-07-08 23:55:44 +02:00
case d3d9::D3DPOOL_MANAGED:
switch (srcDesc.Pool) {
// TODO: Copy on GPU (handle MANAGED similarly to SYSTEMMEM for now)
2024-07-08 23:55:44 +02:00
case d3d9::D3DPOOL_DEFAULT: {
// Get temporary off-screen surface for stretching.
Com<d3d9::IDirect3DSurface9> pBlitImage = dst->GetBlitImage();
// Stretch the source RT to the temporary surface.
2024-07-08 23:55:44 +02:00
HRESULT res = GetD3D9()->StretchRect(
src->GetD3D9(),
&srcRect,
pBlitImage.ptr(),
&dstRect,
d3d9::D3DTEXF_NONE);
2024-07-08 23:55:44 +02:00
if (FAILED(res)) {
2024-07-08 23:55:44 +02:00
return logError(res);
}
// Now sync the rendertarget data into main memory.
2024-07-08 23:55:44 +02:00
return logError(GetD3D9()->GetRenderTargetData(pBlitImage.ptr(), dst->GetD3D9()));
}
2024-07-08 23:55:44 +02:00
case d3d9::D3DPOOL_MANAGED:
case d3d9::D3DPOOL_SYSTEMMEM:
case d3d9::D3DPOOL_SCRATCH: {
// MANAGED/SYSMEM/SCRATCH -> MANAGED: LockRect / memcpy
2024-07-08 23:55:44 +02:00
if (stretch) {
2024-07-08 23:55:44 +02:00
return logError(D3DERR_INVALIDCALL);
}
2024-07-08 23:55:44 +02:00
return logError(copyTextureBuffers(src.ptr(), dst.ptr(), srcDesc, dstDesc, srcRect, dstRect));
}
default: {
return unsupported();
}
} break;
2024-07-08 23:55:44 +02:00
// DEST: SYSTEMMEM
2024-07-08 23:55:44 +02:00
case d3d9::D3DPOOL_SYSTEMMEM: {
// RT (DEFAULT) -> SYSTEMMEM: Use GetRenderTargetData as fast path if possible
if ((srcDesc.Usage & D3DUSAGE_RENDERTARGET || m_renderTarget == src.ptr())) {
// GetRenderTargetData works if the formats and sizes match
if (srcDesc.MultiSampleType == d3d9::D3DMULTISAMPLE_NONE
&& srcDesc.Width == dstDesc.Width
&& srcDesc.Height == dstDesc.Height
&& srcDesc.Format == dstDesc.Format
&& !asymmetric) {
2024-07-08 23:55:44 +02:00
return logError(GetD3D9()->GetRenderTargetData(src->GetD3D9(), dst->GetD3D9()));
}
}
switch (srcDesc.Pool) {
2024-07-08 23:55:44 +02:00
case d3d9::D3DPOOL_DEFAULT: {
// Get temporary off-screen surface for stretching.
Com<d3d9::IDirect3DSurface9> pBlitImage = dst->GetBlitImage();
// Stretch the source RT to the temporary surface.
2024-07-08 23:55:44 +02:00
HRESULT res = GetD3D9()->StretchRect(
src->GetD3D9(),
&srcRect,
pBlitImage.ptr(),
&dstRect,
d3d9::D3DTEXF_NONE);
if (FAILED(res)) {
2024-07-08 23:55:44 +02:00
return logError(res);
}
// Now sync the rendertarget data into main memory.
2024-07-08 23:55:44 +02:00
return logError(GetD3D9()->GetRenderTargetData(pBlitImage.ptr(), dst->GetD3D9()));
}
// MANAGED/SYSMEM/SCRATCH -> SYSMEM: LockRect / memcpy
2024-07-08 23:55:44 +02:00
case d3d9::D3DPOOL_MANAGED:
case d3d9::D3DPOOL_SYSTEMMEM:
case d3d9::D3DPOOL_SCRATCH: {
if (stretch) {
2024-07-08 23:55:44 +02:00
return logError(D3DERR_INVALIDCALL);
}
2024-07-08 23:55:44 +02:00
return logError(copyTextureBuffers(src.ptr(), dst.ptr(), srcDesc, dstDesc, srcRect, dstRect));
}
default: {
return unsupported();
}
} break;
}
// DEST: SCRATCH
case d3d9::D3DPOOL_SCRATCH: {
// RT (DEFAULT) -> SCRATCH: Use GetRenderTargetData as fast path if possible
if ((srcDesc.Usage & D3DUSAGE_RENDERTARGET || m_renderTarget == src.ptr())) {
// GetRenderTargetData works if the formats and sizes match
if (srcDesc.MultiSampleType == d3d9::D3DMULTISAMPLE_NONE
&& srcDesc.Width == dstDesc.Width
&& srcDesc.Height == dstDesc.Height
&& srcDesc.Format == dstDesc.Format
&& !asymmetric) {
return logError(GetD3D9()->GetRenderTargetData(src->GetD3D9(), dst->GetD3D9()));
}
}
switch (srcDesc.Pool) {
case d3d9::D3DPOOL_DEFAULT: {
// Get temporary off-screen surface for stretching.
Com<d3d9::IDirect3DSurface9> pBlitImage = dst->GetBlitImage();
// Stretch the source RT to the temporary surface.
HRESULT res = GetD3D9()->StretchRect(
src->GetD3D9(),
&srcRect,
pBlitImage.ptr(),
&dstRect,
d3d9::D3DTEXF_NONE);
if (FAILED(res)) {
return logError(res);
}
// Now sync the rendertarget data into main memory.
return logError(GetD3D9()->GetRenderTargetData(pBlitImage.ptr(), dst->GetD3D9()));
}
// MANAGED/SYSMEM/SCRATCH -> SCRATCH: LockRect / memcpy
case d3d9::D3DPOOL_MANAGED:
case d3d9::D3DPOOL_SYSTEMMEM:
case d3d9::D3DPOOL_SCRATCH: {
if (stretch) {
return logError(D3DERR_INVALIDCALL);
}
return logError(copyTextureBuffers(src.ptr(), dst.ptr(), srcDesc, dstDesc, srcRect, dstRect));
}
default: {
return unsupported();
}
} break;
}
default: {
return unsupported();
}
}
}
2024-07-08 23:55:44 +02:00
return D3DERR_INVALIDCALL;
}
HRESULT STDMETHODCALLTYPE D3D8Device::UpdateTexture(
IDirect3DBaseTexture8* pSourceTexture,
IDirect3DBaseTexture8* pDestinationTexture) {
if (unlikely(pSourceTexture == nullptr || pDestinationTexture == nullptr))
return D3DERR_INVALIDCALL;
D3D8Texture2D* src = static_cast<D3D8Texture2D*>(pSourceTexture);
D3D8Texture2D* dst = static_cast<D3D8Texture2D*>(pDestinationTexture);
StateChange();
return GetD3D9()->UpdateTexture(D3D8Texture2D::GetD3D9Nullable(src), D3D8Texture2D::GetD3D9Nullable(dst));
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetFrontBuffer(IDirect3DSurface8* pDestSurface) {
if (unlikely(pDestSurface == nullptr))
return D3DERR_INVALIDCALL;
Com<D3D8Surface> surf = static_cast<D3D8Surface*>(pDestSurface);
StateChange();
// This actually gets a copy of the front buffer and writes it to pDestSurface
return GetD3D9()->GetFrontBufferData(0, D3D8Surface::GetD3D9Nullable(surf));
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetRenderTarget(IDirect3DSurface8* pRenderTarget, IDirect3DSurface8* pNewZStencil) {
D3D8DeviceLock lock = LockDevice();
HRESULT res;
if (pRenderTarget != nullptr) {
D3D8Surface* surf = static_cast<D3D8Surface*>(pRenderTarget);
// This will always be a state change and needs to be forwarded to
// D3D9, even when the same render target is set, as the viewport
// needs to be readjusted and reset.
StateChange();
res = GetD3D9()->SetRenderTarget(0, D3D8Surface::GetD3D9Nullable(surf));
if (unlikely(FAILED(res))) return res;
m_renderTarget = surf;
}
// SetDepthStencilSurface is a separate call
D3D8Surface* zStencil = static_cast<D3D8Surface*>(pNewZStencil);
// Depth stencil dimensions can not be lower than
// those of the currently set render target.
if (m_renderTarget != nullptr && zStencil != nullptr) {
D3DSURFACE_DESC rtDesc;
res = m_renderTarget->GetDesc(&rtDesc);
if (unlikely(FAILED(res))) return res;
D3DSURFACE_DESC dsDesc;
res = zStencil->GetDesc(&dsDesc);
if (unlikely(FAILED(res))) return res;
if (unlikely(dsDesc.Width < rtDesc.Width
|| dsDesc.Height < rtDesc.Height))
return D3DERR_INVALIDCALL;
}
StateChange();
res = GetD3D9()->SetDepthStencilSurface(D3D8Surface::GetD3D9Nullable(zStencil));
if (unlikely(FAILED(res))) return res;
m_depthStencil = zStencil;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetRenderTarget(IDirect3DSurface8** ppRenderTarget) {
D3D8DeviceLock lock = LockDevice();
InitReturnPtr(ppRenderTarget);
if (unlikely(ppRenderTarget == nullptr))
return D3DERR_INVALIDCALL;
if (unlikely(m_renderTarget == nullptr)) {
Com<d3d9::IDirect3DSurface9> pRT9 = nullptr;
HRESULT res = GetD3D9()->GetRenderTarget(0, &pRT9); // use RT index 0
if (likely(SUCCEEDED(res))) {
m_renderTarget = new D3D8Surface(this, D3DPOOL_DEFAULT, std::move(pRT9));
*ppRenderTarget = m_renderTarget.ref();
}
return res;
}
*ppRenderTarget = m_renderTarget.ref();
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetDepthStencilSurface(IDirect3DSurface8** ppZStencilSurface) {
D3D8DeviceLock lock = LockDevice();
InitReturnPtr(ppZStencilSurface);
if (unlikely(ppZStencilSurface == nullptr))
return D3DERR_INVALIDCALL;
if (unlikely(m_depthStencil == nullptr)) {
Com<d3d9::IDirect3DSurface9> pStencil9 = nullptr;
HRESULT res = GetD3D9()->GetDepthStencilSurface(&pStencil9);
if (likely(SUCCEEDED(res))) {
m_depthStencil = new D3D8Surface(this, D3DPOOL_DEFAULT, std::move(pStencil9));
*ppZStencilSurface = m_depthStencil.ref();
}
return res;
}
*ppZStencilSurface = m_depthStencil.ref();
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::BeginScene() { return GetD3D9()->BeginScene(); }
HRESULT STDMETHODCALLTYPE D3D8Device::EndScene() { StateChange(); return GetD3D9()->EndScene(); }
HRESULT STDMETHODCALLTYPE D3D8Device::Clear(
DWORD Count,
const D3DRECT* pRects,
DWORD Flags,
D3DCOLOR Color,
float Z,
DWORD Stencil) {
StateChange();
return GetD3D9()->Clear(Count, pRects, Flags, Color, Z, Stencil);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX* pMatrix) {
StateChange();
return GetD3D9()->SetTransform(d3d9::D3DTRANSFORMSTATETYPE(State), pMatrix);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX* pMatrix) {
return GetD3D9()->GetTransform(d3d9::D3DTRANSFORMSTATETYPE(State), pMatrix);
}
HRESULT STDMETHODCALLTYPE D3D8Device::MultiplyTransform(D3DTRANSFORMSTATETYPE TransformState, const D3DMATRIX* pMatrix) {
StateChange();
return GetD3D9()->MultiplyTransform(d3d9::D3DTRANSFORMSTATETYPE(TransformState), pMatrix);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetViewport(const D3DVIEWPORT8* pViewport) {
D3D8DeviceLock lock = LockDevice();
if (likely(pViewport != nullptr)) {
// We need a valid render target to validate the viewport
if (unlikely(m_renderTarget == nullptr))
return D3DERR_INVALIDCALL;
D3DSURFACE_DESC rtDesc;
HRESULT res = m_renderTarget->GetDesc(&rtDesc);
// D3D8 will fail when setting a viewport that's outside of the
// current render target, although this apparently works in D3D9
if (likely(SUCCEEDED(res)) &&
unlikely(pViewport->X + pViewport->Width > rtDesc.Width ||
pViewport->Y + pViewport->Height > rtDesc.Height)) {
// On Linux/Wine and in windowed mode, we can get in situations
// where the actual render target dimensions are off by one
// pixel to what the game sets them to. Allow this corner case
// to skip the validation, in order to prevent issues.
bool isOnePixelWider = pViewport->X + pViewport->Width == rtDesc.Width + 1;
bool isOnePixelTaller = pViewport->Y + pViewport->Height == rtDesc.Height + 1;
if (m_presentParams.Windowed && (isOnePixelWider || isOnePixelTaller)) {
Logger::debug("D3D8Device::SetViewport: Viewport exceeds render target dimensions by one pixel");
} else {
return D3DERR_INVALIDCALL;
}
}
}
StateChange();
return GetD3D9()->SetViewport(reinterpret_cast<const d3d9::D3DVIEWPORT9*>(pViewport));
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetViewport(D3DVIEWPORT8* pViewport) {
D3D8DeviceLock lock = LockDevice();
return GetD3D9()->GetViewport(reinterpret_cast<d3d9::D3DVIEWPORT9*>(pViewport));
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetMaterial(const D3DMATERIAL8* pMaterial) {
StateChange();
return GetD3D9()->SetMaterial((const d3d9::D3DMATERIAL9*)pMaterial);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetMaterial(D3DMATERIAL8* pMaterial) {
return GetD3D9()->GetMaterial((d3d9::D3DMATERIAL9*)pMaterial);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetLight(DWORD Index, const D3DLIGHT8* pLight) {
StateChange();
return GetD3D9()->SetLight(Index, (const d3d9::D3DLIGHT9*)pLight);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetLight(DWORD Index, D3DLIGHT8* pLight) {
return GetD3D9()->GetLight(Index, (d3d9::D3DLIGHT9*)pLight);
}
HRESULT STDMETHODCALLTYPE D3D8Device::LightEnable(DWORD Index, BOOL Enable) {
StateChange();
return GetD3D9()->LightEnable(Index, Enable);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetLightEnable(DWORD Index, BOOL* pEnable) {
return GetD3D9()->GetLightEnable(Index, pEnable);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetClipPlane(DWORD Index, const float* pPlane) {
StateChange();
return GetD3D9()->SetClipPlane(Index, pPlane);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetClipPlane(DWORD Index, float* pPlane) {
return GetD3D9()->GetClipPlane(Index, pPlane);
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateStateBlock(
D3DSTATEBLOCKTYPE Type,
DWORD* pToken) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(pToken == nullptr))
return D3DERR_INVALIDCALL;
// Applications cannot create a state block while another is being recorded
if (unlikely(ShouldRecord()))
return D3DERR_INVALIDCALL;
Com<d3d9::IDirect3DStateBlock9> pStateBlock9;
HRESULT res = GetD3D9()->CreateStateBlock(d3d9::D3DSTATEBLOCKTYPE(Type), &pStateBlock9);
if (likely(SUCCEEDED(res))) {
m_token++;
auto stateBlockIterPair = m_stateBlocks.emplace(std::piecewise_construct,
std::forward_as_tuple(m_token),
std::forward_as_tuple(this, Type, pStateBlock9.ref()));
*pToken = m_token;
// D3D8 state blocks automatically capture state on creation.
stateBlockIterPair.first->second.Capture();
}
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CaptureStateBlock(DWORD Token) {
D3D8DeviceLock lock = LockDevice();
// Applications cannot capture a state block while another is being recorded
if (unlikely(ShouldRecord()))
return D3DERR_INVALIDCALL;
auto stateBlockIter = m_stateBlocks.find(Token);
if (unlikely(stateBlockIter == m_stateBlocks.end())) {
Logger::warn(str::format("D3D8Device::CaptureStateBlock: Invalid token: ", std::hex, Token));
return D3D_OK;
}
return stateBlockIter->second.Capture();
}
HRESULT STDMETHODCALLTYPE D3D8Device::ApplyStateBlock(DWORD Token) {
D3D8DeviceLock lock = LockDevice();
// Applications cannot apply a state block while another is being recorded
if (unlikely(ShouldRecord()))
return D3DERR_INVALIDCALL;
StateChange();
auto stateBlockIter = m_stateBlocks.find(Token);
if (unlikely(stateBlockIter == m_stateBlocks.end())) {
Logger::warn(str::format("D3D8Device::ApplyStateBlock: Invalid token: ", std::hex, Token));
return D3D_OK;
}
return stateBlockIter->second.Apply();
}
HRESULT STDMETHODCALLTYPE D3D8Device::DeleteStateBlock(DWORD Token) {
D3D8DeviceLock lock = LockDevice();
// Applications cannot delete a state block while another is being recorded
if (unlikely(ShouldRecord()))
return D3DERR_INVALIDCALL;
auto stateBlockIter = m_stateBlocks.find(Token);
if (unlikely(stateBlockIter == m_stateBlocks.end())) {
Logger::warn(str::format("D3D8Device::DeleteStateBlock: Invalid token: ", std::hex, Token));
return D3D_OK;
}
m_stateBlocks.erase(stateBlockIter);
2024-07-10 00:51:46 +03:00
// native apparently does drop the token counter in
// situations where the token being removed is the
// last allocated token, which allows some reuse
if (m_token == Token)
m_token--;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::BeginStateBlock() {
D3D8DeviceLock lock = LockDevice();
if (unlikely(m_recorder != nullptr))
return D3DERR_INVALIDCALL;
HRESULT res = GetD3D9()->BeginStateBlock();
if (likely(SUCCEEDED(res))) {
m_token++;
auto stateBlockIterPair = m_stateBlocks.emplace(std::piecewise_construct,
std::forward_as_tuple(m_token),
std::forward_as_tuple(this));
m_recorder = &stateBlockIterPair.first->second;
m_recorderToken = m_token;
}
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::EndStateBlock(DWORD* pToken) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(pToken == nullptr || m_recorder == nullptr))
return D3DERR_INVALIDCALL;
Com<d3d9::IDirect3DStateBlock9> pStateBlock;
HRESULT res = GetD3D9()->EndStateBlock(&pStateBlock);
if (likely(SUCCEEDED(res))) {
m_recorder->SetD3D9(std::move(pStateBlock));
*pToken = m_recorderToken;
m_recorder = nullptr;
m_recorderToken = 0;
}
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetClipStatus(const D3DCLIPSTATUS8* pClipStatus) {
StateChange();
return GetD3D9()->SetClipStatus(reinterpret_cast<const d3d9::D3DCLIPSTATUS9*>(pClipStatus));
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetClipStatus(D3DCLIPSTATUS8* pClipStatus) {
return GetD3D9()->GetClipStatus(reinterpret_cast<d3d9::D3DCLIPSTATUS9*>(pClipStatus));
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetTexture(DWORD Stage, IDirect3DBaseTexture8** ppTexture) {
D3D8DeviceLock lock = LockDevice();
InitReturnPtr(ppTexture);
if (unlikely(ppTexture == nullptr))
return D3DERR_INVALIDCALL;
*ppTexture = m_textures[Stage].ref();
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetTexture(DWORD Stage, IDirect3DBaseTexture8* pTexture) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(Stage >= d8caps::MAX_TEXTURE_STAGES))
return D3DERR_INVALIDCALL;
if (unlikely(ShouldRecord()))
return m_recorder->SetTexture(Stage, pTexture);
D3D8Texture2D* tex = static_cast<D3D8Texture2D*>(pTexture);
// Splinter Cell: Force perspective divide when a shadow map is bound to slot 0
if (unlikely(m_d3d8Options.shadowPerspectiveDivide && Stage == 0)) {
if (tex) {
D3DSURFACE_DESC surf;
tex->GetLevelDesc(0, &surf);
if (isDepthStencilFormat(surf.Format)) {
// If we bound a depth texture to stage 0 then we need to set the projected flag for stage 0 and 1
// Stage 1 is a non-depth light cookie texture but still requires perspective divide to work
GetD3D9()->SetTextureStageState(0, d3d9::D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_PROJECTED);
GetD3D9()->SetTextureStageState(1, d3d9::D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_PROJECTED);
m_shadowPerspectiveDivide = true;
} else if (m_shadowPerspectiveDivide) {
// Non-depth texture bound. Game will reset the transform flags to 0 on its own
m_shadowPerspectiveDivide = false;
}
} else if (m_shadowPerspectiveDivide) {
// Texture unbound. Game will reset the transform flags to 0 on its own
m_shadowPerspectiveDivide = false;
}
}
if (unlikely(m_textures[Stage] == tex))
return D3D_OK;
StateChange();
HRESULT res = GetD3D9()->SetTexture(Stage, D3D8Texture2D::GetD3D9Nullable(tex));
if (likely(SUCCEEDED(res)))
m_textures[Stage] = tex;
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetTextureStageState(
DWORD Stage,
D3DTEXTURESTAGESTATETYPE Type,
DWORD* pValue) {
d3d9::D3DSAMPLERSTATETYPE stateType = GetSamplerStateType9(Type);
if (stateType != -1u) {
// if the type has been remapped to a sampler state type:
return GetD3D9()->GetSamplerState(Stage, stateType, pValue);
}
else {
return GetD3D9()->GetTextureStageState(Stage, d3d9::D3DTEXTURESTAGESTATETYPE(Type), pValue);
}
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetTextureStageState(
DWORD Stage,
D3DTEXTURESTAGESTATETYPE Type,
DWORD Value) {
d3d9::D3DSAMPLERSTATETYPE stateType = GetSamplerStateType9(Type);
if (unlikely(m_d3d8Options.shadowPerspectiveDivide && Type == D3DTSS_TEXTURETRANSFORMFLAGS)) {
// Splinter Cell: Ignore requests to change texture transform flags
// to 0 while shadow mapping perspective divide mode is enabled
if (m_shadowPerspectiveDivide && (Stage == 0 || Stage == 1))
return D3D_OK;
}
StateChange();
if (stateType != -1u) {
// if the type has been remapped to a sampler state type:
return GetD3D9()->SetSamplerState(Stage, stateType, Value);
} else {
return GetD3D9()->SetTextureStageState(Stage, d3d9::D3DTEXTURESTAGESTATETYPE(Type), Value);
}
}
HRESULT STDMETHODCALLTYPE D3D8Device::ValidateDevice(DWORD* pNumPasses) {
return GetD3D9()->ValidateDevice(pNumPasses);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetPaletteEntries(UINT PaletteNumber, const PALETTEENTRY* pEntries) {
StateChange();
return GetD3D9()->SetPaletteEntries(PaletteNumber, pEntries);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY* pEntries) {
return GetD3D9()->GetPaletteEntries(PaletteNumber, pEntries);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetCurrentTexturePalette(UINT PaletteNumber) {
StateChange();
return GetD3D9()->SetCurrentTexturePalette(PaletteNumber);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetCurrentTexturePalette(UINT* PaletteNumber) {
return GetD3D9()->GetCurrentTexturePalette(PaletteNumber);
}
HRESULT STDMETHODCALLTYPE D3D8Device::DrawPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
UINT StartVertex,
UINT PrimitiveCount) {
D3D8DeviceLock lock = LockDevice();
if (ShouldBatch())
return m_batcher->DrawPrimitive(PrimitiveType, StartVertex, PrimitiveCount);
return GetD3D9()->DrawPrimitive(d3d9::D3DPRIMITIVETYPE(PrimitiveType), StartVertex, PrimitiveCount);
}
HRESULT STDMETHODCALLTYPE D3D8Device::DrawIndexedPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
UINT MinVertexIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount) {
D3D8DeviceLock lock = LockDevice();
return GetD3D9()->DrawIndexedPrimitive(
d3d9::D3DPRIMITIVETYPE(PrimitiveType),
static_cast<INT>(std::min(m_baseVertexIndex, static_cast<UINT>(std::numeric_limits<int32_t>::max()))), // set by SetIndices
MinVertexIndex,
NumVertices,
StartIndex,
PrimitiveCount);
}
HRESULT STDMETHODCALLTYPE D3D8Device::DrawPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT PrimitiveCount,
const void* pVertexStreamZeroData,
UINT VertexStreamZeroStride) {
D3D8DeviceLock lock = LockDevice();
StateChange();
// Stream 0 is set to null by this call
m_streams[0] = D3D8VBO {nullptr, 0};
return GetD3D9()->DrawPrimitiveUP(d3d9::D3DPRIMITIVETYPE(PrimitiveType), PrimitiveCount, pVertexStreamZeroData, VertexStreamZeroStride);
}
HRESULT STDMETHODCALLTYPE D3D8Device::DrawIndexedPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT MinVertexIndex,
UINT NumVertices,
UINT PrimitiveCount,
const void* pIndexData,
D3DFORMAT IndexDataFormat,
const void* pVertexStreamZeroData,
UINT VertexStreamZeroStride) {
D3D8DeviceLock lock = LockDevice();
StateChange();
// Stream 0 and the index buffer are set to null by this call
m_streams[0] = D3D8VBO {nullptr, 0};
m_indices = nullptr;
m_baseVertexIndex = 0;
return GetD3D9()->DrawIndexedPrimitiveUP(
d3d9::D3DPRIMITIVETYPE(PrimitiveType),
MinVertexIndex,
NumVertices,
PrimitiveCount,
pIndexData,
d3d9::D3DFORMAT(IndexDataFormat),
pVertexStreamZeroData,
VertexStreamZeroStride);
}
HRESULT STDMETHODCALLTYPE D3D8Device::ProcessVertices(
UINT SrcStartIndex,
UINT DestIndex,
UINT VertexCount,
IDirect3DVertexBuffer8* pDestBuffer,
DWORD Flags) {
D3D8VertexBuffer* buffer = static_cast<D3D8VertexBuffer*>(pDestBuffer);
return GetD3D9()->ProcessVertices(
SrcStartIndex,
DestIndex,
VertexCount,
D3D8VertexBuffer::GetD3D9Nullable(buffer),
nullptr,
Flags
);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetVertexShaderConstant(
DWORD StartRegister,
const void* pConstantData,
DWORD ConstantCount) {
StateChange();
// ConstantCount is actually the same as Vector4fCount
return GetD3D9()->SetVertexShaderConstantF(StartRegister, reinterpret_cast<const float*>(pConstantData), ConstantCount);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShaderConstant(DWORD Register, void* pConstantData, DWORD ConstantCount) {
2025-03-07 11:28:46 +02:00
return GetD3D9()->GetVertexShaderConstantF(Register, reinterpret_cast<float*>(pConstantData), ConstantCount);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetStreamSource(
UINT StreamNumber,
IDirect3DVertexBuffer8* pStreamData,
UINT Stride) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(StreamNumber >= d8caps::MAX_STREAMS))
return D3DERR_INVALIDCALL;
if (unlikely(ShouldRecord()))
return m_recorder->SetStreamSource(StreamNumber, pStreamData, Stride);
D3D8VertexBuffer* buffer = static_cast<D3D8VertexBuffer*>(pStreamData);
HRESULT res = GetD3D9()->SetStreamSource(StreamNumber, D3D8VertexBuffer::GetD3D9Nullable(buffer), 0, Stride);
if (likely(SUCCEEDED(res))) {
if (ShouldBatch())
m_batcher->SetStream(StreamNumber, buffer, Stride);
m_streams[StreamNumber].buffer = buffer;
// The previous stride is preserved if pStreamData is NULL
if (likely(buffer != nullptr))
m_streams[StreamNumber].stride = Stride;
}
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetStreamSource(
UINT StreamNumber,
IDirect3DVertexBuffer8** ppStreamData,
UINT* pStride) {
D3D8DeviceLock lock = LockDevice();
InitReturnPtr(ppStreamData);
if (likely(pStride != nullptr))
*pStride = 0;
if (unlikely(ppStreamData == nullptr || pStride == nullptr))
return D3DERR_INVALIDCALL;
if (unlikely(StreamNumber >= d8caps::MAX_STREAMS))
return D3DERR_INVALIDCALL;
const D3D8VBO& vbo = m_streams[StreamNumber];
*ppStreamData = vbo.buffer.ref();
*pStride = vbo.stride;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetIndices(IDirect3DIndexBuffer8* pIndexData, UINT BaseVertexIndex) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(ShouldRecord()))
return m_recorder->SetIndices(pIndexData, BaseVertexIndex);
if (unlikely(BaseVertexIndex > std::numeric_limits<int32_t>::max()))
Logger::warn("D3D8Device::SetIndices: BaseVertexIndex exceeds INT_MAX");
// used by DrawIndexedPrimitive
m_baseVertexIndex = BaseVertexIndex;
D3D8IndexBuffer* buffer = static_cast<D3D8IndexBuffer*>(pIndexData);
HRESULT res = GetD3D9()->SetIndices(D3D8IndexBuffer::GetD3D9Nullable(buffer));
if (likely(SUCCEEDED(res))) {
if (ShouldBatch())
m_batcher->SetIndices(buffer, m_baseVertexIndex);
m_indices = buffer;
}
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetIndices(
IDirect3DIndexBuffer8** ppIndexData,
UINT* pBaseVertexIndex) {
D3D8DeviceLock lock = LockDevice();
InitReturnPtr(ppIndexData);
if (unlikely(ppIndexData == nullptr || pBaseVertexIndex == nullptr))
return D3DERR_INVALIDCALL;
*ppIndexData = m_indices.ref();
*pBaseVertexIndex = m_baseVertexIndex;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetPixelShaderConstant(DWORD Register, void* pConstantData, DWORD ConstantCount) {
2025-03-07 11:28:46 +02:00
return GetD3D9()->GetPixelShaderConstantF(Register, reinterpret_cast<float*>(pConstantData), ConstantCount);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetPixelShaderConstant(
DWORD StartRegister,
const void* pConstantData,
DWORD ConstantCount) {
StateChange();
// ConstantCount is actually the same as Vector4fCount
return GetD3D9()->SetPixelShaderConstantF(StartRegister, reinterpret_cast<const float*>(pConstantData), ConstantCount);
}
HRESULT STDMETHODCALLTYPE D3D8Device::DrawRectPatch(
UINT Handle,
const float* pNumSegs,
const D3DRECTPATCH_INFO* pRectPatchInfo) {
return GetD3D9()->DrawRectPatch(Handle, pNumSegs, reinterpret_cast<const d3d9::D3DRECTPATCH_INFO*>(pRectPatchInfo));
}
HRESULT STDMETHODCALLTYPE D3D8Device::DrawTriPatch(
UINT Handle,
const float* pNumSegs,
const D3DTRIPATCH_INFO* pTriPatchInfo) {
return GetD3D9()->DrawTriPatch(Handle, pNumSegs, reinterpret_cast<const d3d9::D3DTRIPATCH_INFO*>(pTriPatchInfo));
}
HRESULT STDMETHODCALLTYPE D3D8Device::DeletePatch(UINT Handle) {
return GetD3D9()->DeletePatch(Handle);
}
// Render States //
// ZBIAS can be an integer from 0 to 16 and needs to be remapped to float
static constexpr float ZBIAS_SCALE = -0.000005f;
static constexpr float ZBIAS_SCALE_INV = 1 / ZBIAS_SCALE;
HRESULT STDMETHODCALLTYPE D3D8Device::SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) {
D3D8DeviceLock lock = LockDevice();
d3d9::D3DRENDERSTATETYPE State9 = (d3d9::D3DRENDERSTATETYPE)State;
bool stateChange = true;
switch (State) {
// Most render states translate 1:1 to D3D9
default:
break;
// TODO: Implement D3DRS_LINEPATTERN - vkCmdSetLineRasterizationModeEXT
// and advertise support with D3DPRASTERCAPS_PAT once that is done
case D3DRS_LINEPATTERN:
Logger::warn("D3D8Device::SetRenderState: Unimplemented render state D3DRS_LINEPATTERN");
m_linePattern = bit::cast<D3DLINEPATTERN>(Value);
return D3D_OK;
// Not supported by D3D8, but its value is stored.
case D3DRS_ZVISIBLE:
m_zVisible = Value;
return D3D_OK;
// TODO: Implement D3DRS_ANTIALIASEDLINEENABLE in D9VK.
case D3DRS_EDGEANTIALIAS:
State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE;
break;
case D3DRS_ZBIAS:
State9 = d3d9::D3DRS_DEPTHBIAS;
Value = bit::cast<DWORD>(static_cast<float>(Value) * ZBIAS_SCALE);
break;
case D3DRS_SOFTWAREVERTEXPROCESSING:
// D3D9 can return D3DERR_INVALIDCALL, but we don't care.
if (!(m_behaviorFlags & D3DCREATE_MIXED_VERTEXPROCESSING))
return D3D_OK;
// This was a very easy footgun for D3D8 applications.
if (unlikely(ShouldRecord()))
return m_recorder->SetSoftwareVertexProcessing(Value);
return GetD3D9()->SetSoftwareVertexProcessing(Value);
// TODO: Implement D3DRS_PATCHSEGMENTS
case D3DRS_PATCHSEGMENTS:
Logger::warn("D3D8Device::SetRenderState: Unimplemented render state D3DRS_PATCHSEGMENTS");
m_patchSegments = bit::cast<float>(Value);
return D3D_OK;
}
if (stateChange) {
DWORD value;
// Value at this point is converted for use with D3D9,
// so we need to compare it against D3D9 directly
HRESULT res = GetD3D9()->GetRenderState(State9, &value);
if (likely(SUCCEEDED(res)) && value != Value)
StateChange();
}
// This call will never fail
return GetD3D9()->SetRenderState(State9, Value);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetRenderState(D3DRENDERSTATETYPE State, DWORD* pValue) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(pValue == nullptr))
return D3DERR_INVALIDCALL;
d3d9::D3DRENDERSTATETYPE State9 = (d3d9::D3DRENDERSTATETYPE)State;
switch (State) {
// Most render states translate 1:1 to D3D9
default:
break;
case D3DRS_LINEPATTERN:
*pValue = bit::cast<DWORD>(m_linePattern);
return D3D_OK;
// Not supported by D3D8, but its value is stored.
case D3DRS_ZVISIBLE:
*pValue = m_zVisible;
return D3D_OK;
case D3DRS_EDGEANTIALIAS:
State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE;
break;
case D3DRS_ZBIAS: {
DWORD bias = 0;
HRESULT res = GetD3D9()->GetRenderState(d3d9::D3DRS_DEPTHBIAS, &bias);
*pValue = static_cast<DWORD>(bit::cast<float>(bias) * ZBIAS_SCALE_INV);
return res;
} break;
case D3DRS_SOFTWAREVERTEXPROCESSING:
*pValue = GetD3D9()->GetSoftwareVertexProcessing();
return D3D_OK;
case D3DRS_PATCHSEGMENTS:
*pValue = bit::cast<DWORD>(m_patchSegments);
return D3D_OK;
}
// This call will never fail
return GetD3D9()->GetRenderState(State9, pValue);
}
// Vertex Shaders //
HRESULT STDMETHODCALLTYPE D3D8Device::CreateVertexShader(
const DWORD* pDeclaration,
const DWORD* pFunction,
DWORD* pHandle,
DWORD Usage ) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(pDeclaration == nullptr || pHandle == nullptr))
return D3DERR_INVALIDCALL;
D3D9VertexShaderCode translatedVS;
HRESULT res = TranslateVertexShader8(pDeclaration, pFunction, m_d3d8Options, translatedVS);
if (unlikely(FAILED(res)))
return res;
// Create vertex declaration
Com<d3d9::IDirect3DVertexDeclaration9> pVertexDecl;
res = GetD3D9()->CreateVertexDeclaration(translatedVS.declaration, &pVertexDecl);
if (unlikely(FAILED(res)))
return res;
Com<d3d9::IDirect3DVertexShader9> pVertexShader;
if (pFunction != nullptr) {
res = GetD3D9()->CreateVertexShader(translatedVS.function.data(), &pVertexShader);
} else {
// pFunction is NULL: fixed function pipeline
pVertexShader = nullptr;
}
if (likely(SUCCEEDED(res))) {
D3D8VertexShaderInfo& info = m_vertexShaders.emplace_back();
info.pVertexDecl = std::move(pVertexDecl);
info.pVertexShader = std::move(pVertexShader);
// Store D3D8 bytecodes in the shader info
for (UINT i = 0; pDeclaration[i] != D3DVSD_END(); i++)
info.declaration.push_back(pDeclaration[i]);
info.declaration.push_back(D3DVSD_END());
if (pFunction != nullptr) {
for (UINT i = 0; pFunction[i] != D3DVS_END(); i++)
info.function.push_back(pFunction[i]);
info.function.push_back(D3DVS_END());
}
// Set bit to indicate this is not an FVF
*pHandle = getShaderHandle(m_vertexShaders.size());
}
return res;
}
inline D3D8VertexShaderInfo* getVertexShaderInfo(D3D8Device* device, DWORD Handle) {
Handle = getShaderIndex(Handle);
if (unlikely(Handle >= device->m_vertexShaders.size())) {
Logger::debug(str::format("D3D8: Invalid vertex shader index ", std::hex, Handle));
return nullptr;
}
D3D8VertexShaderInfo& info = device->m_vertexShaders[Handle];
if (unlikely(info.pVertexDecl == nullptr && info.pVertexShader == nullptr)) {
Logger::debug(str::format("D3D8: Application provided deleted vertex shader ", std::hex, Handle));
return nullptr;
}
return &info;
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetVertexShader(DWORD Handle) {
D3D8DeviceLock lock = LockDevice();
HRESULT res;
if (unlikely(ShouldRecord())) {
return m_recorder->SetVertexShader(Handle);
}
// Check for extra bit that indicates this is not an FVF
if (!isFVF(Handle)) {
D3D8VertexShaderInfo* info = getVertexShaderInfo(this, Handle);
if (!info)
return D3DERR_INVALIDCALL;
StateChange();
GetD3D9()->SetVertexDeclaration(info->pVertexDecl.ptr());
res = GetD3D9()->SetVertexShader(info->pVertexShader.ptr());
if (likely(SUCCEEDED(res))) {
// Cache current shader
m_currentVertexShader = Handle;
}
return res;
} else if (m_currentVertexShader != Handle) {
StateChange();
//GetD3D9()->SetVertexDeclaration(nullptr);
GetD3D9()->SetVertexShader(nullptr);
res = GetD3D9()->SetFVF(Handle);
if (likely(SUCCEEDED(res))) {
// Cache current FVF
m_currentVertexShader = Handle;
}
return res;
}
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShader(DWORD* pHandle) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(pHandle == nullptr))
return D3DERR_INVALIDCALL;
// Return cached shader
*pHandle = m_currentVertexShader;
return D3D_OK;
/*
// Slow path. Use to debug cached shader validation. //
d3d9::IDirect3DVertexShader9* pVertexShader;
HRESULT res = GetD3D9()->GetVertexShader(&pVertexShader);
if (FAILED(res) || pVertexShader == nullptr) {
return GetD3D9()->GetFVF(pHandle);
}
for (unsigned int i = 0; i < m_vertexShaders.size(); i++) {
D3D8VertexShaderInfo& info = m_vertexShaders[i];
if (info.pVertexShader == pVertexShader) {
*pHandle = getShaderHandle(DWORD(i));
return res;
}
}
return res;
*/
}
HRESULT STDMETHODCALLTYPE D3D8Device::DeleteVertexShader(DWORD Handle) {
D3D8DeviceLock lock = LockDevice();
if (!isFVF(Handle)) {
D3D8VertexShaderInfo* info = getVertexShaderInfo(this, Handle);
if (!info)
return D3DERR_INVALIDCALL;
2025-03-07 11:28:46 +02:00
info->pVertexDecl = nullptr;
info->pVertexShader = nullptr;
info->declaration.clear();
info->function.clear();
if (m_currentVertexShader == Handle)
m_currentVertexShader = 0;
}
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShaderDeclaration(DWORD Handle, void* pData, DWORD* pSizeOfData) {
D3D8DeviceLock lock = LockDevice();
D3D8VertexShaderInfo* pInfo = getVertexShaderInfo(this, Handle);
if (unlikely(!pInfo))
return D3DERR_INVALIDCALL;
UINT SizeOfData = *pSizeOfData;
// Get actual size
UINT ActualSize = pInfo->declaration.size() * sizeof(DWORD);
if (pData == nullptr) {
*pSizeOfData = ActualSize;
return D3D_OK;
}
// D3D8-specific behavior
if (SizeOfData < ActualSize) {
// D3DERR_MOREDATA should be returned according to the D3D8 documentation,
// along with a correction to the ActualSize, however tests have shown that
// D3DERR_INVALIDCALL is returned and no size correction is performed.
return D3DERR_INVALIDCALL;
}
memcpy(pData, pInfo->declaration.data(), ActualSize);
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShaderFunction(DWORD Handle, void* pData, DWORD* pSizeOfData) {
D3D8DeviceLock lock = LockDevice();
D3D8VertexShaderInfo* pInfo = getVertexShaderInfo(this, Handle);
if (unlikely(!pInfo))
return D3DERR_INVALIDCALL;
UINT SizeOfData = *pSizeOfData;
// Get actual size
UINT ActualSize = pInfo->function.size() * sizeof(DWORD);
if (pData == nullptr) {
*pSizeOfData = ActualSize;
return D3D_OK;
}
// D3D8-specific behavior
if (SizeOfData < ActualSize) {
// D3DERR_MOREDATA should be returned according to the D3D8 documentation,
// along with a correction to the ActualSize, however tests have shown that
// D3DERR_INVALIDCALL is returned and no size correction is performed.
return D3DERR_INVALIDCALL;
}
memcpy(pData, pInfo->function.data(), ActualSize);
return D3D_OK;
}
// Pixel Shaders //
HRESULT STDMETHODCALLTYPE D3D8Device::CreatePixelShader(
const DWORD* pFunction,
DWORD* pHandle) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(pFunction == nullptr || pHandle == nullptr))
return D3DERR_INVALIDCALL;
Com<d3d9::IDirect3DPixelShader9> pPixelShader;
HRESULT res = GetD3D9()->CreatePixelShader(pFunction, &pPixelShader);
if (likely(SUCCEEDED(res))) {
m_pixelShaders.push_back(std::move(pPixelShader));
// Still set the shader bit, to prevent conflicts with NULL.
*pHandle = getShaderHandle(m_pixelShaders.size());
}
return res;
}
inline d3d9::IDirect3DPixelShader9* getPixelShaderPtr(D3D8Device* device, DWORD Handle) {
Handle = getShaderIndex(Handle);
if (unlikely(Handle >= device->m_pixelShaders.size())) {
Logger::debug(str::format("D3D8: Invalid pixel shader index ", std::hex, Handle));
return nullptr;
}
d3d9::IDirect3DPixelShader9* pPixelShader = device->m_pixelShaders[Handle].ptr();
if (unlikely(pPixelShader == nullptr)) {
Logger::debug(str::format("D3D8: Application provided deleted pixel shader ", std::hex, Handle));
return nullptr;
}
return pPixelShader;
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetPixelShader(DWORD Handle) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(ShouldRecord())) {
return m_recorder->SetPixelShader(Handle);
}
if (Handle == DWORD(NULL)) {
StateChange();
m_currentPixelShader = DWORD(NULL);
return GetD3D9()->SetPixelShader(nullptr);
}
d3d9::IDirect3DPixelShader9* pPixelShader = getPixelShaderPtr(this, Handle);
if (unlikely(!pPixelShader)) {
return D3DERR_INVALIDCALL;
}
StateChange();
HRESULT res = GetD3D9()->SetPixelShader(pPixelShader);
if (likely(SUCCEEDED(res))) {
// Cache current pixel shader
m_currentPixelShader = Handle;
}
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetPixelShader(DWORD* pHandle) {
D3D8DeviceLock lock = LockDevice();
if (unlikely(pHandle == nullptr))
return D3DERR_INVALIDCALL;
// Return cached shader
*pHandle = m_currentPixelShader;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::DeletePixelShader(DWORD Handle) {
D3D8DeviceLock lock = LockDevice();
d3d9::IDirect3DPixelShader9* pPixelShader = getPixelShaderPtr(this, Handle);
if (unlikely(!pPixelShader)) {
return D3DERR_INVALIDCALL;
}
m_pixelShaders[getShaderIndex(Handle)] = nullptr;
if (m_currentPixelShader == Handle)
m_currentPixelShader = 0;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetPixelShaderFunction(DWORD Handle, void* pData, DWORD* pSizeOfData) {
D3D8DeviceLock lock = LockDevice();
d3d9::IDirect3DPixelShader9* pPixelShader = getPixelShaderPtr(this, Handle);
if (unlikely(!pPixelShader))
return D3DERR_INVALIDCALL;
UINT SizeOfData = *pSizeOfData;
// Get actual size
UINT ActualSize = 0;
pPixelShader->GetFunction(nullptr, &ActualSize);
if (pData == nullptr) {
*pSizeOfData = ActualSize;
return D3D_OK;
}
// D3D8-specific behavior
if (SizeOfData < ActualSize) {
// D3DERR_MOREDATA should be returned according to the D3D8 documentation,
// along with a correction to the ActualSize, however tests have shown that
// D3DERR_INVALIDCALL is returned and no size correction is performed.
return D3DERR_INVALIDCALL;
}
return pPixelShader->GetFunction(pData, &SizeOfData);
}
}