1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2024-11-28 16:24:13 +01:00

[d3d8] Implement Direct3D 8 Frontend

Co-authored-by: WinterSnowfall <WinterSnowfall@users.noreply.github.com>

## Config Changes

Co-authored-by: Blisto91 <47954800+Blisto91@users.noreply.github.com>
Co-authored-by: simifor <simirmonfor@gmail.com>
This commit is contained in:
Jeff 2024-07-07 12:10:48 +01:00 committed by GitHub
parent ef0c6b6f6f
commit 60e523b4bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 5069 additions and 10 deletions

View File

@ -32,6 +32,13 @@ jobs:
Write-Output "VSDEVCMD=${installationPath}\Common7\Tools\VsDevCmd.bat" `
| Out-File -FilePath "${Env:GITHUB_ENV}" -Append
- name: Download D3D8 SDK Headers
shell: pwsh
run: |
Invoke-WebRequest -URI https://raw.githubusercontent.com/NovaRain/DXSDK_Collection/master/DXSDK_Aug2007/Include/d3d8.h -OutFile include/d3d8.h
Invoke-WebRequest -URI https://raw.githubusercontent.com/NovaRain/DXSDK_Collection/master/DXSDK_Aug2007/Include/d3d8types.h -OutFile include/d3d8types.h
Invoke-WebRequest -URI https://raw.githubusercontent.com/NovaRain/DXSDK_Collection/master/DXSDK_Aug2007/Include/d3d8caps.h -OutFile include/d3d8caps.h
- name: Build MSVC x86
shell: pwsh
run: |

View File

@ -1,5 +1,6 @@
Copyright (c) 2017 Philip Rebohle
Copyright (c) 2019 Joshua Ashton
Copyright (c) 2023 Jeffrey Ellison
zlib/libpng license

View File

@ -1,6 +1,6 @@
# DXVK
A Vulkan-based translation layer for Direct3D 9/10/11 which allows running 3D applications on Linux using Wine.
A Vulkan-based translation layer for Direct3D 8/9/10/11 which allows running 3D applications on Linux using Wine.
For the current status of the project, please refer to the [project wiki](https://github.com/doitsujin/dxvk/wiki).

View File

@ -709,3 +709,29 @@
# - True/False
# d3d9.countLosableResources = True
# Use NVIDIA Shadow Buffers
#
# Vendor behavior for GeForce3 and GeForce4 cards that allows
# sampling depth textures with non-normalized Z coordinates
# and applies hardware shadow filtering.
#
# Supported values:
# - True/False
# d3d8.useShadowBuffers = False
# MANAGED Buffer Placement
#
# Remap some DEFAULT pool vertex and index buffers to the MANAGED pool to improve
# performance by avoiding waiting for games that frequently lock (large) buffers.
#
# This implicitly disables direct buffer mapping. Some applications may need this option
# disabled for certain (smaller) buffers to keep from overwriting in-use buffer regions.
#
# Supported values:
# - True/False
# - Any non-negative integer - sets the minimum size in bytes for a buffer to be MANAGED
# d3d8.managedBufferPlacement = True

View File

@ -1,4 +1,5 @@
option('enable_dxgi', type : 'boolean', value : true, description: 'Build DXGI')
option('enable_d3d8', type : 'boolean', value : true, description: 'Build D3D8')
option('enable_d3d9', type : 'boolean', value : true, description: 'Build D3D9')
option('enable_d3d10', type : 'boolean', value : true, description: 'Build D3D10')
option('enable_d3d11', type : 'boolean', value : true, description: 'Build D3D11')

3
src/d3d8/d3d8.def Normal file
View File

@ -0,0 +1,3 @@
LIBRARY D3D8.DLL
EXPORTS
Direct3DCreate8 @ 5

7
src/d3d8/d3d8.sym Normal file
View File

@ -0,0 +1,7 @@
{
global:
Direct3DCreate8;
local:
*;
};

239
src/d3d8/d3d8_batch.h Normal file
View File

@ -0,0 +1,239 @@
#pragma once
#include "d3d8_include.h"
#include "d3d8_buffer.h"
#include "d3d8_format.h"
#include <vector>
#include <cstdint>
#include <climits>
namespace dxvk {
inline constexpr size_t D3DPT_COUNT = size_t(D3DPT_TRIANGLEFAN) + 1;
inline constexpr D3DPRIMITIVETYPE D3DPT_INVALID = D3DPRIMITIVETYPE(0);
// Vertex buffer that can handle many tiny locks while
// still maintaing the lock ordering of direct-mapped buffers.
class D3D8BatchBuffer final : public D3D8VertexBuffer {
public:
D3D8BatchBuffer(
D3D8Device* pDevice,
D3DPOOL Pool,
DWORD Usage,
UINT Length,
DWORD FVF)
: D3D8VertexBuffer(pDevice, nullptr, Pool, Usage)
, m_data(Length)
, m_fvf(FVF) {
}
HRESULT STDMETHODCALLTYPE Lock(
UINT OffsetToLock,
UINT SizeToLock,
BYTE** ppbData,
DWORD Flags) {
*ppbData = m_data.data() + OffsetToLock;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE Unlock() {
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE GetDesc(D3DVERTEXBUFFER_DESC* pDesc) {
pDesc->Format = D3DFMT_VERTEXDATA;
pDesc->Type = D3DRTYPE_VERTEXBUFFER;
pDesc->Usage = m_usage;
pDesc->Pool = m_pool;
pDesc->Size = m_data.size();
pDesc->FVF = m_fvf;
return D3D_OK;
}
void STDMETHODCALLTYPE PreLoad() {
}
const void* GetPtr(UINT byteOffset = 0) const {
return m_data.data() + byteOffset;
}
UINT Size() const {
return m_data.size();
}
private:
std::vector<BYTE> m_data;
DWORD m_fvf;
};
// Main handler for batching D3D8 draw calls.
class D3D8Batcher {
struct Batch {
D3DPRIMITIVETYPE PrimitiveType = D3DPT_INVALID;
std::vector<uint16_t> Indices;
UINT Offset = 0;
UINT MinVertex = UINT_MAX;
UINT MaxVertex = 0;
UINT PrimitiveCount = 0;
UINT DrawCallCount = 0;
};
public:
D3D8Batcher(D3D8Device* pDevice8, Com<d3d9::IDirect3DDevice9>&& pDevice9)
: m_device8(pDevice8)
, m_device(std::move(pDevice9)) {
}
inline D3D8BatchBuffer* CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool) {
return ref(new D3D8BatchBuffer(m_device8, Pool, Usage, Length, FVF));
}
inline void StateChange() {
if (likely(m_batches.empty()))
return;
for (auto& draw : m_batches) {
if (draw.PrimitiveType == D3DPT_INVALID)
continue;
for (auto& index : draw.Indices)
index -= draw.MinVertex;
m_device->DrawIndexedPrimitiveUP(
d3d9::D3DPRIMITIVETYPE(draw.PrimitiveType),
0,
draw.MaxVertex - draw.MinVertex,
draw.PrimitiveCount,
draw.Indices.data(),
d3d9::D3DFMT_INDEX16,
m_stream->GetPtr(draw.MinVertex * m_stride),
m_stride);
m_device->SetStreamSource(0, D3D8VertexBuffer::GetD3D9Nullable(m_stream), 0, m_stride);
m_device->SetIndices(D3D8IndexBuffer::GetD3D9Nullable(m_indices));
draw.PrimitiveType = D3DPRIMITIVETYPE(0);
draw.Offset = 0;
draw.MinVertex = UINT_MAX;
draw.MaxVertex = 0;
draw.PrimitiveCount = 0;
draw.DrawCallCount = 0;
}
}
inline void EndFrame() {
// Nothing to be done.
}
inline HRESULT DrawPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
UINT StartVertex,
UINT PrimitiveCount) {
// None of this linestrip or fan malarkey
D3DPRIMITIVETYPE batchedPrimType = PrimitiveType;
switch (PrimitiveType) {
case D3DPT_LINESTRIP: batchedPrimType = D3DPT_LINELIST; break;
case D3DPT_TRIANGLEFAN: batchedPrimType = D3DPT_TRIANGLELIST; break;
default: break;
}
Batch* batch = &m_batches[size_t(batchedPrimType)];
batch->PrimitiveType = batchedPrimType;
//UINT vertices = GetVertexCount8(PrimitiveType, PrimitiveCount);
switch (PrimitiveType) {
case D3DPT_POINTLIST:
batch->Indices.resize(batch->Offset + PrimitiveCount);
for (UINT i = 0; i < PrimitiveCount; i++)
batch->Indices[batch->Offset++] = (StartVertex + i);
break;
case D3DPT_LINELIST:
batch->Indices.resize(batch->Offset + PrimitiveCount * 2);
for (UINT i = 0; i < PrimitiveCount; i++) {
batch->Indices[batch->Offset++] = (StartVertex + i * 2 + 0);
batch->Indices[batch->Offset++] = (StartVertex + i * 2 + 1);
}
break;
case D3DPT_LINESTRIP:
batch->Indices.resize(batch->Offset + PrimitiveCount * 2);
for (UINT i = 0; i < PrimitiveCount; i++) {
batch->Indices[batch->Offset++] = (StartVertex + i + 0);
batch->Indices[batch->Offset++] = (StartVertex + i + 1);
}
break;
case D3DPT_TRIANGLELIST:
batch->Indices.resize(batch->Offset + PrimitiveCount * 3);
for (UINT i = 0; i < PrimitiveCount; i++) {
batch->Indices[batch->Offset++] = (StartVertex + i * 3 + 0);
batch->Indices[batch->Offset++] = (StartVertex + i * 3 + 1);
batch->Indices[batch->Offset++] = (StartVertex + i * 3 + 2);
}
break;
case D3DPT_TRIANGLESTRIP:
// Join with degenerate triangle
// 1 2 3, 3 4, 4 5 6
batch->Indices.resize(batch->Offset + PrimitiveCount + 2);
if (batch->Offset > 0) {
batch->Indices[batch->Offset + 1] = batch->Indices[batch->Offset-2];
batch->Indices[batch->Offset += 2] = StartVertex;
}
for (UINT i = 0; i < PrimitiveCount; i++) {
batch->Indices[batch->Offset++] = (StartVertex + i + 0);
}
break;
// 1 2 3 4 5 6 7 -> 1 2 3, 1 3 4, 1 4 5, 1 5 6, 1 6 7
case D3DPT_TRIANGLEFAN:
batch->Indices.resize(batch->Offset + PrimitiveCount * 3);
for (UINT i = 0; i < PrimitiveCount; i++) {
batch->Indices[batch->Offset++] = (StartVertex + 0);
batch->Indices[batch->Offset++] = (StartVertex + i + 1);
batch->Indices[batch->Offset++] = (StartVertex + i + 2);
}
break;
default:
return D3DERR_INVALIDCALL;
}
batch->MinVertex = std::min(batch->MinVertex, StartVertex);
if (!batch->Indices.empty())
batch->MaxVertex = std::max(batch->MaxVertex, UINT(batch->Indices.back() + 1));
batch->PrimitiveCount += PrimitiveCount;
batch->DrawCallCount++;
return D3D_OK;
}
inline void SetStream(UINT num, D3D8VertexBuffer* stream, UINT stride) {
if (unlikely(num != 0)) {
StateChange();
return;
}
if (unlikely(m_stream != stream || m_stride != stride)) {
StateChange();
m_stream = static_cast<D3D8BatchBuffer*>(stream);
m_stride = stride;
}
}
inline void SetIndices(D3D8IndexBuffer* indices, INT baseVertexIndex) {
if (m_indices != indices || m_baseVertexIndex != baseVertexIndex) {
StateChange();
m_indices = indices;
m_baseVertexIndex = baseVertexIndex;
}
}
private:
D3D8Device* m_device8;
Com<d3d9::IDirect3DDevice9> m_device;
D3D8BatchBuffer* m_stream = nullptr;
UINT m_stride = 0;
D3D8IndexBuffer* m_indices = nullptr;
INT m_baseVertexIndex = 0;
std::array<Batch, D3DPT_COUNT> m_batches;
};
}

102
src/d3d8/d3d8_buffer.h Normal file
View File

@ -0,0 +1,102 @@
#pragma once
#include "d3d8_include.h"
#include "d3d8_resource.h"
namespace dxvk {
template <typename D3D9, typename D3D8>
class D3D8Buffer : public D3D8Resource<D3D9, D3D8> {
public:
D3D8Buffer(
D3D8Device* pDevice,
Com<D3D9>&& pBuffer,
D3DPOOL Pool,
DWORD Usage)
: D3D8Resource<D3D9, D3D8> (pDevice, std::move(pBuffer))
, m_pool (Pool)
, m_usage (Usage) {
}
HRESULT STDMETHODCALLTYPE Lock(
UINT OffsetToLock,
UINT SizeToLock,
BYTE** ppbData,
DWORD Flags) {
return this->GetD3D9()->Lock(
OffsetToLock,
SizeToLock,
reinterpret_cast<void**>(ppbData),
Flags);
}
HRESULT STDMETHODCALLTYPE Unlock() {
return this->GetD3D9()->Unlock();
}
void STDMETHODCALLTYPE PreLoad() {
this->GetD3D9()->PreLoad();
}
protected:
// This is the D3D8 pool, not necessarily what's given to D3D9.
const D3DPOOL m_pool;
// This is the D3D8 usage, not necessarily what's given to D3D9.
const DWORD m_usage;
};
using D3D8VertexBufferBase = D3D8Buffer<d3d9::IDirect3DVertexBuffer9, IDirect3DVertexBuffer8>;
class D3D8VertexBuffer : public D3D8VertexBufferBase {
public:
D3D8VertexBuffer(
D3D8Device* pDevice,
Com<d3d9::IDirect3DVertexBuffer9>&& pBuffer,
D3DPOOL Pool,
DWORD Usage)
: D3D8VertexBufferBase(pDevice, std::move(pBuffer), Pool, Usage) {
}
D3DRESOURCETYPE STDMETHODCALLTYPE GetType() final { return D3DRTYPE_VERTEXBUFFER; }
HRESULT STDMETHODCALLTYPE GetDesc(D3DVERTEXBUFFER_DESC* pDesc) {
HRESULT hr = GetD3D9()->GetDesc(reinterpret_cast<d3d9::D3DVERTEXBUFFER_DESC*>(pDesc));
if (!FAILED(hr)) {
pDesc->Pool = m_pool;
pDesc->Usage = m_usage;
}
return hr;
}
};
using D3D8IndexBufferBase = D3D8Buffer<d3d9::IDirect3DIndexBuffer9, IDirect3DIndexBuffer8>;
class D3D8IndexBuffer final : public D3D8IndexBufferBase {
public:
D3D8IndexBuffer(
D3D8Device* pDevice,
Com<d3d9::IDirect3DIndexBuffer9>&& pBuffer,
D3DPOOL Pool,
DWORD Usage)
: D3D8IndexBufferBase(pDevice, std::move(pBuffer), Pool, Usage) {
}
D3DRESOURCETYPE STDMETHODCALLTYPE GetType() final { return D3DRTYPE_INDEXBUFFER; }
HRESULT STDMETHODCALLTYPE GetDesc(D3DINDEXBUFFER_DESC* pDesc) final {
HRESULT hr = GetD3D9()->GetDesc(reinterpret_cast<d3d9::D3DINDEXBUFFER_DESC*>(pDesc));
if (!FAILED(hr)) {
pDesc->Pool = m_pool;
pDesc->Usage = m_usage;
}
return hr;
}
};
}

8
src/d3d8/d3d8_caps.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
namespace dxvk::d8caps {
inline constexpr uint32_t MAX_TEXTURE_STAGES = 8;
inline constexpr uint32_t MAX_STREAMS = 16;
}

164
src/d3d8/d3d8_d3d9_util.h Normal file
View File

@ -0,0 +1,164 @@
#pragma once
// Utility functions for converting
// between DirectX8 and DirectX9 types.
#include "d3d8_include.h"
#include "d3d8_format.h"
#include "d3d8_options.h"
#include <utility>
namespace dxvk {
// (8<-9) D3DCAPSX: Writes to D3DCAPS8 from D3DCAPS9
inline void ConvertCaps8(const d3d9::D3DCAPS9& caps9, D3DCAPS8* pCaps8) {
// should be aligned
std::memcpy(pCaps8, &caps9, sizeof(D3DCAPS8));
// Max supported shader model is PS 1.4 and VS 1.1
pCaps8->VertexShaderVersion = D3DVS_VERSION(1, 1);
pCaps8->PixelShaderVersion = D3DPS_VERSION(1, 4);
// This was removed by D3D9. We can probably render windowed.
pCaps8->Caps2 |= D3DCAPS2_CANRENDERWINDOWED;
// Replaced by D3DPRASTERCAPS_DEPTHBIAS in D3D9
pCaps8->RasterCaps |= D3DPRASTERCAPS_ZBIAS;
// Remove D3D9-specific caps:
pCaps8->Caps2 &= ~D3DCAPS2_CANAUTOGENMIPMAP;
pCaps8->Caps3 &= ~D3DCAPS3_LINEAR_TO_SRGB_PRESENTATION
& ~D3DCAPS3_COPY_TO_VIDMEM
& ~D3DCAPS3_COPY_TO_SYSTEMMEM;
pCaps8->PrimitiveMiscCaps &= ~D3DPMISCCAPS_INDEPENDENTWRITEMASKS
& ~D3DPMISCCAPS_PERSTAGECONSTANT
& ~D3DPMISCCAPS_FOGANDSPECULARALPHA
& ~D3DPMISCCAPS_SEPARATEALPHABLEND
& ~D3DPMISCCAPS_MRTINDEPENDENTBITDEPTHS
& ~D3DPMISCCAPS_MRTPOSTPIXELSHADERBLENDING
& ~D3DPMISCCAPS_FOGVERTEXCLAMPED
& ~D3DPMISCCAPS_POSTBLENDSRGBCONVERT;
pCaps8->RasterCaps &= ~D3DPRASTERCAPS_SCISSORTEST
& ~D3DPRASTERCAPS_SLOPESCALEDEPTHBIAS
& ~D3DPRASTERCAPS_DEPTHBIAS
& ~D3DPRASTERCAPS_MULTISAMPLE_TOGGLE;
pCaps8->SrcBlendCaps &= ~D3DPBLENDCAPS_INVSRCCOLOR2
& ~D3DPBLENDCAPS_SRCCOLOR2;
pCaps8->LineCaps &= ~D3DLINECAPS_ANTIALIAS;
pCaps8->StencilCaps &= ~D3DSTENCILCAPS_TWOSIDED;
}
// (9<-8) D3DD3DPRESENT_PARAMETERS: Returns D3D9's params given an input for D3D8
inline d3d9::D3DPRESENT_PARAMETERS ConvertPresentParameters9(const D3DPRESENT_PARAMETERS* pParams) {
d3d9::D3DPRESENT_PARAMETERS params;
params.BackBufferWidth = pParams->BackBufferWidth;
params.BackBufferHeight = pParams->BackBufferHeight;
params.BackBufferFormat = d3d9::D3DFORMAT(pParams->BackBufferFormat);
params.BackBufferCount = pParams->BackBufferCount;
params.MultiSampleType = d3d9::D3DMULTISAMPLE_TYPE(pParams->MultiSampleType);
params.MultiSampleQuality = 0; // (D3D8: no MultiSampleQuality), TODO: get a value for this
UINT PresentationInterval = pParams->FullScreen_PresentationInterval;
if (pParams->Windowed) {
if (unlikely(PresentationInterval != D3DPRESENT_INTERVAL_DEFAULT)) {
// TODO: what does dx8 do if windowed app sets FullScreen_PresentationInterval?
Logger::warn(str::format(
"D3D8 Application is windowed yet requested FullScreen_PresentationInterval ", PresentationInterval,
" (should be D3DPRESENT_INTERVAL_DEFAULT). This will be ignored."));
}
// D3D8: For windowed swap chain, the back buffer is copied to the window immediately.
PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
}
D3DSWAPEFFECT SwapEffect = pParams->SwapEffect;
// D3DSWAPEFFECT_COPY_VSYNC has been removed
if (SwapEffect == D3DSWAPEFFECT_COPY_VSYNC) {
SwapEffect = D3DSWAPEFFECT_COPY;
// D3D8: In windowed mode, D3DSWAPEFFECT_COPY_VSYNC enables VSYNC.
// In fullscreen, D3DPRESENT_INTERVAL_IMMEDIATE is meaningless.
if (pParams->Windowed || (PresentationInterval & D3DPRESENT_INTERVAL_IMMEDIATE) != 0) {
PresentationInterval = D3DPRESENT_INTERVAL_ONE;
// TODO: what does dx8 do if multiple D3DPRESENT_INTERVAL flags are set?
}
}
params.SwapEffect = d3d9::D3DSWAPEFFECT(SwapEffect);
params.hDeviceWindow = pParams->hDeviceWindow;
params.Windowed = pParams->Windowed;
params.EnableAutoDepthStencil = pParams->EnableAutoDepthStencil;
params.AutoDepthStencilFormat = d3d9::D3DFORMAT(pParams->AutoDepthStencilFormat);
params.Flags = pParams->Flags;
params.FullScreen_RefreshRateInHz = pParams->FullScreen_RefreshRateInHz;
// FullScreen_PresentationInterval -> PresentationInterval
params.PresentationInterval = PresentationInterval;
return params;
}
// (8<-9) Convert D3DSURFACE_DESC
inline void ConvertSurfaceDesc8(const d3d9::D3DSURFACE_DESC* pSurf9, D3DSURFACE_DESC* pSurf8) {
pSurf8->Format = D3DFORMAT(pSurf9->Format);
pSurf8->Type = D3DRESOURCETYPE(pSurf9->Type);
pSurf8->Usage = pSurf9->Usage;
pSurf8->Pool = D3DPOOL(pSurf9->Pool);
pSurf8->Size = getSurfaceSize(pSurf8->Format, pSurf9->Width, pSurf9->Height);
pSurf8->MultiSampleType = D3DMULTISAMPLE_TYPE(pSurf9->MultiSampleType);
// DX8: No multisample quality
pSurf8->Width = pSurf9->Width;
pSurf8->Height = pSurf9->Height;
}
// (8<-9) Convert D3DVOLUME_DESC
inline void ConvertVolumeDesc8(const d3d9::D3DVOLUME_DESC* pVol9, D3DVOLUME_DESC* pVol8) {
pVol8->Format = D3DFORMAT(pVol9->Format);
pVol8->Type = D3DRESOURCETYPE(pVol9->Type);
pVol8->Usage = pVol9->Usage;
pVol8->Pool = D3DPOOL(pVol9->Pool);
pVol8->Size = getSurfaceSize(pVol8->Format, pVol9->Width, pVol9->Height) * pVol9->Depth;
pVol8->Width = pVol9->Width;
pVol8->Height = pVol9->Height;
pVol8->Depth = pVol9->Depth;
}
// If this D3DTEXTURESTAGESTATETYPE has been remapped to a d3d9::D3DSAMPLERSTATETYPE
// it will be returned, otherwise returns -1
inline d3d9::D3DSAMPLERSTATETYPE GetSamplerStateType9(const D3DTEXTURESTAGESTATETYPE StageType) {
switch (StageType) {
// 13-21:
case D3DTSS_ADDRESSU: return d3d9::D3DSAMP_ADDRESSU;
case D3DTSS_ADDRESSV: return d3d9::D3DSAMP_ADDRESSV;
case D3DTSS_BORDERCOLOR: return d3d9::D3DSAMP_BORDERCOLOR;
case D3DTSS_MAGFILTER: return d3d9::D3DSAMP_MAGFILTER;
case D3DTSS_MINFILTER: return d3d9::D3DSAMP_MINFILTER;
case D3DTSS_MIPFILTER: return d3d9::D3DSAMP_MIPFILTER;
case D3DTSS_MIPMAPLODBIAS: return d3d9::D3DSAMP_MIPMAPLODBIAS;
case D3DTSS_MAXMIPLEVEL: return d3d9::D3DSAMP_MIPFILTER;
case D3DTSS_MAXANISOTROPY: return d3d9::D3DSAMP_MAXANISOTROPY;
// 25:
case D3DTSS_ADDRESSW: return d3d9::D3DSAMP_ADDRESSW;
default: return d3d9::D3DSAMPLERSTATETYPE(-1);
}
}
}

1738
src/d3d8/d3d8_device.cpp Normal file
View File

@ -0,0 +1,1738 @@
#include "d3d8_device.h"
#include "d3d8_interface.h"
#include "d3d8_shader.h"
#ifdef MSC_VER
#pragma fenv_access (on)
#endif
namespace dxvk {
static constexpr DWORD isFVF(DWORD Handle) {
return (Handle & D3DFVF_RESERVED0) == 0;
}
static constexpr DWORD getShaderHandle(DWORD Index) {
return (Index << 1) | D3DFVF_RESERVED0;
}
static constexpr DWORD getShaderIndex(DWORD Handle) {
if ((Handle & D3DFVF_RESERVED0) != 0) {
return (Handle & ~(D3DFVF_RESERVED0)) >> 1;
} else {
return Handle;
}
}
struct D3D8VertexShaderInfo {
d3d9::IDirect3DVertexDeclaration9* pVertexDecl = nullptr;
d3d9::IDirect3DVertexShader9* pVertexShader = nullptr;
std::vector<DWORD> declaration;
std::vector<DWORD> function;
};
D3D8Device::D3D8Device(
D3D8Interface* pParent,
Com<d3d9::IDirect3DDevice9>&& pDevice,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS* pParams)
: D3D8DeviceBase(std::move(pDevice))
, m_d3d8Options(pParent->GetOptions())
, m_parent(pParent)
, m_presentParams(*pParams)
, m_deviceType(DeviceType)
, m_window(hFocusWindow)
, m_behaviorFlags(BehaviorFlags) {
// Get the bridge interface to D3D9.
if (FAILED(GetD3D9()->QueryInterface(__uuidof(IDxvkD3D8Bridge), (void**)&m_bridge))) {
throw DxvkError("D3D8Device: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!");
}
m_bridge->SetAPIName("D3D8");
m_bridge->SetD3D8CompatibilityMode(true);
ResetState();
RecreateBackBuffersAndAutoDepthStencil();
if (m_d3d8Options.batching)
m_batcher = new D3D8Batcher(this, GetD3D9());
}
D3D8Device::~D3D8Device() {
if (m_batcher)
delete m_batcher;
// Delete any remaining state blocks.
for (D3D8StateBlock* block : m_stateBlocks) {
delete block;
}
}
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;
d3d9::IDirect3DQuery9* pQuery = nullptr;
switch (DevInfoID) {
// pre-D3D8 queries
case 0:
case D3DDEVINFOID_TEXTUREMANAGER:
case D3DDEVINFOID_D3DTEXTUREMANAGER:
case D3DDEVINFOID_TEXTURING:
return E_FAIL;
case D3DDEVINFOID_VCACHE:
// The query will return D3D_OK on Nvidia and D3DERR_NOTAVAILABLE on AMD/Intel
// in D3D9, however in the case of the latter we'll need to return a
// zeroed out query result and S_FALSE. This behavior has been observed both
// on modern native AMD drivers and D3D8-era native ATI drivers.
res = GetD3D9()->CreateQuery(d3d9::D3DQUERYTYPE_VCACHE, &pQuery);
struct D3DDEVINFO_VCACHE {
DWORD Pattern;
DWORD OptMethod;
DWORD CacheSize;
DWORD MagicNumber;
};
if(FAILED(res)) {
if (DevInfoStructSize != sizeof(D3DDEVINFO_VCACHE))
return D3DERR_INVALIDCALL;
memset(pDevInfoStruct, 0, sizeof(D3DDEVINFO_VCACHE));
return S_FALSE;
}
break;
case D3DDEVINFOID_RESOURCEMANAGER:
// May not be implemented by D9VK.
res = GetD3D9()->CreateQuery(d3d9::D3DQUERYTYPE_RESOURCEMANAGER, &pQuery);
break;
case D3DDEVINFOID_VERTEXSTATS:
res = GetD3D9()->CreateQuery(d3d9::D3DQUERYTYPE_VERTEXSTATS, &pQuery);
break;
default:
Logger::warn(str::format("D3D8Device::GetInfo: Unsupported device info ID: ", DevInfoID));
return E_FAIL;
}
if (unlikely(FAILED(res)))
goto done;
// Immediately issue the query.
// D3D9 will begin it automatically before ending.
res = pQuery->Issue(D3DISSUE_END);
if (unlikely(FAILED(res))) {
goto done;
}
// TODO: Will immediately issuing the query without doing any API calls
// actually yield meaingful results? And should we flush or let it mellow?
res = pQuery->GetData(pDevInfoStruct, DevInfoStructSize, D3DGETDATA_FLUSH);
done:
if (pQuery != nullptr)
pQuery->Release();
if (unlikely(FAILED(res))) {
if (res == D3DERR_NOTAVAILABLE) // unsupported
return E_FAIL;
else // any unknown error
return S_FALSE;
}
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::TestCooperativeLevel() {
// Equivalent of D3D11/DXGI present tests.
return GetD3D9()->TestCooperativeLevel();
}
UINT STDMETHODCALLTYPE D3D8Device::GetAvailableTextureMem() {
return GetD3D9()->GetAvailableTextureMem();
}
HRESULT STDMETHODCALLTYPE D3D8Device::ResourceManagerDiscardBytes(DWORD bytes) {
return GetD3D9()->EvictManagedResources();
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetDirect3D(IDirect3D8** ppD3D8) {
if (ppD3D8 == nullptr)
return D3DERR_INVALIDCALL;
*ppD3D8 = m_parent.ref();
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetDeviceCaps(D3DCAPS8* pCaps) {
d3d9::D3DCAPS9 caps9;
HRESULT res = GetD3D9()->GetDeviceCaps(&caps9);
dxvk::ConvertCaps8(caps9, pCaps);
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetDisplayMode(D3DDISPLAYMODE* pMode) {
// swap chain 0
return GetD3D9()->GetDisplayMode(0, (d3d9::D3DDISPLAYMODE*)pMode);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS* pParameters) {
return GetD3D9()->GetCreationParameters((d3d9::D3DDEVICE_CREATION_PARAMETERS*)pParameters);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetCursorProperties(
UINT XHotSpot,
UINT YHotSpot,
IDirect3DSurface8* pCursorBitmap) {
D3D8Surface* surf = static_cast<D3D8Surface*>(pCursorBitmap);
return GetD3D9()->SetCursorProperties(XHotSpot, YHotSpot, D3D8Surface::GetD3D9Nullable(surf));
}
void STDMETHODCALLTYPE D3D8Device::SetCursorPosition(UINT XScreenSpace, UINT YScreenSpace, DWORD Flags) {
GetD3D9()->SetCursorPosition(XScreenSpace, YScreenSpace, Flags);
}
// Microsoft d3d8.h in the DirectX 9 SDK uses a different function signature...
void STDMETHODCALLTYPE D3D8Device::SetCursorPosition(int X, int Y, DWORD Flags) {
GetD3D9()->SetCursorPosition(X, Y, Flags);
}
BOOL STDMETHODCALLTYPE D3D8Device::ShowCursor(BOOL bShow) {
return GetD3D9()->ShowCursor(bShow);
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateAdditionalSwapChain(
D3DPRESENT_PARAMETERS* pPresentationParameters,
IDirect3DSwapChain8** ppSwapChain) {
Com<d3d9::IDirect3DSwapChain9> pSwapChain9;
d3d9::D3DPRESENT_PARAMETERS params = ConvertPresentParameters9(pPresentationParameters);
HRESULT res = GetD3D9()->CreateAdditionalSwapChain(
&params,
&pSwapChain9
);
*ppSwapChain = ref(new D3D8SwapChain(this, std::move(pSwapChain9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::Reset(D3DPRESENT_PARAMETERS* pPresentationParameters) {
StateChange();
m_presentParams = *pPresentationParameters;
ResetState();
d3d9::D3DPRESENT_PARAMETERS params = ConvertPresentParameters9(pPresentationParameters);
HRESULT res = GetD3D9()->Reset(&params);
if (FAILED(res))
return res;
RecreateBackBuffersAndAutoDepthStencil();
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::Present(
const RECT* pSourceRect,
const RECT* pDestRect,
HWND hDestWindowOverride,
const RGNDATA* pDirtyRegion) {
m_batcher->EndFrame();
StateChange();
return GetD3D9()->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetBackBuffer(
UINT iBackBuffer,
D3DBACKBUFFER_TYPE Type,
IDirect3DSurface8** ppBackBuffer) {
InitReturnPtr(ppBackBuffer);
if (iBackBuffer >= m_backBuffers.size() || m_backBuffers[iBackBuffer] == nullptr) {
Com<d3d9::IDirect3DSurface9> pSurface9;
HRESULT res = GetD3D9()->GetBackBuffer(0, iBackBuffer, (d3d9::D3DBACKBUFFER_TYPE)Type, &pSurface9);
if (FAILED(res)) return res;
m_backBuffers[iBackBuffer] = new D3D8Surface(this, std::move(pSurface9));
*ppBackBuffer = m_backBuffers[iBackBuffer].ref();
return res;
}
*ppBackBuffer = m_backBuffers[iBackBuffer].ref();
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetRasterStatus(D3DRASTER_STATUS* pRasterStatus) {
return GetD3D9()->GetRasterStatus(0, (d3d9::D3DRASTER_STATUS*)pRasterStatus);
}
void STDMETHODCALLTYPE D3D8Device::SetGammaRamp(DWORD Flags, const D3DGAMMARAMP* pRamp) {
StateChange();
// For swap chain 0
GetD3D9()->SetGammaRamp(0, Flags, reinterpret_cast<const d3d9::D3DGAMMARAMP*>(pRamp));
}
void STDMETHODCALLTYPE D3D8Device::GetGammaRamp(D3DGAMMARAMP* pRamp) {
// For swap chain 0
GetD3D9()->GetGammaRamp(0, reinterpret_cast<d3d9::D3DGAMMARAMP*>(pRamp));
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateTexture(
UINT Width,
UINT Height,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DTexture8** ppTexture) {
InitReturnPtr(ppTexture);
// Nvidia & Intel workaround for The Lord of the Rings: The Fellowship of the Ring
if (m_d3d8Options.placeP8InScratch && Format == D3DFMT_P8)
Pool = D3DPOOL_SCRATCH;
Com<d3d9::IDirect3DTexture9> pTex9 = nullptr;
HRESULT res = GetD3D9()->CreateTexture(
Width,
Height,
Levels,
Usage,
d3d9::D3DFORMAT(Format),
d3d9::D3DPOOL(Pool),
&pTex9,
NULL);
if (FAILED(res))
return res;
*ppTexture = ref(new D3D8Texture2D(this, std::move(pTex9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateVolumeTexture(
UINT Width,
UINT Height,
UINT Depth,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DVolumeTexture8** ppVolumeTexture) {
Com<d3d9::IDirect3DVolumeTexture9> pVolume9 = nullptr;
HRESULT res = GetD3D9()->CreateVolumeTexture(
Width, Height, Depth, Levels,
Usage,
d3d9::D3DFORMAT(Format),
d3d9::D3DPOOL(Pool),
&pVolume9,
NULL);
*ppVolumeTexture = ref(new D3D8Texture3D(this, std::move(pVolume9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateCubeTexture(
UINT EdgeLength,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DCubeTexture8** ppCubeTexture) {
Com<d3d9::IDirect3DCubeTexture9> pCube9 = nullptr;
HRESULT res = GetD3D9()->CreateCubeTexture(
EdgeLength,
Levels,
Usage,
d3d9::D3DFORMAT(Format),
d3d9::D3DPOOL(Pool),
&pCube9,
NULL);
*ppCubeTexture = ref(new D3D8TextureCube(this, std::move(pCube9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateVertexBuffer(
UINT Length,
DWORD Usage,
DWORD FVF,
D3DPOOL Pool,
IDirect3DVertexBuffer8** ppVertexBuffer) {
InitReturnPtr(ppVertexBuffer);
if (ShouldBatch()) {
*ppVertexBuffer = m_batcher->CreateVertexBuffer(Length, Usage, FVF, Pool);
return D3D_OK;
}
Com<d3d9::IDirect3DVertexBuffer9> pVertexBuffer9 = nullptr;
HRESULT res = GetD3D9()->CreateVertexBuffer(Length, Usage, FVF, d3d9::D3DPOOL(Pool), &pVertexBuffer9, NULL);
if (!FAILED(res))
*ppVertexBuffer = ref(new D3D8VertexBuffer(this, std::move(pVertexBuffer9), Pool, Usage));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateIndexBuffer(
UINT Length,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DIndexBuffer8** ppIndexBuffer) {
InitReturnPtr(ppIndexBuffer);
Com<d3d9::IDirect3DIndexBuffer9> pIndexBuffer9 = nullptr;
HRESULT res = GetD3D9()->CreateIndexBuffer(Length, Usage, d3d9::D3DFORMAT(Format), d3d9::D3DPOOL(Pool), &pIndexBuffer9, NULL);
if (!FAILED(res))
*ppIndexBuffer = ref(new D3D8IndexBuffer(this, std::move(pIndexBuffer9), Pool, Usage));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateRenderTarget(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DMULTISAMPLE_TYPE MultiSample,
BOOL Lockable,
IDirect3DSurface8** ppSurface) {
Com<d3d9::IDirect3DSurface9> pSurf9 = nullptr;
HRESULT res = GetD3D9()->CreateRenderTarget(
Width,
Height,
d3d9::D3DFORMAT(Format),
d3d9::D3DMULTISAMPLE_TYPE(MultiSample),
0, // TODO: CreateRenderTarget MultisampleQuality
Lockable,
&pSurf9,
NULL);
*ppSurface = ref(new D3D8Surface(this, std::move(pSurf9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateDepthStencilSurface(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DMULTISAMPLE_TYPE MultiSample,
IDirect3DSurface8** ppSurface) {
Com<d3d9::IDirect3DSurface9> pSurf9 = nullptr;
HRESULT res = GetD3D9()->CreateDepthStencilSurface(
Width,
Height,
d3d9::D3DFORMAT(Format),
d3d9::D3DMULTISAMPLE_TYPE(MultiSample),
0, // TODO: CreateDepthStencilSurface MultisampleQuality
true, // TODO: CreateDepthStencilSurface Discard
&pSurf9,
NULL);
*ppSurface = ref(new D3D8Surface(this, std::move(pSurf9)));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateImageSurface(
UINT Width,
UINT Height,
D3DFORMAT Format,
IDirect3DSurface8** ppSurface) {
// FIXME: Handle D3DPOOL_SCRATCH in CopyRects
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);
*ppSurface = ref(new D3D8Surface(this, std::move(pSurf)));
return res;
}
// Copies texture rect in system mem using memcpy.
// Rects must be congruent, but need not be aligned.
HRESULT copyTextureBuffers(
D3D8Surface* src,
D3D8Surface* dst,
const d3d9::D3DSURFACE_DESC& srcDesc,
const d3d9::D3DSURFACE_DESC& dstDesc,
const RECT& srcRect,
const RECT& dstRect) {
HRESULT res = D3D_OK;
D3DLOCKED_RECT srcLocked, dstLocked;
// CopyRects cannot perform format conversions.
if (srcDesc.Format != dstDesc.Format)
return D3DERR_INVALIDCALL;
bool compressed = isDXT(srcDesc.Format);
res = src->LockRect(&srcLocked, &srcRect, D3DLOCK_READONLY);
if (FAILED(res))
return res;
res = dst->LockRect(&dstLocked, &dstRect, 0);
if (FAILED(res)) {
src->UnlockRect();
return res;
}
auto rows = srcRect.bottom - srcRect.top;
auto cols = srcRect.right - srcRect.left;
auto bpp = srcLocked.Pitch / srcDesc.Width;
if (!compressed
&& srcRect.left == 0
&& srcRect.right == LONG(srcDesc.Width)
&& srcDesc.Width == dstDesc.Width
&& srcLocked.Pitch == dstLocked.Pitch) {
// If copying the entire texture into a congruent destination,
// we can do this in one continuous copy.
std::memcpy(dstLocked.pBits, srcLocked.pBits, srcLocked.Pitch * rows);
} else {
// Bytes per row of the rect
auto amplitude = cols * bpp;
// Handle DXT compressed textures.
// TODO: Are rects always 4x4 aligned?
if (compressed) {
// Assume that DXT blocks are 4x4 pixels.
constexpr UINT blockWidth = 4;
constexpr UINT blockHeight = 4;
// Compute rect dimensions in 4x4 blocks
UINT rectWidthBlocks = cols / blockWidth;
UINT rectHeightBlocks = rows / blockHeight;
// Compute total texture width in blocks
// to derive block size in bytes using the pitch.
UINT texWidthBlocks = std::max(srcDesc.Width / blockWidth, 1u);
UINT bytesPerBlock = srcLocked.Pitch / texWidthBlocks;
// Copy H/4 rows of W/4 blocks
amplitude = rectWidthBlocks * bytesPerBlock;
rows = rectHeightBlocks;
}
// Copy one row at a time
size_t srcOffset = 0, dstOffset = 0;
for (auto i = 0; i < rows; i++) {
std::memcpy(
(uint8_t*)dstLocked.pBits + dstOffset,
(uint8_t*)srcLocked.pBits + srcOffset,
amplitude);
srcOffset += srcLocked.Pitch;
dstOffset += dstLocked.Pitch;
}
}
res = dst->UnlockRect();
res = src->UnlockRect();
return res;
}
/**
* \brief D3D8 CopyRects implementation
*
* \details
* The following table shows the possible combinations of source
* and destination surface pools, and how we handle each of them.
*
*
* Src/Dst DEFAULT MANAGED SYSTEMMEM SCRATCH
*
* DEFAULT StretchRect GetRenderTargetData GetRenderTargetData -
* MANAGED UpdateTextureFromBuffer memcpy memcpy -
* SYSTEMMEM UpdateSurface memcpy memcpy -
* SCRATCH - - - -
*
*/
HRESULT STDMETHODCALLTYPE D3D8Device::CopyRects(
IDirect3DSurface8* pSourceSurface,
const RECT* pSourceRectsArray,
UINT cRects,
IDirect3DSurface8* pDestinationSurface,
const POINT* pDestPointsArray) {
if (pSourceSurface == NULL || pDestinationSurface == NULL) {
return D3DERR_INVALIDCALL;
}
// TODO: No format conversion, no stretching, no clipping.
// All src/dest rectangles must fit within the dest surface.
Com<D3D8Surface> src = static_cast<D3D8Surface*>(pSourceSurface);
Com<D3D8Surface> dst = static_cast<D3D8Surface*>(pDestinationSurface);
d3d9::D3DSURFACE_DESC srcDesc, dstDesc;
src->GetD3D9()->GetDesc(&srcDesc);
dst->GetD3D9()->GetDesc(&dstDesc);
// This method cannot be applied to surfaces whose formats
// are classified as depth stencil formats.
if (unlikely(isDepthStencilFormat(D3DFORMAT(srcDesc.Format)) ||
isDepthStencilFormat(D3DFORMAT(dstDesc.Format)))) {
return D3DERR_INVALIDCALL;
}
StateChange();
// If pSourceRectsArray is NULL, then the entire surface is copied
RECT rect;
POINT point = { 0, 0 };
if (pSourceRectsArray == NULL) {
cRects = 1;
rect.top = rect.left = 0;
rect.right = srcDesc.Width;
rect.bottom = srcDesc.Height;
pSourceRectsArray = &rect;
pDestPointsArray = &point;
}
HRESULT res = D3DERR_INVALIDCALL;
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;
stretch = (dstRect.right-dstRect.left) != (srcRect.right-srcRect.left)
|| (dstRect.bottom-dstRect.top) != (srcRect.bottom-srcRect.top);
offset = !stretch && asymmetric;
} else {
dstRect = srcRect;
asymmetric = stretch = offset = false;
}
POINT dstPt = { dstRect.left, dstRect.top };
res = D3DERR_INVALIDCALL;
auto unhandled = [&] {
Logger::debug(str::format("CopyRects: Hit unhandled case from src pool ", srcDesc.Pool, " to dst pool ", dstDesc.Pool));
return D3DERR_INVALIDCALL;
};
switch (dstDesc.Pool) {
// Dest: DEFAULT
case D3DPOOL_DEFAULT:
switch (srcDesc.Pool) {
case D3DPOOL_DEFAULT: {
// DEFAULT -> DEFAULT: use StretchRect
res = GetD3D9()->StretchRect(
src->GetD3D9(),
&srcRect,
dst->GetD3D9(),
&dstRect,
d3d9::D3DTEXF_NONE
);
goto done;
}
case D3DPOOL_MANAGED: {
// MANAGED -> DEFAULT: UpdateTextureFromBuffer
res = m_bridge->UpdateTextureFromBuffer(
src->GetD3D9(),
dst->GetD3D9(),
&srcRect,
&dstPt
);
goto done;
}
case D3DPOOL_SYSTEMMEM: {
// SYSTEMMEM -> DEFAULT: use UpdateSurface
res = GetD3D9()->UpdateSurface(
src->GetD3D9(),
&srcRect,
dst->GetD3D9(),
&dstPt
);
goto done;
}
case D3DPOOL_SCRATCH:
default: {
// TODO: Unhandled case.
return unhandled();
}
} break;
// Dest: MANAGED
case D3DPOOL_MANAGED:
switch (srcDesc.Pool) {
case D3DPOOL_DEFAULT: {
// TODO: Copy on GPU (handle MANAGED similarly to SYSTEMMEM for now)
// Get temporary off-screen surface for stretching.
Com<d3d9::IDirect3DSurface9> pBlitImage = dst->GetBlitImage();
// Stretch the source RT to the temporary surface.
res = GetD3D9()->StretchRect(
src->GetD3D9(),
&srcRect,
pBlitImage.ptr(),
&dstRect,
d3d9::D3DTEXF_NONE);
if (FAILED(res)) {
goto done;
}
// Now sync the rendertarget data into main memory.
res = GetD3D9()->GetRenderTargetData(pBlitImage.ptr(), dst->GetD3D9());
goto done;
}
case D3DPOOL_MANAGED:
case D3DPOOL_SYSTEMMEM: {
// SYSTEMMEM -> MANAGED: LockRect / memcpy
if (stretch) {
res = D3DERR_INVALIDCALL;
goto done;
}
res = copyTextureBuffers(src.ptr(), dst.ptr(), srcDesc, dstDesc, srcRect, dstRect);
goto done;
}
case D3DPOOL_SCRATCH:
default: {
// TODO: Unhandled case.
return unhandled();
}
} break;
// DEST: SYSTEMMEM
case D3DPOOL_SYSTEMMEM: {
// RT (DEFAULT) -> SYSTEMMEM: Use GetRenderTargetData as fast path if possible
if ((srcDesc.Usage & D3DUSAGE_RENDERTARGET || m_renderTarget.ptr() == 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) {
res = GetD3D9()->GetRenderTargetData(src->GetD3D9(), dst->GetD3D9());
goto done;
}
}
switch (srcDesc.Pool) {
case D3DPOOL_DEFAULT: {
// Get temporary off-screen surface for stretching.
Com<d3d9::IDirect3DSurface9> pBlitImage = dst->GetBlitImage();
// Stretch the source RT to the temporary surface.
res = GetD3D9()->StretchRect(
src->GetD3D9(),
&srcRect,
pBlitImage.ptr(),
&dstRect,
d3d9::D3DTEXF_NONE);
if (FAILED(res)) {
goto done;
}
// Now sync the rendertarget data into main memory.
res = GetD3D9()->GetRenderTargetData(pBlitImage.ptr(), dst->GetD3D9());
goto done;
}
// SYSMEM/MANAGED -> SYSMEM: LockRect / memcpy
case D3DPOOL_MANAGED:
case D3DPOOL_SYSTEMMEM: {
if (stretch) {
res = D3DERR_INVALIDCALL;
goto done;
}
res = copyTextureBuffers(src.ptr(), dst.ptr(), srcDesc, dstDesc, srcRect, dstRect);
goto done;
}
case D3DPOOL_SCRATCH:
default: {
// TODO: Unhandled case.
return unhandled();
}
} break;
}
// DEST: SCRATCH
case D3DPOOL_SCRATCH:
default: {
// TODO: Unhandled case.
return unhandled();
}
}
done:
if (FAILED(res)) {
Logger::debug(str::format("CopyRects: FAILED to copy from src pool ", srcDesc.Pool, " to dst pool ", dstDesc.Pool));
return res;
}
}
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::UpdateTexture(
IDirect3DBaseTexture8* pSourceTexture,
IDirect3DBaseTexture8* pDestinationTexture) {
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) {
HRESULT res;
if (pRenderTarget != NULL) {
D3D8Surface* surf = static_cast<D3D8Surface*>(pRenderTarget);
if(likely(m_renderTarget.ptr() != surf)) {
StateChange();
res = GetD3D9()->SetRenderTarget(0, surf->GetD3D9());
if (FAILED(res)) return res;
if (likely(m_renderTarget != surf))
m_renderTarget = surf;
}
}
// SetDepthStencilSurface is a separate call
D3D8Surface* zStencil = static_cast<D3D8Surface*>(pNewZStencil);
if(likely(m_depthStencil.ptr() != zStencil)) {
StateChange();
res = GetD3D9()->SetDepthStencilSurface(D3D8Surface::GetD3D9Nullable(zStencil));
if (FAILED(res)) return res;
if (likely(m_depthStencil != zStencil))
m_depthStencil = zStencil;
}
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetRenderTarget(IDirect3DSurface8** ppRenderTarget) {
InitReturnPtr(ppRenderTarget);
if (unlikely(m_renderTarget == nullptr)) {
Com<d3d9::IDirect3DSurface9> pRT9 = nullptr;
HRESULT res = GetD3D9()->GetRenderTarget(0, &pRT9); // use RT index 0
if(FAILED(res)) return res;
m_renderTarget = new D3D8Surface(this, std::move(pRT9));
*ppRenderTarget = m_renderTarget.ref();
return res;
}
*ppRenderTarget = m_renderTarget.ref();
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetDepthStencilSurface(IDirect3DSurface8** ppZStencilSurface) {
InitReturnPtr(ppZStencilSurface);
if (unlikely(m_depthStencil == nullptr)) {
Com<d3d9::IDirect3DSurface9> pStencil9 = nullptr;
HRESULT res = GetD3D9()->GetDepthStencilSurface(&pStencil9);
if(FAILED(res)) return res;
m_depthStencil = new D3D8Surface(this, std::move(pStencil9));
*ppZStencilSurface = m_depthStencil.ref();
return res;
}
*ppZStencilSurface = m_depthStencil.ref();
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::BeginScene() { return GetD3D9()->BeginScene(); }
HRESULT STDMETHODCALLTYPE D3D8Device::EndScene() { StateChange(); return GetD3D9()->EndScene(); }
HRESULT STDMETHODCALLTYPE D3D8Device::Clear(
DWORD Count,
const D3DRECT* pRects,
DWORD Flags,
D3DCOLOR Color,
float Z,
DWORD Stencil) {
StateChange();
return GetD3D9()->Clear(Count, pRects, Flags, Color, Z, Stencil);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX* pMatrix) {
StateChange();
return GetD3D9()->SetTransform(d3d9::D3DTRANSFORMSTATETYPE(State), pMatrix);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX* pMatrix) {
return GetD3D9()->GetTransform(d3d9::D3DTRANSFORMSTATETYPE(State), pMatrix);
}
HRESULT STDMETHODCALLTYPE D3D8Device::MultiplyTransform(D3DTRANSFORMSTATETYPE TransformState, const D3DMATRIX* pMatrix) {
StateChange();
return GetD3D9()->MultiplyTransform(d3d9::D3DTRANSFORMSTATETYPE(TransformState), pMatrix);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetViewport(const D3DVIEWPORT8* pViewport) {
StateChange();
return GetD3D9()->SetViewport(reinterpret_cast<const d3d9::D3DVIEWPORT9*>(pViewport));
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetViewport(D3DVIEWPORT8* pViewport) {
return GetD3D9()->GetViewport(reinterpret_cast<d3d9::D3DVIEWPORT9*>(pViewport));
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetMaterial(const D3DMATERIAL8* pMaterial) {
StateChange();
return GetD3D9()->SetMaterial((const d3d9::D3DMATERIAL9*)pMaterial);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetMaterial(D3DMATERIAL8* pMaterial) {
return GetD3D9()->GetMaterial((d3d9::D3DMATERIAL9*)pMaterial);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetLight(DWORD Index, const D3DLIGHT8* pLight) {
StateChange();
return GetD3D9()->SetLight(Index, (const d3d9::D3DLIGHT9*)pLight);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetLight(DWORD Index, D3DLIGHT8* pLight) {
return GetD3D9()->GetLight(Index, (d3d9::D3DLIGHT9*)pLight);
}
HRESULT STDMETHODCALLTYPE D3D8Device::LightEnable(DWORD Index, BOOL Enable) {
StateChange();
return GetD3D9()->LightEnable(Index, Enable);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetLightEnable(DWORD Index, BOOL* pEnable) {
return GetD3D9()->GetLightEnable(Index, pEnable);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetClipPlane(DWORD Index, const float* pPlane) {
StateChange();
return GetD3D9()->SetClipPlane(Index, pPlane);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetClipPlane(DWORD Index, float* pPlane) {
return GetD3D9()->GetClipPlane(Index, pPlane);
}
HRESULT STDMETHODCALLTYPE D3D8Device::CreateStateBlock(
D3DSTATEBLOCKTYPE Type,
DWORD* pToken) {
Com<d3d9::IDirect3DStateBlock9> pStateBlock9;
HRESULT res = GetD3D9()->CreateStateBlock(d3d9::D3DSTATEBLOCKTYPE(Type), &pStateBlock9);
D3D8StateBlock* pStateBlock = new D3D8StateBlock(this, Type, pStateBlock9.ref());
m_stateBlocks.insert(pStateBlock);
*pToken = DWORD(reinterpret_cast<uintptr_t>(pStateBlock));
return res;
}
HRESULT STDMETHODCALLTYPE D3D8Device::CaptureStateBlock(DWORD Token) {
return reinterpret_cast<D3D8StateBlock*>(Token)->Capture();
}
HRESULT STDMETHODCALLTYPE D3D8Device::ApplyStateBlock(DWORD Token) {
StateChange();
return reinterpret_cast<D3D8StateBlock*>(Token)->Apply();
}
HRESULT STDMETHODCALLTYPE D3D8Device::DeleteStateBlock(DWORD Token) {
// "Applications cannot delete a device-state block while another is being recorded"
if (unlikely(ShouldRecord()))
return D3DERR_INVALIDCALL;
D3D8StateBlock* block = reinterpret_cast<D3D8StateBlock*>(Token);
m_stateBlocks.erase(block);
delete block;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::BeginStateBlock() {
if (unlikely(m_recorder != nullptr))
return D3DERR_INVALIDCALL;
m_recorder = new D3D8StateBlock(this);
m_stateBlocks.insert(m_recorder);
return GetD3D9()->BeginStateBlock();
}
HRESULT STDMETHODCALLTYPE D3D8Device::EndStateBlock(DWORD* pToken) {
if (unlikely(pToken == nullptr || m_recorder == nullptr))
return D3DERR_INVALIDCALL;
Com<d3d9::IDirect3DStateBlock9> pStateBlock;
HRESULT res = GetD3D9()->EndStateBlock(&pStateBlock);
m_recorder->SetD3D9(std::move(pStateBlock));
*pToken = DWORD(reinterpret_cast<uintptr_t>(m_recorder));
m_recorder = nullptr;
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) {
InitReturnPtr(ppTexture);
*ppTexture = m_textures[Stage].ref();
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetTexture(DWORD Stage, IDirect3DBaseTexture8* pTexture) {
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);
if(unlikely(m_textures[Stage].ptr() == tex))
return D3D_OK;
StateChange();
m_textures[Stage] = tex;
return GetD3D9()->SetTexture(Stage, D3D8Texture2D::GetD3D9Nullable(tex));
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetTextureStageState(
DWORD Stage,
D3DTEXTURESTAGESTATETYPE Type,
DWORD* pValue) {
d3d9::D3DSAMPLERSTATETYPE stateType = GetSamplerStateType9(Type);
if (stateType != -1) {
// 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);
StateChange();
if (stateType != -1) {
// 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) {
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) {
return GetD3D9()->DrawIndexedPrimitive(
d3d9::D3DPRIMITIVETYPE(PrimitiveType),
m_baseVertexIndex, // set by SetIndices
MinVertexIndex,
NumVertices,
StartIndex,
PrimitiveCount);
}
HRESULT STDMETHODCALLTYPE D3D8Device::DrawPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT PrimitiveCount,
const void* pVertexStreamZeroData,
UINT VertexStreamZeroStride) {
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) {
StateChange();
// Stream 0 and the index buffer are set to null by this call
m_streams[0] = D3D8VBO {nullptr, 0};
m_indices = nullptr;
m_baseVertexIndex = 0;
return GetD3D9()->DrawIndexedPrimitiveUP(
d3d9::D3DPRIMITIVETYPE(PrimitiveType),
MinVertexIndex,
NumVertices,
PrimitiveCount,
pIndexData,
d3d9::D3DFORMAT(IndexDataFormat),
pVertexStreamZeroData,
VertexStreamZeroStride);
}
HRESULT STDMETHODCALLTYPE D3D8Device::ProcessVertices(
UINT SrcStartIndex,
UINT DestIndex,
UINT VertexCount,
IDirect3DVertexBuffer8* pDestBuffer,
DWORD Flags) {
if (unlikely(!pDestBuffer))
return D3DERR_INVALIDCALL;
D3D8VertexBuffer* buffer = static_cast<D3D8VertexBuffer*>(pDestBuffer);
return GetD3D9()->ProcessVertices(
SrcStartIndex,
DestIndex,
VertexCount,
buffer->GetD3D9(),
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) {
return GetD3D9()->GetVertexShaderConstantF(Register, (float*)pConstantData, ConstantCount);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetStreamSource(
UINT StreamNumber,
IDirect3DVertexBuffer8* pStreamData,
UINT Stride) {
if (unlikely(StreamNumber >= d8caps::MAX_STREAMS))
return D3DERR_INVALIDCALL;
D3D8VertexBuffer* buffer = static_cast<D3D8VertexBuffer*>(pStreamData);
if (ShouldBatch())
m_batcher->SetStream(StreamNumber, buffer, Stride);
m_streams[StreamNumber] = D3D8VBO {buffer, Stride};
// DXVK: Never fails
return GetD3D9()->SetStreamSource(StreamNumber, D3D8VertexBuffer::GetD3D9Nullable(buffer), 0, Stride);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetStreamSource(
UINT StreamNumber,
IDirect3DVertexBuffer8** ppStreamData,
UINT* pStride) {
InitReturnPtr(ppStreamData);
if (likely(pStride != nullptr))
*pStride = 0;
if (unlikely(ppStreamData == nullptr || pStride == nullptr))
return D3DERR_INVALIDCALL;
if (unlikely(StreamNumber >= d8caps::MAX_STREAMS))
return D3DERR_INVALIDCALL;
const D3D8VBO& vbo = m_streams[StreamNumber];
*ppStreamData = vbo.buffer.ref();
*pStride = vbo.stride;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetIndices(IDirect3DIndexBuffer8* pIndexData, UINT BaseVertexIndex) {
if (unlikely(ShouldRecord()))
return m_recorder->SetIndices(pIndexData, BaseVertexIndex);
// used by DrawIndexedPrimitive
m_baseVertexIndex = static_cast<INT>(BaseVertexIndex);
D3D8IndexBuffer* buffer = static_cast<D3D8IndexBuffer*>(pIndexData);
if (ShouldBatch())
m_batcher->SetIndices(buffer, m_baseVertexIndex);
m_indices = buffer;
// DXVK: Never fails
return GetD3D9()->SetIndices(D3D8IndexBuffer::GetD3D9Nullable(buffer));
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetIndices(
IDirect3DIndexBuffer8** ppIndexData,
UINT* pBaseVertexIndex) {
InitReturnPtr(ppIndexData);
*ppIndexData = m_indices.ref();
*pBaseVertexIndex = m_baseVertexIndex;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetPixelShaderConstant(DWORD Register, void* pConstantData, DWORD ConstantCount) {
return GetD3D9()->GetPixelShaderConstantF(Register, (float*)pConstantData, ConstantCount);
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetPixelShaderConstant(
DWORD StartRegister,
const void* pConstantData,
DWORD ConstantCount) {
StateChange();
// ConstantCount is actually the same as Vector4fCount
return GetD3D9()->SetPixelShaderConstantF(StartRegister, reinterpret_cast<const float*>(pConstantData), ConstantCount);
}
HRESULT STDMETHODCALLTYPE D3D8Device::DrawRectPatch(
UINT Handle,
const float* pNumSegs,
const D3DRECTPATCH_INFO* pRectPatchInfo) {
return GetD3D9()->DrawRectPatch(Handle, pNumSegs, reinterpret_cast<const d3d9::D3DRECTPATCH_INFO*>(pRectPatchInfo));
}
HRESULT STDMETHODCALLTYPE D3D8Device::DrawTriPatch(
UINT Handle,
const float* pNumSegs,
const D3DTRIPATCH_INFO* pTriPatchInfo) {
return GetD3D9()->DrawTriPatch(Handle, pNumSegs, reinterpret_cast<const d3d9::D3DTRIPATCH_INFO*>(pTriPatchInfo));
}
HRESULT STDMETHODCALLTYPE D3D8Device::DeletePatch(UINT Handle) {
return GetD3D9()->DeletePatch(Handle);
}
// Render States //
// ZBIAS can be an integer from 0 to 1 and needs to be remapped to float
static constexpr float ZBIAS_SCALE = -0.000005f;
static constexpr float ZBIAS_SCALE_INV = 1 / ZBIAS_SCALE;
HRESULT STDMETHODCALLTYPE D3D8Device::SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) {
d3d9::D3DRENDERSTATETYPE State9 = (d3d9::D3DRENDERSTATETYPE)State;
bool stateChange = true;
switch (State) {
// Most render states translate 1:1 to D3D9
default:
break;
// TODO: D3DRS_LINEPATTERN - vkCmdSetLineRasterizationModeEXT
case D3DRS_LINEPATTERN: {
[[maybe_unused]]
D3DLINEPATTERN pattern = bit::cast<D3DLINEPATTERN>(Value);
stateChange = false;
} break;
// Not supported by D3D8.
case D3DRS_ZVISIBLE:
stateChange = false;
break;
// TODO: Not implemented by D9VK. Try anyway.
case D3DRS_EDGEANTIALIAS:
State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE;
break;
case D3DRS_ZBIAS:
State9 = d3d9::D3DRS_DEPTHBIAS;
Value = bit::cast<DWORD>(float(Value) * ZBIAS_SCALE);
break;
case D3DRS_SOFTWAREVERTEXPROCESSING:
// D3D9 can return D3DERR_INVALIDCALL, but we don't care.
if (!(m_behaviorFlags & D3DCREATE_MIXED_VERTEXPROCESSING))
return D3D_OK;
// This was a very easy footgun for D3D8 applications.
if (unlikely(ShouldRecord()))
return m_recorder->SetSoftwareVertexProcessing(Value);
return GetD3D9()->SetSoftwareVertexProcessing(Value);
// TODO: D3DRS_PATCHSEGMENTS
case D3DRS_PATCHSEGMENTS:
stateChange = false;
break;
}
if (stateChange) {
DWORD value;
GetRenderState(State, &value);
if (value != Value)
StateChange();
}
return GetD3D9()->SetRenderState(State9, Value);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetRenderState(D3DRENDERSTATETYPE State, DWORD* pValue) {
d3d9::D3DRENDERSTATETYPE State9 = (d3d9::D3DRENDERSTATETYPE)State;
switch (State) {
// Most render states translate 1:1 to D3D9
default:
break;
// TODO: D3DRS_LINEPATTERN
case D3DRS_LINEPATTERN:
break;
// Not supported by D3D8.
case D3DRS_ZVISIBLE:
break;
case D3DRS_EDGEANTIALIAS:
State9 = d3d9::D3DRS_ANTIALIASEDLINEENABLE;
break;
case D3DRS_ZBIAS: {
float bias = 0;
HRESULT res = GetD3D9()->GetRenderState(d3d9::D3DRS_DEPTHBIAS, (DWORD*)&bias);
*pValue = bit::cast<DWORD>(bias * ZBIAS_SCALE_INV);
return res;
} break;
case D3DRS_SOFTWAREVERTEXPROCESSING:
return GetD3D9()->GetSoftwareVertexProcessing();
// TODO: D3DRS_PATCHSEGMENTS
case D3DRS_PATCHSEGMENTS:
break;
}
return GetD3D9()->GetRenderState(State9, pValue);
}
// Vertex Shaders //
HRESULT STDMETHODCALLTYPE D3D8Device::CreateVertexShader(
const DWORD* pDeclaration,
const DWORD* pFunction,
DWORD* pHandle,
DWORD Usage ) {
D3D8VertexShaderInfo& info = m_vertexShaders.emplace_back();
// Store D3D8 bytecodes in the shader info
if (pDeclaration != nullptr)
for (UINT i = 0; pDeclaration[i+1] != D3DVSD_END(); i++)
info.declaration.push_back(pDeclaration[i]);
if (pFunction != nullptr)
for (UINT i = 0; pFunction[i+1] != D3DVS_END(); i++)
info.function.push_back(pFunction[i]);
D3D9VertexShaderCode result = TranslateVertexShader8(pDeclaration, pFunction, m_d3d8Options);
// Create vertex declaration
HRESULT res = GetD3D9()->CreateVertexDeclaration(result.declaration, &(info.pVertexDecl));
if (FAILED(res))
return res;
if (pFunction != nullptr) {
res = GetD3D9()->CreateVertexShader(result.function.data(), &(info.pVertexShader));
} else {
// pFunction is NULL: fixed function pipeline
info.pVertexShader = nullptr;
}
// Set bit to indicate this is not an FVF
*pHandle = getShaderHandle(m_vertexShaders.size() - 1);
return res;
}
inline D3D8VertexShaderInfo* getVertexShaderInfo(D3D8Device* device, DWORD Handle) {
Handle = getShaderIndex(Handle);
if (unlikely(Handle >= device->m_vertexShaders.size())) {
Logger::debug(str::format("getVertexShaderInfo: Invalid vertex shader index ", std::hex, Handle));
return nullptr;
}
D3D8VertexShaderInfo& info = device->m_vertexShaders[Handle];
if (unlikely(!info.pVertexDecl && !info.pVertexShader)) {
Logger::debug(str::format("getVertexShaderInfo: Application provided deleted vertex shader ", std::hex, Handle));
return nullptr;
}
return &info;
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetVertexShader( DWORD Handle ) {
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();
// Cache current shader
m_currentVertexShader = Handle;
GetD3D9()->SetVertexDeclaration(info->pVertexDecl);
return GetD3D9()->SetVertexShader(info->pVertexShader);
} else if (m_currentVertexShader != Handle) {
StateChange();
// Cache current FVF
m_currentVertexShader = Handle;
//GetD3D9()->SetVertexDeclaration(nullptr);
GetD3D9()->SetVertexShader(nullptr);
return GetD3D9()->SetFVF( Handle );
}
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShader(DWORD* pHandle) {
// Return cached shader
*pHandle = m_currentVertexShader;
return D3D_OK;
/*
// Slow path. Use to debug cached shader validation. //
d3d9::IDirect3DVertexShader9* pVertexShader;
HRESULT res = GetD3D9()->GetVertexShader(&pVertexShader);
if (FAILED(res) || pVertexShader == nullptr) {
return GetD3D9()->GetFVF(pHandle);
}
for (unsigned int i = 0; i < m_vertexShaders.size(); i++) {
D3D8VertexShaderInfo& info = m_vertexShaders[i];
if (info.pVertexShader == pVertexShader) {
*pHandle = getShaderHandle(DWORD(i));
return res;
}
}
return res;
*/
}
HRESULT STDMETHODCALLTYPE D3D8Device::DeleteVertexShader(DWORD Handle) {
if (!isFVF(Handle)) {
D3D8VertexShaderInfo* info = getVertexShaderInfo(this, Handle);
if (!info)
return D3DERR_INVALIDCALL;
if (info->pVertexDecl)
info->pVertexDecl->Release();
if (info->pVertexShader)
info->pVertexShader->Release();
info->declaration.clear();
info->function.clear();
}
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShaderDeclaration(DWORD Handle, void* pData, DWORD* pSizeOfData) {
D3D8VertexShaderInfo* pInfo = getVertexShaderInfo(this, Handle);
if (unlikely(!pInfo))
return D3DERR_INVALIDCALL;
UINT SizeOfData = *pSizeOfData;
// Get actual size
UINT ActualSize = pInfo->declaration.size() * sizeof(DWORD);
if (pData == nullptr) {
*pSizeOfData = ActualSize;
return D3D_OK;
}
// D3D8-specific behavior
if (SizeOfData < ActualSize) {
*pSizeOfData = ActualSize;
return D3DERR_MOREDATA;
}
memcpy(pData, pInfo->declaration.data(), ActualSize);
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetVertexShaderFunction(DWORD Handle, void* pData, DWORD* pSizeOfData) {
D3D8VertexShaderInfo* pInfo = getVertexShaderInfo(this, Handle);
if (unlikely(!pInfo))
return D3DERR_INVALIDCALL;
UINT SizeOfData = *pSizeOfData;
// Get actual size
UINT ActualSize = pInfo->function.size() * sizeof(DWORD);
if (pData == nullptr) {
*pSizeOfData = ActualSize;
return D3D_OK;
}
// D3D8-specific behavior
if (SizeOfData < ActualSize) {
*pSizeOfData = ActualSize;
return D3DERR_MOREDATA;
}
memcpy(pData, pInfo->function.data(), ActualSize);
return D3D_OK;
}
// Pixel Shaders //
HRESULT STDMETHODCALLTYPE D3D8Device::CreatePixelShader(
const DWORD* pFunction,
DWORD* pHandle) {
d3d9::IDirect3DPixelShader9* pPixelShader;
HRESULT res = GetD3D9()->CreatePixelShader(pFunction, &pPixelShader);
m_pixelShaders.push_back(pPixelShader);
// Still set the shader bit, to prevent conflicts with NULL.
*pHandle = getShaderHandle(m_pixelShaders.size() - 1);
return res;
}
inline d3d9::IDirect3DPixelShader9* getPixelShaderPtr(D3D8Device* device, DWORD Handle) {
Handle = getShaderIndex(Handle);
if (unlikely(Handle >= device->m_pixelShaders.size())) {
Logger::debug(str::format("getPixelShaderPtr: Invalid pixel shader index ", std::hex, Handle));
return nullptr;
}
d3d9::IDirect3DPixelShader9* pPixelShader = device->m_pixelShaders[Handle];
if (unlikely(pPixelShader == nullptr)) {
Logger::debug(str::format("getPixelShaderPtr: Application provided deleted pixel shader ", std::hex, Handle));
return nullptr;
}
return pPixelShader;
}
HRESULT STDMETHODCALLTYPE D3D8Device::SetPixelShader(DWORD Handle) {
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();
// Cache current pixel shader
m_currentPixelShader = Handle;
return GetD3D9()->SetPixelShader(pPixelShader);
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetPixelShader(DWORD* pHandle) {
// Return cached shader
*pHandle = m_currentPixelShader;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::DeletePixelShader(DWORD Handle) {
d3d9::IDirect3DPixelShader9* pPixelShader = getPixelShaderPtr(this, Handle);
if (unlikely(!pPixelShader)) {
return D3DERR_INVALIDCALL;
}
pPixelShader->Release();
m_pixelShaders[getShaderIndex(Handle)] = nullptr;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D8Device::GetPixelShaderFunction(DWORD Handle, void* pData, DWORD* pSizeOfData) {
d3d9::IDirect3DPixelShader9* pPixelShader = getPixelShaderPtr(this, Handle);
if (unlikely(!pPixelShader))
return D3DERR_INVALIDCALL;
UINT SizeOfData = *pSizeOfData;
// Get actual size
UINT ActualSize = 0;
pPixelShader->GetFunction(nullptr, &ActualSize);
if (pData == nullptr) {
*pSizeOfData = ActualSize;
return D3D_OK;
}
// D3D8-specific behavior
if (SizeOfData < ActualSize) {
*pSizeOfData = ActualSize;
return D3DERR_MOREDATA;
}
return pPixelShader->GetFunction(pData, &SizeOfData);
}
}

454
src/d3d8/d3d8_device.h Normal file
View File

@ -0,0 +1,454 @@
#pragma once
#include "d3d8_include.h"
#include "d3d8_texture.h"
#include "d3d8_buffer.h"
#include "d3d8_swapchain.h"
#include "d3d8_state_block.h"
#include "d3d8_d3d9_util.h"
#include "d3d8_caps.h"
#include "d3d8_batch.h"
#include "../d3d9/d3d9_bridge.h"
#include <array>
#include <vector>
#include <type_traits>
#include <unordered_set>
namespace dxvk {
class D3D8Interface;
struct D3D8VertexShaderInfo;
using D3D8DeviceBase = D3D8WrappedObject<d3d9::IDirect3DDevice9, IDirect3DDevice8>;
class D3D8Device final : public D3D8DeviceBase {
friend class D3D8StateBlock;
public:
D3D8Device(
D3D8Interface* pParent,
Com<d3d9::IDirect3DDevice9>&& pDevice,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS* pParams);
~D3D8Device();
HRESULT STDMETHODCALLTYPE TestCooperativeLevel();
UINT STDMETHODCALLTYPE GetAvailableTextureMem();
HRESULT STDMETHODCALLTYPE ResourceManagerDiscardBytes(DWORD bytes);
HRESULT STDMETHODCALLTYPE GetDirect3D(IDirect3D8** ppD3D8);
HRESULT STDMETHODCALLTYPE GetDeviceCaps(D3DCAPS8* pCaps);
HRESULT STDMETHODCALLTYPE GetDisplayMode(D3DDISPLAYMODE* pMode);
HRESULT STDMETHODCALLTYPE GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS* pParameters);
HRESULT STDMETHODCALLTYPE SetCursorProperties(
UINT XHotSpot,
UINT YHotSpot,
IDirect3DSurface8* pCursorBitmap);
void STDMETHODCALLTYPE SetCursorPosition(UINT XScreenSpace, UINT YScreenSpace, DWORD Flags);
// Microsoft d3d8.h in the DirectX 9 SDK uses a different function signature...
void STDMETHODCALLTYPE SetCursorPosition(int X, int Y, DWORD Flags);
BOOL STDMETHODCALLTYPE ShowCursor(BOOL bShow);
HRESULT STDMETHODCALLTYPE CreateAdditionalSwapChain(
D3DPRESENT_PARAMETERS* pPresentationParameters,
IDirect3DSwapChain8** ppSwapChain);
HRESULT STDMETHODCALLTYPE Reset(D3DPRESENT_PARAMETERS* pPresentationParameters);
HRESULT STDMETHODCALLTYPE Present(
const RECT* pSourceRect,
const RECT* pDestRect,
HWND hDestWindowOverride,
const RGNDATA* pDirtyRegion);
HRESULT STDMETHODCALLTYPE GetBackBuffer(
UINT iBackBuffer,
D3DBACKBUFFER_TYPE Type,
IDirect3DSurface8** ppBackBuffer);
HRESULT STDMETHODCALLTYPE GetRasterStatus(D3DRASTER_STATUS* pRasterStatus);
void STDMETHODCALLTYPE SetGammaRamp(DWORD Flags, const D3DGAMMARAMP* pRamp);
void STDMETHODCALLTYPE GetGammaRamp(D3DGAMMARAMP* pRamp);
HRESULT STDMETHODCALLTYPE CreateTexture(
UINT Width,
UINT Height,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DTexture8** ppTexture);
HRESULT STDMETHODCALLTYPE CreateVolumeTexture(
UINT Width,
UINT Height,
UINT Depth,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DVolumeTexture8** ppVolumeTexture);
HRESULT STDMETHODCALLTYPE CreateCubeTexture(
UINT EdgeLength,
UINT Levels,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DCubeTexture8** ppCubeTexture);
HRESULT STDMETHODCALLTYPE CreateVertexBuffer(
UINT Length,
DWORD Usage,
DWORD FVF,
D3DPOOL Pool,
IDirect3DVertexBuffer8** ppVertexBuffer);
HRESULT STDMETHODCALLTYPE CreateIndexBuffer(
UINT Length,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DIndexBuffer8** ppIndexBuffer);
HRESULT STDMETHODCALLTYPE CreateRenderTarget(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DMULTISAMPLE_TYPE MultiSample,
BOOL Lockable,
IDirect3DSurface8** ppSurface);
HRESULT STDMETHODCALLTYPE CreateDepthStencilSurface(
UINT Width,
UINT Height,
D3DFORMAT Format,
D3DMULTISAMPLE_TYPE MultiSample,
IDirect3DSurface8** ppSurface);
HRESULT STDMETHODCALLTYPE CreateImageSurface(UINT Width, UINT Height, D3DFORMAT Format, IDirect3DSurface8** ppSurface);
HRESULT STDMETHODCALLTYPE CopyRects(
IDirect3DSurface8* pSourceSurface,
const RECT* pSourceRectsArray,
UINT cRects,
IDirect3DSurface8* pDestinationSurface,
const POINT* pDestPointsArray);
HRESULT STDMETHODCALLTYPE UpdateTexture(
IDirect3DBaseTexture8* pSourceTexture,
IDirect3DBaseTexture8* pDestinationTexture);
HRESULT STDMETHODCALLTYPE GetFrontBuffer(IDirect3DSurface8* pDestSurface);
HRESULT STDMETHODCALLTYPE SetRenderTarget(IDirect3DSurface8* pRenderTarget, IDirect3DSurface8* pNewZStencil);
HRESULT STDMETHODCALLTYPE GetRenderTarget(IDirect3DSurface8** ppRenderTarget);
HRESULT STDMETHODCALLTYPE GetDepthStencilSurface(IDirect3DSurface8** ppZStencilSurface);
HRESULT STDMETHODCALLTYPE BeginScene();
HRESULT STDMETHODCALLTYPE EndScene();
HRESULT STDMETHODCALLTYPE Clear(
DWORD Count,
const D3DRECT* pRects,
DWORD Flags,
D3DCOLOR Color,
float Z,
DWORD Stencil);
HRESULT STDMETHODCALLTYPE SetTransform(D3DTRANSFORMSTATETYPE State, const D3DMATRIX* pMatrix);
HRESULT STDMETHODCALLTYPE GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX* pMatrix);
HRESULT STDMETHODCALLTYPE MultiplyTransform(D3DTRANSFORMSTATETYPE TransformState, const D3DMATRIX* pMatrix);
HRESULT STDMETHODCALLTYPE SetViewport(const D3DVIEWPORT8* pViewport);
HRESULT STDMETHODCALLTYPE GetViewport(D3DVIEWPORT8* pViewport);
HRESULT STDMETHODCALLTYPE SetMaterial(const D3DMATERIAL8* pMaterial);
HRESULT STDMETHODCALLTYPE GetMaterial(D3DMATERIAL8* pMaterial);
HRESULT STDMETHODCALLTYPE SetLight(DWORD Index, const D3DLIGHT8* pLight);
HRESULT STDMETHODCALLTYPE GetLight(DWORD Index, D3DLIGHT8* pLight);
HRESULT STDMETHODCALLTYPE LightEnable(DWORD Index, BOOL Enable);
HRESULT STDMETHODCALLTYPE GetLightEnable(DWORD Index, BOOL* pEnable);
HRESULT STDMETHODCALLTYPE SetClipPlane(DWORD Index, const float* pPlane);
HRESULT STDMETHODCALLTYPE GetClipPlane(DWORD Index, float* pPlane);
HRESULT STDMETHODCALLTYPE SetRenderState(D3DRENDERSTATETYPE State, DWORD Value);
HRESULT STDMETHODCALLTYPE GetRenderState(D3DRENDERSTATETYPE State, DWORD* pValue);
HRESULT STDMETHODCALLTYPE CreateStateBlock(
D3DSTATEBLOCKTYPE Type,
DWORD* pToken);
HRESULT STDMETHODCALLTYPE CaptureStateBlock(DWORD Token);
HRESULT STDMETHODCALLTYPE ApplyStateBlock(DWORD Token);
HRESULT STDMETHODCALLTYPE DeleteStateBlock(DWORD Token);
HRESULT STDMETHODCALLTYPE BeginStateBlock();
HRESULT STDMETHODCALLTYPE EndStateBlock(DWORD* pToken);
HRESULT STDMETHODCALLTYPE SetClipStatus(const D3DCLIPSTATUS8* pClipStatus);
HRESULT STDMETHODCALLTYPE GetClipStatus(D3DCLIPSTATUS8* pClipStatus);
HRESULT STDMETHODCALLTYPE GetTexture(DWORD Stage, IDirect3DBaseTexture8** ppTexture);
HRESULT STDMETHODCALLTYPE SetTexture(DWORD Stage, IDirect3DBaseTexture8* pTexture);
HRESULT STDMETHODCALLTYPE GetTextureStageState(
DWORD Stage,
D3DTEXTURESTAGESTATETYPE Type,
DWORD* pValue);
HRESULT STDMETHODCALLTYPE SetTextureStageState(
DWORD Stage,
D3DTEXTURESTAGESTATETYPE Type,
DWORD Value);
HRESULT STDMETHODCALLTYPE ValidateDevice(DWORD* pNumPasses);
HRESULT STDMETHODCALLTYPE GetInfo(DWORD DevInfoID, void* pDevInfoStruct, DWORD DevInfoStructSize);
HRESULT STDMETHODCALLTYPE SetPaletteEntries(UINT PaletteNumber, const PALETTEENTRY* pEntries);
HRESULT STDMETHODCALLTYPE GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY* pEntries);
HRESULT STDMETHODCALLTYPE SetCurrentTexturePalette(UINT PaletteNumber);
HRESULT STDMETHODCALLTYPE GetCurrentTexturePalette(UINT* PaletteNumber);
HRESULT STDMETHODCALLTYPE DrawPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
UINT StartVertex,
UINT PrimitiveCount);
HRESULT STDMETHODCALLTYPE DrawIndexedPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
UINT MinVertexIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount);
HRESULT STDMETHODCALLTYPE DrawPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT PrimitiveCount,
const void* pVertexStreamZeroData,
UINT VertexStreamZeroStride);
HRESULT STDMETHODCALLTYPE DrawIndexedPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT MinVertexIndex,
UINT NumVertices,
UINT PrimitiveCount,
const void* pIndexData,
D3DFORMAT IndexDataFormat,
const void* pVertexStreamZeroData,
UINT VertexStreamZeroStride);
HRESULT STDMETHODCALLTYPE ProcessVertices(
UINT SrcStartIndex,
UINT DestIndex,
UINT VertexCount,
IDirect3DVertexBuffer8* pDestBuffer,
DWORD Flags);
HRESULT STDMETHODCALLTYPE CreateVertexShader(
const DWORD* pDeclaration,
const DWORD* pFunction,
DWORD* pHandle,
DWORD Usage);
HRESULT STDMETHODCALLTYPE SetVertexShader(DWORD Handle);
HRESULT STDMETHODCALLTYPE GetVertexShader(DWORD* pHandle);
HRESULT STDMETHODCALLTYPE DeleteVertexShader(DWORD Handle);
HRESULT STDMETHODCALLTYPE SetVertexShaderConstant(
DWORD StartRegister,
const void* pConstantData,
DWORD ConstantCount);
HRESULT STDMETHODCALLTYPE GetVertexShaderConstant(DWORD Register, void* pConstantData, DWORD ConstantCount);
HRESULT STDMETHODCALLTYPE GetVertexShaderDeclaration(DWORD Handle, void* pData, DWORD* pSizeOfData);
HRESULT STDMETHODCALLTYPE GetVertexShaderFunction(DWORD Handle, void* pData, DWORD* pSizeOfData);
HRESULT STDMETHODCALLTYPE SetStreamSource(
UINT StreamNumber,
IDirect3DVertexBuffer8* pStreamData,
UINT Stride);
HRESULT STDMETHODCALLTYPE GetStreamSource(
UINT StreamNumber,
IDirect3DVertexBuffer8** ppStreamData,
UINT* pStride);
HRESULT STDMETHODCALLTYPE SetIndices(IDirect3DIndexBuffer8* pIndexData, UINT BaseVertexIndex);
HRESULT STDMETHODCALLTYPE GetIndices(
IDirect3DIndexBuffer8** ppIndexData,
UINT* pBaseVertexIndex);
HRESULT STDMETHODCALLTYPE CreatePixelShader(
const DWORD* pFunction,
DWORD* pHandle);
HRESULT STDMETHODCALLTYPE SetPixelShader(DWORD Handle);
HRESULT STDMETHODCALLTYPE GetPixelShader(DWORD* pHandle);
HRESULT STDMETHODCALLTYPE DeletePixelShader(THIS_ DWORD Handle);
HRESULT STDMETHODCALLTYPE GetPixelShaderConstant(DWORD Register, void* pConstantData, DWORD ConstantCount);
HRESULT STDMETHODCALLTYPE SetPixelShaderConstant(
DWORD StartRegister,
const void* pConstantData,
DWORD ConstantCount);
HRESULT STDMETHODCALLTYPE GetPixelShaderFunction(DWORD Handle, void* pData, DWORD* pSizeOfData);
HRESULT STDMETHODCALLTYPE DrawRectPatch(
UINT Handle,
const float* pNumSegs,
const D3DRECTPATCH_INFO* pRectPatchInfo);
HRESULT STDMETHODCALLTYPE DrawTriPatch(
UINT Handle,
const float* pNumSegs,
const D3DTRIPATCH_INFO* pTriPatchInfo);
HRESULT STDMETHODCALLTYPE DeletePatch(UINT Handle);
public: // Internal Methods //
inline bool ShouldRecord() { return m_recorder != nullptr; }
inline bool ShouldBatch() { return m_batcher != nullptr; }
/**
* Marks any state change in the device, so we can signal
* the batcher to emit draw calls. StateChange should be
* called immediately before changing any D3D9 state.
*/
inline void StateChange() {
if (ShouldBatch())
m_batcher->StateChange();
}
inline void ResetState() {
// Mirrors how D3D9 handles the BackBufferCount
m_presentParams.BackBufferCount = std::max(m_presentParams.BackBufferCount, 1u);
// Purge cached objects
// TODO: Some functions may need to be called here (e.g. SetTexture, etc.)
// in case Reset can be recorded by state blocks and other things.
m_textures.fill(nullptr);
m_streams.fill(D3D8VBO());
m_indices = nullptr;
m_renderTarget = nullptr;
m_depthStencil = nullptr;
m_backBuffers.clear();
m_backBuffers.resize(m_presentParams.BackBufferCount);
m_autoDepthStencil = nullptr;
}
inline void RecreateBackBuffersAndAutoDepthStencil() {
for (UINT i = 0; i < m_presentParams.BackBufferCount; i++) {
Com<d3d9::IDirect3DSurface9> pSurface9;
GetD3D9()->GetBackBuffer(0, i, d3d9::D3DBACKBUFFER_TYPE_MONO, &pSurface9);
m_backBuffers[i] = new D3D8Surface(this, std::move(pSurface9));
}
Com<d3d9::IDirect3DSurface9> pStencil9 = nullptr;
GetD3D9()->GetDepthStencilSurface(&pStencil9);
m_autoDepthStencil = new D3D8Surface(this, std::move(pStencil9));
m_renderTarget = m_backBuffers[0];
m_depthStencil = m_autoDepthStencil;
}
friend d3d9::IDirect3DPixelShader9* getPixelShaderPtr(D3D8Device* device, DWORD Handle);
friend D3D8VertexShaderInfo* getVertexShaderInfo(D3D8Device* device, DWORD Handle);
private:
Com<IDxvkD3D8Bridge> m_bridge;
const D3D8Options& m_d3d8Options;
Com<D3D8Interface> m_parent;
D3DPRESENT_PARAMETERS m_presentParams;
D3D8StateBlock* m_recorder = nullptr;
std::unordered_set<D3D8StateBlock*> m_stateBlocks;
D3D8Batcher* m_batcher = nullptr;
struct D3D8VBO {
Com<D3D8VertexBuffer, false> buffer = nullptr;
UINT stride = 0;
};
// Remember to fill() these in the constructor!
std::array<Com<D3D8Texture2D, false>, d8caps::MAX_TEXTURE_STAGES> m_textures;
std::array<D3D8VBO, d8caps::MAX_STREAMS> m_streams;
Com<D3D8IndexBuffer, false> m_indices;
INT m_baseVertexIndex = 0;
// TODO: Which of these should be a private ref
std::vector<Com<D3D8Surface, false>> m_backBuffers;
Com<D3D8Surface, false> m_autoDepthStencil;
Com<D3D8Surface, false> m_renderTarget;
Com<D3D8Surface, false> m_depthStencil;
std::vector<D3D8VertexShaderInfo> m_vertexShaders;
std::vector<d3d9::IDirect3DPixelShader9*> m_pixelShaders;
DWORD m_currentVertexShader = 0; // can be FVF or vs index (marked by D3DFVF_RESERVED0)
DWORD m_currentPixelShader = 0;
D3DDEVTYPE m_deviceType;
HWND m_window;
DWORD m_behaviorFlags;
};
}

View File

@ -0,0 +1,71 @@
#pragma once
// Common methods for device-tied objects.
// - AddRef, Release from IUnknown
// - GetDevice from various classes including IDirect3DResource8
#include "d3d8_include.h"
#include "d3d8_wrapped_object.h"
namespace dxvk {
class D3D8Device;
template <typename D3D9, typename D3D8>
class D3D8DeviceChild : public D3D8WrappedObject<D3D9, D3D8> {
public:
D3D8DeviceChild(D3D8Device* pDevice, Com<D3D9>&& Object)
: D3D8WrappedObject<D3D9, D3D8>(std::move(Object))
, m_parent( pDevice ) { }
ULONG STDMETHODCALLTYPE AddRef() {
uint32_t refCount = this->m_refCount++;
if (unlikely(!refCount)) {
this->AddRefPrivate();
GetDevice()->AddRef();
}
return refCount + 1;
}
ULONG STDMETHODCALLTYPE Release() {
// ignore Release calls on objects with 0 refCount
if(unlikely(!this->m_refCount))
return this->m_refCount;
uint32_t refCount = --this->m_refCount;
if (unlikely(!refCount)) {
auto* pDevice = GetDevice();
this->ReleasePrivate();
pDevice->Release();
}
return refCount;
}
HRESULT STDMETHODCALLTYPE GetDevice(IDirect3DDevice8** ppDevice) {
InitReturnPtr(ppDevice);
if (ppDevice == nullptr)
return D3DERR_INVALIDCALL;
*ppDevice = ref(GetDevice());
return D3D_OK;
}
IDirect3DDevice8* GetDevice() {
return reinterpret_cast<IDirect3DDevice8*>(m_parent);
}
D3D8Device* GetParent() {
return m_parent;
}
protected:
D3D8Device* m_parent;
};
}

220
src/d3d8/d3d8_format.h Normal file
View File

@ -0,0 +1,220 @@
#pragma once
#include "d3d8_include.h"
namespace dxvk {
constexpr bool isDXT(D3DFORMAT fmt) {
return fmt == D3DFMT_DXT1
|| fmt == D3DFMT_DXT2
|| fmt == D3DFMT_DXT3
|| fmt == D3DFMT_DXT4
|| fmt == D3DFMT_DXT5;
}
constexpr bool isDXT(d3d9::D3DFORMAT fmt) {
return isDXT(D3DFORMAT(fmt));
}
constexpr bool isUnsupportedSurfaceFormat(D3DFORMAT fmt) {
// mirror what dxvk doesn't support in terms of d3d9 surface formats
return fmt == D3DFMT_R8G8B8
|| fmt == D3DFMT_R3G3B2
|| fmt == D3DFMT_A8R3G3B2
|| fmt == D3DFMT_A8P8
|| fmt == D3DFMT_P8;
// not included in the d3d8 spec
//|| fmt == D3DFMT_CXV8U8;
}
constexpr bool isSupportedDepthStencilFormat(D3DFORMAT fmt) {
// native d3d8 doesn't support D3DFMT_D32, D3DFMT_D15S1 or D3DFMT_D24X4S4
return fmt == D3DFMT_D16_LOCKABLE
|| fmt == D3DFMT_D16
//|| fmt == D3DFMT_D32
//|| fmt == D3DFMT_D15S1
//|| fmt == D3DFMT_D24X4S4
|| fmt == D3DFMT_D24S8
|| fmt == D3DFMT_D24X8;
}
constexpr bool isDepthStencilFormat(D3DFORMAT fmt) {
return fmt == D3DFMT_D16_LOCKABLE
|| fmt == D3DFMT_D16
|| fmt == D3DFMT_D32
|| fmt == D3DFMT_D15S1
|| fmt == D3DFMT_D24X4S4
|| fmt == D3DFMT_D24S8
|| fmt == D3DFMT_D24X8;
}
// Get bytes per pixel (or 4x4 block for DXT)
constexpr UINT getFormatStride(D3DFORMAT fmt) {
switch (fmt) {
default:
case D3DFMT_UNKNOWN:
return 0;
case D3DFMT_R3G3B2:
case D3DFMT_A8:
case D3DFMT_P8:
case D3DFMT_L8:
case D3DFMT_A4L4:
return 1;
case D3DFMT_R5G6B5:
case D3DFMT_X1R5G5B5:
case D3DFMT_A1R5G5B5:
case D3DFMT_A4R4G4B4:
case D3DFMT_A8R3G3B2:
case D3DFMT_X4R4G4B4:
case D3DFMT_A8P8:
case D3DFMT_A8L8:
case D3DFMT_V8U8:
case D3DFMT_L6V5U5:
case D3DFMT_D16_LOCKABLE:
case D3DFMT_D15S1:
case D3DFMT_D16:
case D3DFMT_UYVY:
case D3DFMT_YUY2:
return 2;
case D3DFMT_R8G8B8:
return 3;
case D3DFMT_A8R8G8B8:
case D3DFMT_X8R8G8B8:
case D3DFMT_A2B10G10R10:
//case D3DFMT_A8B8G8R8:
//case D3DFMT_X8B8G8R8:
case D3DFMT_G16R16:
case D3DFMT_X8L8V8U8:
case D3DFMT_Q8W8V8U8:
case D3DFMT_V16U16:
case D3DFMT_W11V11U10:
case D3DFMT_A2W10V10U10:
case D3DFMT_D32:
case D3DFMT_D24S8:
case D3DFMT_D24X8:
case D3DFMT_D24X4S4:
return 4;
case D3DFMT_DXT1:
return 8;
case D3DFMT_DXT2:
case D3DFMT_DXT3:
case D3DFMT_DXT4:
case D3DFMT_DXT5:
return 16;
}
}
constexpr uint32_t GetVertexCount8(D3DPRIMITIVETYPE type, UINT count) {
switch (type) {
default:
case D3DPT_TRIANGLELIST: return count * 3;
case D3DPT_POINTLIST: return count;
case D3DPT_LINELIST: return count * 2;
case D3DPT_LINESTRIP: return count + 1;
case D3DPT_TRIANGLESTRIP: return count + 2;
case D3DPT_TRIANGLEFAN: return count + 2;
}
}
// Essentially the same logic as D3D9VertexDecl::SetFVF
constexpr UINT GetFVFStride(DWORD FVF) {
uint32_t texCount = 0;
uint32_t betas = 0;
uint8_t betaIdx = 0xFF;
UINT size = 0;
switch (FVF & D3DFVF_POSITION_MASK) {
case D3DFVF_XYZ:
case D3DFVF_XYZB1:
case D3DFVF_XYZB2:
case D3DFVF_XYZB3:
case D3DFVF_XYZB4:
case D3DFVF_XYZB5:
size += sizeof(float) * 3;
if ((FVF & D3DFVF_POSITION_MASK) == D3DFVF_XYZ)
break;
betas = (((FVF & D3DFVF_XYZB5) - D3DFVF_XYZB1) >> 1) + 1;
if (FVF & D3DFVF_LASTBETA_D3DCOLOR)
betaIdx = sizeof(D3DCOLOR);
else if (FVF & D3DFVF_LASTBETA_UBYTE4)
betaIdx = sizeof(BYTE) * 4;
else if ((FVF & D3DFVF_XYZB5) == D3DFVF_XYZB5)
betaIdx = sizeof(float);
if (betaIdx != 0xFF)
betas--;
if (betas > 0) {
if (betas <= 4)
size += sizeof(float) * betas;
}
if (betaIdx != 0xFF) {
size += betaIdx;
}
break;
case D3DFVF_XYZW:
case D3DFVF_XYZRHW:
size += sizeof(float) * 4;
break;
default:
break;
}
if (FVF & D3DFVF_NORMAL) {
size += sizeof(float) * 3;
}
if (FVF & D3DFVF_PSIZE) {
size += sizeof(float);
}
if (FVF & D3DFVF_DIFFUSE) {
size += sizeof(D3DCOLOR);
}
if (FVF & D3DFVF_SPECULAR) {
size += sizeof(D3DCOLOR);
}
texCount = (FVF & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT;
texCount = std::min(texCount, 8u);
for (uint32_t i = 0; i < texCount; i++) {
switch ((FVF >> (16 + i * 2)) & 0x3) {
case D3DFVF_TEXTUREFORMAT1:
size += sizeof(float);
break;
case D3DFVF_TEXTUREFORMAT2:
size += sizeof(float) * 2;
break;
case D3DFVF_TEXTUREFORMAT3:
size += sizeof(float) * 3;
break;
case D3DFVF_TEXTUREFORMAT4:
size += sizeof(float) * 4;
break;
default:
break;
}
}
return size;
}
constexpr UINT getSurfaceSize(D3DFORMAT Format, UINT Width, UINT Height) {
if (isDXT(Format)) {
Width = ((Width + 3) >> 2);
Height = ((Height + 3) >> 2);
}
return Width * Height * getFormatStride(Format);
}
}

200
src/d3d8/d3d8_include.h Normal file
View File

@ -0,0 +1,200 @@
#pragma once
#ifndef _MSC_VER
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0A00
#endif
#include <stdint.h>
#include <d3d8.h>
// Declare __uuidof for D3D8 interfaces
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(IDirect3D8, 0x1DD9E8DA,0x1C77,0x4D40,0xB0,0xCF,0x98,0xFE,0xFD,0xFF,0x95,0x12);
__CRT_UUID_DECL(IDirect3DDevice8, 0x7385E5DF,0x8FE8,0x41D5,0x86,0xB6,0xD7,0xB4,0x85,0x47,0xB6,0xCF);
__CRT_UUID_DECL(IDirect3DResource8, 0x1B36BB7B,0x09B7,0x410A,0xB4,0x45,0x7D,0x14,0x30,0xD7,0xB3,0x3F);
__CRT_UUID_DECL(IDirect3DVertexBuffer8, 0x8AEEEAC7,0x05F9,0x44D4,0xB5,0x91,0x00,0x0B,0x0D,0xF1,0xCB,0x95);
__CRT_UUID_DECL(IDirect3DVolume8, 0xBD7349F5,0x14F1,0x42E4,0x9C,0x79,0x97,0x23,0x80,0xDB,0x40,0xC0);
__CRT_UUID_DECL(IDirect3DSwapChain8, 0x928C088B,0x76B9,0x4C6B,0xA5,0x36,0xA5,0x90,0x85,0x38,0x76,0xCD);
__CRT_UUID_DECL(IDirect3DSurface8, 0xB96EEBCA,0xB326,0x4EA5,0x88,0x2F,0x2F,0xF5,0xBA,0xE0,0x21,0xDD);
__CRT_UUID_DECL(IDirect3DIndexBuffer8, 0x0E689C9A,0x053D,0x44A0,0x9D,0x92,0xDB,0x0E,0x3D,0x75,0x0F,0x86);
__CRT_UUID_DECL(IDirect3DBaseTexture8, 0xB4211CFA,0x51B9,0x4A9F,0xAB,0x78,0xDB,0x99,0xB2,0xBB,0x67,0x8E);
__CRT_UUID_DECL(IDirect3DTexture8, 0xE4CDD575,0x2866,0x4F01,0xB1,0x2E,0x7E,0xEC,0xE1,0xEC,0x93,0x58);
__CRT_UUID_DECL(IDirect3DCubeTexture8, 0x3EE5B968,0x2ACA,0x4C34,0x8B,0xB5,0x7E,0x0C,0x3D,0x19,0xB7,0x50);
__CRT_UUID_DECL(IDirect3DVolumeTexture8, 0x4B8AAAFA,0x140F,0x42BA,0x91,0x31,0x59,0x7E,0xAF,0xAA,0x2E,0xAD);
#elif defined(_MSC_VER)
interface DECLSPEC_UUID("1DD9E8DA-1C77-4D40-B0CF-98FEFDFF9512") IDirect3D8;
interface DECLSPEC_UUID("7385E5DF-8FE8-41D5-86B6-D7B48547B6CF") IDirect3DDevice8;
interface DECLSPEC_UUID("1B36BB7B-09B7-410A-B445-7D1430D7B33F") IDirect3DResource8;
interface DECLSPEC_UUID("8AEEEAC7-05F9-44D4-B591-000B0DF1CB95") IDirect3DVertexBuffer8;
interface DECLSPEC_UUID("BD7349F5-14F1-42E4-9C79-972380DB40C0") IDirect3DVolume8;
interface DECLSPEC_UUID("928C088B-76B9-4C6B-A536-A590853876CD") IDirect3DSwapChain8;
interface DECLSPEC_UUID("B96EEBCA-B326-4EA5-882F-2FF5BAE021DD") IDirect3DSurface8;
interface DECLSPEC_UUID("0E689C9A-053D-44A0-9D92-DB0E3D750F86") IDirect3DIndexBuffer8;
interface DECLSPEC_UUID("B4211CFA-51B9-4A9F-AB78-DB99B2BB678E") IDirect3DBaseTexture8;
interface DECLSPEC_UUID("E4CDD575-2866-4F01-B12E-7EECE1EC9358") IDirect3DTexture8;
interface DECLSPEC_UUID("3EE5B968-2ACA-4C34-8BB5-7E0C3D19B750") IDirect3DCubeTexture8;
interface DECLSPEC_UUID("4B8AAAFA-140F-42BA-9131-597EAFAA2EAD") IDirect3DVolumeTexture8;
#endif
// Undefine D3D8 macros
#undef DIRECT3D_VERSION
#undef D3D_SDK_VERSION
#undef D3DCS_ALL // parentheses added in D3D9
#undef D3DFVF_POSITION_MASK // changed from 0x00E to 0x400E in D3D9
#undef D3DFVF_RESERVED2 // reduced from 4 to 2 in DX9
#undef D3DSP_REGNUM_MASK // changed from 0x00000FFF to 0x000007FF in D3D9
#if defined(__MINGW32__) || defined(__GNUC__)
// Avoid redundant definitions (add D3D*_DEFINED macros here)
#define D3DRECT_DEFINED
#define D3DMATRIX_DEFINED
// Temporarily override __CRT_UUID_DECL to allow usage in d3d9 namespace
#pragma push_macro("__CRT_UUID_DECL")
#ifdef __CRT_UUID_DECL
#undef __CRT_UUID_DECL
#endif
#ifdef __MINGW32__
#define __CRT_UUID_DECL(type,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
} \
extern "C++" template<> struct __mingw_uuidof_s<d3d9::type> { static constexpr IID __uuid_inst = {l,w1,w2, {b1,b2,b3,b4,b5,b6,b7,b8}}; }; \
extern "C++" template<> constexpr const GUID &__mingw_uuidof<d3d9::type>() { return __mingw_uuidof_s<d3d9::type>::__uuid_inst; } \
extern "C++" template<> constexpr const GUID &__mingw_uuidof<d3d9::type*>() { return __mingw_uuidof_s<d3d9::type>::__uuid_inst; } \
namespace d3d9 {
#elif defined(__GNUC__)
#define __CRT_UUID_DECL(type, a, b, c, d, e, f, g, h, i, j, k) \
} \
extern "C++" { template <> constexpr GUID __uuidof_helper<d3d9::type>() { return GUID{a,b,c,{d,e,f,g,h,i,j,k}}; } } \
extern "C++" { template <> constexpr GUID __uuidof_helper<d3d9::type*>() { return __uuidof_helper<d3d9::type>(); } } \
extern "C++" { template <> constexpr GUID __uuidof_helper<const d3d9::type*>() { return __uuidof_helper<d3d9::type>(); } } \
extern "C++" { template <> constexpr GUID __uuidof_helper<d3d9::type&>() { return __uuidof_helper<d3d9::type>(); } } \
extern "C++" { template <> constexpr GUID __uuidof_helper<const d3d9::type&>() { return __uuidof_helper<d3d9::type>(); } } \
namespace d3d9 {
#endif
#endif // defined(__MINGW32__) || defined(__GNUC__)
/**
* \brief Direct3D 9
*
* All D3D9 interfaces are included within
* a namespace, so as not to collide with
* D3D8 interfaces.
*/
namespace d3d9 {
#include <d3d9.h>
}
// Indicates d3d9:: namespace is in-use.
#define DXVK_D3D9_NAMESPACE
#if defined(__MINGW32__) || defined(__GNUC__)
#pragma pop_macro("__CRT_UUID_DECL")
#endif
//for some reason we need to specify __declspec(dllexport) for MinGW
#if defined(__WINE__) || !defined(_WIN32)
#define DLLEXPORT __attribute__((visibility("default")))
#else
#define DLLEXPORT
#endif
#include "../util/com/com_guid.h"
#include "../util/com/com_object.h"
#include "../util/com/com_pointer.h"
#include "../util/log/log.h"
#include "../util/log/log_debug.h"
#include "../util/util_error.h"
#include "../util/util_likely.h"
#include "../util/util_string.h"
// Missed definitions in Wine/MinGW.
#ifndef D3DPRESENT_BACK_BUFFERS_MAX_EX
#define D3DPRESENT_BACK_BUFFERS_MAX_EX 30
#endif
#ifndef D3DSI_OPCODE_MASK
#define D3DSI_OPCODE_MASK 0x0000FFFF
#endif
#ifndef D3DSP_TEXTURETYPE_MASK
#define D3DSP_TEXTURETYPE_MASK 0x78000000
#endif
#ifndef D3DUSAGE_AUTOGENMIPMAP
#define D3DUSAGE_AUTOGENMIPMAP 0x00000400L
#endif
#ifndef D3DSP_DCL_USAGE_MASK
#define D3DSP_DCL_USAGE_MASK 0x0000000f
#endif
#ifndef D3DSP_OPCODESPECIFICCONTROL_MASK
#define D3DSP_OPCODESPECIFICCONTROL_MASK 0x00ff0000
#endif
#ifndef D3DSP_OPCODESPECIFICCONTROL_SHIFT
#define D3DSP_OPCODESPECIFICCONTROL_SHIFT 16
#endif
#ifndef D3DCURSOR_IMMEDIATE_UPDATE
#define D3DCURSOR_IMMEDIATE_UPDATE 0x00000001L
#endif
#ifndef D3DPRESENT_FORCEIMMEDIATE
#define D3DPRESENT_FORCEIMMEDIATE 0x00000100L
#endif
// From d3dtypes.h
#ifndef D3DDEVINFOID_TEXTUREMANAGER
#define D3DDEVINFOID_TEXTUREMANAGER 1
#endif
#ifndef D3DDEVINFOID_D3DTEXTUREMANAGER
#define D3DDEVINFOID_D3DTEXTUREMANAGER 2
#endif
#ifndef D3DDEVINFOID_TEXTURING
#define D3DDEVINFOID_TEXTURING 3
#endif
// From d3dhal.h
#ifndef D3DDEVINFOID_VCACHE
#define D3DDEVINFOID_VCACHE 4
#endif
// MinGW headers are broken. Who'dve guessed?
#ifndef _MSC_VER
// Missing from d3d8types.h
#ifndef D3DDEVINFOID_RESOURCEMANAGER
#define D3DDEVINFOID_RESOURCEMANAGER 5
#endif
#ifndef D3DDEVINFOID_VERTEXSTATS
#define D3DDEVINFOID_VERTEXSTATS 6 // Aka D3DDEVINFOID_D3DVERTEXSTATS
#endif
#else // _MSC_VER
// These are enum typedefs in the MinGW headers, but not defined by Microsoft
#define D3DVSDT_TYPE DWORD
#define D3DVSDE_REGISTER DWORD
#endif

136
src/d3d8/d3d8_interface.cpp Normal file
View File

@ -0,0 +1,136 @@
#include "d3d8_interface.h"
#include "d3d8_device.h"
#include "d3d8_texture.h"
#include <cstring>
namespace dxvk
{
D3D8Interface::D3D8Interface() {
m_d3d9 = d3d9::Direct3DCreate9(D3D_SDK_VERSION);
// Get the bridge interface to D3D9.
if (FAILED(m_d3d9->QueryInterface(__uuidof(IDxvkD3D8InterfaceBridge), (void**)&m_bridge))) {
throw DxvkError("D3D8Device: ERROR! Failed to get D3D9 Bridge. d3d9.dll might not be DXVK!");
}
m_d3d8Options = D3D8Options(*m_bridge->GetConfig());
m_adapterCount = m_d3d9->GetAdapterCount();
m_adapterModeCounts.resize(m_adapterCount);
m_adapterModes.reserve(m_adapterCount);
for (UINT adapter = 0; adapter < m_adapterCount; adapter++) {
m_adapterModes.emplace_back();
// cache adapter modes and mode counts for each d3d9 format
for (d3d9::D3DFORMAT fmt : ADAPTER_FORMATS) {
const UINT modeCount = m_d3d9->GetAdapterModeCount(adapter, fmt);
for (UINT mode = 0; mode < modeCount; mode++) {
m_adapterModes[adapter].emplace_back();
m_d3d9->EnumAdapterModes(adapter, fmt, mode, &(m_adapterModes[adapter].back()));
// can't use modeCount as it's only for one fmt
m_adapterModeCounts[adapter]++;
}
}
}
}
HRESULT STDMETHODCALLTYPE D3D8Interface::QueryInterface(REFIID riid, void** ppvObject) {
if (ppvObject == nullptr)
return E_POINTER;
*ppvObject = nullptr;
if (riid == __uuidof(IUnknown)
|| riid == __uuidof(IDirect3D8)) {
*ppvObject = ref(this);
return S_OK;
}
Logger::warn("D3D8Interface::QueryInterface: Unknown interface query");
Logger::warn(str::format(riid));
return E_NOINTERFACE;
}
HRESULT STDMETHODCALLTYPE D3D8Interface::GetAdapterIdentifier(
UINT Adapter,
DWORD Flags,
D3DADAPTER_IDENTIFIER8* pIdentifier) {
// This flag now has the opposite effect.
// Either way, WHQLevel will be 1 with Direct3D9Ex
if (Flags & D3DENUM_NO_WHQL_LEVEL)
Flags &= ~D3DENUM_WHQL_LEVEL;
else
Flags |= D3DENUM_WHQL_LEVEL;
d3d9::D3DADAPTER_IDENTIFIER9 identifier9;
HRESULT res = m_d3d9->GetAdapterIdentifier(Adapter, Flags, &identifier9);
strncpy(pIdentifier->Driver, identifier9.Driver, MAX_DEVICE_IDENTIFIER_STRING);
strncpy(pIdentifier->Description, identifier9.Description, MAX_DEVICE_IDENTIFIER_STRING);
pIdentifier->DriverVersion = identifier9.DriverVersion;
pIdentifier->VendorId = identifier9.VendorId;
pIdentifier->DeviceId = identifier9.DeviceId;
pIdentifier->SubSysId = identifier9.SubSysId;
pIdentifier->Revision = identifier9.Revision;
pIdentifier->DeviceIdentifier = identifier9.DeviceIdentifier;
pIdentifier->WHQLLevel = identifier9.WHQLLevel;
return res;
}
HRESULT __stdcall D3D8Interface::EnumAdapterModes(
UINT Adapter,
UINT Mode,
D3DDISPLAYMODE* pMode) {
if (Adapter >= m_adapterCount || Mode >= m_adapterModeCounts[Adapter] || pMode == nullptr) {
return D3DERR_INVALIDCALL;
}
pMode->Width = m_adapterModes[Adapter][Mode].Width;
pMode->Height = m_adapterModes[Adapter][Mode].Height;
pMode->RefreshRate = m_adapterModes[Adapter][Mode].RefreshRate;
pMode->Format = D3DFORMAT(m_adapterModes[Adapter][Mode].Format);
return D3D_OK;
}
HRESULT __stdcall D3D8Interface::CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS* pPresentationParameters,
IDirect3DDevice8** ppReturnedDeviceInterface) {
Com<d3d9::IDirect3DDevice9> pDevice9 = nullptr;
d3d9::D3DPRESENT_PARAMETERS params = ConvertPresentParameters9(pPresentationParameters);
HRESULT res = m_d3d9->CreateDevice(
Adapter,
(d3d9::D3DDEVTYPE)DeviceType,
hFocusWindow,
BehaviorFlags,
&params,
&pDevice9
);
if (FAILED(res)) {
return res;
}
*ppReturnedDeviceInterface = ref(new D3D8Device(
this, std::move(pDevice9),
DeviceType, hFocusWindow, BehaviorFlags,
pPresentationParameters
));
return res;
}
}

163
src/d3d8/d3d8_interface.h Normal file
View File

@ -0,0 +1,163 @@
#pragma once
#include "d3d8_include.h"
#include "d3d8_d3d9_util.h"
#include "d3d8_options.h"
#include "d3d8_format.h"
#include "../d3d9/d3d9_bridge.h"
namespace dxvk {
/**
* \brief D3D8 interface implementation
*
* Implements the IDirect3DDevice8 interfaces
* which provides the way to get adapters and create other objects such as \ref IDirect3DDevice8.
* similar to \ref DxgiFactory but for D3D8.
*/
class D3D8Interface final : public ComObjectClamp<IDirect3D8> {
static constexpr d3d9::D3DFORMAT ADAPTER_FORMATS[] = {
d3d9::D3DFMT_A1R5G5B5,
//d3d9::D3DFMT_A2R10G10B10, (not in D3D8)
d3d9::D3DFMT_A8R8G8B8,
d3d9::D3DFMT_R5G6B5,
d3d9::D3DFMT_X1R5G5B5,
d3d9::D3DFMT_X8R8G8B8
};
public:
D3D8Interface();
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject);
HRESULT STDMETHODCALLTYPE RegisterSoftwareDevice(void* pInitializeFunction) {
return m_d3d9->RegisterSoftwareDevice(pInitializeFunction);
}
UINT STDMETHODCALLTYPE GetAdapterCount() {
return m_d3d9->GetAdapterCount();
}
HRESULT STDMETHODCALLTYPE GetAdapterIdentifier(
UINT Adapter,
DWORD Flags,
D3DADAPTER_IDENTIFIER8* pIdentifier);
UINT STDMETHODCALLTYPE GetAdapterModeCount(UINT Adapter) {
return m_adapterModeCounts[Adapter];
}
HRESULT STDMETHODCALLTYPE EnumAdapterModes(
UINT Adapter,
UINT Mode,
D3DDISPLAYMODE* pMode);
HRESULT STDMETHODCALLTYPE GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE* pMode) {
return m_d3d9->GetAdapterDisplayMode(Adapter, (d3d9::D3DDISPLAYMODE*)pMode);
}
HRESULT STDMETHODCALLTYPE CheckDeviceType(
UINT Adapter,
D3DDEVTYPE DevType,
D3DFORMAT AdapterFormat,
D3DFORMAT BackBufferFormat,
BOOL bWindowed) {
return m_d3d9->CheckDeviceType(
Adapter,
(d3d9::D3DDEVTYPE)DevType,
(d3d9::D3DFORMAT)AdapterFormat,
(d3d9::D3DFORMAT)BackBufferFormat,
bWindowed
);
}
HRESULT STDMETHODCALLTYPE CheckDeviceFormat(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DFORMAT AdapterFormat,
DWORD Usage,
D3DRESOURCETYPE RType,
D3DFORMAT CheckFormat) {
return m_d3d9->CheckDeviceFormat(
Adapter,
(d3d9::D3DDEVTYPE)DeviceType,
(d3d9::D3DFORMAT)AdapterFormat,
Usage,
(d3d9::D3DRESOURCETYPE)RType,
(d3d9::D3DFORMAT)CheckFormat
);
}
HRESULT STDMETHODCALLTYPE CheckDeviceMultiSampleType(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DFORMAT SurfaceFormat,
BOOL Windowed,
D3DMULTISAMPLE_TYPE MultiSampleType) {
DWORD* pQualityLevels = nullptr;
return m_d3d9->CheckDeviceMultiSampleType(
Adapter,
(d3d9::D3DDEVTYPE)DeviceType,
(d3d9::D3DFORMAT)SurfaceFormat,
Windowed,
(d3d9::D3DMULTISAMPLE_TYPE)MultiSampleType,
pQualityLevels
);
}
HRESULT STDMETHODCALLTYPE CheckDepthStencilMatch(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DFORMAT AdapterFormat,
D3DFORMAT RenderTargetFormat,
D3DFORMAT DepthStencilFormat) {
if (isSupportedDepthStencilFormat(DepthStencilFormat))
return m_d3d9->CheckDepthStencilMatch(
Adapter,
(d3d9::D3DDEVTYPE)DeviceType,
(d3d9::D3DFORMAT)AdapterFormat,
(d3d9::D3DFORMAT)RenderTargetFormat,
(d3d9::D3DFORMAT)DepthStencilFormat
);
return D3DERR_NOTAVAILABLE;
}
HRESULT STDMETHODCALLTYPE GetDeviceCaps(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DCAPS8* pCaps) {
d3d9::D3DCAPS9 caps9;
HRESULT res = m_d3d9->GetDeviceCaps(Adapter, (d3d9::D3DDEVTYPE)DeviceType, &caps9);
dxvk::ConvertCaps8(caps9, pCaps);
return res;
}
HMONITOR STDMETHODCALLTYPE GetAdapterMonitor(UINT Adapter) {
return m_d3d9->GetAdapterMonitor(Adapter);
}
HRESULT STDMETHODCALLTYPE CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS* pPresentationParameters,
IDirect3DDevice8** ppReturnedDeviceInterface);
const D3D8Options& GetOptions() { return m_d3d8Options; }
private:
UINT m_adapterCount;
std::vector<UINT> m_adapterModeCounts;
std::vector<std::vector<d3d9::D3DDISPLAYMODE>> m_adapterModes;
d3d9::IDirect3D9* m_d3d9;
Com<IDxvkD3D8InterfaceBridge> m_bridge;
D3D8Options m_d3d8Options;
};
}

24
src/d3d8/d3d8_main.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "d3d8_interface.h"
namespace dxvk {
Logger Logger::s_instance("d3d8.log");
HRESULT CreateD3D8(IDirect3D8** ppDirect3D8) {
if (!ppDirect3D8)
return D3DERR_INVALIDCALL;
*ppDirect3D8 = ref(new D3D8Interface());
return D3D_OK;
}
}
extern "C" {
DLLEXPORT IDirect3D8* __stdcall Direct3DCreate8(UINT nSDKVersion) {
dxvk::Logger::trace("Direct3DCreate8 called");
IDirect3D8* pDirect3D = nullptr;
dxvk::CreateD3D8(&pDirect3D);
return pDirect3D;
}
}

55
src/d3d8/d3d8_options.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "d3d8_options.h"
#include "../d3d9/d3d9_bridge.h"
#include "../util/config/config.h"
#include "../util/util_string.h"
#include <charconv>
namespace dxvk {
static inline uint32_t parseDword(std::string_view str) {
uint32_t value = UINT32_MAX;
std::from_chars(str.data(), str.data() + str.size(), value);
return value;
}
void D3D8Options::parseVsDecl(const std::string& decl) {
if (decl.empty())
return;
if (decl.find_first_of("0123456789") == std::string::npos) {
Logger::warn(str::format("D3D8: Invalid forceVsDecl value: ", decl));
Logger::warn("D3D8: Expected numbers.");
return;
}
if (decl.find_first_of(":,;") == std::string::npos) {
Logger::warn(str::format("D3D8: Invalid forceVsDecl value: ", decl));
Logger::warn("D3D8: Expected a comma-separated list of colon-separated number pairs.");
return;
}
std::vector<std::string_view> decls = str::split(decl, ":,;");
if (decls.size() % 2 != 0) {
Logger::warn(str::format("D3D8: Invalid forceVsDecl value: ", decl));
Logger::warn("D3D8: Expected an even number of numbers.");
return;
}
for (size_t i = 0; i < decls.size(); i += 2) {
uint32_t reg = parseDword(decls[i]);
uint32_t type = parseDword(decls[i+1]);
if (reg > D3DVSDE_NORMAL2) {
Logger::warn(str::format("D3D8: Invalid forceVsDecl register number: ", decls[i]));
return;
}
if (type > D3DVSDT_SHORT4) {
Logger::warn(str::format("D3D8: Invalid forceVsDecl type: ", decls[i+1]));
return;
}
forceVsDecl.emplace_back(D3DVSDE_REGISTER(reg), D3DVSDT_TYPE(type));
}
}
}

48
src/d3d8/d3d8_options.h Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include "d3d8_include.h"
#include "../d3d9/d3d9_bridge.h"
#include "../util/config/config.h"
namespace dxvk {
struct D3D8Options {
/// Some games rely on undefined behavior by using undeclared vertex shader inputs.
/// The simplest way to fix them is to simply modify their vertex shader decl.
///
/// This option takes a comma-separated list of colon-separated number pairs, where
/// the first number is a D3DVSDE_REGISTER value, the second is a D3DVSDT_TYPE value.
/// e.g. "0:2,3:2,7:1" for float3 position : v0, float3 normal : v3, float2 uv : v7
std::vector<std::pair<D3DVSDE_REGISTER, D3DVSDT_TYPE>> forceVsDecl;
/// Specialized drawcall batcher, typically for games that draw a lot of similar
/// geometry in separate drawcalls (sometimes even one triangle at a time).
///
/// May hurt performance outside of specifc games that benefit from it.
bool batching = false;
/// The Lord of the Rings: The Fellowship of the Ring tries to create a P8 texture
/// in D3DPOOL_MANAGED on Nvidia and Intel, which fails, but has a separate code
/// path for ATI/AMD that creates it in D3DPOOL_SCRATCH instead, which works.
///
/// The internal logic determining this path doesn't seem to be d3d-related, but
/// the game works universally if we mimic its own ATI/AMD workaround during P8
/// texture creation.
///
/// Early Nvidia GPUs, such as the GeForce 4 generation cards, included and exposed
/// P8 texture support. However, it was no longer advertised with cards in the FX series
/// and above. Most likely ATI/AMD drivers never supported P8 in the first place.
bool placeP8InScratch = false;
D3D8Options() {}
D3D8Options(const Config& config) {
auto forceVsDeclStr = config.getOption<std::string>("d3d8.forceVsDecl", "");
batching = config.getOption<bool> ("d3d8.batching", batching);
placeP8InScratch = config.getOption<bool> ("d3d8.placeP8InScratch", placeP8InScratch);
parseVsDecl(forceVsDeclStr);
}
void parseVsDecl(const std::string& decl);
};
}

100
src/d3d8/d3d8_resource.h Normal file
View File

@ -0,0 +1,100 @@
#pragma once
/** Implements IDirect3DResource8
*
* - SetPrivateData, GetPrivateData, FreePrivateData
* - SetPriority, GetPriority
*
* - Subclasses provide: PreLoad, GetType
*/
#include "d3d8_device_child.h"
#include "../util/com/com_private_data.h"
namespace dxvk {
template <typename D3D9, typename D3D8>
class D3D8Resource : public D3D8DeviceChild<D3D9, D3D8> {
public:
D3D8Resource(D3D8Device* pDevice, Com<D3D9>&& Object)
: D3D8DeviceChild<D3D9, D3D8>(pDevice, std::move(Object))
, m_priority ( 0 ) { }
HRESULT STDMETHODCALLTYPE SetPrivateData(
REFGUID refguid,
const void* pData,
DWORD SizeOfData,
DWORD Flags) final {
HRESULT hr;
if (Flags & D3DSPD_IUNKNOWN) {
IUnknown* unknown =
const_cast<IUnknown*>(
reinterpret_cast<const IUnknown*>(pData));
hr = m_privateData.setInterface(
refguid, unknown);
}
else
hr = m_privateData.setData(
refguid, SizeOfData, pData);
if (FAILED(hr))
return D3DERR_INVALIDCALL;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE GetPrivateData(
REFGUID refguid,
void* pData,
DWORD* pSizeOfData) final {
HRESULT hr = m_privateData.getData(
refguid, reinterpret_cast<UINT*>(pSizeOfData), pData);
if (FAILED(hr))
return D3DERR_INVALIDCALL;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE FreePrivateData(REFGUID refguid) final {
HRESULT hr = m_privateData.setData(refguid, 0, nullptr);
if (FAILED(hr))
return D3DERR_INVALIDCALL;
return D3D_OK;
}
DWORD STDMETHODCALLTYPE SetPriority(DWORD PriorityNew) {
DWORD oldPriority = m_priority;
m_priority = PriorityNew;
return oldPriority;
}
DWORD STDMETHODCALLTYPE GetPriority() {
return m_priority;
}
virtual IUnknown* GetInterface(REFIID riid) override try {
return D3D8DeviceChild<D3D9, D3D8>::GetInterface(riid);
} catch (HRESULT err) {
if (riid == __uuidof(IDirect3DResource8))
return this;
throw err;
}
protected:
DWORD m_priority;
private:
ComPrivateData m_privateData;
};
}

336
src/d3d8/d3d8_shader.cpp Normal file
View File

@ -0,0 +1,336 @@
#include "d3d8_shader.h"
#define VSD_SHIFT_MASK(token, field) ((token & field ## MASK) >> field ## SHIFT)
#define VSD_ENCODE(token, field) ((token << field ## _SHIFT) & field ## _MASK)
// Magic number from D3DVSD_SKIP(...)
#define VSD_SKIP_FLAG 0x10000000
// This bit is set on all parameter (non-instruction) tokens.
#define VS_BIT_PARAM 0x80000000
namespace dxvk {
static constexpr int D3D8_NUM_VERTEX_INPUT_REGISTERS = 17;
/**
* Standard mapping of vertex input registers v0-v16 to D3D9 usages and usage indices
* (See D3DVSDE_REGISTER values in d3d8types.h or DirectX 8 docs for vertex shader input registers vN)
*
* \cite https://learn.microsoft.com/en-us/windows/win32/direct3d9/mapping-between-a-directx-9-declaration-and-directx-8
*/
static constexpr BYTE D3D8_VERTEX_INPUT_REGISTERS[D3D8_NUM_VERTEX_INPUT_REGISTERS][2] = {
{d3d9::D3DDECLUSAGE_POSITION, 0}, // dcl_position v0
{d3d9::D3DDECLUSAGE_BLENDWEIGHT, 0}, // dcl_blendweight v1
{d3d9::D3DDECLUSAGE_BLENDINDICES, 0}, // dcl_blendindices v2
{d3d9::D3DDECLUSAGE_NORMAL, 0}, // dcl_normal v3
{d3d9::D3DDECLUSAGE_PSIZE, 0}, // dcl_psize v4
{d3d9::D3DDECLUSAGE_COLOR, 0}, // dcl_color v5 ; diffuse
{d3d9::D3DDECLUSAGE_COLOR, 1}, // dcl_color1 v6 ; specular
{d3d9::D3DDECLUSAGE_TEXCOORD, 0}, // dcl_texcoord0 v7
{d3d9::D3DDECLUSAGE_TEXCOORD, 1}, // dcl_texcoord1 v8
{d3d9::D3DDECLUSAGE_TEXCOORD, 2}, // dcl_texcoord2 v9
{d3d9::D3DDECLUSAGE_TEXCOORD, 3}, // dcl_texcoord3 v10
{d3d9::D3DDECLUSAGE_TEXCOORD, 4}, // dcl_texcoord4 v11
{d3d9::D3DDECLUSAGE_TEXCOORD, 5}, // dcl_texcoord5 v12
{d3d9::D3DDECLUSAGE_TEXCOORD, 6}, // dcl_texcoord6 v13
{d3d9::D3DDECLUSAGE_TEXCOORD, 7}, // dcl_texcoord7 v14
{d3d9::D3DDECLUSAGE_POSITION, 1}, // dcl_position1 v15 ; position 2
{d3d9::D3DDECLUSAGE_NORMAL, 1}, // dcl_normal1 v16 ; normal 2
};
/** Width in bytes of each d3d9::D3DDECLTYPE or d3d8 D3DVSDT_TYPE */
static constexpr BYTE D3D9_DECL_TYPE_SIZES[d3d9::MAXD3DDECLTYPE + 1] = {
4, // FLOAT1
8, // FLOAT2
12, // FLOAT3
16, // FLOAT4
4, // D3DCOLOR
4, // UBYTE4
4, // SHORT2
8, // SHORT4
// The following are for vs2.0+ //
4, // UBYTE4N
4, // SHORT2N
8, // SHORT4N
4, // USHORT2N
8, // USHORT4N
6, // UDEC3
6, // DEC3N
8, // FLOAT16_2
16, // FLOAT16_4
0 // UNUSED
};
/**
* Encodes a \ref DxsoShaderInstruction
*
* \param [in] opcode DxsoOpcode
* \cite https://learn.microsoft.com/en-us/windows-hardware/drivers/display/instruction-token
*/
constexpr DWORD encodeInstruction(d3d9::D3DSHADER_INSTRUCTION_OPCODE_TYPE opcode) {
DWORD token = 0;
token |= opcode & 0xFFFF; // bits 0:15
return token;
}
/**
* Encodes a \ref DxsoRegister
*
* \param [in] regType DxsoRegisterType
* \cite https://learn.microsoft.com/en-us/windows-hardware/drivers/display/destination-parameter-token
*/
constexpr DWORD encodeDestRegister(d3d9::D3DSHADER_PARAM_REGISTER_TYPE type, UINT reg) {
DWORD token = 0;
token |= reg & 0x7FF; // bits 0:10 num
token |= ((type & 0x07) << 28); // bits 28:30 type[0:2]
token |= ((type & 0x18) >> 3) << 11; // bits 11:12 type[3:4]
// UINT addrMode : 1; // bit 13 hasRelative
token |= 0b1111 << 16; // bits 16:19 DxsoRegMask
// UINT resultModifier : 3; // bits 20:23
// UINT resultShift : 3; // bits 24:27
token |= 1 << 31; // bit 31 always 1
return token;
}
/**
* Encodes a \ref DxsoDeclaration
*
* \cite https://learn.microsoft.com/en-us/windows-hardware/drivers/display/dcl-instruction
*/
constexpr DWORD encodeDeclaration(d3d9::D3DDECLUSAGE usage, DWORD index) {
DWORD token = 0;
token |= VSD_ENCODE(usage, D3DSP_DCL_USAGE); // bits 0:4 DxsoUsage (TODO: missing MSB)
token |= VSD_ENCODE(index, D3DSP_DCL_USAGEINDEX); // bits 16:19 usageIndex
token |= 1 << 31; // bit 31 always 1
return token;
}
/**
* Converts a D3D8 vertex shader + declaration
* to a D3D9 vertex shader + declaration.
*/
D3D9VertexShaderCode TranslateVertexShader8(
const DWORD* pDeclaration,
const DWORD* pFunction,
const D3D8Options& options) {
using d3d9::D3DDECLTYPE;
using d3d9::D3DDECLTYPE_UNUSED;
D3D9VertexShaderCode result;
std::vector<DWORD>& tokens = result.function;
std::vector<DWORD> defs; // Constant definitions
// shaderInputRegisters:
// set bit N to enable input register vN
DWORD shaderInputRegisters = 0;
d3d9::D3DVERTEXELEMENT9* vertexElements = result.declaration;
unsigned int elementIdx = 0;
// These are used for pDeclaration and pFunction
int i = 0;
DWORD token;
std::stringstream dbg;
dbg << "Vertex Declaration Tokens:\n\t";
WORD currentStream = 0;
WORD currentOffset = 0;
auto addVertexElement = [&] (D3DVSDE_REGISTER reg, D3DVSDT_TYPE type) {
vertexElements[elementIdx].Stream = currentStream;
vertexElements[elementIdx].Offset = currentOffset;
vertexElements[elementIdx].Method = d3d9::D3DDECLMETHOD_DEFAULT;
vertexElements[elementIdx].Type = D3DDECLTYPE(type); // (D3DVSDT_TYPE values map directly to D3DDECLTYPE)
vertexElements[elementIdx].Usage = D3D8_VERTEX_INPUT_REGISTERS[reg][0];
vertexElements[elementIdx].UsageIndex = D3D8_VERTEX_INPUT_REGISTERS[reg][1];
// Increase stream offset
currentOffset += D3D9_DECL_TYPE_SIZES[type];
// Enable register vn
shaderInputRegisters |= 1 << reg;
// Finished with this element
elementIdx++;
};
// Remap d3d8 decl tokens to d3d9 vertex elements,
// and enable bits on shaderInputRegisters for each.
if (options.forceVsDecl.size() == 0) do {
token = pDeclaration[i++];
D3DVSD_TOKENTYPE tokenType = D3DVSD_TOKENTYPE(VSD_SHIFT_MASK(token, D3DVSD_TOKENTYPE));
switch (tokenType) {
case D3DVSD_TOKEN_NOP:
dbg << "NOP";
break;
case D3DVSD_TOKEN_STREAM: {
dbg << "STREAM ";
// TODO: D3DVSD_STREAM_TESS
if (token & D3DVSD_STREAMTESSMASK) {
dbg << "TESS";
}
DWORD streamNum = VSD_SHIFT_MASK(token, D3DVSD_STREAMNUMBER);
currentStream = WORD(streamNum);
currentOffset = 0; // reset offset
dbg << ", num=" << streamNum;
break;
}
case D3DVSD_TOKEN_STREAMDATA: {
dbg << "STREAMDATA ";
// D3DVSD_SKIP
if (token & VSD_SKIP_FLAG) {
auto skipCount = VSD_SHIFT_MASK(token, D3DVSD_SKIPCOUNT);
dbg << "SKIP " << " count=" << skipCount;
currentOffset += WORD(skipCount) * sizeof(DWORD);
break;
}
// D3DVSD_REG
DWORD dataLoadType = VSD_SHIFT_MASK(token, D3DVSD_DATALOADTYPE);
if ( dataLoadType == 0 ) { // vertex
D3DVSDT_TYPE type = D3DVSDT_TYPE(VSD_SHIFT_MASK(token, D3DVSD_DATATYPE));
D3DVSDE_REGISTER reg = D3DVSDE_REGISTER(VSD_SHIFT_MASK(token, D3DVSD_VERTEXREG));
addVertexElement(reg, type);
dbg << "type=" << type << ", register=" << reg;
} else {
// TODO: When would this bit be 1?
dbg << "D3DVSD_DATALOADTYPE " << dataLoadType;
}
break;
}
case D3DVSD_TOKEN_TESSELLATOR:
dbg << "TESSELLATOR " << std::hex << token;
// TODO: D3DVSD_TOKEN_TESSELLATOR
break;
case D3DVSD_TOKEN_CONSTMEM: {
dbg << "CONSTMEM ";
DWORD count = VSD_SHIFT_MASK(token, D3DVSD_CONSTCOUNT);
DWORD regCount = count * 4;
DWORD addr = VSD_SHIFT_MASK(token, D3DVSD_CONSTADDRESS);
DWORD rs = VSD_SHIFT_MASK(token, D3DVSD_CONSTRS);
dbg << "count=" << count << ", addr=" << addr << ", rs=" << rs;
// Add a DEF instruction for each constant
for (DWORD j = 0; j < regCount; j += 4) {
defs.push_back(encodeInstruction(d3d9::D3DSIO_DEF));
defs.push_back(encodeDestRegister(d3d9::D3DSPR_CONST2, addr));
defs.push_back(pDeclaration[i+j+0]);
defs.push_back(pDeclaration[i+j+1]);
defs.push_back(pDeclaration[i+j+2]);
defs.push_back(pDeclaration[i+j+3]);
addr++;
}
i += regCount;
break;
}
case D3DVSD_TOKEN_EXT: {
dbg << "EXT " << std::hex << token << " ";
DWORD extInfo = VSD_SHIFT_MASK(token, D3DVSD_EXTINFO);
DWORD extCount = VSD_SHIFT_MASK(token, D3DVSD_EXTCOUNT);
dbg << "info=" << extInfo << ", count=" << extCount;
break;
}
case D3DVSD_TOKEN_END: {
vertexElements[elementIdx++] = D3DDECL_END();
dbg << "END";
break;
}
default:
dbg << "UNKNOWN TYPE";
break;
}
dbg << "\n\t";
//dbg << std::hex << token << " ";
} while (token != D3DVSD_END());
Logger::debug(dbg.str());
// If forceVsDecl is set, use that decl instead.
if (options.forceVsDecl.size() > 0) {
for (auto [reg, type] : options.forceVsDecl) {
addVertexElement(reg, type);
}
vertexElements[elementIdx++] = D3DDECL_END();
}
if (pFunction != nullptr) {
// Copy first token (version)
tokens.push_back(pFunction[0]);
DWORD vsMajor = D3DSHADER_VERSION_MAJOR(pFunction[0]);
DWORD vsMinor = D3DSHADER_VERSION_MINOR(pFunction[0]);
Logger::debug(str::format("VS version: ", vsMajor, ".", vsMinor));
// Insert dcl instructions
for (int vn = 0; vn < D3D8_NUM_VERTEX_INPUT_REGISTERS; vn++) {
// If bit N is set then we need to dcl register vN
if ((shaderInputRegisters & (1 << vn)) != 0) {
Logger::debug(str::format("\tShader Input Regsiter: v", vn));
DWORD usage = D3D8_VERTEX_INPUT_REGISTERS[vn][0];
DWORD index = D3D8_VERTEX_INPUT_REGISTERS[vn][1];
tokens.push_back(encodeInstruction(d3d9::D3DSIO_DCL)); // dcl opcode
tokens.push_back(encodeDeclaration(d3d9::D3DDECLUSAGE(usage), index)); // usage token
tokens.push_back(encodeDestRegister(d3d9::D3DSPR_INPUT, vn)); // dest register num
}
}
// Copy constant defs
for (DWORD def : defs) {
tokens.push_back(def);
}
// Copy shader tokens from input,
// skip first token (we already copied it)
i = 1;
do {
token = pFunction[i++];
DWORD opcode = token & D3DSI_OPCODE_MASK;
// Instructions
if ((token & VS_BIT_PARAM) == 0) {
// RSQ swizzle fixup
if (opcode == D3DSIO_RSQ) {
tokens.push_back(token); // instr
tokens.push_back(token = pFunction[i++]); // dest
token = pFunction[i++]; // src0
// If no swizzling is done, then use the w-component.
// See d8vk#43 for more information as this may need to change in some cases.
if (((token & D3DVS_NOSWIZZLE) == D3DVS_NOSWIZZLE)) {
token &= ~D3DVS_SWIZZLE_MASK;
token |= (D3DVS_X_W | D3DVS_Y_W | D3DVS_Z_W | D3DVS_W_W);
}
}
}
tokens.push_back(token);
} while (token != D3DVS_END());
}
return result;
}
}

18
src/d3d8/d3d8_shader.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include "d3d8_include.h"
#include "d3d8_options.h"
namespace dxvk {
struct D3D9VertexShaderCode {
d3d9::D3DVERTEXELEMENT9 declaration[MAXD3DDECLLENGTH + 1];
std::vector<DWORD> function;
};
D3D9VertexShaderCode TranslateVertexShader8(
const DWORD* pDeclaration,
const DWORD* pFunction,
const D3D8Options& overrides);
}

View File

@ -0,0 +1,49 @@
#include "d3d8_device.h"
#include "d3d8_state_block.h"
HRESULT dxvk::D3D8StateBlock::Capture() {
if (unlikely(m_stateBlock == nullptr))
return D3DERR_INVALIDCALL;
if (m_capture.vs) m_device->GetVertexShader(&m_vertexShader);
if (m_capture.ps) m_device->GetPixelShader(&m_pixelShader);
for (DWORD stage = 0; stage < m_textures.size(); stage++) {
if (m_capture.textures.get(stage))
m_textures[stage] = m_device->m_textures[stage].ptr();
}
if (m_capture.indices) {
m_baseVertexIndex = m_device->m_baseVertexIndex;
m_indices = m_device->m_indices.ptr();
}
if (m_capture.swvp)
m_device->GetRenderState(D3DRS_SOFTWAREVERTEXPROCESSING, (DWORD*)&m_isSWVP);
return m_stateBlock->Capture();
}
HRESULT dxvk::D3D8StateBlock::Apply() {
if (unlikely(m_stateBlock == nullptr))
return D3DERR_INVALIDCALL;
HRESULT res = m_stateBlock->Apply();
if (m_capture.vs) m_device->SetVertexShader(m_vertexShader);
if (m_capture.ps) m_device->SetPixelShader(m_pixelShader);
for (DWORD stage = 0; stage < m_textures.size(); stage++) {
if (m_capture.textures.get(stage))
m_device->SetTexture(stage, m_textures[stage]);
}
if (m_capture.indices)
m_device->SetIndices(m_indices, m_baseVertexIndex);
// This was a very easy footgun for D3D8 applications.
if (m_capture.swvp)
m_device->SetRenderState(D3DRS_SOFTWAREVERTEXPROCESSING, m_isSWVP);
return res;
}

134
src/d3d8/d3d8_state_block.h Normal file
View File

@ -0,0 +1,134 @@
#pragma once
#include "d3d8_caps.h"
#include "d3d8_include.h"
#include "d3d8_device.h"
#include "d3d8_device_child.h"
#include <array>
namespace dxvk {
struct D3D8StateCapture {
bool vs : 1;
bool ps : 1;
bool indices : 1;
bool swvp : 1;
bit::bitset<d8caps::MAX_TEXTURE_STAGES> textures;
D3D8StateCapture()
: vs(false)
, ps(false)
, indices(false)
, swvp(false) {
// Ensure all bits are initialized to false
textures.clearAll();
}
};
// Wrapper class for D3D9 state blocks. Captures D3D8-specific state.
class D3D8StateBlock {
public:
D3D8StateBlock(
D3D8Device* pDevice,
D3DSTATEBLOCKTYPE Type,
Com<d3d9::IDirect3DStateBlock9>&& pStateBlock)
: m_device(pDevice)
, m_stateBlock(std::move(pStateBlock))
, m_type(Type) {
if (Type == D3DSBT_VERTEXSTATE || Type == D3DSBT_ALL) {
// Lights, D3DTSS_TEXCOORDINDEX and D3DTSS_TEXTURETRANSFORMFLAGS,
// vertex shader, VS constants, and various render states.
m_capture.vs = true;
}
if (Type == D3DSBT_PIXELSTATE || Type == D3DSBT_ALL) {
// Pixel shader, PS constants, and various RS/TSS states.
m_capture.ps = true;
}
if (Type == D3DSBT_ALL) {
m_capture.indices = true;
m_capture.swvp = true;
m_capture.textures.setAll();
}
m_textures.fill(nullptr);
}
~D3D8StateBlock() {}
// Construct a state block without a D3D9 object
D3D8StateBlock(D3D8Device* pDevice)
: D3D8StateBlock(pDevice, D3DSTATEBLOCKTYPE(0), nullptr) {
}
// Attach a D3D9 object to a state block that doesn't have one yet
void SetD3D9(Com<d3d9::IDirect3DStateBlock9>&& pStateBlock) {
if (likely(m_stateBlock == nullptr)) {
m_stateBlock = std::move(pStateBlock);
} else {
Logger::err("D3D8StateBlock::SetD3D9 called when m_stateBlock has already been initialized");
}
}
HRESULT Capture();
HRESULT Apply();
inline HRESULT SetVertexShader(DWORD Handle) {
m_vertexShader = Handle;
m_capture.vs = true;
return D3D_OK;
}
inline HRESULT SetPixelShader(DWORD Handle) {
m_pixelShader = Handle;
m_capture.ps = true;
return D3D_OK;
}
inline HRESULT SetTexture(DWORD Stage, IDirect3DBaseTexture8* pTexture) {
m_textures[Stage] = pTexture;
m_capture.textures.set(Stage, true);
return D3D_OK;
}
inline HRESULT SetIndices(IDirect3DIndexBuffer8* pIndexData, UINT BaseVertexIndex) {
m_indices = pIndexData;
m_baseVertexIndex = BaseVertexIndex;
m_capture.indices = true;
return D3D_OK;
}
inline HRESULT SetSoftwareVertexProcessing(bool value) {
m_isSWVP = value;
m_capture.swvp = true;
return D3D_OK;
}
private:
D3D8Device* m_device;
Com<d3d9::IDirect3DStateBlock9> m_stateBlock;
D3DSTATEBLOCKTYPE m_type;
private: // State Data //
D3D8StateCapture m_capture;
DWORD m_vertexShader; // vs
DWORD m_pixelShader; // ps
std::array<IDirect3DBaseTexture8*, d8caps::MAX_TEXTURE_STAGES> m_textures; // textures
IDirect3DIndexBuffer8* m_indices = nullptr; // indices
UINT m_baseVertexIndex; // indices
bool m_isSWVP; // D3DRS_SOFTWAREVERTEXPROCESSING
};
}

View File

@ -0,0 +1,61 @@
#pragma once
#include "d3d8_resource.h"
namespace dxvk {
// Base class for Surfaces and Volumes,
// which can be attached to Textures.
template <typename D3D9, typename D3D8>
class D3D8Subresource : public D3D8Resource<D3D9, D3D8> {
using Resource = D3D8Resource<D3D9, D3D8>;
public:
D3D8Subresource(
D3D8Device* pDevice,
Com<D3D9>&& Object,
IDirect3DBaseTexture8* pBaseTexture)
: Resource(pDevice, std::move(Object)),
m_container(pBaseTexture) {
}
~D3D8Subresource() {
}
// Refing subresources implicitly refs the container texture,
ULONG STDMETHODCALLTYPE AddRef() final {
if (m_container != nullptr)
return m_container->AddRef();
return Resource::AddRef();
}
// and releasing them implicitly releases the texture.
ULONG STDMETHODCALLTYPE Release() final {
if (m_container != nullptr)
return m_container->Release();
return Resource::Release();
}
// Clients can grab the container if they want
HRESULT STDMETHODCALLTYPE GetContainer(REFIID riid, void** ppContainer) final {
if (m_container != nullptr)
return m_container->QueryInterface(riid, ppContainer);
return this->GetDevice()->QueryInterface(riid, ppContainer);
}
inline IDirect3DBaseTexture8* GetBaseTexture() {
return m_container;
}
protected:
IDirect3DBaseTexture8* m_container;
};
}

26
src/d3d8/d3d8_surface.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "d3d8_surface.h"
#include "d3d8_device.h"
namespace dxvk {
Com<d3d9::IDirect3DSurface9> D3D8Surface::CreateBlitImage() {
d3d9::D3DSURFACE_DESC desc;
GetD3D9()->GetDesc(&desc);
// NOTE: This adds a D3DPOOL_DEFAULT resource to the
// device, which counts as losable during device reset
Com<d3d9::IDirect3DSurface9> image = nullptr;
HRESULT res = GetParent()->GetD3D9()->CreateRenderTarget(
desc.Width, desc.Height, desc.Format,
d3d9::D3DMULTISAMPLE_NONE, 0,
FALSE,
&image,
NULL);
if (FAILED(res))
throw new DxvkError("D3D8: Failed to create blit image");
return image;
}
}

81
src/d3d8/d3d8_surface.h Normal file
View File

@ -0,0 +1,81 @@
#pragma once
#include "d3d8_include.h"
#include "d3d8_subresource.h"
#include "d3d8_d3d9_util.h"
namespace dxvk {
// TODO: all inherited methods in D3D8Surface should be final like in d9vk
using D3D8SurfaceBase = D3D8Subresource<d3d9::IDirect3DSurface9, IDirect3DSurface8>;
class D3D8Surface final : public D3D8SurfaceBase {
public:
D3D8Surface(
D3D8Device* pDevice,
IDirect3DBaseTexture8* pTexture,
Com<d3d9::IDirect3DSurface9>&& pSurface)
: D3D8SurfaceBase (pDevice, std::move(pSurface), pTexture) {
}
// A surface does not need to be attached to a texture
D3D8Surface(
D3D8Device* pDevice,
Com<d3d9::IDirect3DSurface9>&& pSurface)
: D3D8Surface (pDevice, nullptr, std::move(pSurface)) {
}
D3DRESOURCETYPE STDMETHODCALLTYPE GetType() {
return D3DRESOURCETYPE(GetD3D9()->GetType());
}
HRESULT STDMETHODCALLTYPE GetDesc(D3DSURFACE_DESC* pDesc) {
d3d9::D3DSURFACE_DESC desc;
HRESULT res = GetD3D9()->GetDesc(&desc);
ConvertSurfaceDesc8(&desc, pDesc);
return res;
}
HRESULT STDMETHODCALLTYPE LockRect(D3DLOCKED_RECT* pLockedRect, CONST RECT* pRect, DWORD Flags) {
return GetD3D9()->LockRect((d3d9::D3DLOCKED_RECT*)pLockedRect, pRect, Flags);
}
HRESULT STDMETHODCALLTYPE UnlockRect() {
return GetD3D9()->UnlockRect();
}
HRESULT STDMETHODCALLTYPE GetDC(HDC* phDC) {
return GetD3D9()->GetDC(phDC);
}
HRESULT STDMETHODCALLTYPE ReleaseDC(HDC hDC) {
return GetD3D9()->ReleaseDC(hDC);
}
public:
/**
* \brief Allocate or reuse an image of the same size
* as this texture for performing blit into system mem.
*
* TODO: Consider creating only one texture to
* encompass all surface levels of a texture.
*/
Com<d3d9::IDirect3DSurface9> GetBlitImage() {
if (unlikely(m_blitImage == nullptr)) {
m_blitImage = CreateBlitImage();
}
return m_blitImage;
}
private:
Com<d3d9::IDirect3DSurface9> CreateBlitImage();
Com<d3d9::IDirect3DSurface9> m_blitImage = nullptr;
};
}

42
src/d3d8/d3d8_swapchain.h Normal file
View File

@ -0,0 +1,42 @@
#pragma once
#include "d3d8_device_child.h"
#include "d3d8_surface.h"
#include "d3d8_d3d9_util.h"
namespace dxvk {
using D3D8SwapChainBase = D3D8DeviceChild<d3d9::IDirect3DSwapChain9, IDirect3DSwapChain8>;
class D3D8SwapChain final : public D3D8SwapChainBase {
public:
D3D8SwapChain(
D3D8Device* pDevice,
Com<d3d9::IDirect3DSwapChain9>&& pSwapChain)
: D3D8SwapChainBase(pDevice, std::move(pSwapChain)) {}
HRESULT STDMETHODCALLTYPE Present(const RECT *src, const RECT *dst, HWND hWnd, const RGNDATA *dirtyRegion) final {
return GetD3D9()->Present(src, dst, hWnd, dirtyRegion, 0);
}
HRESULT STDMETHODCALLTYPE GetBackBuffer(UINT BackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface8** ppBackBuffer) final {
// Same logic as in D3D8Device::GetBackBuffer
if (unlikely(m_backBuffer == nullptr)) {
Com<d3d9::IDirect3DSurface9> pSurface9;
HRESULT res = GetD3D9()->GetBackBuffer(BackBuffer, (d3d9::D3DBACKBUFFER_TYPE)Type, &pSurface9);
m_backBuffer = new D3D8Surface(GetParent(), std::move(pSurface9));
*ppBackBuffer = m_backBuffer.ref();
return res;
}
*ppBackBuffer = m_backBuffer.ref();
return D3D_OK;
}
private:
Com<D3D8Surface> m_backBuffer = nullptr;
};
}

233
src/d3d8/d3d8_texture.h Normal file
View File

@ -0,0 +1,233 @@
#pragma once
#include "d3d8_resource.h"
#include "d3d8_surface.h"
#include "d3d8_volume.h"
#include "d3d8_d3d9_util.h"
#include <vector>
#include <new>
namespace dxvk {
template <typename SubresourceType, typename D3D9, typename D3D8>
class D3D8BaseTexture : public D3D8Resource<D3D9, D3D8> {
public:
constexpr static UINT CUBE_FACES = 6;
using SubresourceType8 = typename SubresourceType::D3D8;
using SubresourceType9 = typename SubresourceType::D3D9;
D3D8BaseTexture(
D3D8Device* pDevice,
Com<D3D9>&& pBaseTexture,
UINT SubresourceCount)
: D3D8Resource<D3D9, D3D8> ( pDevice, std::move(pBaseTexture) ) {
m_subresources.resize(SubresourceCount, nullptr);
}
~D3D8BaseTexture() {
for (size_t i = 0; i < m_subresources.size(); i++)
if (m_subresources[i] != nullptr)
m_subresources[i] = nullptr;
}
virtual IUnknown* GetInterface(REFIID riid) final override try {
return D3D8Resource<D3D9, D3D8>::GetInterface(riid);
} catch (HRESULT err) {
if (riid == __uuidof(IDirect3DBaseTexture8))
return this;
throw err;
}
void STDMETHODCALLTYPE PreLoad() final {
this->GetD3D9()->PreLoad();
}
DWORD STDMETHODCALLTYPE SetLOD(DWORD LODNew) final {
return this->GetD3D9()->SetLOD(LODNew);
}
DWORD STDMETHODCALLTYPE GetLOD() final {
return this->GetD3D9()->GetLOD();
}
DWORD STDMETHODCALLTYPE GetLevelCount() final {
return this->GetD3D9()->GetLevelCount();
}
protected:
HRESULT STDMETHODCALLTYPE GetSubresource(UINT Index, SubresourceType8** ppSubresource) {
InitReturnPtr(ppSubresource);
if (unlikely(Index >= m_subresources.size()))
return D3DERR_INVALIDCALL;
if (m_subresources[Index] == nullptr) {
try {
Com<SubresourceType9> subresource = LookupSubresource(Index);
// Cache the subresource
m_subresources[Index] = new SubresourceType(this->m_parent, this, std::move(subresource));
} catch (HRESULT res) {
return res;
}
}
*ppSubresource = m_subresources[Index].ref();
return D3D_OK;
}
private:
Com<SubresourceType9> LookupSubresource(UINT Index) {
Com<SubresourceType9> ptr = nullptr;
HRESULT res = D3DERR_INVALIDCALL;
if constexpr (std::is_same_v<D3D8, IDirect3DTexture8>) {
res = this->GetD3D9()->GetSurfaceLevel(Index, &ptr);
} else if constexpr (std::is_same_v<D3D8, IDirect3DVolume8>) {
res = this->GetD3D9()->GetVolumeLevel(Index, &ptr);
} else if constexpr (std::is_same_v<D3D8, IDirect3DCubeTexture8>) {
res = this->GetD3D9()->GetCubeMapSurface(d3d9::D3DCUBEMAP_FACES(Index % CUBE_FACES), Index / CUBE_FACES, &ptr);
}
if (FAILED(res))
throw res;
return ptr;
}
std::vector<Com<SubresourceType, false>> m_subresources;
};
using D3D8Texture2DBase = D3D8BaseTexture<D3D8Surface, d3d9::IDirect3DTexture9, IDirect3DTexture8>;
class D3D8Texture2D final : public D3D8Texture2DBase {
public:
D3D8Texture2D(
D3D8Device* pDevice,
Com<d3d9::IDirect3DTexture9>&& pTexture)
: D3D8Texture2DBase(pDevice, std::move(pTexture), pTexture->GetLevelCount()) {
}
D3DRESOURCETYPE STDMETHODCALLTYPE GetType() final { return D3DRTYPE_TEXTURE; }
HRESULT STDMETHODCALLTYPE GetLevelDesc(UINT Level, D3DSURFACE_DESC* pDesc) {
d3d9::D3DSURFACE_DESC surf;
HRESULT res = GetD3D9()->GetLevelDesc(Level, &surf);
ConvertSurfaceDesc8(&surf, pDesc);
return res;
}
HRESULT STDMETHODCALLTYPE GetSurfaceLevel(UINT Level, IDirect3DSurface8** ppSurfaceLevel) {
return GetSubresource(Level, ppSurfaceLevel);
}
HRESULT STDMETHODCALLTYPE LockRect(UINT Level, D3DLOCKED_RECT* pLockedRect, CONST RECT* pRect, DWORD Flags) {
return GetD3D9()->LockRect(Level, reinterpret_cast<d3d9::D3DLOCKED_RECT*>(pLockedRect), pRect, Flags);
}
HRESULT STDMETHODCALLTYPE UnlockRect(UINT Level) {
return GetD3D9()->UnlockRect(Level);
}
HRESULT STDMETHODCALLTYPE AddDirtyRect(CONST RECT* pDirtyRect) {
return GetD3D9()->AddDirtyRect(pDirtyRect);
}
};
using D3D8Texture3DBase = D3D8BaseTexture<D3D8Volume, d3d9::IDirect3DVolumeTexture9, IDirect3DVolumeTexture8>;
class D3D8Texture3D final : public D3D8Texture3DBase {
public:
D3D8Texture3D(
D3D8Device* pDevice,
Com<d3d9::IDirect3DVolumeTexture9>&& pVolumeTexture)
: D3D8Texture3DBase(pDevice, std::move(pVolumeTexture), pVolumeTexture->GetLevelCount()) {}
D3DRESOURCETYPE STDMETHODCALLTYPE GetType() final { return D3DRTYPE_VOLUMETEXTURE; }
HRESULT STDMETHODCALLTYPE GetLevelDesc(UINT Level, D3DVOLUME_DESC *pDesc) {
d3d9::D3DVOLUME_DESC vol;
HRESULT res = GetD3D9()->GetLevelDesc(Level, &vol);
ConvertVolumeDesc8(&vol, pDesc);
return res;
}
HRESULT STDMETHODCALLTYPE GetVolumeLevel(UINT Level, IDirect3DVolume8** ppVolumeLevel) {
return GetSubresource(Level, ppVolumeLevel);
}
HRESULT STDMETHODCALLTYPE LockBox(UINT Level, D3DLOCKED_BOX* pLockedBox, CONST D3DBOX* pBox, DWORD Flags) {
return GetD3D9()->LockBox(
Level,
reinterpret_cast<d3d9::D3DLOCKED_BOX*>(pLockedBox),
reinterpret_cast<const d3d9::D3DBOX*>(pBox),
Flags
);
}
HRESULT STDMETHODCALLTYPE UnlockBox(UINT Level) {
return GetD3D9()->UnlockBox(Level);
}
HRESULT STDMETHODCALLTYPE AddDirtyBox(CONST D3DBOX* pDirtyBox) {
return GetD3D9()->AddDirtyBox(reinterpret_cast<const d3d9::D3DBOX*>(pDirtyBox));
}
};
using D3D8TextureCubeBase = D3D8BaseTexture<D3D8Surface, d3d9::IDirect3DCubeTexture9, IDirect3DCubeTexture8>;
class D3D8TextureCube final : public D3D8TextureCubeBase {
public:
D3D8TextureCube(
D3D8Device* pDevice,
Com<d3d9::IDirect3DCubeTexture9>&& pTexture)
: D3D8TextureCubeBase(pDevice, std::move(pTexture), pTexture->GetLevelCount() * CUBE_FACES) {
}
D3DRESOURCETYPE STDMETHODCALLTYPE GetType() final { return D3DRTYPE_CUBETEXTURE; }
HRESULT STDMETHODCALLTYPE GetLevelDesc(UINT Level, D3DSURFACE_DESC* pDesc) {
d3d9::D3DSURFACE_DESC surf;
HRESULT res = GetD3D9()->GetLevelDesc(Level, &surf);
ConvertSurfaceDesc8(&surf, pDesc);
return res;
}
HRESULT STDMETHODCALLTYPE GetCubeMapSurface(D3DCUBEMAP_FACES Face, UINT Level, IDirect3DSurface8** ppSurfaceLevel) {
return GetSubresource((Level * CUBE_FACES) + Face, ppSurfaceLevel);
}
HRESULT STDMETHODCALLTYPE LockRect(
D3DCUBEMAP_FACES Face,
UINT Level,
D3DLOCKED_RECT* pLockedRect,
const RECT* pRect,
DWORD Flags) {
return GetD3D9()->LockRect(
d3d9::D3DCUBEMAP_FACES(Face),
Level,
reinterpret_cast<d3d9::D3DLOCKED_RECT*>(pLockedRect),
pRect,
Flags);
}
HRESULT STDMETHODCALLTYPE UnlockRect(D3DCUBEMAP_FACES Face, UINT Level) {
return GetD3D9()->UnlockRect(d3d9::D3DCUBEMAP_FACES(Face), Level);
}
HRESULT STDMETHODCALLTYPE AddDirtyRect(D3DCUBEMAP_FACES Face, const RECT* pDirtyRect) {
return GetD3D9()->AddDirtyRect(d3d9::D3DCUBEMAP_FACES(Face), pDirtyRect);
}
};
}

40
src/d3d8/d3d8_volume.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include "d3d8_subresource.h"
#include "d3d8_d3d9_util.h"
namespace dxvk {
using D3D8VolumeBase = D3D8Subresource<d3d9::IDirect3DVolume9, IDirect3DVolume8>;
class D3D8Volume final : public D3D8VolumeBase {
public:
D3D8Volume(
D3D8Device* pDevice,
IDirect3DVolumeTexture8* pTexture,
Com<d3d9::IDirect3DVolume9>&& pVolume)
: D3D8VolumeBase(pDevice, std::move(pVolume), pTexture) {}
HRESULT STDMETHODCALLTYPE GetDesc(D3DVOLUME_DESC* pDesc) {
d3d9::D3DVOLUME_DESC desc;
HRESULT res = GetD3D9()->GetDesc(&desc);
ConvertVolumeDesc8(&desc, pDesc);
return res;
}
HRESULT STDMETHODCALLTYPE LockBox(D3DLOCKED_BOX* pLockedBox, CONST D3DBOX* pBox, DWORD Flags) final {
return GetD3D9()->LockBox(
reinterpret_cast<d3d9::D3DLOCKED_BOX*>(pLockedBox),
reinterpret_cast<const d3d9::D3DBOX*>(pBox),
Flags
);
}
HRESULT STDMETHODCALLTYPE UnlockBox() final {
return GetD3D9()->UnlockBox();
}
};
}

View File

@ -0,0 +1,68 @@
#pragma once
#include "d3d8_include.h"
namespace dxvk {
template <typename D3D9Type, typename D3D8Type>
class D3D8WrappedObject : public ComObjectClamp<D3D8Type> {
public:
using D3D9 = D3D9Type;
using D3D8 = D3D8Type;
D3D8WrappedObject(Com<D3D9>&& object)
: m_d3d9(std::move(object)) {
}
D3D9* GetD3D9() {
return m_d3d9.ptr();
}
// For cases where the object may be null.
static D3D9* GetD3D9Nullable(D3D8WrappedObject* self) {
if (unlikely(self == NULL)) {
return NULL;
}
return self->m_d3d9.ptr();
}
template <typename T>
static D3D9* GetD3D9Nullable(Com<T>& self) {
return GetD3D9Nullable(self.ptr());
}
virtual IUnknown* GetInterface(REFIID riid) {
if (riid == __uuidof(IUnknown))
return this;
if (riid == __uuidof(D3D8))
return this;
throw E_NOINTERFACE;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) final {
if (ppvObject == nullptr)
return E_POINTER;
*ppvObject = nullptr;
try {
*ppvObject = ref(this->GetInterface(riid));
return S_OK;
} catch (HRESULT err) {
Logger::warn("D3D8WrappedObject::QueryInterface: Unknown interface query");
Logger::warn(str::format(riid));
return err;
}
}
private:
Com<D3D9> m_d3d9;
};
}

42
src/d3d8/meson.build Normal file
View File

@ -0,0 +1,42 @@
d3d8_res = wrc_generator.process('version.rc')
d3d8_src = [
'd3d8_main.cpp',
'd3d8_interface.cpp',
'd3d8_device.cpp',
'd3d8_options.cpp',
'd3d8_surface.cpp',
'd3d8_state_block.cpp',
'd3d8_shader.cpp'
]
d3d8_ld_args = []
d3d8_link_depends = []
if platform != 'windows'
lib_d3d9 = d3d9_dep
d3d8_ld_args += [ '-Wl,--version-script', join_paths(meson.current_source_dir(), 'd3d8.sym') ]
d3d8_link_depends += files('d3d8.sym')
endif
d3d8_dll = shared_library(dxvk_name_prefix+'d3d8', d3d8_src, d3d8_res,
dependencies : [ lib_d3d9, util_dep, dxso_dep, dxvk_dep ],
include_directories : dxvk_include_path,
install : true,
vs_module_defs : 'd3d8'+def_spec_ext,
link_args : d3d8_ld_args,
link_depends : [ d3d8_link_depends ],
kwargs : dxvk_so_version,
)
d3d8_dep = declare_dependency(
link_with : [ d3d8_dll ],
include_directories : [ dxvk_include_path ],
)
if platform != 'windows'
pkg.generate(d3d8_dll,
filebase: dxvk_pkg_prefix + 'd3d8',
subdirs: 'dxvk',
)
endif

31
src/d3d8/version.rc Normal file
View File

@ -0,0 +1,31 @@
#include <windows.h>
// DLL version information.
VS_VERSION_INFO VERSIONINFO
FILEVERSION 10,0,17763,1
PRODUCTVERSION 10,0,17763,1
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS 0
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "080904b0"
BEGIN
VALUE "CompanyName", "DXVK"
VALUE "FileDescription", "Direct3D 8 Runtime"
VALUE "FileVersion", "10.0.17763.1 (WinBuild.160101.0800)"
VALUE "InternalName", "D3D8.dll"
VALUE "LegalCopyright", "zlib/libpng license"
VALUE "OriginalFilename", "D3D8.dll"
VALUE "ProductName", "DXVK"
VALUE "ProductVersion", "10.0.17763.1"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0809, 1200
END
END

View File

@ -31,7 +31,14 @@ if get_option('enable_d3d9')
subdir('d3d9')
endif
if get_option('enable_d3d8')
if not get_option('enable_d3d9')
error('D3D9 is required for D3D8.')
endif
subdir('d3d8')
endif
# Nothing selected
if not get_option('enable_d3d9') and not get_option('enable_dxgi')
if not get_option('enable_d3d8') and not get_option('enable_d3d9') and not get_option('enable_dxgi')
warning('Nothing selected to be built.?')
endif

View File

@ -698,10 +698,6 @@ namespace dxvk {
{ R"(\\SWTFU2\.exe$)", {{
{ "d3d9.forceSamplerTypeSpecConstants", "True" },
}} },
/* Scrapland (Remastered) */
{ R"(\\Scrap\.exe$)", {{
{ "d3d9.deferSurfaceCreation", "True" },
}} },
/* Majesty 2 (Collection) *
* Crashes on UMA without a memory limit, *
* since the game(s) will allocate all *
@ -732,10 +728,6 @@ namespace dxvk {
{ R"(\\bionic_commando\.exe$)", {{
{ "d3d9.maxFrameRate", "60" },
}} },
/* Need For Speed 3 modern patch */
{ R"(\\nfs3\.exe$)", {{
{ "d3d9.enableDialogMode", "True" },
}} },
/* Beyond Good And Evil *
* UI breaks at high fps */
{ R"(\\BGE\.exe$)", {{
@ -996,6 +988,133 @@ namespace dxvk {
{ R"(\\P3R\.exe$)", {{
{ "dxgi.syncInterval", "1" },
}} },
/**********************************************/
/* D3D8 GAMES */
/**********************************************/
/* Duke Nukem Forever (2001) */
{ R"(\\DukeForever\.exe$)", {{
{ "d3d9.maxFrameRate", "60" },
}} },
/* Indiana Jones and the Emperor's Tomb *
* Fixes intro window being stuck on screen */
{ R"(\\indy\.exe$)", {{
{ "d3d9.enableDialogMode", "True" },
}} },
/* Tom Clancy's Splinter Cell *
* Supports shadow buffers */
{ R"(\\splintercell\.exe$)", {{
{ "d3d8.useShadowBuffers", "True" },
}} },
/* Anito: Defend a Land Enraged */
{ R"(\\Anito\.exe$)", {{
{ "d3d9.memoryTrackTest", "True" },
{ "d3d9.maxAvailableMemory", "1024" },
}} },
/* Red Faction *
* Fixes crashing when starting a new game */
{ R"(\\RF\.exe$)", {{
{ "d3d9.allowDirectBufferMapping", "False" },
}} },
/* Commandos 3 *
* The game doesn't use NOOVERWRITE properly *
* and reads from actively modified buffers, *
* which causes graphical glitches at times */
{ R"(\\Commandos3\.exe$)", {{
{ "d3d9.allowDirectBufferMapping", "False" },
}} },
/* Motor City Online */
{ R"(\\MCity_d\.exe$)", {{
{ "d3d9.cachedDynamicBuffers", "True" },
{ "d3d8.batching", "True" },
}} },
/* Railroad Tycoon 3 */
{ R"(\\RT3\.exe$)", {{
{ "d3d9.maxFrameRate", "60" },
}} },
/* Pure Pinball 2.0 REDUX *
* This game reads from undeclared vs inputs *
* but somehow works on native. Let's just *
* change its declaration to make them work. */
{ R"(\\Pure Pinball 2\.0 REDUX\.exe$)", {{
{ "d3d8.forceVsDecl", "0:2,4:2,7:4,9:1,8:1" },
}} },
/* Need for Speed III: Hot Pursuit *
(with the "Modern Patch") */
{ R"(\\nfs3\.exe$)", {{
{ "d3d9.enableDialogMode", "True" },
{ "d3d9.cachedDynamicBuffers", "True" },
{ "d3d8.batching", "True" },
}} },
/* Need for Speed: High Stakes / Road *
Challenge (with the "Modern Patch") - *
Won't actually render anything in game *
without a memory limit in place */
{ R"(\\nfs4\.exe$)", {{
{ "d3d9.enableDialogMode", "True" },
{ "d3d9.cachedDynamicBuffers", "True" },
{ "d3d9.memoryTrackTest", "True" },
{ "d3d9.maxAvailableMemory", "256" },
{ "d3d8.batching", "True" },
}} },
/* Need for Speed: Hot Pursuit 2 */
{ R"(\\NFSHP2\.exe$)", {{
{ "d3d9.cachedDynamicBuffers", "True" },
}} },
/* Project I.G.I. 2: Covert Strike */
{ R"(\\igi2\.exe$)", {{
{ "d3d9.cachedDynamicBuffers", "True" },
}} },
/* Treasure Planet: Battle at Procyon *
* Declares v5 as color but shader uses v6 */
{ R"(\\TP_Win32\.exe$)", {{
{ "d3d8.forceVsDecl", "0:2,3:2,6:4,7:1" },
}} },
/* Scrapland (Remastered) */
{ R"(\\Scrap\.exe$)", {{
{ "d3d9.deferSurfaceCreation", "True" },
}} },
/* V-Rally 3 */
{ R"(\\VRally3(Demo)?\.exe$)", {{
{ "d3d9.maxFrameRate", "60" },
}} },
/* Soldiers: Heroes Of World War II *
* Fills up all available memory and hangs *
* while loading the main menu otherwise */
{ R"(\\Soldiers\.exe$)", {{
{ "d3d9.memoryTrackTest", "True" },
{ "d3d9.maxAvailableMemory", "512" },
}} },
/* Cossacks II: Napoleonic Wars & *
* Battle for Europe */
{ R"(\\Cossacks II.*\\engine\.exe$)", {{
{ "d3d9.maxFrameRate", "60" },
}} },
/* Alexander */
{ R"(\\Alexander\\Data\\engine\.exe$)", {{
{ "d3d9.maxFrameRate", "60" },
}} },
/* 3DMark2001 (SE) *
* Fixes a drastic performance drop in the *
* "Car Chase - High Detail" benchmark */
{ R"(\\3DMark2001(SE)?\.exe$)", {{
{ "d3d9.allowDirectBufferMapping", "False" },
}} },
/* Delta Force: Black Hawk Down */
{ R"(\\dfbhd\.exe$)", {{
{ "d3d9.cachedDynamicBuffers", "True" },
}} },
/* X2: The Threat */
{ R"(\\X2\.exe$)", {{
{ "d3d9.cachedDynamicBuffers", "True" },
}} },
/* The Lord of the Rings: *
* The Fellowship of the Ring */
{ R"(\\Fellowship\.exe$)", {{
{ "d3d9.maxFrameRate", "60" },
{ "d3d8.placeP8InScratch", "True" },
}} },
}};

View File

@ -4,6 +4,11 @@
#include "vulkan_loader.h"
#if defined(_MSC_VER)
// Unary minus on unsigned type
#pragma warning( disable : 4146 )
#endif
namespace dxvk::vk {
inline VkImageSubresourceRange makeSubresourceRange(