From 60e523b4bf9c30d4794094710c5ac5214ad78561 Mon Sep 17 00:00:00 2001 From: Jeff Date: Sun, 7 Jul 2024 12:10:48 +0100 Subject: [PATCH] [d3d8] Implement Direct3D 8 Frontend Co-authored-by: WinterSnowfall ## Config Changes Co-authored-by: Blisto91 <47954800+Blisto91@users.noreply.github.com> Co-authored-by: simifor --- .github/workflows/test-build-windows.yml | 7 + LICENSE | 1 + README.md | 2 +- dxvk.conf | 26 + meson_options.txt | 1 + src/d3d8/d3d8.def | 3 + src/d3d8/d3d8.sym | 7 + src/d3d8/d3d8_batch.h | 239 +++ src/d3d8/d3d8_buffer.h | 102 ++ src/d3d8/d3d8_caps.h | 8 + src/d3d8/d3d8_d3d9_util.h | 164 ++ src/d3d8/d3d8_device.cpp | 1738 ++++++++++++++++++++++ src/d3d8/d3d8_device.h | 454 ++++++ src/d3d8/d3d8_device_child.h | 71 + src/d3d8/d3d8_format.h | 220 +++ src/d3d8/d3d8_include.h | 200 +++ src/d3d8/d3d8_interface.cpp | 136 ++ src/d3d8/d3d8_interface.h | 163 ++ src/d3d8/d3d8_main.cpp | 24 + src/d3d8/d3d8_options.cpp | 55 + src/d3d8/d3d8_options.h | 48 + src/d3d8/d3d8_resource.h | 100 ++ src/d3d8/d3d8_shader.cpp | 336 +++++ src/d3d8/d3d8_shader.h | 18 + src/d3d8/d3d8_state_block.cpp | 49 + src/d3d8/d3d8_state_block.h | 134 ++ src/d3d8/d3d8_subresource.h | 61 + src/d3d8/d3d8_surface.cpp | 26 + src/d3d8/d3d8_surface.h | 81 + src/d3d8/d3d8_swapchain.h | 42 + src/d3d8/d3d8_texture.h | 233 +++ src/d3d8/d3d8_volume.h | 40 + src/d3d8/d3d8_wrapped_object.h | 68 + src/d3d8/meson.build | 42 + src/d3d8/version.rc | 31 + src/meson.build | 9 +- src/util/config/config.cpp | 135 +- src/vulkan/vulkan_util.h | 5 + 38 files changed, 5069 insertions(+), 10 deletions(-) create mode 100644 src/d3d8/d3d8.def create mode 100644 src/d3d8/d3d8.sym create mode 100644 src/d3d8/d3d8_batch.h create mode 100644 src/d3d8/d3d8_buffer.h create mode 100644 src/d3d8/d3d8_caps.h create mode 100644 src/d3d8/d3d8_d3d9_util.h create mode 100644 src/d3d8/d3d8_device.cpp create mode 100644 src/d3d8/d3d8_device.h create mode 100644 src/d3d8/d3d8_device_child.h create mode 100644 src/d3d8/d3d8_format.h create mode 100644 src/d3d8/d3d8_include.h create mode 100644 src/d3d8/d3d8_interface.cpp create mode 100644 src/d3d8/d3d8_interface.h create mode 100644 src/d3d8/d3d8_main.cpp create mode 100644 src/d3d8/d3d8_options.cpp create mode 100644 src/d3d8/d3d8_options.h create mode 100644 src/d3d8/d3d8_resource.h create mode 100644 src/d3d8/d3d8_shader.cpp create mode 100644 src/d3d8/d3d8_shader.h create mode 100644 src/d3d8/d3d8_state_block.cpp create mode 100644 src/d3d8/d3d8_state_block.h create mode 100644 src/d3d8/d3d8_subresource.h create mode 100644 src/d3d8/d3d8_surface.cpp create mode 100644 src/d3d8/d3d8_surface.h create mode 100644 src/d3d8/d3d8_swapchain.h create mode 100644 src/d3d8/d3d8_texture.h create mode 100644 src/d3d8/d3d8_volume.h create mode 100644 src/d3d8/d3d8_wrapped_object.h create mode 100644 src/d3d8/meson.build create mode 100644 src/d3d8/version.rc diff --git a/.github/workflows/test-build-windows.yml b/.github/workflows/test-build-windows.yml index 503bd480..bae8414b 100644 --- a/.github/workflows/test-build-windows.yml +++ b/.github/workflows/test-build-windows.yml @@ -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: | diff --git a/LICENSE b/LICENSE index 253cadd6..68b6498f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ Copyright (c) 2017 Philip Rebohle Copyright (c) 2019 Joshua Ashton + Copyright (c) 2023 Jeffrey Ellison zlib/libpng license diff --git a/README.md b/README.md index 364f2bb7..753b375f 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/dxvk.conf b/dxvk.conf index 453024fe..87642979 100644 --- a/dxvk.conf +++ b/dxvk.conf @@ -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 diff --git a/meson_options.txt b/meson_options.txt index 5ac9ea7b..2a621ece 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -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') diff --git a/src/d3d8/d3d8.def b/src/d3d8/d3d8.def new file mode 100644 index 00000000..62aca0c9 --- /dev/null +++ b/src/d3d8/d3d8.def @@ -0,0 +1,3 @@ +LIBRARY D3D8.DLL +EXPORTS + Direct3DCreate8 @ 5 diff --git a/src/d3d8/d3d8.sym b/src/d3d8/d3d8.sym new file mode 100644 index 00000000..357113c3 --- /dev/null +++ b/src/d3d8/d3d8.sym @@ -0,0 +1,7 @@ +{ + global: + Direct3DCreate8; + + local: + *; +}; diff --git a/src/d3d8/d3d8_batch.h b/src/d3d8/d3d8_batch.h new file mode 100644 index 00000000..a510b67a --- /dev/null +++ b/src/d3d8/d3d8_batch.h @@ -0,0 +1,239 @@ +#pragma once + +#include "d3d8_include.h" +#include "d3d8_buffer.h" +#include "d3d8_format.h" + +#include +#include +#include + +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 m_data; + DWORD m_fvf; + }; + + + // Main handler for batching D3D8 draw calls. + class D3D8Batcher { + + struct Batch { + D3DPRIMITIVETYPE PrimitiveType = D3DPT_INVALID; + std::vector Indices; + UINT Offset = 0; + UINT MinVertex = UINT_MAX; + UINT MaxVertex = 0; + UINT PrimitiveCount = 0; + UINT DrawCallCount = 0; + }; + + public: + D3D8Batcher(D3D8Device* pDevice8, Com&& 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(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 m_device; + + D3D8BatchBuffer* m_stream = nullptr; + UINT m_stride = 0; + D3D8IndexBuffer* m_indices = nullptr; + INT m_baseVertexIndex = 0; + std::array m_batches; + }; +} diff --git a/src/d3d8/d3d8_buffer.h b/src/d3d8/d3d8_buffer.h new file mode 100644 index 00000000..89b1aae9 --- /dev/null +++ b/src/d3d8/d3d8_buffer.h @@ -0,0 +1,102 @@ +#pragma once + +#include "d3d8_include.h" +#include "d3d8_resource.h" + +namespace dxvk { + + template + class D3D8Buffer : public D3D8Resource { + + public: + + D3D8Buffer( + D3D8Device* pDevice, + Com&& pBuffer, + D3DPOOL Pool, + DWORD Usage) + : D3D8Resource (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(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; + class D3D8VertexBuffer : public D3D8VertexBufferBase { + + public: + + D3D8VertexBuffer( + D3D8Device* pDevice, + Com&& 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(pDesc)); + if (!FAILED(hr)) { + pDesc->Pool = m_pool; + pDesc->Usage = m_usage; + } + return hr; + } + + }; + + using D3D8IndexBufferBase = D3D8Buffer; + class D3D8IndexBuffer final : public D3D8IndexBufferBase { + + public: + + D3D8IndexBuffer( + D3D8Device* pDevice, + Com&& 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(pDesc)); + if (!FAILED(hr)) { + pDesc->Pool = m_pool; + pDesc->Usage = m_usage; + } + return hr; + } + + }; +} \ No newline at end of file diff --git a/src/d3d8/d3d8_caps.h b/src/d3d8/d3d8_caps.h new file mode 100644 index 00000000..0da600c7 --- /dev/null +++ b/src/d3d8/d3d8_caps.h @@ -0,0 +1,8 @@ +#pragma once + +namespace dxvk::d8caps { + + inline constexpr uint32_t MAX_TEXTURE_STAGES = 8; + inline constexpr uint32_t MAX_STREAMS = 16; + +} \ No newline at end of file diff --git a/src/d3d8/d3d8_d3d9_util.h b/src/d3d8/d3d8_d3d9_util.h new file mode 100644 index 00000000..1143899e --- /dev/null +++ b/src/d3d8/d3d8_d3d9_util.h @@ -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 + +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); + } + } +} + diff --git a/src/d3d8/d3d8_device.cpp b/src/d3d8/d3d8_device.cpp new file mode 100644 index 00000000..61be21e1 --- /dev/null +++ b/src/d3d8/d3d8_device.cpp @@ -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 declaration; + std::vector function; + }; + + D3D8Device::D3D8Device( + D3D8Interface* pParent, + Com&& 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(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 pSwapChain9; + d3d9::D3DPRESENT_PARAMETERS params = ConvertPresentParameters9(pPresentationParameters); + HRESULT res = GetD3D9()->CreateAdditionalSwapChain( + ¶ms, + &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(¶ms); + + 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 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(pRamp)); + } + + void STDMETHODCALLTYPE D3D8Device::GetGammaRamp(D3DGAMMARAMP* pRamp) { + // For swap chain 0 + GetD3D9()->GetGammaRamp(0, reinterpret_cast(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 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 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 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 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 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 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 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 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 src = static_cast(pSourceSurface); + Com dst = static_cast(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 = ▭ + + 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 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 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(pSourceTexture); + D3D8Texture2D* dst = static_cast(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 surf = static_cast(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(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(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 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 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(pViewport)); + } + + HRESULT STDMETHODCALLTYPE D3D8Device::GetViewport(D3DVIEWPORT8* pViewport) { + return GetD3D9()->GetViewport(reinterpret_cast(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 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(pStateBlock)); + + return res; + } + + HRESULT STDMETHODCALLTYPE D3D8Device::CaptureStateBlock(DWORD Token) { + return reinterpret_cast(Token)->Capture(); + } + + HRESULT STDMETHODCALLTYPE D3D8Device::ApplyStateBlock(DWORD Token) { + StateChange(); + return reinterpret_cast(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(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 pStateBlock; + HRESULT res = GetD3D9()->EndStateBlock(&pStateBlock); + + m_recorder->SetD3D9(std::move(pStateBlock)); + + *pToken = DWORD(reinterpret_cast(m_recorder)); + + m_recorder = nullptr; + + return res; + } + + HRESULT STDMETHODCALLTYPE D3D8Device::SetClipStatus(const D3DCLIPSTATUS8* pClipStatus) { + StateChange(); + return GetD3D9()->SetClipStatus(reinterpret_cast(pClipStatus)); + } + + HRESULT STDMETHODCALLTYPE D3D8Device::GetClipStatus(D3DCLIPSTATUS8* pClipStatus) { + return GetD3D9()->GetClipStatus(reinterpret_cast(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(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(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(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(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(BaseVertexIndex); + + D3D8IndexBuffer* buffer = static_cast(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(pConstantData), ConstantCount); + } + + HRESULT STDMETHODCALLTYPE D3D8Device::DrawRectPatch( + UINT Handle, + const float* pNumSegs, + const D3DRECTPATCH_INFO* pRectPatchInfo) { + return GetD3D9()->DrawRectPatch(Handle, pNumSegs, reinterpret_cast(pRectPatchInfo)); + } + + HRESULT STDMETHODCALLTYPE D3D8Device::DrawTriPatch( + UINT Handle, + const float* pNumSegs, + const D3DTRIPATCH_INFO* pTriPatchInfo) { + return GetD3D9()->DrawTriPatch(Handle, pNumSegs, reinterpret_cast(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(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(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(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); + } + +} diff --git a/src/d3d8/d3d8_device.h b/src/d3d8/d3d8_device.h new file mode 100644 index 00000000..14172363 --- /dev/null +++ b/src/d3d8/d3d8_device.h @@ -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 +#include +#include +#include + +namespace dxvk { + + class D3D8Interface; + + struct D3D8VertexShaderInfo; + + using D3D8DeviceBase = D3D8WrappedObject; + class D3D8Device final : public D3D8DeviceBase { + + friend class D3D8StateBlock; + public: + + D3D8Device( + D3D8Interface* pParent, + Com&& 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 pSurface9; + GetD3D9()->GetBackBuffer(0, i, d3d9::D3DBACKBUFFER_TYPE_MONO, &pSurface9); + m_backBuffers[i] = new D3D8Surface(this, std::move(pSurface9)); + } + + Com 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 m_bridge; + const D3D8Options& m_d3d8Options; + + Com m_parent; + + D3DPRESENT_PARAMETERS m_presentParams; + + D3D8StateBlock* m_recorder = nullptr; + std::unordered_set m_stateBlocks; + D3D8Batcher* m_batcher = nullptr; + + struct D3D8VBO { + Com buffer = nullptr; + UINT stride = 0; + }; + + // Remember to fill() these in the constructor! + std::array, d8caps::MAX_TEXTURE_STAGES> m_textures; + std::array m_streams; + + Com m_indices; + INT m_baseVertexIndex = 0; + + // TODO: Which of these should be a private ref + std::vector> m_backBuffers; + Com m_autoDepthStencil; + + Com m_renderTarget; + Com m_depthStencil; + + std::vector m_vertexShaders; + std::vector 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; + + }; + +} diff --git a/src/d3d8/d3d8_device_child.h b/src/d3d8/d3d8_device_child.h new file mode 100644 index 00000000..d4c2523e --- /dev/null +++ b/src/d3d8/d3d8_device_child.h @@ -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 + class D3D8DeviceChild : public D3D8WrappedObject { + + public: + + D3D8DeviceChild(D3D8Device* pDevice, Com&& Object) + : D3D8WrappedObject(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(m_parent); + } + + D3D8Device* GetParent() { + return m_parent; + } + + protected: + + D3D8Device* m_parent; + + }; + +} \ No newline at end of file diff --git a/src/d3d8/d3d8_format.h b/src/d3d8/d3d8_format.h new file mode 100644 index 00000000..3aea400d --- /dev/null +++ b/src/d3d8/d3d8_format.h @@ -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); + } + +} diff --git a/src/d3d8/d3d8_include.h b/src/d3d8/d3d8_include.h new file mode 100644 index 00000000..78d7dcd1 --- /dev/null +++ b/src/d3d8/d3d8_include.h @@ -0,0 +1,200 @@ +#pragma once + +#ifndef _MSC_VER +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0A00 +#endif + +#include +#include + +// 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 { static constexpr IID __uuid_inst = {l,w1,w2, {b1,b2,b3,b4,b5,b6,b7,b8}}; }; \ + extern "C++" template<> constexpr const GUID &__mingw_uuidof() { return __mingw_uuidof_s::__uuid_inst; } \ + extern "C++" template<> constexpr const GUID &__mingw_uuidof() { return __mingw_uuidof_s::__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() { return GUID{a,b,c,{d,e,f,g,h,i,j,k}}; } } \ + extern "C++" { template <> constexpr GUID __uuidof_helper() { return __uuidof_helper(); } } \ + extern "C++" { template <> constexpr GUID __uuidof_helper() { return __uuidof_helper(); } } \ + extern "C++" { template <> constexpr GUID __uuidof_helper() { return __uuidof_helper(); } } \ + extern "C++" { template <> constexpr GUID __uuidof_helper() { return __uuidof_helper(); } } \ +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 +} + +// 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 diff --git a/src/d3d8/d3d8_interface.cpp b/src/d3d8/d3d8_interface.cpp new file mode 100644 index 00000000..b17fd35c --- /dev/null +++ b/src/d3d8/d3d8_interface.cpp @@ -0,0 +1,136 @@ +#include "d3d8_interface.h" + +#include "d3d8_device.h" +#include "d3d8_texture.h" + +#include + +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 pDevice9 = nullptr; + d3d9::D3DPRESENT_PARAMETERS params = ConvertPresentParameters9(pPresentationParameters); + HRESULT res = m_d3d9->CreateDevice( + Adapter, + (d3d9::D3DDEVTYPE)DeviceType, + hFocusWindow, + BehaviorFlags, + ¶ms, + &pDevice9 + ); + + if (FAILED(res)) { + return res; + } + + *ppReturnedDeviceInterface = ref(new D3D8Device( + this, std::move(pDevice9), + DeviceType, hFocusWindow, BehaviorFlags, + pPresentationParameters + )); + + return res; + } + + +} \ No newline at end of file diff --git a/src/d3d8/d3d8_interface.h b/src/d3d8/d3d8_interface.h new file mode 100644 index 00000000..59b87a86 --- /dev/null +++ b/src/d3d8/d3d8_interface.h @@ -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 { + + 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 m_adapterModeCounts; + std::vector> m_adapterModes; + + d3d9::IDirect3D9* m_d3d9; + Com m_bridge; + D3D8Options m_d3d8Options; + }; + +} \ No newline at end of file diff --git a/src/d3d8/d3d8_main.cpp b/src/d3d8/d3d8_main.cpp new file mode 100644 index 00000000..265a5baa --- /dev/null +++ b/src/d3d8/d3d8_main.cpp @@ -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; + } +} diff --git a/src/d3d8/d3d8_options.cpp b/src/d3d8/d3d8_options.cpp new file mode 100644 index 00000000..61f77805 --- /dev/null +++ b/src/d3d8/d3d8_options.cpp @@ -0,0 +1,55 @@ +#include "d3d8_options.h" +#include "../d3d9/d3d9_bridge.h" +#include "../util/config/config.h" +#include "../util/util_string.h" + +#include + +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 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)); + } + + } +} diff --git a/src/d3d8/d3d8_options.h b/src/d3d8/d3d8_options.h new file mode 100644 index 00000000..67bdf359 --- /dev/null +++ b/src/d3d8/d3d8_options.h @@ -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> 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("d3d8.forceVsDecl", ""); + batching = config.getOption ("d3d8.batching", batching); + placeP8InScratch = config.getOption ("d3d8.placeP8InScratch", placeP8InScratch); + + parseVsDecl(forceVsDeclStr); + } + + void parseVsDecl(const std::string& decl); + }; +} diff --git a/src/d3d8/d3d8_resource.h b/src/d3d8/d3d8_resource.h new file mode 100644 index 00000000..c5562e1a --- /dev/null +++ b/src/d3d8/d3d8_resource.h @@ -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 + class D3D8Resource : public D3D8DeviceChild { + + public: + + D3D8Resource(D3D8Device* pDevice, Com&& Object) + : D3D8DeviceChild(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( + reinterpret_cast(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(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::GetInterface(riid); + } catch (HRESULT err) { + if (riid == __uuidof(IDirect3DResource8)) + return this; + + throw err; + } + + protected: + + DWORD m_priority; + + private: + + ComPrivateData m_privateData; + + }; + + +} \ No newline at end of file diff --git a/src/d3d8/d3d8_shader.cpp b/src/d3d8/d3d8_shader.cpp new file mode 100644 index 00000000..7b44ba5e --- /dev/null +++ b/src/d3d8/d3d8_shader.cpp @@ -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& tokens = result.function; + std::vector 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; + } +} diff --git a/src/d3d8/d3d8_shader.h b/src/d3d8/d3d8_shader.h new file mode 100644 index 00000000..f0a83ae1 --- /dev/null +++ b/src/d3d8/d3d8_shader.h @@ -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 function; + }; + + D3D9VertexShaderCode TranslateVertexShader8( + const DWORD* pDeclaration, + const DWORD* pFunction, + const D3D8Options& overrides); + +} \ No newline at end of file diff --git a/src/d3d8/d3d8_state_block.cpp b/src/d3d8/d3d8_state_block.cpp new file mode 100644 index 00000000..b4cb468e --- /dev/null +++ b/src/d3d8/d3d8_state_block.cpp @@ -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; +} diff --git a/src/d3d8/d3d8_state_block.h b/src/d3d8/d3d8_state_block.h new file mode 100644 index 00000000..18ab4cb9 --- /dev/null +++ b/src/d3d8/d3d8_state_block.h @@ -0,0 +1,134 @@ +#pragma once + +#include "d3d8_caps.h" +#include "d3d8_include.h" +#include "d3d8_device.h" +#include "d3d8_device_child.h" + +#include + +namespace dxvk { + + struct D3D8StateCapture { + bool vs : 1; + bool ps : 1; + bool indices : 1; + bool swvp : 1; + + bit::bitset 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&& 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&& 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 m_stateBlock; + D3DSTATEBLOCKTYPE m_type; + + private: // State Data // + + D3D8StateCapture m_capture; + + DWORD m_vertexShader; // vs + DWORD m_pixelShader; // ps + + std::array m_textures; // textures + + IDirect3DIndexBuffer8* m_indices = nullptr; // indices + UINT m_baseVertexIndex; // indices + + bool m_isSWVP; // D3DRS_SOFTWAREVERTEXPROCESSING + }; + + +} \ No newline at end of file diff --git a/src/d3d8/d3d8_subresource.h b/src/d3d8/d3d8_subresource.h new file mode 100644 index 00000000..c4cd1460 --- /dev/null +++ b/src/d3d8/d3d8_subresource.h @@ -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 + class D3D8Subresource : public D3D8Resource { + + using Resource = D3D8Resource; + + public: + + D3D8Subresource( + D3D8Device* pDevice, + Com&& 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; + }; + +} \ No newline at end of file diff --git a/src/d3d8/d3d8_surface.cpp b/src/d3d8/d3d8_surface.cpp new file mode 100644 index 00000000..8d7c970b --- /dev/null +++ b/src/d3d8/d3d8_surface.cpp @@ -0,0 +1,26 @@ + +#include "d3d8_surface.h" +#include "d3d8_device.h" + +namespace dxvk { + + Com 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 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; + } +} \ No newline at end of file diff --git a/src/d3d8/d3d8_surface.h b/src/d3d8/d3d8_surface.h new file mode 100644 index 00000000..600f47eb --- /dev/null +++ b/src/d3d8/d3d8_surface.h @@ -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; + class D3D8Surface final : public D3D8SurfaceBase { + + public: + + D3D8Surface( + D3D8Device* pDevice, + IDirect3DBaseTexture8* pTexture, + Com&& pSurface) + : D3D8SurfaceBase (pDevice, std::move(pSurface), pTexture) { + } + + // A surface does not need to be attached to a texture + D3D8Surface( + D3D8Device* pDevice, + Com&& 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 GetBlitImage() { + if (unlikely(m_blitImage == nullptr)) { + m_blitImage = CreateBlitImage(); + } + + return m_blitImage; + } + + + private: + Com CreateBlitImage(); + + Com m_blitImage = nullptr; + + }; +} \ No newline at end of file diff --git a/src/d3d8/d3d8_swapchain.h b/src/d3d8/d3d8_swapchain.h new file mode 100644 index 00000000..35707213 --- /dev/null +++ b/src/d3d8/d3d8_swapchain.h @@ -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; + class D3D8SwapChain final : public D3D8SwapChainBase { + + public: + + D3D8SwapChain( + D3D8Device* pDevice, + Com&& 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 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 m_backBuffer = nullptr; + + }; + +} \ No newline at end of file diff --git a/src/d3d8/d3d8_texture.h b/src/d3d8/d3d8_texture.h new file mode 100644 index 00000000..6978adb8 --- /dev/null +++ b/src/d3d8/d3d8_texture.h @@ -0,0 +1,233 @@ +#pragma once + +#include "d3d8_resource.h" +#include "d3d8_surface.h" +#include "d3d8_volume.h" + +#include "d3d8_d3d9_util.h" + +#include +#include + +namespace dxvk { + + template + class D3D8BaseTexture : public D3D8Resource { + + public: + + constexpr static UINT CUBE_FACES = 6; + + using SubresourceType8 = typename SubresourceType::D3D8; + using SubresourceType9 = typename SubresourceType::D3D9; + + D3D8BaseTexture( + D3D8Device* pDevice, + Com&& pBaseTexture, + UINT SubresourceCount) + : D3D8Resource ( 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::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 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 LookupSubresource(UINT Index) { + Com ptr = nullptr; + HRESULT res = D3DERR_INVALIDCALL; + if constexpr (std::is_same_v) { + res = this->GetD3D9()->GetSurfaceLevel(Index, &ptr); + } else if constexpr (std::is_same_v) { + res = this->GetD3D9()->GetVolumeLevel(Index, &ptr); + } else if constexpr (std::is_same_v) { + res = this->GetD3D9()->GetCubeMapSurface(d3d9::D3DCUBEMAP_FACES(Index % CUBE_FACES), Index / CUBE_FACES, &ptr); + } + if (FAILED(res)) + throw res; + return ptr; + } + + std::vector> m_subresources; + + }; + + using D3D8Texture2DBase = D3D8BaseTexture; + class D3D8Texture2D final : public D3D8Texture2DBase { + + public: + + D3D8Texture2D( + D3D8Device* pDevice, + Com&& 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(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; + class D3D8Texture3D final : public D3D8Texture3DBase { + + public: + + D3D8Texture3D( + D3D8Device* pDevice, + Com&& 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(pLockedBox), + reinterpret_cast(pBox), + Flags + ); + } + + HRESULT STDMETHODCALLTYPE UnlockBox(UINT Level) { + return GetD3D9()->UnlockBox(Level); + } + + HRESULT STDMETHODCALLTYPE AddDirtyBox(CONST D3DBOX* pDirtyBox) { + return GetD3D9()->AddDirtyBox(reinterpret_cast(pDirtyBox)); + } + + }; + + using D3D8TextureCubeBase = D3D8BaseTexture; + class D3D8TextureCube final : public D3D8TextureCubeBase { + + public: + + D3D8TextureCube( + D3D8Device* pDevice, + Com&& 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(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); + } + }; +} \ No newline at end of file diff --git a/src/d3d8/d3d8_volume.h b/src/d3d8/d3d8_volume.h new file mode 100644 index 00000000..7f3f29ea --- /dev/null +++ b/src/d3d8/d3d8_volume.h @@ -0,0 +1,40 @@ +#pragma once + +#include "d3d8_subresource.h" +#include "d3d8_d3d9_util.h" + +namespace dxvk { + + using D3D8VolumeBase = D3D8Subresource; + class D3D8Volume final : public D3D8VolumeBase { + + public: + + D3D8Volume( + D3D8Device* pDevice, + IDirect3DVolumeTexture8* pTexture, + Com&& 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(pLockedBox), + reinterpret_cast(pBox), + Flags + ); + } + + HRESULT STDMETHODCALLTYPE UnlockBox() final { + return GetD3D9()->UnlockBox(); + } + + }; + +} \ No newline at end of file diff --git a/src/d3d8/d3d8_wrapped_object.h b/src/d3d8/d3d8_wrapped_object.h new file mode 100644 index 00000000..e79fecf7 --- /dev/null +++ b/src/d3d8/d3d8_wrapped_object.h @@ -0,0 +1,68 @@ +#pragma once + +#include "d3d8_include.h" + +namespace dxvk { + + template + class D3D8WrappedObject : public ComObjectClamp { + + public: + + using D3D9 = D3D9Type; + using D3D8 = D3D8Type; + + D3D8WrappedObject(Com&& 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 + static D3D9* GetD3D9Nullable(Com& 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 m_d3d9; + + }; + +} \ No newline at end of file diff --git a/src/d3d8/meson.build b/src/d3d8/meson.build new file mode 100644 index 00000000..6a0e0bf5 --- /dev/null +++ b/src/d3d8/meson.build @@ -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 diff --git a/src/d3d8/version.rc b/src/d3d8/version.rc new file mode 100644 index 00000000..9e2b099f --- /dev/null +++ b/src/d3d8/version.rc @@ -0,0 +1,31 @@ +#include + +// 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 diff --git a/src/meson.build b/src/meson.build index 779d9d46..18d4e6fc 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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 diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp index 52b2c2ac..34d88b68 100644 --- a/src/util/config/config.cpp +++ b/src/util/config/config.cpp @@ -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" }, + }} }, }}; diff --git a/src/vulkan/vulkan_util.h b/src/vulkan/vulkan_util.h index 7fa064dd..588c9224 100644 --- a/src/vulkan/vulkan_util.h +++ b/src/vulkan/vulkan_util.h @@ -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(