2024-07-07 12:10:48 +01:00
|
|
|
#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) {
|
2024-09-30 22:58:31 +03:00
|
|
|
return ((Handle & ~(D3DFVF_RESERVED0)) >> 1) - 1;
|
2024-07-07 12:10:48 +01:00
|
|
|
} else {
|
|
|
|
return Handle;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct D3D8VertexShaderInfo {
|
2024-09-10 15:35:02 +03:00
|
|
|
Com<d3d9::IDirect3DVertexDeclaration9> pVertexDecl;
|
|
|
|
Com<d3d9::IDirect3DVertexShader9> pVertexShader;
|
|
|
|
std::vector<DWORD> declaration;
|
|
|
|
std::vector<DWORD> function;
|
2024-07-07 12:10:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
D3D8Device::D3D8Device(
|
2025-01-09 11:59:59 +02:00
|
|
|
D3D8Interface* pParent,
|
|
|
|
Com<d3d9::IDirect3DDevice9>&& pDevice,
|
|
|
|
D3DDEVTYPE DeviceType,
|
|
|
|
HWND hFocusWindow,
|
|
|
|
DWORD BehaviorFlags,
|
|
|
|
D3DPRESENT_PARAMETERS* pParams)
|
2024-07-07 12:10:48 +01:00
|
|
|
: D3D8DeviceBase(std::move(pDevice))
|
|
|
|
, m_d3d8Options(pParent->GetOptions())
|
|
|
|
, m_parent(pParent)
|
|
|
|
, m_presentParams(*pParams)
|
|
|
|
, m_deviceType(DeviceType)
|
|
|
|
, m_window(hFocusWindow)
|
2024-09-17 01:05:35 +03:00
|
|
|
, m_behaviorFlags(BehaviorFlags)
|
|
|
|
, m_multithread(BehaviorFlags & D3DCREATE_MULTITHREADED) {
|
2024-07-07 12:10:48 +01:00
|
|
|
// 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)))) {
|
2024-07-07 12:10:48 +01:00
|
|
|
throw DxvkError("D3D8Device: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!");
|
|
|
|
}
|
|
|
|
|
|
|
|
ResetState();
|
|
|
|
RecreateBackBuffersAndAutoDepthStencil();
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (m_d3d8Options.batching)
|
|
|
|
m_batcher = new D3D8Batcher(this, GetD3D9());
|
2025-01-21 01:31:59 +02:00
|
|
|
|
|
|
|
d3d9::D3DCAPS9 caps9;
|
|
|
|
HRESULT res = GetD3D9()->GetDeviceCaps(&caps9);
|
|
|
|
|
|
|
|
if (unlikely(SUCCEEDED(res) && caps9.PixelShaderVersion == D3DPS_VERSION(0, 0)))
|
|
|
|
m_isFixedFunctionOnly = true;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2024-09-10 15:35:02 +03:00
|
|
|
Com<d3d9::IDirect3DQuery9> pQuery;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
switch (DevInfoID) {
|
|
|
|
// pre-D3D8 queries
|
|
|
|
case 0:
|
|
|
|
case D3DDEVINFOID_TEXTUREMANAGER:
|
|
|
|
case D3DDEVINFOID_D3DTEXTUREMANAGER:
|
|
|
|
case D3DDEVINFOID_TEXTURING:
|
|
|
|
return E_FAIL;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (FAILED(res)) {
|
2024-10-14 12:08:25 +03:00
|
|
|
// The struct size needs to be at least equal or larger
|
|
|
|
if (DevInfoStructSize < sizeof(D3DDEVINFO_VCACHE))
|
2024-07-07 12:10:48 +01:00
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
|
|
|
memset(pDevInfoStruct, 0, sizeof(D3DDEVINFO_VCACHE));
|
|
|
|
return S_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
2024-09-26 15:38:15 +03:00
|
|
|
|
|
|
|
case D3DDEVINFOID_RESOURCEMANAGER: // Not yet implemented by D9VK.
|
2024-07-07 12:10:48 +01:00
|
|
|
res = GetD3D9()->CreateQuery(d3d9::D3DQUERYTYPE_RESOURCEMANAGER, &pQuery);
|
|
|
|
break;
|
2024-09-26 15:38:15 +03:00
|
|
|
|
|
|
|
case D3DDEVINFOID_VERTEXSTATS: // Not yet implemented by D9VK.
|
2024-07-07 12:10:48 +01:00
|
|
|
res = GetD3D9()->CreateQuery(d3d9::D3DQUERYTYPE_VERTEXSTATS, &pQuery);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
Logger::warn(str::format("D3D8Device::GetInfo: Unsupported device info ID: ", DevInfoID));
|
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
|
2024-09-26 15:38:15 +03:00
|
|
|
if (FAILED(res)) {
|
2024-07-07 12:10:48 +01:00
|
|
|
if (res == D3DERR_NOTAVAILABLE) // unsupported
|
|
|
|
return E_FAIL;
|
|
|
|
else // any unknown error
|
|
|
|
return S_FALSE;
|
|
|
|
}
|
2024-09-10 15:35:02 +03:00
|
|
|
|
2024-09-26 15:38:15 +03:00
|
|
|
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?
|
2025-01-10 23:10:57 +02:00
|
|
|
//
|
2024-09-26 15:38:15 +03:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::TestCooperativeLevel() {
|
|
|
|
// Equivalent of D3D11/DXGI present tests.
|
|
|
|
return GetD3D9()->TestCooperativeLevel();
|
|
|
|
}
|
|
|
|
|
|
|
|
UINT STDMETHODCALLTYPE D3D8Device::GetAvailableTextureMem() {
|
|
|
|
return GetD3D9()->GetAvailableTextureMem();
|
|
|
|
}
|
|
|
|
|
2025-01-09 11:59:59 +02:00
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::ResourceManagerDiscardBytes(DWORD bytes) {
|
2024-07-07 12:10:48 +01:00
|
|
|
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);
|
2024-08-24 15:37:01 +03:00
|
|
|
|
|
|
|
if (likely(SUCCEEDED(res)))
|
2025-01-09 11:59:59 +02:00
|
|
|
ConvertCaps8(caps9, pCaps);
|
2024-08-24 15:37:01 +03:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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) {
|
2024-08-24 15:37:01 +03:00
|
|
|
InitReturnPtr(ppSwapChain);
|
|
|
|
|
|
|
|
if (unlikely(pPresentationParameters == nullptr || ppSwapChain == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
Com<d3d9::IDirect3DSwapChain9> pSwapChain9;
|
|
|
|
d3d9::D3DPRESENT_PARAMETERS params = ConvertPresentParameters9(pPresentationParameters);
|
|
|
|
HRESULT res = GetD3D9()->CreateAdditionalSwapChain(
|
|
|
|
¶ms,
|
|
|
|
&pSwapChain9
|
|
|
|
);
|
2024-08-24 15:37:01 +03:00
|
|
|
|
|
|
|
if (likely(SUCCEEDED(res)))
|
2024-10-04 16:50:43 +03:00
|
|
|
*ppSwapChain = ref(new D3D8SwapChain(this, pPresentationParameters, std::move(pSwapChain9)));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::Reset(D3DPRESENT_PARAMETERS* pPresentationParameters) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
StateChange();
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(pPresentationParameters == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-10-14 00:56:53 +03:00
|
|
|
// 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;
|
|
|
|
|
2025-01-11 11:27:52 +02:00
|
|
|
// 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;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
m_presentParams = *pPresentationParameters;
|
|
|
|
ResetState();
|
|
|
|
|
|
|
|
d3d9::D3DPRESENT_PARAMETERS params = ConvertPresentParameters9(pPresentationParameters);
|
|
|
|
HRESULT res = GetD3D9()->Reset(¶ms);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res)))
|
|
|
|
RecreateBackBuffersAndAutoDepthStencil();
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::Present(
|
|
|
|
const RECT* pSourceRect,
|
|
|
|
const RECT* pDestRect,
|
|
|
|
HWND hDestWindowOverride,
|
|
|
|
const RGNDATA* pDirtyRegion) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
m_batcher->EndFrame();
|
|
|
|
StateChange();
|
|
|
|
return GetD3D9()->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetBackBuffer(
|
|
|
|
UINT iBackBuffer,
|
|
|
|
D3DBACKBUFFER_TYPE Type,
|
|
|
|
IDirect3DSurface8** ppBackBuffer) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
InitReturnPtr(ppBackBuffer);
|
2024-08-24 15:37:01 +03:00
|
|
|
|
|
|
|
if (unlikely(ppBackBuffer == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (iBackBuffer >= m_backBuffers.size() || m_backBuffers[iBackBuffer] == nullptr) {
|
|
|
|
Com<d3d9::IDirect3DSurface9> pSurface9;
|
|
|
|
HRESULT res = GetD3D9()->GetBackBuffer(0, iBackBuffer, (d3d9::D3DBACKBUFFER_TYPE)Type, &pSurface9);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res))) {
|
2025-02-14 00:45:47 +02:00
|
|
|
m_backBuffers[iBackBuffer] = new D3D8Surface(this, D3DPOOL_DEFAULT, std::move(pSurface9));
|
2024-08-24 15:37:01 +03:00
|
|
|
*ppBackBuffer = m_backBuffers[iBackBuffer].ref();
|
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ppBackBuffer = m_backBuffers[iBackBuffer].ref();
|
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetRasterStatus(D3DRASTER_STATUS* pRasterStatus) {
|
2024-08-24 15:37:01 +03:00
|
|
|
return GetD3D9()->GetRasterStatus(0, reinterpret_cast<d3d9::D3DRASTER_STATUS*>(pRasterStatus));
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2024-10-14 11:22:11 +03:00
|
|
|
// D3D8 returns D3DERR_INVALIDCALL for D3DFMT_UNKNOWN
|
|
|
|
// before clearing the content of ppTexture.
|
|
|
|
if (unlikely(Format == D3DFMT_UNKNOWN))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
InitReturnPtr(ppTexture);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(ppTexture == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// 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);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res)))
|
2025-02-14 00:45:47 +02:00
|
|
|
*ppTexture = ref(new D3D8Texture2D(this, Pool, std::move(pTex9)));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CreateVolumeTexture(
|
|
|
|
UINT Width,
|
|
|
|
UINT Height,
|
|
|
|
UINT Depth,
|
|
|
|
UINT Levels,
|
|
|
|
DWORD Usage,
|
|
|
|
D3DFORMAT Format,
|
|
|
|
D3DPOOL Pool,
|
|
|
|
IDirect3DVolumeTexture8** ppVolumeTexture) {
|
2024-10-14 11:22:11 +03:00
|
|
|
// D3D8 returns D3DERR_INVALIDCALL for D3DFMT_UNKNOWN
|
|
|
|
// before clearing the content of ppVolumeTexture.
|
|
|
|
if (unlikely(Format == D3DFMT_UNKNOWN))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
InitReturnPtr(ppVolumeTexture);
|
|
|
|
|
|
|
|
if (unlikely(ppVolumeTexture == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
Com<d3d9::IDirect3DVolumeTexture9> pVolume9 = nullptr;
|
|
|
|
HRESULT res = GetD3D9()->CreateVolumeTexture(
|
|
|
|
Width, Height, Depth, Levels,
|
|
|
|
Usage,
|
|
|
|
d3d9::D3DFORMAT(Format),
|
|
|
|
d3d9::D3DPOOL(Pool),
|
|
|
|
&pVolume9,
|
|
|
|
NULL);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res)))
|
2025-02-14 00:45:47 +02:00
|
|
|
*ppVolumeTexture = ref(new D3D8Texture3D(this, Pool, std::move(pVolume9)));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CreateCubeTexture(
|
|
|
|
UINT EdgeLength,
|
|
|
|
UINT Levels,
|
|
|
|
DWORD Usage,
|
|
|
|
D3DFORMAT Format,
|
|
|
|
D3DPOOL Pool,
|
|
|
|
IDirect3DCubeTexture8** ppCubeTexture) {
|
2024-10-14 11:22:11 +03:00
|
|
|
// D3D8 returns D3DERR_INVALIDCALL for D3DFMT_UNKNOWN
|
|
|
|
// before clearing the content of ppCubeTexture.
|
|
|
|
if (unlikely(Format == D3DFMT_UNKNOWN))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
InitReturnPtr(ppCubeTexture);
|
|
|
|
|
|
|
|
if (unlikely(ppCubeTexture == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
Com<d3d9::IDirect3DCubeTexture9> pCube9 = nullptr;
|
|
|
|
HRESULT res = GetD3D9()->CreateCubeTexture(
|
|
|
|
EdgeLength,
|
|
|
|
Levels,
|
|
|
|
Usage,
|
|
|
|
d3d9::D3DFORMAT(Format),
|
|
|
|
d3d9::D3DPOOL(Pool),
|
|
|
|
&pCube9,
|
|
|
|
NULL);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res)))
|
2025-02-14 00:45:47 +02:00
|
|
|
*ppCubeTexture = ref(new D3D8TextureCube(this, Pool, std::move(pCube9)));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CreateVertexBuffer(
|
|
|
|
UINT Length,
|
|
|
|
DWORD Usage,
|
|
|
|
DWORD FVF,
|
|
|
|
D3DPOOL Pool,
|
|
|
|
IDirect3DVertexBuffer8** ppVertexBuffer) {
|
|
|
|
InitReturnPtr(ppVertexBuffer);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(ppVertexBuffer == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res)))
|
2024-07-07 12:10:48 +01:00
|
|
|
*ppVertexBuffer = ref(new D3D8VertexBuffer(this, std::move(pVertexBuffer9), Pool, Usage));
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CreateIndexBuffer(
|
|
|
|
UINT Length,
|
|
|
|
DWORD Usage,
|
|
|
|
D3DFORMAT Format,
|
|
|
|
D3DPOOL Pool,
|
|
|
|
IDirect3DIndexBuffer8** ppIndexBuffer) {
|
|
|
|
InitReturnPtr(ppIndexBuffer);
|
2024-08-24 15:37:01 +03:00
|
|
|
|
|
|
|
if (unlikely(ppIndexBuffer == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
Com<d3d9::IDirect3DIndexBuffer9> pIndexBuffer9 = nullptr;
|
|
|
|
HRESULT res = GetD3D9()->CreateIndexBuffer(Length, Usage, d3d9::D3DFORMAT(Format), d3d9::D3DPOOL(Pool), &pIndexBuffer9, NULL);
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res)))
|
2024-07-07 12:10:48 +01:00
|
|
|
*ppIndexBuffer = ref(new D3D8IndexBuffer(this, std::move(pIndexBuffer9), Pool, Usage));
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CreateRenderTarget(
|
|
|
|
UINT Width,
|
|
|
|
UINT Height,
|
|
|
|
D3DFORMAT Format,
|
|
|
|
D3DMULTISAMPLE_TYPE MultiSample,
|
|
|
|
BOOL Lockable,
|
|
|
|
IDirect3DSurface8** ppSurface) {
|
2024-10-14 11:22:11 +03:00
|
|
|
// D3D8 returns D3DERR_INVALIDCALL for D3DFMT_UNKNOWN
|
|
|
|
// before clearing the content of ppSurface.
|
|
|
|
if (unlikely(Format == D3DFMT_UNKNOWN))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
InitReturnPtr(ppSurface);
|
|
|
|
|
|
|
|
if (unlikely(ppSurface == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
Com<d3d9::IDirect3DSurface9> pSurf9 = nullptr;
|
|
|
|
HRESULT res = GetD3D9()->CreateRenderTarget(
|
|
|
|
Width,
|
|
|
|
Height,
|
|
|
|
d3d9::D3DFORMAT(Format),
|
|
|
|
d3d9::D3DMULTISAMPLE_TYPE(MultiSample),
|
2025-01-10 20:54:11 +02:00
|
|
|
0,
|
2024-07-07 12:10:48 +01:00
|
|
|
Lockable,
|
|
|
|
&pSurf9,
|
|
|
|
NULL);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res)))
|
2025-02-14 00:45:47 +02:00
|
|
|
*ppSurface = ref(new D3D8Surface(this, D3DPOOL_DEFAULT, std::move(pSurf9)));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CreateDepthStencilSurface(
|
|
|
|
UINT Width,
|
|
|
|
UINT Height,
|
|
|
|
D3DFORMAT Format,
|
|
|
|
D3DMULTISAMPLE_TYPE MultiSample,
|
|
|
|
IDirect3DSurface8** ppSurface) {
|
2024-10-14 11:22:11 +03:00
|
|
|
// D3D8 returns D3DERR_INVALIDCALL for D3DFMT_UNKNOWN
|
|
|
|
// before clearing the content of ppSurface.
|
|
|
|
if (unlikely(Format == D3DFMT_UNKNOWN))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
InitReturnPtr(ppSurface);
|
|
|
|
|
|
|
|
if (unlikely(ppSurface == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
Com<d3d9::IDirect3DSurface9> pSurf9 = nullptr;
|
|
|
|
HRESULT res = GetD3D9()->CreateDepthStencilSurface(
|
|
|
|
Width,
|
|
|
|
Height,
|
|
|
|
d3d9::D3DFORMAT(Format),
|
|
|
|
d3d9::D3DMULTISAMPLE_TYPE(MultiSample),
|
2025-01-10 20:54:11 +02:00
|
|
|
0,
|
2025-01-10 20:56:14 +02:00
|
|
|
FALSE, // z-buffer discarding is not used in D3D8
|
2024-07-07 12:10:48 +01:00
|
|
|
&pSurf9,
|
|
|
|
NULL);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res)))
|
2025-02-14 00:45:47 +02:00
|
|
|
*ppSurface = ref(new D3D8Surface(this, D3DPOOL_DEFAULT, std::move(pSurf9)));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CreateImageSurface(
|
|
|
|
UINT Width,
|
|
|
|
UINT Height,
|
|
|
|
D3DFORMAT Format,
|
|
|
|
IDirect3DSurface8** ppSurface) {
|
2024-10-14 11:22:11 +03:00
|
|
|
// Only D3D8 CreateImageSurface clears the content of ppSurface
|
|
|
|
// before checking if Format is equal to D3DFMT_UNKNOWN.
|
2024-08-24 15:37:01 +03:00
|
|
|
InitReturnPtr(ppSurface);
|
|
|
|
|
2024-10-14 11:22:11 +03:00
|
|
|
if (unlikely(Format == D3DFMT_UNKNOWN))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(ppSurface == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res)))
|
2025-02-14 00:45:47 +02:00
|
|
|
*ppSurface = ref(new D3D8Surface(this, pool, std::move(pSurf)));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
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)))
|
2024-07-07 12:10:48 +01:00
|
|
|
return res;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
res = dst->LockRect(&dstLocked, &dstRect, 0);
|
2025-03-07 11:28:46 +02:00
|
|
|
if (unlikely(FAILED(res))) {
|
2024-07-07 12:10:48 +01:00
|
|
|
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) {
|
2025-01-10 23:10:57 +02:00
|
|
|
// DXT blocks are always 4x4 pixels.
|
2024-07-07 12:10:48 +01:00
|
|
|
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,
|
2024-07-07 12:10:48 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
res = src->UnlockRect();
|
2025-03-07 11:28:46 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief D3D8 CopyRects implementation
|
2024-07-08 23:55:44 +02:00
|
|
|
*
|
2024-07-07 12:10:48 +01: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
|
|
|
*
|
2025-01-14 01:10:53 +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 │
|
|
|
|
* └────────────┴───────────────────────────┴───────────────────────┴───────────────────────┴──────────────────────┘
|
2024-07-07 12:10:48 +01:00
|
|
|
*/
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CopyRects(
|
|
|
|
IDirect3DSurface8* pSourceSurface,
|
|
|
|
const RECT* pSourceRectsArray,
|
|
|
|
UINT cRects,
|
|
|
|
IDirect3DSurface8* pDestinationSurface,
|
|
|
|
const POINT* pDestPointsArray) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
// The source and destination surfaces can not be identical.
|
|
|
|
if (unlikely(pSourceSurface == nullptr ||
|
|
|
|
pDestinationSurface == nullptr ||
|
|
|
|
pSourceSurface == pDestinationSurface)) {
|
2024-07-07 12:10:48 +01:00
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
}
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
// 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.
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
// This method does not support format conversion.
|
|
|
|
if (unlikely(srcDesc.Format != dstDesc.Format))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// 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))))
|
2024-07-07 12:10:48 +01:00
|
|
|
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 = ▭
|
|
|
|
|
|
|
|
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
|
|
|
|
2024-07-07 12:10:48 +01: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
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
offset = !stretch && asymmetric;
|
|
|
|
} else {
|
|
|
|
dstRect = srcRect;
|
|
|
|
asymmetric = stretch = offset = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
POINT dstPt = { dstRect.left, dstRect.top };
|
|
|
|
|
2025-01-14 01:10:53 +02:00
|
|
|
auto unsupported = [&] {
|
|
|
|
Logger::err(str::format("D3D8Device::CopyRects: Unsupported case from src pool ", srcDesc.Pool, " to dst pool ", dstDesc.Pool));
|
2024-07-07 12:10:48 +01:00
|
|
|
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.
|
2024-10-07 18:11:50 +03:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
switch (dstDesc.Pool) {
|
2024-07-08 23:55:44 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// Dest: DEFAULT
|
2024-07-08 23:55:44 +02:00
|
|
|
case d3d9::D3DPOOL_DEFAULT:
|
2024-07-07 12:10:48 +01:00
|
|
|
switch (srcDesc.Pool) {
|
2024-07-08 23:55:44 +02:00
|
|
|
case d3d9::D3DPOOL_DEFAULT: {
|
2024-07-07 12:10:48 +01:00
|
|
|
// DEFAULT -> DEFAULT: use StretchRect
|
2024-07-08 23:55:44 +02:00
|
|
|
return logError(GetD3D9()->StretchRect(
|
2024-07-07 12:10:48 +01:00
|
|
|
src->GetD3D9(),
|
|
|
|
&srcRect,
|
|
|
|
dst->GetD3D9(),
|
|
|
|
&dstRect,
|
|
|
|
d3d9::D3DTEXF_NONE
|
2024-07-08 23:55:44 +02:00
|
|
|
));
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
2024-07-08 23:55:44 +02:00
|
|
|
case d3d9::D3DPOOL_MANAGED: {
|
2024-07-07 12:10:48 +01:00
|
|
|
// MANAGED -> DEFAULT: UpdateTextureFromBuffer
|
2024-07-08 23:55:44 +02:00
|
|
|
return logError(m_bridge->UpdateTextureFromBuffer(
|
2024-07-07 12:10:48 +01:00
|
|
|
src->GetD3D9(),
|
|
|
|
dst->GetD3D9(),
|
|
|
|
&srcRect,
|
|
|
|
&dstPt
|
2024-07-08 23:55:44 +02:00
|
|
|
));
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
2024-07-08 23:55:44 +02:00
|
|
|
case d3d9::D3DPOOL_SYSTEMMEM: {
|
2024-07-07 12:10:48 +01:00
|
|
|
// SYSTEMMEM -> DEFAULT: use UpdateSurface
|
2024-07-08 23:55:44 +02:00
|
|
|
return logError(GetD3D9()->UpdateSurface(
|
2024-07-07 12:10:48 +01:00
|
|
|
src->GetD3D9(),
|
|
|
|
&srcRect,
|
|
|
|
dst->GetD3D9(),
|
|
|
|
&dstPt
|
2024-07-08 23:55:44 +02:00
|
|
|
));
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
2025-01-14 01:10:53 +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
|
|
|
|
));
|
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
default: {
|
2025-01-14 01:10:53 +02:00
|
|
|
return unsupported();
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
} break;
|
2024-07-08 23:55:44 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// Dest: MANAGED
|
2024-07-08 23:55:44 +02:00
|
|
|
case d3d9::D3DPOOL_MANAGED:
|
2024-07-07 12:10:48 +01:00
|
|
|
switch (srcDesc.Pool) {
|
2025-01-10 23:10:57 +02:00
|
|
|
// TODO: Copy on GPU (handle MANAGED similarly to SYSTEMMEM for now)
|
2024-07-08 23:55:44 +02:00
|
|
|
case d3d9::D3DPOOL_DEFAULT: {
|
2024-07-07 12:10:48 +01:00
|
|
|
// 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(
|
2024-07-07 12:10:48 +01:00
|
|
|
src->GetD3D9(),
|
|
|
|
&srcRect,
|
|
|
|
pBlitImage.ptr(),
|
|
|
|
&dstRect,
|
|
|
|
d3d9::D3DTEXF_NONE);
|
2024-07-08 23:55:44 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (FAILED(res)) {
|
2024-07-08 23:55:44 +02:00
|
|
|
return logError(res);
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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-07 12:10:48 +01:00
|
|
|
}
|
2024-07-08 23:55:44 +02:00
|
|
|
case d3d9::D3DPOOL_MANAGED:
|
2025-01-14 01:10:53 +02:00
|
|
|
case d3d9::D3DPOOL_SYSTEMMEM:
|
|
|
|
case d3d9::D3DPOOL_SCRATCH: {
|
|
|
|
// MANAGED/SYSMEM/SCRATCH -> MANAGED: LockRect / memcpy
|
2024-07-08 23:55:44 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (stretch) {
|
2024-07-08 23:55:44 +02:00
|
|
|
return logError(D3DERR_INVALIDCALL);
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
2024-07-08 23:55:44 +02:00
|
|
|
return logError(copyTextureBuffers(src.ptr(), dst.ptr(), srcDesc, dstDesc, srcRect, dstRect));
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
default: {
|
2025-01-14 01:10:53 +02:00
|
|
|
return unsupported();
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
} break;
|
2024-07-08 23:55:44 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// DEST: SYSTEMMEM
|
2024-07-08 23:55:44 +02:00
|
|
|
case d3d9::D3DPOOL_SYSTEMMEM: {
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
// RT (DEFAULT) -> SYSTEMMEM: Use GetRenderTargetData as fast path if possible
|
2024-08-29 11:14:12 +03:00
|
|
|
if ((srcDesc.Usage & D3DUSAGE_RENDERTARGET || m_renderTarget == src.ptr())) {
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
// 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()));
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (srcDesc.Pool) {
|
2024-07-08 23:55:44 +02:00
|
|
|
case d3d9::D3DPOOL_DEFAULT: {
|
2024-07-07 12:10:48 +01:00
|
|
|
// 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(
|
2024-07-07 12:10:48 +01:00
|
|
|
src->GetD3D9(),
|
|
|
|
&srcRect,
|
|
|
|
pBlitImage.ptr(),
|
|
|
|
&dstRect,
|
|
|
|
d3d9::D3DTEXF_NONE);
|
2025-01-14 01:10:53 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (FAILED(res)) {
|
2024-07-08 23:55:44 +02:00
|
|
|
return logError(res);
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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-07 12:10:48 +01:00
|
|
|
}
|
2025-01-14 01:10:53 +02:00
|
|
|
// MANAGED/SYSMEM/SCRATCH -> SYSMEM: LockRect / memcpy
|
2024-07-08 23:55:44 +02:00
|
|
|
case d3d9::D3DPOOL_MANAGED:
|
2025-01-14 01:10:53 +02:00
|
|
|
case d3d9::D3DPOOL_SYSTEMMEM:
|
|
|
|
case d3d9::D3DPOOL_SCRATCH: {
|
2024-07-07 12:10:48 +01:00
|
|
|
if (stretch) {
|
2024-07-08 23:55:44 +02:00
|
|
|
return logError(D3DERR_INVALIDCALL);
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
2024-07-08 23:55:44 +02:00
|
|
|
return logError(copyTextureBuffers(src.ptr(), dst.ptr(), srcDesc, dstDesc, srcRect, dstRect));
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
default: {
|
2025-01-14 01:10:53 +02:00
|
|
|
return unsupported();
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
} break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// DEST: SCRATCH
|
2025-01-14 01:10:53 +02:00
|
|
|
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;
|
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
default: {
|
2025-01-14 01:10:53 +02:00
|
|
|
return unsupported();
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-08 23:55:44 +02:00
|
|
|
return D3DERR_INVALIDCALL;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::UpdateTexture(
|
|
|
|
IDirect3DBaseTexture8* pSourceTexture,
|
|
|
|
IDirect3DBaseTexture8* pDestinationTexture) {
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(pSourceTexture == nullptr || pDestinationTexture == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
HRESULT res;
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (pRenderTarget != nullptr) {
|
2024-07-07 12:10:48 +01:00
|
|
|
D3D8Surface* surf = static_cast<D3D8Surface*>(pRenderTarget);
|
|
|
|
|
2024-10-05 21:44:12 +03:00
|
|
|
// 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));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-10-05 21:44:12 +03:00
|
|
|
if (unlikely(FAILED(res))) return res;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-10-05 21:44:12 +03:00
|
|
|
m_renderTarget = surf;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetDepthStencilSurface is a separate call
|
|
|
|
D3D8Surface* zStencil = static_cast<D3D8Surface*>(pNewZStencil);
|
|
|
|
|
2024-11-19 16:13:14 +02:00
|
|
|
// 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);
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(FAILED(res))) return res;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-11-19 16:13:14 +02:00
|
|
|
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;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
2024-11-19 16:13:14 +02:00
|
|
|
StateChange();
|
|
|
|
res = GetD3D9()->SetDepthStencilSurface(D3D8Surface::GetD3D9Nullable(zStencil));
|
|
|
|
|
|
|
|
if (unlikely(FAILED(res))) return res;
|
|
|
|
|
|
|
|
m_depthStencil = zStencil;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetRenderTarget(IDirect3DSurface8** ppRenderTarget) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
InitReturnPtr(ppRenderTarget);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(ppRenderTarget == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (unlikely(m_renderTarget == nullptr)) {
|
|
|
|
Com<d3d9::IDirect3DSurface9> pRT9 = nullptr;
|
|
|
|
HRESULT res = GetD3D9()->GetRenderTarget(0, &pRT9); // use RT index 0
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res))) {
|
2025-02-14 00:45:47 +02:00
|
|
|
m_renderTarget = new D3D8Surface(this, D3DPOOL_DEFAULT, std::move(pRT9));
|
2024-08-24 15:37:01 +03:00
|
|
|
*ppRenderTarget = m_renderTarget.ref();
|
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ppRenderTarget = m_renderTarget.ref();
|
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetDepthStencilSurface(IDirect3DSurface8** ppZStencilSurface) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
InitReturnPtr(ppZStencilSurface);
|
2024-08-24 15:37:01 +03:00
|
|
|
|
|
|
|
if (unlikely(ppZStencilSurface == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (unlikely(m_depthStencil == nullptr)) {
|
|
|
|
Com<d3d9::IDirect3DSurface9> pStencil9 = nullptr;
|
|
|
|
HRESULT res = GetD3D9()->GetDepthStencilSurface(&pStencil9);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res))) {
|
2025-02-14 00:45:47 +02:00
|
|
|
m_depthStencil = new D3D8Surface(this, D3DPOOL_DEFAULT, std::move(pStencil9));
|
2024-08-24 15:37:01 +03:00
|
|
|
*ppZStencilSurface = m_depthStencil.ref();
|
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ppZStencilSurface = m_depthStencil.ref();
|
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::BeginScene() { return GetD3D9()->BeginScene(); }
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-09-08 00:56:18 +03:00
|
|
|
if (likely(pViewport != nullptr)) {
|
2024-09-15 11:43:02 +03:00
|
|
|
// We need a valid render target to validate the viewport
|
2024-09-08 00:56:18 +03:00
|
|
|
if (unlikely(m_renderTarget == nullptr))
|
2024-08-24 15:37:01 +03:00
|
|
|
return D3DERR_INVALIDCALL;
|
2024-09-08 00:56:18 +03:00
|
|
|
|
|
|
|
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 ||
|
2024-09-15 11:43:02 +03:00
|
|
|
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)) {
|
2024-10-07 18:11:50 +03:00
|
|
|
Logger::debug("D3D8Device::SetViewport: Viewport exceeds render target dimensions by one pixel");
|
2024-09-15 11:43:02 +03:00
|
|
|
} else {
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
}
|
|
|
|
}
|
2024-09-08 00:56:18 +03:00
|
|
|
}
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
StateChange();
|
|
|
|
return GetD3D9()->SetViewport(reinterpret_cast<const d3d9::D3DVIEWPORT9*>(pViewport));
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetViewport(D3DVIEWPORT8* pViewport) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
2024-07-07 12:10:48 +01:00
|
|
|
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);
|
|
|
|
}
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CreateStateBlock(
|
|
|
|
D3DSTATEBLOCKTYPE Type,
|
|
|
|
DWORD* pToken) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(pToken == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-10-13 23:20:10 +03:00
|
|
|
// Applications cannot create a state block while another is being recorded
|
|
|
|
if (unlikely(ShouldRecord()))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
Com<d3d9::IDirect3DStateBlock9> pStateBlock9;
|
|
|
|
HRESULT res = GetD3D9()->CreateStateBlock(d3d9::D3DSTATEBLOCKTYPE(Type), &pStateBlock9);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res))) {
|
|
|
|
m_token++;
|
2024-10-04 09:35:49 +03:00
|
|
|
auto stateBlockIterPair = m_stateBlocks.emplace(std::piecewise_construct,
|
|
|
|
std::forward_as_tuple(m_token),
|
|
|
|
std::forward_as_tuple(this, Type, pStateBlock9.ref()));
|
2024-08-24 15:37:01 +03:00
|
|
|
*pToken = m_token;
|
2024-10-04 09:35:49 +03:00
|
|
|
|
|
|
|
// D3D8 state blocks automatically capture state on creation.
|
|
|
|
stateBlockIterPair.first->second.Capture();
|
2024-08-24 15:37:01 +03:00
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CaptureStateBlock(DWORD Token) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-10-13 23:20:10 +03:00
|
|
|
// Applications cannot capture a state block while another is being recorded
|
|
|
|
if (unlikely(ShouldRecord()))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 15:36:19 +03:00
|
|
|
auto stateBlockIter = m_stateBlocks.find(Token);
|
|
|
|
|
|
|
|
if (unlikely(stateBlockIter == m_stateBlocks.end())) {
|
2024-10-07 18:11:50 +03:00
|
|
|
Logger::warn(str::format("D3D8Device::CaptureStateBlock: Invalid token: ", std::hex, Token));
|
|
|
|
return D3D_OK;
|
2024-07-07 15:36:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return stateBlockIter->second.Capture();
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::ApplyStateBlock(DWORD Token) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-10-13 23:20:10 +03:00
|
|
|
// Applications cannot apply a state block while another is being recorded
|
|
|
|
if (unlikely(ShouldRecord()))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
StateChange();
|
2024-07-07 15:36:19 +03:00
|
|
|
|
|
|
|
auto stateBlockIter = m_stateBlocks.find(Token);
|
|
|
|
|
|
|
|
if (unlikely(stateBlockIter == m_stateBlocks.end())) {
|
2024-10-07 18:11:50 +03:00
|
|
|
Logger::warn(str::format("D3D8Device::ApplyStateBlock: Invalid token: ", std::hex, Token));
|
|
|
|
return D3D_OK;
|
2024-07-07 15:36:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return stateBlockIter->second.Apply();
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::DeleteStateBlock(DWORD Token) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-10-13 23:20:10 +03:00
|
|
|
// Applications cannot delete a state block while another is being recorded
|
2024-07-07 12:10:48 +01:00
|
|
|
if (unlikely(ShouldRecord()))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 15:36:19 +03:00
|
|
|
auto stateBlockIter = m_stateBlocks.find(Token);
|
|
|
|
|
|
|
|
if (unlikely(stateBlockIter == m_stateBlocks.end())) {
|
2024-10-07 18:11:50 +03:00
|
|
|
Logger::warn(str::format("D3D8Device::DeleteStateBlock: Invalid token: ", std::hex, Token));
|
|
|
|
return D3D_OK;
|
2024-07-07 15:36:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
m_stateBlocks.erase(stateBlockIter);
|
2024-07-07 12:10:48 +01:00
|
|
|
|
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--;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::BeginStateBlock() {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (unlikely(m_recorder != nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
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;
|
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
return res;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::EndStateBlock(DWORD* pToken) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (unlikely(pToken == nullptr || m_recorder == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
|
|
|
Com<d3d9::IDirect3DStateBlock9> pStateBlock;
|
|
|
|
HRESULT res = GetD3D9()->EndStateBlock(&pStateBlock);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res))) {
|
|
|
|
m_recorder->SetD3D9(std::move(pStateBlock));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
*pToken = m_recorderToken;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
m_recorder = nullptr;
|
|
|
|
m_recorderToken = 0;
|
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
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) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
InitReturnPtr(ppTexture);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(ppTexture == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
*ppTexture = m_textures[Stage].ref();
|
|
|
|
|
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::SetTexture(DWORD Stage, IDirect3DBaseTexture8* pTexture) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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);
|
|
|
|
|
2025-01-31 19:01:02 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-29 11:14:12 +03:00
|
|
|
if (unlikely(m_textures[Stage] == tex))
|
2024-07-07 12:10:48 +01:00
|
|
|
return D3D_OK;
|
|
|
|
|
|
|
|
StateChange();
|
2024-08-24 15:37:01 +03:00
|
|
|
HRESULT res = GetD3D9()->SetTexture(Stage, D3D8Texture2D::GetD3D9Nullable(tex));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res)))
|
|
|
|
m_textures[Stage] = tex;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
return res;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetTextureStageState(
|
|
|
|
DWORD Stage,
|
|
|
|
D3DTEXTURESTAGESTATETYPE Type,
|
|
|
|
DWORD* pValue) {
|
|
|
|
d3d9::D3DSAMPLERSTATETYPE stateType = GetSamplerStateType9(Type);
|
|
|
|
|
2024-11-16 18:15:17 +02:00
|
|
|
if (stateType != -1u) {
|
2024-07-07 12:10:48 +01:00
|
|
|
// 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);
|
|
|
|
|
2025-01-31 19:01:02 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
StateChange();
|
2024-11-16 18:15:17 +02:00
|
|
|
if (stateType != -1u) {
|
2024-07-07 12:10:48 +01:00
|
|
|
// 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) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
return GetD3D9()->DrawIndexedPrimitive(
|
|
|
|
d3d9::D3DPRIMITIVETYPE(PrimitiveType),
|
2025-02-15 13:43:40 +02:00
|
|
|
static_cast<INT>(std::min(m_baseVertexIndex, static_cast<UINT>(std::numeric_limits<int32_t>::max()))), // set by SetIndices
|
2024-07-07 12:10:48 +01:00
|
|
|
MinVertexIndex,
|
|
|
|
NumVertices,
|
|
|
|
StartIndex,
|
|
|
|
PrimitiveCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::DrawPrimitiveUP(
|
|
|
|
D3DPRIMITIVETYPE PrimitiveType,
|
|
|
|
UINT PrimitiveCount,
|
|
|
|
const void* pVertexStreamZeroData,
|
|
|
|
UINT VertexStreamZeroStride) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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,
|
2024-08-24 15:37:01 +03:00
|
|
|
D3D8VertexBuffer::GetD3D9Nullable(buffer),
|
2024-07-07 12:10:48 +01:00
|
|
|
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);
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::SetStreamSource(
|
|
|
|
UINT StreamNumber,
|
|
|
|
IDirect3DVertexBuffer8* pStreamData,
|
|
|
|
UINT Stride) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (unlikely(StreamNumber >= d8caps::MAX_STREAMS))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-10-04 00:27:45 +03:00
|
|
|
if (unlikely(ShouldRecord()))
|
|
|
|
return m_recorder->SetStreamSource(StreamNumber, pStreamData, Stride);
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
D3D8VertexBuffer* buffer = static_cast<D3D8VertexBuffer*>(pStreamData);
|
2024-08-24 15:37:01 +03:00
|
|
|
HRESULT res = GetD3D9()->SetStreamSource(StreamNumber, D3D8VertexBuffer::GetD3D9Nullable(buffer), 0, Stride);
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res))) {
|
|
|
|
if (ShouldBatch())
|
|
|
|
m_batcher->SetStream(StreamNumber, buffer, Stride);
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-10-04 22:03:55 +03:00
|
|
|
m_streams[StreamNumber].buffer = buffer;
|
|
|
|
// The previous stride is preserved if pStreamData is NULL
|
|
|
|
if (likely(buffer != nullptr))
|
|
|
|
m_streams[StreamNumber].stride = Stride;
|
2024-08-24 15:37:01 +03:00
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
return res;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetStreamSource(
|
|
|
|
UINT StreamNumber,
|
|
|
|
IDirect3DVertexBuffer8** ppStreamData,
|
|
|
|
UINT* pStride) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
const D3D8VBO& vbo = m_streams[StreamNumber];
|
|
|
|
|
|
|
|
*ppStreamData = vbo.buffer.ref();
|
|
|
|
*pStride = vbo.stride;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::SetIndices(IDirect3DIndexBuffer8* pIndexData, UINT BaseVertexIndex) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (unlikely(ShouldRecord()))
|
|
|
|
return m_recorder->SetIndices(pIndexData, BaseVertexIndex);
|
|
|
|
|
2025-02-15 13:43:40 +02:00
|
|
|
if (unlikely(BaseVertexIndex > std::numeric_limits<int32_t>::max()))
|
2024-10-07 18:11:50 +03:00
|
|
|
Logger::warn("D3D8Device::SetIndices: BaseVertexIndex exceeds INT_MAX");
|
2024-08-31 21:10:01 +03:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// used by DrawIndexedPrimitive
|
2024-08-31 21:10:01 +03:00
|
|
|
m_baseVertexIndex = BaseVertexIndex;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
D3D8IndexBuffer* buffer = static_cast<D3D8IndexBuffer*>(pIndexData);
|
2024-08-24 15:37:01 +03:00
|
|
|
HRESULT res = GetD3D9()->SetIndices(D3D8IndexBuffer::GetD3D9Nullable(buffer));
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res))) {
|
|
|
|
if (ShouldBatch())
|
|
|
|
m_batcher->SetIndices(buffer, m_baseVertexIndex);
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
m_indices = buffer;
|
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
return res;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetIndices(
|
|
|
|
IDirect3DIndexBuffer8** ppIndexData,
|
|
|
|
UINT* pBaseVertexIndex) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
InitReturnPtr(ppIndexData);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(ppIndexData == nullptr || pBaseVertexIndex == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
*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);
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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 //
|
|
|
|
|
2025-02-10 00:22:50 +02:00
|
|
|
// ZBIAS can be an integer from 0 to 16 and needs to be remapped to float
|
2024-07-07 12:10:48 +01:00
|
|
|
static constexpr float ZBIAS_SCALE = -0.000005f;
|
|
|
|
static constexpr float ZBIAS_SCALE_INV = 1 / ZBIAS_SCALE;
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
d3d9::D3DRENDERSTATETYPE State9 = (d3d9::D3DRENDERSTATETYPE)State;
|
|
|
|
bool stateChange = true;
|
|
|
|
|
|
|
|
switch (State) {
|
|
|
|
// Most render states translate 1:1 to D3D9
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
|
2025-01-10 22:12:21 +02:00
|
|
|
// TODO: Implement D3DRS_LINEPATTERN - vkCmdSetLineRasterizationModeEXT
|
2025-01-23 00:31:19 +02:00
|
|
|
// and advertise support with D3DPRASTERCAPS_PAT once that is done
|
2025-01-10 22:12:21 +02:00
|
|
|
case D3DRS_LINEPATTERN:
|
|
|
|
Logger::warn("D3D8Device::SetRenderState: Unimplemented render state D3DRS_LINEPATTERN");
|
|
|
|
m_linePattern = bit::cast<D3DLINEPATTERN>(Value);
|
|
|
|
return D3D_OK;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2025-02-10 11:12:54 +02:00
|
|
|
// Not supported by D3D8, but its value is stored.
|
2024-07-07 12:10:48 +01:00
|
|
|
case D3DRS_ZVISIBLE:
|
2025-02-10 11:12:54 +02:00
|
|
|
m_zVisible = Value;
|
2025-01-10 22:44:38 +02:00
|
|
|
return D3D_OK;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2025-01-10 23:10:57 +02:00
|
|
|
// TODO: Implement D3DRS_ANTIALIASEDLINEENABLE in D9VK.
|
2024-07-07 12:10:48 +01:00
|
|
|
case D3DRS_EDGEANTIALIAS:
|
|
|
|
State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case D3DRS_ZBIAS:
|
|
|
|
State9 = d3d9::D3DRS_DEPTHBIAS;
|
2025-02-10 00:22:50 +02:00
|
|
|
Value = bit::cast<DWORD>(static_cast<float>(Value) * ZBIAS_SCALE);
|
2024-07-07 12:10:48 +01:00
|
|
|
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);
|
|
|
|
|
2025-01-10 22:33:19 +02:00
|
|
|
// TODO: Implement D3DRS_PATCHSEGMENTS
|
2024-07-07 12:10:48 +01:00
|
|
|
case D3DRS_PATCHSEGMENTS:
|
2025-01-10 22:33:19 +02:00
|
|
|
Logger::warn("D3D8Device::SetRenderState: Unimplemented render state D3DRS_PATCHSEGMENTS");
|
|
|
|
m_patchSegments = bit::cast<float>(Value);
|
|
|
|
return D3D_OK;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stateChange) {
|
|
|
|
DWORD value;
|
2024-09-17 01:05:35 +03:00
|
|
|
// 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)
|
2024-07-07 12:10:48 +01:00
|
|
|
StateChange();
|
|
|
|
}
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
// This call will never fail
|
2024-07-07 12:10:48 +01:00
|
|
|
return GetD3D9()->SetRenderState(State9, Value);
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetRenderState(D3DRENDERSTATETYPE State, DWORD* pValue) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(pValue == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
d3d9::D3DRENDERSTATETYPE State9 = (d3d9::D3DRENDERSTATETYPE)State;
|
|
|
|
|
|
|
|
switch (State) {
|
|
|
|
// Most render states translate 1:1 to D3D9
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case D3DRS_LINEPATTERN:
|
2025-01-10 22:12:21 +02:00
|
|
|
*pValue = bit::cast<DWORD>(m_linePattern);
|
|
|
|
return D3D_OK;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2025-02-10 11:12:54 +02:00
|
|
|
// Not supported by D3D8, but its value is stored.
|
2024-07-07 12:10:48 +01:00
|
|
|
case D3DRS_ZVISIBLE:
|
2025-02-10 11:12:54 +02:00
|
|
|
*pValue = m_zVisible;
|
2025-01-10 22:44:38 +02:00
|
|
|
return D3D_OK;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
case D3DRS_EDGEANTIALIAS:
|
|
|
|
State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case D3DRS_ZBIAS: {
|
2025-02-10 00:22:50 +02:00
|
|
|
DWORD bias = 0;
|
|
|
|
HRESULT res = GetD3D9()->GetRenderState(d3d9::D3DRS_DEPTHBIAS, &bias);
|
|
|
|
*pValue = static_cast<DWORD>(bit::cast<float>(bias) * ZBIAS_SCALE_INV);
|
2024-07-07 12:10:48 +01:00
|
|
|
return res;
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case D3DRS_SOFTWAREVERTEXPROCESSING:
|
2025-01-12 18:51:42 +02:00
|
|
|
*pValue = GetD3D9()->GetSoftwareVertexProcessing();
|
|
|
|
return D3D_OK;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
case D3DRS_PATCHSEGMENTS:
|
2025-01-10 22:33:19 +02:00
|
|
|
*pValue = bit::cast<DWORD>(m_patchSegments);
|
|
|
|
return D3D_OK;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
// This call will never fail
|
2024-07-07 12:10:48 +01:00
|
|
|
return GetD3D9()->GetRenderState(State9, pValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vertex Shaders //
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CreateVertexShader(
|
|
|
|
const DWORD* pDeclaration,
|
|
|
|
const DWORD* pFunction,
|
|
|
|
DWORD* pHandle,
|
|
|
|
DWORD Usage ) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(pDeclaration == nullptr || pHandle == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2025-03-04 19:59:46 +02:00
|
|
|
D3D9VertexShaderCode translatedVS;
|
|
|
|
HRESULT res = TranslateVertexShader8(pDeclaration, pFunction, m_d3d8Options, translatedVS);
|
|
|
|
if (unlikely(FAILED(res)))
|
|
|
|
return res;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
// Create vertex declaration
|
2025-03-04 19:59:46 +02:00
|
|
|
Com<d3d9::IDirect3DVertexDeclaration9> pVertexDecl;
|
|
|
|
res = GetD3D9()->CreateVertexDeclaration(translatedVS.declaration, &pVertexDecl);
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(FAILED(res)))
|
2024-07-07 12:10:48 +01:00
|
|
|
return res;
|
|
|
|
|
2025-03-04 19:59:46 +02:00
|
|
|
Com<d3d9::IDirect3DVertexShader9> pVertexShader;
|
2024-07-07 12:10:48 +01:00
|
|
|
if (pFunction != nullptr) {
|
2025-03-04 19:59:46 +02:00
|
|
|
res = GetD3D9()->CreateVertexShader(translatedVS.function.data(), &pVertexShader);
|
2024-07-07 12:10:48 +01:00
|
|
|
} else {
|
|
|
|
// pFunction is NULL: fixed function pipeline
|
2025-03-04 19:59:46 +02:00
|
|
|
pVertexShader = nullptr;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res))) {
|
2025-03-04 19:59:46 +02:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
// Set bit to indicate this is not an FVF
|
2024-09-30 22:58:31 +03:00
|
|
|
*pHandle = getShaderHandle(m_vertexShaders.size());
|
2024-08-24 15:37:01 +03:00
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline D3D8VertexShaderInfo* getVertexShaderInfo(D3D8Device* device, DWORD Handle) {
|
2024-09-17 01:05:35 +03:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
Handle = getShaderIndex(Handle);
|
|
|
|
|
|
|
|
if (unlikely(Handle >= device->m_vertexShaders.size())) {
|
2024-10-07 18:11:50 +03:00
|
|
|
Logger::debug(str::format("D3D8: Invalid vertex shader index ", std::hex, Handle));
|
2024-07-07 12:10:48 +01:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
D3D8VertexShaderInfo& info = device->m_vertexShaders[Handle];
|
|
|
|
|
2024-09-10 15:35:02 +03:00
|
|
|
if (unlikely(info.pVertexDecl == nullptr && info.pVertexShader == nullptr)) {
|
2024-10-07 18:11:50 +03:00
|
|
|
Logger::debug(str::format("D3D8: Application provided deleted vertex shader ", std::hex, Handle));
|
2024-07-07 12:10:48 +01:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return &info;
|
|
|
|
}
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::SetVertexShader(DWORD Handle) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
HRESULT res;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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();
|
|
|
|
|
2024-09-10 15:35:02 +03:00
|
|
|
GetD3D9()->SetVertexDeclaration(info->pVertexDecl.ptr());
|
|
|
|
res = GetD3D9()->SetVertexShader(info->pVertexShader.ptr());
|
2024-08-24 15:37:01 +03:00
|
|
|
|
|
|
|
if (likely(SUCCEEDED(res))) {
|
|
|
|
// Cache current shader
|
|
|
|
m_currentVertexShader = Handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
} else if (m_currentVertexShader != Handle) {
|
|
|
|
StateChange();
|
|
|
|
|
|
|
|
//GetD3D9()->SetVertexDeclaration(nullptr);
|
|
|
|
GetD3D9()->SetVertexShader(nullptr);
|
2024-08-24 15:37:01 +03:00
|
|
|
res = GetD3D9()->SetFVF(Handle);
|
|
|
|
|
|
|
|
if (likely(SUCCEEDED(res))) {
|
|
|
|
// Cache current FVF
|
|
|
|
m_currentVertexShader = Handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
2024-08-24 15:37:01 +03:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShader(DWORD* pHandle) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(pHandle == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// Return cached shader
|
|
|
|
*pHandle = m_currentVertexShader;
|
|
|
|
|
|
|
|
return D3D_OK;
|
|
|
|
|
|
|
|
/*
|
|
|
|
// Slow path. Use to debug cached shader validation. //
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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;
|
2024-07-07 12:10:48 +01:00
|
|
|
info->declaration.clear();
|
|
|
|
info->function.clear();
|
2024-11-19 15:19:11 +02:00
|
|
|
|
|
|
|
if (m_currentVertexShader == Handle)
|
|
|
|
m_currentVertexShader = 0;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShaderDeclaration(DWORD Handle, void* pData, DWORD* pSizeOfData) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
D3D8VertexShaderInfo* pInfo = getVertexShaderInfo(this, Handle);
|
|
|
|
|
|
|
|
if (unlikely(!pInfo))
|
|
|
|
return D3DERR_INVALIDCALL;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
UINT SizeOfData = *pSizeOfData;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// Get actual size
|
|
|
|
UINT ActualSize = pInfo->declaration.size() * sizeof(DWORD);
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (pData == nullptr) {
|
|
|
|
*pSizeOfData = ActualSize;
|
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// D3D8-specific behavior
|
|
|
|
if (SizeOfData < ActualSize) {
|
2024-11-19 15:19:11 +02:00
|
|
|
// 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;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(pData, pInfo->declaration.data(), ActualSize);
|
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShaderFunction(DWORD Handle, void* pData, DWORD* pSizeOfData) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
D3D8VertexShaderInfo* pInfo = getVertexShaderInfo(this, Handle);
|
|
|
|
|
|
|
|
if (unlikely(!pInfo))
|
|
|
|
return D3DERR_INVALIDCALL;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
UINT SizeOfData = *pSizeOfData;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// Get actual size
|
|
|
|
UINT ActualSize = pInfo->function.size() * sizeof(DWORD);
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (pData == nullptr) {
|
|
|
|
*pSizeOfData = ActualSize;
|
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// D3D8-specific behavior
|
|
|
|
if (SizeOfData < ActualSize) {
|
2024-11-19 15:19:11 +02:00
|
|
|
// 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;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(pData, pInfo->function.data(), ActualSize);
|
|
|
|
return D3D_OK;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pixel Shaders //
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::CreatePixelShader(
|
|
|
|
const DWORD* pFunction,
|
|
|
|
DWORD* pHandle) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(pFunction == nullptr || pHandle == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2025-03-04 19:59:46 +02:00
|
|
|
Com<d3d9::IDirect3DPixelShader9> pPixelShader;
|
2024-07-07 12:10:48 +01:00
|
|
|
HRESULT res = GetD3D9()->CreatePixelShader(pFunction, &pPixelShader);
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res))) {
|
2025-03-04 19:59:46 +02:00
|
|
|
m_pixelShaders.push_back(std::move(pPixelShader));
|
2024-08-24 15:37:01 +03:00
|
|
|
// Still set the shader bit, to prevent conflicts with NULL.
|
2024-09-30 22:58:31 +03:00
|
|
|
*pHandle = getShaderHandle(m_pixelShaders.size());
|
2024-08-24 15:37:01 +03:00
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline d3d9::IDirect3DPixelShader9* getPixelShaderPtr(D3D8Device* device, DWORD Handle) {
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
Handle = getShaderIndex(Handle);
|
|
|
|
|
|
|
|
if (unlikely(Handle >= device->m_pixelShaders.size())) {
|
2024-10-07 18:11:50 +03:00
|
|
|
Logger::debug(str::format("D3D8: Invalid pixel shader index ", std::hex, Handle));
|
2024-07-07 12:10:48 +01:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2024-09-10 15:35:02 +03:00
|
|
|
d3d9::IDirect3DPixelShader9* pPixelShader = device->m_pixelShaders[Handle].ptr();
|
2024-07-07 12:10:48 +01:00
|
|
|
|
|
|
|
if (unlikely(pPixelShader == nullptr)) {
|
2024-10-07 18:11:50 +03:00
|
|
|
Logger::debug(str::format("D3D8: Application provided deleted pixel shader ", std::hex, Handle));
|
2024-07-07 12:10:48 +01:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pPixelShader;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::SetPixelShader(DWORD Handle) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
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();
|
2024-08-24 15:37:01 +03:00
|
|
|
HRESULT res = GetD3D9()->SetPixelShader(pPixelShader);
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (likely(SUCCEEDED(res))) {
|
|
|
|
// Cache current pixel shader
|
|
|
|
m_currentPixelShader = Handle;
|
|
|
|
}
|
2024-07-07 12:10:48 +01:00
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
return res;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetPixelShader(DWORD* pHandle) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-08-24 15:37:01 +03:00
|
|
|
if (unlikely(pHandle == nullptr))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// Return cached shader
|
|
|
|
*pHandle = m_currentPixelShader;
|
|
|
|
|
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::DeletePixelShader(DWORD Handle) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
d3d9::IDirect3DPixelShader9* pPixelShader = getPixelShaderPtr(this, Handle);
|
|
|
|
|
|
|
|
if (unlikely(!pPixelShader)) {
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_pixelShaders[getShaderIndex(Handle)] = nullptr;
|
|
|
|
|
2024-11-19 15:19:11 +02:00
|
|
|
if (m_currentPixelShader == Handle)
|
|
|
|
m_currentPixelShader = 0;
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D8Device::GetPixelShaderFunction(DWORD Handle, void* pData, DWORD* pSizeOfData) {
|
2024-09-17 01:05:35 +03:00
|
|
|
D3D8DeviceLock lock = LockDevice();
|
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
d3d9::IDirect3DPixelShader9* pPixelShader = getPixelShaderPtr(this, Handle);
|
|
|
|
|
|
|
|
if (unlikely(!pPixelShader))
|
|
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
|
|
|
|
UINT SizeOfData = *pSizeOfData;
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
// Get actual size
|
|
|
|
UINT ActualSize = 0;
|
|
|
|
pPixelShader->GetFunction(nullptr, &ActualSize);
|
2025-01-09 11:59:59 +02:00
|
|
|
|
2024-07-07 12:10:48 +01:00
|
|
|
if (pData == nullptr) {
|
|
|
|
*pSizeOfData = ActualSize;
|
|
|
|
return D3D_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// D3D8-specific behavior
|
|
|
|
if (SizeOfData < ActualSize) {
|
2024-11-19 15:19:11 +02:00
|
|
|
// 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;
|
2024-07-07 12:10:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return pPixelShader->GetFunction(pData, &SizeOfData);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|